#pragma semicolon 1 #include #include #include #include #include #pragma newdecls required #define COMMAND_SIZE 1024 // bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ) Handle g_hAcceptInput; StringMap g_Rules; ArrayList g_aRules; ArrayList g_Regexes; ArrayList g_RegexRules; enum { MODE_NONE = 0, MODE_ALL = 1, MODE_STRVALUE = 2, MODE_INTVALUE = 4, MODE_FLOATVALUE = 8, MODE_REGEXVALUE = 16, MODE_MIN = 32, MODE_MAX = 64, MODE_ALLOW = 128, MODE_DENY = 256, // Reverse MODE_CLAMP = 512, STATE_NONE = 0, STATE_ALLOW = 1, STATE_DENY = 2, STATE_CLAMPMIN = 4, STATE_CLAMPMAX = 8 }; public Plugin myinfo = { name = "PointServerCommandFilter", author = "BotoX", description = "Filters point_servercommand->Command() using user-defined rules to restrict maps.", version = "1.0", url = "" }; public void OnPluginStart() { Handle hGameConf = LoadGameConfigFile("sdktools.games"); if(hGameConf == INVALID_HANDLE) { SetFailState("Couldn't load sdktools game config!"); return; } int Offset = GameConfGetOffset(hGameConf, "AcceptInput"); g_hAcceptInput = DHookCreate(Offset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity, AcceptInput); DHookAddParam(g_hAcceptInput, HookParamType_CharPtr); DHookAddParam(g_hAcceptInput, HookParamType_CBaseEntity); DHookAddParam(g_hAcceptInput, HookParamType_CBaseEntity); DHookAddParam(g_hAcceptInput, HookParamType_Object, 20, DHookPass_ByVal|DHookPass_ODTOR|DHookPass_OCTOR|DHookPass_OASSIGNOP); //varaint_t is a union of 12 (float[3]) plus two int type params 12 + 8 = 20 DHookAddParam(g_hAcceptInput, HookParamType_Int); CloseHandle(hGameConf); /* Late Load */ int entity = INVALID_ENT_REFERENCE; while((entity = FindEntityByClassname(entity, "point_servercommand")) != INVALID_ENT_REFERENCE) { OnEntityCreated(entity, "point_servercommand"); } LoadConfig(); } public void OnEntityCreated(int entity, const char[] classname) { if(StrEqual(classname, "point_servercommand")) { DHookEntity(g_hAcceptInput, false, entity); } } // bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ) public MRESReturn AcceptInput(int pThis, Handle hReturn, Handle hParams) { char szInputName[128]; DHookGetParamString(hParams, 1, szInputName, sizeof(szInputName)); if(!StrEqual(szInputName, "Command", false)) return MRES_Ignored; int client = 0; if(!DHookIsNullParam(hParams, 2)) client = DHookGetParam(hParams, 2); char sCommand[COMMAND_SIZE]; DHookGetParamObjectPtrString(hParams, 4, 0, ObjectValueType_String, sCommand, sizeof(sCommand)); int bReplaced = 0; if(client > 0 && client <= MaxClients && IsClientInGame(client)) { char sName[MAX_NAME_LENGTH]; GetClientName(client, sName, sizeof(sName)); char sSteamId[32]; GetClientAuthId(client, AuthId_Engine, sSteamId, sizeof(sSteamId)); char sUserID[32]; FormatEx(sUserID, sizeof(sUserID), "#%d", GetClientUserId(client)); char sTeam[32]; if(GetClientTeam(client) == CS_TEAM_CT) strcopy(sTeam, sizeof(sTeam), "@ct"); else if(GetClientTeam(client) == CS_TEAM_T) strcopy(sTeam, sizeof(sTeam), "@t"); bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.name", sName, false); bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.steamid", sSteamId, false); bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator.team", sTeam, false); bReplaced += ReplaceString(sCommand, sizeof(sCommand), "!activator", sUserID, false); } Action iAction = PointServerCommandForward(sCommand); if(iAction == Plugin_Stop) { DHookSetReturn(hReturn, false); return MRES_Supercede; } else if(iAction == Plugin_Changed || GetEngineVersion() == Engine_CSGO || bReplaced) { ServerCommand(sCommand); DHookSetReturn(hReturn, true); return MRES_Supercede; } return MRES_Ignored; } Action PointServerCommandForward(char[] sOrigCommand) { static char sCommandRight[1024]; static char sCommandLeft[128]; strcopy(sCommandRight, sizeof(sCommandRight), sOrigCommand); TrimString(sCommandRight); int Split = SplitString(sCommandRight, " ", sCommandLeft, sizeof(sCommandLeft)); if(Split == -1) { strcopy(sCommandLeft, sizeof(sCommandLeft), sCommandRight); Split = 0; } TrimString(sCommandLeft); strcopy(sCommandRight, sizeof(sCommandRight), sCommandRight[Split]); StringToLower(sCommandLeft); StringToLower(sCommandRight); ArrayList RuleList; if(g_Rules.GetValue(sCommandLeft, RuleList)) return MatchRuleList(RuleList, sOrigCommand, sCommandLeft, sCommandRight); for(int i = 0; i < g_Regexes.Length; i++) { Regex hRegex = g_Regexes.Get(i); if(MatchRegex(hRegex, sCommandLeft) > 0) { RuleList = g_RegexRules.Get(i); return MatchRuleList(RuleList, sOrigCommand, sCommandLeft, sCommandRight); } } LogMessage("Blocked (No Rule): \"%s\"", sOrigCommand); return Plugin_Stop; } Action MatchRuleList(ArrayList RuleList, char[] sOrigCommand, const char[] sCommandLeft, const char[] sCommandRight) { for(int r = 0; r < RuleList.Length; r++) { int State = STATE_NONE; StringMap Rule = RuleList.Get(r); int Mode; Rule.GetValue("mode", Mode); bool IsNumeric = IsCharNumeric(sCommandRight[0]) || (sCommandRight[0] == '-' && IsCharNumeric(sCommandRight[1])); if(Mode & MODE_ALL) State |= STATE_ALLOW; else if(Mode & MODE_STRVALUE) { static char sValue[512]; Rule.GetString("value", sValue, sizeof(sValue)); if(strcmp(sCommandRight, sValue) == 0) State |= STATE_ALLOW; } else if(Mode & MODE_INTVALUE) { int WantValue; int IsValue; Rule.GetValue("value", WantValue); IsValue = StringToInt(sCommandRight); if(IsNumeric && WantValue == IsValue) State |= STATE_ALLOW; } else if(Mode & MODE_FLOATVALUE) { float WantValue; float IsValue; Rule.GetValue("value", WantValue); IsValue = StringToFloat(sCommandRight); if(IsNumeric && FloatCompare(IsValue, WantValue) == 0) State |= STATE_ALLOW; } else if(Mode & MODE_REGEXVALUE) { Regex hRegex; Rule.GetValue("value", hRegex); if(MatchRegex(hRegex, sCommandRight) > 0) State |= STATE_ALLOW; } float MinValue; float MaxValue; float IsValue = StringToFloat(sCommandRight); if(!IsNumeric && (Mode & MODE_MIN || Mode & MODE_MAX)) continue; // Ignore non-numerical if(Mode & MODE_MIN) { Rule.GetValue("minvalue", MinValue); if(IsValue >= MinValue) State |= STATE_ALLOW; else State |= STATE_DENY | STATE_CLAMPMIN; } if(Mode & MODE_MAX) { Rule.GetValue("maxvalue", MaxValue); if(IsValue <= MaxValue) State |= STATE_ALLOW; else State |= STATE_DENY | STATE_CLAMPMAX; } // Reverse mode if(Mode & MODE_DENY && State & STATE_ALLOW && !(State & STATE_DENY)) { LogMessage("Blocked (Deny): \"%s\"", sOrigCommand); return Plugin_Stop; } // Clamping? // If there is no clamp rule (State == STATE_NONE) try to clamp to "clampvalue" // aka. always clamp to "clampvalue" if there are no rules in clamp mode if(Mode & MODE_CLAMP && (State & STATE_DENY || State == STATE_NONE)) { bool Clamp = false; float ClampValue; if(Rule.GetValue("clampvalue", ClampValue)) Clamp = true; else if(State & STATE_CLAMPMIN) { ClampValue = MinValue; Clamp = true; } else if(State & STATE_CLAMPMAX) { ClampValue = MaxValue; Clamp = true; } if(Clamp) { LogMessage("Clamped (%f -> %f): \"%s\"", IsValue, ClampValue, sOrigCommand); FormatEx(sOrigCommand, COMMAND_SIZE, "%s %f", sCommandLeft, ClampValue); return Plugin_Changed; } else // Can this even happen? Yesh, dumb user. -> "clamp" {} { LogMessage("Blocked (!Clamp): \"%s\"", sOrigCommand); return Plugin_Stop; } } else if(Mode & MODE_CLAMP && State & STATE_ALLOW) { LogMessage("Allowed (Clamp): \"%s\"", sOrigCommand); return Plugin_Continue; } if(Mode & MODE_ALLOW && State & STATE_ALLOW && !(State & STATE_DENY)) { LogMessage("Allowed (Allow): \"%s\"", sOrigCommand); return Plugin_Continue; } } LogMessage("Blocked (No Match): \"%s\"", sOrigCommand); return Plugin_Stop; } void Cleanup() { if(!g_Rules) return; for(int i = 0; i < g_aRules.Length; i++) { ArrayList RuleList = g_aRules.Get(i); CleanupRuleList(RuleList); } delete g_aRules; delete g_Rules; for(int i = 0; i < g_Regexes.Length; i++) { Regex hRegex = g_Regexes.Get(i); delete hRegex; ArrayList RuleList = g_RegexRules.Get(i); CleanupRuleList(RuleList); } delete g_Regexes; delete g_RegexRules; } void CleanupRuleList(ArrayList RuleList) { for(int j = 0; j < RuleList.Length; j++) { StringMap Rule = RuleList.Get(j); int Mode; if(Rule.GetValue("mode", Mode)) { if(Mode & MODE_REGEXVALUE) { Regex hRegex; Rule.GetValue("value", hRegex); delete hRegex; } } delete Rule; } delete RuleList; } void LoadConfig() { if(g_Rules) Cleanup(); static char sConfigFile[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/PointServerCommandFilter.cfg"); if(!FileExists(sConfigFile)) SetFailState("Could not find config: \"%s\"", sConfigFile); KeyValues Config = new KeyValues("PointServerCommandFilter"); if(!Config.ImportFromFile(sConfigFile)) { delete Config; SetFailState("ImportFromFile() failed!"); } if(!Config.GotoFirstSubKey(false)) { delete Config; SetFailState("GotoFirstSubKey() failed!"); } g_Rules = new StringMap(); g_aRules = new ArrayList(); g_Regexes = new ArrayList(); g_RegexRules = new ArrayList(); do { static char sLeft[128]; Config.GetSectionName(sLeft, sizeof(sLeft)); StringToLower(sLeft); int LeftLen = strlen(sLeft); ArrayList RuleList; if(sLeft[0] == '/' && sLeft[LeftLen - 1] == '/') { sLeft[LeftLen - 1] = 0; Regex hRegex; static char sError[512]; hRegex = CompileRegex(sLeft[1], PCRE_CASELESS, sError, sizeof(sError)); if(hRegex == INVALID_HANDLE) { LogError("Regex error from %s", sLeft); LogError(sError); continue; } else { RuleList = new ArrayList(); g_Regexes.Push(hRegex); g_RegexRules.Push(RuleList); } } else if(!g_Rules.GetValue(sLeft, RuleList)) { RuleList = new ArrayList(); g_Rules.SetValue(sLeft, RuleList); g_aRules.Push(RuleList); } // Section if(Config.GotoFirstSubKey(false)) { do { static char sSection[128]; Config.GetSectionName(sSection, sizeof(sSection)); int Mode = MODE_NONE; if(strcmp(sSection, "deny", false) == 0) Mode |= MODE_DENY; else if(strcmp(sSection, "allow", false) == 0) Mode |= MODE_ALLOW; else if(strcmp(sSection, "clamp", false) == 0) Mode |= MODE_CLAMP; // Section if(Config.GotoFirstSubKey(false)) { StringMap Rule = new StringMap(); int RuleMode = MODE_NONE; do { static char sKey[128]; Config.GetSectionName(sKey, sizeof(sKey)); if(strcmp(sKey, "min", false) == 0) { float Value = Config.GetFloat(NULL_STRING); Rule.SetValue("minvalue", Value); RuleMode |= MODE_MIN; } else if(strcmp(sKey, "max", false) == 0) { float Value = Config.GetFloat(NULL_STRING); Rule.SetValue("maxvalue", Value); RuleMode |= MODE_MAX; } else if(Mode & MODE_CLAMP) { float Value = Config.GetFloat(NULL_STRING); Rule.SetValue("clampvalue", Value); RuleMode |= MODE_CLAMP; } else { StringMap Rule_ = new StringMap(); if(ParseRule(Config, sLeft, Mode, Rule_)) RuleList.Push(Rule_); else delete Rule_; } } while(Config.GotoNextKey(false)); Config.GoBack(); if(RuleMode != MODE_NONE) { Rule.SetValue("mode", Mode | RuleMode); RuleList.Push(Rule); } else delete Rule; } else // Value { StringMap Rule = new StringMap(); if(ParseRule(Config, sLeft, Mode, Rule)) RuleList.Push(Rule); else delete Rule; } } while(Config.GotoNextKey(false)); Config.GoBack(); } else // Value { StringMap Rule = new StringMap(); if(ParseRule(Config, sLeft, MODE_ALLOW, Rule)) RuleList.Push(Rule); else delete Rule; } } while(Config.GotoNextKey(false)); delete Config; for(int i = 0; i < g_aRules.Length; i++) { ArrayList RuleList = g_aRules.Get(i); SortADTArrayCustom(RuleList, SortRuleList); } } bool ParseRule(KeyValues Config, const char[] sLeft, int Mode, StringMap Rule) { static char sValue[512]; if(Config.GetDataType(NULL_STRING) == KvData_String) { Config.GetString(NULL_STRING, sValue, sizeof(sValue)); int ValueLen = strlen(sValue); if(sValue[0] == '/' && sValue[ValueLen - 1] == '/') { sValue[ValueLen - 1] = 0; Regex hRegex; static char sError[512]; hRegex = CompileRegex(sValue[1], PCRE_CASELESS, sError, sizeof(sError)); if(hRegex == INVALID_HANDLE) { LogError("Regex error in %s from %s", sLeft, sValue[1]); LogError(sError); return false; } else { Rule.SetValue("mode", Mode | MODE_REGEXVALUE); Rule.SetValue("value", hRegex); } } else { StringToLower(sValue); Rule.SetValue("mode", Mode | MODE_STRVALUE); Rule.SetString("value", sValue); } } else if(Config.GetDataType(NULL_STRING) == KvData_Int) { int Value = Config.GetNum(NULL_STRING); Rule.SetValue("mode", Mode | MODE_INTVALUE); Rule.SetValue("value", Value); } else if(Config.GetDataType(NULL_STRING) == KvData_Float) { float Value = Config.GetFloat(NULL_STRING); Rule.SetValue("mode", Mode | MODE_FLOATVALUE); Rule.SetValue("value", Value); } else Rule.SetValue("mode", Mode | MODE_ALL); return true; } public int SortRuleList(int index1, int index2, Handle array, Handle hndl) { StringMap Rule1 = GetArrayCell(array, index1); StringMap Rule2 = GetArrayCell(array, index2); int Mode1; int Mode2; Rule1.GetValue("mode", Mode1); Rule2.GetValue("mode", Mode2); // Deny should be first if(Mode1 & MODE_DENY && !(Mode2 & MODE_DENY)) return -1; if(Mode2 & MODE_DENY && !(Mode1 & MODE_DENY)) return 1; // Clamp should be last if(Mode1 & MODE_CLAMP && !(Mode2 & MODE_CLAMP)) return 1; if(Mode2 & MODE_CLAMP && !(Mode1 & MODE_CLAMP)) return -1; return 0; } stock void StringToLower(char[] Str) { for(int i = 0; Str[i]; i++) Str[i] = CharToLower(Str[i]); }