415 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
/*  Oryx AC: collects and analyzes statistics to find some cheaters in CS:S, CS:GO, and TF2 bunnyhop.
 | 
						|
 *  Copyright (C) 2018  Nolan O.
 | 
						|
 *  Copyright (C) 2018  shavit.
 | 
						|
 *
 | 
						|
 *  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/>.
 | 
						|
*/
 | 
						|
 | 
						|
#include <sourcemod>
 | 
						|
#include <sdktools>
 | 
						|
#include <oryx>
 | 
						|
 | 
						|
#undef REQUIRE_PLUGIN
 | 
						|
#include <shavit>
 | 
						|
#include <bTimes-tas>
 | 
						|
#include <bTimes-timer_hack>
 | 
						|
 | 
						|
#pragma newdecls required
 | 
						|
#pragma semicolon 1
 | 
						|
 | 
						|
enum
 | 
						|
{
 | 
						|
	Timer_None,
 | 
						|
	Timer_Shavit,
 | 
						|
	Timer_Blacky2,
 | 
						|
	Timer_Blacky183
 | 
						|
}
 | 
						|
 | 
						|
int gI_Timer = Timer_None;
 | 
						|
char gS_SpecialString[128];
 | 
						|
 | 
						|
ConVar gCV_AllowBypass = null;
 | 
						|
 | 
						|
EngineVersion gEV_Type = Engine_Unknown;
 | 
						|
 | 
						|
Handle gH_Forwards_OnTrigger = null;
 | 
						|
 | 
						|
char gS_LogPath[PLATFORM_MAX_PATH];
 | 
						|
char gS_BeepSound[PLATFORM_MAX_PATH];
 | 
						|
bool gB_NoSound = false;
 | 
						|
 | 
						|
bool gB_Testing[MAXPLAYERS+1];
 | 
						|
bool gB_Locked[MAXPLAYERS+1];
 | 
						|
 | 
						|
public Plugin myinfo = 
 | 
						|
{
 | 
						|
	name = "ORYX bunnyhop anti-cheat",
 | 
						|
	author = "Rusty, shavit",
 | 
						|
	description = "Cheat detection interface.",
 | 
						|
	version = ORYX_VERSION,
 | 
						|
	url = "https://github.com/shavitush/Oryx-AC"
 | 
						|
}
 | 
						|
 | 
						|
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
 | 
						|
{
 | 
						|
	CreateNative("Oryx_CanBypass", Native_CanBypass);
 | 
						|
	CreateNative("Oryx_Trigger", Native_OryxTrigger);
 | 
						|
	CreateNative("Oryx_WithinThreshold", Native_WithinThreshold);
 | 
						|
	CreateNative("Oryx_PrintToAdmins", Native_PrintToAdmins);
 | 
						|
	CreateNative("Oryx_PrintToAdminsConsole", Native_PrintToAdminsConsole);
 | 
						|
	CreateNative("Oryx_LogMessage", Native_LogMessage);
 | 
						|
 | 
						|
	// registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins
 | 
						|
	RegPluginLibrary("oryx");
 | 
						|
 | 
						|
	return APLRes_Success;
 | 
						|
}
 | 
						|
 | 
						|
