From b65de29c920701b48310bd7bff579a0c3689cbe3 Mon Sep 17 00:00:00 2001 From: Nicholas Hastings Date: Wed, 2 Mar 2016 15:25:01 -0500 Subject: [PATCH] Add initial version of safety checks for CS:GO to attempt to avoid user GSLT bans. --- configs/core.cfg | 13 ++++++++ core/HalfLife2.cpp | 51 +++++++++++++++++++++++++++++++ core/HalfLife2.h | 13 ++++++++ core/smn_entities.cpp | 22 +++++++++++++ extensions/sdktools/extension.cpp | 9 ++++++ extensions/sdktools/extension.h | 2 ++ extensions/sdktools/vnatives.cpp | 14 +++++++++ 7 files changed, 124 insertions(+) diff --git a/configs/core.cfg b/configs/core.cfg index 9dfef4ca..c04ffbe8 100644 --- a/configs/core.cfg +++ b/configs/core.cfg @@ -132,5 +132,18 @@ * passed. You can disable this feature by setting the value to "0". */ "SlowScriptTimeout" "8" + + /** + * Per "http://blog.counter-strike.net/index.php/server_guidelines/", certain plugin + * functionality will trigger all of the game server owner's Game Server Login Tokens + * (GSLTs) to get banned when executed on a Counter-Strike: Global Offensive game server. + * + * Enabling this option will block plugins from using functionality that is known to cause this. + * This option only has any effect on CS:GO. Note that this does NOT guarantee that you cannot + * receive a ban. + * + * Disable this option at your own risk. + */ + "FollowCSGOServerGuidelines" "yes" } diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 5e6555bf..61235934 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -137,6 +137,57 @@ void CHalfLife2::OnSourceModAllInitialized_Post() { InitLogicalEntData(); InitCommandLine(); +#if SOURCE_ENGINE == SE_CSGO + m_CSGOBadList.init(); + m_CSGOBadList.add("m_iItemDefinitionIndex"); + m_CSGOBadList.add("m_iEntityLevel"); + m_CSGOBadList.add("m_iItemIDHigh"); + m_CSGOBadList.add("m_iItemIDLow"); + m_CSGOBadList.add("m_iAccountID"); + m_CSGOBadList.add("m_iEntityQuality"); + m_CSGOBadList.add("m_bInitialized"); + m_CSGOBadList.add("m_szCustomName"); + m_CSGOBadList.add("m_iAttributeDefinitionIndex"); + m_CSGOBadList.add("m_iRawValue32"); + m_CSGOBadList.add("m_iRawInitialValue32"); + m_CSGOBadList.add("m_nRefundableCurrency"); + m_CSGOBadList.add("m_bSetBonus"); + m_CSGOBadList.add("m_OriginalOwnerXuidLow"); + m_CSGOBadList.add("m_OriginalOwnerXuidHigh"); + m_CSGOBadList.add("m_nFallbackPaintKit"); + m_CSGOBadList.add("m_nFallbackSeed"); + m_CSGOBadList.add("m_flFallbackWear"); + m_CSGOBadList.add("m_nFallbackStatTrak"); + m_CSGOBadList.add("m_iCompetitiveRanking"); + m_CSGOBadList.add("m_nActiveCoinRank"); + m_CSGOBadList.add("m_nMusicID"); +#endif +} + +ConfigResult CHalfLife2::OnSourceModConfigChanged(const char *key, const char *value, + ConfigSource source, char *error, size_t maxlength) +{ + if (strcasecmp(key, "FollowCSGOServerGuidelines") == 0) + { +#if SOURCE_ENGINE == SE_CSGO + if (strcasecmp(value, "no") == 0) + { + m_bFollowCSGOServerGuidelines = false; + return ConfigResult_Accept; + } + else if (strcasecmp(value, "yes") == 0) + { + m_bFollowCSGOServerGuidelines = true; + return ConfigResult_Accept; + } + + return ConfigResult_Reject; +#else + return ConfigResult_Accept; +#endif + } + + return ConfigResult_Ignore; } void CHalfLife2::InitLogicalEntData() diff --git a/core/HalfLife2.h b/core/HalfLife2.h index 811bdcbb..0ddc5e1e 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -152,6 +153,8 @@ public: void OnSourceModAllInitialized(); void OnSourceModAllInitialized_Post(); /*void OnSourceModAllShutdown();*/ + ConfigResult OnSourceModConfigChanged(const char *key, const char *value, + ConfigSource source, char *error, size_t maxlength) override; public: //IGameHelpers SendProp *FindInSendTable(const char *classname, const char *offset); bool FindSendPropInfo(const char *classname, const char *offset, sm_sendprop_info_t *info); @@ -220,6 +223,16 @@ private: CStack m_CommandStack; Queue m_DelayedKicks; void *m_pGetCommandLine; +#if SOURCE_ENGINE == SE_CSGO +public: + bool CanSetCSGOEntProp(const char *pszPropName) + { + return !m_bFollowCSGOServerGuidelines || !m_CSGOBadList.has(pszPropName); + } +private: + ke::HashSet m_CSGOBadList; + bool m_bFollowCSGOServerGuidelines = true; +#endif }; extern CHalfLife2 g_HL2; diff --git a/core/smn_entities.cpp b/core/smn_entities.cpp index 5abcf8d3..3ef07b02 100644 --- a/core/smn_entities.cpp +++ b/core/smn_entities.cpp @@ -93,6 +93,15 @@ enum PropFieldType PropField_String_T, /**< Valid for Data fields. Read only! */ }; +inline bool CanSetPropName(const char *pszPropName) +{ +#if SOURCE_ENGINE == SE_CSGO + return g_HL2.CanSetCSGOEntProp(pszPropName); +#else + return true; +#endif +} + inline edict_t *BaseEntityToEdict(CBaseEntity *pEntity) { IServerUnknown *pUnk = (IServerUnknown *)pEntity; @@ -1351,6 +1360,10 @@ static cell_t SetEntProp(IPluginContext *pContext, const cell_t *params) case Prop_Send: { FIND_PROP_SEND(DPT_Int, "integer"); + if (!CanSetPropName(prop)) + { + return pContext->ThrowNativeError("Cannot set %s with \"FollowCSGOServerGuidelines\" option enabled.", prop); + } // This isn't in CS:S yet, but will be, doesn't hurt to add now, and will save us a build later #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_HL2DM || SOURCE_ENGINE == SE_DODS \ @@ -1503,6 +1516,10 @@ static cell_t SetEntPropFloat(IPluginContext *pContext, const cell_t *params) case Prop_Send: { FIND_PROP_SEND(DPT_Float, "float"); + if (!CanSetPropName(prop)) + { + return pContext->ThrowNativeError("Cannot set %s with \"FollowCSGOServerGuidelines\" option enabled.", prop); + } break; } default: @@ -2088,6 +2105,11 @@ static cell_t SetEntPropString(IPluginContext *pContext, const cell_t *params) } } + if (!CanSetPropName(prop)) + { + return pContext->ThrowNativeError("Cannot set %s with \"FollowCSGOServerGuidelines\" option enabled.", prop); + } + if (bIsStringIndex) { offset += (element * (td->fieldSizeInBytes / td->fieldSize)); diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 60016827..c58cb290 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -170,6 +170,15 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) InitSDKToolsAPI(); +#if SOURCE_ENGINE == SE_CSGO + m_bFollowCSGOServerGuidelines = true; + const char *pszValue = g_pSM->GetCoreConfigValue("FollowCSGOServerGuidelines"); + if (pszValue && strcasecmp(pszValue, "no") == 0) + { + m_bFollowCSGOServerGuidelines = false; + } +#endif + return true; } diff --git a/extensions/sdktools/extension.h b/extensions/sdktools/extension.h index 4a8ea6d5..5a4a4c15 100644 --- a/extensions/sdktools/extension.h +++ b/extensions/sdktools/extension.h @@ -113,8 +113,10 @@ public: void OnServerActivate(edict_t *pEdictList, int edictCount, int clientMax); public: bool HasAnyLevelInited() { return m_bAnyLevelInited; } + bool ShouldFollowCSGOServerGuidelines() const { return m_bFollowCSGOServerGuidelines; } private: + bool m_bFollowCSGOServerGuidelines = false; bool m_bAnyLevelInited = false; }; diff --git a/extensions/sdktools/vnatives.cpp b/extensions/sdktools/vnatives.cpp index d21a835f..cccc47f6 100644 --- a/extensions/sdktools/vnatives.cpp +++ b/extensions/sdktools/vnatives.cpp @@ -144,6 +144,20 @@ static cell_t RemovePlayerItem(IPluginContext *pContext, const cell_t *params) class CEconItemView; static cell_t GiveNamedItem(IPluginContext *pContext, const cell_t *params) { + if (g_SdkTools.ShouldFollowCSGOServerGuidelines()) + { + char *pWeaponName; + pContext->LocalToString(params[2], &pWeaponName); + + // Don't allow knives other than weapon_knife, weapon_knifegg, and wewapon_knife_t. + // Others follow pattern weapon_knife_* + size_t len = strlen(pWeaponName); + if (len >= 14 && strnicmp(pWeaponName, "weapon_knife_", 13) == 0 && !(pWeaponName[13] == 't' && pWeaponName[14] == '\0')) + { + return pContext->ThrowNativeError("Blocked giving of %s due to core.cfg option FollowCSGOServerGuidelines", pWeaponName); + } + } + static ValveCall *pCall = NULL; if (!pCall) {