[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
{
const char* Bans::BanListFile = "userraw/bans.json";
// Have only one instance of IW4x read/write the file
std::unique_lock<Utils::NamedMutex> 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"];

View File

@ -9,8 +9,6 @@ namespace Components
Bans();
static std::unique_lock<Utils::NamedMutex> 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<Game::netIP_t> ipList;
};
static const char* BanListFile;
static std::unique_lock<Utils::NamedMutex> Lock();
static void LoadBans(BanList* list);
static void SaveBans(const BanList* list);
};

View File

@ -5,6 +5,8 @@
#include "GSC/Script.hpp"
#include <json.hpp>
namespace Components
{
Dvar::Var Chat::cg_chatWidth;
@ -14,10 +16,19 @@ namespace Components
bool Chat::SendChat;
Utils::Concurrency::Container<Chat::muteList> Chat::MutedList;
const char* Chat::MutedListFile = "userraw/muted-users.json";
bool Chat::CanAddCallback = true;
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)
{
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<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);
});
@ -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<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");
Events::OnSVInit(AddChatCommands);
LoadMutedList();
// Intercept chat sending
Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick();
Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick();

View File

@ -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<std::uint64_t>;
static Utils::Concurrency::Container<muteList> MutedList;
static const char* MutedListFile;
static bool CanAddCallback; // ClientCommand & GSC thread are the same
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 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();

View File

@ -3,6 +3,7 @@
namespace Components
{
Utils::Signal<Events::ClientCallback> Events::ClientDisconnectSignal;
Utils::Signal<Events::ClientConnectCallback> Events::ClientConnectSignal;
Utils::Signal<Events::Callback> Events::SteamDisconnectSignal;
Utils::Signal<Events::Callback> Events::ShutdownSystemSignal;
Utils::Signal<Events::Callback> Events::ClientInitSignal;
@ -13,6 +14,11 @@ namespace Components
ClientDisconnectSignal.connect(callback);
}
void Events::OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback)
{
ClientConnectSignal.connect(callback);
}
void Events::OnSteamDisconnect(const Utils::Slot<Callback>& callback)
{
SteamDisconnectSignal.connect(callback);
@ -44,6 +50,13 @@ namespace Components
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()
{
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

View File

@ -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<ClientCallback>& callback);
// Server side
static void OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback);
// Client side
static void OnSteamDisconnect(const Utils::Slot<Callback>& callback);
@ -25,12 +29,14 @@ namespace Components
private:
static Utils::Signal<ClientCallback> ClientDisconnectSignal;
static Utils::Signal<ClientConnectCallback> ClientConnectSignal;
static Utils::Signal<Callback> SteamDisconnectSignal;
static Utils::Signal<Callback> ShutdownSystemSignal;
static Utils::Signal<Callback> ClientInitSignal;
static Utils::Signal<Callback> 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();

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#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();

View File

@ -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<std::string> 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<std::string> ListFiles(const std::filesystem::path& directory, bool recursive = false);
}