From 1913cbbe0598fcd7178262f3b83034be1e1335a5 Mon Sep 17 00:00:00 2001 From: Rudolf Polzer Date: Mon, 26 May 2014 12:06:19 +0200 Subject: [PATCH] Anticheat improvements. --- qcsrc/server/anticheat.qc | 68 ++++++++++++++++++- qcsrc/server/anticheat.qh | 7 +- qcsrc/server/cl_player.qc | 10 +-- qcsrc/server/g_world.qc | 2 + .../mutators/mutator_spawn_near_teammate.qc | 4 +- qcsrc/server/playerstats.qc | 31 +++++++++ qcsrc/server/sv_main.qc | 2 + qcsrc/server/t_teleporters.qc | 6 ++ 8 files changed, 114 insertions(+), 16 deletions(-) diff --git a/qcsrc/server/anticheat.qc b/qcsrc/server/anticheat.qc index d00c60b09..99e23aabd 100644 --- a/qcsrc/server/anticheat.qc +++ b/qcsrc/server/anticheat.qc @@ -25,6 +25,8 @@ float mean_evaluate(entity e, .float a, .float c, float mean) #define MEAN_EVALUATE(prefix) mean_evaluate(self,prefix##_accumulator,prefix##_count,prefix##_mean) #define MEAN_DECLARE(prefix,m) float prefix##_mean = m; .float prefix##_count, prefix##_accumulator +.float anticheat_fixangle_endtime; + float anticheat_div0_evade_evasion_delta; .float anticheat_div0_evade_offset; .vector anticheat_div0_evade_v_angle; @@ -37,6 +39,13 @@ MEAN_DECLARE(anticheat_div0_strafebot_old, 5); .vector anticheat_div0_strafebot_forward_prev; MEAN_DECLARE(anticheat_div0_strafebot_new, 5); +// Snap-aim detection: we track the average angular speed of aiming over time, in "radians per second". +// Signal: a high-power mean. Cheaters will have high "signal" here. +// Noise: a low-power mean. Active/shivery players will have high "noise" here. +// Note one can always artificially add noise - so very high values of both signal and noise need to be checked too. +MEAN_DECLARE(anticheat_idle_snapaim_signal, 5); +MEAN_DECLARE(anticheat_idle_snapaim_noise, 1); + .float anticheat_speedhack_offset; .float anticheat_speedhack_movetime, anticheat_speedhack_movetime_count, anticheat_speedhack_movetime_frac; MEAN_DECLARE(anticheat_speedhack, 5); @@ -75,8 +84,27 @@ void anticheat_physics() MEAN_ACCUMULATE(anticheat_div0_strafebot_old, movement_oddity(self.movement, self.anticheat_div0_strafebot_movement_prev), 1); self.anticheat_div0_strafebot_movement_prev = self.movement; - if(vlen(self.anticheat_div0_strafebot_forward_prev)) - MEAN_ACCUMULATE(anticheat_div0_strafebot_new, 0.5 - 0.5 * (self.anticheat_div0_strafebot_forward_prev * v_forward), 1); + // Note: this actually tries to detect snap-aim. + if(vlen(self.anticheat_div0_strafebot_forward_prev) && time > self.anticheat_fixangle_endtime) { + float cosangle = self.anticheat_div0_strafebot_forward_prev * v_forward; + float angle = cosangle < -1 ? M_PI : cosangle > 1 ? 0 : acos(cosangle); + /* + if (angle >= 10 * M_PI / 180) + printf("SNAP %s: %f for %f, %f since fixangle\n", self.netname, angle * 180 / M_PI, cosangle, time - self.anticheat_fixangle_endtime); + */ + MEAN_ACCUMULATE(anticheat_div0_strafebot_new, angle / M_PI, 1); + + if (autocvar_slowmo > 0) { + // Technically this is a NOP, as the engine should be ensuring + // this in the first place. Let's guard against dividing by + // zero anyway. + float dt = max(0.001, frametime) / autocvar_slowmo; + + float anglespeed = angle / dt; + MEAN_ACCUMULATE(anticheat_idle_snapaim_signal, anglespeed, dt); + MEAN_ACCUMULATE(anticheat_idle_snapaim_noise, anglespeed, dt); + } + } self.anticheat_div0_strafebot_forward_prev = v_forward; // generic speedhack detection: correlate anticheat_speedhack_movetime (UPDATED BEFORE THIS) and server time @@ -168,15 +196,49 @@ void anticheat_report() { if(!autocvar_sv_eventlog) return; + // TODO(divVerent): Use xonstat to acquire good thresholds. GameLogEcho(strcat(":anticheat:_time:", ftos(self.playerid), ":", ftos(servertime - self.anticheat_jointime))); GameLogEcho(strcat(":anticheat:speedhack:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_speedhack), 240, 0.1, 0.15))); GameLogEcho(strcat(":anticheat:div0_strafebot_old:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_strafebot_old), 120, 0.3, 0.4))); GameLogEcho(strcat(":anticheat:div0_strafebot_new:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_strafebot_new), 120, 0.3, 0.4))); GameLogEcho(strcat(":anticheat:div0_evade:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_div0_evade), 120, 0.1, 0.2))); + GameLogEcho(strcat(":anticheat:idle_snapaim:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_signal) - MEAN_EVALUATE(anticheat_idle_snapaim_noise), 120, 0.1, 0.2))); + GameLogEcho(strcat(":anticheat:idle_snapaim_signal:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_signal), 120, 0.1, 0.2))); + GameLogEcho(strcat(":anticheat:idle_snapaim_noise:", ftos(self.playerid), ":", anticheat_display(MEAN_EVALUATE(anticheat_idle_snapaim_noise), 120, 0.1, 0.2))); +} + +float anticheat_getvalue(string id) +{ + switch(id) { + case "_time": return servertime - self.anticheat_jointime; + case "speedhack": return MEAN_EVALUATE(anticheat_speedhack); + case "div0_strafebot_old": return MEAN_EVALUATE(anticheat_div0_strafebot_old); + case "div0_strafebot_new": return MEAN_EVALUATE(anticheat_div0_strafebot_new); + case "div0_evade": return MEAN_EVALUATE(anticheat_div0_evade); + case "idle_snapaim": return MEAN_EVALUATE(anticheat_idle_snapaim_signal) - MEAN_EVALUATE(anticheat_idle_snapaim_noise); + case "idle_snapaim_signal": return MEAN_EVALUATE(anticheat_idle_snapaim_signal); + case "idle_snapaim_noise": return MEAN_EVALUATE(anticheat_idle_snapaim_noise); + } + return -1; +} + +void anticheat_startframe() +{ + anticheat_div0_evade_evasion_delta += frametime * (0.5 + random()); +} + +void anticheat_fixangle() +{ + self.anticheat_fixangle_endtime = servertime + ANTILAG_LATENCY(self) + 0.2; } -void anticheat_serverframe() +void anticheat_endframe() { + entity oldself = self; + FOR_EACH_CLIENT(self) + if (self.fixangle) + anticheat_fixangle(); + self = oldself; anticheat_div0_evade_evasion_delta += frametime * (0.5 + random()); } diff --git a/qcsrc/server/anticheat.qh b/qcsrc/server/anticheat.qh index 80c7636a0..e46dcce7b 100644 --- a/qcsrc/server/anticheat.qh +++ b/qcsrc/server/anticheat.qh @@ -6,4 +6,9 @@ void anticheat_physics(); void anticheat_spectatecopy(entity spectatee); void anticheat_prethink(); -void anticheat_serverframe(); +float anticheat_getvalue(string name); + +void anticheat_startframe(); +void anticheat_endframe(); + +void anticheat_fixangle(); diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index 67738c4c6..05a3b4544 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -673,15 +673,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht Portal_ClearAllLater(self); - if(IS_REAL_CLIENT(self)) - { - self.fixangle = TRUE; - //msg_entity = self; - //WriteByte (MSG_ONE, SVC_SETANGLE); - //WriteAngle (MSG_ONE, self.v_angle_x); - //WriteAngle (MSG_ONE, self.v_angle_y); - //WriteAngle (MSG_ONE, 80); - } + self.fixangle = TRUE; if(defer_ClientKill_Now_TeamChange) ClientKill_Now_TeamChange(); // can turn player into spectator diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 875a0c3e6..c779f9d4b 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -2730,6 +2730,8 @@ string GotoMap(string m) void EndFrame() { + anticheat_endframe(); + float altime; FOR_EACH_REALCLIENT(self) { diff --git a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc index 54df3a97c..e53c80f84 100644 --- a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc +++ b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc @@ -41,6 +41,7 @@ MUTATOR_HOOKFUNCTION(msnt_Spawn_Score) MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) { + // Note: when entering this, fixangle is already set. if(autocvar_g_spawn_near_teammate_ignore_spawnpoint) { if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death) @@ -112,7 +113,6 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) setorigin(self, trace_endpos); self.angles = team_mate.angles; self.angles_z = 0; // never spawn tilted even if the spot says to - self.fixangle = TRUE; // turn this way immediately team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; return 0; } @@ -130,7 +130,6 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) setorigin(self, best_spot); self.angles = best_mate.angles; self.angles_z = 0; // never spawn tilted even if the spot says to - self.fixangle = TRUE; // turn this way immediately best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; } } @@ -139,7 +138,6 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) self.angles = vectoangles(spawn_spot.msnt_lookat.origin - self.origin); self.angles_x = -self.angles_x; self.angles_z = 0; // never spawn tilted even if the spot says to - self.fixangle = TRUE; // turn this way immediately /* sprint(self, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n"); sprint(self, "distance: ", vtos(spawn_spot.msnt_lookat.origin - self.origin), "\n"); diff --git a/qcsrc/server/playerstats.qc b/qcsrc/server/playerstats.qc index 2c6e941a6..f43a6399d 100644 --- a/qcsrc/server/playerstats.qc +++ b/qcsrc/server/playerstats.qc @@ -5,6 +5,16 @@ string events_last; .float playerstats_addedglobalinfo; .string playerstats_id; +#define ALL_ANTICHEATS \ + ANTICHEAT("_time"); \ + ANTICHEAT("speedhack"); \ + ANTICHEAT("div0_strafebot_old"); \ + ANTICHEAT("div0_strafebot_new"); \ + ANTICHEAT("div0_evade"); \ + ANTICHEAT("idle_snapaim"); \ + ANTICHEAT("idle_snapaim_signal"); \ + ANTICHEAT("idle_snapaim_noise"); + void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly { string uri; @@ -44,6 +54,11 @@ void PlayerStats_Init() // initiated before InitGameplayMode so that scores are PlayerStats_AddEvent(strcat("acc-", w.netname, "-frags")); } +#define ANTICHEAT(name) \ + PlayerStats_AddEvent("anticheat-" name) + ALL_ANTICHEATS +#undef ANTICHEAT + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3); PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5); PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10); @@ -356,6 +371,18 @@ void PlayerStats_Accuracy(entity p) //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n")); } +void PlayerStats_Anticheat(entity p) +{ + entity oldself = self; + self = p; + +#define ANTICHEAT(name) \ + PlayerStats_Event(p, "anticheat-" name, anticheat_getvalue(name)) + ALL_ANTICHEATS +#undef ANTICHEAT + self = oldself; +} + void PlayerStats_AddGlobalInfo(entity p) { if(playerstats_db < 0) @@ -384,6 +411,8 @@ void PlayerStats_AddGlobalInfo(entity p) PlayerStats_Accuracy(p); + PlayerStats_Anticheat(p); + if(IS_REAL_CLIENT(p)) { if(p.latency_cnt) @@ -430,3 +459,5 @@ void PlayerStats_EndMatch(float finished) } } } + +#undef ALL_ANTICHEATS diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 920f738ae..73e733be8 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -238,6 +238,8 @@ void StartFrame (void) FOR_EACH_PLAYER(self) self.porto_forbidden = max(0, self.porto_forbidden - 1); + anticheat_startframe(); + MUTATOR_CALLHOOK(SV_StartFrame); } diff --git a/qcsrc/server/t_teleporters.qc b/qcsrc/server/t_teleporters.qc index 8f15a4f82..6e7c35b6a 100644 --- a/qcsrc/server/t_teleporters.qc +++ b/qcsrc/server/t_teleporters.qc @@ -338,6 +338,12 @@ void spawnfunc_trigger_teleport (void) void WarpZone_PostTeleportPlayer_Callback(entity pl) { UpdateCSQCProjectileAfterTeleport(pl); + { + entity oldself = self; + self = pl; + anticheat_fixangle(); + self = oldself; + } // "disown" projectiles after teleport if(pl.owner) if(pl.owner == pl.realowner) -- 2.39.2