[Chat]: New save feature (#732)

This commit is contained in:
Edo 2023-01-25 18:20:44 +00:00 committed by GitHub
parent 5805331bfd
commit bd7d2fe059
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 19 deletions

View File

@ -5,6 +5,8 @@
namespace Components namespace Components
{ {
const char* Bans::BanListFile = "userraw/bans.json";
// Have only one instance of IW4x read/write the file // Have only one instance of IW4x read/write the file
std::unique_lock<Utils::NamedMutex> Bans::Lock() std::unique_lock<Utils::NamedMutex> Bans::Lock()
{ {
@ -89,7 +91,7 @@ namespace Components
void Bans::SaveBans(const BanList* list) void Bans::SaveBans(const BanList* list)
{ {
assert(list != nullptr); assert(list);
const auto _ = Lock(); const auto _ = Lock();
@ -107,7 +109,8 @@ namespace Components
ipEntry.bytes[0] & 0xFF, ipEntry.bytes[0] & 0xFF,
ipEntry.bytes[1] & 0xFF, ipEntry.bytes[1] & 0xFF,
ipEntry.bytes[2] & 0xFF, ipEntry.bytes[2] & 0xFF,
ipEntry.bytes[3] & 0xFF)); ipEntry.bytes[3] & 0xFF)
);
} }
const nlohmann::json bans = nlohmann::json const nlohmann::json bans = nlohmann::json
@ -116,18 +119,17 @@ namespace Components
{ "id", idVector }, { "id", idVector },
}; };
FileSystem::FileWriter ("bans.json").write(bans.dump()); Utils::IO::WriteFile(BanListFile, bans.dump());
} }
void Bans::LoadBans(BanList* list) void Bans::LoadBans(BanList* list)
{ {
assert(list != nullptr); assert(list);
const auto _ = Lock(); const auto _ = Lock();
FileSystem::File bans("bans.json"); const auto bans = Utils::IO::ReadFile(BanListFile);
if (bans.empty())
if (!bans.exists())
{ {
Logger::Debug("bans.json does not exist"); Logger::Debug("bans.json does not exist");
return; return;
@ -136,14 +138,20 @@ namespace Components
nlohmann::json banData; nlohmann::json banData;
try 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()); Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
return; 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& idList = banData["id"];
const auto& ipList = banData["ip"]; const auto& ipList = banData["ip"];

View File

@ -9,8 +9,6 @@ namespace Components
Bans(); Bans();
static std::unique_lock<Utils::NamedMutex> Lock();
static void BanClient(Game::client_t* cl, const std::string& reason); static void BanClient(Game::client_t* cl, const std::string& reason);
static void UnbanClient(SteamID id); static void UnbanClient(SteamID id);
static void UnbanClient(Game::netIP_t ip); static void UnbanClient(Game::netIP_t ip);
@ -25,6 +23,10 @@ namespace Components
std::vector<Game::netIP_t> ipList; std::vector<Game::netIP_t> ipList;
}; };
static const char* BanListFile;
static std::unique_lock<Utils::NamedMutex> Lock();
static void LoadBans(BanList* list); static void LoadBans(BanList* list);
static void SaveBans(const BanList* list); static void SaveBans(const BanList* list);
}; };

View File

