2024-12-10 12:34:45 +01:00
/ *
* Spray Exploit Fixer
* Copyright ( C ) 2024 Silvers
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* 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 < https : //www.gnu.org/licenses/>.
* /
# define PLUGIN_VERSION "2.23"
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Plugin Info :
* Name : [ ANY ] Spray Exploit Fixer
* Author : SilverShot
* Descrp : Deletes bad sprays and prevents them from crashing clients .
* Link : https : //forums.alliedmods.net/showthread.php?t=323447
* Plugins : https : //sourcemod.net/plugins.php?exact=exact&sortby=title&search=1&author=Silvers
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Change Log :
2.23 ( 05 - Nov - 2024 ) - Update by " .Rushaway "
- Added cvar " spray_exploit_fixer_punish " to specify which exploits to test for .
- Added cvar " spray_exploit_fixer_bantime " to set the ban length .
- Now less checks of GetClientAuthId by storing them .
- Switch to Steam3 format for AuthID .
- Fixed g_smWaiting not removing data for unverified clients .
- Prevent g_smWaiting not removing data if client was already disconnected .
- LogAction now print infos even if client is not verified .
2.22 ( 28 - Jan - 2024 )
- Fixed memory leak caused by clearing StringMap / ArrayList data instead of deleting .
2.21 ( 19 - Feb - 2023 )
- Now prevents even more log spamming duplicate entries . Thanks to " .Rushaway " for reporting .
2.20 ( 20 - Jan - 2023 )
- Now logs if a Steam ID is unverified .
- Now prevents log spamming duplicate entries .
- Fixed checking bots for sprays .
- Thanks to " .Rushaway " for reporting and help testing .
2.19 ( 07 - Jan - 2023 )
- Fixed processing getting stuck . Thanks to " SuperConker " for reporting and help testing .
- Fixed invalid handle errors . Thanks to " nikooo777 " for reporting .
2.18 ( 24 - Dec - 2022 )
- Changed moving sprays to use an asynchronous method to prevent a script execution timed out error . Thanks to " .Rushaway " for reporting .
2.17 ( 08 - Oct - 2022 )
- Fixed command " sm_spray_test " getting stuck processing under certain conditions .
- Re - wrote the recursive directory function to avoid several bugs under several conditions .
- Now only moves sprays ( . dat or . dat . ztmp ) and not other files to the " backup_sprays " folder .
- Now deletes empty directories on plugin start after moving sprays to the " backup_sprays " folder .
2.16 ( 30 - Sep - 2022 )
- Fixed not moving all sprays on disconnect .
- Fixed client not in game errors when renaming sprays .
- Now moves " dat.ztmp " spray files to backup folder .
2.15 ( 22 - Sep - 2022 )
- Fixed not deleting the old backup if the names match .
2.14 ( 22 - Sep - 2022 )
- Added cvar " spray_exploit_fixer_msg " to control if messages should print to the server console . Requested by " .Rushaway " .
- Plugin now moves all sprays to the " download/backup_sprays " folder on plugin start and client disconnect .
- Removed saving checked and blocked sprays to file . All sprays will be checked .
2.13 ( 22 - May - 2022 )
- More detailed " LogAction " when kicking or banning clients .
2.12 ( 22 - May - 2022 )
- Added some more " LogAction " when kicking or banning clients .
2.11 ( 22 - May - 2022 )
- Added cvar " spray_exploit_fixer_kick " to kick clients . Ban cvar overrides this . Requested by " .Rushaway " .
- Changes to fix not kicking or banning clients under some conditions .
2.10 ( 23 - Apr - 2022 )
- Fixed the plugin blocking sprays on some servers . Thanks to " SuperConker " for reporting and lots of testing .
2.9 ( 10 - Apr - 2022 )
- Fixed showing the wrong invalid files count . Thanks to " sappho " for reporting .
2.8 ( 20 - Mar - 2022 )
- Added another check and prevention against crash exploits . Thanks to " Sreaper " and " ficool2 " for lots of help .
- Fixed some false positives due to recent updates .
2.7 ( 08 - Mar - 2022 )
- Added support for banning using the " Material Admin " plugin . Thanks to " lechuga " for adding .
2.6 ( 01 - Mar - 2022 )
- Another crash exploit fixed . Thanks to Kenzzer for reporting .
2.5 ( 15 - Jan - 2022 )
- Fixed randomly using recursive folder and extension names in spray filenames causing validation failure . Thanks to " A1m " for reporting .
2.4 ( 02 - Dec - 2021 )
- Added support for banning using the " SourceBans " plugin . Thanks to " lechuga " for adding .
2.3 ( 12 - Nov - 2021 )
- Added a check for missing downloads folder and filename . Thanks to " nebsun " for reporting .
- Changes to fix warnings when compiling on SourceMod 1.11 .
2.2 ( 30 - Jun - 2021 )
- Fixed another Spray exploit . Thanks to " Madness (null138) " for fixing and reporting .
2.1 ( 31 - Mar - 2021 )
- Added a check for " sm_sprays_allowed " in the command admin_overrides . cfg to only allow specific flag groups to use sprays .
2.0 ( 09 - Aug - 2020 )
- Now should support all games .
- Added more checks for invalid files .
- Added cvar " spray_exploit_fixer_path " to specify the downloads folder if not correctly detected .
- Removed gamedata and DHooks dependency .
- Removed cvar " spray_exploit_fixer_name " .
1.6 ( 15 - Jul - 2020 )
- Fixed issue with CSS game . Thanks to " NeonC " for reporting .
- Added cvar " spray_exploit_fixer_name " to choose the method for retrieving the spray owner .
1.5 ( 14 - May - 2020 )
- Added better error log message when gamedata file is missing .
- Fixed gamedata for HL2 : DM . Thanks to " CliptonHeist " for reporting and " asherkin " for explaining engine ! = game .
- ( Info : the gamedata " engine " key for HL2 : DM uses " hl2dm " ( the engine name ) while the " game " part uses " hl2mp " ( game name ) e . g . for offsets ) .
1.4 ( 10 - May - 2020 )
- Added support for " Zombie Panic! Source " game . Requires gamedata update .
- Fixed " sm_spray_test " timing out when checking many sprays . Thanks to " Sreaper " for reporting and testing .
- Now checks 50 files and waits 0.1 seconds before checking the next batch .
- TF2 updated to fix clients crashing , but this plugin is still recommended to delete the other randomly uploaded user files .
1.3 ( 26 - Apr - 2020 )
- Changed cvar " spray_exploit_fixer_log " to log all files or only invalid sprays .
- Logging now saves to " sourcemod/logs/spray_downloads.log " file .
1.2 ( 23 - Apr - 2020 )
- Added better checks to detect more bad sprays .
- Added better checks for TF2 and other games to avoid false positives .
- Prevented banning people in TF2 since many random invalid files are sent , not just sprays .
1.1 ( 21 - Apr - 2020 )
- Added better checks to prevent false positives .
- Added ability to detect the users uploading sprays or other files .
- Added cvar " spray_exploit_fixer_ban " to ban players with invalid sprays .
- Added cvar " spray_exploit_fixer_log " to log players and files they uploaded .
- Changed " sm_spray_test " to allow recursive searching the downloads directory .
- Fixed plugin crashing TF2 .
- Updated GameData required .
1.0 ( 20 - Apr - 2020 )
- Initial release .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
# pragma semicolon 1
# undef REQUIRE_PLUGIN
# tryinclude <materialadmin>
# tryinclude <sourcebanspp>
# define REQUIRE_PLUGIN
# pragma newdecls required
# include <sourcemod>
# include <sdktools>
# define MAX_READ 50
# define TIMEOUT_LOG 10.0
# define PATH_BACKUP "backup_sprays"
int 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 } ;
char g_sFilename [ PLATFORM_MAX_PATH ] ;
char g_sMoveFiles [ PLATFORM_MAX_PATH ] ;
char g_sDownloads [ PLATFORM_MAX_PATH ] ;
char g_sPath1 [ MAXPLAYERS + 1 ] [ PLATFORM_MAX_PATH ] ;
char g_sPath2 [ MAXPLAYERS + 1 ] [ PLATFORM_MAX_PATH ] ;
char g_sAuth [ MAXPLAYERS + 1 ] [ 64 ] ;
char g_sAuthUnverified [ MAXPLAYERS + 1 ] [ 64 ] ;
float g_fSprayed [ MAXPLAYERS + 1 ] ;
ConVar g_hCvarBan , g_hCvarBanTime , g_hCvarKick , g_hCvarLog , g_hCvarMsg , g_hCvarPath , g_hCvarPunish ;
EngineVersion g_iEngine ;
StringMap g_smChecked ;
StringMap g_smReceive ;
StringMap g_smWaiting ;
int g_iTotal ;
float g_fTime ;
bool g_bLate ;
bool g_bProc ;
bool g_bDecal ;
bool g_bSourceBans ;
bool g_bMaterialAdmin ;
// Added this here so it compiles on the forum without the SourceBans/MaterialAdmin includes.
# if !defined _sourcebanspp_included
native void SBPP_BanPlayer ( int iAdmin , int iTarget , int iTime , const char [ ] sReason ) ;
# endif
# if !defined _materialadmin_included
native bool MABanPlayer ( int iClient , int iTarget , int iType , int iTime , char [ ] sReason ) ;
# define MA_BAN_STEAM 1
# endif
public Plugin myinfo =
{
name = " [ANY] Spray Exploit Fixer " ,
author = " SilverShot " ,
description = " Deletes bad sprays and prevents them from crashing clients. " ,
version = PLUGIN_VERSION ,
url = " https://forums.alliedmods.net/showthread.php?t=323447 "
}
public APLRes AskPluginLoad2 ( Handle myself , bool late , char [ ] error , int err_max )
{
MarkNativeAsOptional ( " SBPP_BanPlayer " ) ;
MarkNativeAsOptional ( " MABanPlayer " ) ;
g_iEngine = GetEngineVersion ( ) ;
g_bLate = late ;
return APLRes_Success ;
}
public void OnLibraryAdded ( const char [ ] name )
{
if ( strcmp ( name , " sourcebans++ " ) = = 0 )
g_bSourceBans = true ;
else if ( strcmp ( name , " materialadmin " ) = = 0 )
g_bMaterialAdmin = true ;
}
public void OnLibraryRemoved ( const char [ ] name )
{
if ( strcmp ( name , " sourcebans++ " ) = = 0 )
g_bSourceBans = false ;
else if ( strcmp ( name , " materialadmin " ) = = 0 )
g_bMaterialAdmin = false ;
}
public void OnPluginStart ( )
{
RegAdminCmd ( " sm_spray_test " , CmdSprays , ADMFLAG_ROOT , " Tests all sprays in the games downloads folder, listing bad ones. " ) ;
switch ( g_iEngine )
{
case Engine_SourceSDK2006 , Engine_SourceSDK2007 , Engine_Left4Dead , Engine_Left4Dead2 :
{
g_sDownloads = " downloads/ " ;
}
default :
{
g_sDownloads = " download/user_custom/cc/ " ;
}
}
CreateConVar ( " spray_exploit_fixer " , PLUGIN_VERSION , " Spray Exploit Fixer plugin version. " , FCVAR_DONTRECORD ) ;
g_hCvarPunish = CreateConVar ( " spray_exploit_fixer_punish " , " 3 " , " 0=Off. 1=PlayerDecal. 2=FileCheck. 3=Both. Which exploits to test for. " ) ;
if ( g_iEngine ! = Engine_TF2 )
{
g_hCvarBan = CreateConVar ( " spray_exploit_fixer_ban " , " 0 " , " 0=Off. 1=Ban users who trigger invalid sprays (may still be some false positives). " ) ;
g_hCvarKick = CreateConVar ( " spray_exploit_fixer_kick " , " 0 " , " 0=Off. 1=Kick users who trigger invalid sprays (may still be some false positives). " ) ;
g_hCvarBanTime = CreateConVar ( " spray_exploit_fixer_bantime " , " 5 " , " 0=Permanent. Ban time (in minutes). " ) ;
}
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. " ) ;
g_hCvarMsg = CreateConVar ( " spray_exploit_fixer_msg " , " 1 " , " Print to server console: 0=Off. 1=Missing sprays and invalid sprays. 2=Only invalid sprays. " ) ;
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. " ) ;
AutoExecConfig ( true , " spray_exploit_fixer " ) ;
g_hCvarPath . AddChangeHook ( ConVarChanged_Cvars ) ;
g_smChecked = new StringMap ( ) ;
g_smReceive = new StringMap ( ) ;
g_smWaiting = new StringMap ( ) ;
AddTempEntHook ( " Player Decal " , PlayerDecal ) ;
char sPath [ PLATFORM_MAX_PATH ] ;
strcopy ( sPath , sizeof ( sPath ) , g_sDownloads ) ;
ReplaceString ( sPath , sizeof ( sPath ) , " /cc " , " " ) ;
StrCat ( sPath , sizeof ( sPath ) , PATH_BACKUP ) ;
CreateDirectory ( sPath , 511 , true ) ;
if ( ! g_bLate )
MoveSprays ( ) ;
}
public Action OnPlayerRunCmd ( int client , int & buttons , int & impulse , float vel [ 3 ] , float angles [ 3 ] )
{
if ( impulse = = 0xCA )
{
static char cc [ 6 ] ;
static char sTemp [ PLATFORM_MAX_PATH ] ;
GetPlayerJingleFile ( client , sTemp , sizeof ( sTemp ) ) ;
Format ( cc , sizeof ( cc ) , " /%c%c/ " , sTemp [ 0 ] , sTemp [ 1 ] ) ;
Format ( sTemp , sizeof ( sTemp ) , " %s%s.dat " , g_sDownloads , sTemp ) ;
ReplaceString ( sTemp , sizeof ( sTemp ) , " /cc/ " , cc ) ;
bool val ;
if ( ! g_smChecked . GetValue ( sTemp , val ) | | ! val )
{
impulse = 0 ;
return Plugin_Changed ;
}
}
return Plugin_Continue ;
}
public void OnClientPutInServer ( int client )
{
char sSteamID [ 64 ] ;
GetClientAuthId ( client , AuthId_Steam3 , sSteamID , sizeof ( sSteamID ) ) ;
FormatEx ( g_sAuth [ client ] , sizeof ( g_sAuth [ ] ) , " %s " , sSteamID ) ;
char sSteamIDUnverified [ 32 ] ;
GetClientAuthId ( client , AuthId_Steam3 , sSteamIDUnverified , sizeof ( sSteamIDUnverified ) , false ) ;
FormatEx ( g_sAuthUnverified [ client ] , sizeof ( g_sAuthUnverified [ ] ) , " %s " , sSteamIDUnverified ) ;
}
public void OnClientConnected ( int client )
{
g_fSprayed [ client ] = 0.0 ;
g_sPath1 [ client ] [ 0 ] = 0 ;
g_sPath2 [ client ] [ 0 ] = 0 ;
}
public void OnClientDisconnect ( int client )
{
if ( IsFakeClient ( client ) ) return ;
g_smWaiting . Remove ( g_sAuthUnverified [ client ] ) ;
g_smChecked . Remove ( g_sPath1 [ client ] ) ;
g_smReceive . Remove ( g_sPath1 [ client ] ) ;
g_smChecked . Remove ( g_sPath2 [ client ] ) ;
g_smReceive . Remove ( g_sPath2 [ client ] ) ;
g_sAuth [ client ] [ 0 ] = 0 ;
g_sAuth [ client ] [ 6 ] = 0 ;
g_sAuthUnverified [ client ] [ 0 ] = 0 ;
/ *
static char sPath [ PLATFORM_MAX_PATH ] ;
static char sOld [ PLATFORM_MAX_PATH ] ;
static char sNew [ PLATFORM_MAX_PATH ] ;
for ( int i = 0 ; i < 2 ; i + + )
{
sPath [ 0 ] = 0 ;
switch ( i )
{
case 0 :
{
if ( g_sPath1 [ client ] [ 0 ] )
strcopy ( sPath , sizeof ( sPath ) , g_sPath1 [ client ] ) ;
else if ( IsClientInGame ( client ) )
GetPlayerDecalFile ( client , sPath , sizeof ( sPath ) ) ;
}
case 1 :
{
if ( g_sPath2 [ client ] [ 0 ] )
strcopy ( sPath , sizeof ( sPath ) , g_sPath2 [ client ] ) ;
else if ( IsClientInGame ( client ) )
GetPlayerJingleFile ( client , sPath , sizeof ( sPath ) ) ;
}
}
if ( sPath [ 0 ] )
{
if ( i = = 0 )
{
g_smChecked . Remove ( g_sPath1 [ client ] ) ;
g_smReceive . Remove ( g_sPath1 [ client ] ) ;
}
else
{
g_smChecked . Remove ( g_sPath2 [ client ] ) ;
g_smReceive . Remove ( g_sPath2 [ client ] ) ;
}
Format ( sOld , sizeof ( sOld ) , " %s%s.dat " , g_sDownloads , sPath ) ;
Format ( sNew , sizeof ( sNew ) , " %s%s/%s.dat " , g_sDownloads , PATH_BACKUP , sPath ) ;
if ( FileExists ( sOld ) )
{
if ( FileExists ( sNew , true ) ) DeleteFile ( sNew , true ) ;
RenameFile ( sNew , sOld , true ) ;
}
StrCat ( sOld , sizeof ( sOld ) , " .ztmp " ) ;
StrCat ( sNew , sizeof ( sNew ) , " .ztmp " ) ;
if ( FileExists ( sOld ) )
{
if ( FileExists ( sNew , true ) ) DeleteFile ( sNew , true ) ;
RenameFile ( sNew , sOld , true ) ;
}
}
}
* /
}
public void OnMapEnd ( )
{
MoveSprays ( ) ;
// .Clear() is creating a memory leak
// g_smReceive.Clear();
// g_smWaiting.Clear();
delete g_smReceive ;
delete g_smWaiting ;
g_smReceive = new StringMap ( ) ;
g_smWaiting = new StringMap ( ) ;
for ( int i = 1 ; i < = MaxClients ; i + + )
{
g_fSprayed [ i ] = 0.0 ;
}
}
void ConVarChanged_Cvars ( Handle convar , const char [ ] oldValue , const char [ ] newValue )
{
g_hCvarPath . GetString ( g_sDownloads , sizeof ( g_sDownloads ) ) ;
}
Action CmdSprays ( int client , int args )
{
if ( g_bProc )
{
ReplyToCommand ( client , " [Sprays] Already processing. " ) ;
return Plugin_Handled ;
}
ReplyToCommand ( client , " [Sprays] checking files, please wait... " ) ;
g_iTotal = 0 ;
g_bProc = true ;
g_fTime = GetEngineTime ( ) ;
ArrayList aList = new ArrayList ( ByteCountToCells ( PLATFORM_MAX_PATH ) ) ;
int pos = StrContains ( g_sDownloads , " / " ) ;
if ( pos ! = - 1 ) g_sDownloads [ pos ] = 0 ;
RecursiveFiles ( aList , false , g_sDownloads ) ;
if ( pos ! = - 1 ) g_sDownloads [ pos ] = '/' ;
int count , counts ;
RecursiveSearchDirs ( client , aList , count , counts , false ) ;
return Plugin_Handled ;
}
void RecursiveFiles ( ArrayList aList , bool move , const char sDir [ PLATFORM_MAX_PATH ] )
{
FileType type ;
DirectoryListing hDir ;
File hFile ;
int iRead [ 4 ] ;
int moving ;
if ( DirExists ( sDir ) )
{
hDir = OpenDirectory ( sDir , true ) ;
if ( hDir )
{
char sPath [ PLATFORM_MAX_PATH ] ;
while ( ReadDirEntry ( hDir , sPath , sizeof ( sPath ) , type ) )
{
if ( strcmp ( sPath , " . " ) & & strcmp ( sPath , " .. " ) )
{
moving = 0 ;
switch ( type )
{
case FileType_Directory :
{
if ( ! move | | strcmp ( sPath , PATH_BACKUP ) )
{
Format ( sPath , sizeof ( sPath ) , " %s/%s " , sDir , sPath ) ;
RecursiveFiles ( aList , move , sPath ) ;
}
}
case FileType_File :
{
int len = strlen ( sPath ) ;
if ( len > 4 )
{
if ( strcmp ( sPath [ len - 4 ] , " .dat " ) = = 0 )
moving = 1 ;
else if ( move & & strcmp ( sPath [ len - 5 ] , " .ztmp " ) = = 0 )
moving = 2 ;
if ( moving )
{
if ( moving = = 2 )
{
Format ( sPath , sizeof ( sPath ) , " %s/%s " , sDir , sPath ) ;
ReplaceString ( sPath , sizeof ( sPath ) , " .ztmp " , " " ) ;
if ( FileExists ( sPath , true ) = = false )
{
moving = 0 ;
}
else
{
StrCat ( sPath , sizeof ( sPath ) , " .ztmp " ) ;
}
}
else
{
Format ( sPath , sizeof ( sPath ) , " %s/%s " , sDir , sPath ) ;
}
if ( moving )
{
hFile = OpenFile ( sPath , " rb " , false ) ;
if ( hFile )
{
hFile . Read ( iRead , sizeof ( iRead ) , 1 ) ;
delete hFile ;
if (
( iRead [ 0 ] = = 86 & & iRead [ 1 ] = = 84 & & iRead [ 2 ] = = 70 & & iRead [ 3 ] = = 0 ) | |
( moving = = 2 & & iRead [ 0 ] = = 76 & & iRead [ 1 ] = = 90 & & iRead [ 2 ] = = 83 & & iRead [ 3 ] = = 83 )
)
{
aList . PushString ( sPath ) ;
}
}
}
}
}
}
}
}
}
delete hDir ;
}
}
}
void MoveSprays ( )
{
int count , counts ;
ArrayList aList = new ArrayList ( ByteCountToCells ( PLATFORM_MAX_PATH ) ) ;
strcopy ( g_sMoveFiles , sizeof ( g_sMoveFiles ) , g_sDownloads ) ;
int pos = StrContains ( g_sMoveFiles , " / " ) ;
if ( pos ! = - 1 ) g_sMoveFiles [ pos ] = 0 ;
RecursiveFiles ( aList , true , g_sMoveFiles ) ;
RecursiveSearchDirs ( 0 , aList , count , counts , true ) ;
}
void RecursiveSearchDirs ( int client , ArrayList aList , int & count , int & counts , bool move = false )
{
static char sNew [ PLATFORM_MAX_PATH ] ;
static char sPath [ PLATFORM_MAX_PATH ] ;
File hFile ;
int iRead [ sizeof ( g_iVal ) ] ;
int len , pos , i ;
while ( aList . Length > 0 )
{
aList . GetString ( 0 , sPath , sizeof ( sPath ) ) ;
aList . Erase ( 0 ) ;
len = strlen ( sPath ) ;
if ( ( len > 4 & & strcmp ( sPath [ len - 4 ] , " .dat " ) = = 0 ) | | ( move & & len > 4 & & strcmp ( sPath [ len - 5 ] , " .ztmp " ) = = 0 ) )
{
if ( move )
{
pos = FindCharInString ( sPath , '/' , true ) ;
if ( pos ! = - 1 ) sPath [ pos ] = 0 ;
Format ( sNew , sizeof ( sNew ) , " %s/%s/%s " , g_sMoveFiles , PATH_BACKUP , sPath [ pos + 1 ] ) ;
if ( FileExists ( sNew , true ) ) DeleteFile ( sNew , true ) ;
if ( pos ! = - 1 ) sPath [ pos ] = '/' ;
RenameFile ( sNew , sPath , true ) ;
}
else
{
counts + + ;
hFile = OpenFile ( sPath , " rb " ) ;
if ( hFile )
{
hFile . Read ( iRead , sizeof ( iRead ) , 1 ) ;
delete hFile ;
i = ValFile ( iRead ) ;
if ( i ! = - 1 )
{
count + + ;
PrintToConsole ( client , " Invalid file: %s: %02d (%02X <> %02X) " , sPath , i , iRead [ i ] , g_iVal [ i ] ) ;
}
}
}
}
if ( g_iTotal + + > MAX_READ )
{
g_iTotal = 0 ;
DataPack dPack ;
CreateDataTimer ( 0.1 , TimerNext , dPack ) ;
dPack . WriteCell ( client ) ;
dPack . WriteCell ( aList ) ;
dPack . WriteCell ( count ) ;
dPack . WriteCell ( counts ) ;
dPack . WriteCell ( move ) ;
return ;
}
}
if ( aList . Length = = 0 )
{
if ( ! move )
{
g_bProc = false ;
ReplyToCommand ( client , " [Sprays] downloads checked. Found %d of %d invalid. Took %0.2f seconds. " , count , counts , GetEngineTime ( ) - g_fTime ) ;
delete aList ;
}
else
{
DeleteEmptyDirs ( g_sMoveFiles ) ;
delete aList ;
}
}
}
Action TimerNext ( Handle timer , DataPack dPack )
{
ArrayList aList ;
int client , count , counts ;
bool move ;
dPack . Reset ( ) ;
client = dPack . ReadCell ( ) ;
aList = dPack . ReadCell ( ) ;
count = dPack . ReadCell ( ) ;
counts = dPack . ReadCell ( ) ;
move = dPack . ReadCell ( ) ;
RecursiveSearchDirs ( client , aList , count , counts , move ) ;
return Plugin_Continue ;
}
void DeleteEmptyDirs ( const char sDir [ PLATFORM_MAX_PATH ] )
{
FileType type ;
DirectoryListing hDir ;
bool del = true ;
if ( DirExists ( sDir ) )
{
hDir = OpenDirectory ( sDir , true ) ;
if ( hDir )
{
char sPath [ PLATFORM_MAX_PATH ] ;
while ( ReadDirEntry ( hDir , sPath , sizeof ( sPath ) , type ) )
{
if ( strcmp ( sPath , " . " ) & & strcmp ( sPath , " .. " ) )
{
switch ( type )
{
case FileType_Directory :
{
Format ( sPath , sizeof ( sPath ) , " %s/%s " , sDir , sPath ) ;
DeleteEmptyDirs ( sPath ) ;
del = false ;
}
case FileType_File :
{
del = false ;
}
}
}
}
delete hDir ;
}
if ( del )
{
RemoveDir ( sDir ) ;
}
}
}
Action PlayerDecal ( const char [ ] te_name , const int [ ] Players , int numClients , float delay )
{
if ( g_bDecal ) return Plugin_Continue ;
int client = TE_ReadNum ( " m_nPlayer " ) ;
if ( ! client | | ! IsClientInGame ( client ) | | ! CheckCommandAccess ( client , " sm_sprays_allowed " , 0 , true ) )
{
return Plugin_Handled ;
}
if ( IsFakeClient ( client ) )
{
return Plugin_Continue ;
}
g_sFilename [ 0 ] = 0 ;
GetPlayerDecalFile ( client , g_sFilename , sizeof ( g_sFilename ) ) ;
bool val ;
if ( g_sFilename [ 0 ] )
{
char cc [ 6 ] ;
ReplaceString ( g_sFilename , sizeof ( g_sFilename ) , g_sDownloads , " " ) ;
ReplaceString ( g_sFilename , sizeof ( g_sFilename ) , " .dat " , " " ) ;
Format ( cc , sizeof ( cc ) , " /%c%c/ " , g_sFilename [ 0 ] , g_sFilename [ 1 ] ) ;
Format ( g_sFilename , sizeof ( g_sFilename ) , " %s%s.dat " , g_sDownloads , g_sFilename ) ;
ReplaceString ( g_sFilename , sizeof ( g_sFilename ) , " /cc/ " , cc ) ;
if ( ! g_smChecked . GetValue ( g_sFilename , val ) )
{
FileCheck ( ) ;
g_smChecked . GetValue ( g_sFilename , val ) ;
}
}
if ( ! val )
{
static char auth [ 64 ] ;
if ( g_sAuth [ client ] [ 6 ] = = 'I' )
Format ( auth , sizeof ( auth ) , " Unverified: %s " , g_sAuthUnverified [ client ] ) ;
else
Format ( auth , sizeof ( auth ) , " %s " , g_sAuth [ client ] ) ;
if ( FileExists ( g_sFilename ) )
{
if ( GetGameTime ( ) - g_fSprayed [ client ] > TIMEOUT_LOG )
{
g_fSprayed [ client ] = GetGameTime ( ) ;
if ( g_hCvarLog . IntValue ) LogCustom ( " Blocked invalid spray: %s from (%N) [%s] " , g_sFilename , client , auth ) ;
if ( g_hCvarMsg . IntValue ) PrintToServer ( " [Spray Exploit] Blocked invalid spray: %s from (%N) [%s] " , g_sFilename , client , auth ) ;
}
if ( g_hCvarPunish . IntValue = = 1 | | g_hCvarPunish . IntValue > = 3 )
TestClient ( client ) ;
}
else
{
if ( GetGameTime ( ) - g_fSprayed [ client ] > TIMEOUT_LOG & & ! g_smWaiting . GetValue ( g_sAuthUnverified [ client ] , val ) )
{
g_fSprayed [ client ] = GetGameTime ( ) ;
g_smWaiting . SetValue ( g_sAuthUnverified [ client ] , true ) ;
if ( g_hCvarLog . IntValue ) LogCustom ( " Blocked unchecked spray - missing file: %s from (%N) [%s] " , g_sFilename , client , auth ) ;
if ( g_hCvarMsg . IntValue = = 1 ) PrintToServer ( " [Spray Exploit] Blocked unchecked spray - missing file: %s from (%N) [%s] " , g_sFilename , client , auth ) ;
}
}
float vPos [ 3 ] ;
TE_ReadVector ( " m_vecOrigin " , vPos ) ;
DataPack hPack = new DataPack ( ) ;
hPack . WriteCell ( GetClientUserId ( client ) ) ;
hPack . WriteFloat ( vPos [ 0 ] ) ;
hPack . WriteFloat ( vPos [ 1 ] ) ;
hPack . WriteFloat ( vPos [ 2 ] ) ;
RequestFrame ( ReqTempEnt , hPack ) ;
return Plugin_Handled ;
}
return Plugin_Continue ;
}
void ReqTempEnt ( DataPack hPack )
{
hPack . Reset ( ) ;
int client = hPack . ReadCell ( ) ;
client = GetClientOfUserId ( client ) ;
if ( client )
{
float vPos [ 3 ] ;
vPos [ 0 ] = hPack . ReadFloat ( ) ;
vPos [ 1 ] = hPack . ReadFloat ( ) ;
vPos [ 2 ] = hPack . ReadFloat ( ) ;
g_bDecal = true ;
TE_Start ( " Player Decal " ) ;
TE_WriteVector ( " m_vecOrigin " , vPos ) ;
TE_WriteNum ( " m_nEntity " , 0 ) ;
TE_WriteNum ( " m_nPlayer " , client ) ;
TE_SendToClient ( client ) ;
g_bDecal = false ;
}
delete hPack ;
}
int GetClientFromSpray ( )
{
char hex [ 10 ] ;
for ( int i = 1 ; i < = MaxClients ; i + + )
{
if ( IsClientInGame ( i ) )
{
hex [ 0 ] = 0 ;
GetPlayerDecalFile ( i , hex , sizeof ( hex ) ) ;
if ( hex [ 0 ] & & StrContains ( g_sFilename , hex ) ! = - 1 )
return i ;
}
}
return 0 ;
}
int GetClientFromJingle ( )
{
char hex [ 10 ] ;
for ( int i = 1 ; i < = MaxClients ; i + + )
{
if ( IsClientInGame ( i ) )
{
hex [ 0 ] = 0 ;
GetPlayerJingleFile ( i , hex , sizeof ( hex ) ) ;
if ( hex [ 0 ] & & StrContains ( g_sFilename , hex ) ! = - 1 )
return i ;
}
}
return 0 ;
}
void TestClient ( int client )
{
if ( g_iEngine ! = Engine_TF2 & & client )
{
if ( g_hCvarBan . IntValue )
{
int iDuration = g_hCvarBanTime . IntValue ;
if ( g_bSourceBans )
SBPP_BanPlayer ( 0 , client , iDuration , " Invalid spray " ) ;
else if ( g_bMaterialAdmin )
MABanPlayer ( 0 , client , MA_BAN_STEAM , iDuration , " Invalid spray " ) ;
else
BanClient ( client , iDuration , BANFLAG_AUTO , " Invalid spray " ) ;
LogAction ( client , - 1 , " [Spray Exploit] %N %s was banned %d minutes for invalid Spray " , client , g_sAuthUnverified [ client ] , iDuration ) ;
return ;
}
else if ( g_hCvarKick . IntValue )
{
KickClient ( client , " Invalid spray. Please change it " ) ;
LogAction ( client , - 1 , " [Spray Exploit] %N %s was kicked for invalid Spray. " , client , g_sAuthUnverified [ client ] ) ;
return ;
}
}
}
public Action OnFileReceive ( int client , const char [ ] sFile )
{
strcopy ( g_sFilename , sizeof ( g_sFilename ) , sFile ) ;
client = GetClientFromSpray ( ) ;
if ( ! client ) client = GetClientFromJingle ( ) ;
bool log ;
if ( client )
{
static char sPath [ PLATFORM_MAX_PATH ] ;
GetPlayerDecalFile ( client , sPath , sizeof ( sPath ) ) ;
if ( strcmp ( sPath , g_sPath1 [ client ] ) )
{
ReplaceString ( sPath , sizeof ( sPath ) , g_sDownloads , " " ) ;
strcopy ( g_sPath1 [ client ] , sizeof ( g_sPath1 [ ] ) , sPath ) ;
log = true ;
}
GetPlayerJingleFile ( client , sPath , sizeof ( sPath ) ) ;
if ( strcmp ( sPath , g_sPath2 [ client ] ) )
{
ReplaceString ( sPath , sizeof ( sPath ) , g_sDownloads , " " ) ;
strcopy ( g_sPath2 [ client ] , sizeof ( g_sPath2 [ ] ) , sPath ) ;
log = true ;
}
}
else
{
log = true ;
}
if ( log & & g_hCvarLog . IntValue = = 1 )
{
if ( client )
{
static char auth [ 64 ] ;
if ( g_sAuth [ client ] [ 6 ] = = 'I' )
Format ( auth , sizeof ( auth ) , " Unverified: %s " , g_sAuthUnverified [ client ] ) ;
else
Format ( auth , sizeof ( auth ) , " %s " , g_sAuth [ client ] ) ;
LogCustom ( " File received: %s from (%N) [%s] " , sFile , client , auth ) ;
}
else
{
int val ;
if ( ! g_smReceive . GetValue ( sFile , val ) )
{
g_smReceive . SetValue ( sFile , true ) ;
LogCustom ( " File received: %s " , sFile ) ;
}
}
}
return Plugin_Continue ;
}
public Action OnFileSend ( int client , const char [ ] sFile )
{
strcopy ( g_sFilename , sizeof ( g_sFilename ) , sFile ) ;
bool val ;
if ( g_smChecked . GetValue ( sFile , val ) )
{
if ( ! val ) return Plugin_Handled ;
} else {
FileCheck ( ) ;
if ( g_smChecked . GetValue ( sFile , val ) )
{
if ( ! val ) return Plugin_Handled ;
}
}
return Plugin_Continue ;
}
void FileCheck ( )
{
if ( FileExists ( g_sFilename ) )
{
int len = strlen ( g_sFilename ) ;
if ( len > 4 & & strcmp ( g_sFilename [ len - 4 ] , " .dat " ) = = 0 )
{
int iRead [ sizeof ( g_iVal ) ] ;
File hFile = OpenFile ( g_sFilename , " rb " , false ) ;
if ( hFile )
{
hFile . Read ( iRead , sizeof ( iRead ) , 1 ) ;
delete hFile ;
int i = ValFile ( iRead ) ;
if ( i ! = - 1 )
{
int client = GetClientFromSpray ( ) ;
if ( ! client ) client = GetClientFromJingle ( ) ;
if ( client )
{
static char auth [ 64 ] ;
if ( g_sAuth [ client ] [ 6 ] = = 'I' )
Format ( auth , sizeof ( auth ) , " Unverified: %s " , g_sAuthUnverified [ client ] ) ;
else
Format ( auth , sizeof ( auth ) , " %s " , g_sAuth [ client ] ) ;
if ( g_hCvarLog . IntValue ) LogCustom ( " Invalid spray: %s from (%N) [%s] " , g_sFilename , client , auth ) ;
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 ) ;
} else {
if ( g_hCvarLog . IntValue ) LogCustom ( " Invalid spray: %s " , g_sFilename ) ;
if ( g_hCvarMsg . IntValue ) PrintToServer ( " [Spray Exploit] Invalid spray: %s: %02d (%02X <> %02X) " , g_sFilename , i , iRead [ i ] , g_iVal [ i ] ) ;
}
if ( g_hCvarPunish . IntValue > = 2 )
TestClient ( client ) ;
g_smChecked . SetValue ( g_sFilename , false ) ;
return ;
}
g_smChecked . SetValue ( g_sFilename , true ) ;
} else {
if ( g_hCvarLog . IntValue ) LogCustom ( " Missing file: %s " , g_sFilename ) ;
if ( g_hCvarMsg . IntValue = = 1 ) PrintToServer ( " [Spray Exploit] Missing file: %s " , g_sFilename ) ;
}
}
}
}
int ValFile ( int iRead [ sizeof ( g_iVal ) ] )
{
//this was suggested by madness to solve new spray exploit december 2024.
//he was a bit vague, just saying header[21] == 42 && header[24] > 1 has to be blocked
if ( iRead [ 21 ] = = 42 & & iRead [ 24 ] > 1 )
{
LogMessage ( " blocked spray with madness option. " ) ;
2024-12-13 18:48:30 +01:00
return 21 ; //should not return -1, should return the iRead value probably.
2024-12-10 12:34:45 +01:00
}
if ( iRead [ 0 ] = = 82 & & iRead [ 1 ] = = 73 & & iRead [ 2 ] = = 70 & & iRead [ 3 ] = = 70 & & iRead [ 8 ] = = 87 & & iRead [ 9 ] = = 65 & & iRead [ 10 ] = = 86 & & iRead [ 11 ] = = 69 )
{
if ( iRead [ 34 ] + iRead [ 35 ] * 256 = = 32 )
return 34 ;
return - 1 ;
}
char bytes [ 10 ] ;
bool read = true ;
int n ;
for ( int i = 0 ; i < sizeof ( g_iVal ) ; i + + )
{
if ( g_iVal [ i ] = = 42 )
{
switch ( i )
{
case 8 : read = iRead [ i ] < = 5 ;
case 16 , 18 :
{
Format ( bytes , sizeof ( bytes ) , " %02X%02X " , iRead [ i + 1 ] , iRead [ i ] ) ;
n = HexToDec ( bytes ) ;
if ( n < 0 | | n > 8192 ) read = false ;
}
case 20 :
{
Format ( bytes , sizeof ( bytes ) , " %02X%02X%02X%02X " , iRead [ i + 3 ] , iRead [ i + 2 ] , iRead [ i + 1 ] , iRead [ i ] ) ;
n = HexToDec ( bytes ) ;
if ( n & ( 0x8000 | 0x10000 | 0x800000 ) ) read = false ;
}
/ *
case 25 :
{
if ( iRead [ i ] > 0 ) read = false ;
}
// */
}
}
else if ( i < 27 )
{
read = iRead [ i ] = = g_iVal [ i ] ;
}
if ( ! read ) return i ;
}
return - 1 ;
}
int HexToDec ( char [ ] bytes )
{
int len = strlen ( bytes ) ;
int base = 1 ;
int value = 0 ;
for ( int i = len - 1 ; i > = 0 ; i - - )
{
if ( bytes [ i ] > = '0' & & bytes [ i ] < = '9' )
{
value + = ( bytes [ i ] - 48 ) * base ;
base = base * 16 ;
}
else if ( bytes [ i ] > = 'A' & & bytes [ i ] < = 'F' )
{
value + = ( bytes [ i ] - 55 ) * base ;
base = base * 16 ;
}
}
return value ;
}
void LogCustom ( const char [ ] format , any . . . )
{
static char buffer [ 512 ] ;
VFormat ( buffer , sizeof ( buffer ) , format , 2 ) ;
static char sPath [ PLATFORM_MAX_PATH ] , sTime [ 32 ] ;
BuildPath ( Path_SM , sPath , sizeof ( sPath ) , " logs/spray_downloads.log " ) ;
File file = OpenFile ( sPath , " a+ " ) ;
FormatTime ( sTime , sizeof ( sTime ) , " %d-%b-%Y - %H:%M:%S " ) ;
file . WriteLine ( " %s: %s " , sTime , buffer ) ;
FlushFile ( file ) ;
delete file ;
}