11/*
22* Spray Exploit Fixer
3- * Copyright (C) 2024 Silvers
3+ * Copyright (C) 2025 Silvers
44*
55* This program is free software: you can redistribute it and/or modify
66* it under the terms of the GNU General Public License as published by
3232========================================================================================
3333 Change Log:
3434
35- 2.25 (28-Jan-2024)
36- - Fixed memory leak caused by clearing StringMap/ArrayList data instead of deleting.
37- 2.24 (17-Jul-2023)
38- - Now less check of GetClientAuthId by storing them. Thanks to ".Rushaway".
35+ 2.25 (04-Jan-2025)
36+ - Changes to the recent exploit fix. Thanks to "Madness (null138)" for fixing and reporting.
3937
40- 2.23 (01-Apr-2023 )
41- - Now only TestClient depending cvar
42- - Now ban lenght can be adjusted via cvar
38+ 2.24 (14-Dec-2024 )
39+ - Added forward "OnSprayExploit" for 3rd party plugins. Requested by ".Rushaway".
40+ - Fixed another Spray exploit. Thanks to "Madness (null138)" for fixing and reporting.
4341
44- 2.22 (19-Feb-2023)
45- - Now prevents even more log spamming duplicate entries. Thanks to ".Rushaway" for reporting.
42+ 2.23 (05-Nov-2024) - Update by ".Rushaway"
43+ - Added cvar "spray_exploit_fixer_punish" to specify which exploits to test for.
44+ - Added cvar "spray_exploit_fixer_bantime" to set the ban length.
45+ - Now less checks of GetClientAuthId by storing them.
46+ - Switch to Steam3 format for AuthID.
47+ - Fixed g_smWaiting not removing data for unverified clients.
48+ - Prevent g_smWaiting not removing data if client was already disconnected.
49+ - LogAction now print infos even if client is not verified.
50+
51+ 2.22 (28-Jan-2024)
52+ - Fixed memory leak caused by clearing StringMap/ArrayList data instead of deleting.
4653
47- 2.21 (22-Jan -2023)
54+ 2.21 (19-Feb -2023)
4855 - Now prevents even more log spamming duplicate entries. Thanks to ".Rushaway" for reporting.
4956
50572.20 (20-Jan-2023)
116123 - Changes to fix warnings when compiling on SourceMod 1.11.
117124
1181252.2 (30-Jun-2021)
119- - Fixed another Spray exploit. Thanks to "Madness (null138)" for fixing and reporting.
126+ - Fixed another spray exploit. Thanks to "Madness (null138)" for fixing and reporting.
120127
1211282.1 (31-Mar-2021)
122129 - Added a check for "sm_sprays_allowed" in the command admin_overrides.cfg to only allow specific flag groups to use sprays.
180187
181188
182189#define MAX_READ 50
183- #define TIMEOUT_LOG 5 .0
190+ #define TIMEOUT_LOG 10 .0
184191#define PATH_BACKUP " backup_sprays"
185192
186193int g_iVal [] = {86 ,84 ,70 ,0 ,7 ,0 ,0 ,0 ,42 ,0 ,0 ,0 ,42 ,0 ,0 ,0 ,42 ,42 ,42 ,42 ,42 ,42 ,42 ,42 ,42 ,42 ,42 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 };
@@ -204,6 +211,7 @@ bool g_bProc;
204211bool g_bDecal ;
205212bool g_bSourceBans ;
206213bool g_bMaterialAdmin ;
214+ GlobalForward g_hExploit ;
207215
208216
209217
@@ -235,6 +243,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
235243 g_iEngine = GetEngineVersion ();
236244 g_bLate = late ;
237245
246+ g_hExploit = new GlobalForward (" OnSprayExploit" , ET_Ignore , Param_Cell , Param_Cell , Param_Cell );
247+
238248 return APLRes_Success ;
239249}
240250
@@ -271,17 +281,17 @@ public void OnPluginStart()
271281 }
272282
273283 CreateConVar ( " spray_exploit_fixer" , PLUGIN_VERSION , " Spray Exploit Fixer plugin version." , FCVAR_DONTRECORD );
274- g_hCvarPunish = CreateConVar ( " spray_exploit_fixer_punish_client " , " 0 " , " 0=Off. 1=PlayerDecal. 2=FileCheck. 3=Both. Run the TestClient function " );
284+ g_hCvarPunish = CreateConVar ( " spray_exploit_fixer_punish " , " 3 " , " 0=Off. 1=PlayerDecal. 2=FileCheck. 3=Both. Which exploits to test for. " );
275285 if ( g_iEngine != Engine_TF2 )
276286 {
277- g_hCvarBan = CreateConVar ( " spray_exploit_fixer_ban" , " 0" , " 0=Off. 1=Ban users who trigger invalid sprays (may still be some false positives)." );
278- g_hCvarKick = CreateConVar ( " spray_exploit_fixer_kick" , " 0" , " 0=Off. 1=Kick users who trigger invalid sprays (may still be some false positives)." );
279- g_hCvarBanTime = CreateConVar ( " spray_exploit_fixer_bantime" , " 5" , " 0=Perm . Ban time (in minutes)" );
287+ g_hCvarBan = CreateConVar ( " spray_exploit_fixer_ban" , " 0" , " 0=Off. 1=Ban users who trigger invalid sprays (may still be some false positives)." );
288+ g_hCvarKick = CreateConVar ( " spray_exploit_fixer_kick" , " 0" , " 0=Off. 1=Kick users who trigger invalid sprays (may still be some false positives)." );
289+ g_hCvarBanTime = CreateConVar ( " spray_exploit_fixer_bantime" , " 5" , " 0=Permanent . Ban time (in minutes). " );
280290 }
281- g_hCvarLog = CreateConVar ( " spray_exploit_fixer_log" , " 1" , " Logging saved to sourcemod/logs/spray_downloads.log: 0=Off. 1=Log all user uploads. 2=Log invalid sprays only." );
282- g_hCvarMsg = CreateConVar ( " spray_exploit_fixer_msg" , " 1" , " Print to server console: 0=Off. 1=Missing sprays and invalid sprays. 2=Only invalid sprays." );
283- g_hCvarPath = CreateConVar ( " spray_exploit_fixer_path" , g_sDownloads , " Path to the downloads folder of sprays. Add /cc/ if sprays are stored in individual 2 character folders. Must contain trailing / slash." );
284- AutoExecConfig (true , " spray_exploit_fixer" );
291+ g_hCvarLog = CreateConVar ( " spray_exploit_fixer_log" , " 1" , " Logging saved to sourcemod/logs/spray_downloads.log: 0=Off. 1=Log all user uploads. 2=Log invalid sprays only." );
292+ g_hCvarMsg = CreateConVar ( " spray_exploit_fixer_msg" , " 1" , " Print to server console: 0=Off. 1=Missing sprays and invalid sprays. 2=Only invalid sprays." );
293+ g_hCvarPath = CreateConVar ( " spray_exploit_fixer_path" , g_sDownloads , " Path to the downloads folder of sprays. Add /cc/ if sprays are stored in individual 2 character folders. Must contain trailing / slash." );
294+ AutoExecConfig (true , " spray_exploit_fixer" );
285295 g_hCvarPath .AddChangeHook (ConVarChanged_Cvars );
286296
287297 g_smChecked = new StringMap ();
@@ -326,11 +336,11 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
326336public void OnClientPutInServer (int client )
327337{
328338 char sSteamID [64 ];
329- GetClientAuthId (client , AuthId_Steam2 , sSteamID , sizeof (sSteamID ));
339+ GetClientAuthId (client , AuthId_Steam3 , sSteamID , sizeof (sSteamID ));
330340 FormatEx (g_sAuth [client ], sizeof (g_sAuth []), " %s " , sSteamID );
331341
332342 char sSteamIDUnverified [32 ];
333- GetClientAuthId (client , AuthId_Steam2 , sSteamIDUnverified , sizeof (sSteamIDUnverified ), false );
343+ GetClientAuthId (client , AuthId_Steam3 , sSteamIDUnverified , sizeof (sSteamIDUnverified ), false );
334344 FormatEx (g_sAuthUnverified [client ], sizeof (g_sAuthUnverified []), " %s " , sSteamIDUnverified );
335345}
336346
@@ -343,23 +353,17 @@ public void OnClientConnected(int client)
343353
344354public void OnClientDisconnect (int client )
345355{
346- if ( ! IsFakeClient (client ) )
347- {
348- if ( IsClientConnected (client ) )
349- {
350- //static char auth[32];
351- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth), false);
352- g_smWaiting .Remove (g_sAuthUnverified [client ]);
353- }
354-
355- g_smChecked .Remove (g_sPath1 [client ]);
356- g_smReceive .Remove (g_sPath1 [client ]);
357- g_smChecked .Remove (g_sPath2 [client ]);
358- g_smReceive .Remove (g_sPath2 [client ]);
359- }
356+ if ( IsFakeClient (client ) ) return ;
357+
358+ g_smWaiting .Remove (g_sAuthUnverified [client ]);
359+ g_smChecked .Remove (g_sPath1 [client ]);
360+ g_smReceive .Remove (g_sPath1 [client ]);
361+ g_smChecked .Remove (g_sPath2 [client ]);
362+ g_smReceive .Remove (g_sPath2 [client ]);
360363
361- FormatEx (g_sAuth [client ], sizeof (g_sAuth []), " " );
362- FormatEx (g_sAuthUnverified [client ], sizeof (g_sAuthUnverified []), " " );
364+ g_sAuth [client ][0 ] = 0 ;
365+ g_sAuth [client ][6 ] = 0 ;
366+ g_sAuthUnverified [client ][0 ] = 0 ;
363367
364368 /*
365369 static char sPath[PLATFORM_MAX_PATH];
@@ -426,6 +430,7 @@ public void OnClientDisconnect(int client)
426430public void OnMapEnd ()
427431{
428432 MoveSprays ();
433+
429434 // .Clear() is creating a memory leak
430435 // g_smReceive.Clear();
431436 // g_smWaiting.Clear();
@@ -742,6 +747,10 @@ Action PlayerDecal(const char[] te_name, const int[] Players, int numClients, fl
742747 return Plugin_Continue ;
743748 }
744749
750+ // Here because of error: "No TempEntity call is in progress" - Must have taken too long in this function when placed with the vPos use below and become invalid
751+ float vPos [3 ];
752+ TE_ReadVector (" m_vecOrigin" , vPos );
753+
745754 g_sFilename [0 ] = 0 ;
746755 GetPlayerDecalFile (client , g_sFilename , sizeof (g_sFilename ));
747756
@@ -767,16 +776,10 @@ Action PlayerDecal(const char[] te_name, const int[] Players, int numClients, fl
767776 if ( ! val )
768777 {
769778 static char auth [64 ];
770- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth));
771779 if ( g_sAuth [client ][6 ] == ' I' )
772- {
773- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth), false);
774780 Format (auth , sizeof (auth ), " Unverified: %s " , g_sAuthUnverified [client ]);
775- }
776781 else
777- {
778782 Format (auth , sizeof (auth ), " %s " , g_sAuth [client ]);
779- }
780783
781784 if ( FileExists (g_sFilename ) )
782785 {
@@ -792,17 +795,16 @@ Action PlayerDecal(const char[] te_name, const int[] Players, int numClients, fl
792795 }
793796 else
794797 {
795- if ( GetGameTime () - g_fSprayed [client ] > TIMEOUT_LOG && ! g_smWaiting .GetValue (auth , val ) )
798+ if ( GetGameTime () - g_fSprayed [client ] > TIMEOUT_LOG && ! g_smWaiting .GetValue (g_sAuthUnverified [ client ] , val ) )
796799 {
797800 g_fSprayed [client ] = GetGameTime ();
798- g_smWaiting .SetValue (auth , true );
801+ g_smWaiting .SetValue (g_sAuthUnverified [client ], true );
802+
799803 if ( g_hCvarLog .IntValue ) LogCustom (" Blocked unchecked spray - missing file: %s from (%N ) [%s ]" , g_sFilename , client , auth );
800804 if ( g_hCvarMsg .IntValue == 1 ) PrintToServer (" [Spray Exploit] Blocked unchecked spray - missing file: %s from (%N ) [%s ]" , g_sFilename , client , auth );
801805 }
802806 }
803807
804- float vPos [3 ];
805- TE_ReadVector (" m_vecOrigin" , vPos );
806808 DataPack hPack = new DataPack ();
807809 hPack .WriteCell (GetClientUserId (client ));
808810 hPack .WriteFloat (vPos [0 ]);
@@ -888,13 +890,13 @@ void TestClient(int client)
888890 else
889891 BanClient (client , iDuration , BANFLAG_AUTO , " Invalid spray" );
890892
891- LogAction (client , - 1 , " [Spray Exploit] %L Banned %d minutes for invalid Spray" , client , iDuration );
893+ LogAction (client , - 1 , " [Spray Exploit] %N %s was banned %d minutes for invalid Spray" , client , g_sAuthUnverified [ client ] , iDuration );
892894 return ;
893895 }
894896 else if ( g_hCvarKick .IntValue )
895897 {
896898 KickClient (client , " Invalid spray. Please change it" );
897- LogAction (client , - 1 , " [Spray Exploit] %L was kicked for invalid Spray." , client );
899+ LogAction (client , - 1 , " [Spray Exploit] %N %s was kicked for invalid Spray." , client , g_sAuthUnverified [ client ] );
898900 return ;
899901 }
900902 }
@@ -939,16 +941,10 @@ public Action OnFileReceive(int client, const char[] sFile)
939941 if ( client )
940942 {
941943 static char auth [64 ];
942- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth));
943944 if ( g_sAuth [client ][6 ] == ' I' )
944- {
945- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth), false);
946945 Format (auth , sizeof (auth ), " Unverified: %s " , g_sAuthUnverified [client ]);
947- }
948946 else
949- {
950947 Format (auth , sizeof (auth ), " %s " , g_sAuth [client ]);
951- }
952948
953949 LogCustom (" File received: %s from (%N ) [%s ]" , sFile , client , auth );
954950 }
@@ -1009,16 +1005,10 @@ void FileCheck()
10091005 if ( client )
10101006 {
10111007 static char auth [64 ];
1012- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth));
10131008 if ( g_sAuth [client ][6 ] == ' I' )
1014- {
1015- //GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth), false);
10161009 Format (auth , sizeof (auth ), " Unverified: %s " , g_sAuthUnverified [client ]);
1017- }
10181010 else
1019- {
10201011 Format (auth , sizeof (auth ), " %s " , g_sAuth [client ]);
1021- }
10221012
10231013 if ( g_hCvarLog .IntValue ) LogCustom (" Invalid spray: %s from (%N ) [%s ]" , g_sFilename , client , auth );
10241014 if ( g_hCvarMsg .IntValue ) PrintToServer (" [Spray Exploit] Invalid spray: %s : %02d (%02X <> %02X ) from (%N ) [%s ]" , g_sFilename , i , iRead [i ], g_iVal [i ], client , auth );
@@ -1027,6 +1017,12 @@ void FileCheck()
10271017 if ( g_hCvarMsg .IntValue ) PrintToServer (" [Spray Exploit] Invalid spray: %s : %02d (%02X <> %02X )" , g_sFilename , i , iRead [i ], g_iVal [i ]);
10281018 }
10291019
1020+ Call_StartForward (g_hExploit );
1021+ Call_PushCell (client );
1022+ Call_PushCell (i );
1023+ Call_PushCell (iRead [i ]);
1024+ Call_Finish ();
1025+
10301026 if ( g_hCvarPunish .IntValue >= 2 )
10311027 TestClient (client );
10321028
@@ -1052,6 +1048,11 @@ int ValFile(int iRead[sizeof(g_iVal)])
10521048 return - 1 ;
10531049 }
10541050
1051+ if ( iRead [16 ] == 80 && iRead [24 ] > 1 )
1052+ {
1053+ return 24 ;
1054+ }
1055+
10551056 char bytes [10 ];
10561057 bool read = true ;
10571058 int n ;
0 commit comments