@ -5,6 +5,8 @@
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
#include <json.hpp>
namespace Components namespace Components
{ {
Dvar::Var Chat::cg_chatWidth; Dvar::Var Chat::cg_chatWidth;
@ -14,10 +16,19 @@ namespace Components
bool Chat::SendChat; bool Chat::SendChat;
Utils::Concurrency::Container<Chat::muteList> Chat::MutedList; Utils::Concurrency::Container<Chat::muteList> Chat::MutedList;
const char* Chat::MutedListFile = "userraw/muted-users.json";
bool Chat::CanAddCallback = true; bool Chat::CanAddCallback = true;
std::vector<Scripting::Function> Chat::SayCallbacks; std::vector<Scripting::Function> Chat::SayCallbacks;
// Have only one instance of IW4x read/write the file
std::unique_lock<Utils::NamedMutex> 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) const char* Chat::EvaluateSay(char* text, Game::gentity_t* player, int mode)
{ {
SendChat = true; SendChat = true;
@ -236,8 +247,10 @@ namespace Components
Game::cgsArray[0].teamChatPos++; Game::cgsArray[0].teamChatPos++;
if (Game::cgsArray[0].teamChatPos - Game::cgsArray[0].teamLastChatPos > chatHeight) if (Game::cgsArray[0].teamChatPos - Game::cgsArray[0].teamLastChatPos > chatHeight)
{
Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight; Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight;
} }
}
__declspec(naked) void Chat::CG_AddToTeamChat_Stub() __declspec(naked) void Chat::CG_AddToTeamChat_Stub()
{ {
@ -259,7 +272,20 @@ namespace Components
const auto clientNum = ent - Game::g_entities; const auto clientNum = ent - Game::g_entities;
const auto xuid = Game::svs_clients[clientNum].steamID; const auto xuid = Game::svs_clients[clientNum].steamID;
const auto result = MutedList.access<bool>([&](muteList& clients) const auto result = MutedList.access<bool>([&](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<bool>([&](const muteList& clients)
{ {
return clients.contains(xuid); return clients.contains(xuid);
}); });
@ -273,6 +299,7 @@ namespace Components
MutedList.access([&](muteList& clients) MutedList.access([&](muteList& clients)
{ {
clients.insert(xuid); clients.insert(xuid);
SaveMutedList(clients);
}); });
Logger::Print("{} was muted\n", client->name); Logger::Print("{} was muted\n", client->name);
@ -295,6 +322,67 @@ namespace Components
clients.clear(); clients.clear();
else else
clients.erase(id); 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<std::uint64_t>());
}
}
}); });
} }
@ -522,6 +610,8 @@ namespace Components
sv_disableChat = Dvar::Register<bool>("sv_disableChat", false, Game::DVAR_NONE, "Disable chat messages from clients"); sv_disableChat = Dvar::Register<bool>("sv_disableChat", false, Game::DVAR_NONE, "Disable chat messages from clients");
Events::OnSVInit(AddChatCommands); Events::OnSVInit(AddChatCommands);
LoadMutedList();
// Intercept chat sending // Intercept chat sending
Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick(); Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick();
Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick(); Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick();

View File

