/* 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 .
*/
#include
#include
#include
#undef REQUIRE_PLUGIN
#include
#include
#include
#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 ");
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(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(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(result);
}
public int Native_WithinThreshold(Handle plugin, int numParams)
{
float f1 = GetNativeCell(1);
float f2 = GetNativeCell(2);
float threshold = GetNativeCell(3);
return view_as(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);
}