From c71eddc5a9b7f46c937e1eafc64e32fafce477af Mon Sep 17 00:00:00 2001 From: Edo Date: Thu, 9 Mar 2023 18:39:10 +0000 Subject: [PATCH] [ServerList]: Check for duplicates (#820) --- src/Components/Modules/Auth.cpp | 2 +- src/Components/Modules/Bots.cpp | 12 +- src/Components/Modules/GSC/Script.cpp | 2 +- src/Components/Modules/GSC/Script.hpp | 2 +- src/Components/Modules/GSC/ScriptPatches.cpp | 2 +- src/Components/Modules/GSC/ScriptStorage.cpp | 8 +- src/Components/Modules/GSC/UserInfo.cpp | 4 +- src/Components/Modules/Logger.cpp | 2 +- src/Components/Modules/Security.cpp | 2 +- src/Components/Modules/ServerList.cpp | 218 +++++++++---------- src/Components/Modules/ServerList.hpp | 25 ++- src/DllMain.cpp | 2 +- src/STDInclude.hpp | 1 + src/Steam/Interfaces/SteamUser.cpp | 4 +- src/Utils/Cryptography.cpp | 12 +- 15 files changed, 160 insertions(+), 138 deletions(-) diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index c0ada431..c0181db1 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -471,7 +471,7 @@ namespace Components } else { - const auto level = static_cast(atoi(params->get(1))); + const auto level = std::strtoul(params->get(1), nullptr, 10); Auth::IncreaseSecurityLevel(level); } }); diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index d7843aa9..590af1f7 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -167,8 +167,10 @@ namespace Components Scheduler::Once([] { auto* ent = Game::SV_AddTestClient(); - if (ent == nullptr) + if (!ent) + { return; + } Scheduler::Once([ent] { @@ -232,7 +234,7 @@ namespace Components const auto* weapon = Game::Scr_GetString(0); - if (weapon == nullptr || weapon[0] == '\0') + if (!weapon || !*weapon) { g_botai[entref.entnum].weapon = 1; return; @@ -255,7 +257,7 @@ namespace Components const auto* action = Game::Scr_GetString(0); - if (action == nullptr) + if (!action) { Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!"); return; @@ -305,8 +307,10 @@ namespace Components void Bots::BotAiAction(Game::client_t* cl) { - if (cl->gentity == nullptr) + if (!cl->gentity) + { return; + } const auto entnum = cl->gentity->s.number; diff --git a/src/Components/Modules/GSC/Script.cpp b/src/Components/Modules/GSC/Script.cpp index f790c28f..2a0d2317 100644 --- a/src/Components/Modules/GSC/Script.cpp +++ b/src/Components/Modules/GSC/Script.cpp @@ -205,7 +205,7 @@ namespace Components::GSC { assert(ent); - if (ent->client == nullptr) + if (!ent->client) { Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); return nullptr; diff --git a/src/Components/Modules/GSC/Script.hpp b/src/Components/Modules/GSC/Script.hpp index 09474aed..b8b83e0c 100644 --- a/src/Components/Modules/GSC/Script.hpp +++ b/src/Components/Modules/GSC/Script.hpp @@ -28,7 +28,7 @@ namespace Components::GSC assert(entref.entnum < Game::MAX_GENTITIES); auto* ent = &Game::g_entities[entref.entnum]; - if (ent->client == nullptr) + if (!ent->client) { Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum)); return nullptr; diff --git a/src/Components/Modules/GSC/ScriptPatches.cpp b/src/Components/Modules/GSC/ScriptPatches.cpp index 45709fa7..94f4b30a 100644 --- a/src/Components/Modules/GSC/ScriptPatches.cpp +++ b/src/Components/Modules/GSC/ScriptPatches.cpp @@ -34,7 +34,7 @@ namespace Components::GSC const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable; - if (table == nullptr) + if (!table) { Game::Scr_ParamError(0, Utils::String::VA("%s does not exist", fileName)); return; diff --git a/src/Components/Modules/GSC/ScriptStorage.cpp b/src/Components/Modules/GSC/ScriptStorage.cpp index c2cecdf5..b670ff51 100644 --- a/src/Components/Modules/GSC/ScriptStorage.cpp +++ b/src/Components/Modules/GSC/ScriptStorage.cpp @@ -14,7 +14,7 @@ namespace Components::GSC const auto* key = Game::Scr_GetString(0); const auto* value = Game::Scr_GetString(1); - if (key == nullptr || value == nullptr) + if (!key || !value) { Game::Scr_Error("^1StorageSet: Illegal parameters!"); return; @@ -27,7 +27,7 @@ namespace Components::GSC { const auto* key = Game::Scr_GetString(0); - if (key == nullptr) + if (!key) { Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!"); return; @@ -46,7 +46,7 @@ namespace Components::GSC { const auto* key = Game::Scr_GetString(0); - if (key == nullptr) + if (!key) { Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!"); return; @@ -65,7 +65,7 @@ namespace Components::GSC { const auto* key = Game::Scr_GetString(0); - if (key == nullptr) + if (!key) { Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!"); return; diff --git a/src/Components/Modules/GSC/UserInfo.cpp b/src/Components/Modules/GSC/UserInfo.cpp index d3ec1d21..5cd52b19 100644 --- a/src/Components/Modules/GSC/UserInfo.cpp +++ b/src/Components/Modules/GSC/UserInfo.cpp @@ -52,7 +52,7 @@ namespace Components::GSC const auto* ent = Game::GetPlayerEntity(entref); const auto* name = Game::Scr_GetString(0); - if (name == nullptr) + if (!name) { Game::Scr_ParamError(0, "^1SetName: Illegal parameter!"); return; @@ -77,7 +77,7 @@ namespace Components::GSC const auto* ent = Game::GetPlayerEntity(entref); const auto* clanName = Game::Scr_GetString(0); - if (clanName == nullptr) + if (!clanName) { Game::Scr_ParamError(0, "^1SetClanTag: Illegal parameter!"); return; diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index c6db9af7..8d9cdc99 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -96,7 +96,7 @@ namespace Components ++(*Game::com_errorPrintsCount); MessagePrint(channel, msg); - if (Game::cls->uiStarted != 0 && (*Game::com_fixedConsolePosition == 0)) + if (Game::cls->uiStarted && (*Game::com_fixedConsolePosition == 0)) { Game::CL_ConsoleFixPosition(); } diff --git a/src/Components/Modules/Security.cpp b/src/Components/Modules/Security.cpp index e374a1c1..a1854c30 100644 --- a/src/Components/Modules/Security.cpp +++ b/src/Components/Modules/Security.cpp @@ -55,7 +55,7 @@ namespace Components } const auto* dvar = Game::Dvar_FindVar(name); - if (dvar == nullptr) + if (!dvar) { // If it's not a dvar let it continue Game::CL_SelectStringTableEntryInDvar_f(); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index b66c9811..ba51b830 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -194,7 +194,7 @@ namespace Components auto* list = GetList(); if (!list) return; - std::vector tempList(*list); + const std::vector tempList(*list); if (tempList.empty()) { @@ -209,7 +209,7 @@ namespace Components RefreshContainer.sendCount = 0; RefreshContainer.sentCount = 0; - for (auto& server : tempList) + for (const auto& server : tempList) { InsertRequest(server.addr); } @@ -274,13 +274,7 @@ namespace Components void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Dvar::Var("ui_serverSelected").set(false); - //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); -#if 0 - OnlineList.clear(); - OfflineList.clear(); - FavouriteList.clear(); -#endif auto* list = GetList(); if (list) list->clear(); @@ -320,7 +314,7 @@ namespace Components RefreshContainer.awaitTime = Game::Sys_Milliseconds(); RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort)); - Logger::Print("Sending serverlist request to master\n"); + Logger::Print("Sending server list request to master\n"); Network::SendCommand(RefreshContainer.host, "getservers", std::format("IW4 {} full empty", PROTOCOL)); } else if (IsFavouriteList()) @@ -417,8 +411,6 @@ namespace Components if (list) list->clear(); RefreshVisibleListInternal(UIScript::Token(), nullptr); - - Game::ShowMessageBox("Server removed from favourites.", "Success"); } void ServerList::LoadFavourties() @@ -471,7 +463,7 @@ namespace Components container.sent = false; container.target = address; - bool alreadyInserted = false; + auto alreadyInserted = false; for (auto &server : RefreshContainer.servers) { if (server.target == container.target) @@ -510,99 +502,104 @@ namespace Components for (auto i = RefreshContainer.servers.begin(); i != RefreshContainer.servers.end();) { // Our desired server - if ((i->target == address) && i->sent) + if ((i->target != address) || !i->sent) { - // Challenge did not match - if (i->challenge != info.get("challenge")) + ++i; + continue; + } + + // Challenge did not match + if (i->challenge != info.get("challenge")) + { + // Shall we remove the server from the queue? + // Better not, it might send a second response with the correct challenge. + // This might happen when users refresh twice (or more often) in a short period of time + break; + } + + ServerInfo server; + server.hostname = info.get("hostname"); + server.mapname = info.get("mapname"); + server.gametype = info.get("gametype"); + server.shortversion = info.get("shortversion"); + server.mod = info.get("fs_game"); + server.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10); + server.clients = std::strtol(info.get("clients").data(), nullptr, 10); + server.bots = std::strtol(info.get("bots").data(), nullptr, 10); + server.securityLevel = std::strtol(info.get("securityLevel").data(), nullptr, 10); + server.maxClients = std::strtol(info.get("sv_maxclients").data(), nullptr, 10); + server.password = info.get("isPrivate") == "1"s; + server.aimassist = info.get("aimAssist") == "1"; + server.voice = info.get("voiceChat") == "1"s; + server.hardcore = info.get("hc") == "1"s; + server.svRunning = info.get("sv_running") == "1"s; + server.ping = (Game::Sys_Milliseconds() - i->sendTime); + server.addr = address; + + std::hash hashFn; + server.hash = hashFn(server); + + server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname); + server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname); + server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype); + server.mod = TextRenderer::StripMaterialTextIcons(server.mod); + + // Remove server from queue + i = RefreshContainer.servers.erase(i); + + // Servers with more than 18 players or less than 0 players are faking for sure + // So lets ignore those + if (static_cast(server.clients) > Game::MAX_CLIENTS || static_cast(server.maxClients) > Game::MAX_CLIENTS) + { + return; + } + + // Check if already inserted and remove + auto* list = GetList(); + if (!list) return; + + std::size_t k = 0; + for (auto j = list->begin(); j != list->end(); ++k) + { + if (j->addr == address) { - // Shall we remove the server from the queue? - // Better not, it might send a second response with the correct challenge. - // This might happen when users refresh twice (or more often) in a short period of time - break; + j = list->erase(j); } - - ServerInfo server; - server.hostname = info.get("hostname"); - server.mapname = info.get("mapname"); - server.gametype = info.get("gametype"); - server.shortversion = info.get("shortversion"); - server.mod = info.get("fs_game"); - server.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10); - server.clients = std::strtol(info.get("clients").data(), nullptr, 10); - server.bots = std::strtol(info.get("bots").data(), nullptr, 10); - server.securityLevel = std::strtol(info.get("securityLevel").data(), nullptr, 10); - server.maxClients = std::strtol(info.get("sv_maxclients").data(), nullptr, 10); - server.password = info.get("isPrivate") == "1"s; - server.aimassist = info.get("aimAssist") == "1"; - server.voice = info.get("voiceChat") == "1"s; - server.hardcore = info.get("hc") == "1"s; - server.svRunning = info.get("sv_running") == "1"s; - server.ping = (Game::Sys_Milliseconds() - i->sendTime); - server.addr = address; - - server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname); - server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname); - server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype); - server.mod = TextRenderer::StripMaterialTextIcons(server.mod); - - // Remove server from queue - i = RefreshContainer.servers.erase(i); - - // Servers with more than 18 players or less than 0 players are faking for sure - // So lets ignore those - if (server.clients > 18 || server.maxClients > 18 || server.clients < 0 || server.maxClients < 0) + else { - return; + ++j; } + } - // Check if already inserted and remove - auto* list = GetList(); - if (!list) return; - - std::size_t k = 0; - for (auto j = list->begin(); j != list->end(); ++k) + // Also remove from visible list + for (auto j = VisibleList.begin(); j != VisibleList.end();) + { + if (*j == k) { - if (j->addr == address) - { - j = list->erase(j); - } - else - { - ++j; - } + j = VisibleList.erase(j); } - - // Also remove from visible list - for (auto j = VisibleList.begin(); j != VisibleList.end();) + else { - if (*j == k) - { - j = VisibleList.erase(j); - } - else - { - ++j; - } + ++j; } + } - if (info.get("gamename") == "IW4"s && server.matchType + if (info.get("gamename") == "IW4"s && server.matchType #if !defined(DEBUG) && defined(VERSION_FILTER) - && CompareVersion(server.shortversion, SHORTVERSION) + && CompareVersion(server.shortversion, SHORTVERSION) #endif - ) + ) + { + auto* lList = GetList(); + if (lList) { - auto* lList = GetList(); - if (lList) + if (!IsServerDuplicate(lList, server)) { lList->push_back(server); RefreshVisibleListInternal(UIScript::Token(), nullptr); } } } - else - { - ++i; - } } } @@ -626,7 +623,7 @@ namespace Components } catch (const std::exception& ex) { - Logger::Warning(Game::CON_CHANNEL_CONSOLEONLY, "{} while performing numeric comparison between {} and {}\n", ex.what(), subVersions1[i], subVersions2[i]); + Logger::PrintError(Game::CON_CHANNEL_ERROR, "{} while performing numeric comparison between {} and {}\n", ex.what(), subVersions1[i], subVersions2[i]); return false; } } @@ -634,6 +631,19 @@ namespace Components return true; } + bool ServerList::IsServerDuplicate(const std::vector* list, const ServerInfo& server) + { + for (auto l = list->begin(); l != list->end(); ++l) + { + if (l->hash == server.hash) + { + return true; + } + } + + return false; + } + ServerList::ServerInfo* ServerList::GetCurrentServer() { return GetServer(CurrentServer); @@ -730,6 +740,7 @@ namespace Components } } + const auto challenge = Utils::Cryptography::Rand::GenerateChallenge(); auto requestLimit = NETServerQueryLimit.get(); for (std::size_t i = 0; i < RefreshContainer.servers.size() && requestLimit > 0; ++i) { @@ -741,14 +752,11 @@ namespace Components requestLimit--; server->sendTime = Game::Sys_Milliseconds(); - server->challenge = Utils::Cryptography::Rand::GenerateChallenge(); + server->challenge = challenge; ++RefreshContainer.sentCount; Network::SendCommand(server->target, "getinfo", server->challenge); - - // Display in the menu, like in CoD4 - Disabled to avoid spamming? - //Localization::Set("MPUI_SERVERQUERIED", Utils::String::VA("Sent requests: %d/%d", ServerList::RefreshContainer.sentCount, ServerList::RefreshContainer.sendCount)); } UpdateVisibleInfo(); @@ -784,17 +792,17 @@ namespace Components void ServerList::UpdateVisibleInfo() { - static int servers = 0; - static int players = 0; - static int bots = 0; + static auto servers = 0; + static auto players = 0; + static auto bots = 0; - auto list = GetList(); + auto* list = GetList(); if (list) { - int newSevers = list->size(); - int newPlayers = 0; - int newBots = 0; + auto newSevers = static_cast(list->size()); + auto newPlayers = 0; + auto newBots = 0; for (std::size_t i = 0; i < list->size(); ++i) { @@ -808,7 +816,7 @@ namespace Components players = newPlayers; bots = newBots; - Localization::Set("MPUI_SERVERQUERIED", Utils::String::VA("Servers: %i\nPlayers: %i (%i)", servers, players, bots)); + Localization::Set("MPUI_SERVERQUERIED", std::format("Servers: {}\nPlayers: {} ({})", servers, players, bots)); } } } @@ -960,18 +968,6 @@ namespace Components } }); -#ifdef _DEBUG - Command::Add("playerCount", [](Command::Params*) - { - auto count = 0; - for (const auto& server : OnlineList) - { - count += server.clients; - } - - Logger::Debug("There are {} players playing", count); - }); -#endif // Add required ownerDraws UIScript::AddOwnerDraw(220, UpdateSource); UIScript::AddOwnerDraw(253, UpdateGameType); @@ -980,7 +976,7 @@ namespace Components Scheduler::Loop(Frame, Scheduler::Pipeline::CLIENT); } - ServerList::~ServerList() + void ServerList::preDestroy() { std::lock_guard _(RefreshContainer.mutex); RefreshContainer.awatingList = false; diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 1bbe90d5..c21f874a 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -10,15 +10,15 @@ namespace Components public: typedef int(SortCallback)(const void*, const void*); - class ServerInfo + struct ServerInfo { - public: Network::Address addr; std::string hostname; std::string mapname; std::string gametype; std::string mod; std::string shortversion; + std::size_t hash; int clients; int bots; int maxClients; @@ -33,7 +33,8 @@ namespace Components }; ServerList(); - ~ServerList(); + + void preDestroy() override; static void Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static void RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); @@ -138,6 +139,7 @@ namespace Components static ServerInfo* GetServer(unsigned int index); static bool CompareVersion(const std::string& version1, const std::string& version2); + static bool IsServerDuplicate(const std::vector* list, const ServerInfo& server); static int SortKey; static bool SortAsc; @@ -159,3 +161,20 @@ namespace Components static bool IsServerListOpen(); }; } + +template <> +struct std::hash +{ + std::size_t operator()(const Components::ServerList::ServerInfo& x) const noexcept + { + std::size_t hash = 0; + + hash ^= std::hash()(x.hostname); + hash ^= std::hash()(x.mapname); + hash ^= std::hash()(x.mod); + hash ^= std::hash()(*reinterpret_cast(&x.addr.getIP().bytes[0]));; + hash ^= x.clients; + + return hash; + } +}; diff --git a/src/DllMain.cpp b/src/DllMain.cpp index df8ec984..35281e3d 100644 --- a/src/DllMain.cpp +++ b/src/DllMain.cpp @@ -56,7 +56,7 @@ BOOL APIENTRY DllMain(HINSTANCE /*hinstDLL*/, DWORD fdwReason, LPVOID /*lpvReser #ifndef DEBUG_BINARY_CHECK const auto* binary = reinterpret_cast(0x6F9358); - if (binary == nullptr || std::memcmp(binary, BASEGAME_NAME, 14) != 0) + if (!binary || std::memcmp(binary, BASEGAME_NAME, 14) != 0) #endif { MessageBoxA(nullptr, diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index dd07cf6e..7bd49baa 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Steam/Interfaces/SteamUser.cpp b/src/Steam/Interfaces/SteamUser.cpp index a61a2e2f..26f8ce43 100644 --- a/src/Steam/Interfaces/SteamUser.cpp +++ b/src/Steam/Interfaces/SteamUser.cpp @@ -22,11 +22,11 @@ namespace Steam if (!idBits) { - if (Components::Dedicated::IsEnabled() || Components::ZoneBuilder::IsEnabled()) // Dedi guid + if (Components::ZoneBuilder::IsEnabled()) { idBits = *reinterpret_cast(const_cast("DEDICATE")); } - else if (Components::Singleton::IsFirstInstance()) // ECDSA guid + else if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid { idBits = Components::Auth::GetKeyHash(); } diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index c2e515cc..8209f152 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -18,12 +18,14 @@ namespace Utils std::string Rand::GenerateChallenge() { - std::string challenge; - challenge.append(String::VA("%X", GenerateInt())); - challenge.append(String::VA("%X", ~timeGetTime() ^ GenerateInt())); - challenge.append(String::VA("%X", GenerateInt())); + char buffer[512]{}; + int buffer_pos = 0; - return challenge; + buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", GenerateInt()); + buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", ~timeGetTime() ^ GenerateInt()); + buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", GenerateInt()); + + return std::string(buffer, static_cast(buffer_pos)); } std::uint32_t Rand::GenerateInt()