@ -8,6 +8,9 @@ namespace Components
public: public:
Chat(); Chat();
static bool IsMuted(const Game::gentity_s* ent);
static bool IsMuted(const Game::client_t* cl);
private: private:
static Dvar::Var cg_chatWidth; static Dvar::Var cg_chatWidth;
static Dvar::Var sv_disableChat; static Dvar::Var sv_disableChat;
@ -17,10 +20,13 @@ namespace Components
using muteList = std::unordered_set<std::uint64_t>; using muteList = std::unordered_set<std::uint64_t>;
static Utils::Concurrency::Container<muteList> MutedList; static Utils::Concurrency::Container<muteList> MutedList;
static const char* MutedListFile;
static bool CanAddCallback; // ClientCommand & GSC thread are the same static bool CanAddCallback; // ClientCommand & GSC thread are the same
static std::vector<Scripting::Function> SayCallbacks; static std::vector<Scripting::Function> SayCallbacks;
static std::unique_lock<Utils::NamedMutex> Lock();
static const char* EvaluateSay(char* text, Game::gentity_t* player, int mode); static const char* EvaluateSay(char* text, Game::gentity_t* player, int mode);
static void PreSayStub(); static void PreSayStub();
@ -30,10 +36,12 @@ namespace Components
static void CG_AddToTeamChat(const char* text); static void CG_AddToTeamChat(const char* text);
static void CG_AddToTeamChat_Stub(); static void CG_AddToTeamChat_Stub();
static bool IsMuted(const Game::gentity_s* ent);
static void MuteClient(const Game::client_t* client); static void MuteClient(const Game::client_t* client);
static void UnmuteClient(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 UnmuteInternal(std::uint64_t id, bool everyone = false);
static void SaveMutedList(const muteList& list);
static void LoadMutedList();
static void AddChatCommands(); static void AddChatCommands();
static int GetCallbackReturn(); static int GetCallbackReturn();

View File

@ -3,6 +3,7 @@
namespace Components namespace Components
{ {
Utils::Signal<Events::ClientCallback> Events::ClientDisconnectSignal; Utils::Signal<Events::ClientCallback> Events::ClientDisconnectSignal;
Utils::Signal<Events::ClientConnectCallback> Events::ClientConnectSignal;
Utils::Signal<Events::Callback> Events::SteamDisconnectSignal; Utils::Signal<Events::Callback> Events::SteamDisconnectSignal;
Utils::Signal<Events::Callback> Events::ShutdownSystemSignal; Utils::Signal<Events::Callback> Events::ShutdownSystemSignal;
Utils::Signal<Events::Callback> Events::ClientInitSignal; Utils::Signal<Events::Callback> Events::ClientInitSignal;
@ -13,6 +14,11 @@ namespace Components
ClientDisconnectSignal.connect(callback); ClientDisconnectSignal.connect(callback);
} }
void Events::OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback)
{
ClientConnectSignal.connect(callback);
}
void Events::OnSteamDisconnect(const Utils::Slot<Callback>& callback) void Events::OnSteamDisconnect(const Utils::Slot<Callback>& callback)
{ {
SteamDisconnectSignal.connect(callback); SteamDisconnectSignal.connect(callback);
@ -44,6 +50,13 @@ namespace Components
Utils::Hook::Call<void(int)>(0x4AA430)(clientNum); // ClientDisconnect Utils::Hook::Call<void(int)>(0x4AA430)(clientNum); // ClientDisconnect
} }
void Events::SV_UserinfoChanged_Hk(Game::client_t* cl)
{
ClientConnectSignal(cl);
Utils::Hook::Call<void(Game::client_t*)>(0x401950)(cl); // SV_UserinfoChanged
}
void Events::SteamDisconnect_Hk() void Events::SteamDisconnect_Hk()
{ {
SteamDisconnectSignal(); SteamDisconnectSignal();
@ -78,6 +91,8 @@ namespace Components
{ {
Utils::Hook(0x625235, ClientDisconnect_Hk, HOOK_CALL).install()->quick(); // SV_FreeClient 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(0x403582, SteamDisconnect_Hk, HOOK_CALL).install()->quick(); // CL_Disconnect
Utils::Hook(0x47548B, Scr_ShutdownSystem_Hk, HOOK_CALL).install()->quick(); // G_LoadGame Utils::Hook(0x47548B, Scr_ShutdownSystem_Hk, HOOK_CALL).install()->quick(); // G_LoadGame

View File

@ -6,6 +6,7 @@ namespace Components
{ {
public: public:
typedef void(ClientCallback)(int clientNum); typedef void(ClientCallback)(int clientNum);
typedef void(ClientConnectCallback)(Game::client_t* cl);
typedef void(Callback)(); typedef void(Callback)();
Events(); Events();
@ -13,6 +14,9 @@ namespace Components
// Server side // Server side
static void OnClientDisconnect(const Utils::Slot<ClientCallback>& callback); static void OnClientDisconnect(const Utils::Slot<ClientCallback>& callback);
// Server side
static void OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback);
// Client side // Client side
static void OnSteamDisconnect(const Utils::Slot<Callback>& callback); static void OnSteamDisconnect(const Utils::Slot<Callback>& callback);
@ -25,12 +29,14 @@ namespace Components
private: private:
static Utils::Signal<ClientCallback> ClientDisconnectSignal; static Utils::Signal<ClientCallback> ClientDisconnectSignal;
static Utils::Signal<ClientConnectCallback> ClientConnectSignal;
static Utils::Signal<Callback> SteamDisconnectSignal; static Utils::Signal<Callback> SteamDisconnectSignal;
static Utils::Signal<Callback> ShutdownSystemSignal; static Utils::Signal<Callback> ShutdownSystemSignal;
static Utils::Signal<Callback> ClientInitSignal; static Utils::Signal<Callback> ClientInitSignal;
static Utils::Signal<Callback> ServerInitSignal; static Utils::Signal<Callback> ServerInitSignal;
static void ClientDisconnect_Hk(int clientNum); static void ClientDisconnect_Hk(int clientNum);
static void SV_UserinfoChanged_Hk(Game::client_t* cl);
static void SteamDisconnect_Hk(); static void SteamDisconnect_Hk();
static void Scr_ShutdownSystem_Hk(unsigned char sys); static void Scr_ShutdownSystem_Hk(unsigned char sys);
static void CL_InitOnceForAllClients_HK(); static void CL_InitOnceForAllClients_HK();

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Chat.hpp"
#include "Voice.hpp" #include "Voice.hpp"
namespace Components namespace Components
@ -381,6 +382,13 @@ namespace Components
Events::OnSteamDisconnect(CL_ClearMutedList); Events::OnSteamDisconnect(CL_ClearMutedList);
Events::OnClientDisconnect(SV_UnmuteClient); 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 // Write voice packets to the server instead of other clients
Utils::Hook(0x487935, CL_WriteVoicePacket_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x487935, CL_WriteVoicePacket_Hk, HOOK_CALL).install()->quick();

View File

@ -2,14 +2,14 @@
namespace Utils::IO 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 WriteFile(const std::string& file, const std::string& data, bool append = false);
bool ReadFile(const std::string& file, std::string* data); 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); 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 CreateDir(const std::string& dir);
bool DirectoryExists(const std::filesystem::path& directory); [[nodiscard]] bool DirectoryExists(const std::filesystem::path& directory);
bool DirectoryIsEmpty(const std::filesystem::path& directory); [[nodiscard]] bool DirectoryIsEmpty(const std::filesystem::path& directory);
std::vector<std::string> ListFiles(const std::filesystem::path& directory, bool recursive = false); [[nodiscard]] std::vector<std::string> ListFiles(const std::filesystem::path& directory, bool recursive = false);
} }