PluginSystem: add callback for chatroom receiver

Allow plugins to block chat msg's based on their text.
This commit is contained in:
Kawe Mazidjatari 2024-04-20 23:27:00 +02:00
parent 699469f7b6
commit 8cfcc00bc5
7 changed files with 125 additions and 8 deletions

View File

@ -177,7 +177,7 @@ CClient* CServer::ConnectClient(CServer* pServer, user_creds_s* pChallenge)
for (auto& callback : !g_PluginSystem.GetConnectClientCallbacks())
{
if (!callback(pServer, pClient, pChallenge))
if (!callback.Function()(pServer, pClient, pChallenge))
{
pClient->Disconnect(REP_MARK_BAD, "#Valve_Reject_Banned");
return nullptr;

View File

@ -18,6 +18,7 @@
#include "engine/server/server.h"
#include "game/shared/usercmd.h"
#include "game/server/util_server.h"
#include "pluginsystem/pluginsystem.h"
//-----------------------------------------------------------------------------
// This is called when a new game is started. (restart, map)
@ -77,8 +78,39 @@ ServerClass* CServerGameDLL::GetAllServerClasses(void)
return CallVFunc<ServerClass*>(index, this);
}
static ConVar chat_debug("chat_debug", "0", FCVAR_RELEASE, "Enables chat-related debug printing.");
void __fastcall CServerGameDLL::OnReceivedSayTextMessage(void* thisptr, int senderId, const char* text, bool isTeamChat)
{
const CGlobalVars* globals = *g_pGlobals;
if (senderId > 0)
{
if (senderId <= globals->m_nMaxPlayers && senderId != 0xFFFF)
{
CPlayer* player = reinterpret_cast<CPlayer*>(globals->m_pEdicts[senderId + 30728]);
if (player && player->IsConnected())
{
for (auto& cb : !g_PluginSystem.GetChatMessageCallbacks())
{
if (!cb.Function()(player, text, sv_forceChatToTeamOnly->GetBool()))
{
if (chat_debug.GetBool())
{
char moduleName[MAX_PATH] = {};
V_UnicodeToUTF8(V_UnqualifiedFileName(cb.ModuleName()), moduleName, MAX_PATH);
Msg(eDLL_T::SERVER, "[%s] Plugin blocked chat message from '%s' (%llu): \"%s\"\n", moduleName, player->GetNetName(), player->GetPlatformUserId(), text);
}
return;
}
}
}
}
}
// set isTeamChat to false so that we can let the convar sv_forceChatToTeamOnly decide whether team chat should be enforced
// this isn't a great way of doing it but it works so meh
CServerGameDLL__OnReceivedSayTextMessage(thisptr, senderId, text, false);

View File

@ -13,8 +13,12 @@ struct PluginHelpWithAnything_t
enum class ePluginCallback : int16_t
{
CModAppSystemGroup_Create = 0,
CServer_ConnectClient
// !! - WARNING: if any existing values are changed, you must increment INTERFACEVERSION_PLUGINSYSTEM - !!
CModAppSystemGroup_Create = 0,
CServer_ConnectClient = 1,
SV_RegisterScriptFunctions = 2,
OnReceivedChatMessage = 3,
};
ePluginHelp m_nHelpID;

View File

@ -131,7 +131,16 @@ CUtlVector<CPluginSystem::PluginInstance_t>& CPluginSystem::GetInstances()
//-----------------------------------------------------------------------------
void CPluginSystem::AddCallback(PluginHelpWithAnything_t* help)
{
#define ADD_PLUGIN_CALLBACK(fn, callback, function) callback += reinterpret_cast<fn>(function)
#define ADD_PLUGIN_CALLBACK(fn, callback, function) callback += reinterpret_cast<fn>(function); callback.GetCallbacks().Tail().SetModuleName(moduleName)
if (!help->m_pFunction)
return;
// [rexx]: This fetches the path to the module that contains the requested callback function.
// The module name is fetched so that callbacks can be identified by the plugin that they came from.
// This must use the wide-char version of this func, as file paths may contain non-ASCII characters and we don't really want those to break.
wchar_t moduleName[MAX_PATH] = {};
GetMappedFileNameW((HANDLE)-1, help->m_pFunction, moduleName, MAX_PATH);
switch (help->m_nCallbackID)
{
@ -145,6 +154,11 @@ void CPluginSystem::AddCallback(PluginHelpWithAnything_t* help)
ADD_PLUGIN_CALLBACK(ConnectClientFn, GetConnectClientCallbacks(), help->m_pFunction);
break;
}
case PluginHelpWithAnything_t::ePluginCallback::OnReceivedChatMessage:
{
ADD_PLUGIN_CALLBACK(OnChatMessageFn, GetChatMessageCallbacks(), help->m_pFunction);
break;
}
default:
break;
}
@ -160,6 +174,9 @@ void CPluginSystem::RemoveCallback(PluginHelpWithAnything_t* help)
{
#define REMOVE_PLUGIN_CALLBACK(fn, callback, function) callback -= reinterpret_cast<fn>(function)
if (!help->m_pFunction)
return;
switch (help->m_nCallbackID)
{
case PluginHelpWithAnything_t::ePluginCallback::CModAppSystemGroup_Create:
@ -172,6 +189,11 @@ void CPluginSystem::RemoveCallback(PluginHelpWithAnything_t* help)
REMOVE_PLUGIN_CALLBACK(ConnectClientFn, GetConnectClientCallbacks(), help->m_pFunction);
break;
}
case PluginHelpWithAnything_t::ePluginCallback::OnReceivedChatMessage:
{
REMOVE_PLUGIN_CALLBACK(OnChatMessageFn, GetChatMessageCallbacks(), help->m_pFunction);
break;
}
default:
break;
}

View File

@ -6,6 +6,7 @@
class CModAppSystemGroup;
class CServer;
class CClient;
class CPlayer;
struct user_creds_s;
template<typename T>
@ -82,6 +83,32 @@ private:
CUtlVector<T> m_vCallbacks;
};
template<typename T>
class CPluginCallback
{
friend class CPluginSystem;
public:
CPluginCallback(T f) : function(f) {};
inline const T& Function() { return function; };
inline const wchar_t* ModuleName() { return moduleName; };
operator bool() const
{
return function;
}
protected:
inline void SetModuleName(wchar_t* name)
{
wcscpy_s(moduleName, name);
};
private:
T function;
wchar_t moduleName[MAX_PATH];
};
class CPluginSystem : IPluginSystem
{
public:
@ -120,10 +147,11 @@ public:
virtual void* HelpWithAnything(PluginHelpWithAnything_t* help);
#define CREATE_PLUGIN_CALLBACK(typeName, type, funcName, varName) public: using typeName = type; CPluginCallbackList<typeName>& funcName() { return varName; } private: CPluginCallbackList<typeName> varName;
#define CREATE_PLUGIN_CALLBACK(typeName, type, funcName, varName) public: using typeName = type; CPluginCallbackList<CPluginCallback<typeName>>& funcName() { return varName; } private: CPluginCallbackList<CPluginCallback<typeName>> varName;
CREATE_PLUGIN_CALLBACK(CreateFn, bool(*)(CModAppSystemGroup*), GetCreateCallbacks, createCallbacks);
CREATE_PLUGIN_CALLBACK(ConnectClientFn, bool(*)(CServer*, CClient*, user_creds_s*), GetConnectClientCallbacks, connectClientCallbacks);
CREATE_PLUGIN_CALLBACK(OnChatMessageFn, bool(*)(CPlayer*, const char*, bool), GetChatMessageCallbacks, chatMessageCallbacks);
#undef CREATE_PLUGIN_CALLBACK
@ -140,4 +168,4 @@ FORCEINLINE CPluginSystem* PluginSystem()
// Monitor this and performance profile this if fps drops are detected.
#define CALL_PLUGIN_CALLBACKS(callback, ...) \
for (auto& cb : !callback) \
cb(__VA_ARGS__)
cb.Function()(__VA_ARGS__)

View File

@ -7,6 +7,7 @@
#define INCORRECT_PATH_SEPARATOR_S "/"
#define CHARACTERS_WHICH_SEPARATE_DIRECTORY_COMPONENTS_IN_PATHNAMES ":/\\"
#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
#define PATHSEPARATORW(c) ((c) == L'\\' || (c) == L'/')
#elif POSIX || defined( _PS3 )
#define CORRECT_PATH_SEPARATOR '/'
#define CORRECT_PATH_SEPARATOR_S "/"
@ -14,6 +15,7 @@
#define INCORRECT_PATH_SEPARATOR_S "\\"
#define CHARACTERS_WHICH_SEPARATE_DIRECTORY_COMPONENTS_IN_PATHNAMES "/"
#define PATHSEPARATOR(c) ((c) == '/')
#define PATHSEPARATORW(c) ((c) == L'/')
#endif
#define COPY_ALL_CHARACTERS -1
@ -156,6 +158,8 @@ inline void V_MakeAbsolutePath(char* pOut, size_t outLen, const char* pPath, con
size_t V_StripLastDir(char* dirName, size_t maxLen);
// Returns a pointer to the unqualified file name (no path) of a file name
const char* V_UnqualifiedFileName(const char* in);
const wchar_t* V_UnqualifiedFileName(const wchar_t* in);
// Given a path and a filename, composes "path\filename", inserting the (OS correct) separator if necessary
void V_ComposeFileName(const char* path, const char* filename, char* dest, size_t destSize);

View File

@ -503,8 +503,15 @@ int V_UnicodeToUTF8(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes
cchResult = wcstombs(pUTF8, pUnicode, cubDestSizeInBytes);
#endif
if (cubDestSizeInBytes > 0)
pUTF8[cubDestSizeInBytes - 1] = 0;
if (cchResult <= 0 || cchResult > cubDestSizeInBytes)
{
if (cchResult != cubDestSizeInBytes || pUTF8[cubDestSizeInBytes - 1] != '\0')
{
*pUTF8 = '\0';
}
}
else
pUTF8[cchResult] = '\0';
return cchResult;
}
@ -1248,6 +1255,26 @@ const char* V_UnqualifiedFileName(const char* in)
return out;
}
const wchar_t* V_UnqualifiedFileName(const wchar_t* in)
{
Assert(in);
const wchar_t* out = in;
while (*in)
{
if (PATHSEPARATORW(*in))
{
// +1 to skip the slash
out = in + 1;
}
in++;
}
return out;
}
//-----------------------------------------------------------------------------
// Purpose: Composes a path and filename together, inserting a path separator
// if need be