From bd7d2fe059c851b5adbf92315d59412d27e3a197 Mon Sep 17 00:00:00 2001 From: Edo Date: Wed, 25 Jan 2023 18:20:44 +0000 Subject: [PATCH] [Chat]: New save feature (#732) --- src/Components/Modules/Bans.cpp | 26 ++++++--- src/Components/Modules/Bans.hpp | 6 +- src/Components/Modules/Chat.cpp | 92 ++++++++++++++++++++++++++++++- src/Components/Modules/Chat.hpp | 10 +++- src/Components/Modules/Events.cpp | 15 +++++ src/Components/Modules/Events.hpp | 6 ++ src/Components/Modules/Voice.cpp | 8 +++ src/Utils/IO.hpp | 12 ++-- 8 files changed, 156 insertions(+), 19 deletions(-) diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index 8928494d..b84ed62f 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -5,6 +5,8 @@ namespace Components { + const char* Bans::BanListFile = "userraw/bans.json"; + // Have only one instance of IW4x read/write the file std::unique_lock Bans::Lock() { @@ -89,7 +91,7 @@ namespace Components void Bans::SaveBans(const BanList* list) { - assert(list != nullptr); + assert(list); const auto _ = Lock(); @@ -107,7 +109,8 @@ namespace Components ipEntry.bytes[0] & 0xFF, ipEntry.bytes[1] & 0xFF, ipEntry.bytes[2] & 0xFF, - ipEntry.bytes[3] & 0xFF)); + ipEntry.bytes[3] & 0xFF) + ); } const nlohmann::json bans = nlohmann::json @@ -116,18 +119,17 @@ namespace Components { "id", idVector }, }; - FileSystem::FileWriter ("bans.json").write(bans.dump()); + Utils::IO::WriteFile(BanListFile, bans.dump()); } void Bans::LoadBans(BanList* list) { - assert(list != nullptr); + assert(list); const auto _ = Lock(); - FileSystem::File bans("bans.json"); - - if (!bans.exists()) + const auto bans = Utils::IO::ReadFile(BanListFile); + if (bans.empty()) { Logger::Debug("bans.json does not exist"); return; @@ -136,14 +138,20 @@ namespace Components nlohmann::json banData; try { - banData = nlohmann::json::parse(bans.getBuffer()); + banData = nlohmann::json::parse(bans); } - catch (const nlohmann::json::parse_error& ex) + catch (const std::exception& ex) { Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what()); return; } + if (!banData.contains("id") || !banData.contains("ip")) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "bans.json contains invalid data\n"); + return; + } + const auto& idList = banData["id"]; const auto& ipList = banData["ip"]; diff --git a/src/Components/Modules/Bans.hpp b/src/Components/Modules/Bans.hpp index 0ba36d12..322a4a12 100644 --- a/src/Components/Modules/Bans.hpp +++ b/src/Components/Modules/Bans.hpp @@ -9,8 +9,6 @@ namespace Components Bans(); - static std::unique_lock Lock(); - static void BanClient(Game::client_t* cl, const std::string& reason); static void UnbanClient(SteamID id); static void UnbanClient(Game::netIP_t ip); @@ -25,6 +23,10 @@ namespace Components std::vector ipList; }; + static const char* BanListFile; + + static std::unique_lock Lock(); + static void LoadBans(BanList* list); static void SaveBans(const BanList* list); }; diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index cff5b9d5..cd9c2c40 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -5,6 +5,8 @@ #include "GSC/Script.hpp" +#include + namespace Components { Dvar::Var Chat::cg_chatWidth; @@ -14,10 +16,19 @@ namespace Components bool Chat::SendChat; Utils::Concurrency::Container Chat::MutedList; + const char* Chat::MutedListFile = "userraw/muted-users.json"; bool Chat::CanAddCallback = true; std::vector Chat::SayCallbacks; + // Have only one instance of IW4x read/write the file + std::unique_lock Chat::Lock() + { + static Utils::NamedMutex mutex{"iw4x-mute-list-lock"}; + std::unique_lock lock{mutex}; + return lock; + } + const char* Chat::EvaluateSay(char* text, Game::gentity_t* player, int mode) { SendChat = true; @@ -236,7 +247,9 @@ namespace Components Game::cgsArray[0].teamChatPos++; if (Game::cgsArray[0].teamChatPos - Game::cgsArray[0].teamLastChatPos > chatHeight) + { Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight; + } } __declspec(naked) void Chat::CG_AddToTeamChat_Stub() @@ -259,7 +272,20 @@ namespace Components const auto clientNum = ent - Game::g_entities; const auto xuid = Game::svs_clients[clientNum].steamID; - const auto result = MutedList.access([&](muteList& clients) + const auto result = MutedList.access([&](const muteList& clients) + { + return clients.contains(xuid); + }); + + return result; + } + + bool Chat::IsMuted(const Game::client_t* cl) + { + const auto clientNum = cl - Game::svs_clients; + const auto xuid = Game::svs_clients[clientNum].steamID; + + const auto result = MutedList.access([&](const muteList& clients) { return clients.contains(xuid); }); @@ -273,6 +299,7 @@ namespace Components MutedList.access([&](muteList& clients) { clients.insert(xuid); + SaveMutedList(clients); }); Logger::Print("{} was muted\n", client->name); @@ -295,6 +322,67 @@ namespace Components clients.clear(); else clients.erase(id); + + SaveMutedList(clients); + }); + } + + void Chat::SaveMutedList(const muteList& list) + { + const auto _ = Lock(); + + const nlohmann::json mutedUsers = nlohmann::json + { + { "SteamID", list }, + }; + + Utils::IO::WriteFile(MutedListFile, mutedUsers.dump()); + } + + void Chat::LoadMutedList() + { + const auto _ = Lock(); + + const auto mutedUsers = Utils::IO::ReadFile(MutedListFile); + if (mutedUsers.empty()) + { + Logger::Debug("muted-users.json does not exist"); + return; + } + + nlohmann::json mutedUsersData; + try + { + mutedUsersData = nlohmann::json::parse(mutedUsers); + } + catch (const std::exception& ex) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what()); + return; + } + + if (!mutedUsersData.contains("SteamID")) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "muted-users.json contains invalid data\n"); + return; + } + + const auto& list = mutedUsersData["SteamID"]; + if (!list.is_array()) + { + return; + } + + MutedList.access([&](muteList& clients) + { + const nlohmann::json::array_t arr = list; + for (auto& entry : arr) + { + if (entry.is_number_unsigned()) + { + clients.insert(entry.get()); + } + } }); } @@ -522,6 +610,8 @@ namespace Components sv_disableChat = Dvar::Register("sv_disableChat", false, Game::DVAR_NONE, "Disable chat messages from clients"); Events::OnSVInit(AddChatCommands); + LoadMutedList(); + // Intercept chat sending Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick(); Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Chat.hpp b/src/Components/Modules/Chat.hpp index f96e29e4..34cbffea 100644 --- a/src/Components/Modules/Chat.hpp +++ b/src/Components/Modules/Chat.hpp @@ -8,6 +8,9 @@ namespace Components public: Chat(); + static bool IsMuted(const Game::gentity_s* ent); + static bool IsMuted(const Game::client_t* cl); + private: static Dvar::Var cg_chatWidth; static Dvar::Var sv_disableChat; @@ -17,10 +20,13 @@ namespace Components using muteList = std::unordered_set; static Utils::Concurrency::Container MutedList; + static const char* MutedListFile; static bool CanAddCallback; // ClientCommand & GSC thread are the same static std::vector SayCallbacks; + static std::unique_lock Lock(); + static const char* EvaluateSay(char* text, Game::gentity_t* player, int mode); static void PreSayStub(); @@ -30,10 +36,12 @@ namespace Components static void CG_AddToTeamChat(const char* text); static void CG_AddToTeamChat_Stub(); - static bool IsMuted(const Game::gentity_s* ent); static void MuteClient(const Game::client_t* client); static void UnmuteClient(const Game::client_t* client); static void UnmuteInternal(std::uint64_t id, bool everyone = false); + static void SaveMutedList(const muteList& list); + static void LoadMutedList(); + static void AddChatCommands(); static int GetCallbackReturn(); diff --git a/src/Components/Modules/Events.cpp b/src/Components/Modules/Events.cpp index e03522ec..3c9d8038 100644 --- a/src/Components/Modules/Events.cpp +++ b/src/Components/Modules/Events.cpp @@ -3,6 +3,7 @@ namespace Components { Utils::Signal Events::ClientDisconnectSignal; + Utils::Signal Events::ClientConnectSignal; Utils::Signal Events::SteamDisconnectSignal; Utils::Signal Events::ShutdownSystemSignal; Utils::Signal Events::ClientInitSignal; @@ -13,6 +14,11 @@ namespace Components ClientDisconnectSignal.connect(callback); } + void Events::OnClientConnect(const Utils::Slot& callback) + { + ClientConnectSignal.connect(callback); + } + void Events::OnSteamDisconnect(const Utils::Slot& callback) { SteamDisconnectSignal.connect(callback); @@ -44,6 +50,13 @@ namespace Components Utils::Hook::Call(0x4AA430)(clientNum); // ClientDisconnect } + void Events::SV_UserinfoChanged_Hk(Game::client_t* cl) + { + ClientConnectSignal(cl); + + Utils::Hook::Call(0x401950)(cl); // SV_UserinfoChanged + } + void Events::SteamDisconnect_Hk() { SteamDisconnectSignal(); @@ -78,6 +91,8 @@ namespace Components { Utils::Hook(0x625235, ClientDisconnect_Hk, HOOK_CALL).install()->quick(); // SV_FreeClient + Utils::Hook(0x4612BD, SV_UserinfoChanged_Hk, HOOK_CALL).install()->quick(); // SV_DirectConnect + Utils::Hook(0x403582, SteamDisconnect_Hk, HOOK_CALL).install()->quick(); // CL_Disconnect Utils::Hook(0x47548B, Scr_ShutdownSystem_Hk, HOOK_CALL).install()->quick(); // G_LoadGame diff --git a/src/Components/Modules/Events.hpp b/src/Components/Modules/Events.hpp index a47f6794..2b211441 100644 --- a/src/Components/Modules/Events.hpp +++ b/src/Components/Modules/Events.hpp @@ -6,6 +6,7 @@ namespace Components { public: typedef void(ClientCallback)(int clientNum); + typedef void(ClientConnectCallback)(Game::client_t* cl); typedef void(Callback)(); Events(); @@ -13,6 +14,9 @@ namespace Components // Server side static void OnClientDisconnect(const Utils::Slot& callback); + // Server side + static void OnClientConnect(const Utils::Slot& callback); + // Client side static void OnSteamDisconnect(const Utils::Slot& callback); @@ -25,12 +29,14 @@ namespace Components private: static Utils::Signal ClientDisconnectSignal; + static Utils::Signal ClientConnectSignal; static Utils::Signal SteamDisconnectSignal; static Utils::Signal ShutdownSystemSignal; static Utils::Signal ClientInitSignal; static Utils::Signal ServerInitSignal; static void ClientDisconnect_Hk(int clientNum); + static void SV_UserinfoChanged_Hk(Game::client_t* cl); static void SteamDisconnect_Hk(); static void Scr_ShutdownSystem_Hk(unsigned char sys); static void CL_InitOnceForAllClients_HK(); diff --git a/src/Components/Modules/Voice.cpp b/src/Components/Modules/Voice.cpp index 10e40a21..83e22a31 100644 --- a/src/Components/Modules/Voice.cpp +++ b/src/Components/Modules/Voice.cpp @@ -1,4 +1,5 @@ #include +#include "Chat.hpp" #include "Voice.hpp" namespace Components @@ -381,6 +382,13 @@ namespace Components Events::OnSteamDisconnect(CL_ClearMutedList); Events::OnClientDisconnect(SV_UnmuteClient); + Events::OnClientConnect([](Game::client_t* cl) -> void + { + if (Chat::IsMuted(cl)) + { + SV_MuteClient(cl - Game::svs_clients); + } + }); // Write voice packets to the server instead of other clients Utils::Hook(0x487935, CL_WriteVoicePacket_Hk, HOOK_CALL).install()->quick(); diff --git a/src/Utils/IO.hpp b/src/Utils/IO.hpp index c4e73825..a0d4e39c 100644 --- a/src/Utils/IO.hpp +++ b/src/Utils/IO.hpp @@ -2,14 +2,14 @@ namespace Utils::IO { - bool FileExists(const std::string& file); + [[nodiscard]] bool FileExists(const std::string& file); bool WriteFile(const std::string& file, const std::string& data, bool append = false); bool ReadFile(const std::string& file, std::string* data); - std::string ReadFile(const std::string& file); + [[nodiscard]] std::string ReadFile(const std::string& file); bool RemoveFile(const std::string& file); - std::size_t FileSize(const std::string& file); + [[nodiscard]] std::size_t FileSize(const std::string& file); bool CreateDir(const std::string& dir); - bool DirectoryExists(const std::filesystem::path& directory); - bool DirectoryIsEmpty(const std::filesystem::path& directory); - std::vector ListFiles(const std::filesystem::path& directory, bool recursive = false); + [[nodiscard]] bool DirectoryExists(const std::filesystem::path& directory); + [[nodiscard]] bool DirectoryIsEmpty(const std::filesystem::path& directory); + [[nodiscard]] std::vector ListFiles(const std::filesystem::path& directory, bool recursive = false); }