public void OnAllPluginsLoaded()
 | 
						|
{
 | 
						|
	// workaround
 | 
						|
	if(gI_Timer == Timer_None &&
 | 
						|
		GetFeatureStatus(FeatureType_Native, "GetClientStyle") == FeatureStatus_Available &&
 | 
						|
		GetFeatureStatus(FeatureType_Native, "Style_GetConfig") == FeatureStatus_Available)
 | 
						|
	{
 | 
						|
		gI_Timer = Timer_Blacky183;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
public void OnPluginStart()
 | 
						|
{
 | 
						|
	gH_Forwards_OnTrigger = CreateGlobalForward("Oryx_OnTrigger", ET_Event, Param_Cell, Param_CellByRef, Param_String);
 | 
						|
 | 
						|
	gEV_Type = GetEngineVersion();
 | 
						|
 | 
						|
	gCV_AllowBypass = CreateConVar("oryx_allow_bypass", "1", "Allow specific styles to bypass Oryx? Refer to README.md for information.", 0, true, 0.0, true, 1.0);
 | 
						|
 | 
						|
	CreateConVar("oryx_version", ORYX_VERSION, "Plugin version.", (FCVAR_NOTIFY | FCVAR_DONTRECORD));
 | 
						|
	
 | 
						|
	RegAdminCmd("sm_otest", Command_OryxTest, ADMFLAG_BAN, "Enables the TRIGGER_TEST detection level.");
 | 
						|
	RegAdminCmd("sm_lock", Command_LockPlayer, ADMFLAG_BAN, "Disables movement for a player.");
 | 
						|
 | 
						|
	LoadTranslations("common.phrases");
 | 
						|
	
 | 
						|
	BuildPath(Path_SM, gS_LogPath, PLATFORM_MAX_PATH, "logs/oryx-ac.log");
 | 
						|
 | 
						|
	if(LibraryExists("shavit"))
 | 
						|
	{
 | 
						|
		gI_Timer = Timer_Shavit;
 | 
						|
	}
 | 
						|
 | 
						|
	else if(LibraryExists("tas"))
 | 
						|
	{
 | 
						|
		gI_Timer = Timer_Blacky2;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
public void OnLibraryAdded(const char[] name)
 | 
						|
{
 | 
						|
	if(StrEqual(name, "shavit"))
 | 
						|
	{
 | 
						|
		gI_Timer = Timer_Shavit;
 | 
						|
	}
 | 
						|
 | 
						|
	else if(StrEqual(name, "tas"))
 | 
						|
	{
 | 
						|
		gI_Timer = Timer_Blacky2;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
public void OnLibraryRemoved(const char[] name)
 | 
						|
{
 | 
						|
	if((StrEqual(name, "shavit") && gI_Timer == Timer_Shavit) ||
 | 
						|
		(StrEqual(name, "tas") && gI_Timer == Timer_Blacky2))
 | 
						|
	{
 | 
						|
		gI_Timer = Timer_None;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
public void OnMapStart()
 | 
						|
{
 | 
						|
	// Beep sounds.
 | 
						|
	Handle hConfig = LoadGameConfigFile("funcommands.games");
 | 
						|
 | 
						|
	if(hConfig == null)
 | 
						|
	{
 | 
						|
		SetFailState("Unable to load game config funcommands.games");
 | 
						|
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	
 | 
						|
	if(GameConfGetKeyValue(hConfig, "SoundBeep", gS_BeepSound, PLATFORM_MAX_PATH))
 | 
						|
	{
 | 
						|
		PrecacheSound(gS_BeepSound, true);
 | 
						|
	}
 | 
						|
 | 
						|
	delete hConfig;
 | 
						|
}
 | 
						|
 | 
						|
public void OnClientPutInServer(int client)
 | 
						|
{
 | 
						|
	gB_Locked[client] = false;
 | 
						|
	gB_Testing[client] = false;
 | 
						|
}
 | 
						|
 | 
						|
public Action Command_OryxTest(int client, int args)
 | 
						|
{
 | 
						|
	gB_Testing[client] = !gB_Testing[client];
 | 
						|
	ReplyToCommand(client, "Testing is %s.", (gB_Testing[client])? "on":"off");
 | 
						|
 | 
						|
	return Plugin_Handled;
 | 
						|
}
 | 
						|
 | 
						|
public Action Command_LockPlayer(int client, int args)
 | 
						|
{
 | 
						|
	if(args < 1)
 | 
						|
	{
 | 
						|
		ReplyToCommand(client, "Usage: sm_lock <target>");
 | 
						|
 | 
						|
		return Plugin_Handled;
 | 
						|
	}
 | 
						|
	
 | 
						|
	char[] sArgs = new char[MAX_TARGET_LENGTH];
 | 
						|
	GetCmdArgString(sArgs, MAX_TARGET_LENGTH);
 | 
						|
 | 
						|
	int target = FindTarget(client, sArgs);
 | 
						|
 | 
						|
	if(target == -1)
 | 
						|
	{
 | 
						|
		return Plugin_Handled;
 | 
						|
	}
 | 
						|
	
 | 
						|
	gB_Locked[target] = !gB_Locked[target];
 | 
						|
	ReplyToCommand(client, "Player has been %s.", (gB_Locked[target])? "locked":"unlocked");
 | 
						|
	PrintToChat(target, "An admin has %s your ability to move!", (gB_Locked[target])? "locked":"unlocked");
 | 
						|
 | 
						|
	return Plugin_Handled;
 | 
						|
}
 | 
						|
 | 
						|
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3])
 | 
						|
{
 | 
						|
	// Movement is locked, don't allow anything.
 | 
						|
	if(gB_Locked[client])
 | 
						|
	{
 | 
						|
		buttons = 0;
 | 
						|
		vel[0] = 0.0;
 | 
						|
		vel[1] = 0.0;
 | 
						|
		impulse = 0;
 | 
						|
 | 
						|
		return Plugin_Changed;
 | 
						|
	}
 | 
						|
 | 
						|
	return Plugin_Continue;
 | 
						|
}
 | 
						|
 | 
						|
public int Native_CanBypass(Handle plugin, int numParams)
 | 
						|
{
 | 
						|
	if(!gCV_AllowBypass.BoolValue)
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	int client = GetNativeCell(1);
 | 
						|
 | 
						|
	switch(gI_Timer)
 | 
						|
	{
 | 
						|
		case Timer_Shavit:
 | 
						|
		{
 | 
						|
			Shavit_GetStyleStrings(Shavit_GetBhopStyle(client), sSpecialString, gS_SpecialString, 128);
 | 
						|
 | 
						|
			if(StrContains(gS_SpecialString, "oryx_bypass", false) != -1)
 | 
						|
			{
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		case Timer_Blacky2:
 | 
						|
		{
 | 
						|
			return TAS_InEditMode(client);
 | 
						|
		}
 | 
						|
 | 
						|
		case Timer_Blacky183:
 | 
						|
		{
 | 
						|
			any styleconfig[StyleConfig];
 | 
						|
			Style_GetConfig(GetClientStyle(client), styleconfig);
 | 
						|
 | 
						|
			if(StrContains(styleconfig[Special_Key], "oryx_bypass", false) != -1)
 | 
						|
			{
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
public int Native_OryxTrigger(Handle plugin, int numParams)
 | 
						|
{
 | 
						|
	int client = GetNativeCell(1);
 | 
						|
	int level = GetNativeCell(2);
 | 
						|
	
 | 
						|
	char[] sLevel = new char[16];
 | 
						|
	char[] sCheatDescription = new char[1024];
 | 
						|
 | 
						|
	GetNativeString(3, sCheatDescription, 1024);
 | 
						|
 | 
						|
	Action result = Plugin_Continue;
 | 
						|
	Call_StartForward(gH_Forwards_OnTrigger);
 | 
						|
	Call_PushCell(client);
 | 
						|
	Call_PushCellRef(level);
 | 
						|
	Call_PushStringEx(sCheatDescription, 1024, SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
 | 
						|
	Call_Finish(result);
 | 
						|
 | 
						|
	if(result == Plugin_Stop)
 | 
						|
	{
 | 
						|
		return view_as<int>(Plugin_Stop);
 | 
						|
	}
 | 
						|
 | 
						|
	if(level == TRIGGER_LOW)
 | 
						|
	{
 | 
						|
		strcopy(sLevel, 16, "LOW");
 | 
						|
		gB_NoSound = true; // Don't play the annoying beep sound for LOW detections.
 | 
						|
	}
 | 
						|
 | 
						|
	else if(level == TRIGGER_MEDIUM)
 | 
						|
	{
 | 
						|
		strcopy(sLevel, 16, "MEDIUM");
 | 
						|
	}
 | 
						|
 | 
						|
	else if(level == TRIGGER_HIGH)
 | 
						|
	{
 | 
						|
		strcopy(sLevel, 16, "HIGH");
 | 
						|
 | 
						|
		if(result != Plugin_Handled)
 | 
						|
		{
 | 
						|
			//KickClient(client, "[ORYX] %s", sCheatDescription);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	else if(level == TRIGGER_HIGH_NOKICK)
 | 
						|
	{
 | 
						|
		strcopy(sLevel, 16, "HIGH-NOKICK");
 | 
						|
	}
 | 
						|
 | 
						|
	else if(level == TRIGGER_DEFINITIVE)
 | 
						|
	{
 | 
						|
		strcopy(sLevel, 16, "DEFINITIVE");
 | 
						|
 | 
						|
		if(result != Plugin_Handled)
 | 
						|
		{
 | 
						|
			//KickClient(client, "[ORYX] %s", sCheatDescription);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	else if(level == TRIGGER_TEST)
 | 
						|
	{
 | 
						|
		char[] sBuffer = new char[128];
 | 
						|
		Format(sBuffer, 128, "(\x03%N\x01) - %s | Level: \x04TESTING", client, sCheatDescription);
 | 
						|
 | 
						|
		for(int i = 1; i <= MaxClients; i++)
 | 
						|
		{
 | 
						|
			if(gB_Testing[i] && IsClientInGame(i))
 | 
						|
			{
 | 
						|
				PrintToChat(i, "%s", sBuffer);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return view_as<int>(result);
 | 
						|
	}
 | 
						|
 | 
						|
	char[] sAuth = new char[32];
 | 
						|
	GetClientAuthId(client, AuthId_Steam3, sAuth, 32);
 | 
						|
 | 
						|
	char[] sBuffer = new char[128];
 | 
						|
	Format(sBuffer, 128, "\x03%N\x01 - \x05%s\x01 Cheat: %s | Level: %s", client, sAuth, sCheatDescription, sLevel);
 | 
						|
	Oryx_PrintToAdmins("%s", sBuffer);
 | 
						|
	
 | 
						|
	LogToFileEx(gS_LogPath, "%L - Cheat: %s | Level: %s", client, sCheatDescription, sLevel);
 | 
						|
 | 
						|
	return view_as<int>(result);
 | 
						|
}
 | 
						|
 | 
						|
public int Native_WithinThreshold(Handle plugin, int numParams)
 | 
						|
{
 | 
						|
	float f1 = GetNativeCell(1);
 | 
						|
	float f2 = GetNativeCell(2);
 | 
						|
	float threshold = GetNativeCell(3);
 | 
						|
 | 
						|
	return view_as<int>(FloatAbs(f1 - f2) <= threshold);
 | 
						|
}
 | 
						|
 | 
						|
public int Native_PrintToAdmins(Handle plugin, int numParams)
 | 
						|
{
 | 
						|
	static int iWritten = 0; // Useless?
 | 
						|
 | 
						|
	char[] sBuffer = new char[300];
 | 
						|
	FormatNativeString(0, 1, 2, 300, iWritten, sBuffer);
 | 
						|
 | 
						|
	for(int i = 1; i <= MaxClients; i++)
 | 
						|
	{
 | 
						|
		if(CheckCommandAccess(i, "oryx_admin", ADMFLAG_GENERIC))
 | 
						|
		{
 | 
						|
			PrintToChat(i, "%s\x04[ORYX]\x01 %s", (gEV_Type == Engine_CSGO)? " ":"", sBuffer);
 | 
						|
 | 
						|
			if(!gB_NoSound)
 | 
						|
			{
 | 
						|
				if(gEV_Type == Engine_CSS || gEV_Type == Engine_TF2)
 | 
						|
				{
 | 
						|
					EmitSoundToClient(i, gS_BeepSound);
 | 
						|
				}
 | 
						|
 | 
						|
				else
 | 
						|
				{
 | 
						|
					ClientCommand(i, "play */%s", gS_BeepSound);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	gB_NoSound = false;
 | 
						|
}
 | 
						|
 | 
						|
public int Native_PrintToAdminsConsole(Handle plugin, int numParams)
 | 
						|
{
 | 
						|
	static int iWritten = 0; // Useless?
 | 
						|
 | 
						|
	char[] sBuffer = new char[300];
 | 
						|
	FormatNativeString(0, 1, 2, 300, iWritten, sBuffer);
 | 
						|
 | 
						|
	for(int i = 1; i <= MaxClients; i++)
 | 
						|
	{
 | 
						|
		if(CheckCommandAccess(i, "oryx_admin", ADMFLAG_GENERIC))
 | 
						|
		{
 | 
						|
			PrintToConsole(i, "[ORYX] %s", sBuffer);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
public int Native_LogMessage(Handle plugin, int numParams)
 | 
						|
{
 | 
						|
	char[] sPlugin = new char[32];
 | 
						|
 | 
						|
	if(!GetPluginInfo(plugin, PlInfo_Name, sPlugin, 32))
 | 
						|
	{
 | 
						|
		GetPluginFilename(plugin, sPlugin, 32);
 | 
						|
	}
 | 
						|
 | 
						|
	static int iWritten = 0; // Useless?
 | 
						|
 | 
						|
	char[] sBuffer = new char[300];
 | 
						|
	FormatNativeString(0, 1, 2, 300, iWritten, sBuffer);
 | 
						|
	
 | 
						|
	LogToFileEx(gS_LogPath, "[%s] %s", sPlugin, sBuffer);
 | 
						|
}
 |