/// <summary> /// Analyze kill and see if performed by a cheater /// </summary> /// <param name="hit">kill performed by the player</param> /// <returns>true if detection reached thresholds, false otherwise</returns> public IEnumerable <DetectionPenaltyResult> ProcessHit(EFClientKill hit) { var results = new List <DetectionPenaltyResult>(); if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 || // hack: prevents false positives (LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50)) { return(new[] { new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Any, } }); } LastWeapon = hit.Weapon; HitLocationCount[hit.HitLoc].Count++; HitCount++; if (hit.IsKill) { Kills++; } #region SNAP if (hit.AnglesList.Count == MIN_ANGLE_COUNT) { if (lastHit == null) { lastHit = hit; } bool areAnglesInvalid = hit.AnglesList[0].Equals(hit.AnglesList[1]) && hit.AnglesList[3].Equals(hit.AnglesList[4]); if ((lastHit == hit || lastHit.VictimId != hit.VictimId || (hit.TimeOffset - lastHit.TimeOffset) >= 1000) && !areAnglesInvalid) { ClientStats.SnapHitCount++; sessionSnapHits++; var currentSnapDistance = Vector3.SnapDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles); double previousAverage = ClientStats.AverageSnapValue; ClientStats.AverageSnapValue = (previousAverage * (ClientStats.SnapHitCount - 1) + currentSnapDistance) / ClientStats.SnapHitCount; double previousSessionAverage = sessionAverageSnapAmount; sessionAverageSnapAmount = (previousSessionAverage * (sessionSnapHits - 1) + currentSnapDistance) / sessionSnapHits; lastHit = hit; //var marginOfError = Thresholds.GetMarginOfError(sessionSnapHits); //var marginOfErrorLifetime = Thresholds.GetMarginOfError(ClientStats.SnapHitCount); if (sessionSnapHits >= Thresholds.MediumSampleMinKills && sessionAverageSnapAmount >= Thresholds.SnapFlagValue /* + marginOfError*/) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Flag, Value = sessionAverageSnapAmount, HitCount = sessionSnapHits, Type = DetectionType.Snap }); } if (sessionSnapHits >= Thresholds.MediumSampleMinKills && sessionAverageSnapAmount >= Thresholds.SnapBanValue /* + marginOfError*/) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = sessionAverageSnapAmount, HitCount = sessionSnapHits, Type = DetectionType.Snap }); } // lifetime if (ClientStats.SnapHitCount >= Thresholds.MediumSampleMinKills * 2 && ClientStats.AverageSnapValue >= Thresholds.SnapFlagValue /* + marginOfErrorLifetime*/) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Flag, Value = sessionAverageSnapAmount, HitCount = ClientStats.SnapHitCount, Type = DetectionType.Snap }); } if (ClientStats.SnapHitCount >= Thresholds.MediumSampleMinKills * 2 && ClientStats.AverageSnapValue >= Thresholds.SnapBanValue /* + marginOfErrorLifetime*/) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = sessionAverageSnapAmount, HitCount = ClientStats.SnapHitCount, Type = DetectionType.Snap }); } } } #endregion #region VIEWANGLES int totalUsableAngleCount = hit.AnglesList.Count - 1; int angleOffsetIndex = totalUsableAngleCount / 2; if (hit.AnglesList.Count == 5) { double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[angleOffsetIndex - 1], hit.AnglesList[angleOffsetIndex + 1], hit.ViewAngles); // LIFETIME var hitLoc = ClientStats.HitLocations .First(hl => hl.Location == hit.HitLoc); float previousAverage = hitLoc.HitOffsetAverage; double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount; hitLoc.HitOffsetAverage = (float)newAverage; int totalHits = ClientStats.HitLocations.Sum(_hit => _hit.HitCount); var weightedLifetimeAverage = ClientStats.HitLocations.Where(_hit => _hit.HitCount > 0) .Sum(_hit => _hit.HitOffsetAverage * _hit.HitCount) / totalHits; if (weightedLifetimeAverage > Thresholds.MaxOffset(totalHits) && hitLoc.HitCount > 100) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = hitLoc.HitOffsetAverage, HitCount = hitLoc.HitCount, Type = DetectionType.Offset }); } // SESSION var sessionHitLoc = HitLocationCount[hit.HitLoc]; sessionHitLoc.Offset = (sessionHitLoc.Offset * (sessionHitLoc.Count - 1) + realAgainstPredict) / sessionHitLoc.Count; int totalSessionHits = HitLocationCount.Sum(_hit => _hit.Value.Count); var weightedSessionAverage = HitLocationCount.Where(_hit => _hit.Value.Count > 0) .Sum(_hit => _hit.Value.Offset * _hit.Value.Count) / totalSessionHits; AngleDifferenceAverage = weightedSessionAverage; if (weightedSessionAverage > Thresholds.MaxOffset(totalSessionHits) && totalSessionHits >= (Thresholds.MediumSampleMinKills * 2)) { Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); Log.WriteDebug($"Session Average = {weightedSessionAverage}"); Log.WriteDebug($"HitCount = {HitCount}"); Log.WriteDebug($"ID = {hit.AttackerId}"); results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = weightedSessionAverage, HitCount = HitCount, Type = DetectionType.Offset, Location = hitLoc.Location }); } #if DEBUG Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); #endif } #endregion #region STRAIN double currentStrain = Strain.GetStrain(hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, LastOffset == 0 ? 50 : (hit.TimeOffset - LastOffset))); #if DEBUG == true Log.WriteDebug($"Current Strain: {currentStrain}"); #endif LastOffset = hit.TimeOffset; if (currentStrain > ClientStats.MaxStrain) { ClientStats.MaxStrain = currentStrain; } // flag if (currentStrain > Thresholds.MaxStrainFlag) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Flag, Value = currentStrain, HitCount = HitCount, Type = DetectionType.Strain }); } // ban if (currentStrain > Thresholds.MaxStrainBan && HitCount >= 5) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = currentStrain, HitCount = HitCount, Type = DetectionType.Strain }); } #endregion #region RECOIL float hitRecoilAverage = 0; bool shouldIgnoreDetection = false; try { shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Recoil] .Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)); } catch (KeyNotFoundException) { } if (!shouldIgnoreDetection) { validRecoilHitCount++; hitRecoilAverage = (hit.AnglesList.Sum(_angle => _angle.Z) + hit.ViewAngles.Z) / (hit.AnglesList.Count + 1); mapAverageRecoilAmount = (mapAverageRecoilAmount * (validRecoilHitCount - 1) + hitRecoilAverage) / validRecoilHitCount; if (validRecoilHitCount >= Thresholds.LowSampleMinKills && Kills > Thresholds.LowSampleMinKillsRecoil && mapAverageRecoilAmount == 0) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = mapAverageRecoilAmount, HitCount = HitCount, Type = DetectionType.Recoil }); } } #endregion #region BUTTON try { shouldIgnoreDetection = false; shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Button] .Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)); } catch (KeyNotFoundException) { } if (!shouldIgnoreDetection) { validButtonHitCount++; double lastDiff = hit.TimeOffset - hit.TimeSinceLastAttack; if (validButtonHitCount > 0 && lastDiff <= 0) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = lastDiff, HitCount = HitCount, Type = DetectionType.Button }); } } #endregion #region SESSION_RATIOS if (Kills >= Thresholds.LowSampleMinKills) { double marginOfError = Thresholds.GetMarginOfError(HitCount); // determine what the max headshot percentage can be for current number of kills double lerpAmount = Math.Min(1.0, (HitCount - Thresholds.LowSampleMinKills) / (double)(/*Thresholds.HighSampleMinKills*/ 60 - Thresholds.LowSampleMinKills)); double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError; double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.5), Thresholds.HeadshotRatioThresholdHighSample(3.5), lerpAmount) + marginOfError; // determine what the max bone percentage can be for current number of kills double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError; double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError; // calculate headshot ratio double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head].Count + HitLocationCount[IW4Info.HitLocation.helmet].Count + HitLocationCount[IW4Info.HitLocation.neck].Count) / (double)HitCount); // calculate maximum bone double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v.Count / (double)HitCount).Max()); var bone = HitLocationCount.FirstOrDefault(b => b.Value.Count == HitLocationCount.Values.Max(_hit => _hit.Count)).Key; #region HEADSHOT_RATIO // flag on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) { // ban on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForBan) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = currentHeadshotRatio, Location = IW4Info.HitLocation.head, HitCount = HitCount, Type = DetectionType.Bone }); } else { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Flag, Value = currentHeadshotRatio, Location = IW4Info.HitLocation.head, HitCount = HitCount, Type = DetectionType.Bone }); } } #endregion #region BONE_RATIO // flag on bone ratio else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag) { // ban on bone ratio if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = currentMaxBoneRatio, Location = bone, HitCount = HitCount, Type = DetectionType.Bone }); } else { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Flag, Value = currentMaxBoneRatio, Location = bone, HitCount = HitCount, Type = DetectionType.Bone }); } } #endregion } #region CHEST_ABDOMEN_RATIO_SESSION int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper].Count; try { shouldIgnoreDetection = false; // reset previous value shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[hit.GameName][DetectionType.Chest] .Any(_weaponRegex => Regex.IsMatch(hit.Weapon.ToString(), _weaponRegex)); } catch (KeyNotFoundException) { } if (chestHits >= Thresholds.MediumSampleMinKills && !shouldIgnoreDetection) { double marginOfError = Thresholds.GetMarginOfError(chestHits); double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); // determine max acceptable ratio of chest to abdomen kills double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(4), Thresholds.ChestAbdomenRatioThresholdHighSample(4), lerpAmount) + marginOfError; double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper].Count / (double)HitLocationCount[IW4Info.HitLocation.torso_lower].Count; if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) { if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills * 2) { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, Value = currentChestAbdomenRatio, Location = IW4Info.HitLocation.torso_upper, Type = DetectionType.Chest, HitCount = chestHits }); } else { results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Flag, Value = currentChestAbdomenRatio, Location = IW4Info.HitLocation.torso_upper, Type = DetectionType.Chest, HitCount = chestHits }); } } } #endregion #endregion var snapshot = new EFACSnapshot() { When = hit.When, ClientId = ClientStats.ClientId, SessionAngleOffset = AngleDifferenceAverage, RecoilOffset = hitRecoilAverage, CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalMinutes, CurrentStrain = currentStrain, CurrentViewAngle = new Vector3(hit.ViewAngles.X, hit.ViewAngles.Y, hit.ViewAngles.Z), Hits = HitCount, Kills = Kills, Deaths = ClientStats.SessionDeaths, //todo[9.1.19]: why does this cause unique failure? HitDestination = new Vector3(hit.DeathOrigin.X, hit.DeathOrigin.Y, hit.DeathOrigin.Z), HitOrigin = new Vector3(hit.KillOrigin.X, hit.KillOrigin.Y, hit.KillOrigin.Z), EloRating = ClientStats.EloRating, HitLocation = hit.HitLoc, LastStrainAngle = new Vector3(Strain.LastAngle.X, Strain.LastAngle.Y, Strain.LastAngle.Z), // this is in "meters" Distance = hit.Distance, SessionScore = ClientStats.SessionScore, HitType = hit.DeathType, SessionSPM = Math.Round(ClientStats.SessionSPM, 0), StrainAngleBetween = Strain.LastDistance, TimeSinceLastEvent = (int)Strain.LastDeltaTime, WeaponId = hit.Weapon, SessionSnapHits = sessionSnapHits, SessionAverageSnapValue = sessionAverageSnapAmount }; snapshot.PredictedViewAngles = hit.AnglesList .Select(_angle => new EFACSnapshotVector3() { Vector = _angle, Snapshot = snapshot }) .ToList(); Tracker.OnChange(snapshot); return(results); }
public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats) { int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount; if (totalChestKills >= 60) { double marginOfError = Thresholds.GetMarginOfError(totalChestKills); double lerpAmount = Math.Min(1.0, (totalChestKills - 60) / 250.0); // determine max acceptable ratio of chest to abdomen kills double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(2), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), lerpAmount) + marginOfError; double currentChestAbdomenRatio = totalChestKills / stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount; if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) { if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan) { Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {stats.ClientId}"); Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); var sb = new StringBuilder(); foreach (var location in stats.HitLocations) { sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); } Log.WriteDebug(sb.ToString()); // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, RatioAmount = currentChestAbdomenRatio, Bone = IW4Info.HitLocation.torso_upper, KillCount = totalChestKills }); } else { Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {stats.ClientId}"); Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var location in stats.HitLocations) { sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); } Log.WriteDebug(sb.ToString()); // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, RatioAmount = currentChestAbdomenRatio, Bone = IW4Info.HitLocation.torso_upper, KillCount = totalChestKills }); } } } return(new DetectionPenaltyResult() { Bone = IW4Info.HitLocation.none, ClientPenalty = Penalty.PenaltyType.Any }); }
/// <summary> /// Analyze kill and see if performed by a cheater /// </summary> /// <param name="kill">kill performed by the player</param> /// <returns>true if detection reached thresholds, false otherwise</returns> public DetectionPenaltyResult ProcessKill(EFClientKill kill) { if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || kill.HitLoc == IW4Info.HitLocation.none) { return new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, RatioAmount = 0 } } ; HitLocationCount[kill.HitLoc]++; Kills++; AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills; #region VIEWANGLES double distance = Vector3.Distance(kill.KillOrigin, kill.DeathOrigin); double x = kill.KillOrigin.X + distance * Math.Cos(kill.ViewAngles.X.ToRadians()) * Math.Cos(kill.ViewAngles.Y.ToRadians()); double y = kill.KillOrigin.Y + (distance * Math.Sin(kill.ViewAngles.X.ToRadians()) * Math.Cos(kill.ViewAngles.Y.ToRadians())); double z = kill.KillOrigin.Z + distance * Math.Sin((360.0f - kill.ViewAngles.Y).ToRadians()); var trueVector = Vector3.Subtract(kill.KillOrigin, kill.DeathOrigin); var calculatedVector = Vector3.Subtract(kill.KillOrigin, new Vector3((float)x, (float)y, (float)z)); double angle = trueVector.AngleBetween(calculatedVector); if (kill.AdsPercent > 0.5 && kill.Distance > 3) { var hitLoc = ClientStats.HitLocations .First(hl => hl.Location == kill.HitLoc); float previousAverage = hitLoc.HitOffsetAverage; double newAverage = (previousAverage * (hitLoc.HitCount - 1) + angle) / hitLoc.HitCount; hitLoc.HitOffsetAverage = (float)newAverage; if (double.IsNaN(hitLoc.HitOffsetAverage)) { Log.WriteWarning("[Detection::ProcessKill] HitOffsetAvgerage NaN"); Log.WriteDebug($"{previousAverage}-{hitLoc.HitCount}-{hitLoc}-{newAverage}"); hitLoc.HitOffsetAverage = 0f; } } #endregion #region SESSION_RATIOS if (Kills >= Thresholds.LowSampleMinKills) { double marginOfError = Thresholds.GetMarginOfError(Kills); // determine what the max headshot percentage can be for current number of kills double lerpAmount = Math.Min(1.0, (Kills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError; double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.0), Thresholds.HeadshotRatioThresholdHighSample(3.0), lerpAmount) + marginOfError; // determine what the max bone percentage can be for current number of kills double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError; double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError; // calculate headshot ratio double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills); // calculate maximum bone double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max()); var bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key; #region HEADSHOT_RATIO // flag on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) { // ban on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) { AboveThresholdCount++; Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, RatioAmount = currentHeadshotRatio, Bone = IW4Info.HitLocation.head, KillCount = Kills }); } else { AboveThresholdCount++; Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, RatioAmount = currentHeadshotRatio, Bone = IW4Info.HitLocation.head, KillCount = Kills }); } } #endregion #region BONE_RATIO // flag on bone ratio else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag) { // ban on bone ratio if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) { Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, RatioAmount = currentMaxBoneRatio, Bone = bone, KillCount = Kills }); } else { Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"**Kills: {Kills}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, RatioAmount = currentMaxBoneRatio, Bone = bone, KillCount = Kills }); } } #endregion } #region CHEST_ABDOMEN_RATIO_SESSION int chestKills = HitLocationCount[IW4Info.HitLocation.torso_upper]; if (chestKills >= Thresholds.MediumSampleMinKills) { double marginOfError = Thresholds.GetMarginOfError(chestKills); double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); // determine max acceptable ratio of chest to abdomen kills double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(4), Thresholds.ChestAbdomenRatioThresholdHighSample(4), lerpAmount) + marginOfError; double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower]; if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) { if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestKills >= Thresholds.MediumSampleMinKills + 30) { Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"**Chest Kills: {chestKills}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, RatioAmount = currentChestAbdomenRatio, Bone = 0, KillCount = chestKills }); } else { Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"**Chest Kills: {chestKills}"); Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, RatioAmount = currentChestAbdomenRatio, Bone = 0, KillCount = chestKills }); } } } #endregion #endregion return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, RatioAmount = 0 }); }
/// <summary> /// Analyze kill and see if performed by a cheater /// </summary> /// <param name="hit">kill performed by the player</param> /// <returns>true if detection reached thresholds, false otherwise</returns> public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage) { if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 || // hack: prevents false positives (LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50)) { return(new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, }); } DetectionPenaltyResult result = null; LastWeapon = hit.Weapon; HitLocationCount[hit.HitLoc]++; HitCount++; if (!isDamage) { Kills++; } #region VIEWANGLES if (hit.AnglesList.Count >= 2) { double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles); // LIFETIME var hitLoc = ClientStats.HitLocations .First(hl => hl.Location == hit.HitLoc); float previousAverage = hitLoc.HitOffsetAverage; double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount; hitLoc.HitOffsetAverage = (float)newAverage; if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) && hitLoc.HitCount > 100) { //Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***"); //Log.WriteDebug($"Lifetime Average = {newAverage}"); //Log.WriteDebug($"Bone = {hitLoc.Location}"); //Log.WriteDebug($"HitCount = {hitLoc.HitCount}"); //Log.WriteDebug($"ID = {hit.AttackerId}"); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = hitLoc.HitOffsetAverage, HitCount = hitLoc.HitCount, Type = DetectionType.Offset }; } // SESSION double sessAverage = (AngleDifferenceAverage * (HitCount - 1) + realAgainstPredict) / HitCount; AngleDifferenceAverage = sessAverage; if (sessAverage > Thresholds.MaxOffset(HitCount) && HitCount > 30) { //Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); //Log.WriteDebug($"Session Average = {sessAverage}"); //Log.WriteDebug($"HitCount = {HitCount}"); //Log.WriteDebug($"ID = {hit.AttackerId}"); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = sessAverage, HitCount = HitCount, Type = DetectionType.Offset, Location = hitLoc.Location }; } #if DEBUG Log.WriteDebug($"PredictVsReal={realAgainstPredict}"); #endif } double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset)); #if DEBUG == true Log.WriteDebug($"Current Strain: {currentStrain}"); #endif LastOffset = hit.TimeOffset; if (currentStrain > ClientStats.MaxStrain) { ClientStats.MaxStrain = currentStrain; } // flag if (currentStrain > Thresholds.MaxStrainFlag) { result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentStrain, HitCount = HitCount, Type = DetectionType.Strain }; } // ban if (currentStrain > Thresholds.MaxStrainBan && HitCount >= 5) { result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentStrain, HitCount = HitCount, Type = DetectionType.Strain }; } #endregion #region SESSION_RATIOS if (Kills >= Thresholds.LowSampleMinKills) { double marginOfError = Thresholds.GetMarginOfError(HitCount); // determine what the max headshot percentage can be for current number of kills double lerpAmount = Math.Min(1.0, (HitCount - Thresholds.LowSampleMinKills) / (double)(/*Thresholds.HighSampleMinKills*/ 60 - Thresholds.LowSampleMinKills)); double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError; double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.5), Thresholds.HeadshotRatioThresholdHighSample(3.5), lerpAmount) + marginOfError; // determine what the max bone percentage can be for current number of kills double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError; double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError; // calculate headshot ratio double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet] + HitLocationCount[IW4Info.HitLocation.neck]) / (double)HitCount); // calculate maximum bone double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)HitCount).Max()); var bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key; #region HEADSHOT_RATIO // flag on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) { // ban on headshot if (currentHeadshotRatio > maxHeadshotLerpValueForBan) { Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {hit.AttackerId}"); Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentHeadshotRatio, Location = IW4Info.HitLocation.head, HitCount = HitCount, Type = DetectionType.Bone }; } else { Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {hit.AttackerId}"); Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentHeadshotRatio, Location = IW4Info.HitLocation.head, HitCount = HitCount, Type = DetectionType.Bone }; } } #endregion #region BONE_RATIO // flag on bone ratio else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag) { // ban on bone ratio if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) { Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); Log.WriteDebug($"ClientId: {hit.AttackerId}"); Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentMaxBoneRatio, Location = bone, HitCount = HitCount, Type = DetectionType.Bone }; } else { Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); Log.WriteDebug($"ClientId: {hit.AttackerId}"); Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); var sb = new StringBuilder(); foreach (var kvp in HitLocationCount) { sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); } Log.WriteDebug(sb.ToString()); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentMaxBoneRatio, Location = bone, HitCount = HitCount, Type = DetectionType.Bone }; } } #endregion } #region CHEST_ABDOMEN_RATIO_SESSION int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper]; if (chestHits >= Thresholds.MediumSampleMinKills) { double marginOfError = Thresholds.GetMarginOfError(chestHits); double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); // determine max acceptable ratio of chest to abdomen kills double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError; double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(4), Thresholds.ChestAbdomenRatioThresholdHighSample(4), lerpAmount) + marginOfError; double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower]; if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) { if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30) { //Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**"); //Log.WriteDebug($"ClientId: {hit.AttackerId}"); //Log.WriteDebug($"**Chest Hits: {chestHits}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); //var sb = new StringBuilder(); //foreach (var kvp in HitLocationCount) // sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); //Log.WriteDebug(sb.ToString()); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Ban, Value = currentChestAbdomenRatio, Location = IW4Info.HitLocation.torso_upper, Type = DetectionType.Chest, HitCount = chestHits }; } else { //Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); //Log.WriteDebug($"ClientId: {hit.AttackerId}"); //Log.WriteDebug($"**Chest Hits: {chestHits}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); //var sb = new StringBuilder(); //foreach (var kvp in HitLocationCount) // sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); //Log.WriteDebug(sb.ToString()); result = new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Flag, Value = currentChestAbdomenRatio, Location = IW4Info.HitLocation.torso_upper, Type = DetectionType.Chest, HitCount = chestHits }; } } } #endregion #endregion Tracker.OnChange(new EFACSnapshot() { When = hit.When, ClientId = ClientStats.ClientId, SessionAngleOffset = AngleDifferenceAverage, CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds, CurrentStrain = currentStrain, CurrentViewAngle = hit.ViewAngles, Hits = HitCount, Kills = Kills, Deaths = ClientStats.SessionDeaths, HitDestinationId = hit.DeathOrigin.Vector3Id, HitDestination = hit.DeathOrigin, HitOriginId = hit.KillOrigin.Vector3Id, HitOrigin = hit.KillOrigin, EloRating = ClientStats.EloRating, HitLocation = hit.HitLoc, LastStrainAngle = Strain.LastAngle, PredictedViewAngles = hit.AnglesList, // this is in "meters" Distance = hit.Distance, SessionScore = ClientStats.SessionScore, HitType = hit.DeathType, SessionSPM = ClientStats.SessionSPM, StrainAngleBetween = Strain.LastDistance, TimeSinceLastEvent = (int)Strain.LastDeltaTime, WeaponId = hit.Weapon }); return(result ?? new DetectionPenaltyResult() { ClientPenalty = Penalty.PenaltyType.Any, }); }