diff --git a/.gitmodules b/.gitmodules index 95e898f6..912d4f5f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,7 +17,7 @@ [submodule "deps/mongoose"] path = deps/mongoose url = https://github.com/cesanta/mongoose.git - branch = 7.9 + branch = 7.8 [submodule "deps/protobuf"] path = deps/protobuf url = https://github.com/google/protobuf.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 52eaa35c..c35b7fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.9] - 2023-03-31 + +### Added +- Game scripts can be loaded from the `scripts/mp`, `scripts/mp/` and `scripts/mp/` folders (#859) +- Add `ReadStream` GSC function (#862) +- Add `IString` GSC function (#877) + +### Changed +- Test Clients will no longer receive names from the Xlabs Patreon website. The old behaviour was restored (#852) +- Enabled `OpenFile` GSC function (#862) +- Enabled `CloseFile` GSC function (#862) +- Chat system uses "reliable message" to mitigate message duplication (#873) +- The built-in GSC compiler no longer throws fatal errors when overriding a built-in function or method (IW3 behaviour) (#880) +- `CastFloat` GSC function was renamed to `Float` (#880) + +### Fixed +- Fix bug where knife lounges would not work with a gamepad (#848) +- Fix rare RCon crash (#861) +- Fix bug where `sv_RandomBotNames` stopped working (#876) +- Fix crash caused by "Mongoose" (dependency) (#874) + +### Known issues + +- Sound issue fix is experimental as the bug is not fully understood. + ## [0.7.8] - 2023-03-17 ### Added @@ -32,7 +57,8 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - Servers can no longer modify client Dvars that are saved in the client's config (#694) - `banClient` and `muteClient` server commands do not apply to bots anymore (#730) - Remove `zb_prefer_disk_assets` Dvar (#772) -- The max value of `perk_extendedMeleeRange`Dvar was increased (#782) +- The max value of `perk_extendedMeleeRange` Dvar was increased (#782) +- Test Clients will receive names from the Xlabs Patreon website in addition to the names from the bots.txt file (#771) ### Fixed - Fix bug where`reloadmenus` command would not free resources used by custom menus (#740) diff --git a/deps/mongoose b/deps/mongoose index 4236405b..0a265e79 160000 --- a/deps/mongoose +++ b/deps/mongoose @@ -1 +1 @@ -Subproject commit 4236405b90e051310aadda818e21c811e404b4d8 +Subproject commit 0a265e79a67d7bfcdca27f2ccb98ccb474677ec6 diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 42189495..de0f2420 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -1,6 +1,7 @@ #include #include +#include "Modules/ArenaLength.hpp" #include "Modules/Bans.hpp" #include "Modules/Bots.hpp" #include "Modules/Branding.hpp" diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 7fc7d59c..dee129b3 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -73,7 +73,6 @@ namespace Components #include "Modules/UIScript.hpp" #include "Modules/ZoneBuilder.hpp" -#include "Modules/ArenaLength.hpp" #include "Modules/AssetHandler.hpp" #include "Modules/Dedicated.hpp" #include "Modules/Events.hpp" diff --git a/src/Components/Modules/ArenaLength.cpp b/src/Components/Modules/ArenaLength.cpp index 3c0c2a93..077ee65f 100644 --- a/src/Components/Modules/ArenaLength.cpp +++ b/src/Components/Modules/ArenaLength.cpp @@ -1,4 +1,5 @@ #include +#include "ArenaLength.hpp" namespace Components { diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp index 0d366ce2..2066126c 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp @@ -12,56 +12,6 @@ namespace Assets assert(header->data); } - void IFxEffectDef::loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) - { - - switch (elemType) - { - case Game::FX_ELEM_TYPE_MODEL: - { - if (visuals->model) - { - visuals->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString(), builder).model; - } - - break; - } - - case Game::FX_ELEM_TYPE_OMNI_LIGHT: - case Game::FX_ELEM_TYPE_SPOT_LIGHT: - break; - - case Game::FX_ELEM_TYPE_SOUND: - { - if (visuals->soundName) - { - visuals->soundName = reader->readCString(); - } - break; - } - - case Game::FX_ELEM_TYPE_RUNNER: - { - if (visuals->effectDef.handle) - { - visuals->effectDef.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, reader->readString(), builder).fx; - } - - break; - } - - default: - { - if (visuals->material) - { - visuals->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString(), builder).material; - } - - break; - } - } - } - void IFxEffectDef::loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { header->fx = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_FX, name); @@ -169,7 +119,16 @@ namespace Assets case 0xA: { - builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_SOUND, visuals->soundName, false); + if (visuals->soundName) + { + // Double "find call" but we have to because otherwise we'd crash on missing asset + // Sometimes Fx reference by name a sound that does not exist. IW oversight ? + // Never happens on iw3 but often happens on iw5, especially DLC maps. + if (Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND, visuals->soundName, builder, false).data) + { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_SOUND, visuals->soundName, false); + } + } break; } diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp index 0ad259ed..10463eea 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp @@ -18,7 +18,5 @@ namespace Assets void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); - - void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader); }; } diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index c0181db1..3dc576ed 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -451,7 +451,7 @@ namespace Components Utils::Hook::Set(0x4D0D60, 0xC301B0); // Guid command - Command::Add("guid", [](Command::Params*) + Command::Add("guid", [] { Logger::Print("Your guid: {:#X}\n", Steam::SteamUser()->GetSteamID().bits); }); diff --git a/src/Components/Modules/Auth.hpp b/src/Components/Modules/Auth.hpp index 84d35fca..48e821f7 100644 --- a/src/Components/Modules/Auth.hpp +++ b/src/Components/Modules/Auth.hpp @@ -19,7 +19,7 @@ namespace Components static unsigned __int64 GetKeyHash(const std::string& key); static uint32_t GetSecurityLevel(); - static void IncreaseSecurityLevel(uint32_t level, const std::string& command = ""); + static void IncreaseSecurityLevel(uint32_t level, const std::string& command = {}); static uint32_t GetZeroBits(Utils::Cryptography::Token token, const std::string& publicKey); static void IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, const std::string& publicKey, uint32_t zeroBits, bool* cancel = nullptr, uint64_t* count = nullptr); diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index 507223bd..0d259589 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -259,17 +259,17 @@ namespace Components } } - const auto num = std::atoi(input); - if (num < 0 || num >= *Game::svs_clientCount) + const auto clientNum = std::strtoul(input, nullptr, 10); + if (clientNum >= Game::MAX_CLIENTS) { - Logger::Print("Bad client slot: {}\n", num); + Logger::Print("Bad client slot: {}\n", clientNum); return; } - const auto* cl = &Game::svs_clients[num]; - if (cl->header.state == Game::CS_FREE) + const auto* cl = &Game::svs_clients[clientNum]; + if (cl->header.state < Game::CS_ACTIVE) { - Logger::Print("Client {} is not active\n", num); + Logger::Print("Client {} is not active\n", clientNum); return; } @@ -279,7 +279,7 @@ namespace Components } const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2); - BanClient(&Game::svs_clients[num], reason); + BanClient(&Game::svs_clients[clientNum], reason); }); Command::Add("unbanClient", [](Command::Params* params) @@ -309,7 +309,7 @@ namespace Components else if (type == "guid"s) { SteamID id; - id.bits = strtoull(params->get(2), nullptr, 16); + id.bits = std::strtoull(params->get(2), nullptr, 16); UnbanClient(id); diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 369e98f6..337f4708 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,8 +1,6 @@ #include -#include #include "Bots.hpp" -#include "ServerList.hpp" #include "GSC/Script.hpp" @@ -14,7 +12,7 @@ namespace Components { std::vector Bots::BotNames; - Dvar::Var Bots::SVClanName; + Dvar::Var Bots::SVRandomBotNames; struct BotMovementInfo { @@ -25,7 +23,7 @@ namespace Components bool active; }; - static BotMovementInfo g_botai[18]; + static BotMovementInfo g_botai[Game::MAX_CLIENTS]; struct BotAction { @@ -59,19 +57,6 @@ namespace Components std::ranges::shuffle(BotNames, gen); } - void Bots::UpdateBotNames() - { - const auto masterPort = (*Game::com_masterPort)->current.integer; - const auto* masterServerName = (*Game::com_masterServerName)->current.string; - - Game::netadr_t master; - if (ServerList::GetMasterServer(masterServerName, masterPort, master)) - { - Logger::Print("Getting bots...\n"); - Network::Send(master, "getbots"); - } - } - void Bots::LoadBotNames() { FileSystem::File bots("bots.txt"); @@ -83,15 +68,8 @@ namespace Components auto data = Utils::String::Split(bots.getBuffer(), '\n'); - auto i = 0; for (auto& entry : data) { - if (i >= 18) - { - // Only parse 18 names from the file - break; - } - // Take into account for CR line endings Utils::String::Replace(entry, "\r", ""); // Remove whitespace @@ -116,11 +94,10 @@ namespace Components entry = entry.substr(0, pos); } - BotNames.emplace_back(std::make_pair(entry, clanAbbrev)); - ++i; + BotNames.emplace_back(entry, clanAbbrev); } - if (i) + if (SVRandomBotNames.get()) { RandomizeBotNames(); } @@ -128,7 +105,7 @@ namespace Components int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { - static size_t botId = 0; // Loop over the BotNames vector + static std::size_t botId = 0; // Loop over the BotNames vector static bool loadedNames = false; // Load file only once std::string botName; std::string clanName; @@ -152,11 +129,6 @@ namespace Components clanName = "BOT"s; } - if (const auto svClanName = SVClanName.get(); !svClanName.empty()) - { - clanName = svClanName; - } - return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port); } @@ -203,15 +175,14 @@ namespace Components Game::Scr_AddBool(Game::SV_IsTestClient(ent->s.number) != 0); } - void Bots::AddMethods() + void Bots::AddScriptMethods() { GSC::Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient(); - GSC::Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); + GSC::Script::AddMethod("BotStop", [](const Game::scr_entref_t entref) // Usage: BotStop(); { const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - - if (Game::SV_IsTestClient(ent->s.number) == 0) + if (!Game::SV_IsTestClient(ent->s.number)) { Game::Scr_Error("BotStop: Can only call on a bot!"); return; @@ -222,18 +193,16 @@ namespace Components g_botai[entref.entnum].active = true; }); - GSC::Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); + GSC::Script::AddMethod("BotWeapon", [](const Game::scr_entref_t entref) // Usage: BotWeapon(); { const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - - if (Game::SV_IsTestClient(ent->s.number) == 0) + if (!Game::SV_IsTestClient(ent->s.number)) { Game::Scr_Error("BotWeapon: Can only call on a bot!"); return; } const auto* weapon = Game::Scr_GetString(0); - if (!weapon || !*weapon) { g_botai[entref.entnum].weapon = 1; @@ -245,18 +214,16 @@ namespace Components g_botai[entref.entnum].active = true; }); - GSC::Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); + GSC::Script::AddMethod("BotAction", [](const Game::scr_entref_t entref) // Usage: BotAction(); { const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - - if (Game::SV_IsTestClient(ent->s.number) == 0) + if (!Game::SV_IsTestClient(ent->s.number)) { Game::Scr_Error("BotAction: Can only call on a bot!"); return; } const auto* action = Game::Scr_GetString(0); - if (!action) { Game::Scr_ParamError(0, "BotAction: Illegal parameter!"); @@ -286,11 +253,10 @@ namespace Components Game::Scr_ParamError(0, "BotAction: Unknown action"); }); - GSC::Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); + GSC::Script::AddMethod("BotMovement", [](const Game::scr_entref_t entref) // Usage: BotMovement(, ); { const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - - if (Game::SV_IsTestClient(ent->s.number) == 0) + if (!Game::SV_IsTestClient(ent->s.number)) { Game::Scr_Error("BotMovement: Can only call on a bot!"); return; @@ -399,30 +365,7 @@ namespace Components Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); - Events::OnDvarInit([] - { - SVClanName = Dvar::Register("sv_clanName", "", Game::DVAR_NONE, "The clan name for test clients"); - }); - - Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN); - - Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data) - { - const auto masterPort = (*Game::com_masterPort)->current.integer; - const auto* masterServerName = (*Game::com_masterServerName)->current.string; - - Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); - if (master == address) - { - auto botNames = Utils::String::Split(data, '\n'); - for (const auto& entry : botNames) - { - BotNames.emplace_back(std::make_pair(entry, "BOT")); - } - - RandomizeBotNames(); - } - }); + SVRandomBotNames = Dvar::Register("sv_RandomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); // Reset BotMovementInfo.active when client is dropped Events::OnClientDisconnect([](const int clientNum) @@ -475,7 +418,7 @@ namespace Components Spawn(count); }); - AddMethods(); + AddScriptMethods(); // In case a loaded mod didn't call "BotStop" before the VM shutdown Events::OnVMShutdown([] diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index efa4d3a7..b7423914 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -11,17 +11,16 @@ namespace Components using botData = std::pair< std::string, std::string>; static std::vector BotNames; - static Dvar::Var SVClanName; + static Dvar::Var SVRandomBotNames; static void RandomizeBotNames(); - static void UpdateBotNames(); static void LoadBotNames(); static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static void Spawn(unsigned int count); static void GScr_isTestClient(Game::scr_entref_t entref); - static void AddMethods(); + static void AddScriptMethods(); static void BotAiAction(Game::client_t* cl); static void SV_BotUserMove_Hk(); diff --git a/src/Components/Modules/Ceg.cpp b/src/Components/Modules/Ceg.cpp index f6fb5a33..29c9c680 100644 --- a/src/Components/Modules/Ceg.cpp +++ b/src/Components/Modules/Ceg.cpp @@ -44,6 +44,9 @@ namespace Components Utils::Hook::Nop(0x43EC96, 9); Utils::Hook::Nop(0x4675C6, 9); Utils::Hook::Nop(0x405A36, 9); + Utils::Hook::Nop(0x4CE656, 9); + Utils::Hook::Nop(0x461E66, 9); + Utils::Hook::Nop(0x4EB3F6, 9); // Random checks scattered throughout the binary Utils::Hook::Set(0x499F90, 0xC3); @@ -56,13 +59,7 @@ namespace Components Utils::Hook::Set(0x461930, 0xC3); Utils::Hook::Set(0x430410, 0xC3); - // Used next to file system functions - Utils::Hook::Set(0x47BC00, 0xC3); - // Looking for stuff in the registry Utils::Hook::Nop(0x4826F8, 5); - - // Live_Init - Utils::Hook::Nop(0x420937, 5); } } diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index 34e5ebac..0e4e4537 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -63,13 +63,13 @@ namespace Components if (IsMuted(player)) { SendChat = false; - Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65)); + Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You are muted\"", 0x65)); } if (sv_disableChat.get()) { SendChat = false; - Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65)); + Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"Chat is disabled\"", 0x65)); } // Message might be empty after processing the '/' @@ -302,7 +302,7 @@ namespace Components }); Logger::Print("{} was muted\n", client->name); - Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were muted\"", 0x65)); + Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You were muted\"", 0x65)); } void Chat::UnmuteClient(const Game::client_t* client) @@ -310,7 +310,7 @@ namespace Components UnmuteInternal(client->steamID); Logger::Print("{} was unmuted\n", client->name); - Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were unmuted\"", 0x65)); + Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You were unmuted\"", 0x65)); } void Chat::UnmuteInternal(const std::uint64_t id, bool everyone) @@ -461,17 +461,17 @@ namespace Components if (params->size() < 2) return; - auto message = params->join(1); - auto name = sv_sayName.get(); + const auto message = params->join(1); + const auto name = sv_sayName.get(); if (!name.empty()) { - Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message)); + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message)); Logger::Print(Game::CON_CHANNEL_SERVER, "{}: {}\n", name, message); } else { - Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message)); + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message)); Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message); } }); @@ -486,19 +486,21 @@ namespace Components if (params->size() < 3) return; - const auto client = std::atoi(params->get(1)); - auto message = params->join(2); - auto name = sv_sayName.get(); + const auto parsedInput = std::strtoul(params->get(1), nullptr, 10); + const auto clientNum = static_cast(std::min(parsedInput, Game::MAX_CLIENTS)); + + const auto message = params->join(2); + const auto name = sv_sayName.get(); if (!name.empty()) { - Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message)); - Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, client, message); + Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message)); + Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, clientNum, message); } else { - Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message)); - Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", client, message); + Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message)); + Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", clientNum, message); } }); @@ -512,8 +514,8 @@ namespace Components if (params->size() < 2) return; - auto message = params->join(1); - Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message)); + const auto message = params->join(1); + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}\"", 0x68, message)); Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message); }); @@ -527,10 +529,12 @@ namespace Components if (params->size() < 3) return; - const auto client = atoi(params->get(1)); - std::string message = params->join(2); - Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message)); - Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", client, message); + const auto parsedInput = std::strtoul(params->get(1), nullptr, 10); + const auto clientNum = static_cast(std::min(parsedInput, Game::MAX_CLIENTS)); + + const auto message = params->join(2); + Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}\"", 0x68, message)); + Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", clientNum, message); }); sv_sayName = Dvar::Register("sv_sayName", "^7Console", Game::DVAR_NONE, "The alias of the server when broadcasting a chat message"); @@ -620,6 +624,9 @@ namespace Components Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick(); Utils::Hook(0x4D0110, PostSayStub, HOOK_CALL).install()->quick(); + // Patch G_SayTo to use SV_CMD_RELIABLE + Utils::Hook::Set(0x5DF7E3, Game::SV_CMD_RELIABLE); + // 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(); diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 5256b1de..a1fc116a 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -61,31 +61,8 @@ namespace Components void ClientCommand::AddCheatCommands() { - Add("noclip", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) - { - if (!CheatsOk(ent)) - return; - - ent->client->flags ^= Game::PF_NOCLIP; - - const auto entNum = ent->s.number; - Logger::Debug("Noclip toggled for entity {}", entNum); - - Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); - }); - - Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) - { - if (!CheatsOk(ent)) - return; - - ent->client->flags ^= Game::PF_UFO; - - const auto entNum = ent->s.number; - Logger::Debug("UFO toggled for entity {}", entNum); - - Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); - }); + Add("noclip", Cmd_Noclip_f); + Add("ufo", Cmd_UFO_f); Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) { @@ -369,6 +346,21 @@ namespace Components }); } + void ClientCommand::AddScriptMethods() + { + GSC::Script::AddMethod("Noclip", [](const Game::scr_entref_t entref) // gsc: self Noclip(); + { + auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + Cmd_Noclip_f(ent, nullptr); + }); + + GSC::Script::AddMethod("Ufo", [](const Game::scr_entref_t entref) // gsc: self Ufo(); + { + auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + Cmd_UFO_f(ent, nullptr); + }); + } + const char* ClientCommand::EntInfoLine(const int entNum) { const auto* ent = &Game::g_entities[entNum]; @@ -477,6 +469,32 @@ namespace Components Logger::Print(Game::CON_CHANNEL_SERVER, "Done writing file.\n"); } + void ClientCommand::Cmd_Noclip_f(Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) + { + if (!CheatsOk(ent)) + return; + + ent->client->flags ^= Game::PF_NOCLIP; + + const auto entNum = ent->s.number; + Logger::Debug("Noclip toggled for entity {}", entNum); + + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); + } + + void ClientCommand::Cmd_UFO_f(Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) + { + if (!CheatsOk(ent)) + return; + + ent->client->flags ^= Game::PF_UFO; + + const auto entNum = ent->s.number; + Logger::Debug("UFO toggled for entity {}", entNum); + + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); + } + ClientCommand::ClientCommand() { AssertOffset(Game::playerState_s, stats, 0x150); @@ -486,6 +504,7 @@ namespace Components AddCheatCommands(); AddScriptFunctions(); + AddScriptMethods(); #ifdef _DEBUG AddDevelopmentCommands(); #endif diff --git a/src/Components/Modules/ClientCommand.hpp b/src/Components/Modules/ClientCommand.hpp index 554a9c26..b6b39cb5 100644 --- a/src/Components/Modules/ClientCommand.hpp +++ b/src/Components/Modules/ClientCommand.hpp @@ -16,10 +16,15 @@ namespace Components static void ClientCommandStub(int clientNum); static void AddCheatCommands(); static void AddDevelopmentCommands(); + static void AddScriptFunctions(); + static void AddScriptMethods(); static const char* EntInfoLine(int entNum); static void G_DumpEntityDebugInfoToConsole(bool logfileOnly); static void G_DumpEntityDebugInfoToCSV(const char* filenameSuffix); + + static void Cmd_Noclip_f(Game::gentity_s* ent, const Command::ServerParams* params); + static void Cmd_UFO_f(Game::gentity_s* ent, const Command::ServerParams* params); }; } diff --git a/src/Components/Modules/Command.hpp b/src/Components/Modules/Command.hpp index 57cc4f67..41cb5ec9 100644 --- a/src/Components/Modules/Command.hpp +++ b/src/Components/Modules/Command.hpp @@ -57,16 +57,16 @@ namespace Components static Game::cmd_function_s* Allocate(); static void Add(const char* name, const std::function& callback); - static void Add(const char* name, const std::function& callback); + static void Add(const char* name, const std::function& callback); static void AddRaw(const char* name, void(*callback)(), bool key = false); - static void AddSV(const char* name, const std::function& callback); + static void AddSV(const char* name, const std::function& callback); static void Execute(std::string command, bool sync = true); static Game::cmd_function_s* Find(const std::string& command); private: - static std::unordered_map> FunctionMap; - static std::unordered_map> FunctionMapSV; + static std::unordered_map> FunctionMap; + static std::unordered_map> FunctionMapSV; static void AddRawSV(const char* name, void(*callback)()); diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index ed61489c..c30824cd 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -34,14 +34,14 @@ namespace Components bool Console::SkipShutdown = false; COLORREF Console::TextColor = -#if _DEBUG +#ifdef _DEBUG RGB(255, 200, 117); #else RGB(120, 237, 122); #endif COLORREF Console::BackgroundColor = -#if _DEBUG +#ifdef _DEBUG RGB(35, 21, 0); #else RGB(25, 32, 25); @@ -383,6 +383,7 @@ namespace Components RefreshOutput(); +#ifdef _DEBUG if (IsDebuggerPresent()) { while (true) @@ -390,6 +391,7 @@ namespace Components std::this_thread::sleep_for(5s); } } +#endif TerminateProcess(GetCurrentProcess(), EXIT_FAILURE); } @@ -894,7 +896,7 @@ namespace Components Utils::Hook(0x64DC6B, 0x64DCC2, HOOK_JUMP).install()->quick(); #ifdef _DEBUG - Console::AddConsoleCommand(); + AddConsoleCommand(); #endif if (Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 51291f79..f0a15590 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -14,6 +14,8 @@ namespace Components Dvar::Var Dedicated::SVMOTD; Dvar::Var Dedicated::COMLogFilter; + const Game::dvar_t* Dedicated::com_dedicated; + bool Dedicated::IsEnabled() { static std::optional flag; @@ -184,8 +186,6 @@ namespace Components // Make sure all callbacks are handled Scheduler::Loop(Steam::SteamAPI_RunCallbacks, Scheduler::Pipeline::SERVER); - SVLanOnly = Dvar::Register("sv_lanOnly", false, Game::DVAR_NONE, "Don't act as node"); - Utils::Hook(0x60BE98, InitDedicatedServer, HOOK_CALL).install()->quick(); Utils::Hook::Set(0x683370, 0xC3); // steam sometimes doesn't like the server @@ -249,6 +249,26 @@ namespace Components Events::OnDvarInit([] { SVMOTD = Dvar::Register("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers"); + SVLanOnly = Dvar::Register("sv_lanOnly", false, Game::DVAR_NONE, "Don't act as node"); + + static const char* g_dedicatedEnumNames[] = + { + "listen server", + "dedicated LAN server", + "dedicated internet server", + nullptr, + }; + + // IW5MP Dedicated Server adds another flag. That flag should not exist on this version of IW4 + com_dedicated = Game::Dvar_RegisterEnum("dedicated", g_dedicatedEnumNames, 2, Game::DVAR_ROM, "True if this is a dedicated server"); + // Dedicated only behaviour from IW5MP Dedicated Server. + if (com_dedicated->current.integer != 1 && com_dedicated->current.integer != 2) + { + Game::DvarValue value; + value.integer = 0; + Game::Dvar_SetVariant(const_cast(com_dedicated), value, Game::DVAR_SOURCE_INTERNAL); + + } }); // Post initialization point diff --git a/src/Components/Modules/Dedicated.hpp b/src/Components/Modules/Dedicated.hpp index 1a8ff668..0ba53f5e 100644 --- a/src/Components/Modules/Dedicated.hpp +++ b/src/Components/Modules/Dedicated.hpp @@ -12,6 +12,8 @@ namespace Components static Dvar::Var SVMOTD; static Dvar::Var COMLogFilter; + static const Game::dvar_t* com_dedicated; + static bool IsEnabled(); static bool IsRunning(); diff --git a/src/Components/Modules/Discord.cpp b/src/Components/Modules/Discord.cpp index 12889f5e..9087d734 100644 --- a/src/Components/Modules/Discord.cpp +++ b/src/Components/Modules/Discord.cpp @@ -62,6 +62,8 @@ namespace Components return; } + char hostNameBuffer[256]{}; + const auto* map = Game::UI_GetMapDisplayName((*Game::ui_mapname)->current.string); const Game::StringTable* table; @@ -86,7 +88,6 @@ namespace Components } else { - char hostNameBuffer[256]{}; TextRenderer::StripColors(Party::GetHostName().data(), hostNameBuffer, sizeof(hostNameBuffer)); TextRenderer::StripAllTextIcons(hostNameBuffer, hostNameBuffer, sizeof(hostNameBuffer)); @@ -97,7 +98,7 @@ namespace Components std::hash hashFn; const auto address = Party::Target(); - DiscordPresence.partyId = Utils::String::VA("%zu", hashFn(address) ^ GetDiscordNonce()); + DiscordPresence.partyId = Utils::String::VA("%s - %zu", hostNameBuffer, hashFn(address) ^ GetDiscordNonce()); DiscordPresence.joinSecret = address.getCString(); DiscordPresence.partySize = Game::cgArray[0].snap ? Game::cgArray[0].snap->numClients : 1; DiscordPresence.partyMax = Party::GetMaxClients(); diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index a5b1ce7d..de1360a9 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -9,6 +9,8 @@ #include +#define MG_OVERRIDE_LOG_FN + namespace Components { static mg_mgr Mgr; @@ -26,6 +28,8 @@ namespace Components volatile bool Download::Terminate; bool Download::ServerRunning; + std::string Download::MongooseLogBuffer; + #pragma region Client void Download::InitiateMapDownload(const std::string& map, bool needPassword) @@ -416,6 +420,19 @@ namespace Components #pragma region Server + void Download::LogFn(char c, [[maybe_unused]] void* param) + { + // Truncate & print if buffer is 1024 characters in length or otherwise only print when we reached a 'new line' + if (!std::isprint(static_cast(c)) || MongooseLogBuffer.size() == 1024) + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "{}\n", MongooseLogBuffer); + MongooseLogBuffer.clear(); + return; + } + + MongooseLogBuffer.push_back(c); + } + static std::string InfoHandler() { const auto status = ServerInfo::GetInfo(); @@ -425,6 +442,7 @@ namespace Components info["status"] = status.to_json(); info["host"] = host.to_json(); info["map_rotation"] = MapRotation::to_json(); + info["dedicated"] = Dedicated::com_dedicated->current.value; std::vector players; @@ -432,23 +450,27 @@ namespace Components for (auto i = 0; i < Game::MAX_CLIENTS; ++i) { std::unordered_map playerInfo; + // Insert default values playerInfo["score"] = 0; playerInfo["ping"] = 0; playerInfo["name"] = ""; + playerInfo["test_client"] = 0; if (Dedicated::IsRunning()) { - if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue; + if (Game::svs_clients[i].header.state < Game::CS_ACTIVE) continue; + if (!Game::svs_clients[i].gentity || !Game::svs_clients[i].gentity->client) continue; playerInfo["score"] = Game::SV_GameClientNum_Score(i); playerInfo["ping"] = Game::svs_clients[i].ping; playerInfo["name"] = Game::svs_clients[i].name; + playerInfo["test_client"] = Game::svs_clients[i].bIsTestClient; } else { // Score and ping are irrelevant const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i); - if (name == nullptr || *name == '\0') continue; + if (!name || !*name) continue; playerInfo["name"] = name; } @@ -457,7 +479,7 @@ namespace Components } info["players"] = players; - return {nlohmann::json(info).dump()}; + return nlohmann::json(info).dump(); } static std::string ListHandler() @@ -503,7 +525,7 @@ namespace Components jsonList = fileList; } - return {jsonList.dump()}; + return jsonList.dump(); } static std::string MapHandler() @@ -547,7 +569,7 @@ namespace Components jsonList = fileList; } - return {jsonList.dump()}; + return jsonList.dump(); } static void FileHandler(mg_connection* c, const mg_http_message* hm) @@ -663,6 +685,16 @@ namespace Components { if (!Flags::HasFlag("disable-mongoose")) { +#ifdef _DEBUG + mg_log_set(MG_LL_INFO); +#else + mg_log_set(MG_LL_ERROR); +#endif + +#ifdef MG_OVERRIDE_LOG_FN + mg_log_set_fn(LogFn, nullptr); +#endif + mg_mgr_init(&Mgr); Network::OnStart([] diff --git a/src/Components/Modules/Download.hpp b/src/Components/Modules/Download.hpp index fa3dd654..fe8ab847 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -91,10 +91,14 @@ namespace Components static volatile bool Terminate; static bool ServerRunning; + static std::string MongooseLogBuffer; + static void DownloadProgress(FileDownload* fDownload, std::size_t bytes); static void ModDownloader(ClientDownload* download); static bool ParseModList(ClientDownload* download, const std::string& list); static bool DownloadFile(ClientDownload* download, unsigned int index); + + static void LogFn(char c, void* param); }; } diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index 8ded131e..f6ae52b2 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -432,6 +432,9 @@ namespace Components // Uncheat ui_debugMode Utils::Hook::Xor(0x6312DE, Game::DVAR_CHEAT); + // Uncheat jump_slowdownEnable + Utils::Hook::Xor(0x4EFABE, Game::DVAR_CHEAT); + // Hook dvar 'name' registration Utils::Hook(0x40531C, Dvar_RegisterName, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index b222ef40..900c2111 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -438,7 +438,7 @@ namespace Components if (!Friends::IsOnline(user.lastTime)) return "Online"; if (user.server.getType() == Game::NA_BAD) return "Playing IW4x"; if (user.serverName.empty()) return Utils::String::VA("Playing on %s", user.server.getCString()); - return Utils::String::VA("Playing %s on %s", Game::UI_LocalizeMapName(user.mapname.data()), user.serverName.data()); + return Utils::String::VA("Playing %s on %s", Localization::LocalizeMapName(user.mapname.data()), user.serverName.data()); } default: diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp index 203fcbbc..af5ac692 100644 --- a/src/Components/Modules/GSC/IO.cpp +++ b/src/Components/Modules/GSC/IO.cpp @@ -4,17 +4,100 @@ namespace Components::GSC { - const char* IO::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + const char* IO::ForbiddenStrings[] = { R"(..)", R"(../)", R"(..\)" }; + + FILE* IO::openScriptIOFileHandle; + + std::filesystem::path IO::Path; + + void IO::GScr_OpenFile() + { + const auto* filepath = Game::Scr_GetString(0); + const auto* mode = Game::Scr_GetString(1); + + for (std::size_t i = 0; i < std::extent_v; ++i) + { + if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile: directory traversal is not allowed!\n"); + Game::Scr_AddInt(-1); + return; + } + } + + if (mode != "read"s) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "Valid openfile modes are 'read'\n"); + Game::Scr_AddInt(-1); + return; + } + + if (openScriptIOFileHandle) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. {} files already open\n", 1); + Game::Scr_AddInt(-1); + return; + } + + const auto scriptData = Path / "scriptdata"s / filepath; + + _set_errno(0); + const auto result = fopen_s(&openScriptIOFileHandle, scriptData.string().data(), "r"); + if (result || !openScriptIOFileHandle) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. '{}'", result); + Game::Scr_AddInt(-1); + return; + } + + Game::Scr_AddInt(1); + } + + void IO::GScr_ReadStream() + { + if (!openScriptIOFileHandle) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "ReadStream failed. File stream was not opened\n"); + return; + } + + char line[1024]{}; + if (std::fgets(line, sizeof(line), openScriptIOFileHandle) != nullptr) + { + Game::Scr_AddString(line); + return; + } + + Logger::Warning(Game::CON_CHANNEL_PARSERSCRIPT, "ReadStream failed.\n"); + + if (std::feof(openScriptIOFileHandle)) + { + Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "ReadStream: EOF reached\n"); + } + } + + void IO::GScr_CloseFile() + { + if (!openScriptIOFileHandle) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "CloseFile failed. File stream was not opened\n"); + Game::Scr_AddInt(-1); + return; + } + + Game::Scr_AddInt(std::fclose(openScriptIOFileHandle)); + openScriptIOFileHandle = nullptr; + } void IO::AddScriptFunctions() { Script::AddFunction("FileWrite", [] // gsc: FileWrite(, , ) { - const auto* path = Game::Scr_GetString(0); - auto* text = Game::Scr_GetString(1); - auto* mode = Game::Scr_GetString(2); + const auto* filepath = Game::Scr_GetString(0); + const auto* text = Game::Scr_GetString(1); + const auto* mode = Game::Scr_GetString(2); - if (!path) + if (!filepath) { Game::Scr_ParamError(0, "FileWrite: filepath is not defined!"); return; @@ -26,117 +109,124 @@ namespace Components::GSC return; } - for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + for (std::size_t i = 0; i < std::extent_v; ++i) { - if (std::strstr(path, QueryStrings[i]) != nullptr) + if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) { - Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileWrite: directory traversal is not allowed!\n"); + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileWrite: directory traversal is not allowed!\n"); return; } } if (mode != "append"s && mode != "write"s) { - Logger::Warning(Game::CON_CHANNEL_SCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); + Logger::Warning(Game::CON_CHANNEL_PARSERSCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); mode = "write"; } - const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); - - if (mode == "write"s) - { - FileSystem::FileWriter(scriptData).write(text); - } - else if (mode == "append"s) - { - FileSystem::FileWriter(scriptData, true).write(text); - } + const auto append = mode == "append"s; + const auto scriptData = Path / "scriptdata"s / filepath; + Utils::IO::WriteFile(scriptData.string(), text, append); }); Script::AddFunction("FileRead", [] // gsc: FileRead() { - const auto* path = Game::Scr_GetString(0); - - if (!path) + const auto* filepath = Game::Scr_GetString(0); + if (!filepath) { Game::Scr_ParamError(0, "FileRead: filepath is not defined!"); return; } - for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + for (std::size_t i = 0; i < std::extent_v; ++i) { - if (std::strstr(path, QueryStrings[i]) != nullptr) + if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) { - Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: directory traversal is not allowed!\n"); + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: directory traversal is not allowed!\n"); return; } } - const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); + const auto scriptData = Path / "scriptdata"s / filepath; - const auto file = FileSystem::FileReader(scriptData); - if (!file.exists()) + std::string file; + if (!Utils::IO::ReadFile(scriptData.string(), &file)) { - Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: file '{}' not found!\n", scriptData); + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: file '{}' not found!\n", scriptData.string()); return; } - auto buffer = file.getBuffer(); - buffer = buffer.substr(0, 1024 - 1); // 1024 is the max string size for the SL system - Game::Scr_AddString(buffer.data()); + file = file.substr(0, 1024 - 1); // 1024 is the max string size for the SL system + Game::Scr_AddString(file.data()); }); Script::AddFunction("FileExists", [] // gsc: FileExists() { - const auto* path = Game::Scr_GetString(0); - - if (!path) + const auto* filepath = Game::Scr_GetString(0); + if (!filepath) { Game::Scr_ParamError(0, "FileExists: filepath is not defined!"); return; } - for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + for (std::size_t i = 0; i < std::extent_v; ++i) { - if (std::strstr(path, QueryStrings[i]) != nullptr) + if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) { - Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileExists: directory traversal is not allowed!\n"); + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileExists: directory traversal is not allowed!\n"); return; } } - const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); - Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists()); + const auto scriptData = Path / "scriptdata"s / filepath; + Game::Scr_AddBool(Utils::IO::FileExists(scriptData.string())); }); Script::AddFunction("FileRemove", [] // gsc: FileRemove() { - const auto* path = Game::Scr_GetString(0); - - if (!path) + const auto* filepath = Game::Scr_GetString(0); + if (!filepath) { Game::Scr_ParamError(0, "FileRemove: filepath is not defined!"); return; } - for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + for (std::size_t i = 0; i < std::extent_v; ++i) { - if (std::strstr(path, QueryStrings[i]) != nullptr) + if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) { - Logger::Print("FileRemove: directory traversal is not allowed!\n"); + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRemove: directory traversal is not allowed!\n"); return; } } - const auto p = "scriptdata"s / std::filesystem::path(path); - const auto folder = p.parent_path().string(); - const auto file = p.filename().string(); - Game::Scr_AddInt(FileSystem::_DeleteFile(folder, file)); + const auto scriptData = Path / "scriptdata"s / filepath; + Game::Scr_AddBool(Utils::IO::RemoveFile(scriptData.string())); }); + + Script::AddFunction("ReadStream", GScr_ReadStream); } IO::IO() { + openScriptIOFileHandle = nullptr; + Path = "userraw"s; + AddScriptFunctions(); + + Utils::Hook::Set(0x79A858, GScr_OpenFile); + Utils::Hook::Set(0x79A85C, 0); + + Utils::Hook::Set(0x79A864, GScr_CloseFile); + Utils::Hook::Set(0x79A868, 0); + + Events::OnVMShutdown([] + { + if (openScriptIOFileHandle) + { + std::fclose(openScriptIOFileHandle); + openScriptIOFileHandle = nullptr; + } + }); } } diff --git a/src/Components/Modules/GSC/IO.hpp b/src/Components/Modules/GSC/IO.hpp index a4df0788..11729c89 100644 --- a/src/Components/Modules/GSC/IO.hpp +++ b/src/Components/Modules/GSC/IO.hpp @@ -8,7 +8,15 @@ namespace Components::GSC IO(); private: - static const char* QueryStrings[]; + static const char* ForbiddenStrings[]; + + static FILE* openScriptIOFileHandle; + + static std::filesystem::path Path; + + static void GScr_OpenFile(); + static void GScr_ReadStream(); + static void GScr_CloseFile(); static void AddScriptFunctions(); }; diff --git a/src/Components/Modules/GSC/Int64.cpp b/src/Components/Modules/GSC/Int64.cpp index f091fead..c922a21c 100644 --- a/src/Components/Modules/GSC/Int64.cpp +++ b/src/Components/Modules/GSC/Int64.cpp @@ -72,16 +72,20 @@ namespace Components::GSC const auto* op = Game::Scr_GetString(1); const auto b = GetInt64Arg(2, true); - if (const auto itr = Operations.find(op); itr != Operations.end()) { - Game::Scr_AddString(Utils::String::VA("%lld", itr->second(a, b))); - return; + if (const auto itr = Operations.find(op); itr != Operations.end()) + { + Game::Scr_AddString(Utils::String::VA("%lld", itr->second(a, b))); + return; + } } - if (const auto itr = Comparisons.find(op); itr != Comparisons.end()) { - Game::Scr_AddBool(itr->second(a, b)); - return; + if (const auto itr = Comparisons.find(op); itr != Comparisons.end()) + { + Game::Scr_AddBool(itr->second(a, b)); + return; + } } Game::Scr_ParamError(1, "Invalid int64 operation"); diff --git a/src/Components/Modules/GSC/Script.cpp b/src/Components/Modules/GSC/Script.cpp index 7cd888d3..cb8ea6bb 100644 --- a/src/Components/Modules/GSC/Script.cpp +++ b/src/Components/Modules/GSC/Script.cpp @@ -35,24 +35,23 @@ namespace Components::GSC Game::Scr_StartupGameType(); } - // Do not use C++ objects because Scr_LoadScript may longjmp - void Script::GScr_LoadGameTypeScript_Stub() + void Script::LoadCustomScriptsFromFolder(const char* dir) { - // Clear handles (from previous GSC loading session) - ScriptMainHandles.clear(); - ScriptInitHandles.clear(); + char path[MAX_OSPATH]{}; + char searchPath[MAX_OSPATH]{}; - char path[MAX_PATH]{}; + strncpy_s(searchPath, dir, _TRUNCATE); + strncat_s(searchPath, "/", _TRUNCATE); auto numFiles = 0; - const auto** files = Game::FS_ListFiles("scripts/", "gsc", Game::FS_LIST_ALL, &numFiles, 10); + const auto** files = Game::FS_ListFiles(searchPath, "gsc", Game::FS_LIST_ALL, &numFiles, 10); for (auto i = 0; i < numFiles; ++i) { const auto* scriptFile = files[i]; Logger::Print("Loading script {}...\n", scriptFile); - const auto len = sprintf_s(path, "%s/%s", "scripts", scriptFile); + const auto len = sprintf_s(path, "%s/%s", dir, scriptFile); if (len == -1) { continue; @@ -63,7 +62,7 @@ namespace Components::GSC if (!Game::Scr_LoadScript(path)) { - Logger::Print("Script {} encountered an error while loading. (doesn't exist?)", path); + Logger::Print("Script {} encountered an error while loading. A compilation error is the most likely cause\n", path); continue; } @@ -72,12 +71,14 @@ namespace Components::GSC const auto initHandle = Game::Scr_GetFunctionHandle(path, "init"); if (initHandle != 0) { + Logger::Debug("Loaded '{}::init'", path); ScriptInitHandles.insert_or_assign(path, initHandle); } const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main"); if (mainHandle != 0) { + Logger::Debug("Loaded '{}::main'", path); ScriptMainHandles.insert_or_assign(path, mainHandle); } @@ -85,6 +86,34 @@ namespace Components::GSC } Game::FS_FreeFileList(files, 10); + } + + void Script::LoadCustomScripts() + { + LoadCustomScriptsFromFolder("scripts"); + + // Game specific + const auto* gameDir = "scripts/mp"; + LoadCustomScriptsFromFolder(gameDir); + + // Map specific + const auto* mapDir = Utils::String::Format("scripts/mp/{}", (*Game::sv_mapname)->current.string); + LoadCustomScriptsFromFolder(mapDir); + + // Mode specific + const auto* modeDir = Utils::String::Format("scripts/mp/{}", (*Game::g_gametype)->current.string); + LoadCustomScriptsFromFolder(modeDir); + } + + // Do not use C++ objects because Scr_LoadScript may longjmp and crash or leak memory + void Script::GScr_LoadGameTypeScript_Stub() + { + // Clear handles (from previous GSC loading session) + ScriptMainHandles.clear(); + ScriptInitHandles.clear(); + + LoadCustomScripts(); + Game::GScr_LoadGameTypeScript(); } @@ -255,5 +284,9 @@ namespace Components::GSC Utils::Hook(0x4EC8DD, BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick(); + + // Restore IW3's compiler behaviour when dealing with 'overriding builtin function' + Utils::Hook::Nop(0x613EDA, 2); // Scr_GetFunction + Utils::Hook::Nop(0x613EF0, 2); // Scr_GetMethod } } diff --git a/src/Components/Modules/GSC/Script.hpp b/src/Components/Modules/GSC/Script.hpp index 7ca0cb24..96f3566a 100644 --- a/src/Components/Modules/GSC/Script.hpp +++ b/src/Components/Modules/GSC/Script.hpp @@ -39,6 +39,9 @@ namespace Components::GSC static std::unordered_map ScriptMainHandles; static std::unordered_map ScriptInitHandles; + static void LoadCustomScriptsFromFolder(const char* dir); + static void LoadCustomScripts(); + static void Scr_LoadGameType_Stub(); static void Scr_StartupGameType_Stub(); static void GScr_LoadGameTypeScript_Stub(); diff --git a/src/Components/Modules/GSC/ScriptExtension.cpp b/src/Components/Modules/GSC/ScriptExtension.cpp index 635f1e82..2d8919ed 100644 --- a/src/Components/Modules/GSC/ScriptExtension.cpp +++ b/src/Components/Modules/GSC/ScriptExtension.cpp @@ -35,14 +35,14 @@ namespace Components::GSC void ScriptExtension::GScr_AddFieldsForEntityStub() { - for (const auto& [offset, field] : CustomEntityFields) + for (const auto& field : CustomEntityFields | std::views::values) { Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); } Utils::Hook::Call(0x4A7CF0)(); // GScr_AddFieldsForClient - for (const auto& [offset, field] : CustomClientFields) + for (const auto& field : CustomClientFields | std::views::values) { Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); } diff --git a/src/Components/Modules/GSC/String.cpp b/src/Components/Modules/GSC/String.cpp index bb497a69..786fade8 100644 --- a/src/Components/Modules/GSC/String.cpp +++ b/src/Components/Modules/GSC/String.cpp @@ -5,6 +5,8 @@ namespace Components::GSC { + using namespace Utils::String; + void String::AddScriptFunctions() { Script::AddFunction("ToUpper", [] // gsc: ToUpper() @@ -108,7 +110,7 @@ namespace Components::GSC }); // Func present on IW5 - Script::AddFunction("CastFloat", [] // gsc: CastFloat() + Script::AddFunction("Float", [] // gsc: Float() { switch (Game::Scr_GetType(0)) { @@ -122,7 +124,7 @@ namespace Components::GSC Game::Scr_AddFloat(static_cast(Game::Scr_GetInt(0))); break; default: - Game::Scr_ParamError(0, Utils::String::VA("cannot cast %s to float", Game::Scr_GetTypeName(0))); + Game::Scr_ParamError(0, VA("cannot cast %s to float", Game::Scr_GetTypeName(0))); break; } }); @@ -141,6 +143,22 @@ namespace Components::GSC Game::Scr_AddInt(result); }); + + Script::AddFunction("IString", [] // gsc: IString() + { + if (Game::Scr_GetType(0) != Game::VAR_STRING) + { + Game::Scr_ParamError(0, VA("cannot cast %s to istring", Game::Scr_GetTypeName(0))); + return; + } + + const auto value = Game::Scr_GetConstString(0); + + Game::SL_AddRefToString(value); + Game::Scr_AddIString(Game::Scr_GetString(0)); + + Game::SL_RemoveRefToString(value); + }); } String::String() diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 2f00d7de..f2515a65 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -21,7 +21,7 @@ namespace Components {Game::GPAD_UP, Game::K_DPAD_UP}, {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, - {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} + {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT}, }; Game::StickToCodeMap_t Gamepad::analogStickList[4] @@ -712,6 +712,8 @@ namespace Components void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) { + assert(input); + assert(output); AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS); auto& aaGlob = Game::aaGlobArray[input->localClientNum]; @@ -724,13 +726,11 @@ namespace Components Game::AimAssist_UpdateTweakables(input->localClientNum); Game::AimAssist_UpdateAdsLerp(input); AimAssist_ApplyTurnRates(input, output); - - // Automelee has already been done by keyboard so don't do it again - + Game::AimAssist_ApplyAutoMelee(input, output); AimAssist_ApplyLockOn(input, output); - } - aaGlob.prevButtons = input->buttons; + aaGlob.prevButtons = input->buttons; + } } void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) @@ -960,6 +960,10 @@ namespace Components aimInput.forwardAxis = forward; aimInput.rightAxis = side; AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); + + cmd->meleeChargeDist = aimOutput.meleeChargeDist; + cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; + clientActive.clViewangles[0] = aimOutput.pitch; clientActive.clViewangles[1] = aimOutput.yaw; } diff --git a/src/Components/Modules/IPCPipe.cpp b/src/Components/Modules/IPCPipe.cpp index a7e79a40..ea2f2980 100644 --- a/src/Components/Modules/IPCPipe.cpp +++ b/src/Components/Modules/IPCPipe.cpp @@ -226,7 +226,7 @@ namespace Components Command::Add("ipcping", []([[maybe_unused]] Command::Params* params) { Logger::Print("Sending ping to pipe!\n"); - Write("ping", ""); + Write("ping", {}); }); } diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index cdf8d8d8..413203d8 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -1,4 +1,5 @@ #include +#include "ArenaLength.hpp" namespace Components { @@ -330,6 +331,25 @@ namespace Components return pszString; } + const char* Localization::LocalizeMapName(const char* mapName) + { + for (int i = 0; i < *Game::arenaCount; ++i) + { + if (!_stricmp(ArenaLength::NewArenas[i].mapName, mapName)) + { + auto* uiName = &ArenaLength::NewArenas[i].uiName[0]; + if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH + { + return Get(uiName); + } + + return uiName; + } + } + + return mapName; + } + Localization::Localization() { SetCredits(); diff --git a/src/Components/Modules/Localization.hpp b/src/Components/Modules/Localization.hpp index c4c6951e..fa345621 100644 --- a/src/Components/Modules/Localization.hpp +++ b/src/Components/Modules/Localization.hpp @@ -14,6 +14,8 @@ namespace Components static std::optional PrefixOverride; static void ParseOutput(const std::function& callback); + static const char* LocalizeMapName(const char* mapName); + private: static std::recursive_mutex LocalizeMutex; static std::unordered_map LocalizeMap; diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index 8d9cdc99..e5798d5b 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -28,25 +28,24 @@ namespace Components void Logger::MessagePrint(const int channel, const std::string& msg) { - std::string out = msg; - - // Filter out coloured strings for stdout - if (out[0] == '^' && out[1] != '\0') + static const auto shouldPrint = []() -> bool { - out = out.substr(2); - } + return Flags::HasFlag("stdout") || Loader::IsPerformingUnitTests(); + }(); - if (Flags::HasFlag("stdout") || Loader::IsPerformingUnitTests()) + if (shouldPrint) { - printf("%s", out.data()); - fflush(stdout); + std::printf("%s", msg.data()); + std::fflush(stdout); return; } +#ifdef _DEBUG if (!IsConsoleReady()) { - OutputDebugStringA(out.data()); + OutputDebugStringA(msg.data()); } +#endif if (!Game::Sys_IsMainThread()) { @@ -141,7 +140,7 @@ namespace Components void Logger::NetworkLog(const char* data, bool gLog) { - if (data == nullptr) + if (!data) { return; } @@ -163,7 +162,7 @@ namespace Components va_end(ap); const auto time = Game::level->time / 1000; - const auto len = _snprintf_s(string, _TRUNCATE, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, string2); + const auto len = sprintf_s(string, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, string2); if (Game::level->logFile) { @@ -183,25 +182,29 @@ namespace Components jz returnPrint pushad + push [esp + 28h] call PrintMessagePipe add esp, 4h + popad - retn + ret returnPrint: pushad - push 0 - push [esp + 2Ch] + + push 0 // gLog + push [esp + 2Ch] // data call NetworkLog add esp, 8h + popad push esi mov esi, [esp + 0Ch] push 4AA835h - retn + ret } } diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 48ee62c8..04c761ef 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -1,5 +1,6 @@ #include +#include "ArenaLength.hpp" #include "FastFiles.hpp" #include "RawFiles.hpp" #include "StartupMessages.hpp" @@ -92,9 +93,9 @@ namespace Components Game::unzClose(this->searchPath.iwd->handle); - auto _free = Utils::Hook::Call(0x6B5CF2); - _free(this->searchPath.iwd->buildBuffer); - _free(this->searchPath.iwd); + // Use game's free function + Utils::Hook::Call(0x6B5CF2)(this->searchPath.iwd->buildBuffer); + Utils::Hook::Call(0x6B5CF2)(this->searchPath.iwd); ZeroMemory(&this->searchPath, sizeof this->searchPath); } @@ -619,8 +620,8 @@ namespace Components { if (entry.is_directory()) { - auto zoneName = entry.path().filename().string(); - auto mapPath = std::format("{}\\{}.ff", entry.path().string(), zoneName); + const auto zoneName = entry.path().filename().string(); + const auto mapPath = std::format("{}\\{}.ff", entry.path().string(), zoneName); if (Utils::IO::FileExists(mapPath)) { FoundCustomMaps.push_back(zoneName); @@ -659,7 +660,7 @@ namespace Components if (error) { Logger::Error(Game::ERR_DISCONNECT, "Missing DLC pack {} ({}) containing map {} ({}).\nPlease download it to play this map.", - pack.name, pack.index, Game::UI_LocalizeMapName(mapname.data()), mapname); + pack.name, pack.index, Localization::LocalizeMapName(mapname.data()), mapname); } return dlcIsTrue; @@ -758,8 +759,8 @@ namespace Components Maps::AddDlc({ 1, "Stimulus Pack", {"mp_complex", "mp_compact", "mp_storm", "mp_overgrown", "mp_crash"} }); Maps::AddDlc({ 2, "Resurgence Pack", {"mp_abandon", "mp_vacant", "mp_trailerpark", "mp_strike", "mp_fuel2"} }); - Maps::AddDlc({ 3, "IW4x Classics", {"mp_nuked", "mp_cross_fire", "mp_cargoship", "mp_bloc", "mp_killhouse", "mp_bog_sh", "mp_cargoship_sh", "mp_shipment_long", "mp_rust_long", "mp_firingrange", "mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} }); - Maps::AddDlc({ 4, "Call Of Duty 4 Pack", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy"} }); + Maps::AddDlc({ 3, "IW4x Classics", {"mp_nuked", "mp_cross_fire", "mp_cargoship", "mp_bloc", "mp_killhouse", "mp_bog_sh", "mp_cargoship_sh", "mp_shipment", "mp_shipment_long", "mp_rust_long", "mp_firingrange", "mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} }); + Maps::AddDlc({ 4, "Call Of Duty 4 Pack", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy", "mp_citystreets"} }); Maps::AddDlc({ 5, "Modern Warfare 3 Pack", {"mp_dome", "mp_hardhat", "mp_paris", "mp_seatown", "mp_bravo", "mp_underground", "mp_plaza2", "mp_village", "mp_alpha"}}); Maps::UpdateDlcStatus(); diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 6b135803..00c551a2 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -6,13 +6,21 @@ namespace Components // Packet interception std::unordered_map Network::CL_Callbacks; + Network::Address::Address() + { + ZeroMemory(&this->address, sizeof(Game::netadr_t)); + this->setType(Game::NA_BAD); + } + Network::Address::Address(const std::string& addrString) { + ZeroMemory(&this->address, sizeof(Game::netadr_t)); Game::NET_StringToAdr(addrString.data(), &this->address); } Network::Address::Address(sockaddr* addr) { + ZeroMemory(&this->address, sizeof(Game::netadr_t)); Game::SockadrToNetadr(addr, &this->address); } @@ -23,7 +31,7 @@ namespace Components void Network::Address::setPort(unsigned short port) { - this->address.port = htons(port); + this->address.port = ::htons(port); } unsigned short Network::Address::getPort() const @@ -76,7 +84,7 @@ namespace Components this->toSockAddr(reinterpret_cast(addr)); } - Game::netadr_t* Network::Address::get() + const Game::netadr_t* Network::Address::get() const noexcept { return &this->address; } @@ -88,7 +96,7 @@ namespace Components std::string Network::Address::getString() const { - return {this->getCString()}; + return std::string{ this->getCString() }; } bool Network::Address::isLocal() const noexcept @@ -148,7 +156,7 @@ namespace Components StartupSignal.connect(callback); } - void Network::Send(Game::netsrc_t type, Address target, const std::string& data) + void Network::Send(Game::netsrc_t type, const Address& target, const std::string& data) { // Do not use NET_OutOfBandPrint. It only supports non-binary data! @@ -159,12 +167,12 @@ namespace Components SendRaw(type, target, rawData); } - void Network::Send(Address target, const std::string& data) + void Network::Send(const Address& target, const std::string& data) { Send(Game::netsrc_t::NS_CLIENT1, target, data); } - void Network::SendRaw(Game::netsrc_t type, Address target, const std::string& data) + void Network::SendRaw(Game::netsrc_t type, const Address& target, const std::string& data) { if (!target.isValid()) return; @@ -172,12 +180,12 @@ namespace Components Game::Sys_SendPacket(type, data.size(), data.data(), *target.get()); } - void Network::SendRaw(Address target, const std::string& data) + void Network::SendRaw(const Address& target, const std::string& data) { SendRaw(Game::NS_CLIENT1, target, data); } - void Network::SendCommand(Game::netsrc_t type, Address target, const std::string& command, const std::string& data) + void Network::SendCommand(Game::netsrc_t type, const Address& target, const std::string& command, const std::string& data) { // Use space as separator (possible separators are '\n', ' '). // Though, our handler only needs exactly 1 char as separator and doesn't care which char it is. @@ -190,7 +198,7 @@ namespace Components Send(type, target, packet); } - void Network::SendCommand(Address target, const std::string& command, const std::string& data) + void Network::SendCommand(const Address& target, const std::string& command, const std::string& data) { SendCommand(Game::NS_CLIENT1, target, command, data); } @@ -293,8 +301,8 @@ namespace Components const std::string data(reinterpret_cast(message->data) + offset, message->cursize - offset); - auto address_ = Address(address); - handler->second(address_, data); + auto target = Address{ address }; + handler->second(target, data); return true; } diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index 9f62839b..bd55a5cc 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -8,7 +8,7 @@ namespace Components class Address { public: - Address() { setType(Game::NA_BAD); } + Address(); Address(const std::string& addrString); Address(sockaddr* addr); Address(sockaddr addr) : Address(&addr) {} @@ -33,7 +33,7 @@ namespace Components [[nodiscard]] sockaddr getSockAddr(); void toSockAddr(sockaddr* addr); void toSockAddr(sockaddr_in* addr); - Game::netadr_t* get(); + [[nodiscard]] const Game::netadr_t* get() const noexcept; [[nodiscard]] const char* getCString() const; [[nodiscard]] std::string getString() const; @@ -57,16 +57,16 @@ namespace Components static void OnStart(const Utils::Slot& callback); // Send quake-styled binary data - static void Send(Address target, const std::string& data); - static void Send(Game::netsrc_t type, Address target, const std::string& data); + static void Send(const Address& target, const std::string& data); + static void Send(Game::netsrc_t type, const Address& target, const std::string& data); // Allows sending raw data without quake header - static void SendRaw(Address target, const std::string& data); - static void SendRaw(Game::netsrc_t type, Address target, const std::string& data); + static void SendRaw(const Address& target, const std::string& data); + static void SendRaw(Game::netsrc_t type, const Address& target, const std::string& data); // Send quake-style command using binary data - static void SendCommand(Address target, const std::string& command, const std::string& data = ""); - static void SendCommand(Game::netsrc_t type, Address target, const std::string& command, const std::string& data = ""); + static void SendCommand(const Address& target, const std::string& command, const std::string& data = {}); + static void SendCommand(Game::netsrc_t type, const Address& target, const std::string& command, const std::string& data = {}); static void Broadcast(unsigned short port, const std::string& data); static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data); diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp index fabe29bf..d03cc88f 100644 --- a/src/Components/Modules/PlayerName.cpp +++ b/src/Components/Modules/PlayerName.cpp @@ -6,6 +6,26 @@ namespace Components { Dvar::Var PlayerName::sv_allowColoredNames; + bool PlayerName::IsBadChar(int c) + { + if (c == '%') + { + return true; + } + + if (c == '~') + { + return true; + } + + if (c < 32 || c > 126) + { + return true; + } + + return false; + } + void PlayerName::UserInfoCopy(char* buffer, const char* name, const int size) { if (!sv_allowColoredNames.get()) @@ -71,13 +91,8 @@ namespace Components while (i < size - 1 && dest[i] != '\0') { // Check for various illegal characters - - if (dest[i] == '%') - { - return false; - } - - if (std::iscntrl(static_cast(dest[i]))) + const auto c = static_cast(dest[i]); + if (IsBadChar(c)) { return false; } @@ -119,7 +134,7 @@ namespace Components { sv_allowColoredNames = Dvar::Register("sv_allowColoredNames", true, Game::DVAR_NONE, "Allow colored names on the server"); - // Disable SV_UpdateUserinfo_f, to block changing the name ingame + // Disable SV_UpdateUserinfo_f to block changing the name ingame Utils::Hook::Set(0x6258D0, 0xC3); // Allow colored names ingame. Hook placed in ClientUserinfoChanged diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp index fb8641b4..f216e14e 100644 --- a/src/Components/Modules/PlayerName.hpp +++ b/src/Components/Modules/PlayerName.hpp @@ -16,6 +16,8 @@ namespace Components // Message used when kicking players static constexpr auto INVALID_NAME_MSG = "Invalid name detected"; + static bool IsBadChar(int c); + static char* CleanStrStub(char* string); static void ClientCleanName(); diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index 029c42bc..ddab0636 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -143,6 +143,63 @@ namespace Components } } + void RCon::RconExecuter(const Network::Address& address, std::string data) + { + Utils::String::Trim(data); + + const auto pos = data.find_first_of(' '); + if (pos == std::string::npos) + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getString()); + return; + } + + auto password = data.substr(0, pos); + auto command = data.substr(pos + 1); + + // B3 sends the password inside quotes :S + if (!password.empty() && password[0] == '"' && password.back() == '"') + { + password.pop_back(); + password.erase(password.begin()); + } + + const auto svPassword = RconPassword.get(); + if (svPassword.empty()) + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString()); + return; + } + + if (svPassword != password) + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString()); + return; + } + + static std::string outputBuffer; + outputBuffer.clear(); + +#ifndef _DEBUG + if (RconLogRequests.get()) +#endif + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command); + } + + Logger::PipeOutput([](const std::string& output) + { + outputBuffer.append(output); + }); + + Command::Execute(command, true); + + Logger::PipeOutput(nullptr); + + Network::SendCommand(address, "print", outputBuffer); + outputBuffer.clear(); + } + RCon::RCon() { Events::OnSVInit(AddCommands); @@ -213,61 +270,11 @@ namespace Components RateLimitCleanup(time); - std::string data_ = data; - Utils::String::Trim(data_); - - const auto pos = data.find_first_of(' '); - if (pos == std::string::npos) + auto rconData = data; + Scheduler::Once([address, s = std::move(rconData)] { - Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getString()); - return; - } - - auto password = data.substr(0, pos); - auto command = data.substr(pos + 1); - - // B3 sends the password inside quotes :S - if (!password.empty() && password[0] == '"' && password.back() == '"') - { - password.pop_back(); - password.erase(password.begin()); - } - - const auto svPassword = RconPassword.get(); - - if (svPassword.empty()) - { - Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString()); - return; - } - - if (svPassword != password) - { - Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString()); - return; - } - - static std::string outputBuffer; - outputBuffer.clear(); - -#ifndef DEBUG - if (RconLogRequests.get()) -#endif - { - Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command); - } - - Logger::PipeOutput([](const std::string& output) - { - outputBuffer.append(output); - }); - - Command::Execute(command, true); - - Logger::PipeOutput(nullptr); - - Network::SendCommand(address, "print", outputBuffer); - outputBuffer.clear(); + RconExecuter(address, s); + }, Scheduler::Pipeline::MAIN); }); Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data) diff --git a/src/Components/Modules/RCon.hpp b/src/Components/Modules/RCon.hpp index 907592c6..eef8d404 100644 --- a/src/Components/Modules/RCon.hpp +++ b/src/Components/Modules/RCon.hpp @@ -47,5 +47,7 @@ namespace Components static bool IsRateLimitCheckDisabled(); static bool RateLimitCheck(const Network::Address& address, int time); static void RateLimitCleanup(int time); + + static void RconExecuter(const Network::Address& address, std::string data); }; } diff --git a/src/Components/Modules/RawFiles.cpp b/src/Components/Modules/RawFiles.cpp index a9f235e6..d914d38d 100644 --- a/src/Components/Modules/RawFiles.cpp +++ b/src/Components/Modules/RawFiles.cpp @@ -142,7 +142,7 @@ namespace Components const char* data = Game::Scr_AddSourceBuffer(nullptr, file.getName().data(), nullptr, false); - if (data != nullptr) + if (data) { Utils::IO::WriteFile("raw/" + file.getName(), data); Logger::Print("File '{}' written to raw!\n", file.getName()); diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 0172f4a5..7dd1ede3 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -64,7 +64,7 @@ namespace Components Dvar::Var("uiSi_KillCam").set("@MENU_NO"); Dvar::Var("uiSi_ffType").set("@MENU_DISABLED"); Dvar::Var("uiSi_MapName").set(serverInfo->mapname); - Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(serverInfo->mapname.data())); + Dvar::Var("uiSi_MapNameLoc").set(Localization::LocalizeMapName(serverInfo->mapname.data())); Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(serverInfo->gametype.data())); Dvar::Var("uiSi_ModName").set(""); Dvar::Var("uiSi_aimAssist").set(serverInfo->aimassist ? "@MENU_YES" : "@MENU_NO"); @@ -220,7 +220,7 @@ namespace Components const auto* client = Game::svs_clients[i].gentity->client; const auto team = client->sess.cs.team; - if (team == Game::TEAM_SPECTATOR) + if (Game::svs_clients[i].bIsTestClient || team == Game::TEAM_SPECTATOR) { continue; } @@ -233,7 +233,7 @@ namespace Components { // Score and ping are irrelevant const auto* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); - if (!namePtr || !namePtr[0]) continue; + if (!namePtr || !*namePtr) continue; name = namePtr; } @@ -261,7 +261,7 @@ namespace Components Dvar::Var("uiSi_Hardcore").set(info.get("g_hardcore") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED"); Dvar::Var("uiSi_KillCam").set(info.get("scr_game_allowkillcam") == "0" ? "@MENU_NO" : "@MENU_YES"); Dvar::Var("uiSi_MapName").set(info.get("mapname")); - Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info.get("mapname").data())); + Dvar::Var("uiSi_MapNameLoc").set(Localization::LocalizeMapName(info.get("mapname").data())); Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info.get("g_gametype").data())); Dvar::Var("uiSi_ModName").set(""); Dvar::Var("uiSi_aimAssist").set(info.get("aimAssist") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED"); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index ba51b830..2900c1ba 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -118,13 +118,13 @@ namespace Components { if (!sorting && !Maps::CheckMapInstalled(server->mapname)) { - return Utils::String::VA("^1%s", Game::UI_LocalizeMapName(server->mapname.data())); + return Utils::String::VA("^1%s", Localization::LocalizeMapName(server->mapname.data())); } - return Game::UI_LocalizeMapName(server->mapname.data()); + return Localization::LocalizeMapName(server->mapname.data()); } - return Utils::String::VA("^3%s", Game::UI_LocalizeMapName(server->mapname.data())); + return Utils::String::VA("^3%s", Localization::LocalizeMapName(server->mapname.data())); } case Column::Players: diff --git a/src/Components/Modules/Theatre.cpp b/src/Components/Modules/Theatre.cpp index 4d89eb19..e8250bfb 100644 --- a/src/Components/Modules/Theatre.cpp +++ b/src/Components/Modules/Theatre.cpp @@ -311,7 +311,7 @@ namespace Components if (item < Demos.size()) { auto info = Demos.at(item); - return Utils::String::VA("%s on %s", Game::UI_LocalizeGameType(info.gametype.data()), Game::UI_LocalizeMapName(info.mapname.data())); + return Utils::String::VA("%s on %s", Game::UI_LocalizeGameType(info.gametype.data()), Localization::LocalizeMapName(info.mapname.data())); } return ""; @@ -330,7 +330,7 @@ namespace Components asctime_s(buffer, sizeof buffer, &time); Dvar::Var("ui_demo_mapname").set(info.mapname); - Dvar::Var("ui_demo_mapname_localized").set(Game::UI_LocalizeMapName(info.mapname.data())); + Dvar::Var("ui_demo_mapname_localized").set(Localization::LocalizeMapName(info.mapname.data())); Dvar::Var("ui_demo_gametype").set(Game::UI_LocalizeGameType(info.gametype.data())); Dvar::Var("ui_demo_length").set(Utils::String::FormatTimeSpan(info.length)); Dvar::Var("ui_demo_author").set(info.author); diff --git a/src/Components/Modules/UIFeeder.cpp b/src/Components/Modules/UIFeeder.cpp index 526f745a..e7d43c38 100644 --- a/src/Components/Modules/UIFeeder.cpp +++ b/src/Components/Modules/UIFeeder.cpp @@ -290,7 +290,7 @@ namespace Components return maps.at(index).data(); } -#ifdef DEBUG +#ifdef _DEBUG if (IsDebuggerPresent()) { __debugbreak(); diff --git a/src/Components/Modules/Voice.cpp b/src/Components/Modules/Voice.cpp index 296ff4a8..c14b0c34 100644 --- a/src/Components/Modules/Voice.cpp +++ b/src/Components/Modules/Voice.cpp @@ -41,14 +41,14 @@ namespace Components Game::msg_t msg{}; const auto clientNum = client - Game::svs_clients; - const auto msg_buf_large = std::make_unique(0x10000); + const auto msg_buf_large = std::make_unique(0x20000); auto* msg_buf = msg_buf_large.get(); assert(VoicePacketCount[clientNum] >= 0); if (client->header.state == Game::CS_ACTIVE && VoicePacketCount[clientNum]) { - Game::MSG_Init(&msg, msg_buf, 0x10000); + Game::MSG_Init(&msg, msg_buf, 0x20000); assert(msg.cursize == 0); assert(msg.bit == 0); diff --git a/src/Game/Dvars.cpp b/src/Game/Dvars.cpp index cae637d4..2a5b18a8 100644 --- a/src/Game/Dvars.cpp +++ b/src/Game/Dvars.cpp @@ -97,7 +97,7 @@ namespace Game const dvar_t** ip = reinterpret_cast(0x64A1DF8); const dvar_t** port = reinterpret_cast(0x64A3004); - __declspec(naked) void Dvar_SetVariant(dvar_t*, DvarValue, DvarSetSource) + __declspec(naked) void Dvar_SetVariant(dvar_t* /*dvar*/, DvarValue /*value*/, DvarSetSource /*source*/) { static DWORD Dvar_SetVariant_t = 0x647400; diff --git a/src/Game/Dvars.hpp b/src/Game/Dvars.hpp index 02cde645..906b83e6 100644 --- a/src/Game/Dvars.hpp +++ b/src/Game/Dvars.hpp @@ -149,6 +149,6 @@ namespace Game extern const dvar_t** ip; extern const dvar_t** port; - extern void Dvar_SetVariant(dvar_t* var, DvarValue value, DvarSetSource source); + extern void Dvar_SetVariant(dvar_t* dvar, DvarValue value, DvarSetSource source); extern void Dvar_SetFromStringFromSource(const dvar_t* dvar, const char* string, DvarSetSource source); } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 5cc612ed..9dd91cea 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -103,6 +103,7 @@ namespace Game MSG_ReadDeltaUsercmdKey_t MSG_ReadDeltaUsercmdKey = MSG_ReadDeltaUsercmdKey_t(0x491F00); MSG_ReadBitsCompress_t MSG_ReadBitsCompress = MSG_ReadBitsCompress_t(0x4DCC30); MSG_WriteBitsCompress_t MSG_WriteBitsCompress = MSG_WriteBitsCompress_t(0x4319D0); + Huff_offsetReceive_t Huff_offsetReceive = Huff_offsetReceive_t(0x466060); NetadrToSockadr_t NetadrToSockadr = NetadrToSockadr_t(0x4B4B40); @@ -389,6 +390,8 @@ namespace Game bool* s_havePlaylists = reinterpret_cast(0x1AD3680); + huffman_t* msgHuff = reinterpret_cast(0x1CB9EC0); + const char* TableLookup(StringTable* stringtable, int row, int column) { if (!stringtable || !stringtable->values || row >= stringtable->rowCount || column >= stringtable->columnCount) return ""; @@ -399,25 +402,6 @@ namespace Game return value; } - const char* UI_LocalizeMapName(const char* mapName) - { - for (int i = 0; i < *arenaCount; ++i) - { - if (!_stricmp(Components::ArenaLength::NewArenas[i].mapName, mapName)) - { - char* uiName = &Components::ArenaLength::NewArenas[i].uiName[0]; - if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH - { - return SEH_StringEd_GetString(uiName); - } - - return uiName; - } - } - - return mapName; - } - const char* UI_LocalizeGameType(const char* gameType) { if (!gameType || !*gameType) diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 53f65385..c7c9635c 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -263,6 +263,9 @@ namespace Game typedef int(*MSG_WriteBitsCompress_t)(bool trainHuffman, const unsigned char* from, unsigned char* to, int size); extern MSG_WriteBitsCompress_t MSG_WriteBitsCompress; + typedef void(*Huff_offsetReceive_t)(nodetype* node, int* ch, const unsigned char* fin, int* offset); + extern Huff_offsetReceive_t Huff_offsetReceive; + typedef void(*NetadrToSockadr_t)(netadr_t *a, sockaddr *s); extern NetadrToSockadr_t NetadrToSockadr; @@ -727,6 +730,8 @@ namespace Game extern bool* s_havePlaylists; + extern huffman_t* msgHuff; + constexpr auto MAX_MSGLEN = 0x20000; ScreenPlacement* ScrPlace_GetFullPlacement(); @@ -737,7 +742,6 @@ namespace Game void Menu_SetNextCursorItem(UiContext* ctx, menuDef_t* currentMenu, int unk = 1); void Menu_SetPrevCursorItem(UiContext* ctx, menuDef_t* currentMenu, int unk = 1); const char* TableLookup(StringTable* stringtable, int row, int column); - const char* UI_LocalizeMapName(const char* mapName); const char* UI_LocalizeGameType(const char* gameType); float UI_GetScoreboardLeft(void*); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index c42eb789..d252e9ce 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -11011,6 +11011,31 @@ namespace Game snd_alias_list_t* aliasList; }; + struct nodetype + { + nodetype* left; + nodetype* right; + nodetype* parent; + int weight; + int symbol; + }; + + struct huff_t + { + int blocNode; + int blocPtrs; + nodetype* tree; + nodetype* loc[257]; + nodetype** freelist; + nodetype nodeList[768]; + nodetype* nodePtrs[768]; + }; + + struct huffman_t + { + huff_t compressDecompress; + }; + #pragma endregion #ifndef IDA diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 7bd49baa..4009d601 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index f14ecd31..365b71ef 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -7,8 +7,8 @@ namespace Utils::String { const char* VA(const char* fmt, ...) { - static VAProvider<4, 100> globalProvider; - static thread_local VAProvider<8, 256> provider; + static VAProvider<4, 256> globalProvider; + static thread_local VAProvider<8, 1024> provider; va_list ap; va_start(ap, fmt); @@ -24,7 +24,7 @@ namespace Utils::String std::string ToLower(const std::string& text) { std::string result; - std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) + std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) -> char { return static_cast(std::tolower(input)); }); @@ -35,7 +35,7 @@ namespace Utils::String std::string ToUpper(const std::string& text) { std::string result; - std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) + std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) -> char { return static_cast(std::toupper(input)); }); @@ -45,7 +45,7 @@ namespace Utils::String bool Compare(const std::string& lhs, const std::string& rhs) { - return std::ranges::equal(lhs, rhs, [](const unsigned char a, const unsigned char b) + return std::ranges::equal(lhs, rhs, [](const unsigned char a, const unsigned char b) -> bool { return std::tolower(a) == std::tolower(b); }); diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 88856d75..826ffbd2 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -11,69 +11,68 @@ namespace Utils::String public: static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); - VAProvider() : currentBuffer(0) {} - ~VAProvider() = default; + VAProvider() : currentBuffer_(0) {} [[nodiscard]] const char* get(const char* format, va_list ap) { - ++this->currentBuffer %= ARRAY_COUNT(this->stringPool); - auto entry = &this->stringPool[this->currentBuffer]; + ++this->currentBuffer_ %= ARRAY_COUNT(this->stringPool_); + auto entry = &this->stringPool_[this->currentBuffer_]; - if (!entry->size || !entry->buffer) + if (!entry->size_ || !entry->buffer_) { throw std::runtime_error("String pool not initialized"); } while (true) { - const auto res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + const auto res = vsnprintf_s(entry->buffer_, entry->size_, _TRUNCATE, format, ap); if (res > 0) break; // Success if (res == 0) return ""; // Error entry->doubleSize(); } - return entry->buffer; + return entry->buffer_; } private: class Entry { public: - Entry(std::size_t _size = MinBufferSize) : size(_size), buffer(nullptr) + Entry(std::size_t size = MinBufferSize) : size_(size), buffer_(nullptr) { - if (this->size < MinBufferSize) this->size = MinBufferSize; + if (this->size_ < MinBufferSize) this->size_ = MinBufferSize; this->allocate(); } ~Entry() { - if (this->buffer) Memory::GetAllocator()->free(this->buffer); - this->size = 0; - this->buffer = nullptr; + if (this->buffer_) Memory::GetAllocator()->free(this->buffer_); + this->size_ = 0; + this->buffer_ = nullptr; } void allocate() { - if (this->buffer) Memory::GetAllocator()->free(this->buffer); - this->buffer = Memory::GetAllocator()->allocateArray(this->size + 1); + if (this->buffer_) Memory::GetAllocator()->free(this->buffer_); + this->buffer_ = Memory::GetAllocator()->allocateArray(this->size_ + 1); } void doubleSize() { - this->size *= 2; + this->size_ *= 2; this->allocate(); } - std::size_t size; - char* buffer; + std::size_t size_; + char* buffer_; }; - std::size_t currentBuffer; - Entry stringPool[Buffers]; + std::size_t currentBuffer_; + Entry stringPool_[Buffers]; }; - template // This should display a nice "null" instead of a number + template // This should display a nice "nullptr" instead of a number static void SanitizeFormatArgs(Arg& arg) { if constexpr (std::is_same_v || std::is_same_v)