diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index c16c02e6..69bcff29 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -13,22 +13,33 @@ namespace Components std::mutex Chat::AccessMutex; std::unordered_set Chat::MuteList; - const char* Chat::EvaluateSay(char* text, Game::gentity_t* player) + bool Chat::CanAddCallback = true; + std::vector Chat::SayCallbacks; + + const char* Chat::EvaluateSay(char* text, Game::gentity_t* player, int mode) { - Chat::SendChat = true; + SendChat = true; + + const auto _0 = gsl::finally([] + { + CanAddCallback = true; + }); + + // Prevent callbacks from adding a new callback (would make the vector iterator invalid) + CanAddCallback = false; if (text[1] == '/') { - Chat::SendChat = false; + SendChat = false; text[1] = text[0]; ++text; } - std::unique_lock lock(Chat::AccessMutex); - if (Chat::MuteList.contains(Game::svs_clients[player->s.number].steamID)) + std::unique_lock lock(AccessMutex); + if (MuteList.contains(Game::svs_clients[player->s.number].steamID)) { lock.unlock(); - Chat::SendChat = false; + SendChat = false; Game::SV_GameSendServerCommand(player->s.number, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65)); } @@ -39,9 +50,17 @@ namespace Components lock.unlock(); } - if (Chat::sv_disableChat.get()) + for (const auto& callback : SayCallbacks) { - Chat::SendChat = false; + if (!ChatCallback(player, callback.getPos(), (text + 1), mode)) + { + SendChat = false; + } + } + + if (sv_disableChat.get()) + { + SendChat = false; Game::SV_GameSendServerCommand(player->s.number, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65)); } @@ -59,21 +78,22 @@ namespace Components { __asm { - mov eax, [esp + 100h + 10h] + mov eax, [esp + 0x100 + 0x10] push eax pushad - push [esp + 100h + 28h] - push eax - call Chat::EvaluateSay - add esp, 8h + push [esp + 0x100 + 0x30] // mode + push [esp + 0x100 + 0x2C] // player + push eax // text + call EvaluateSay + add esp, 0xC - mov [esp + 20h], eax + mov [esp + 0x20], eax popad pop eax - mov [esp + 100h + 10h], eax + mov [esp + 0x100 + 0x10], eax jmp PlayerName::CleanStrStub } @@ -87,7 +107,7 @@ namespace Components push eax xor eax, eax - mov al, Chat::SendChat + mov al, SendChat test al, al jnz return @@ -224,11 +244,11 @@ namespace Components void Chat::MuteClient(const Game::client_t* client) { - std::unique_lock lock(Chat::AccessMutex); + std::unique_lock lock(AccessMutex); - if (!Chat::MuteList.contains(client->steamID)) + if (!MuteList.contains(client->steamID)) { - Chat::MuteList.insert(client->steamID); + MuteList.insert(client->steamID); lock.unlock(); Logger::Print("{} was muted\n", client->name); @@ -245,7 +265,7 @@ namespace Components void Chat::UnmuteClient(const Game::client_t* client) { - Chat::UnmuteInternal(client->steamID); + UnmuteInternal(client->steamID); Logger::Print("{} was unmuted\n", client->name); Game::SV_GameSendServerCommand(client->gentity->s.number, Game::SV_CMD_CAN_IGNORE, @@ -254,12 +274,12 @@ namespace Components void Chat::UnmuteInternal(const std::uint64_t id, bool everyone) { - std::unique_lock lock(Chat::AccessMutex); + std::unique_lock lock(AccessMutex); if (everyone) - Chat::MuteList.clear(); + MuteList.clear(); else - Chat::MuteList.erase(id); + MuteList.erase(id); } void Chat::AddChatCommands() @@ -282,7 +302,7 @@ namespace Components const auto* client = Game::SV_GetPlayerByNum(); if (client != nullptr) { - Chat::MuteClient(client); + MuteClient(client); } }); @@ -305,27 +325,91 @@ namespace Components if (client != nullptr) { - Chat::UnmuteClient(client); + UnmuteClient(client); return; } if (std::strcmp(params->get(1), "all") == 0) { Logger::Print("All players were unmuted\n"); - Chat::UnmuteInternal(0, true); + UnmuteInternal(0, true); } else { const auto steamId = std::strtoull(params->get(1), nullptr, 16); - Chat::UnmuteInternal(steamId); + UnmuteInternal(steamId); } }); } + int Chat::GetCallbackReturn() + { + if (Game::scrVmPub->inparamcount == 0) + { + // Nothing. Let's not mute the player + return 1; + } + + Game::Scr_ClearOutParams(); + Game::scrVmPub->outparamcount = Game::scrVmPub->inparamcount; + Game::scrVmPub->inparamcount = 0; + + const auto* result = &Game::scrVmPub->top[1 - Game::scrVmPub->outparamcount]; + + if (result->type != Game::scrParamType_t::VAR_INTEGER) + { + // Garbage was returned + return 1; + } + + return result->u.intValue; + } + + int Chat::ChatCallback(Game::gentity_s* self, const char* codePos, const char* message, int mode) + { + const auto entityId = Game::Scr_GetEntityId(self->s.number, 0); + + Game::Scr_AddInt(mode); + Game::Scr_AddString(message); + + Game::VariableValue value; + value.type = Game::scrParamType_t::VAR_OBJECT; + value.u.uintValue = entityId; + + Game::AddRefToValue(value.type, value.u); + const auto localId = Game::AllocThread(entityId); + + const auto result = Game::VM_Execute_0(localId, codePos, 2); + Game::RemoveRefToObject(result); + + return GetCallbackReturn(); + } + + void Chat::AddScriptFunctions() + { + Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay() + { + if (Game::Scr_GetNumParam() != 1) + { + Game::Scr_Error("^1OnPlayerSay: Needs one function pointer!\n"); + return; + } + + if (!CanAddCallback) + { + Game::Scr_Error("^1OnPlayerSay: Cannot add a callback in this context"); + return; + } + + const auto* func = Script::GetCodePosForParam(0); + SayCallbacks.emplace_back(func); + }); + } + Chat::Chat() { cg_chatWidth = Dvar::Register("cg_chatWidth", 52, 1, std::numeric_limits::max(), Game::DVAR_ARCHIVE, "The normalized maximum width of a chat message"); - Chat::sv_disableChat = Dvar::Register("sv_disableChat", false, Game::dvar_flag::DVAR_NONE, "Disable chat messages from clients"); + sv_disableChat = Dvar::Register("sv_disableChat", false, Game::dvar_flag::DVAR_NONE, "Disable chat messages from clients"); Scheduler::Once(Chat::AddChatCommands, Scheduler::Pipeline::MAIN); // Intercept chat sending @@ -335,5 +419,13 @@ namespace Components // Change logic that does word splitting with new lines for chat messages to support fonticons Utils::Hook(0x592E10, CG_AddToTeamChat_Stub, HOOK_JUMP).install()->quick(); + + AddScriptFunctions(); + + // Avoid duplicates + Events::OnVMShutdown([] + { + SayCallbacks.clear(); + }); } } diff --git a/src/Components/Modules/Chat.hpp b/src/Components/Modules/Chat.hpp index a19469ae..a9bd64d1 100644 --- a/src/Components/Modules/Chat.hpp +++ b/src/Components/Modules/Chat.hpp @@ -21,7 +21,10 @@ namespace Components static std::mutex AccessMutex; static std::unordered_set MuteList; - static const char* EvaluateSay(char* text, Game::gentity_t* player); + static bool CanAddCallback; // ClientCommand & GSC thread are the same + static std::vector SayCallbacks; + + static const char* EvaluateSay(char* text, Game::gentity_t* player, int mode); static void PreSayStub(); static void PostSayStub(); @@ -34,5 +37,9 @@ namespace Components static void UnmuteClient(const Game::client_t* client); static void UnmuteInternal(const std::uint64_t id, bool everyone = false); static void AddChatCommands(); + + static int GetCallbackReturn(); + static int ChatCallback(Game::gentity_s* self, const char* codePos, const char* message, int mode); + static void AddScriptFunctions(); }; } diff --git a/src/Components/Modules/MapRotation.cpp b/src/Components/Modules/MapRotation.cpp index e7fe38c0..7de82389 100644 --- a/src/Components/Modules/MapRotation.cpp +++ b/src/Components/Modules/MapRotation.cpp @@ -121,7 +121,7 @@ namespace Components void MapRotation::AddMapRotationCommands() { - Command::Add("AddMap", [](Command::Params* params) + Command::Add("addMap", [](Command::Params* params) { if (params->size() < 2) { @@ -132,7 +132,7 @@ namespace Components DedicatedRotation.addEntry("map", params->get(1)); }); - Command::Add("AddGametype", [](Command::Params* params) + Command::Add("addGametype", [](Command::Params* params) { if (params->size() < 2) { diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index 563bc966..8ef576ab 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -15,6 +15,8 @@ namespace Components static Game::client_t* GetClient(const Game::gentity_t* gentity); + static const char* GetCodePosForParam(int index); + private: static std::string ScriptName; static std::vector ScriptHandles; @@ -54,7 +56,6 @@ namespace Components static unsigned int SetExpFogStub(); - static const char* GetCodePosForParam(int index); static void GetReplacedPos(const char* pos); static void SetReplacedPos(const char* what, const char* with); static void VMExecuteInternalStub(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 6bc10828..15c1be07 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -24,6 +24,9 @@ namespace Game AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360); AllocObject_t AllocObject = AllocObject_t(0x434320); + AddRefToValue_t AddRefToValue = AddRefToValue_t(0x482740); + AllocThread_t AllocThread = AllocThread_t(0x4F78C0); + VM_Execute_0_t VM_Execute_0 = VM_Execute_0_t(0x6222A0); AngleVectors_t AngleVectors = AngleVectors_t(0x4691A0); @@ -282,6 +285,7 @@ namespace Game Scr_GetInt_t Scr_GetInt = Scr_GetInt_t(0x4F31D0); Scr_GetObject_t Scr_GetObject = Scr_GetObject_t(0x462100); Scr_GetNumParam_t Scr_GetNumParam = Scr_GetNumParam_t(0x4B0E90); + Scr_GetEntityId_t Scr_GetEntityId = Scr_GetEntityId_t(0x4165E0); Scr_ExecThread_t Scr_ExecThread = Scr_ExecThread_t(0x4AD0B0); Scr_FreeThread_t Scr_FreeThread = Scr_FreeThread_t(0x4BD320); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index e82932d3..bec366d7 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -28,6 +28,15 @@ namespace Game typedef unsigned int(__cdecl * AllocObject_t)(); extern AllocObject_t AllocObject; + typedef void(__cdecl * AddRefToValue_t)(int type, VariableUnion u); + extern AddRefToValue_t AddRefToValue; + + typedef unsigned int(__cdecl * AllocThread_t)(unsigned int self); + extern AllocThread_t AllocThread; + + typedef unsigned int(__cdecl * VM_Execute_0_t)(unsigned int localId, const char* pos, unsigned int paramcount); + extern VM_Execute_0_t VM_Execute_0; + typedef void(__cdecl * AngleVectors_t)(float *angles, float *forward, float *right, float *up); extern AngleVectors_t AngleVectors; @@ -735,12 +744,18 @@ namespace Game typedef unsigned int(__cdecl * Scr_GetNumParam_t)(); extern Scr_GetNumParam_t Scr_GetNumParam; + typedef unsigned int(__cdecl * Scr_GetEntityId_t)(int entnum, unsigned int classnum); + extern Scr_GetEntityId_t Scr_GetEntityId; + typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char* filename, const char* name); extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; typedef int(__cdecl * Scr_ExecThread_t)(int, int); extern Scr_ExecThread_t Scr_ExecThread; + typedef int(__cdecl * Scr_ExecEntThread_t)(gentity_s* ent, int handle, unsigned int paramcount); + extern Scr_ExecEntThread_t Scr_ExecEntThread; + typedef int(__cdecl * Scr_FreeThread_t)(int); extern Scr_FreeThread_t Scr_FreeThread; diff --git a/src/Game/Scripting/Function.cpp b/src/Game/Scripting/Function.cpp new file mode 100644 index 00000000..7c137529 --- /dev/null +++ b/src/Game/Scripting/Function.cpp @@ -0,0 +1,14 @@ +#include + +namespace Scripting +{ + Function::Function(const char* pos) + : pos_(pos) + { + } + + const char* Function::getPos() const + { + return this->pos_; + } +} diff --git a/src/Game/Scripting/Function.hpp b/src/Game/Scripting/Function.hpp new file mode 100644 index 00000000..ff96a206 --- /dev/null +++ b/src/Game/Scripting/Function.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace Scripting +{ + class Function + { + public: + Function(const char* pos); + + [[nodiscard]] const char* getPos() const; + + private: + const char* pos_; + }; +} diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 46086ffb..63e5d202 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -138,6 +138,7 @@ using namespace std::literals; #include "Game/Structs.hpp" #include "Game/Functions.hpp" +#include #include "Utils/Stream.hpp" // Breaks order on purpose