From da06f014b2d7c17f05528338ef0615d0e46d0248 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 11:19:22 -0500 Subject: [PATCH 01/16] Changes to stac.sp Only slight changes occurred to this file. Some of these will make more sense as I make more commits. --- scripting/stac.sp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripting/stac.sp b/scripting/stac.sp index 0806b96c..759cd7ff 100755 --- a/scripting/stac.sp +++ b/scripting/stac.sp @@ -15,6 +15,7 @@ #include #include #include +#include #undef REQUIRE_PLUGIN #tryinclude #tryinclude @@ -27,7 +28,7 @@ #pragma semicolon 1 #pragma newdecls required -#define PLUGIN_VERSION "5.4.3" +#define PLUGIN_VERSION "5.4.4" #define UPDATE_URL "https://raw.githubusercontent.com/sapphonie/StAC-tf2/master/updatefile.txt" @@ -70,6 +71,7 @@ public Plugin myinfo = public void OnPluginStart() { StacLog("\n\n----> StAC version [%s] loaded\n", PLUGIN_VERSION); + CreateConVar("stac_version", PLUGIN_VERSION, "StAC version", (FCVAR_DONTRECORD)); // check if tf2, unload if not if (GetEngineVersion() != Engine_TF2) { From 803d66f0bf0c46a8e8155fd5d5979840ee696226 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 11:46:19 -0500 Subject: [PATCH 02/16] More changes to stac.sp ... that I forgot to put in the last commit! --- scripting/stac.sp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripting/stac.sp b/scripting/stac.sp index 759cd7ff..f96ec259 100755 --- a/scripting/stac.sp +++ b/scripting/stac.sp @@ -95,12 +95,12 @@ public void OnPluginStart() } // reg admin commands - // TODO: make these invisible for non admins RegConsoleCmd("sm_stac_checkall", checkAdmin, "Force check all client convars (ALL CLIENTS) for anticheat stuff"); RegConsoleCmd("sm_stac_detections", checkAdmin, "Show all current detections on all connected clients"); + RegConsoleCmd("sm_stac_version", checkAdmin, "Prints the current active version of StAC."); RegConsoleCmd("sm_stac_getauth", checkAdmin, "Print StAC's cached auth for a client"); RegConsoleCmd("sm_stac_livefeed", checkAdmin, "Show live feed (debug info etc) for a client. This gets printed to SourceTV if available."); - + RegConsoleCmd("sm_stac_printos", checkAdmin, "Shows operating system of each connected client"); // setup regex - "Recording to ".*"" demonameRegex = CompileRegex("Recording to \".*\""); @@ -163,6 +163,8 @@ public void OnPluginStart() // jaypatch OnPluginStart_jaypatch(); + AddCommandListener(joinTeam, "jointeam"); + AddCommandListener(joinClass, "joinclass"); } public void OnPluginEnd() From 18cfa828c6b304de130b87ee6401422c7a7c293d Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 11:51:13 -0500 Subject: [PATCH 03/16] stac_client changes :) --- scripting/stac/stac_client.sp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripting/stac/stac_client.sp b/scripting/stac/stac_client.sp index f1c3520b..6e900c66 100644 --- a/scripting/stac/stac_client.sp +++ b/scripting/stac/stac_client.sp @@ -6,6 +6,8 @@ public void OnClientPutInServer(int Cl) { int userid = GetClientUserId(Cl); + + timeSinceJoined[Cl] = GetEngineTime(); // Store time since joined for auto-class/team join checks if (IsValidClientOrBot(Cl)) { @@ -289,6 +291,8 @@ void ClearClBasedVars(int userid) fakeAngDetects [Cl] = 0; aimsnapDetects [Cl] = -1; // ignore first detect, it's prolly bunk pSilentDetects [Cl] = -1; // ignore first detect, it's prolly bunk + invalidWishVelDetects [Cl] = -1; // ignore first detect, it's prolly bunk + unsyncMoveDetects [Cl] = 0; bhopDetects [Cl] = -1; // set to -1 to ignore single jumps cmdnumSpikeDetects [Cl] = 0; tbotDetects [Cl] = -1; // ignore first detect, it's prolly bunk @@ -309,8 +313,16 @@ void ClearClBasedVars(int userid) playerTaunting [Cl] = false; playerInBadCond [Cl] = 0; userBanQueued [Cl] = false; + clientOS [Cl] = 2; + teamChecked [Cl] = false; + classChecked [Cl] = false; + printedOnce [Cl] = false; // STORED SENS PER CLIENT sensFor [Cl] = 0.0; + // STORED JOYSTICK SHIT + joystick [Cl] = false; + joy_xcon [Cl] = false; + waitTillNextQuery [Cl] = true; // don't bother clearing arrays LiveFeedOn [Cl] = false; @@ -329,6 +341,8 @@ void ClearClBasedVars(int userid) // has client has waited 60 seconds for their first cvar check hasWaitedForCvarCheck [Cl] = false; + joystickQueried [Cl] = false; + joy_xconQueried [Cl] = false; } /********** TIMER FOR NETINFO **********/ From 4fea5d7a3be5031aa2d149051942947d81ce976a Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:00:46 -0500 Subject: [PATCH 04/16] Changes to stac_commands --- scripting/stac/stac_commands.sp | 75 +++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/scripting/stac/stac_commands.sp b/scripting/stac/stac_commands.sp index 49beb819..18e16382 100755 --- a/scripting/stac/stac_commands.sp +++ b/scripting/stac/stac_commands.sp @@ -11,20 +11,21 @@ Action checkAdmin(int callingCl, int args) if (callingCl != 0) { - bool isAdmin; - AdminId clAdmin = GetUserAdmin(callingCl); - if (GetAdminFlag(clAdmin, Admin_Ban)) + if (IsValidAdmin(callingCl)) { - isAdmin = true; + if (GetClientCount(true) >= 1 && !DEBUG) + { + ReplyToCommand(callingCl, "[StAC] Only one player is on. Most checks are logging only and cvar checking doesn't occur."); + } } - if (!isAdmin) + else { PrintToImportant("{hotpink}[StAC]{white} Client %N attempted to use %s, blocked access." , callingCl, arg0); StacLogSteam(GetClientUserId(callingCl)); StacGeneralPlayerNotify(GetClientUserId(callingCl), "Client %N attempted to use %s, blocked access!", callingCl, arg0); - return Plugin_Handled; + return Plugin_Continue; // Return this instead. This causes non-admins to get an "Unknown Command" message, further disguising the anticheat. } - StacGeneralPlayerNotify(GetClientUserId(callingCl), "Admin %N used %s", callingCl, arg0); + //OracxGeneralPlayerNotify(GetClientUserId(callingCl), "Admin %N used %s", callingCl, arg0); // Why should we notify for this? } if (StrEqual(arg0, "sm_stac_checkall")) @@ -38,6 +39,12 @@ Action checkAdmin(int callingCl, int args) ShowAllDetections(callingCl); return Plugin_Handled; } + + if (StrEqual(arg0, "sm_stac_version")) + { + ShowVersion(callingCl); + return Plugin_Handled; + } if (StrEqual(arg0, "sm_stac_getauth")) { @@ -59,6 +66,11 @@ Action checkAdmin(int callingCl, int args) StacTargetCommand(callingCl, arg0, arg1); return Plugin_Handled; } + if (StrEqual(arg0, "sm_stac_printos")) + { + ShowAllOS(callingCl); + return Plugin_Handled; + } return Plugin_Handled; } @@ -92,6 +104,8 @@ void ShowAllDetections(int callingCl) || cmdnumSpikeDetects [Cl] > 0 || tbotDetects [Cl] > 0 || userinfoSpamDetects [Cl] > 0 + || invalidWishVelDetects [Cl] > 0 + || unsyncMoveDetects [Cl] > 0 ) { PrintToConsole @@ -105,6 +119,8 @@ void ShowAllDetections(int callingCl) \n pSilent %i\ \n Cmdnum spikes %i\ \n Triggerbots %i\ + \n Invalid wish velocity %i\ + \n Unsynchronized movement %i\ \n", Cl, turnTimes [Cl], @@ -112,7 +128,9 @@ void ShowAllDetections(int callingCl) aimsnapDetects [Cl], pSilentDetects [Cl], cmdnumSpikeDetects [Cl], - tbotDetects [Cl] + tbotDetects [Cl], + invalidWishVelDetects [Cl], + unsyncMoveDetects [Cl] ); } } @@ -122,6 +140,47 @@ void ShowAllDetections(int callingCl) return; } +// sm_stac_version +void ShowVersion(int callingCl) +{ + ReplyToCommand(callingCl, "StAC version [%s]", PLUGIN_VERSION); + return; +} + +// sm_stac_printos +void ShowAllOS(int callingCl) +{ + if (callingCl != 0) + { + ReplyToCommand(callingCl, "[StAC] Check your console!"); + } + PrintToConsole(callingCl, "[StAC] === Printing client operating systems ==="); + for (int Cl = 1; Cl <= MaxClients; Cl++) + { + if (IsValidClient(Cl)) + { + switch(clientOS[Cl]) + { + case 0: + { + PrintToConsole(callingCl, "\n%L: Windows/Wine", Cl); + } + case 1: + { + PrintToConsole(callingCl, "\n%L: Linux/MacOS", Cl); + } + default: + { + PrintToConsole(callingCl, "\n%L: Unknown, probably hasn't been queried yet.", Cl); + } + } + } + } + PrintToConsole(callingCl, "[StAC] === Done ==="); + + return; +} + // sm_stac_getauth // sm_stac_livefeed void StacTargetCommand(int callingCl, const char[] arg0, const char[] arg1) From e6eaa2ad352ca1bde4a2b99d5f0efe6df8a2c2ea Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:06:19 -0500 Subject: [PATCH 05/16] Changes to stac_cvar_checks --- scripting/stac/stac_cvar_checks.sp | 48 ++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/scripting/stac/stac_cvar_checks.sp b/scripting/stac/stac_cvar_checks.sp index 705c3a93..3c594d19 100644 --- a/scripting/stac/stac_cvar_checks.sp +++ b/scripting/stac/stac_cvar_checks.sp @@ -30,6 +30,13 @@ char miscVars[][] = "r_portalsopenall", // must be == 1.0 "host_timescale", + // Exists on Linux only + "dxa_nullrefresh_capslock", + // Refresh this every once in a while + // (Supposedly) using the controller + "joystick", + // Controller plugged in + "joy_xcontroller_found" // sv_force_transmit_ents ? // sv_suppress_viewpunch ? // tf_showspeed ? @@ -37,8 +44,9 @@ char miscVars[][] = }; // DEFINITE cheat vars get appended to this array. -// Every cheat except cathook is smart enough to not have queryable cvars. +// Every cheat except fedoraware (dead) is smart enough to not have queryable cvars. // For now. +// Cathook fixed their cvars being queryable but I'll keep the query in for now. char cheatVars[][] = { // lith @@ -51,6 +59,7 @@ char cheatVars[][] = // ncc doesn't have any that i can find lol // cathook "cat_load", + "crash" // ...melancholy? maybe? lol // "caramelldansen", // "SetCursor", @@ -267,6 +276,35 @@ public void ConVarCheck(QueryCookie cookie, int Cl, ConVarQueryResult result, co } } + // dxa_nullrefresh_capslock + else if (StrEqual(cvarName, "dxa_nullrefresh_capslock")) + { + if (result == ConVarQuery_NotFound) + { + clientOS[Cl] = 0; + } + else + { + clientOS[Cl] = 1; + } + } + + // joystick + else if (StrEqual(cvarName, "joystick")) + { + bool joy = (0.0 <= StringToFloat(cvarValue) < 1.0)?false:true; + joystick[Cl] = joy; + joystickQueried[Cl] = true; + } + // joy_xcontroller_found + else if (StrEqual(cvarName, "joy_xcontroller_found")) + { + bool joy = (0.0 <= StringToFloat(cvarValue) < 1.0)?false:true; + joy_xcon[Cl] = joy; + joy_xconQueried[Cl] = true; + return; + } + /* cheat program only cvars */ @@ -279,7 +317,7 @@ public void ConVarCheck(QueryCookie cookie, int Cl, ConVarQueryResult result, co } } // log something about cvar errors - else if (result != ConVarQuery_Okay && !IsCheatOnlyVar(cvarName)) + else if (result != ConVarQuery_Okay && !IsCheatOnlyVar(cvarName) && !StrEqual(cvarName, "dxa_nullrefresh_capslock")) { PrintToImportant("{hotpink}[StAC]{white} Could not query cvar %s on Player %N", cvarName, Cl); StacLog("Could not query cvar %s on player %L", cvarName, Cl); @@ -441,6 +479,12 @@ Action Timer_CheckClientConVars(Handle timer, int userid) // query all cvars and netprops for userid void QueryCvarsEtc(int userid, int i) { + // No point in running this if only one player is on. + if (GetClientCount(true) == 1 && !DEBUG) + { + return; + } + // get client index of userid int Cl = GetClientOfUserId(userid); // don't go no further if client isn't valid! From 9c58957d86856be9281b42674e3e0c7374b9ac9f Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:24:19 -0500 Subject: [PATCH 06/16] Changes to stac_cvars --- scripting/stac/stac_cvars.sp | 126 ++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 24 deletions(-) diff --git a/scripting/stac/stac_cvars.sp b/scripting/stac/stac_cvars.sp index 9dc40a90..b22fa527 100755 --- a/scripting/stac/stac_cvars.sp +++ b/scripting/stac/stac_cvars.sp @@ -64,7 +64,41 @@ void initCvars() false, _ ); - HookConVarChange(stac_verbose_info, setStacVars); + HookConVarChange(stac_ban_duration, setStacVars); + + // min time before ban queue fires + FloatToString(minTimeBeforeBan, buffer, sizeof(buffer)); + stac_min_time_before_ban = + AutoExecConfig_CreateConVar + ( + "stac_min_time_before_ban", + buffer, + "[StAC] delay ban AT LEAST this long in seconds after a detection\n\ + (recommended 15)", + FCVAR_NONE, + true, + 1.0, + false, + _ + ); + HookConVarChange(stac_min_time_before_ban, setStacVars); + + // max time before ban queue fires + FloatToString(maxTimeBeforeBan, buffer, sizeof(buffer)); + stac_max_time_before_ban = + AutoExecConfig_CreateConVar + ( + "stac_max_time_before_ban", + buffer, + "[StAC] delay ban AT MOST this long in seconds after a detection\n\ + (recommended 60)", + FCVAR_NONE, + true, + 2.0, + false, + _ + ); + HookConVarChange(stac_max_time_before_ban, setStacVars); // turn seconds FloatToString(maxAllowedTurnSecs, buffer, sizeof(buffer)); @@ -168,6 +202,42 @@ void initCvars() ); HookConVarChange(stac_max_psilent_detections, setStacVars); + // invalid wish velocity detections + IntToString(maxInvalidWishVelDetections, buffer, sizeof(buffer)); + stac_max_invalid_wish_vel_detections = + AutoExecConfig_CreateConVar + ( + "stac_max_invalid_wish_vel_detections", + buffer, + "[StAC] maximum invalid wish velocity detections on a client before they get banned.\n\ + -1 to disable checking for invalid wish velocity (saves cpu), 0 to print to admins/stv but never ban\n\ + (recommended 10 or higher)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_max_invalid_wish_vel_detections, setStacVars); + + // unsynchronized move detections + IntToString(maxUnsyncMoveDetections, buffer, sizeof(buffer)); + stac_max_unsync_move_detections = + AutoExecConfig_CreateConVar + ( + "stac_max_unsync_move_detections", + buffer, + "[StAC] maximum unsynchronized movement detections on a client before they get banned.\n\ + -1 to disable checking for unsynchronized movement (saves cpu), 0 to print to admins/stv but never ban\n\ + (recommended 10)", + FCVAR_NONE, + true, + -1.0, + false, + _ + ); + HookConVarChange(stac_max_unsync_move_detections, setStacVars); + // bhop detections IntToString(maxBhopDetections, buffer, sizeof(buffer)); stac_max_bhop_detections = @@ -497,87 +567,95 @@ void setStacVars(ConVar convar, const char[] oldValue, const char[] newValue) SetFailState("[StAC] stac_enabled is set to 0 - aborting!"); } - // ban duration var - banDuration = GetConVarInt(stac_ban_duration); + // ban vars + banDuration = GetConVarInt(stac_ban_duration); + minTimeBeforeBan = GetConVarFloat(stac_min_time_before_ban); + maxTimeBeforeBan = GetConVarFloat(stac_max_time_before_ban); // verbose info var - DEBUG = GetConVarBool(stac_verbose_info); + DEBUG = GetConVarBool(stac_verbose_info); // turn seconds var - maxAllowedTurnSecs = GetConVarFloat(stac_max_allowed_turn_secs); + maxAllowedTurnSecs = GetConVarFloat(stac_max_allowed_turn_secs); if (maxAllowedTurnSecs < 0.0 && maxAllowedTurnSecs != -1.0) { maxAllowedTurnSecs = 0.0; } // misccheats - banForMiscCheats = GetConVarBool(stac_ban_for_misccheats); + banForMiscCheats = GetConVarBool(stac_ban_for_misccheats); // optimizecvars - optimizeCvars = GetConVarBool(stac_optimize_cvars); + optimizeCvars = GetConVarBool(stac_optimize_cvars); if (optimizeCvars) { RunOptimizeCvars(); } // aimsnap var - maxAimsnapDetections = GetConVarInt(stac_max_aimsnap_detections); + maxAimsnapDetections = GetConVarInt(stac_max_aimsnap_detections); // psilent var - maxPsilentDetections = GetConVarInt(stac_max_psilent_detections); + maxPsilentDetections = GetConVarInt(stac_max_psilent_detections); + + // invalid wish vel var + maxInvalidWishVelDetections = GetConVarInt(stac_max_invalid_wish_vel_detections); + + // unsync move var + maxUnsyncMoveDetections = GetConVarInt(stac_max_unsync_move_detections); // bhop var - maxBhopDetections = GetConVarInt(stac_max_bhop_detections); + maxBhopDetections = GetConVarInt(stac_max_bhop_detections); // fakeang var - maxFakeAngDetections = GetConVarInt(stac_max_fakeang_detections); + maxFakeAngDetections = GetConVarInt(stac_max_fakeang_detections); // cmdnum spikes var - maxCmdnumDetections = GetConVarInt(stac_max_cmdnum_detections); + maxCmdnumDetections = GetConVarInt(stac_max_cmdnum_detections); // tbot var - maxTbotDetections = GetConVarInt(stac_max_tbot_detections); + maxTbotDetections = GetConVarInt(stac_max_tbot_detections); // max ping reduce detections - clamp to -1 if 0 - maxuserinfoSpamDetections = GetConVarInt(stac_max_cmdrate_spam_detections); + maxuserinfoSpamDetections = GetConVarInt(stac_max_cmdrate_spam_detections); // minterp var - clamp to -1 if 0 - min_interp_ms = GetConVarInt(stac_min_interp_ms); + min_interp_ms = GetConVarInt(stac_min_interp_ms); if (min_interp_ms == 0) { min_interp_ms = -1; } // maxterp var - clamp to -1 if 0 - max_interp_ms = GetConVarInt(stac_max_interp_ms); + max_interp_ms = GetConVarInt(stac_max_interp_ms); if (max_interp_ms == 0) { max_interp_ms = -1; } // min check sec var - minRandCheckVal = GetConVarFloat(stac_min_randomcheck_secs); + minRandCheckVal = GetConVarFloat(stac_min_randomcheck_secs); // max check sec var - maxRandCheckVal = GetConVarFloat(stac_max_randomcheck_secs); + maxRandCheckVal = GetConVarFloat(stac_max_randomcheck_secs); // log to file - logtofile = GetConVarBool(stac_log_to_file); + logtofile = GetConVarBool(stac_log_to_file); // properly fix pingmasking - fixpingmasking = GetConVarBool(stac_fixpingmasking_enabled); + fixpingmasking = GetConVarBool(stac_fixpingmasking_enabled); // kick unauthed clients - kickUnauth = GetConVarBool(stac_kick_unauthed_clients); + kickUnauth = GetConVarBool(stac_kick_unauthed_clients); // silent mode - silent = GetConVarInt(stac_silent); + silent = 0; // This should always be 0. Setting this to anything else is BAD. Comment and I can change it if you disagree, otherwise, do it properly. // max conns from same ip - maxip = GetConVarInt(stac_max_connections_from_ip); + maxip = GetConVarInt(stac_max_connections_from_ip); // should stac work with sv_cheats or not - ignore_sv_cheats = GetConVarBool(stac_work_with_sv_cheats); + ignore_sv_cheats = GetConVarBool(stac_work_with_sv_cheats); } public void GenericCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue) From d14f90a8a5c131f7d7ba6d2b532bacac82eb8b35 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:31:30 -0500 Subject: [PATCH 07/16] Changes to stac_globals --- scripting/stac/stac_globals.sp | 44 ++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/scripting/stac/stac_globals.sp b/scripting/stac/stac_globals.sp index 12695700..55ed5e2f 100644 --- a/scripting/stac/stac_globals.sp +++ b/scripting/stac/stac_globals.sp @@ -9,12 +9,16 @@ /***** Cvar Handles *****/ ConVar stac_enabled; ConVar stac_ban_duration; +ConVar stac_min_time_before_ban; +ConVar stac_max_time_before_ban; ConVar stac_verbose_info; ConVar stac_max_allowed_turn_secs; ConVar stac_ban_for_misccheats; ConVar stac_optimize_cvars; ConVar stac_max_aimsnap_detections; ConVar stac_max_psilent_detections; +ConVar stac_max_invalid_wish_vel_detections; +ConVar stac_max_unsync_move_detections; ConVar stac_max_bhop_detections; ConVar stac_max_fakeang_detections; ConVar stac_max_cmdnum_detections; @@ -33,8 +37,10 @@ ConVar stac_max_connections_from_ip; ConVar stac_work_with_sv_cheats; /***** Misc cheat defaults *****/ -// ban duration +// ban duration & banqueue times int banDuration = 0; +float minTimeBeforeBan = 15.0; +float maxTimeBeforeBan = 60.0; // verbose mode bool DEBUG = false; // interp @@ -61,6 +67,8 @@ bool ignore_sv_cheats = false; int maxAimsnapDetections = 20; int maxPsilentDetections = 10; int maxFakeAngDetections = 5; +int maxInvalidWishVelDetections = 10; // Not sure what these values should be by default since they have never been widely rolled out. +int maxUnsyncMoveDetections = 10; // ^ int maxBhopDetections = 10; int maxCmdnumDetections = 20; int maxTbotDetections = 0; @@ -118,6 +126,8 @@ int turnTimes [TFMAXPLAYERS+1]; int fakeAngDetects [TFMAXPLAYERS+1]; int aimsnapDetects [TFMAXPLAYERS+1] = {-1, ...}; // set to -1 to ignore first detections, as theyre most likely junk int pSilentDetects [TFMAXPLAYERS+1] = {-1, ...}; // ^ +int invalidWishVelDetects [TFMAXPLAYERS+1] = {-1, ...}; // ^ +int unsyncMoveDetects [TFMAXPLAYERS+1]; int bhopDetects [TFMAXPLAYERS+1] = {-1, ...}; // set to -1 to ignore single jumps int cmdnumSpikeDetects [TFMAXPLAYERS+1]; int tbotDetects [TFMAXPLAYERS+1] = {-1, ...}; @@ -147,20 +157,33 @@ int clmouse [TFMAXPLAYERS+1] [2]; float engineTime [TFMAXPLAYERS+1][3]; float fuzzyClangles [TFMAXPLAYERS+1][5][2]; float clpos [TFMAXPLAYERS+1][2][3]; +bool joystickQueried [TFMAXPLAYERS+1] = {false, ...}; // Not sure whether or not it's necessary to set any of these. +bool joy_xconQueried [TFMAXPLAYERS+1] = {false, ...}; +bool joystick [TFMAXPLAYERS+1] = {false, ...}; +bool joy_xcon [TFMAXPLAYERS+1] = {false, ...}; +bool printedOnce [TFMAXPLAYERS+1]; +bool waitTillNextQuery [TFMAXPLAYERS+1] = {true, ...}; // Misc stuff per client [ client index ][char size] char SteamAuthFor [TFMAXPLAYERS+1][64]; -bool highGrav [TFMAXPLAYERS+1]; -bool playerTaunting [TFMAXPLAYERS+1]; -int playerInBadCond [TFMAXPLAYERS+1]; -bool userBanQueued [TFMAXPLAYERS+1]; -float sensFor [TFMAXPLAYERS+1]; +bool highGrav [TFMAXPLAYERS+1]; +bool playerTaunting [TFMAXPLAYERS+1]; +int playerInBadCond [TFMAXPLAYERS+1]; +int clientOS [TFMAXPLAYERS+1] = {2, ...}; +bool teamChecked [TFMAXPLAYERS+1]; +bool classChecked [TFMAXPLAYERS+1]; +bool userBanQueued [TFMAXPLAYERS+1]; +float timeSinceJoined [TFMAXPLAYERS+1]; +float timeSinceJointeam [TFMAXPLAYERS+1]; +float timeSinceJoinclass [TFMAXPLAYERS+1]; +bool userBanQueued [TFMAXPLAYERS+1]; +float sensFor [TFMAXPLAYERS+1]; // weapon name, gets passed to aimsnap check -char hurtWeapon [TFMAXPLAYERS+1][256]; -char lastCommandFor [TFMAXPLAYERS+1][256]; -bool LiveFeedOn [TFMAXPLAYERS+1]; -bool hasBadName [TFMAXPLAYERS+1]; +char hurtWeapon [TFMAXPLAYERS+1][256]; +char lastCommandFor [TFMAXPLAYERS+1][256]; +bool LiveFeedOn [TFMAXPLAYERS+1]; +bool hasBadName [TFMAXPLAYERS+1]; // network info float lossFor [TFMAXPLAYERS+1]; @@ -189,6 +212,7 @@ Handle HudSyncNetwork; // Timer handles Handle QueryTimer [TFMAXPLAYERS+1]; +Handle BanTimer [TFMAXPLAYERS+1]; Handle TriggerTimedStuffTimer; /* From 06c5c9fc89a7e1efd107c049828f21e1c4635785 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:34:22 -0500 Subject: [PATCH 08/16] Changes to stac_mapchange --- scripting/stac/stac_mapchange.sp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripting/stac/stac_mapchange.sp b/scripting/stac/stac_mapchange.sp index 26ce5ced..bfd6339a 100644 --- a/scripting/stac/stac_mapchange.sp +++ b/scripting/stac/stac_mapchange.sp @@ -10,6 +10,7 @@ public void OnConfigsExecuted() public void OnMapStart() { + checkForBadPlugins(); // Not sure if this is the best place for this. OpenStacLog(); ActuallySetRandomSeed(); DoTPSMath(); @@ -25,6 +26,11 @@ public void OnMapStart() GetConVarString(FindConVar("hostname"), hostname, sizeof(hostname)); } +public void OnAllPluginsLoaded() +{ + checkForBadPlugins(); // Also not sure on this. +} + public Action eRoundStart(Handle event, char[] name, bool dontBroadcast) { DoTPSMath(); @@ -229,3 +235,23 @@ void DoTPSMath() StacLog("tickinterv %f, tps %f", tickinterv, tps); } } + +void checkForBadPlugins() +{ + // Make sure we're not using the original adminhelp. + if (FindPluginByFile("adminhelp.smx") != INVALID_HANDLE) + { + char message[] = "StAC failed to load because a possibly unmodified \"adminhelp\" was found."; + PrintToImportant(message); + SendMessageToDiscord(message); + SetFailState("Unmodified(?) adminhelp copy is still present (Unexpected adminhelp.smx). Delete it from the folder, use \"sm plugins unload adminhelp\", and reload StAC."); + } + // Make sure we're not using the original SBP. + if (FindPluginByFile("sbp.smx") != INVALID_HANDLE) + { + char message[] = "StAC failed to load because a possibly unmodified \"sbp\" was found."; + PrintToImportant(message); + SendMessageToDiscord(message); + SetFailState("Unmodified(?) sbp copy is still present (Unexpected sbp.smx). Delete it from the folder, use \"sm plugins unload sbp\", and reload StAC."); + } +} From c442953f86e731f3579af7074ec4045830697ab9 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:36:58 -0500 Subject: [PATCH 09/16] Changes to stac_misc_checks --- scripting/stac/stac_misc_checks.sp | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/scripting/stac/stac_misc_checks.sp b/scripting/stac/stac_misc_checks.sp index 664b5633..d6eaac87 100644 --- a/scripting/stac/stac_misc_checks.sp +++ b/scripting/stac/stac_misc_checks.sp @@ -37,6 +37,44 @@ public Action OnClientSayCommand(int Cl, const char[] command, const char[] sArg return Plugin_Continue; } +Action joinTeam(int Cl, const char[] command, int argc) +{ + if (!teamChecked[Cl]) + { + char ClName[64]; + int userid = GetClientUserId(Cl); + GetClientName(Cl, ClName, sizeof(ClName)); + timeSinceJointeam[Cl] = GetEngineTime(); + if (timeSinceJointeam[Cl] - timeSinceJoined[Cl] < 2.5) + { + PrintToImportant("Suspicious: %.2f seconds between %s fully joined and chose team", timeSinceJointeam[Cl] - timeSinceJoined[Cl], ClName); + StacGeneralPlayerNotify(userid, "Suspicious: %.2f seconds between %s fully joined and chose team", timeSinceJointeam[Cl] - timeSinceJoined[Cl], ClName); + } + teamChecked[Cl] = true; + return Plugin_Continue; + } + return Plugin_Continue; +} + +Action joinClass(int Cl, const char[] command, int argc) +{ + if (!classChecked[Cl]) + { + char ClName[64]; + int userid = GetClientUserId(Cl); + GetClientName(Cl, ClName, sizeof(ClName)); + timeSinceJoinclass[Cl] = GetEngineTime(); + if (timeSinceJoinclass[Cl] - timeSinceJoined[Cl] < 2.5) // This value may need to be tweaked. + { + PrintToImportant("Suspicious: %.2f seconds between %s fully joined and chose class", timeSinceJoinclass[Cl] - timeSinceJoined[Cl], ClName); + StacGeneralPlayerNotify(userid, "Suspicious: %.2f seconds between %s fully joined and chose class", timeSinceJoinclass[Cl] - timeSinceJoined[Cl], ClName); + } + classChecked[Cl] = true; + return Plugin_Continue; + } + return Plugin_Continue; +} + void NameCheck(int userid) { int Cl = GetClientOfUserId(userid); From 6c66e55f08e0416d6ab53b7dd7d313d16c2819bd Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:12:31 -0500 Subject: [PATCH 10/16] Changes to stac_onplayerruncmd I don't know how to handle copyright. A lot of the code in here isn't mine, or is based off of another person's code. This was never meant to be a public project, but I'm doing this to contribute to the "greater good". Credits to shavit and Rusty. --- scripting/stac/stac_onplayerruncmd.sp | 228 +++++++++++++++++++++++++- 1 file changed, 221 insertions(+), 7 deletions(-) diff --git a/scripting/stac/stac_onplayerruncmd.sp b/scripting/stac/stac_onplayerruncmd.sp index 3e00058a..7b5457cb 100755 --- a/scripting/stac/stac_onplayerruncmd.sp +++ b/scripting/stac/stac_onplayerruncmd.sp @@ -9,6 +9,9 @@ - AIM SNAPS - FAKE ANGLES - TURN BINDS + - BHOP CHEATS + - INVALID WISH VELOCITY (can and will false positive on +strafe (left alt key by default)) + - UNSYNCHRONIZED MOVEMENT (will false positive if cl_yawspeed = 0 and client uses turnbinds (+right, +left) and +strafe.) */ public void OnPlayerRunCmdPre @@ -47,10 +50,13 @@ public Action OnPlayerRunCmd OnPlayerRunCmd_jaypatch(Cl, buttons, impulse, vel, angles, weapon, subtype, cmdnum, tickcount, seed, mouse); // sanity check, don't let banned clients do anything! + // This lets them know something is up immediately, removing the point of the banqueue. + /* if (userBanQueued[Cl]) { return Plugin_Handled; } + */ return Plugin_Continue; } @@ -133,7 +139,7 @@ stock void PlayerRunCmd } clcmdnum[Cl][0] = cmdnum; - // grab tickccount + // grab tickcount for (int i = 5; i > 0; --i) { cltickcount[Cl][i] = cltickcount[Cl][i-1]; @@ -226,10 +232,10 @@ stock void PlayerRunCmd if ( // make sure client doesn't have invalid angles. "invalid" in this case means "any angle is 0.000000", usually caused by plugin / trigger based teleportation - !HasValidAngles(Cl) + !HasValidAngles(Cl) // This is actively abused to bypass, according to untrustworthy sources! But, I do see it as very possible. // make sure client doesn't have OUTRAGEOUS ping // most cheater fakeping goes up to 800 so tack on 50 just in case - || pingFor[Cl] > 850.0 + || pingFor[Cl] > 850.0 // I really don't think this is necessary. The other lag checks should prevent any fucky stuff from happening, but what do I know. ) { return; @@ -244,8 +250,8 @@ stock void PlayerRunCmd if ( // make sure client isnt using a spin bind - buttons & IN_LEFT - || buttons & IN_RIGHT + buttons & IN_LEFT // Almost certainly an easy bypass. + || buttons & IN_RIGHT // ^ // make sure we're not lagging and that cmdnum is saneish || IsUserLagging(userid, true, false) ) @@ -256,12 +262,186 @@ stock void PlayerRunCmd aimsnapCheck(userid); triggerbotCheck(userid); psilentCheck(userid); + invalidWishVelCheck(userid, vel[0], vel[1], buttons); // In theory, this doesn't need to be down here. I'm only worried about halloween conditions causing issues for this. + unsynchronizedMoveCheck(userid, buttons, vel[0], vel[1]); return; } +/* + INVALID WISH VELOCITY CHECK + Thanks to Oryx and the devs behind it for this check. + Actually implemented it into the anticheat properly this time! + * Copyright (C) 2018 Nolan O. + * Copyright (C) 2018 shavit. +*/ +void invalidWishVelCheck(int userid, float forwardmove, float sidemove, int buttons) +{ + int Cl = GetClientOfUserId(userid); + + int attack = 0; + if ((clbuttons[Cl][0] & IN_ATTACK)) + { + attack = 1; + } + else if ((clbuttons[Cl][0] & IN_ATTACK2)) + { + attack = 2; + } + + if + ( + ( + // The absolute value of forwardmove and sidemove should NEVER be greater than 450 (or 450.00003 for some reason...) + ( + FloatAbs(forwardmove) > 450.00003 + || + FloatAbs(sidemove) > 450.00003 + /* + Reasoning for the sidemove/forwardmove value of 450.00003: + Essentially, some legit clients report a value of 450.0000305175 (even happened to my client), which IS greater than 450.0. Why does this happen? No clue. + I just wanted to add a bit of leniency. Honestly, this isn't enough to cause any issues. + This could also be caused by +strafe, but who knows. + */ + ) + ) + && + ( + clangles[Cl][1][1] != clangles[Cl][0][1] // (somewhat) Workaround false positives occuring while holding +strafe. Technically may open up room for a bypass, but I have found no other solution. + ) + ) + { + invalidWishVelDetects[Cl]++; + if (invalidWishVelDetects[Cl] > 0) // First detect is ignored. + { + PrintToImportant + ( + "{hotpink}[StAC]{white} Player %N {mediumpurple}has invalid wish velocity{white}!\ + \nThis player is *probably* cheating, especially if they trigger this more than once.\ + \nDetections so far: {palegreen}%i", + Cl, + invalidWishVelDetects[Cl] + ); + PrintToImportant + ( + "{white}Forwardmove: {palegreen}%.10f, {white}Sidemove: {palegreen}%.10f", + forwardmove, + sidemove + ); + if (attack > 0) + { + PrintToImportant + ( + "{hotpink}[StAC]{red} This player was ATTACKING (ATTACK%i), \ + you should ban them manually with the reason \"Banned from server\"", + attack + ); + } + StacLogSteam(userid); + if (invalidWishVelDetects[Cl] == 1 || invalidWishVelDetects[Cl] % 5 == 0) + { + char invalidWishVelInfo[128]; + if (attack > 0) + { + Format + ( + invalidWishVelInfo, + sizeof(invalidWishVelInfo), + "invalid wish velocity WHILE ATTACKING (attack%i, forwardmove: %.10f, sidemove %.10f)", + attack, + forwardmove, + sidemove + ); + } + else + { + Format + ( + invalidWishVelInfo, + sizeof(invalidWishVelInfo), + "invalid wish velocity (forwardmove: %.10f, sidemove %.10f)", + forwardmove, + sidemove + ); + } + StacDetectionNotify(userid, invalidWishVelInfo, invalidWishVelDetects[Cl]); + } + if (invalidWishVelDetects[Cl] >= maxInvalidWishVelDetections && maxInvalidWishVelDetections > 0) + { + char reason[128]; + Format(reason, sizeof(reason), "%t", "invalidWishVelBanMsg", invalidWishVelDetects[Cl]); + char pubreason[256]; + Format(pubreason, sizeof(pubreason), "%t", "invalidWishVelBanAllChat", Cl, invalidWishVelDetects[Cl]); + BanUser(userid, reason, pubreason); + } + } + } +} + +/* + UNSYNCHRONIZED MOVEMENT CHECK -- Catches autostrafers + Thanks to Oryx and the devs behind it for this check. + * Copyright (C) 2018 Nolan O. + * Copyright (C) 2018 shavit. +*/ +// If it was my choice, I would ban controller users and cheaters all the same since they all trigger this. Plus, controller players are annoying. +void unsynchronizedMoveCheck(int userid, int buttons, float forwardmove, float sidemove) +{ + int Cl = GetClientOfUserId(userid); + if (playerUsingController(userid)) + { + return; + } + + if + ( + // don't bother checking if unsync'd move detection is off + maxUnsyncMoveDetections != -1 + && + // Unsynchronized usercmd->forwardmove or usercmd->sidemove. + // cl_forwardspeed and cl_sidespeed are the fully-pressed move values. + // The game will never apply them unless the buttons are added into the usercmd too. (exceptions: controllers) + // https://mxr.alliedmods.net/hl2sdk-css/source/game/client/in_main.cpp#557 + // https://mxr.alliedmods.net/hl2sdk-css/source/game/client/in_main.cpp#842 + ( + (forwardmove == 450.0 && (buttons & IN_FORWARD) == 0) || // Check for unsynchronized forwards movement + (sidemove == -450.0 && (buttons & IN_MOVELEFT) == 0) || // Check for unsynchronized sideways (left) movement + (forwardmove == -450.0 && (buttons & IN_BACK) == 0) || // Check for unsynchronized backwards movement + (sidemove == 450.0 && (buttons & IN_MOVERIGHT) == 0) // Check for unsynchronized sideways (right) movement + ) + ) + { + unsyncMoveDetects[Cl]++; + PrintToImportant + ( + "{hotpink}[StAC]{white} Player %N {mediumpurple}had unsynchronized movement{white}!\ + \nConsecutive detections so far: {palegreen}%i", + Cl, + unsyncMoveDetects[Cl] + ); + PrintToImportant("{hotpink}[StAC]{red} Hey, since this doesn't ban automatically yet (testing mode), you should ban them manually with the reason \"Banned from server\". This goes for any detection, but especially this one."); + StacLogSteam(userid); + + if (unsyncMoveDetects[Cl] == 1 || unsyncMoveDetects[Cl] % 5 == 0) + { + char unsyncMovInfo[128]; + Format(unsyncMovInfo, sizeof(unsyncMovInfo), "unsynchronized movement"); + StacDetectionNotify(userid, unsyncMovInfo, unsyncMoveDetects[Cl]); + } + if (unsyncMoveDetects[Cl] >= maxUnsyncMoveDetections && maxUnsyncMoveDetections > 0) + { + char reason[128]; + Format(reason, sizeof(reason), "%t", "unsynchronizedMoveBanMsg", unsyncMoveDetects[Cl]); + char pubreason[256]; + Format(pubreason, sizeof(pubreason), "%t", "unsynchronizedMoveBanAllChat", Cl, unsyncMoveDetects[Cl]); + BanUser(userid, reason, pubreason); + } + } +} + /* BHOP DETECTION - using lilac and ssac as reference, this one's better tho + There is a better way to do this without notifying the client that they have been bhop checked. (Can be abused to detect StAC running on the server) Look at Oryx/Bash2. */ void bhopCheck(int userid) { @@ -337,7 +517,7 @@ void bhopCheck(int userid) Format(reason, sizeof(reason), "%t", "bhopBanMsg", bhopDetects[Cl]); char pubreason[256]; Format(pubreason, sizeof(pubreason), "%t", "bhopBanAllChat", Cl, bhopDetects[Cl]); - BanUser(userid, reason, pubreason); + BanUserOriginal(userid, reason, pubreason); // We use the original version of this function because you can tell when you've triggered it, therefore leading to discovery of a ban queue system. return; } @@ -621,7 +801,8 @@ void psilentCheck(int userid) // doing this might make it harder to detect legitcheaters but like. legitcheating in a 12 yr old dead game OMEGALUL who fucking cares if ( - aDiffReal >= 1.0 && fuzzy >= 0 + //aDiffReal >= 0.5 && fuzzy >= 0 // I care about legitcheating in a 12 yr old "dead game". I want a fair game. If a client triggers this consistently, especially at a consistent angle value, they are almost certainly cheating. The anticheat handles false positives already. + fuzzy >= 0 ) { pSilentDetects[Cl]++; @@ -651,6 +832,8 @@ void psilentCheck(int userid) } if (pSilentDetects[Cl] % 5 == 0) { + char pSilentInfo[128]; + Format(pSilentInfo, sizeof(pSilentInfo), "psilent (anglediff: %.2f°, fuzzy: %s, norecoil: %s)", aDiffReal, fuzzy == 1 ? "yes" : "no", aDiffReal <= 3.0 ? "yes" : "no"); StacDetectionNotify(userid, "psilent", pSilentDetects[Cl]); } // BAN USER if they trigger too many detections @@ -1049,6 +1232,37 @@ bool isTickcountRepeated(int userid) return false; } +// I would like to check if the player has controller-like movements someday, because if this goes open source, people will force these cvars to avoid detection. +bool playerUsingController(int userid) +{ + int Cl = GetClientOfUserId(userid); + // If we haven't queried these cvars from this client, we don't know if the player is using a controller or not yet. + if (!joystickQueried[Cl] || !joy_xconQueried[Cl]) + { + return true; + } + // If the player has joystick set to 1 (enables controller) and has joy_xcon set to 1 (controller is plugged in), they are using a controller. + if (joystick[Cl] && joy_xcon[Cl]) + { + if (!printedOnce[Cl]) + { + PrintToImportant("{hotpink}[StAC]{white} Player %N {powderblue}appears to be using a controller{white}!", Cl); + StacGeneralPlayerNotify(userid, "Client appears to be using a controller"); + printedOnce[Cl] = true; + } + return true; + } + // Make sure we don't detect on people who plug their controller in mid-game. This will make any checks calling this take longer to kick in, but prevent false positives. + if (waitTillNextQuery[Cl]) + { + waitTillNextQuery[Cl] = false; + joystickQueried[Cl] = false; + joy_xconQueried[Cl] = false; + return true; + } + return false; +} + /********** DETECTION FORGIVENESS TIMERS **********/ Action Timer_decr_aimsnaps(Handle timer, any userid) From e42c8fc8d94fa21891a596043619b748db346307 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:26:55 -0500 Subject: [PATCH 11/16] Changes to stac_stocks Alright, here this goes. The developer of StAC is working on making the anticheat invisible... But what's the point if cheaters can find out that it's running on the server just by getting banned or seeing someone else get banned? They can just come back and bypass the anticheat on an alt account if that happens. So, ban messages should be as simple as possible ("Banned from server"), and all other players on the server should just see that as well. If they have questions or want to know why, just tell them that the player who was banned was cheating. The only people who should have any idea of what is going on with the anticheat are any admins, moderators, or anyone who can be trusted. Regular players should not ever know that the anticheat is running on the server, or at the very least, shouldn't know what anticheat is running on the server or what detections there are. --- scripting/stac/stac_stocks.sp | 166 ++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 30 deletions(-) diff --git a/scripting/stac/stac_stocks.sp b/scripting/stac/stac_stocks.sp index 52270658..a63fd4c8 100644 --- a/scripting/stac/stac_stocks.sp +++ b/scripting/stac/stac_stocks.sp @@ -381,18 +381,84 @@ bool IsValidSrcTV(int client) /********** MISC FUNCS **********/ -void BanUser(int userid, char[] reason, char[] pubreason) +// Thanks to Mitchell on AlliedModders for this. +stock int FindClientBySteamID(char[] SteamID) +{ + char steamid[64]; + for (int Cl = 1; Cl <= MaxClients; Cl++) + { + if (IsClientInGame(Cl)) + { + GetClientAuthId(Cl, AuthId_Steam2, steamid, sizeof(steamid)); + if (StrEqual(steamid, SteamID, false)) + { + return Cl; + } + } + } + return -1; +} + +Action Timer_BanAfterTime(Handle timer, DataPack pack) +{ + int userid; + int Cl; + char reason[128]; + char pubreason[256]; + char bannedID[TFMAXPLAYERS+1][64]; + char steamid[64]; + // We probably want to set this to something so that if Cl == 0, we don't error out on the strcmp because there is nothing stored in the string. + steamid = "STEAM_1:1:00000000"; + + pack.Reset(); + userid = pack.ReadCell(); + Cl = GetClientOfUserId(userid); + pack.ReadString(reason, sizeof(reason)); + pack.ReadString(pubreason, sizeof(pubreason)); + pack.ReadString(bannedID[Cl], sizeof(bannedID)); + + if (Cl != 0) + { + GetClientAuthId(Cl, AuthId_Steam2, steamid, sizeof(steamid)); // Get the client's SteamID + } + + if (strcmp(steamid, bannedID[Cl]) == 0) // Make sure we're not banning the wrong client. + { + BanUserOriginal(userid, reason, pubreason); // Why wasn't I doing this before? Lol. + } + else + { + int bannedClient = FindClientBySteamID(bannedID[Cl]); + if (bannedClient == -1) + { + BanIdentity(bannedID[Cl], 0, BANFLAG_AUTHID, "Banned from server"); // User isn't connected to the server. Add to banlist. + } + else + { + int newuserid = GetClientUserId(bannedClient); + BanUserOriginal(newuserid, reason, pubreason); // Client is connected to the server. Ban and kick them. + } + } + return Plugin_Continue; +} + +/* + We need this still. +*/ +void BanUserOriginal(int userid, char[] reason, char[] pubreason) { int Cl = GetClientOfUserId(userid); // prevent double bans + /* if (userBanQueued[Cl]) { - KickClient(Cl, "Banned by StAC"); + KickClient(Cl, "Banned from server"); return; - } + } + */ - StacGeneralPlayerNotify(userid, reason); + // OracxGeneralPlayerNotify(userid, reason); // make sure we dont detect on already banned players userBanQueued[Cl] = true; @@ -418,22 +484,22 @@ void BanUser(int userid, char[] reason, char[] pubreason) { if (SOURCEBANS) { - SBPP_BanPlayer(0, Cl, banDuration, reason); + SBPP_BanPlayer(0, Cl, banDuration, "Banned from server"); // there's no return value for that native, so we have to just assume it worked lol return; } - if (MATERIALADMIN && MABanPlayer(0, Cl, MA_BAN_STEAM, banDuration, reason)) + if (MATERIALADMIN && MABanPlayer(0, Cl, MA_BAN_STEAM, banDuration, "Banned from server")) { return; } if (GBANS) { - ServerCommand("gb_ban %i, %i, %s", userid, banDuration, reason); + ServerCommand("gb_ban %i, %i, %s", userid, banDuration, "Banned from server"); // there's no return value nor a native for gbans bans (YET), so we have to just assume it worked lol return; } // stock tf2, no ext ban system. if we somehow fail here, keep going. - if (BanClient(Cl, banDuration, BANFLAG_AUTO, reason, reason, _, _)) + if (BanClient(Cl, banDuration, BANFLAG_AUTO, reason, "Banned from server", _, _)) { return; } @@ -443,8 +509,8 @@ void BanUser(int userid, char[] reason, char[] pubreason) // if this returns true, we can still ban the client with their steamid in a roundabout and annoying way. if (!IsActuallyNullString(SteamAuthFor[Cl])) { - ServerCommand("sm_addban %i \"%s\" %s", banDuration, SteamAuthFor[Cl], reason); - KickClient(Cl, "%s", reason); + ServerCommand("sm_addban %i \"%s\" %s", banDuration, SteamAuthFor[Cl], "Banned from server"); + KickClient(Cl, "%s", "Banned from server"); } // if the above returns false, we can only do ip :/ else @@ -453,12 +519,13 @@ void BanUser(int userid, char[] reason, char[] pubreason) GetClientIP(Cl, ip, sizeof(ip)); StacLog("No cached SteamID for %N! Banning with IP %s...", Cl, ip); - ServerCommand("sm_banip %s %i %s", ip, banDuration, reason); + ServerCommand("sm_banip %s %i %s", ip, banDuration, "Banned from server"); // this kick client might not be needed - you get kicked by "being added to ban list" // KickClient(Cl, "%s", reason); } - MC_PrintToChatAll("%s", pubreason); + // MC_PrintToChatAll("%s", pubreason); This still isn't happening. Why should we notify all clients WHY they were banned? Bad idea. + PrintToImportant("%s", pubreason); // This may be redundant. Testing needed. StacLog("%s", pubreason); } @@ -707,16 +774,17 @@ void StacGeneralPlayerNotify(int userid, const char[] format, any ...) static char generalTemplate[2048] = \ "{ \"embeds\": \ - [{ \"title\": \"StAC Detection!\", \"color\": 16738740, \"fields\":\ + [{ \"title\": \"StAC Notification!\", \"color\": 16738740, \"fields\":\ [\ - { \"name\": \"Player\", \"value\": \"%N\" } ,\ - { \"name\": \"SteamID\", \"value\": \"%s\" } ,\ - { \"name\": \"Message\", \"value\": \"%s\" } ,\ - { \"name\": \"Hostname\", \"value\": \"%s\" } ,\ - { \"name\": \"Server IP\", \"value\": \"%s\" } ,\ - { \"name\": \"Current Demo\", \"value\": \"%s\" } ,\ - { \"name\": \"Demo Tick\", \"value\": \"%i\" } ,\ - { \"name\": \"Unix timestamp\", \"value\": \"%i\" } \ + { \"name\": \"Player\", \"value\": \"%N\" } ,\ + { \"name\": \"SteamID\", \"value\": \"%s\" } ,\ + { \"name\": \"Operating System\", \"value\": \"%s\" } ,\ + { \"name\": \"Message\", \"value\": \"%s\" } ,\ + { \"name\": \"Hostname\", \"value\": \"%s\" } ,\ + { \"name\": \"Server IP\", \"value\": \"%s\" } ,\ + { \"name\": \"Current Demo\", \"value\": \"%s\" } ,\ + { \"name\": \"Demo Tick\", \"value\": \"%i\" } ,\ + { \"name\": \"Unix timestamp\", \"value\": \"%i\" } \ ]\ }],\ \"avatar_url\": \"https://i.imgur.com/RKRaLPl.png\"\ @@ -745,6 +813,24 @@ void StacGeneralPlayerNotify(int userid, const char[] format, any ...) { steamid = "N/A"; } + + char OS[36]; // 34 bytes (largest outcome) + 1 (string read end byte) + 1 (to make it an even number lol) Note: I don't know anything about how to handle arrays! + switch(clientOS[Cl]) + { + case 0: + { + Format(OS, sizeof(OS), "Windows/Wine"); + } + case 1: + { + Format(OS, sizeof(OS), "Linux/MacOS (Most likely Cathook)"); + } + default: + { + Format(OS, sizeof(OS), "Not queried yet or blocked by user"); + } + } + Format ( msg, @@ -752,6 +838,7 @@ void StacGeneralPlayerNotify(int userid, const char[] format, any ...) generalTemplate, Cl, steamid, + OS, message, hostname, hostipandport, @@ -776,15 +863,16 @@ void StacDetectionNotify(int userid, char[] type, int detections) "{ \"embeds\": \ [{ \"title\": \"StAC Detection!\", \"color\": 16738740, \"fields\":\ [\ - { \"name\": \"Player\", \"value\": \"%N\" } ,\ - { \"name\": \"SteamID\", \"value\": \"%s\" } ,\ - { \"name\": \"Detection type\", \"value\": \"%s\" } ,\ - { \"name\": \"Detection\", \"value\": \"%i\" } ,\ - { \"name\": \"Hostname\", \"value\": \"%s\" } ,\ - { \"name\": \"Server IP\", \"value\": \"%s\" } ,\ - { \"name\": \"Current Demo\", \"value\": \"%s\" } ,\ - { \"name\": \"Demo Tick\", \"value\": \"%i\" } ,\ - { \"name\": \"Unix timestamp\", \"value\": \"%i\" } \ + { \"name\": \"Player\", \"value\": \"%N\" } ,\ + { \"name\": \"SteamID\", \"value\": \"%s\" } ,\ + { \"name\": \"Operating System\", \"value\": \"%s\" } ,\ + { \"name\": \"Detection type\", \"value\": \"%s\" } ,\ + { \"name\": \"Detection\", \"value\": \"%i\" } ,\ + { \"name\": \"Hostname\", \"value\": \"%s\" } ,\ + { \"name\": \"Server IP\", \"value\": \"%s\" } ,\ + { \"name\": \"Current Demo\", \"value\": \"%s\" } ,\ + { \"name\": \"Demo Tick\", \"value\": \"%i\" } ,\ + { \"name\": \"Unix timestamp\", \"value\": \"%i\" } \ ]\ }],\ \"avatar_url\": \"https://i.imgur.com/RKRaLPl.png\"\ @@ -810,6 +898,23 @@ void StacDetectionNotify(int userid, char[] type, int detections) { steamid = "N/A"; } + + char OS[36]; // 34 bytes (largest outcome) + 1 (string read end byte) + 1 (to make it an even number lol) Note: I don't know anything about how to handle arrays! + switch(clientOS[Cl]) + { + case 0: + { + Format(OS, sizeof(OS), "Windows/Wine"); + } + case 1: + { + Format(OS, sizeof(OS), "Linux/MacOS (Most likely Cathook)"); + } + default: + { + Format(OS, sizeof(OS), "Not queried yet or blocked by user"); + } + } Format ( @@ -818,6 +923,7 @@ void StacDetectionNotify(int userid, char[] type, int detections) detectionTemplate, Cl, steamid, + OS, type, detections, hostname, From 209c9079efe98050f36c3549e01c0f8861d28d26 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:29:50 -0500 Subject: [PATCH 12/16] Changes to stac.phrases A lot of these will go unused, I never did feel like removing the ones that are unused. Feel free to! --- translations/stac.phrases.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/translations/stac.phrases.txt b/translations/stac.phrases.txt index 9e8e80db..7c90c8db 100755 --- a/translations/stac.phrases.txt +++ b/translations/stac.phrases.txt @@ -366,6 +366,26 @@ "es" "[StAC] Baneado por valores de logros falsos" "de" "[StAC] Gebannt für falsche Achievements" } + "invalidWishVelBanAllChat" + { + "#format" "{1:N},{2:i}" + "en" "{hotpink}[StAC]{white} Player {1} had {mediumpurple}invalid wish velocity{white} using a {mediumpurple}cheat program{white}. Total invalid wish velocities: {mediumpurple}{2}{white}. {palegreen}Queued for ban!" + } + "invalidWishVelBanMsg" + { + "#format" "{1:i}" + "en" "[StAC] Banned for invalid wish velocity after {1} detections" + } + "unsynchronizedMoveBanAllChat" + { + "#format" "{1:N},{2:i}" + "en" "{hotpink}[StAC]{white} Player {1} had {mediumpurple}unsynchronized movement{white}. Total unsynchronized movements: {mediumpurple}{2}{white}. {palegreen}Queued for ban!" + } + "unsynchronizedMoveBanMsg" + { + "#format" "{1:i}" + "en" "[StAC] Banned for unsynchronized movement after {1} detections" + } // newlines in chat "newlineBanAllChat" { From 5c0949a8a5bcfeb8d9d2760f363ed1f4ad2360c2 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:30:40 -0500 Subject: [PATCH 13/16] Create sbp.games.txt Required for my modification to SBP. --- gamedata/sbp.games.txt | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 gamedata/sbp.games.txt diff --git a/gamedata/sbp.games.txt b/gamedata/sbp.games.txt new file mode 100644 index 00000000..7c2d16c4 --- /dev/null +++ b/gamedata/sbp.games.txt @@ -0,0 +1,107 @@ +"Games" +{ + "#default" + { + "Keys" + { + "EngineInterface" "VEngineServer021" + } + "Signatures" + { + "CreateInterface" + { + "library" "engine" + "windows" "@CreateInterface" + "linux" "@CreateInterface" + } + } + "Offsets" + { + "ClientPrintf" + { + "windows" "45" + "linux" "45" + } + } + } + + "cstrike" + { + "Keys" + { + "EngineInterface" "VEngineServer023" + } + "Offsets" + { + "ClientPrintf" + { + "windows" "45" + "linux" "45" + } + } + } + + "tf" + { + "Keys" + { + "EngineInterface" "VEngineServer023" + } + "Offsets" + { + "ClientPrintf" + { + "windows" "45" + "linux" "45" + } + } + } + + "csgo" + { + "Keys" + { + "EngineInterface" "VEngineServer023" + } + "Offsets" + { + "ClientPrintf" + { + "windows" "47" + "linux" "47" + } + } + } + + "left4dead2" + { + "Keys" + { + "EngineInterface" "VEngineServer022" + } + "Offsets" + { + "ClientPrintf" + { + "windows" "46" + "linux" "46" + } + } + } + + "nmrih" + { + "Keys" + { + "EngineInterface" "VEngineServer023" + } + "Offsets" + { + "ClientPrintf" + { + "windows" "45" + "linux" "45" + } + } + } +} From 3c630eebc4ccd7970b5b532d9f4b72417bfdf618 Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:32:40 -0500 Subject: [PATCH 14/16] Create adminhelpstac.sp Our replacement to the default adminhelp, which would leak StAC commands. Not a perfect solution, but the only thing I can really care to do, since it's simple. Conflicts with the Sourcemod bundled adminhelp.sp/smx. --- scripting/adminhelpstac.sp | 190 +++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 scripting/adminhelpstac.sp diff --git a/scripting/adminhelpstac.sp b/scripting/adminhelpstac.sp new file mode 100644 index 00000000..6cc75b92 --- /dev/null +++ b/scripting/adminhelpstac.sp @@ -0,0 +1,190 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Admin Help Plugin + * Displays and searches SourceMod commands and descriptions. + * + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#pragma semicolon 1 + +#include + +#pragma newdecls required + +#define COMMANDS_PER_PAGE 10 + +public Plugin myinfo = +{ + name = "Admin Help", + author = "AlliedModders LLC", + description = "Display command information", + version = SOURCEMOD_VERSION, + url = "http://www.sourcemod.net/" +}; + +public void OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("adminhelp.phrases"); + RegConsoleCmd("sm_help", HelpCmd, "Displays SourceMod commands and descriptions"); + RegConsoleCmd("sm_searchcmd", HelpCmd, "Searches SourceMod commands"); +} + +public Action HelpCmd(int client, int args) +{ + if (client && !IsClientInGame(client)) + { + return Plugin_Handled; + } + + char arg[64], cmdName[20]; + int pageNum = 1; + bool doSearch; + + GetCmdArg(0, cmdName, sizeof(cmdName)); + + if (args >= 1) + { + GetCmdArg(1, arg, sizeof(arg)); + StringToIntEx(arg, pageNum); + pageNum = (pageNum <= 0) ? 1 : pageNum; + } + + doSearch = (strcmp("sm_help", cmdName) == 0) ? false : true; + + if (GetCmdReplySource() == SM_REPLY_TO_CHAT) + { + ReplyToCommand(client, "[SM] %t", "See console for output"); + } + + char name[64]; + char desc[255]; + char noDesc[128]; + CommandIterator cmdIter = new CommandIterator(); + + FormatEx(noDesc, sizeof(noDesc), "%T", "No description available", client); + + if (doSearch) + { + int i = 1; + while (cmdIter.Next()) + { + cmdIter.GetName(name, sizeof(name)); + cmdIter.GetDescription(desc, sizeof(desc)); + + if (CheckCommandAccess(client, "sm_admin", ADMFLAG_ROOT, true)) + { + if ((StrContains(name, arg, false) != -1) && CheckCommandAccess(client, name, cmdIter.Flags)) + { + PrintToConsole(client, "[%03d] %s - %s", i++, name, (desc[0] == '\0') ? noDesc : desc); + } + } + else + { + if ((StrContains(name, arg, false) != -1) && (StrContains(name, "stac", false) == -1) && CheckCommandAccess(client, name, cmdIter.Flags)) + { + PrintToConsole(client, "[%03d] %s - %s", i++, name, (desc[0] == '\0') ? noDesc : desc); + } + } + } + + if (i == 1) + { + PrintToConsole(client, "%t", "No matching results found"); + } + } else { + PrintToConsole(client, "%t", "SM help commands"); + + /* Skip the first N commands if we need to */ + if (pageNum > 1) + { + int i; + int endCmd = (pageNum-1) * COMMANDS_PER_PAGE - 1; + for (i=0; cmdIter.Next() && i Date: Thu, 23 Feb 2023 13:40:26 -0500 Subject: [PATCH 15/16] Create sbpstac.sp This is my fork of sbp. It is incompatible with the regular sbp, but also required in order to prevent the plugins from showing up in the plugin list. I guarantee a better solution than this exists, but this worked for me. Won't work for all servers since some might actually use the plugins we impersonate, creating duplicate entries. --- scripting/sbpstac.sp | 343 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 scripting/sbpstac.sp diff --git a/scripting/sbpstac.sp b/scripting/sbpstac.sp new file mode 100644 index 00000000..a47f3451 --- /dev/null +++ b/scripting/sbpstac.sp @@ -0,0 +1,343 @@ +#pragma semicolon 1 + +#define TFMAXPLAYERS 33 + +#include +#include +#include +#include +#include +#undef REQUIRE_PLUGIN +#tryinclude + +#pragma newdecls required + +public Plugin myinfo = +{ + name = "[DHooks] Block SM Plugins (Ricochet's Fork)", + description = "", + author = "Bara, Ricochet", + version = "shfjgsh625063", + url = "https://github.com/Bara" +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + RegPluginLibrary("sbpstac"); + + return APLRes_Success; +} + +Handle g_hClientPrintf = null; + +char g_sLogs[PLATFORM_MAX_PATH + 1]; +char stacVersion[32]; // The size of this may be able to be reduced. +bool DISCORD; +bool adminsNotified[TFMAXPLAYERS+1] = {false, ...}; +char hostname[64]; +char realSMVer[32]; +Regex smVerRegex; + +public void OnPluginStart() +{ + // Fake the admin commands for the plugins we lie about... + RegAdminCmd("sm_sql_addadmin", Command_DoNothing, ADMFLAG_ROOT, "Adds an admin to the SQL database"); + RegAdminCmd("sm_sql_deladmin", Command_DoNothing, ADMFLAG_ROOT, "Removes an admin from the SQL database"); + RegAdminCmd("sm_sql_addgroup", Command_DoNothing, ADMFLAG_ROOT, "Adds a group to the SQL database"); + RegAdminCmd("sm_sql_delgroup", Command_DoNothing, ADMFLAG_ROOT, "Removes a group from the SQL database"); + RegAdminCmd("sm_sql_setadmingroups", Command_DoNothing, ADMFLAG_ROOT, "Sets an admin's groups in the SQL database"); + + Handle gameconf = LoadGameConfigFile("sbp.games"); + if (gameconf == null) + { + SetFailState("Failed to find sbp.games.txt gamedata"); + delete gameconf; + } + + int offset = GameConfGetOffset(gameconf, "ClientPrintf"); + if (offset == -1) + { + SetFailState("Failed to find offset for ClientPrintf"); + delete gameconf; + } + + StartPrepSDKCall(SDKCall_Static); + + if (!PrepSDKCall_SetFromConf(gameconf, SDKConf_Signature, "CreateInterface")) + { + SetFailState("Failed to get CreateInterface"); + delete gameconf; + } + + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer, VDECODE_FLAG_ALLOWNULL); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + + char identifier[64]; + if (!GameConfGetKeyValue(gameconf, "EngineInterface", identifier, sizeof(identifier))) + { + SetFailState("Failed to get engine identifier name"); + delete gameconf; + } + + Handle temp = EndPrepSDKCall(); + Address addr = SDKCall(temp, identifier, 0); + + delete gameconf; + delete temp; + + if (!addr) + { + SetFailState("Failed to get engine ptr"); + } + + g_hClientPrintf = DHookCreate(offset, HookType_Raw, ReturnType_Void, ThisPointer_Ignore, Hook_ClientPrintf); + DHookAddParam(g_hClientPrintf, HookParamType_Edict); + DHookAddParam(g_hClientPrintf, HookParamType_CharPtr); + DHookRaw(g_hClientPrintf, false, addr); + + char sDate[18]; + FormatTime(sDate, sizeof(sDate), "%y-%m-%d"); + BuildPath(Path_SM, g_sLogs, sizeof(g_sLogs), "logs/sbp-%s.log", sDate); +} + +public void OnMapStart() +{ + smVerRegex = CompileRegex("([1-9]\\d*|0)(\\.(([1-9]\\d*)|0)){0,3}"); // Might break someday lol + char smVerOut[2048]; + ServerCommandEx(smVerOut, sizeof(smVerOut), "sm version"); + GetConVarString(FindConVar("hostname"), hostname, sizeof(hostname)); + + if (MatchRegex(smVerRegex, smVerOut) > 0) + { + GetRegexSubString(smVerRegex, 0, realSMVer, sizeof(realSMVer)); + } + else + { + realSMVer = SOURCEMOD_VERSION; + } + CreateTimer(0.1, checkDiscord); +} + +public void OnClientDisconnect(int client) +{ + adminsNotified[client] = false; +} + +public MRESReturn Hook_ClientPrintf(Handle hParams) +{ + char sBuffer[1024]; + int client = DHookGetParam(hParams, 1); + + if (client == 0) + { + return MRES_Ignored; + } + + if (IsValidAdmin(client)) + { + return MRES_Ignored; + } + + DHookGetParamString(hParams, 2, sBuffer, sizeof(sBuffer)); + // Ideally, I wouldn't need to fake what plugin is loaded and would just remove ours from the list, but that seemingly isn't possible with this approach. + // DiscordAPI + char fakePreSQL[64] = " \"SQL Admins (Prefetch)\" ("; + StrCat(fakePreSQL, sizeof(fakePreSQL), realSMVer); + StrCat(fakePreSQL, sizeof(fakePreSQL), ") by AlliedModders LLC\n"); + //char fakePreSQL[64]; + //Format(fakePreSQL, sizeof(fakePreSQL), " \"SQL Admins (Prefetch)\" (" ... realSMVer ... ") by AlliedModders LLC\n"); + char discordName[] = " \"Discord API\" (1.0) by .#Zipcore, Credits: Shavit, bara, ImACow and Phire\n"; + if (StrEqual(sBuffer, discordName)) + { + notifyAdmins(client); + DHookSetParamString(hParams, 2, fakePreSQL); + return MRES_ChangedHandled; + } + + // StAC + // Create fake Admin Manager string + char fakeAdmMan[64] = " \"SQL Admin Manager\" ("; + StrCat(fakeAdmMan, sizeof(fakeAdmMan), realSMVer); + StrCat(fakeAdmMan, sizeof(fakeAdmMan), ") by AlliedModders LLC\n"); + // Create real StAC string for strcmp + char stacName[128] = " \"Steph's AntiCheat [StAC]\" ("; + StrCat(stacName, sizeof(stacName), stacVersion); + StrCat(stacName, sizeof(stacName), ") by https://sappho.io\n"); // May not be escaped properly. I haven't tried compiling StAC's version yet. + if (StrEqual(sBuffer, stacName)) + { + notifyAdmins(client); + DHookSetParamString(hParams, 2, fakeAdmMan); + return MRES_ChangedHandled; + } + // SBP itself + // Create fake SQL Admins string + char fakeThrSQL[64] = " \"SQL Admins (Threaded)\" ("; + StrCat(fakeThrSQL, sizeof(fakeThrSQL), realSMVer); + StrCat(fakeThrSQL, sizeof(fakeThrSQL), ") by AlliedModders LLC\n"); + // Create SBP string for strcmp + char sbpName[] = " \"[DHooks] Block SM Plugins (Ricochet's Fork)\" (shfjgsh625063) by Bara, Ricochet\n"; + if (StrEqual(sBuffer, sbpName)) + { + notifyAdmins(client); + DHookSetParamString(hParams, 2, fakeThrSQL); + return MRES_ChangedHandled; + } + // Make sure Admin Help looks as if it's bundled (version number) + if (!StrEqual(SOURCEMOD_VERSION, realSMVer)) // No need to do any of this if the compiler version number and real version number are the same + { + // Create fake Admin Help string + char fakeAdmHlp[64] = " \"Admin Help\" ("; + StrCat(fakeAdmHlp, sizeof(fakeAdmHlp), realSMVer); + StrCat(fakeAdmHlp, sizeof(fakeAdmHlp), ") by AlliedModders LLC\n"); + // Create real Admin Help string for strcmp + char admHlpName[64] = " \"Admin Help\" ("; + StrCat(admHlpName, sizeof(admHlpName), SOURCEMOD_VERSION); + StrCat(admHlpName, sizeof(admHlpName), ") by AlliedModders LLC\n"); + if (StrEqual(sBuffer, admHlpName)) + { + notifyAdmins(client); + DHookSetParamString(hParams, 2, fakeAdmHlp); + return MRES_ChangedHandled; + } + } + return MRES_Ignored; +} + +public Action Command_DoNothing(int client, int args) +{ + return Plugin_Handled; +} + +public void OnAllPluginsLoaded() +{ + GetConVarString(FindConVar("stac_version"), stacVersion, sizeof(stacVersion)); + HookConVarChange(FindConVar("stac_version"), getStACVersion); +} + +public void getStACVersion(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if (StrEqual(stacVersion, newValue)) + { + return; + } + GetConVarString(FindConVar("stac_version"), stacVersion, sizeof(stacVersion)); +} + +// print colored chat to all server/sourcemod admins +void PrintToImportant(const char[] format, any ...) +{ + char buffer[254]; + + // print translations in the servers lang first + SetGlobalTransTarget(LANG_SERVER); + // format it properly + VFormat(buffer, sizeof(buffer), format, 2); + buffer[0] = '\0'; + + for (int i = 1; i <= MaxClients; i++) + { + if (IsValidAdmin(i)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 2); + MC_PrintToChat(i, "%s", buffer); + } + } +} + +public Action checkDiscord(Handle timer) +{ + // discord functionality + if (GetFeatureStatus(FeatureType_Native, "Discord_SendMessage") == FeatureStatus_Available) + { + DISCORD = true; + } + return Plugin_Handled; +} + +void notifyAdmins(int client) +{ + if (adminsNotified[client]) + { + return; + } + adminsNotified[client] = true; + PrintToImportant("{hotpink}[StAC]{white} %N accessed sm plugins", client); + SendMessageToDiscord(client, "Client accessed sm plugins"); +} + +void SendMessageToDiscord(int client, const char[] format, any ...) +{ + + if (!DISCORD) + { + return; + } + + static char generalTemplate[2048] = \ + "{ \"embeds\": \ + [{ \"title\": \"StAC Notification!\", \"color\": 14177041, \"fields\":\ + [\ + { \"name\": \"Player\", \"value\": \"%N\" } ,\ + { \"name\": \"Message\", \"value\": \"%s\" } ,\ + { \"name\": \"Hostname\", \"value\": \"%s\" } ,\ + { \"name\": \"Unix timestamp\", \"value\": \"%i\" } \ + ]\ + }],\ + \"avatar_url\": \"https://i.imgur.com/RKRaLPl.png\"\ + }"; + + char msg[1024]; + + char message[256]; + VFormat(message, sizeof(message), format, 3); + + char ClName[64]; + GetClientName(client, ClName, sizeof(ClName)); + Discord_EscapeString(ClName, sizeof(ClName)); + + Format + ( + msg, + sizeof(msg), + generalTemplate, + client, + message, + hostname, + GetTime() + ); + + char webhook[8] = "stac"; + Discord_SendMessage(webhook, msg); +} + +bool IsValidClient(int client) +{ + if + ( + (0 < client <= MaxClients) + && IsClientInGame(client) + && !IsClientInKickQueue(client) + && !IsFakeClient(client) + ) + { + return true; + } + return false; +} + +bool IsValidAdmin(int Cl) +{ + if (IsValidClient(Cl)) + { + if + ( + CheckCommandAccess(Cl, "sm_ban", ADMFLAG_GENERIC) + ) + { + return true; + } + } + return false; +} From 2de60f2d9f1d1745576922c47d29009a68ee019a Mon Sep 17 00:00:00 2001 From: RicochetYT <36606403+RicochetYT@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:41:33 -0500 Subject: [PATCH 16/16] Create sbpstac.inc Necessary for protection stuff. --- scripting/include/sbpstac.inc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 scripting/include/sbpstac.inc diff --git a/scripting/include/sbpstac.inc b/scripting/include/sbpstac.inc new file mode 100644 index 00000000..7a9627ec --- /dev/null +++ b/scripting/include/sbpstac.inc @@ -0,0 +1,15 @@ +#if defined _sbpstac_included + #endinput +#endif +#define __sbpstac_included_included + +public SharedPlugin __pl_sbpstac = +{ + name = "sbpstac", + file = "sbpstac.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +};