diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 1c4b90e3..9609dc02 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -18,7 +18,7 @@ jobs: id: normalize_version run: | version="${{ github.event.inputs.version }}" - version="v${version#v}" + version="r${version#v}" echo "::set-output name=version::$version" # Set up committer info and GPG key diff --git a/CHANGELOG.md b/CHANGELOG.md index c35b7fb2..6944b407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,36 @@ 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/). +## r4190 - 2023-04-19 + +### Added + +- Add `LogString` GSC function (#895) +- Add `LogString` GSC method (#895) +- Add `sv_replaceTestClients` Dvar (#930) + +### Changed + +- `sv_mapRotationCurrent` supports `exec` directive for executing cfg scripts from the `game_settings` folder + +### Fixed + +- `sv_privatePassword` will work as intended (#908) + +### Known issues + +- Sound issue fix is experimental as the bug is not fully understood. + ## [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) @@ -20,6 +42,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - `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) @@ -32,6 +55,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. ## [0.7.8] - 2023-03-17 ### Added + - Clients can unprotect "saved" Dvars using the command line argument `-unprotect-dvars` (#694) - Clients can protect all Dvars from being modified by the server using the command line argument `-protect-dvars` (#823) - Add helpful information to script-related errors (#721) @@ -54,6 +78,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - Add new map porting utility tool that makes the map porting process between CoD8 to CoD6 easy ### Changed + - 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) @@ -61,6 +86,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - 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) - Fix bug where demo playback would stop when opening a laptop based killstreak (#699) - Fix bug where mod download speed was inexplicably slow (#707) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index cf45b460..1bc514d4 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit cf45b460fe32a8c858b30445df779e29821cfdee +Subproject commit 1bc514d4c981532d1195b420a8fc0ce809c65944 diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 2a1b2846..fae62af0 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447 +Subproject commit fae62af0ab16f469c2512ec04575dd60ca018657 diff --git a/deps/libtommath b/deps/libtommath index 03de03de..0df542cb 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 03de03dee753442d4b23166982514639c4ccbc39 +Subproject commit 0df542cb70f621bbeec207be1949832fb1442479 diff --git a/deps/premake/rapidjson.lua b/deps/premake/rapidjson.lua index d1085120..0dc3b4b9 100644 --- a/deps/premake/rapidjson.lua +++ b/deps/premake/rapidjson.lua @@ -3,6 +3,10 @@ rapidjson = { } function rapidjson.import() + defines { + "RAPIDJSON_HAS_STDSTRING" + } + rapidjson.includes() end diff --git a/deps/rapidjson b/deps/rapidjson index 012be852..949c771b 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24 +Subproject commit 949c771b03de448bdedea80c44a4a5f65284bfeb diff --git a/deps/zlib b/deps/zlib index eb0e038b..b8a8373e 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485 +Subproject commit b8a8373ec195c8d286fe7e81e78b4a6d31bd859f diff --git a/premake5.lua b/premake5.lua index a311c929..f81f657a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -158,15 +158,11 @@ newaction { versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n") versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n") versionHeader:write("\n") - versionHeader:write("// Legacy definitions (needed for update check)\n") + versionHeader:write("// New revision definition. Will be used from now on\n") versionHeader:write("#define REVISION " .. revNumber .. "\n") versionHeader:write("\n") - versionHeader:write("// Version transformed for RC files\n") - versionHeader:write("#define VERSION_RC " .. table.concat(vertonumarr(tagName, revNumber), ",") .. "\n") - versionHeader:write("\n") versionHeader:write("// Alias definitions\n") versionHeader:write("#define VERSION GIT_DESCRIBE\n") - versionHeader:write("#define SHORTVERSION " .. cstrquote(table.concat(vertonumarr(tagName, revNumber), ".")) .. "\n") versionHeader:close() local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w")) versionHeader:write("/*\n") diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index de0f2420..cce1e935 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -2,6 +2,7 @@ #include #include "Modules/ArenaLength.hpp" +#include "Modules/Auth.hpp" #include "Modules/Bans.hpp" #include "Modules/Bots.hpp" #include "Modules/Branding.hpp" @@ -20,13 +21,20 @@ #include "Modules/Discovery.hpp" #include "Modules/Download.hpp" #include "Modules/Elevators.hpp" +#include "Modules/Exception.hpp" #include "Modules/FastFiles.hpp" +#include "Modules/Friends.hpp" #include "Modules/Gamepad.hpp" +#include "Modules/IPCPipe.hpp" #include "Modules/Lean.hpp" #include "Modules/MapDump.hpp" #include "Modules/MapRotation.hpp" +#include "Modules/Materials.hpp" +#include "Modules/ModList.hpp" +#include "Modules/ModelSurfs.hpp" #include "Modules/NetworkDebug.hpp" #include "Modules/News.hpp" +#include "Modules/Node.hpp" #include "Modules/Party.hpp" #include "Modules/PlayerMovement.hpp" #include "Modules/PlayerName.hpp" @@ -46,13 +54,16 @@ #include "Modules/Stats.hpp" #include "Modules/StringTable.hpp" #include "Modules/StructuredData.hpp" +#include "Modules/TextRenderer.hpp" #include "Modules/Theatre.hpp" #include "Modules/Threading.hpp" +#include "Modules/Toast.hpp" #include "Modules/UIFeeder.hpp" #include "Modules/VisionFile.hpp" #include "Modules/Voice.hpp" #include "Modules/Vote.hpp" #include "Modules/Weapon.hpp" +#include "Modules/Window.hpp" #include "Modules/BotLib/lPrecomp.hpp" @@ -97,7 +108,7 @@ namespace Components Register(new Logger()); Register(new UIScript()); Register(new ZoneBuilder()); - + Register(new ArenaLength()); Register(new AssetHandler()); Register(new Bans()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index dee129b3..d4282cdc 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -62,10 +62,8 @@ namespace Components } // Priority -#include "Modules/Auth.hpp" #include "Modules/Command.hpp" #include "Modules/Dvar.hpp" -#include "Modules/Exception.hpp" #include "Modules/Flags.hpp" #include "Modules/Network.hpp" #include "Modules/Logger.hpp" @@ -77,20 +75,11 @@ namespace Components #include "Modules/Dedicated.hpp" #include "Modules/Events.hpp" #include "Modules/FileSystem.hpp" -#include "Modules/Friends.hpp" -#include "Modules/IPCPipe.hpp" #include "Modules/Localization.hpp" #include "Modules/Maps.hpp" -#include "Modules/Materials.hpp" #include "Modules/Menus.hpp" -#include "Modules/ModList.hpp" -#include "Modules/ModelSurfs.hpp" -#include "Modules/Node.hpp" #include "Modules/Renderer.hpp" #include "Modules/Scheduler.hpp" -#include "Modules/TextRenderer.hpp" -#include "Modules/Toast.hpp" -#include "Modules/Window.hpp" #include "Modules/Zones.hpp" #include "Modules/GSC/GSC.hpp" diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index 3dc576ed..af3dbb3c 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -3,7 +3,11 @@ #include +#include "Auth.hpp" #include "Bans.hpp" +#include "Bots.hpp" +#include "Friends.hpp" +#include "Toast.hpp" namespace Components { @@ -19,10 +23,12 @@ namespace Components 0xf7e33c4081337fa3, 0x6f5597f103cc50e9 }; + + bool Auth::HasAccessToReservedSlot; void Auth::Frame() { - if (Auth::TokenContainer.generating) + if (TokenContainer.generating) { static double mseconds = 0; static Utils::Time::Interval interval; @@ -31,54 +37,54 @@ namespace Components { interval.update(); - int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime; - double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff; - double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes; + int diff = Game::Sys_Milliseconds() - TokenContainer.startTime; + double hashPMS = (TokenContainer.hashes * 1.0) / diff; + double requiredHashes = std::pow(2, TokenContainer.targetLevel + 1) - TokenContainer.hashes; mseconds = requiredHashes / hashPMS; if (mseconds < 0) mseconds = 0; } - Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast(mseconds)).data())); + Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)",GetSecurityLevel(), TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast(mseconds)).data())); } - else if (Auth::TokenContainer.thread.joinable()) + else if (TokenContainer.thread.joinable()) { - Auth::TokenContainer.thread.join(); - Auth::TokenContainer.generating = false; + TokenContainer.thread.join(); + TokenContainer.generating = false; - Auth::StoreKey(); - Logger::Debug("Security level is {}", Auth::GetSecurityLevel()); + StoreKey(); + Logger::Debug("Security level is {}",GetSecurityLevel()); Command::Execute("closemenu security_increase_popmenu", false); - if (!Auth::TokenContainer.cancel) + if (!TokenContainer.cancel) { - if (Auth::TokenContainer.command.empty()) + if (TokenContainer.command.empty()) { - Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), "Success"); + Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", GetSecurityLevel()), "Success"); } else { - Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), 5000); - Command::Execute(Auth::TokenContainer.command, false); + Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", GetSecurityLevel()), 5000); + Command::Execute(TokenContainer.command, false); } } - Auth::TokenContainer.cancel = false; + TokenContainer.cancel = false; } } - void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len) + void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len) { // Ensure our certificate is loaded Steam::SteamUser()->GetSteamID(); - if (!Auth::GuidKey.isValid()) + if (!GuidKey.isValid()) { Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!"); return; } - if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != Auth::BannedUids.end()) + if (std::find(BannedUids.begin(), BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != BannedUids.end()) { - Auth::GenerateKey(); + GenerateKey(); Logger::Error(Game::ERR_SERVERDISCONNECT, "Your online profile is invalid. A new key has been generated."); return; } @@ -121,9 +127,9 @@ namespace Components Game::SV_Cmd_EndTokenizedString(); Proto::Auth::Connect connectData; - connectData.set_token(Auth::GuidToken.toString()); - connectData.set_publickey(Auth::GuidKey.getPublicKey()); - connectData.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, challenge)); + connectData.set_token(GuidToken.toString()); + connectData.set_publickey(GuidKey.getPublicKey()); + connectData.set_signature(Utils::Cryptography::ECC::SignMessage(GuidKey, challenge)); connectData.set_infostring(connectString); Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString()); @@ -182,11 +188,11 @@ namespace Components } // Parse the infostring - Utils::InfoString infostr(params[2]); + Utils::InfoString infostr(params.get(2)); // Read the required data - const auto& steamId = infostr.get("xuid"); - const auto& challenge = infostr.get("challenge"); + const auto steamId = infostr.get("xuid"); + const auto challenge = infostr.get("challenge"); if (steamId.empty() || challenge.empty()) { @@ -206,13 +212,13 @@ namespace Components return; } - if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), xuid) != Auth::BannedUids.end()) + if (std::find(BannedUids.begin(), BannedUids.end(), xuid) != BannedUids.end()) { Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7."); return; } - if (xuid != Auth::GetKeyHash(connectData.publickey())) + if (xuid != GetKeyHash(connectData.publickey())) { Network::Send(address, "error\nXUID doesn't match the certificate!"); return; @@ -230,7 +236,7 @@ namespace Components // Verify the security level auto ourLevel = Dvar::Var("sv_securityLevel").get(); - auto userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey()); + auto userLevel = GetZeroBits(connectData.token(), connectData.publickey()); if (userLevel < ourLevel) { @@ -252,7 +258,7 @@ namespace Components lea eax, [esp + 20h] push eax push esi - call Auth::ParseConnectData + call ParseConnectData pop esi pop eax popad @@ -262,6 +268,47 @@ namespace Components } } + char* Auth::Info_ValueForKeyStub(const char* s, const char* key) + { + auto* value = Game::Info_ValueForKey(s, key); + + HasAccessToReservedSlot = std::strcmp((*Game::sv_privatePassword)->current.string, value) == 0; + + // This stub runs right before the 'server is full check' so we can call this here + Bots::SV_DirectConnect_Full_Check(); + + return value; + } + + __declspec(naked) void Auth::DirectConnectPrivateClientStub() + { + __asm + { + push eax + + mov al, HasAccessToReservedSlot + test al, al + + pop eax + + je noAccess + + // Set the number of private clients to 0 if the client has the right password + xor eax, eax + jmp safeContinue + + noAccess: + mov eax, dword ptr [edx + 0x10] + + safeContinue: + // Game code skipped by hook + add esp, 0xC + + push 0x460FB3 + ret + } + } + unsigned __int64 Auth::GetKeyHash(const std::string& key) { std::string hash = Utils::Cryptography::SHA1::Compute(key); @@ -276,18 +323,18 @@ namespace Components unsigned __int64 Auth::GetKeyHash() { - Auth::LoadKey(); - return Auth::GetKeyHash(Auth::GuidKey.getPublicKey()); + LoadKey(); + return GetKeyHash(GuidKey.getPublicKey()); } void Auth::StoreKey() { - if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && Auth::GuidKey.isValid()) + if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && GuidKey.isValid()) { Proto::Auth::Certificate cert; - cert.set_token(Auth::GuidToken.toString()); - cert.set_ctoken(Auth::ComputeToken.toString()); - cert.set_privatekey(Auth::GuidKey.serialize(PK_PRIVATE)); + cert.set_token(GuidToken.toString()); + cert.set_ctoken(ComputeToken.toString()); + cert.set_privatekey(GuidKey.serialize(PK_PRIVATE)); Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString()); } @@ -295,30 +342,30 @@ namespace Components void Auth::GenerateKey() { - Auth::GuidToken.clear(); - Auth::ComputeToken.clear(); - Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512); - Auth::StoreKey(); + GuidToken.clear(); + ComputeToken.clear(); + GuidKey = Utils::Cryptography::ECC::GenerateKey(512); + StoreKey(); } void Auth::LoadKey(bool force) { if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; - if (!force && Auth::GuidKey.isValid()) return; + if (!force && GuidKey.isValid()) return; Proto::Auth::Certificate cert; if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat"))) { - Auth::GuidKey.deserialize(cert.privatekey()); - Auth::GuidToken = cert.token(); - Auth::ComputeToken = cert.ctoken(); + GuidKey.deserialize(cert.privatekey()); + GuidToken = cert.token(); + ComputeToken = cert.ctoken(); } else { - Auth::GuidKey.free(); + GuidKey.free(); } - if (!Auth::GuidKey.isValid()) + if (!GuidKey.isValid()) { Auth::GenerateKey(); } @@ -326,32 +373,32 @@ namespace Components uint32_t Auth::GetSecurityLevel() { - return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); + return GetZeroBits(GuidToken, GuidKey.getPublicKey()); } void Auth::IncreaseSecurityLevel(uint32_t level, const std::string& command) { - if (Auth::GetSecurityLevel() >= level) return; + if (GetSecurityLevel() >= level) return; - if (!Auth::TokenContainer.generating) + if (!TokenContainer.generating) { - Auth::TokenContainer.cancel = false; - Auth::TokenContainer.targetLevel = level; - Auth::TokenContainer.command = command; + TokenContainer.cancel = false; + TokenContainer.targetLevel = level; + TokenContainer.command = command; // Open menu Command::Execute("openmenu security_increase_popmenu", true); // Start thread - Auth::TokenContainer.thread = std::thread([&level]() + TokenContainer.thread = std::thread([&level]() { - Auth::TokenContainer.generating = true; - Auth::TokenContainer.hashes = 0; - Auth::TokenContainer.startTime = Game::Sys_Milliseconds(); - Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.getPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes); - Auth::TokenContainer.generating = false; + TokenContainer.generating = true; + TokenContainer.hashes = 0; + TokenContainer.startTime = Game::Sys_Milliseconds(); + IncrementToken(GuidToken, ComputeToken, GuidKey.getPublicKey(), TokenContainer.targetLevel, &TokenContainer.cancel, &TokenContainer.hashes); + TokenContainer.generating = false; - if (Auth::TokenContainer.cancel) + if (TokenContainer.cancel) { Logger::Print("Token incrementation thread terminated\n"); } @@ -399,7 +446,7 @@ namespace Components } // Check if we already have the desired security level - uint32_t lastLevel = Auth::GetZeroBits(token, publicKey); + uint32_t lastLevel = GetZeroBits(token, publicKey); uint32_t level = lastLevel; if (level >= zeroBits) return; @@ -407,7 +454,7 @@ namespace Components { ++computeToken; if (count) ++(*count); - level = Auth::GetZeroBits(computeToken, publicKey); + level = GetZeroBits(computeToken, publicKey); // Store level if higher than the last one if (level >= lastLevel) @@ -425,30 +472,36 @@ namespace Components Auth::Auth() { - Auth::TokenContainer.cancel = false; - Auth::TokenContainer.generating = false; + TokenContainer.cancel = false; + TokenContainer.generating = false; + + HasAccessToReservedSlot = false; Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", ""); // Load the key - Auth::LoadKey(true); + LoadKey(true); Steam::SteamUser()->GetSteamID(); - Scheduler::Loop(Auth::Frame, Scheduler::Pipeline::MAIN); + Scheduler::Loop(Frame, Scheduler::Pipeline::MAIN); // Register dvar Dvar::Register("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)"); // Install registration hook - Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x41D3E3, Auth::SendConnectDataStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x6265F9, DirectConnectStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x460EF5, Info_ValueForKeyStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x460FAD, DirectConnectPrivateClientStub, HOOK_JUMP).install()->quick(); + Utils::Hook::Nop(0x460FAD + 5, 1); + + Utils::Hook(0x41D3E3, SendConnectDataStub, HOOK_CALL).install()->quick(); // SteamIDs can only contain 31 bits of actual 'id' data. // The other 33 bits are steam internal data like universe and so on. // Using only 31 bits for fingerprints is pretty insecure. // The function below verifies the integrity steam's part of the SteamID. // Patching that check allows us to use 64 bit for fingerprints. - Utils::Hook::Set(0x4D0D60, 0xC301B0); + Utils::Hook::Set(0x4D0D60, 0xC301B0); // Guid command Command::Add("guid", [] @@ -462,42 +515,42 @@ namespace Components { if (params->size() < 2) { - const auto level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); + const auto level = GetZeroBits(GuidToken, GuidKey.getPublicKey()); Logger::Print("Your current security level is {}\n", level); - Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(Auth::GuidToken.toString(), "")); - Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(Auth::ComputeToken.toString(), "")); + Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(GuidToken.toString(), "")); + Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(ComputeToken.toString(), "")); Toast::Show("cardicon_locked", "^5Security Level", Utils::String::VA("Your security level is %d", level), 3000); } else { const auto level = std::strtoul(params->get(1), nullptr, 10); - Auth::IncreaseSecurityLevel(level); + IncreaseSecurityLevel(level); } }); } UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - Auth::TokenContainer.cancel = true; + TokenContainer.cancel = true; Logger::Print("Token incrementation process canceled!\n"); }); } Auth::~Auth() { - Auth::StoreKey(); + StoreKey(); } void Auth::preDestroy() { - Auth::TokenContainer.cancel = true; - Auth::TokenContainer.generating = false; + TokenContainer.cancel = true; + TokenContainer.generating = false; // Terminate thread - if (Auth::TokenContainer.thread.joinable()) + if (TokenContainer.thread.joinable()) { - Auth::TokenContainer.thread.join(); + TokenContainer.thread.join(); } } diff --git a/src/Components/Modules/Auth.hpp b/src/Components/Modules/Auth.hpp index 48e821f7..5ab438ce 100644 --- a/src/Components/Modules/Auth.hpp +++ b/src/Components/Modules/Auth.hpp @@ -44,10 +44,14 @@ namespace Components static Utils::Cryptography::Token ComputeToken; static Utils::Cryptography::ECC::Key GuidKey; static std::vector BannedUids; + + static bool HasAccessToReservedSlot; - static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len); + static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len); static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr); static void DirectConnectStub(); + static char* Info_ValueForKeyStub(const char* s, const char* key); + static void DirectConnectPrivateClientStub(); static void Frame(); }; diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index 0d259589..b7702656 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -266,7 +266,7 @@ namespace Components return; } - const auto* cl = &Game::svs_clients[clientNum]; + auto* cl = &Game::svs_clients[clientNum]; if (cl->header.state < Game::CS_ACTIVE) { Logger::Print("Client {} is not active\n", clientNum); @@ -279,7 +279,7 @@ namespace Components } const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2); - BanClient(&Game::svs_clients[clientNum], reason); + BanClient(cl, reason); }); Command::Add("unbanClient", [](Command::Params* params) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 337f4708..a598c4ef 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,18 +1,22 @@ #include #include "Bots.hpp" +#include "ClanTags.hpp" #include "GSC/Script.hpp" // From Quake-III -#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX) -#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1))) +#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX) +#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1))) namespace Components { + constexpr std::size_t MAX_NAME_LENGTH = 16; + std::vector Bots::BotNames; - Dvar::Var Bots::SVRandomBotNames; + const Game::dvar_t* Bots::sv_randomBotNames; + const Game::dvar_t* Bots::sv_replaceBots; struct BotMovementInfo { @@ -88,16 +92,18 @@ namespace Components // Only start copying over from non-null characters (otherwise it can be "<=") if ((pos + 1) < entry.size()) { - clanAbbrev = entry.substr(pos + 1); + clanAbbrev = entry.substr(pos + 1, ClanTags::MAX_CLAN_NAME_LENGTH - 1); } entry = entry.substr(0, pos); } + entry = entry.substr(0, MAX_NAME_LENGTH - 1); + BotNames.emplace_back(entry, clanAbbrev); } - if (SVRandomBotNames.get()) + if (sv_randomBotNames->current.enabled) { RandomizeBotNames(); } @@ -152,7 +158,7 @@ namespace Components Scheduler::Once([ent] { - Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u)); + Game::Scr_AddString(Utils::String::Format("class{}", std::rand() % 5)); Game::Scr_AddString("changeclass"); Game::Scr_Notify(ent, static_cast(Game::SL_GetString("menuresponse", 0)), 2); }, Scheduler::Pipeline::SERVER, 1s); @@ -269,6 +275,9 @@ namespace Components g_botai[entref.entnum].right = static_cast(rightInt); g_botai[entref.entnum].active = true; }); + + GSC::Script::AddMethod("SetPing", []([[maybe_unused]] const Game::scr_entref_t entref) + {}); } void Bots::BotAiAction(Game::client_t* cl) @@ -278,10 +287,8 @@ namespace Components return; } - const auto entnum = cl->gentity->s.number; - // Keep test client functionality - if (!g_botai[entnum].active) + if (!g_botai[cl - Game::svs_clients].active) { Game::SV_BotUserMove(cl); return; @@ -292,10 +299,10 @@ namespace Components userCmd.serverTime = *Game::svs_time; - userCmd.buttons = g_botai[entnum].buttons; - userCmd.forwardmove = g_botai[entnum].forward; - userCmd.rightmove = g_botai[entnum].right; - userCmd.weapon = g_botai[entnum].weapon; + userCmd.buttons = g_botai[cl - Game::svs_clients].buttons; + userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward; + userCmd.rightmove = g_botai[cl - Game::svs_clients].right; + userCmd.weapon = g_botai[cl - Game::svs_clients].weapon; userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1])); @@ -349,10 +356,68 @@ namespace Components } } + int Bots::SV_GetClientPing_Hk(const int clientNum) + { + AssertIn(clientNum, Game::MAX_CLIENTS); + + if (Game::SV_IsTestClient(clientNum)) + { + return -1; + } + + return Game::svs_clients[clientNum].ping; + } + + bool Bots::IsFull() + { + auto i = 0; + while (i < *Game::svs_clientCount) + { + if (Game::svs_clients[i].header.state == Game::CS_FREE) + { + // Free slot was found + break; + } + + ++i; + } + + return i == *Game::svs_clientCount; + } + + void Bots::SV_DirectConnect_Full_Check() + { + if (!sv_replaceBots->current.enabled || !IsFull()) + { + return; + } + + for (auto i = 0; i < (*Game::sv_maxclients)->current.integer; ++i) + { + auto* cl = &Game::svs_clients[i]; + if (cl->bIsTestClient) + { + Game::SV_DropClient(cl, "EXE_DISCONNECTED", false); + cl->header.state = Game::CS_FREE; + return; + } + } + } + + void Bots::CleanBotArray() + { + ZeroMemory(&g_botai, sizeof(g_botai)); + for (std::size_t i = 0; i < std::extent_v; ++i) + { + g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon + } + } + Bots::Bots() { AssertOffset(Game::client_t, bIsTestClient, 0x41AF0); AssertOffset(Game::client_t, ping, 0x212C8); + AssertOffset(Game::client_t, gentity, 0x212A0); // Replace connect string Utils::Hook::Set(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\clanAbbrev\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\""); @@ -365,20 +430,18 @@ namespace Components Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); - SVRandomBotNames = Dvar::Register("sv_RandomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); + Utils::Hook(0x459654, SV_GetClientPing_Hk, HOOK_CALL).install()->quick(); + + sv_randomBotNames = Game::Dvar_RegisterBool("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); + sv_replaceBots = Game::Dvar_RegisterBool("sv_replaceBots", false, Game::DVAR_NONE, "Test clients will be replaced by connecting players when the server is full."); // Reset BotMovementInfo.active when client is dropped - Events::OnClientDisconnect([](const int clientNum) + Events::OnClientDisconnect([](const int clientNum) -> void { g_botai[clientNum].active = false; }); - // Zero the bot command array - for (std::size_t i = 0; i < std::extent_v; ++i) - { - ZeroMemory(&g_botai[i], sizeof(BotMovementInfo)); - g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon - } + CleanBotArray(); Command::Add("spawnBot", [](Command::Params* params) { @@ -388,6 +451,12 @@ namespace Components return; } + if (IsFull()) + { + Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Server is full.\n"); + return; + } + std::size_t count = 1; if (params->size() > 1) @@ -421,12 +490,6 @@ namespace Components AddScriptMethods(); // In case a loaded mod didn't call "BotStop" before the VM shutdown - Events::OnVMShutdown([] - { - for (std::size_t i = 0; i < std::extent_v; ++i) - { - g_botai[i].active = false; - } - }); + Events::OnVMShutdown(CleanBotArray); } } diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index b7423914..9d7526e1 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -7,11 +7,14 @@ namespace Components public: Bots(); + static void SV_DirectConnect_Full_Check(); + private: using botData = std::pair< std::string, std::string>; static std::vector BotNames; - static Dvar::Var SVRandomBotNames; + static const Game::dvar_t* sv_randomBotNames; + static const Game::dvar_t* sv_replaceBots; static void RandomizeBotNames(); static void LoadBotNames(); @@ -27,5 +30,11 @@ namespace Components static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); static void G_SelectWeaponIndex_Hk(); + + static int SV_GetClientPing_Hk(int clientNum); + + static bool IsFull(); + + static void CleanBotArray(); }; } diff --git a/src/Components/Modules/Branding.cpp b/src/Components/Modules/Branding.cpp index 0c293357..e95e32bb 100644 --- a/src/Components/Modules/Branding.cpp +++ b/src/Components/Modules/Branding.cpp @@ -48,16 +48,14 @@ namespace Components const char* Branding::GetBuildNumber() { - return SHORTVERSION " latest " __DATE__ " " __TIME__; + return VERSION " latest " __DATE__ " " __TIME__; } const char* Branding::GetVersionString() { // IW4x is technically a beta - const auto* result = Utils::String::VA("%s %s build %s %s", + return Utils::String::VA("%s %s build %s %s", BUILD_TYPE, "(Beta)", Branding::GetBuildNumber(), reinterpret_cast(0x7170A0)); - - return result; } void Branding::Dvar_SetVersionString(const Game::dvar_t* dvar, [[maybe_unused]] const char* value) @@ -100,14 +98,14 @@ namespace Components Branding::RegisterBrandingDvars(); // UI version string - Utils::Hook::Set(0x43F73B, "IW4x: " VERSION); + Utils::Hook::Set(0x43F73B, "IW4x - " GIT_TAG); // Short version dvar - Utils::Hook::Set(0x60BD91, SHORTVERSION); + Utils::Hook::Set(0x60BD91, GIT_TAG); // Com_Init_Try_Block_Function Utils::Hook::Set(0x60BAF4, BUILD_TYPE); - Utils::Hook::Set(0x60BAEf, SHORTVERSION); + Utils::Hook::Set(0x60BAEf, GIT_TAG); Utils::Hook::Set(0x60BAE5, __DATE__); // G_InitGame @@ -132,15 +130,15 @@ namespace Components // Console title if (ZoneBuilder::IsEnabled()) { - Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): ZoneBuilder"); + Utils::Hook::Set(0x4289E8, "IW4x (" GIT_TAG "): ZoneBuilder"); } else if (Dedicated::IsEnabled()) { - Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): Dedicated"); + Utils::Hook::Set(0x4289E8, "IW4x (" GIT_TAG "): Dedicated"); } else { - Utils::Hook::Set(0x4289E8, "IW4x (" VERSION "): Console"); + Utils::Hook::Set(0x4289E8, "IW4x (" GIT_TAG "): Console"); } } } diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index 0e4e4537..d3aa5397 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -1,6 +1,7 @@ #include #include "Chat.hpp" #include "PlayerName.hpp" +#include "TextRenderer.hpp" #include "Voice.hpp" #include "GSC/Script.hpp" @@ -40,24 +41,16 @@ namespace Components // Prevent callbacks from adding a new callback (would make the vector iterator invalid) CanAddCallback = false; - // Chat messages sent through the console do not begin with \x15 + // Chat messages sent through the console do not begin with \x15. In some cases it contains \x14 auto msgIndex = 0; - if (text[msgIndex] == '\x15') + while (text[msgIndex] == '\x15' || text[msgIndex] == '\x14') { - msgIndex = 1; + ++msgIndex; } if (text[msgIndex] == '/') { SendChat = false; - - if (msgIndex == 1) - { - // Overwrite / with \x15 - text[msgIndex] = text[msgIndex - 1]; - } - // Skip over the first character - ++text; } if (IsMuted(player)) @@ -72,13 +65,15 @@ namespace Components 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 '/' + // Message might be empty after the special characters if (text[msgIndex] == '\0') { SendChat = false; return text; } + Logger::Print("{}: {}\n", Game::svs_clients[player - Game::g_entities].name, (text + msgIndex)); + for (const auto& callback : SayCallbacks) { if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode)) diff --git a/src/Components/Modules/ClanTags.cpp b/src/Components/Modules/ClanTags.cpp index 0db83001..40233a4c 100644 --- a/src/Components/Modules/ClanTags.cpp +++ b/src/Components/Modules/ClanTags.cpp @@ -8,7 +8,7 @@ namespace Components const Game::dvar_t* ClanTags::ClanName; // bgs_t and clientState_s do not have this - char ClanTags::ClientState[Game::MAX_CLIENTS][5]; + char ClanTags::ClientState[Game::MAX_CLIENTS][MAX_CLAN_NAME_LENGTH]; const char* ClanTags::GetClanTagWithName(int clientNum, const char* playerName) { @@ -73,7 +73,7 @@ namespace Components void ClanTags::CL_SanitizeClanName() { - char saneNameBuf[5]{}; + char saneNameBuf[MAX_CLAN_NAME_LENGTH]{}; auto* saneName = saneNameBuf; assert(ClanName); @@ -238,7 +238,7 @@ namespace Components ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE, "Your clan abbreviation"); }); - std::memset(&ClientState, 0, sizeof(char[Game::MAX_CLIENTS][5])); + std::memset(&ClientState, 0, sizeof(char[Game::MAX_CLIENTS][MAX_CLAN_NAME_LENGTH])); ServerCommands::OnCommand(22, [](Command::Params* params) { diff --git a/src/Components/Modules/ClanTags.hpp b/src/Components/Modules/ClanTags.hpp index 6618504b..72b6c9ad 100644 --- a/src/Components/Modules/ClanTags.hpp +++ b/src/Components/Modules/ClanTags.hpp @@ -5,6 +5,8 @@ namespace Components class ClanTags : public Component { public: + static constexpr std::size_t MAX_CLAN_NAME_LENGTH = 5; + ClanTags(); static const char* GetClanTagWithName(int clientNum, const char* playerName); diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index 0aa4430c..c4a0e4ff 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -116,7 +116,12 @@ namespace Components void Command::Execute(std::string command, bool sync) { - command.append("\n"); // Make sure it's terminated + if (command.empty()) + { + return; + } + + command.push_back('\n'); // Make sure it's terminated assert(command.size() < Game::MAX_CMD_LINE); diff --git a/src/Components/Modules/ConnectProtocol.cpp b/src/Components/Modules/ConnectProtocol.cpp index 4a44ad34..1793b44a 100644 --- a/src/Components/Modules/ConnectProtocol.cpp +++ b/src/Components/Modules/ConnectProtocol.cpp @@ -1,5 +1,6 @@ #include #include "ConnectProtocol.hpp" +#include "IPCPipe.hpp" namespace Components { diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index c30824cd..0a91504d 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -1,5 +1,6 @@ #include #include "Console.hpp" +#include "TextRenderer.hpp" #include "Terminus_4.49.1.ttf.hpp" @@ -95,14 +96,14 @@ namespace Components } else if (IsWindow(GetWindow()) != FALSE) { - SetWindowTextA(GetWindow(), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data())); + SetWindowTextA(GetWindow(), Utils::String::VA("IW4x(" GIT_TAG ") : %s", hostname.data())); } } void Console::ShowPrompt() { wattron(InputWindow, COLOR_PAIR(10) | A_BOLD); - wprintw(InputWindow, "%s> ", VERSION); + wprintw(InputWindow, "%s> ", GIT_TAG); } void Console::RefreshOutput() @@ -859,7 +860,7 @@ namespace Components AssertOffset(Game::clientUIActive_t, keyCatchers, 0x9B0); // Console '%s: %s> ' string - Utils::Hook::Set(0x5A44B4, "IW4x MP: " VERSION "> "); + Utils::Hook::Set(0x5A44B4, "IW4x_MP: " GIT_TAG "> "); // Patch console color static float consoleColor[] = { 0.70f, 1.00f, 0.00f, 1.00f }; diff --git a/src/Components/Modules/Debug.cpp b/src/Components/Modules/Debug.cpp index 5b8bcfcd..4e7b7c95 100644 --- a/src/Components/Modules/Debug.cpp +++ b/src/Components/Modules/Debug.cpp @@ -1,5 +1,6 @@ #include #include "Debug.hpp" +#include "TextRenderer.hpp" #include "Game/Engine/ScopedCriticalSection.hpp" diff --git a/src/Components/Modules/Discord.cpp b/src/Components/Modules/Discord.cpp index 9087d734..b487e106 100644 --- a/src/Components/Modules/Discord.cpp +++ b/src/Components/Modules/Discord.cpp @@ -1,6 +1,7 @@ #include #include "Discord.hpp" #include "Party.hpp" +#include "TextRenderer.hpp" #include diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index de1360a9..41cc285c 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -453,7 +453,7 @@ namespace Components // Insert default values playerInfo["score"] = 0; playerInfo["ping"] = 0; - playerInfo["name"] = ""; + playerInfo["name"] = "Unknown Soldier"; playerInfo["test_client"] = 0; if (Dedicated::IsRunning()) diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index f6ae52b2..665abae7 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -1,5 +1,8 @@ #include +#include "Friends.hpp" +#include "TextRenderer.hpp" + namespace Components { Dvar::Var Dvar::Name; @@ -411,9 +414,30 @@ namespace Components // un-cheat cg_draw2D Utils::Hook::Set(0x4F8EEE, Game::DVAR_NONE); + // un-cheat cg_overheadNamesFarScale + Utils::Hook::Set(0x4FA7C4, Game::DVAR_NONE); + + // un-cheat cg_overheadNamesSize + Utils::Hook::Set(0x4FA7F9, Game::DVAR_NONE); + + // un-cheat cg_overheadRankSize + Utils::Hook::Set(0x4FA863, Game::DVAR_NONE); + + // un-cheat cg_overheadIconSize + Utils::Hook::Set(0x4FA833, Game::DVAR_NONE); + + // un-cheat cg_overheadTitleSize + Utils::Hook::Set(0x4FA898, Game::DVAR_NONE); + + // un-cheat cg_overheadNamesGlow + Utils::Hook::Set(0x4FA8C9, Game::DVAR_NONE); + // remove archive flags for cg_hudChatPosition Utils::Hook::Xor(0x4F9992, Game::DVAR_ARCHIVE); + // remove archive flags for sv_hostname + Utils::Hook::Xor(0x4D3786, Game::DVAR_ARCHIVE); + // remove write protection from fs_game Utils::Hook::Xor(0x6431EA, Game::DVAR_INIT); @@ -426,13 +450,13 @@ namespace Components static float volume = 1.0f; Utils::Hook::Set(0x408078, &volume); - // Uncheat ui_showList + // un-cheat ui_showList Utils::Hook::Xor(0x6310DC, Game::DVAR_CHEAT); - // Uncheat ui_debugMode + // un-cheat ui_debugMode Utils::Hook::Xor(0x6312DE, Game::DVAR_CHEAT); - // Uncheat jump_slowdownEnable + // un-cheat jump_slowdownEnable Utils::Hook::Xor(0x4EFABE, Game::DVAR_CHEAT); // Hook dvar 'name' registration @@ -468,7 +492,7 @@ namespace Components Utils::Hook(0x636608, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); Utils::Hook(0x636695, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - // Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server + // Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can protect dvars Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick(); // For debugging diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index 7860a375..0bb0db17 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -1,5 +1,8 @@ #include + #include "Console.hpp" +#include "Exception.hpp" +#include "Window.hpp" #include @@ -50,32 +53,39 @@ namespace Components Game::Sys_SuspendOtherThreads(); } - void Exception::CopyMessageToClipboard(const std::string& error) + void Exception::CopyMessageToClipboard(const char* error) { const auto hWndNewOwner = GetDesktopWindow(); const auto result = OpenClipboard(hWndNewOwner); if (result == FALSE) + { return; + } - EmptyClipboard(); - auto* hMem = GlobalAlloc(GMEM_MOVEABLE, error.size() + 1); - - if (hMem == nullptr) + const auto _0 = gsl::finally([] { CloseClipboard(); + }); + + EmptyClipboard(); + + const auto len = std::strlen(error); + auto* hMem = GlobalAlloc(GMEM_MOVEABLE, len + 1); + + if (!hMem) + { return; } auto* lock = GlobalLock(hMem); - if (lock != nullptr) + if (lock) { - std::memcpy(lock, error.data(), error.size() + 1); + std::memcpy(lock, error, len + 1); GlobalUnlock(hMem); - SetClipboardData(1, hMem); + SetClipboardData(CF_TEXT, hMem); } - CloseClipboard(); GlobalFree(hMem); } @@ -88,18 +98,18 @@ namespace Components return EXCEPTION_CONTINUE_EXECUTION; } - std::string errorStr; + const char* error; if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { - errorStr = "Termination because of a stack overflow.\nCopy exception address to clipboard?"; + error = "Termination because of a stack overflow.\nCopy exception address to clipboard?"; } else { - errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); + error = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); } // Message should be copied to the keyboard if no button is pressed - if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES) + if (MessageBoxA(nullptr, error, nullptr, MB_YESNO | MB_ICONERROR) == IDYES) { CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress)); } @@ -136,8 +146,8 @@ namespace Components MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR); #ifdef _DEBUG OutputDebugStringA("Failed to create new minidump!"); -#endif Utils::OutputDebugLastError(); +#endif TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); } @@ -190,25 +200,6 @@ namespace Components Utils::Hook(0x61F17D, LongJmp_Internal_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x61F248, LongJmp_Internal_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x61F5E7, LongJmp_Internal_Stub, HOOK_CALL).install()->quick(); - -#ifdef MAP_TEST - Command::Add("mapTest", [](Command::Params* params) - { - Game::UI_UpdateArenas(); - - std::string command; - for (auto i = 0; i < (params->size() >= 2 ? atoi(params->get(1)) : *Game::arenaCount); ++i) - { - const auto* mapName = ArenaLength::NewArenas[i % *Game::arenaCount].mapName; - - if (!(i % 2)) command.append("wait 250;disconnect;wait 750;"); // Test a disconnect - else command.append("wait 500;"); // Test direct map switch - command.append(Utils::String::VA("map %s;", mapName)); - } - - Command::Execute(command, false); - }); -#endif } Exception::~Exception() diff --git a/src/Components/Modules/Exception.hpp b/src/Components/Modules/Exception.hpp index 40b5e820..9967144d 100644 --- a/src/Components/Modules/Exception.hpp +++ b/src/Components/Modules/Exception.hpp @@ -17,7 +17,7 @@ namespace Components static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo); static __declspec(noreturn) void LongJmp_Internal_Stub(jmp_buf env, int status); - static void CopyMessageToClipboard(const std::string& error); + static void CopyMessageToClipboard(const char* error); static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_Stub(LPTOP_LEVEL_EXCEPTION_FILTER); diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index 9762fce4..02b27006 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -278,9 +278,17 @@ namespace Components } } - int FileSystem::ExecIsFSStub(const char* execFilename) + int FileSystem::Cmd_Exec_f_Stub(const char* s0, [[maybe_unused]] const char* s1) { - return !File(execFilename).exists(); + int f; + auto len = Game::FS_FOpenFileByMode(s0, &f, Game::FS_READ); + if (len < 0) + { + return 1; // Not found + } + + Game::FS_FCloseFile(f); + return 0; // Found } void FileSystem::FsStartupSync(const char* a1) @@ -335,7 +343,7 @@ namespace Components Utils::Hook(Game::FS_FreeFile, FreeFile, HOOK_JUMP).install()->quick(); // Filesystem config checks - Utils::Hook(0x6098FD, ExecIsFSStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x6098FD, Cmd_Exec_f_Stub, HOOK_CALL).install()->quick(); // Don't strip the folders from the config name (otherwise our ExecIsFSStub fails) Utils::Hook::Nop(0x6098F2, 5); diff --git a/src/Components/Modules/FileSystem.hpp b/src/Components/Modules/FileSystem.hpp index 3a403027..cb721369 100644 --- a/src/Components/Modules/FileSystem.hpp +++ b/src/Components/Modules/FileSystem.hpp @@ -112,7 +112,7 @@ namespace Components static void RegisterFolders(); static void StartupStub(); - static int ExecIsFSStub(const char* execFilename); + static int Cmd_Exec_f_Stub(const char* s0, const char* s1); static void FsStartupSync(const char* a1); static void FsRestartSync(int localClientNum, int checksumFeed); diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index 900c2111..dea87d13 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -5,7 +5,12 @@ #include #pragma warning(pop) +#include "Friends.hpp" +#include "Materials.hpp" +#include "Node.hpp" #include "Party.hpp" +#include "TextRenderer.hpp" +#include "Toast.hpp" #include "UIFeeder.hpp" namespace Components diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp index af5ac692..662bc079 100644 --- a/src/Components/Modules/GSC/IO.cpp +++ b/src/Components/Modules/GSC/IO.cpp @@ -8,21 +8,42 @@ namespace Components::GSC FILE* IO::openScriptIOFileHandle; - std::filesystem::path IO::Path; + std::filesystem::path IO::DefaultDestPath; + + bool IO::ValidatePath(const char* function, const char* path) + { + for (std::size_t i = 0; i < std::extent_v; ++i) + { + if (std::strstr(path, ForbiddenStrings[i]) != nullptr) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "{}: directory traversal is not allowed!\n", function); + return false; + } + } + + return true; + } + + std::filesystem::path IO::BuildPath(const char* path) + { + const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string; + if (!fsGame.empty()) + { + return fsGame / "scriptdata"s / path; + } + + return DefaultDestPath / "scriptdata"s / 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 (!ValidatePath("OpenFile", filepath)) { - 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; - } + Game::Scr_AddInt(-1); + return; } if (mode != "read"s) @@ -39,10 +60,10 @@ namespace Components::GSC return; } - const auto scriptData = Path / "scriptdata"s / filepath; + const auto dest = BuildPath(filepath); _set_errno(0); - const auto result = fopen_s(&openScriptIOFileHandle, scriptData.string().data(), "r"); + const auto result = fopen_s(&openScriptIOFileHandle, dest.string().data(), "r"); if (result || !openScriptIOFileHandle) { Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. '{}'", result); @@ -97,27 +118,11 @@ namespace Components::GSC const auto* text = Game::Scr_GetString(1); const auto* mode = Game::Scr_GetString(2); - if (!filepath) + if (!ValidatePath("FileWrite", filepath)) { - Game::Scr_ParamError(0, "FileWrite: filepath is not defined!"); return; } - if (!text || !mode) - { - Game::Scr_Error("FileWrite: Illegal parameters!"); - return; - } - - for (std::size_t i = 0; i < std::extent_v; ++i) - { - if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) - { - 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_PARSERSCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); @@ -125,34 +130,24 @@ namespace Components::GSC } const auto append = mode == "append"s; - const auto scriptData = Path / "scriptdata"s / filepath; - Utils::IO::WriteFile(scriptData.string(), text, append); + const auto dest = BuildPath(filepath); + Utils::IO::WriteFile(dest.string(), text, append); }); Script::AddFunction("FileRead", [] // gsc: FileRead() { const auto* filepath = Game::Scr_GetString(0); - if (!filepath) + if (!ValidatePath("FileRead", filepath)) { - Game::Scr_ParamError(0, "FileRead: filepath is not defined!"); return; } - for (std::size_t i = 0; i < std::extent_v; ++i) - { - if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) - { - Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: directory traversal is not allowed!\n"); - return; - } - } - - const auto scriptData = Path / "scriptdata"s / filepath; + const auto dest = BuildPath(filepath); std::string file; - if (!Utils::IO::ReadFile(scriptData.string(), &file)) + if (!Utils::IO::ReadFile(dest.string(), &file)) { - Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: file '{}' not found!\n", scriptData.string()); + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: file '{}' not found!\n", dest.string()); return; } @@ -163,45 +158,73 @@ namespace Components::GSC Script::AddFunction("FileExists", [] // gsc: FileExists() { const auto* filepath = Game::Scr_GetString(0); - if (!filepath) + if (!ValidatePath("FileExists", filepath)) { - Game::Scr_ParamError(0, "FileExists: filepath is not defined!"); return; } - for (std::size_t i = 0; i < std::extent_v; ++i) - { - if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) - { - Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileExists: directory traversal is not allowed!\n"); - return; - } - } - - const auto scriptData = Path / "scriptdata"s / filepath; - Game::Scr_AddBool(Utils::IO::FileExists(scriptData.string())); + const auto dest = BuildPath(filepath); + Game::Scr_AddBool(Utils::IO::FileExists(dest.string())); }); Script::AddFunction("FileRemove", [] // gsc: FileRemove() { const auto* filepath = Game::Scr_GetString(0); - if (!filepath) + if (!ValidatePath("FileRemove", filepath)) { - Game::Scr_ParamError(0, "FileRemove: filepath is not defined!"); return; } - for (std::size_t i = 0; i < std::extent_v; ++i) + const auto dest = BuildPath(filepath); + Game::Scr_AddBool(Utils::IO::RemoveFile(dest.string())); + }); + + Script::AddFunction("FileRename", [] // gsc: FileRename(, ) + { + const auto* filepath = Game::Scr_GetString(0); + const auto* destpath = Game::Scr_GetString(0); + if (!ValidatePath("FileRename", filepath) || !ValidatePath("FileRename", destpath)) { - if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) - { - Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRemove: directory traversal is not allowed!\n"); - return; - } + return; } - const auto scriptData = Path / "scriptdata"s / filepath; - Game::Scr_AddBool(Utils::IO::RemoveFile(scriptData.string())); + const auto from = BuildPath(filepath); + const auto to = BuildPath(destpath); + + std::error_code err; + std::filesystem::rename(from, to, err); + if (err.value()) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRename: failed to rename file! Error message: {}\n", err.message()); + Game::Scr_AddInt(-1); + return; + } + + Game::Scr_AddInt(1); + }); + + Script::AddFunction("FileCopy", [] // gsc: FileCopy(, ) + { + const auto* filepath = Game::Scr_GetString(0); + const auto* destpath = Game::Scr_GetString(0); + if (!ValidatePath("FileCopy", filepath) || !ValidatePath("FileCopy", destpath)) + { + return; + } + + const auto from = BuildPath(filepath); + const auto to = BuildPath(destpath); + + std::error_code err; + std::filesystem::copy(from, to, err); + if (err.value()) + { + Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileCopy: failed to copy file! Error message: {}\n", err.message()); + Game::Scr_AddInt(-1); + return; + } + + Game::Scr_AddInt(1); }); Script::AddFunction("ReadStream", GScr_ReadStream); @@ -210,7 +233,7 @@ namespace Components::GSC IO::IO() { openScriptIOFileHandle = nullptr; - Path = "userraw"s; + DefaultDestPath = "userraw"s; AddScriptFunctions(); diff --git a/src/Components/Modules/GSC/IO.hpp b/src/Components/Modules/GSC/IO.hpp index 11729c89..45a9a0b8 100644 --- a/src/Components/Modules/GSC/IO.hpp +++ b/src/Components/Modules/GSC/IO.hpp @@ -12,7 +12,10 @@ namespace Components::GSC static FILE* openScriptIOFileHandle; - static std::filesystem::path Path; + static std::filesystem::path DefaultDestPath; + + static bool ValidatePath(const char* function, const char* path); + static std::filesystem::path BuildPath(const char* path); static void GScr_OpenFile(); static void GScr_ReadStream(); diff --git a/src/Components/Modules/GSC/ScriptError.cpp b/src/Components/Modules/GSC/ScriptError.cpp index 8b4def7b..3a197a5a 100644 --- a/src/Components/Modules/GSC/ScriptError.cpp +++ b/src/Components/Modules/GSC/ScriptError.cpp @@ -754,7 +754,7 @@ namespace Components::GSC assert(scrParserGlob.saveSourceBufferLookupLen > 0); --scrParserGlob.saveSourceBufferLookupLen; - auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen; + const auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen; const auto len = saveSourceBuffer->len; assert(len >= -1); @@ -799,7 +799,7 @@ namespace Components::GSC if (Game::FindVariable(Game::scrCompilePub->loadedscripts, name)) { Game::SL_RemoveRefToString(name); - auto filePosPtr = Game::FindVariable(Game::scrCompilePub->scriptsPos, name); + const auto filePosPtr = Game::FindVariable(Game::scrCompilePub->scriptsPos, name); return filePosPtr ? Game::FindObject(Game::scrCompilePub->scriptsPos, filePosPtr) : 0; } @@ -809,7 +809,7 @@ namespace Components::GSC sprintf_s(extFilename, "%s.gsc", Game::SL_ConvertToString(static_cast(name))); const auto* oldSourceBuf = scrParserPub.sourceBuf; - auto* sourceBuffer = Scr_AddSourceBuffer(Game::SL_ConvertToString(static_cast(name)), extFilename, Game::TempMalloc(0), true); + const auto* sourceBuffer = Scr_AddSourceBuffer(Game::SL_ConvertToString(static_cast(name)), extFilename, Game::TempMalloc(0), true); if (!sourceBuffer) { diff --git a/src/Components/Modules/GSC/ScriptExtension.cpp b/src/Components/Modules/GSC/ScriptExtension.cpp index 2d8919ed..9ce1c5c2 100644 --- a/src/Components/Modules/GSC/ScriptExtension.cpp +++ b/src/Components/Modules/GSC/ScriptExtension.cpp @@ -273,51 +273,6 @@ namespace Components::GSC void ScriptExtension::AddMethods() { - // ScriptExtension methods - Script::AddMethod("GetIp", [](const Game::scr_entref_t entref) // gsc: self GetIp() - { - const auto* ent = Script::Scr_GetPlayerEntity(entref); - const auto* client = Script::GetClient(ent); - - std::string ip = Game::NET_AdrToString(client->header.netchan.remoteAddress); - - const auto extractIPAddress = [](const std::string& input) -> std::string - { - const auto colonPos = input.find(':'); - if (colonPos == std::string::npos) - { - return input; - } - - auto ipAddress = input.substr(0, colonPos); - return ipAddress; - }; - - ip = extractIPAddress(ip); - - Game::Scr_AddString(ip.data()); - }); - - Script::AddMethod("GetPing", [](const Game::scr_entref_t entref) // gsc: self GetPing() - { - const auto* ent = Script::Scr_GetPlayerEntity(entref); - const auto* client = Script::GetClient(ent); - - Game::Scr_AddInt(client->ping); - }); - - Script::AddMethod("SetPing", [](const Game::scr_entref_t entref) // gsc: self SetPing() - { - auto ping = Game::Scr_GetInt(0); - - ping = std::clamp(ping, 0, 999); - - const auto* ent = Script::Scr_GetPlayerEntity(entref); - auto* client = Script::GetClient(ent); - - client->ping = ping; - }); - // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); { diff --git a/src/Components/Modules/IPCPipe.cpp b/src/Components/Modules/IPCPipe.cpp index ea2f2980..64d9a31d 100644 --- a/src/Components/Modules/IPCPipe.cpp +++ b/src/Components/Modules/IPCPipe.cpp @@ -1,6 +1,8 @@ #include #include +#include "IPCPipe.hpp" + namespace Components { Pipe IPCPipe::ServerPipe; diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index e5798d5b..69864abc 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -3,10 +3,16 @@ namespace Components { + using namespace Utils::String; + std::mutex Logger::MessageMutex; std::vector Logger::MessageQueue; + + std::recursive_mutex Logger::LoggingMutex; std::vector Logger::LoggingAddresses[2]; + Dvar::Var Logger::IW4x_oneLog; + void(*Logger::PipeCallback)(const std::string&) = nullptr;; bool Logger::IsConsoleReady() @@ -23,7 +29,7 @@ namespace Components vsnprintf_s(buf, _TRUNCATE, message, va); va_end(va); - MessagePrint(channel, {buf}); + MessagePrint(channel, std::string{ buf }); } void Logger::MessagePrint(const int channel, const std::string& msg) @@ -145,6 +151,7 @@ namespace Components return; } + std::unique_lock lock(LoggingMutex); for (const auto& addr : LoggingAddresses[gLog & 1]) { Network::SendCommand(addr, "print", data); @@ -216,13 +223,15 @@ namespace Components void Logger::RedirectOSPath(const char* file, char* folder) { - if (Dvar::Var("g_log").get() == file) + const auto* g_log = (*Game::g_log) ? (*Game::g_log)->current.string : ""; + + if (std::strcmp(g_log, file) == 0) { - if (folder != "userraw"s) + if (std::strcmp(folder, "userraw") != 0) { - if (Dvar::Var("iw4x_onelog").get()) + if (IW4x_oneLog.get()) { - strcpy_s(folder, 256, "userraw"); + strncpy_s(folder, 256, "userraw", _TRUNCATE); } } } @@ -234,11 +243,9 @@ namespace Components { pushad - push [esp + 28h] - push [esp + 30h] - + push [esp + 20h + 8h] + push [esp + 20h + 10h] call RedirectOSPath - add esp, 8h popad @@ -253,14 +260,25 @@ namespace Components } } + void Logger::LSP_LogString_Stub([[maybe_unused]] int localControllerIndex, const char* string) + { + NetworkLog(string, false); + } + + void Logger::LSP_LogStringAboutUser_Stub([[maybe_unused]] int localControllerIndex, std::uint64_t xuid, const char* string) + { + NetworkLog(VA("%" PRIx64 ";%s", xuid, string), false); + } + void Logger::AddServerCommands() { Command::AddSV("log_add", [](Command::Params* params) { if (params->size() < 2) return; - Network::Address addr(params->get(1)); + std::unique_lock lock(LoggingMutex); + Network::Address addr(params->get(1)); if (std::find(LoggingAddresses[0].begin(), LoggingAddresses[0].end(), addr) == LoggingAddresses[0].end()) { LoggingAddresses[0].push_back(addr); @@ -271,8 +289,10 @@ namespace Components { if (params->size() < 2) return; + std::unique_lock lock(LoggingMutex); + const auto num = std::atoi(params->get(1)); - if (!std::strcmp(Utils::String::VA("%i", num), params->get(1)) && static_cast(num) < LoggingAddresses[0].size()) + if (!std::strcmp(VA("%i", num), params->get(1)) && static_cast(num) < LoggingAddresses[0].size()) { auto addr = Logger::LoggingAddresses[0].begin() + num; Print("Address {} removed\n", addr->getString()); @@ -300,6 +320,8 @@ namespace Components Print("# ID: Address\n"); Print("-------------\n"); + std::unique_lock lock(LoggingMutex); + for (unsigned int i = 0; i < LoggingAddresses[0].size(); ++i) { Print("#{:03d}: {}\n", i, LoggingAddresses[0][i].getString()); @@ -310,8 +332,9 @@ namespace Components { if (params->size() < 2) return; - const Network::Address addr(params->get(1)); + std::unique_lock lock(LoggingMutex); + const Network::Address addr(params->get(1)); if (std::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr) == LoggingAddresses[1].end()) { LoggingAddresses[1].push_back(addr); @@ -322,8 +345,10 @@ namespace Components { if (params->size() < 2) return; + std::unique_lock lock(LoggingMutex); + const auto num = std::atoi(params->get(1)); - if (!std::strcmp(Utils::String::VA("%i", num), params->get(1)) && static_cast(num) < LoggingAddresses[1].size()) + if (!std::strcmp(VA("%i", num), params->get(1)) && static_cast(num) < LoggingAddresses[1].size()) { const auto addr = LoggingAddresses[1].begin() + num; Print("Address {} removed\n", addr->getString()); @@ -332,7 +357,6 @@ namespace Components else { const Network::Address addr(params->get(1)); - const auto i = std::ranges::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr); if (i != LoggingAddresses[1].end()) { @@ -351,6 +375,7 @@ namespace Components Print("# ID: Address\n"); Print("-------------\n"); + std::unique_lock lock(LoggingMutex); for (std::size_t i = 0; i < LoggingAddresses[1].size(); ++i) { Print("#{:03d}: {}\n", i, LoggingAddresses[1][i].getString()); @@ -360,7 +385,7 @@ namespace Components Logger::Logger() { - Dvar::Register("iw4x_onelog", false, Game::DVAR_LATCH | Game::DVAR_ARCHIVE, "Only write the game log to the 'userraw' OS folder"); + IW4x_oneLog = Dvar::Register("iw4x_onelog", false, Game::DVAR_LATCH, "Only write the game log to the 'userraw' OS folder"); Utils::Hook(0x642139, BuildOSPath_Stub, HOOK_JUMP).install()->quick(); Scheduler::Loop(Frame, Scheduler::Pipeline::SERVER); @@ -368,6 +393,9 @@ namespace Components Utils::Hook(Game::G_LogPrintf, G_LogPrintf_Hk, HOOK_JUMP).install()->quick(); Utils::Hook(Game::Com_PrintMessage, PrintMessage_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x5F67AE, LSP_LogString_Stub, HOOK_CALL).install()->quick(); // Scr_LogString + Utils::Hook(0x5F67EE, LSP_LogStringAboutUser_Stub, HOOK_CALL).install()->quick(); // ScrCmd_LogString_Stub + if (Loader::IsPerformingUnitTests()) { Utils::Hook(Game::Com_Printf, Print_Stub, HOOK_JUMP).install()->quick(); @@ -378,12 +406,12 @@ namespace Components Logger::~Logger() { + std::unique_lock lock_logging(LoggingMutex); LoggingAddresses[0].clear(); LoggingAddresses[1].clear(); - std::unique_lock lock(MessageMutex); + std::unique_lock lock_message(MessageMutex); MessageQueue.clear(); - lock.unlock(); // Flush the console log if (*Game::logfile) diff --git a/src/Components/Modules/Logger.hpp b/src/Components/Modules/Logger.hpp index 8bc8f454..2ada2a40 100644 --- a/src/Components/Modules/Logger.hpp +++ b/src/Components/Modules/Logger.hpp @@ -110,8 +110,12 @@ namespace Components private: static std::mutex MessageMutex; static std::vector MessageQueue; + + static std::recursive_mutex LoggingMutex; static std::vector LoggingAddresses[2]; + static Dvar::Var IW4x_oneLog; + static void(*PipeCallback)(const std::string&); static void MessagePrint(int channel, const std::string& msg); @@ -128,6 +132,9 @@ namespace Components static void NetworkLog(const char* data, bool gLog); + static void LSP_LogString_Stub(int localControllerIndex, const char* string); + static void LSP_LogStringAboutUser_Stub(int localControllerIndex, std::uint64_t xuid, const char* string); + static void AddServerCommands(); }; } diff --git a/src/Components/Modules/MapRotation.cpp b/src/Components/Modules/MapRotation.cpp index 8ed6ad4a..a449c763 100644 --- a/src/Components/Modules/MapRotation.cpp +++ b/src/Components/Modules/MapRotation.cpp @@ -1,5 +1,6 @@ #include #include "MapRotation.hpp" +#include "Party.hpp" namespace Components { @@ -25,7 +26,7 @@ namespace Components void MapRotation::RotationData::addEntry(const std::string& key, const std::string& value) { - this->rotationEntries_.emplace_back(std::make_pair(key, value)); + this->rotationEntries_.emplace_back(key, value); } std::size_t MapRotation::RotationData::getEntriesSize() const noexcept @@ -36,7 +37,7 @@ namespace Components MapRotation::RotationData::rotationEntry& MapRotation::RotationData::getNextEntry() { const auto index = this->index_; - ++this->index_ %= this->rotationEntries_.size(); // Point index_ to the next entry + ++this->index_ %= this->rotationEntries_.size(); return this->rotationEntries_.at(index); } @@ -45,6 +46,19 @@ namespace Components return this->rotationEntries_.at(this->index_); } + void MapRotation::RotationData::setHandler(const std::string& key, const rotationCallback& callback) + { + this->rotationHandlers_[key] = callback; + } + + void MapRotation::RotationData::callHandler(const rotationEntry& entry) const + { + if (const auto itr = this->rotationHandlers_.find(entry.first); itr != this->rotationHandlers_.end()) + { + itr->second(entry.second); + } + } + void MapRotation::RotationData::parse(const std::string& data) { const auto tokens = Utils::String::Split(data, ' '); @@ -54,14 +68,12 @@ namespace Components const auto& key = tokens[i]; const auto& value = tokens[i + 1]; - if (key == "map"s || key == "gametype"s) + if (!this->containsHandler(key)) { - this->addEntry(key, value); - } - else - { - throw MapRotationParseError(); + throw MapRotationParseError(std::format("Invalid key '{}'", key)); } + + this->addEntry(key, value); } } @@ -78,6 +90,16 @@ namespace Components }); } + bool MapRotation::RotationData::containsHandler(const std::string& key) const + { + return this->rotationHandlers_.contains(key); + } + + void MapRotation::RotationData::clear() noexcept + { + this->rotationEntries_.clear(); + } + nlohmann::json MapRotation::RotationData::to_json() const { std::vector mapVector; @@ -97,8 +119,8 @@ namespace Components auto mapRotationJson = nlohmann::json { - {"maps", mapVector}, - {"gametypes", gametypeVector}, + { "maps", mapVector }, + { "gametypes", gametypeVector }, }; return mapRotationJson; @@ -183,7 +205,7 @@ namespace Components return false; } - if (Dvar::Var("party_enable").get() && Dvar::Var("party_host").get()) + if (Party::IsEnabled() && Dvar::Var("party_host").get()) { Logger::Warning(Game::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n"); return false; @@ -212,6 +234,12 @@ namespace Components Game::Dvar_SetStringByName("g_gametype", gametype.data()); } + void MapRotation::ApplyExec(const std::string& name) + { + assert(!name.empty()); + Command::Execute(std::format("exec game_settings/{}", name), false); + } + void MapRotation::RestartCurrentMap() { std::string svMapname = (*Game::sv_mapname)->current.string; @@ -234,21 +262,15 @@ namespace Components while (i < rotation.getEntriesSize()) { const auto& entry = rotation.getNextEntry(); + rotation.callHandler(entry); + Logger::Print("MapRotation: applying key '{}' with value '{}'\n", entry.first, entry.second); + if (entry.first == "map"s) { - Logger::Print("Loading new map: '{}'", entry.second); - ApplyMap(entry.second); - // Map was found so we exit the loop break; } - if (entry.first == "gametype"s) - { - Logger::Print("Applying new gametype: '{}'", entry.second); - ApplyGametype(entry.second); - } - ++i; } @@ -374,13 +396,15 @@ namespace Components AddMapRotationCommands(); Utils::Hook::Set(0x4152E8, SV_MapRotate_f); + DedicatedRotation.setHandler("map", ApplyMap); + DedicatedRotation.setHandler("gametype", ApplyGametype); + DedicatedRotation.setHandler("exec", ApplyExec); + Events::OnDvarInit(RegisterMapRotationDvars); } bool MapRotation::unitTest() { - RotationData rotation; - Logger::Debug("Testing map rotation parsing..."); const auto* normal = "map mp_highrise map mp_terminal map mp_firingrange map mp_trailerpark gametype dm map mp_shipment_long"; @@ -395,6 +419,8 @@ namespace Components return false; } + DedicatedRotation.clear(); + const auto* mistake = "spdevmap mp_dome"; auto success = false; diff --git a/src/Components/Modules/MapRotation.hpp b/src/Components/Modules/MapRotation.hpp index 68e51b23..3acbd4f3 100644 --- a/src/Components/Modules/MapRotation.hpp +++ b/src/Components/Modules/MapRotation.hpp @@ -14,9 +14,27 @@ namespace Components bool unitTest() override; private: - struct MapRotationParseError : public std::exception + class MapRotationParseError : public std::runtime_error { - [[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Error"; } + private: + static std::string fmt(const std::string& message) + { + std::string error = "Map Rotation Parse Error"; + + if (!message.empty()) + { + error.append(": "); + error.append(message); + } + + return error; + } + + public: + MapRotationParseError(const std::string& message) + : std::runtime_error(fmt(message)) + { + } }; class RotationData @@ -24,6 +42,8 @@ namespace Components public: using rotationEntry = std::pair; + using rotationCallback = std::function; + RotationData(); void randomize(); @@ -36,15 +56,22 @@ namespace Components rotationEntry& getNextEntry(); rotationEntry& peekNextEntry(); + void setHandler(const std::string& key, const rotationCallback& callback); + void callHandler(const rotationEntry& entry) const; + void parse(const std::string& data); [[nodiscard]] bool empty() const noexcept; [[nodiscard]] bool contains(const std::string& key, const std::string& value) const; + [[nodiscard]] bool containsHandler(const std::string& key) const; + + void clear() noexcept; [[nodiscard]] nlohmann::json to_json() const; private: std::vector rotationEntries_; + std::unordered_map rotationHandlers_; std::size_t index_; }; @@ -67,6 +94,7 @@ namespace Components static bool ShouldRotate(); static void ApplyMap(const std::string& map); static void ApplyGametype(const std::string& gametype); + static void ApplyExec(const std::string& name); static void RestartCurrentMap(); static void ApplyRotation(RotationData& rotation); static void ApplyMapRotationCurrent(const std::string& data); diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 04c761ef..da4a65f3 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -316,13 +316,13 @@ namespace Components void Maps::GetBSPName(char* buffer, size_t size, const char* format, const char* mapname) { - if (!Utils::String::StartsWith(mapname, "mp_") && !Utils::String::StartsWith(mapname, "zm_")) + if (!Utils::String::StartsWith(mapname, "mp_")) { format = "maps/%s.d3dbsp"; } - // Redirect shipment to shipment long - if (mapname == "mp_shipment"s) + // TODO: Remove this hack by using CoD4 version of the map + if (std::strcmp(mapname, "mp_shipment") == 0) { mapname = "mp_shipment_long"; } diff --git a/src/Components/Modules/Materials.cpp b/src/Components/Modules/Materials.cpp index 69176b08..16178a44 100644 --- a/src/Components/Modules/Materials.cpp +++ b/src/Components/Modules/Materials.cpp @@ -1,4 +1,5 @@ #include +#include "Materials.hpp" namespace Components { diff --git a/src/Components/Modules/ModList.cpp b/src/Components/Modules/ModList.cpp index 13f6c3ab..7782fb33 100644 --- a/src/Components/Modules/ModList.cpp +++ b/src/Components/Modules/ModList.cpp @@ -1,4 +1,5 @@ #include +#include "ModList.hpp" #include "UIFeeder.hpp" namespace Components diff --git a/src/Components/Modules/ModelSurfs.cpp b/src/Components/Modules/ModelSurfs.cpp index 4c5424a4..e7e888eb 100644 --- a/src/Components/Modules/ModelSurfs.cpp +++ b/src/Components/Modules/ModelSurfs.cpp @@ -1,4 +1,5 @@ #include +#include "ModelSurfs.hpp" namespace Components { diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 00c551a2..6e98e466 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -4,7 +4,7 @@ namespace Components { Utils::Signal Network::StartupSignal; // Packet interception - std::unordered_map Network::CL_Callbacks; + std::unordered_map Network::CL_Callbacks; Network::Address::Address() { @@ -269,21 +269,7 @@ namespace Components } } - void Network::SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg) - { - if (client->reliableAcknowledge < 0) - { - Logger::Print(Game::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n", - client->name, client->reliableSequence, client->reliableAcknowledge); - client->reliableAcknowledge = client->reliableSequence; - SendCommand(Game::NS_SERVER, client->header.netchan.remoteAddress, "error", "EXE_LOSTRELIABLECOMMANDS"); - return; - } - - Utils::Hook::Call(0x414D40)(client, msg); - } - - void Network::OnClientPacket(const std::string& command, const NetworkCallback& callback) + void Network::OnClientPacket(const std::string& command, const networkCallback& callback) { CL_Callbacks[Utils::String::ToLower(command)] = callback; } @@ -366,9 +352,6 @@ namespace Components // Prevent recvfrom error spam Utils::Hook(0x46531A, PacketErrorCheck, HOOK_JUMP).install()->quick(); - - // Fix server freezer exploit - Utils::Hook(0x626996, SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick(); // Handle client packets Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index bd55a5cc..fcd8262a 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -48,7 +48,7 @@ namespace Components typedef void(CallbackRaw)(); - using NetworkCallback = std::function; + using networkCallback = std::function; Network(); @@ -72,19 +72,17 @@ namespace Components static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data); static void BroadcastAll(const std::string& data); - static void OnClientPacket(const std::string& command, const NetworkCallback& callback); + static void OnClientPacket(const std::string& command, const networkCallback& callback); private: static Utils::Signal StartupSignal; - static std::unordered_map CL_Callbacks; + static std::unordered_map CL_Callbacks; static void NetworkStart(); static void NetworkStartStub(); static void PacketErrorCheck(); - static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg); - static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message); static void CL_HandleCommandStub(); diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index e79ead0d..39a4b4e6 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -2,8 +2,6 @@ #include "Changelog.hpp" #include "News.hpp" -#include - #define NEWS_MOTD_DEFAULT "Welcome to IW4x Multiplayer!" namespace Components diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 7c796801..cb122cc7 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -4,6 +4,7 @@ #include +#include "Node.hpp" #include "ServerList.hpp" #include "Session.hpp" diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index b77a70c3..c70231ac 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -1,8 +1,11 @@ #include #include +#include "Auth.hpp" #include "Download.hpp" +#include "Friends.hpp" #include "Gamepad.hpp" +#include "Node.hpp" #include "Party.hpp" #include "ServerList.hpp" #include "Stats.hpp" @@ -383,7 +386,7 @@ namespace Components info.set("bots", std::to_string(botCount)); info.set("sv_maxclients", std::to_string(maxClientCount)); info.set("protocol", std::to_string(PROTOCOL)); - info.set("shortversion", SHORTVERSION); + info.set("version", GIT_TAG); info.set("checksum", std::to_string(Game::Sys_Milliseconds())); info.set("mapname", Dvar::Var("mapname").get()); info.set("isPrivate", *password ? "1" : "0"); @@ -434,7 +437,7 @@ namespace Components info.set("wwwDownload", (Download::SV_wwwDownload.get() ? "1" : "0")); info.set("wwwUrl", Download::SV_wwwBaseUrl.get()); - Network::SendCommand(address, "infoResponse", "\\" + info.build()); + Network::SendCommand(address, "infoResponse", info.build()); }); Network::OnClientPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp index d03cc88f..826b97d5 100644 --- a/src/Components/Modules/PlayerName.cpp +++ b/src/Components/Modules/PlayerName.cpp @@ -1,31 +1,12 @@ #include #include "ClanTags.hpp" #include "PlayerName.hpp" +#include "TextRenderer.hpp" 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()) @@ -83,6 +64,26 @@ namespace Components return string; } + bool PlayerName::IsBadChar(int c) + { + if (c == '%') + { + return true; + } + + if (c == '~') + { + return true; + } + + if (c < 32 || c > 126) + { + return true; + } + + return false; + } + bool PlayerName::CopyClientNameCheck(char* dest, const char* source, int size) { Utils::Hook::Call(0x4D6F80)(dest, source, size); // I_strncpyz @@ -103,10 +104,15 @@ namespace Components return true; } + void PlayerName::DropClient(Game::client_t* drop) + { + const auto* reason = "Invalid name detected"; + Network::SendCommand(drop->header.netchan.remoteAddress, "error", reason); + Game::SV_DropClient(drop, reason, false); + } + __declspec(naked) void PlayerName::SV_UserinfoChangedStub() { - using namespace Game; - __asm { call CopyClientNameCheck @@ -116,11 +122,9 @@ namespace Components pushad - push 1 // tellThem - push INVALID_NAME_MSG // reason push edi // drop - call SV_DropClient - add esp, 0xC + call DropClient + add esp, 0x4 popad diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp index f216e14e..e88c3e99 100644 --- a/src/Components/Modules/PlayerName.hpp +++ b/src/Components/Modules/PlayerName.hpp @@ -13,15 +13,13 @@ namespace Components private: static Dvar::Var sv_allowColoredNames; - // 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(); + static bool IsBadChar(int c); static bool CopyClientNameCheck(char* dest, const char* source, int size); + static void DropClient(Game::client_t* drop); static void SV_UserinfoChangedStub(); }; } diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 41fc91b9..b4086ff9 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -2,6 +2,8 @@ #include #include "QuickPatch.hpp" +#include "TextRenderer.hpp" +#include "Toast.hpp" namespace Components { @@ -196,10 +198,14 @@ namespace Components void QuickPatch::CL_KeyEvent_OnEscape() { if (Game::Con_CancelAutoComplete()) + { return; + } if (TextRenderer::HandleFontIconAutocompleteKey(0, TextRenderer::FONT_ICON_ACI_CONSOLE, Game::K_ESCAPE)) + { return; + } // Close console Game::Key_RemoveCatcher(0, ~Game::KEYCATCH_CONSOLE); diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index bc046d7b..7a7ea662 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -1,6 +1,7 @@ #include #include "Gamepad.hpp" #include "RawMouse.hpp" +#include "Window.hpp" namespace Components { diff --git a/src/Components/Modules/Security.cpp b/src/Components/Modules/Security.cpp index a1854c30..eda2a36b 100644 --- a/src/Components/Modules/Security.cpp +++ b/src/Components/Modules/Security.cpp @@ -3,7 +3,7 @@ namespace Components { - int Security::MsgReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size) + int Security::Msg_ReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size) { static unsigned char buffer[0x8000]; @@ -16,7 +16,7 @@ namespace Components return size; } - int Security::MsgReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size) + int Security::Msg_ReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size) { static unsigned char buffer[0x100000]; @@ -29,7 +29,7 @@ namespace Components return size; } - int Security::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) + int Security::SV_CanReplaceServerCommand_Hk([[maybe_unused]] Game::client_t* client, [[maybe_unused]] const char* cmd) { // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them return -1; @@ -40,7 +40,7 @@ namespace Components return std::min(std::atol(string), 18); } - void Security::SelectStringTableEntryInDvarStub() + void Security::SelectStringTableEntryInDvar_Stub() { Command::ClientParams params; @@ -76,7 +76,7 @@ namespace Components Game::CL_SelectStringTableEntryInDvar_f(); } - __declspec(naked) int Security::G_GetClientScore() + __declspec(naked) int Security::G_GetClientScore_Hk() { __asm { @@ -96,12 +96,12 @@ namespace Components } } - void Security::G_LogPrintfStub(const char* fmt) + void Security::G_LogPrintf_Stub(const char* fmt) { Game::G_LogPrintf("%s", fmt); } - void Security::NET_DeferPacketToClientStub(Game::netadr_t* net_from, Game::msg_t* net_message) + void Security::NET_DeferPacketToClient_Hk(Game::netadr_t* net_from, Game::msg_t* net_message) { assert(net_from); assert(net_message); @@ -121,33 +121,53 @@ namespace Components InterlockedIncrement(&Game::deferredQueue->send); } + void Security::SV_ExecuteClientMessage_Stub(Game::client_t* client, Game::msg_t* msg) + { + if ((client->reliableSequence - client->reliableAcknowledge) < 0) + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n", + client->name, client->reliableSequence, client->reliableAcknowledge); + client->reliableAcknowledge = client->reliableSequence; + Game::SV_DropClient(client, "EXE_LOSTRELIABLECOMMANDS", true); + return; + } + + Utils::Hook::Call(0x414D40)(client, msg); + } + Security::Security() { // Exploit fixes - Utils::Hook(0x414D92, MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands - Utils::Hook(0x4A9F56, MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage - Utils::Hook(0x407376, SVCanReplaceServerCommand, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand + Utils::Hook(0x414D92, Msg_ReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands + Utils::Hook(0x4A9F56, Msg_ReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage + Utils::Hook(0x407376, SV_CanReplaceServerCommand_Hk, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand - Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient - Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket + Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient + Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f // Patch selectStringTableEntryInDvar - Utils::Hook::Set(0x405959, SelectStringTableEntryInDvarStub); + Utils::Hook::Set(0x405959, SelectStringTableEntryInDvar_Stub); // Patch G_GetClientScore for uninitialized game - Utils::Hook(0x469AC0, G_GetClientScore, HOOK_JUMP).install()->quick(); + Utils::Hook(0x469AC0, G_GetClientScore_Hk, HOOK_JUMP).install()->quick(); // Requests can be malicious Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest // Patch unsecure call to G_LogPrint inside GScr_LogPrint // This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt) - Utils::Hook(0x5F70B5, G_LogPrintfStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x5F70B5, G_LogPrintf_Stub, HOOK_CALL).install()->quick(); // Fix packets causing buffer overflow - Utils::Hook(0x6267E3, NET_DeferPacketToClientStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x6267E3, NET_DeferPacketToClient_Hk, HOOK_CALL).install()->quick(); + + // Fix server freezer exploit + Utils::Hook(0x626996, SV_ExecuteClientMessage_Stub, HOOK_CALL).install()->quick(); + + // The client can fake the info string + Utils::Hook::Set(0x460F6D, 0xEB); // SV_DirectConnect // Prevent curl 7_19_4 from running // Call to DL_Init from Live_Init diff --git a/src/Components/Modules/Security.hpp b/src/Components/Modules/Security.hpp index 081cbd90..d70d8537 100644 --- a/src/Components/Modules/Security.hpp +++ b/src/Components/Modules/Security.hpp @@ -7,20 +7,22 @@ namespace Components public: Security(); - static int MsgReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size); - static int MsgReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size); + static int Msg_ReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size); + static int Msg_ReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size); private: - static int SVCanReplaceServerCommand(Game::client_t* client, const char* cmd); + static int SV_CanReplaceServerCommand_Hk(Game::client_t* client, const char* cmd); static long AtolAdjustPlayerLimit(const char* string); - static void SelectStringTableEntryInDvarStub(); + static void SelectStringTableEntryInDvar_Stub(); - static int G_GetClientScore(); + static int G_GetClientScore_Hk(); - static void G_LogPrintfStub(const char* fmt); + static void G_LogPrintf_Stub(const char* fmt); - static void NET_DeferPacketToClientStub(Game::netadr_t* net_from, Game::msg_t* net_message); + static void NET_DeferPacketToClient_Hk(Game::netadr_t* net_from, Game::msg_t* net_message); + + static void SV_ExecuteClientMessage_Stub(Game::client_t* client, Game::msg_t* msg); }; } diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 7dd1ede3..b342a142 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -1,6 +1,7 @@ #include #include +#include "Friends.hpp" #include "Gamepad.hpp" #include "Party.hpp" #include "ServerInfo.hpp" @@ -86,7 +87,6 @@ namespace Components const auto* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); auto addressText = Network::Address(*Game::connectedHost).getString(); - if (addressText == "0.0.0.0:0"s || addressText == "loopback"s) { addressText = "Listen Server"s; @@ -149,7 +149,7 @@ namespace Components info.set("gamename", "IW4"); info.set("sv_maxclients", std::to_string(maxClientCount)); info.set("protocol", std::to_string(PROTOCOL)); - info.set("shortversion", SHORTVERSION); + info.set("version", GIT_TAG); info.set("version", (*Game::version)->current.string); info.set("mapname", (*Game::sv_mapname)->current.string); info.set("isPrivate", *password ? "1" : "0"); @@ -238,10 +238,10 @@ namespace Components name = namePtr; } - playerList.append(Utils::String::VA("%i %i \"%s\"\n", score, ping, name.data())); + playerList.append(std::format("{} {} \"{}\"\n", score, ping, name)); } - Network::SendCommand(address, "statusResponse", "\\" + info.build() + "\n" + playerList + "\n"); + Network::SendCommand(address, "statusResponse", info.build() + "\n"s + playerList + "\n"s); }); Network::OnClientPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) @@ -251,7 +251,13 @@ namespace Components return; } - const Utils::InfoString info(data.substr(0, data.find_first_of('\n'))); + const auto pos = data.find_first_of('\n'); + if (pos == std::string::npos) + { + return; + } + + const Utils::InfoString info(data.substr(0, pos)); Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname")); Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients")); @@ -302,13 +308,13 @@ namespace Components if (currentData.size() < 3) continue; // Insert score - player.score = atoi(currentData.substr(0, currentData.find_first_of(' ')).data()); + player.score = std::strtol(currentData.substr(0, currentData.find_first_of(' ')).data(), nullptr, 10); // Remove score currentData = currentData.substr(currentData.find_first_of(' ') + 1); // Insert ping - player.ping = atoi(currentData.substr(0, currentData.find_first_of(' ')).data()); + player.ping = std::strtol(currentData.substr(0, currentData.find_first_of(' ')).data(), nullptr, 10); // Remove ping currentData = currentData.substr(currentData.find_first_of(' ') + 1); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 2900c1ba..cca7be6e 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -2,12 +2,13 @@ #include #include "Discovery.hpp" +#include "Node.hpp" #include "Party.hpp" #include "ServerList.hpp" +#include "TextRenderer.hpp" +#include "Toast.hpp" #include "UIFeeder.hpp" -#include - namespace Components { bool ServerList::SortAsc = true; @@ -584,11 +585,7 @@ namespace Components } } - if (info.get("gamename") == "IW4"s && server.matchType -#if !defined(DEBUG) && defined(VERSION_FILTER) - && CompareVersion(server.shortversion, SHORTVERSION) -#endif - ) + if (info.get("gamename") == "IW4"s && server.matchType) { auto* lList = GetList(); if (lList) @@ -936,7 +933,7 @@ namespace Components UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { auto* serverInfo = GetCurrentServer(); - if (info) + if (info && serverInfo && serverInfo->addr.isValid()) { StoreFavourite(serverInfo->addr.getString()); } @@ -944,7 +941,11 @@ namespace Components UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - StoreFavourite(Dvar::Var("ui_favoriteAddress").get()); + const auto value = Dvar::Var("ui_favoriteAddress").get(); + if (!value.empty()) + { + StoreFavourite(value); + } }); UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) diff --git a/src/Components/Modules/Session.cpp b/src/Components/Modules/Session.cpp index 6058f334..77fbc22b 100644 --- a/src/Components/Modules/Session.cpp +++ b/src/Components/Modules/Session.cpp @@ -14,7 +14,7 @@ namespace Components Utils::Cryptography::ECC::Key Session::SignatureKey; - std::unordered_map Session::PacketHandlers; + std::unordered_map Session::PacketHandlers; std::queue> Session::SignatureQueue; @@ -61,7 +61,7 @@ namespace Components #endif } - void Session::Handle(const std::string& packet, const Network::NetworkCallback& callback) + void Session::Handle(const std::string& packet, const Network::networkCallback& callback) { #ifdef DISABLE_SESSION Network::OnClientPacket(packet, callback); diff --git a/src/Components/Modules/Session.hpp b/src/Components/Modules/Session.hpp index 181d9ce6..5a2cfbca 100644 --- a/src/Components/Modules/Session.hpp +++ b/src/Components/Modules/Session.hpp @@ -35,7 +35,7 @@ namespace Components void preDestroy() override; static void Send(const Network::Address& target, const std::string& command, const std::string& data = ""); - static void Handle(const std::string& packet, const Network::NetworkCallback& callback); + static void Handle(const std::string& packet, const Network::networkCallback& callback); private: static volatile bool Terminate; @@ -46,7 +46,7 @@ namespace Components static Utils::Cryptography::ECC::Key SignatureKey; - static std::unordered_map PacketHandlers; + static std::unordered_map PacketHandlers; static std::queue> SignatureQueue; diff --git a/src/Components/Modules/Singleton.cpp b/src/Components/Modules/Singleton.cpp index 218e6825..5b419c0e 100644 --- a/src/Components/Modules/Singleton.cpp +++ b/src/Components/Modules/Singleton.cpp @@ -18,7 +18,7 @@ namespace Components if (Flags::HasFlag("version")) { printf("%s", "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n"); - printf("%d\n", REVISION); + printf("Revision: %i\n", REVISION); ExitProcess(EXIT_SUCCESS); } diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp index 6c677179..dfc6d6f6 100644 --- a/src/Components/Modules/TextRenderer.cpp +++ b/src/Components/Modules/TextRenderer.cpp @@ -1,4 +1,5 @@ #include +#include "TextRenderer.hpp" namespace Game { diff --git a/src/Components/Modules/Toast.cpp b/src/Components/Modules/Toast.cpp index 80df444a..78c78ca0 100644 --- a/src/Components/Modules/Toast.cpp +++ b/src/Components/Modules/Toast.cpp @@ -1,5 +1,8 @@ #include +#include "Materials.hpp" +#include "Toast.hpp" + namespace Components { std::queue Toast::Queue; diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index 5ed7e497..395135dc 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -1,6 +1,7 @@ #include #include "FastFiles.hpp" +#include "Window.hpp" namespace Components { diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index c1b7a133..be5813b7 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -978,7 +978,7 @@ namespace Components } Logger::Print(" --------------------------------------------------------------------------------\n"); - Logger::Print(" IW4x ZoneBuilder ({})\n", VERSION); + Logger::Print(" IW4x ZoneBuilder - {}\n", VERSION); Logger::Print(" Commands:\n"); Logger::Print("\t-buildzone [zone]: builds a zone from a csv located in zone_source\n"); Logger::Print("\t-buildall: builds all zones in zone_source\n"); diff --git a/src/Game/Dvars.cpp b/src/Game/Dvars.cpp index 2a5b18a8..7731e674 100644 --- a/src/Game/Dvars.cpp +++ b/src/Game/Dvars.cpp @@ -49,6 +49,7 @@ namespace Game const dvar_t** fs_gameDirVar = reinterpret_cast(0x63D0CC0); const dvar_t** fs_homepath = reinterpret_cast(0x63D4FD8); + const dvar_t** sv_privatePassword = reinterpret_cast(0x62C7C14); const dvar_t** sv_hostname = reinterpret_cast(0x2098D98); const dvar_t** sv_gametype = reinterpret_cast(0x2098DD4); const dvar_t** sv_mapname = reinterpret_cast(0x2098DDC); @@ -71,6 +72,7 @@ namespace Game const dvar_t** g_oldVoting = reinterpret_cast(0x1A45DEC); const dvar_t** g_gametype = reinterpret_cast(0x1A45DC8); const dvar_t** g_password = reinterpret_cast(0x18835C0); + const dvar_t** g_log = reinterpret_cast(0x1A45D9C); const dvar_t** cg_chatHeight = reinterpret_cast(0x7ED398); const dvar_t** cg_chatTime = reinterpret_cast(0x9F5DE8); diff --git a/src/Game/Dvars.hpp b/src/Game/Dvars.hpp index 906b83e6..0abf921d 100644 --- a/src/Game/Dvars.hpp +++ b/src/Game/Dvars.hpp @@ -101,6 +101,7 @@ namespace Game extern const dvar_t** fs_gameDirVar; extern const dvar_t** fs_homepath; + extern const dvar_t** sv_privatePassword; extern const dvar_t** sv_hostname; extern const dvar_t** sv_gametype; extern const dvar_t** sv_mapname; @@ -123,6 +124,7 @@ namespace Game extern const dvar_t** g_oldVoting; extern const dvar_t** g_gametype; extern const dvar_t** g_password; + extern const dvar_t** g_log; extern const dvar_t** cg_chatHeight; extern const dvar_t** cg_chatTime; diff --git a/src/Resource.rc b/src/Resource.rc index cda9e955..9936e4b1 100644 --- a/src/Resource.rc +++ b/src/Resource.rc @@ -10,7 +10,7 @@ // // Generated from the TEXTINCLUDE 2 resource. // -#include "windows.h" +#include "Windows.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS @@ -29,7 +29,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 1 TEXTINCLUDE BEGIN - "#include ""windows.h""\r\n" + "#include ""Windows.h""\r\n" "\0" END @@ -47,8 +47,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_RC - PRODUCTVERSION VERSION_RC + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -63,18 +63,18 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "CompanyName", "IW4x" + VALUE "CompanyName", "XLabsProject" #ifdef _DEBUG VALUE "FileDescription", "IW4 client modification (DEBUG)" #else VALUE "FileDescription", "IW4 client modification" #endif - VALUE "FileVersion", SHORTVERSION + VALUE "FileVersion", GIT_TAG VALUE "InternalName", "iw4x" - VALUE "LegalCopyright", "Copyright 2022 The IW4x Team. All rights reserved." + VALUE "LegalCopyright", "Copyright 2023 The XLabsProject Team. All rights reserved." VALUE "OriginalFilename", "iw4x.dll" VALUE "ProductName", "IW4x" - VALUE "ProductVersion", SHORTVERSION + VALUE "ProductVersion", GIT_TAG END END BLOCK "VarFileInfo" diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 4009d601..aa045a9f 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Steam/Interfaces/SteamUser.cpp b/src/Steam/Interfaces/SteamUser.cpp index 26f8ce43..46b42887 100644 --- a/src/Steam/Interfaces/SteamUser.cpp +++ b/src/Steam/Interfaces/SteamUser.cpp @@ -1,4 +1,5 @@ #include +#include "Components/Modules/Auth.hpp" STEAM_IGNORE_WARNINGS_START diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index 8209f152..7d003641 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -188,7 +188,7 @@ namespace Utils std::string hash(reinterpret_cast(buffer), sizeof(buffer)); if (!hex) return hash; - return String::DumpHex(hash, ""); + return String::DumpHex(hash, {}); } #pragma endregion @@ -212,7 +212,7 @@ namespace Utils std::string hash(reinterpret_cast(buffer), sizeof(buffer)); if (!hex) return hash; - return String::DumpHex(hash, ""); + return String::DumpHex(hash, {}); } #pragma endregion @@ -236,7 +236,7 @@ namespace Utils std::string hash(reinterpret_cast(buffer), sizeof(buffer)); if (!hex) return hash; - return String::DumpHex(hash, ""); + return String::DumpHex(hash, {}); } #pragma endregion @@ -260,7 +260,7 @@ namespace Utils std::string hash(reinterpret_cast(buffer), sizeof(buffer)); if (!hex) return hash; - return String::DumpHex(hash, ""); + return String::DumpHex(hash, {}); } #pragma endregion @@ -272,7 +272,7 @@ namespace Utils return Compute(data.data(), data.size()); } - unsigned int JenkinsOneAtATime::Compute(const char *key, std::size_t len) + unsigned int JenkinsOneAtATime::Compute(const char* key, std::size_t len) { unsigned int hash, i; for (hash = i = 0; i < len; ++i) diff --git a/src/Utils/Cryptography.hpp b/src/Utils/Cryptography.hpp index 09f66010..f3b48560 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -335,7 +335,7 @@ namespace Utils { public: static unsigned int Compute(const std::string& data); - static unsigned int Compute(const char *key, std::size_t len); + static unsigned int Compute(const char* key, std::size_t len); }; } } diff --git a/src/Utils/InfoString.cpp b/src/Utils/InfoString.cpp index 523d7c0e..0dbd6429 100644 --- a/src/Utils/InfoString.cpp +++ b/src/Utils/InfoString.cpp @@ -10,17 +10,17 @@ namespace Utils void InfoString::set(const std::string& key, const std::string& value) { - this->keyValuePairs[key] = value; + this->keyValuePairs_[key] = value; } void InfoString::remove(const std::string& key) { - this->keyValuePairs.erase(key); + this->keyValuePairs_.erase(key); } std::string InfoString::get(const std::string& key) const { - if (const auto value = this->keyValuePairs.find(key); value != this->keyValuePairs.end()) + if (const auto value = this->keyValuePairs_.find(key); value != this->keyValuePairs_.end()) { return value->second; } @@ -35,13 +35,16 @@ namespace Utils buffer = buffer.substr(1); } - const auto keyValues = Utils::String::Split(buffer, '\\'); - + const auto keyValues = String::Split(buffer, '\\'); for (std::size_t i = 0; !keyValues.empty() && i < (keyValues.size() - 1); i += 2) { const auto& key = keyValues[i]; const auto& value = keyValues[i + 1]; - this->keyValuePairs[key] = value; + + if (!this->keyValuePairs_.contains(key)) + { + this->keyValuePairs_[key] = value; + } } } @@ -50,8 +53,7 @@ namespace Utils std::string infoString; auto first = true; - - for (const auto& [key, value] : this->keyValuePairs) + for (const auto& [key, value] : this->keyValuePairs_) { if (first) first = false; else infoString.append("\\"); @@ -67,7 +69,7 @@ namespace Utils #ifdef _DEBUG void InfoString::dump() { - for (const auto& [key, value] : this->keyValuePairs) + for (const auto& [key, value] : this->keyValuePairs_) { OutputDebugStringA(String::VA("%s: %s\n", key.data(), value.data())); } @@ -76,6 +78,6 @@ namespace Utils nlohmann::json InfoString::to_json() const { - return this->keyValuePairs; + return this->keyValuePairs_; } } diff --git a/src/Utils/InfoString.hpp b/src/Utils/InfoString.hpp index 3229eebb..ff0243ba 100644 --- a/src/Utils/InfoString.hpp +++ b/src/Utils/InfoString.hpp @@ -21,7 +21,8 @@ namespace Utils [[nodiscard]] nlohmann::json to_json() const; private: - std::unordered_map keyValuePairs; + std::unordered_map keyValuePairs_; + void parse(std::string buffer); }; }