From 769f6f8cc69f554acbecc1769b7a00e86664cae8 Mon Sep 17 00:00:00 2001 From: Fernando-A-Rocha Date: Wed, 8 Jan 2025 13:37:24 +0000 Subject: [PATCH 1/5] Parsing of rules in server browser --- Client/core/CQueryReceiver.cpp | 23 +++++ Client/core/CQueryReceiver.h | 1 + Server/mods/deathmatch/logic/ASE.cpp | 131 ++++++++++++++++++++++++++- Server/mods/deathmatch/logic/ASE.h | 8 ++ 4 files changed, 161 insertions(+), 2 deletions(-) diff --git a/Client/core/CQueryReceiver.cpp b/Client/core/CQueryReceiver.cpp index 4d1e516de91..2be9c32420f 100644 --- a/Client/core/CQueryReceiver.cpp +++ b/Client/core/CQueryReceiver.cpp @@ -192,6 +192,29 @@ SQueryInfo CQueryReceiver::GetServerResponse() if (!strHttpPort.empty()) info.httpPort = atoi(strHttpPort); + // Check if this reply includes rules + if (strncmp(szBuffer + i, "RULES", 5) == 0) + { + i += 5; + while (i < len) + { + // Check if it's the end of rules + if ((unsigned char)szBuffer[i] == 1) + { + i++; + break; + } + + SString key, value; + if (!ReadString(key, szBuffer, i, len)) + return info; + if (!ReadString(value, szBuffer, i, len)) + return info; + + info.rules[key] = value; + } + } + // Get player nicks while (i < len) { diff --git a/Client/core/CQueryReceiver.h b/Client/core/CQueryReceiver.h index 4bc56b90d7e..58e77bf601d 100644 --- a/Client/core/CQueryReceiver.h +++ b/Client/core/CQueryReceiver.h @@ -47,6 +47,7 @@ struct SQueryInfo ushort pingTime; std::vector playersPool; + std::unordered_map rules; }; class CQueryReceiver diff --git a/Server/mods/deathmatch/logic/ASE.cpp b/Server/mods/deathmatch/logic/ASE.cpp index de9da405b9b..5449c5e3d84 100644 --- a/Server/mods/deathmatch/logic/ASE.cpp +++ b/Server/mods/deathmatch/logic/ASE.cpp @@ -187,8 +187,8 @@ void ASE::DoPulse() break; } case 'r': - { // Our own lighter query for ingame browser - Release version only - strReply = QueryLightCached(); + { // New query for ingame server browser + strReply = QueryNewBrowserCached(); break; } case 'x': @@ -395,6 +395,133 @@ const std::string* ASE::QueryLightCached() return &m_strLightCached; } +// Protect against a flood of server queries. +// Send cached version unless player count has changed, or last re-cache is older than m_lNewMinInterval +const std::string* ASE::QueryNewBrowserCached() +{ + if (m_uiCurrentPlayerCount != m_uiNewLastPlayerCount || m_llCurrentTime - m_llNewLastTime > m_lNewMinInterval || m_strNewCached == "") + { + m_strNewCached = QueryNewBrowser(); + m_llNewLastTime = m_llCurrentTime; + m_uiNewLastPlayerCount = m_uiCurrentPlayerCount; + } + return &m_strNewCached; +} + +std::string ASE::QueryNewBrowser() +{ + std::stringstream reply; + + int iJoinedPlayers = m_pPlayerManager->CountJoined(); + int iMaxPlayers = m_pMainConfig->GetMaxPlayers(); + SString strPlayerCount = SString("%d/%d", iJoinedPlayers, iMaxPlayers); + SString strBuildType = SString("%d", MTASA_VERSION_TYPE); + SString strBuildNumber = SString("%d", MTASA_VERSION_BUILD); + SFixedString<32> strPingStatusFixed; + SFixedString<32> strNetRouteFixed; + g_pNetServer->GetPingStatus(&strPingStatusFixed); + g_pNetServer->GetNetRoute(&strNetRouteFixed); + SString strPingStatus = (const char*)strPingStatusFixed; + SString strNetRoute = (const char*)strNetRouteFixed; + SString strUpTime("%d", (uint)(time(NULL) - m_tStartTime)); + SString strHttpPort("%d", m_pMainConfig->GetHTTPPort()); + + uint uiExtraDataLength = (strPlayerCount.length() + 1 + strBuildType.length() + 1 + strBuildNumber.length() + 1 + strPingStatus.length() + 1 + + strNetRoute.length() + 1 + strUpTime.length() + 1 + strHttpPort.length() + 1); + uint uiMaxMapNameLength = 250 - uiExtraDataLength; + m_strMapName = m_strMapName.Left(uiMaxMapNameLength); + + reply << "EYE2"; + // game + reply << (unsigned char)4; + reply << "mta"; + // port + reply << (unsigned char)(m_strPort.length() + 1); + reply << m_strPort; + // server name + reply << (unsigned char)(m_pMainConfig->GetServerName().length() + 1); + reply << m_pMainConfig->GetServerName(); + // game type + reply << (unsigned char)(m_strGameType.length() + 1); + reply << m_strGameType; + // map name with backwardly compatible large player count, build type and build number + reply << (unsigned char)(m_strMapName.length() + 1 + uiExtraDataLength); + reply << m_strMapName; + reply << (unsigned char)0; + reply << strPlayerCount; + reply << (unsigned char)0; + reply << strBuildType; + reply << (unsigned char)0; + reply << strBuildNumber; + reply << (unsigned char)0; + reply << strPingStatus; + reply << (unsigned char)0; + reply << strNetRoute; + reply << (unsigned char)0; + reply << strUpTime; + reply << (unsigned char)0; + reply << strHttpPort; + // version + std::string temp = MTA_DM_ASE_VERSION; + reply << (unsigned char)(temp.length() + 1); + reply << temp; + // passworded + reply << (unsigned char)((m_pMainConfig->HasPassword()) ? 1 : 0); + // serial verification? + reply << (unsigned char)((m_pMainConfig->GetSerialVerificationEnabled()) ? 1 : 0); + // players count + reply << (unsigned char)std::min(iJoinedPlayers, 255); + // players max + reply << (unsigned char)std::min(iMaxPlayers, 255); + + // rules - this informs this response contains them + // previous version of this query did not have rules + reply << "RULES"; + + // send a max of 20 rules + list::iterator rIter = IterBegin(); + int rulesCount = 0; + for (; rIter != IterEnd() && rulesCount < 20; rIter++) + { + reply << (unsigned char)(strlen((*rIter)->GetKey()) + 1); + reply << (*rIter)->GetKey(); + reply << (unsigned char)(strlen((*rIter)->GetValue()) + 1); + reply << (*rIter)->GetValue(); + rulesCount++; + } + reply << (unsigned char)1; + + // players + CPlayer* pPlayer = NULL; + + // Keep the packet under 1350 bytes to try to avoid fragmentation + int iBytesLeft = 1340 - (int)reply.tellp(); + int iPlayersLeft = iJoinedPlayers; + + list::const_iterator pIter = m_pPlayerManager->IterBegin(); + for (; pIter != m_pPlayerManager->IterEnd(); pIter++) + { + pPlayer = *pIter; + if (pPlayer->IsJoined()) + { + // nick + std::string strPlayerName = RemoveColorCodes(pPlayer->GetNick()); + if (strPlayerName.length() == 0) + strPlayerName = pPlayer->GetNick(); + + // Check if we can fit more names + iBytesLeft -= strPlayerName.length() + 1; + if (iBytesLeft < iPlayersLeft--) + strPlayerName = ""; + + reply << (unsigned char)(strPlayerName.length() + 1); + reply << strPlayerName.c_str(); + } + } + + return reply.str(); +} + std::string ASE::QueryLight() { std::stringstream reply; diff --git a/Server/mods/deathmatch/logic/ASE.h b/Server/mods/deathmatch/logic/ASE.h index c517ddfb88b..54d39012c9d 100644 --- a/Server/mods/deathmatch/logic/ASE.h +++ b/Server/mods/deathmatch/logic/ASE.h @@ -84,6 +84,8 @@ class ASE const std::string* QueryFullCached(); std::string QueryFull(); const std::string* QueryLightCached(); + std::string QueryNewBrowser(); + const std::string* QueryNewBrowserCached(); const std::string* QueryXfireLightCached(); std::string QueryXfireLight(); @@ -120,6 +122,12 @@ class ASE long m_lLightMinInterval; std::string m_strLightCached; + // New query cache + unsigned int m_uiNewLastPlayerCount; + long long m_llNewLastTime; + long m_lNewMinInterval; + std::string m_strNewCached; + // XFire Light query cache unsigned int m_uiXfireLightLastPlayerCount; long long m_llXfireLightLastTime; From b796e17b5cde6f34659d6d9625effcf395248c6f Mon Sep 17 00:00:00 2001 From: Fernando-A-Rocha Date: Wed, 8 Jan 2025 13:37:43 +0000 Subject: [PATCH 2/5] Fix CMainConfig::AddMissingSettings --- Server/mods/deathmatch/logic/CMainConfig.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Server/mods/deathmatch/logic/CMainConfig.cpp b/Server/mods/deathmatch/logic/CMainConfig.cpp index ec5bf9b9d9d..038ee65ec86 100644 --- a/Server/mods/deathmatch/logic/CMainConfig.cpp +++ b/Server/mods/deathmatch/logic/CMainConfig.cpp @@ -901,11 +901,15 @@ bool CMainConfig::AddMissingSettings() for (auto it3 = templateAttributes.ListBegin(); it3 != templateAttributes.ListEnd(); ++it3) { CXMLAttribute* templateAttribute = *it3; - const SString& strKey = templateAttribute->GetName(); - const SString& strValue = templateAttribute->GetValue(); + const SString& attrName = templateAttribute->GetName(); + const SString& attrValue = templateAttribute->GetValue(); - CXMLAttribute* foundAttribute = attributes.Find(strKey); - if (!foundAttribute || foundAttribute->GetValue() != strValue) + // Don't check value attributes, as they are meant to be different + if (attrName == "value") + continue; + + CXMLAttribute* foundAttribute = attributes.Find(attrName); + if (!foundAttribute || foundAttribute->GetValue() != attrValue) { attributesMatch = false; break; From f297042af0646ae9a7b11301e16f5ca4e6226628 Mon Sep 17 00:00:00 2001 From: Fernando-A-Rocha Date: Wed, 8 Jan 2025 13:43:16 +0000 Subject: [PATCH 3/5] nvm --- Server/mods/deathmatch/logic/CMainConfig.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Server/mods/deathmatch/logic/CMainConfig.cpp b/Server/mods/deathmatch/logic/CMainConfig.cpp index 038ee65ec86..ec5bf9b9d9d 100644 --- a/Server/mods/deathmatch/logic/CMainConfig.cpp +++ b/Server/mods/deathmatch/logic/CMainConfig.cpp @@ -901,15 +901,11 @@ bool CMainConfig::AddMissingSettings() for (auto it3 = templateAttributes.ListBegin(); it3 != templateAttributes.ListEnd(); ++it3) { CXMLAttribute* templateAttribute = *it3; - const SString& attrName = templateAttribute->GetName(); - const SString& attrValue = templateAttribute->GetValue(); + const SString& strKey = templateAttribute->GetName(); + const SString& strValue = templateAttribute->GetValue(); - // Don't check value attributes, as they are meant to be different - if (attrName == "value") - continue; - - CXMLAttribute* foundAttribute = attributes.Find(attrName); - if (!foundAttribute || foundAttribute->GetValue() != attrValue) + CXMLAttribute* foundAttribute = attributes.Find(strKey); + if (!foundAttribute || foundAttribute->GetValue() != strValue) { attributesMatch = false; break; From de36d48c34da793188fa58d232c439a2b614ca4c Mon Sep 17 00:00:00 2001 From: Fernando-A-Rocha Date: Wed, 8 Jan 2025 14:16:04 +0000 Subject: [PATCH 4/5] Add temp F8 logs for parsing rules on query receive --- Client/core/CQueryReceiver.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Client/core/CQueryReceiver.cpp b/Client/core/CQueryReceiver.cpp index 2be9c32420f..b9ea2dde339 100644 --- a/Client/core/CQueryReceiver.cpp +++ b/Client/core/CQueryReceiver.cpp @@ -195,6 +195,8 @@ SQueryInfo CQueryReceiver::GetServerResponse() // Check if this reply includes rules if (strncmp(szBuffer + i, "RULES", 5) == 0) { + g_pCore->GetConsole()->Printf("Parsing rules for server: %s", info.serverName.c_str()); + i += 5; while (i < len) { @@ -212,7 +214,11 @@ SQueryInfo CQueryReceiver::GetServerResponse() return info; info.rules[key] = value; + + g_pCore->GetConsole()->Printf(" Rule: %s = %s", key.c_str(), value.c_str()); } + + g_pCore->GetConsole()->Printf("Finished parsing rules"); } // Get player nicks From 012895c08af0cb5270d5f2c72e928aa69ec780b7 Mon Sep 17 00:00:00 2001 From: Fernando-A-Rocha Date: Wed, 15 Jan 2025 10:46:40 +0000 Subject: [PATCH 5/5] . --- Client/core/CClientVariables.cpp | 1 - Client/core/ServerBrowser/CServerList.cpp | 2 ++ Client/core/ServerBrowser/CServerList.h | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Client/core/CClientVariables.cpp b/Client/core/CClientVariables.cpp index 85dee800fa4..34bfd588404 100644 --- a/Client/core/CClientVariables.cpp +++ b/Client/core/CClientVariables.cpp @@ -277,7 +277,6 @@ void CClientVariables::LoadDefaults() DEFAULT("debugfile", _S("")); // debug filename DEFAULT("console_pos", CVector2D(0, 0)); // console position DEFAULT("console_size", CVector2D(200, 200)); // console size - DEFAULT("serverbrowser_size", CVector2D(720.0f, 495.0f)); // serverbrowser size DEFAULT("fps_limit", 100); // frame limiter DEFAULT("chat_font", 2); // chatbox font type DEFAULT("chat_lines", 10); // chatbox lines diff --git a/Client/core/ServerBrowser/CServerList.cpp b/Client/core/ServerBrowser/CServerList.cpp index 044f88e4964..eec93f8f45d 100644 --- a/Client/core/ServerBrowser/CServerList.cpp +++ b/Client/core/ServerBrowser/CServerList.cpp @@ -511,6 +511,8 @@ bool CServerListItem::ParseQuery() m_iBuildNumber = info.buildNum; m_usHttpPort = info.httpPort; + rules = info.rules; + if ((uiMasterServerSaysRestrictions & RESTRICTION_PLAYER_LIST) == false) vecPlayers = info.playersPool; diff --git a/Client/core/ServerBrowser/CServerList.h b/Client/core/ServerBrowser/CServerList.h index 5d0e4508736..b7aecb3b0dd 100644 --- a/Client/core/ServerBrowser/CServerList.h +++ b/Client/core/ServerBrowser/CServerList.h @@ -222,6 +222,7 @@ class CServerListItem CQueryReceiver queryReceiver; std::vector vecPlayers; + std::unordered_map rules; void Query();