2022-06-11 23:02:43 +02:00
|
|
|
/* 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];
|
2022-06-11 23:24:33 +02:00
|
|
|
char[] sCheatDescription = new char[1024];
|
2022-06-11 23:02:43 +02:00
|
|
|
|
2022-06-11 23:24:33 +02:00
|
|
|
GetNativeString(3, sCheatDescription, 1024);
|
2022-06-11 23:02:43 +02:00
|
|
|
|
|
|
|
Action result = Plugin_Continue;
|
|
|
|
Call_StartForward(gH_Forwards_OnTrigger);
|
|
|
|
Call_PushCell(client);
|
|
|
|
Call_PushCellRef(level);
|
2022-06-11 23:24:33 +02:00
|
|
|
Call_PushStringEx(sCheatDescription, 1024, SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
|
2022-06-11 23:02:43 +02:00
|
|
|
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);
|
|
|
|
}
|