1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "serverbrowser.h"
4
5#include "serverbrowser_http.h"
6#include "serverbrowser_ping_cache.h"
7
8#include <base/dbg.h>
9#include <base/hash_ctxt.h>
10#include <base/log.h>
11#include <base/mem.h>
12#include <base/secure.h>
13#include <base/str.h>
14#include <base/time.h>
15
16#include <engine/console.h>
17#include <engine/engine.h>
18#include <engine/favorites.h>
19#include <engine/friends.h>
20#include <engine/http.h>
21#include <engine/shared/config.h>
22#include <engine/shared/json.h>
23#include <engine/shared/masterserver.h>
24#include <engine/shared/network.h>
25#include <engine/shared/packer.h>
26#include <engine/shared/protocol.h>
27#include <engine/shared/serverinfo.h>
28#include <engine/storage.h>
29
30#include <algorithm>
31#include <map>
32#include <set>
33#include <vector>
34
35class CSortWrap
36{
37 typedef bool (CServerBrowser::*SortFunc)(int, int) const;
38 SortFunc m_pfnSort;
39 CServerBrowser *m_pThis;
40
41public:
42 CSortWrap(CServerBrowser *pServer, SortFunc Func) :
43 m_pfnSort(Func), m_pThis(pServer) {}
44 bool operator()(int a, int b) { return (g_Config.m_BrSortOrder ? (m_pThis->*m_pfnSort)(b, a) : (m_pThis->*m_pfnSort)(a, b)); }
45};
46
47static bool MatchesPart(const char *a, const char *b)
48{
49 return str_utf8_find_nocase(haystack: a, needle: b) != nullptr;
50}
51
52static bool MatchesExactly(const char *a, const char *b)
53{
54 return str_comp(a, b: &b[1]) == 0;
55}
56
57static NETADDR CommunityAddressKey(const NETADDR &Addr)
58{
59 NETADDR AddressKey = Addr;
60 AddressKey.type &= ~NETTYPE_TW7;
61 return AddressKey;
62}
63
64CServerBrowser::CServerBrowser() :
65 m_CommunityCache(this),
66 m_CountriesFilter(&m_CommunityCache),
67 m_TypesFilter(&m_CommunityCache)
68{
69 m_NeedResort = false;
70 m_Sorthash = 0;
71
72 m_ServerlistType = 0;
73 m_BroadcastTime = 0;
74 secure_random_fill(bytes: m_aTokenSeed, length: sizeof(m_aTokenSeed));
75
76 CleanUp();
77}
78
79CServerBrowser::~CServerBrowser()
80{
81 json_value_free(m_pDDNetInfo);
82
83 delete m_pHttp;
84 m_pHttp = nullptr;
85 delete m_pPingCache;
86 m_pPingCache = nullptr;
87}
88
89void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVersion)
90{
91 m_pNetClient = pClient;
92 str_copy(dst&: m_aNetVersion, src: pNetVersion);
93 m_pConsole = Kernel()->RequestInterface<IConsole>();
94 m_pConfigManager = Kernel()->RequestInterface<IConfigManager>();
95 m_pEngine = Kernel()->RequestInterface<IEngine>();
96 m_pFavorites = Kernel()->RequestInterface<IFavorites>();
97 m_pFriends = Kernel()->RequestInterface<IFriends>();
98 m_pStorage = Kernel()->RequestInterface<IStorage>();
99 m_pHttpClient = Kernel()->RequestInterface<IHttp>();
100 m_pPingCache = CreateServerBrowserPingCache(pConsole: m_pConsole, pStorage: m_pStorage);
101
102 RegisterCommands();
103}
104
105void CServerBrowser::OnInit()
106{
107 m_pHttp = CreateServerBrowserHttp(pEngine: m_pEngine, pStorage: m_pStorage, pHttp: m_pHttpClient, pPreviousBestUrl: g_Config.m_BrCachedBestServerinfoUrl);
108}
109
110void CServerBrowser::RegisterCommands()
111{
112 m_pConfigManager->RegisterCallback(pfnFunc: CServerBrowser::ConfigSaveCallback, pUserData: this);
113 m_pConsole->Register(pName: "add_favorite_community", pParams: "s[community_id]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_AddFavoriteCommunity, pUser: this, pHelp: "Add a community as a favorite");
114 m_pConsole->Register(pName: "remove_favorite_community", pParams: "s[community_id]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_RemoveFavoriteCommunity, pUser: this, pHelp: "Remove a community from the favorites");
115 m_pConsole->Register(pName: "add_excluded_community", pParams: "s[community_id]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_AddExcludedCommunity, pUser: this, pHelp: "Add a community to the exclusion filter");
116 m_pConsole->Register(pName: "remove_excluded_community", pParams: "s[community_id]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_RemoveExcludedCommunity, pUser: this, pHelp: "Remove a community from the exclusion filter");
117 m_pConsole->Register(pName: "add_excluded_country", pParams: "s[community_id] s[country_code]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_AddExcludedCountry, pUser: this, pHelp: "Add a country to the exclusion filter for a specific community (ISO 3166-1 numeric)");
118 m_pConsole->Register(pName: "remove_excluded_country", pParams: "s[community_id] s[country_code]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_RemoveExcludedCountry, pUser: this, pHelp: "Remove a country from the exclusion filter for a specific community (ISO 3166-1 numeric)");
119 m_pConsole->Register(pName: "add_excluded_type", pParams: "s[community_id] s[type]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_AddExcludedType, pUser: this, pHelp: "Add a type to the exclusion filter for a specific community");
120 m_pConsole->Register(pName: "remove_excluded_type", pParams: "s[community_id] s[type]", Flags: CFGFLAG_CLIENT, pfnFunc: Con_RemoveExcludedType, pUser: this, pHelp: "Remove a type from the exclusion filter for a specific community");
121 m_pConsole->Register(pName: "leak_ip_address_to_all_servers", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: Con_LeakIpAddress, pUser: this, pHelp: "Leaks your IP address to all servers by pinging each of them, also acquiring the latency in the process");
122}
123
124void CServerBrowser::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
125{
126 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
127 pThis->FavoriteCommunitiesFilter().Save(pConfigManager);
128 pThis->CommunitiesFilter().Save(pConfigManager);
129 pThis->CountriesFilter().Save(pConfigManager);
130 pThis->TypesFilter().Save(pConfigManager);
131}
132
133void CServerBrowser::Con_AddFavoriteCommunity(IConsole::IResult *pResult, void *pUserData)
134{
135 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
136 const char *pCommunityId = pResult->GetString(Index: 0);
137 if(!pThis->ValidateCommunityId(pCommunityId))
138 return;
139 pThis->FavoriteCommunitiesFilter().Add(pCommunityId);
140}
141
142void CServerBrowser::Con_RemoveFavoriteCommunity(IConsole::IResult *pResult, void *pUserData)
143{
144 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
145 const char *pCommunityId = pResult->GetString(Index: 0);
146 if(!pThis->ValidateCommunityId(pCommunityId))
147 return;
148 pThis->FavoriteCommunitiesFilter().Remove(pCommunityId);
149}
150
151void CServerBrowser::Con_AddExcludedCommunity(IConsole::IResult *pResult, void *pUserData)
152{
153 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
154 const char *pCommunityId = pResult->GetString(Index: 0);
155 if(!pThis->ValidateCommunityId(pCommunityId))
156 return;
157 pThis->CommunitiesFilter().Add(pCommunityId);
158}
159
160void CServerBrowser::Con_RemoveExcludedCommunity(IConsole::IResult *pResult, void *pUserData)
161{
162 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
163 const char *pCommunityId = pResult->GetString(Index: 0);
164 if(!pThis->ValidateCommunityId(pCommunityId))
165 return;
166 pThis->CommunitiesFilter().Remove(pCommunityId);
167}
168
169void CServerBrowser::Con_AddExcludedCountry(IConsole::IResult *pResult, void *pUserData)
170{
171 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
172 const char *pCommunityId = pResult->GetString(Index: 0);
173 const char *pCountryName = pResult->GetString(Index: 1);
174 if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateCountryName(pCountryName))
175 return;
176 pThis->CountriesFilter().Add(pCommunityId, pCountryName);
177}
178
179void CServerBrowser::Con_RemoveExcludedCountry(IConsole::IResult *pResult, void *pUserData)
180{
181 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
182 const char *pCommunityId = pResult->GetString(Index: 0);
183 const char *pCountryName = pResult->GetString(Index: 1);
184 if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateCountryName(pCountryName))
185 return;
186 pThis->CountriesFilter().Remove(pCommunityId, pCountryName);
187}
188
189void CServerBrowser::Con_AddExcludedType(IConsole::IResult *pResult, void *pUserData)
190{
191 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
192 const char *pCommunityId = pResult->GetString(Index: 0);
193 const char *pTypeName = pResult->GetString(Index: 1);
194 if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateTypeName(pTypeName))
195 return;
196 pThis->TypesFilter().Add(pCommunityId, pTypeName);
197}
198
199void CServerBrowser::Con_RemoveExcludedType(IConsole::IResult *pResult, void *pUserData)
200{
201 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
202 const char *pCommunityId = pResult->GetString(Index: 0);
203 const char *pTypeName = pResult->GetString(Index: 1);
204 if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateTypeName(pTypeName))
205 return;
206 pThis->TypesFilter().Remove(pCommunityId, pTypeName);
207}
208
209void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData)
210{
211 CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
212
213 // We only consider the first address of every server.
214
215 std::vector<int> vSortedServers;
216 // Sort servers by IP address, ignoring port.
217 class CAddrComparer
218 {
219 public:
220 CServerBrowser *m_pThis;
221 bool operator()(int i, int j) const
222 {
223 NETADDR Addr1 = m_pThis->m_vpServerlist[i]->m_Info.m_aAddresses[0];
224 NETADDR Addr2 = m_pThis->m_vpServerlist[j]->m_Info.m_aAddresses[0];
225 Addr1.port = 0;
226 Addr2.port = 0;
227 return net_addr_comp(a: &Addr1, b: &Addr2) < 0;
228 }
229 };
230 vSortedServers.reserve(n: pThis->m_vpServerlist.size());
231 for(int i = 0; i < (int)pThis->m_vpServerlist.size(); i++)
232 {
233 vSortedServers.push_back(x: i);
234 }
235 std::sort(first: vSortedServers.begin(), last: vSortedServers.end(), comp: CAddrComparer{.m_pThis: pThis});
236
237 // Group the servers into those with same IP address (but differing
238 // port).
239 NETADDR Addr;
240 int Start = -1;
241 for(int i = 0; i <= (int)vSortedServers.size(); i++)
242 {
243 NETADDR NextAddr;
244 if(i < (int)vSortedServers.size())
245 {
246 NextAddr = pThis->m_vpServerlist[vSortedServers[i]]->m_Info.m_aAddresses[0];
247 NextAddr.port = 0;
248 }
249 bool New = Start == -1 || i == (int)vSortedServers.size() || net_addr_comp(a: &Addr, b: &NextAddr) != 0;
250 if(Start != -1 && New)
251 {
252 int Chosen = Start + secure_rand_below(below: i - Start);
253 CServerEntry *pChosen = pThis->m_vpServerlist[vSortedServers[Chosen]];
254 pChosen->m_RequestIgnoreInfo = true;
255 pThis->QueueRequest(pEntry: pChosen);
256 char aAddr[NETADDR_MAXSTRSIZE];
257 net_addr_str(addr: &pChosen->m_Info.m_aAddresses[0], string: aAddr, max_length: sizeof(aAddr), add_port: true);
258 dbg_msg(sys: "serverbrowser", fmt: "queuing ping request for %s", aAddr);
259 }
260 if(i < (int)vSortedServers.size() && New)
261 {
262 Start = i;
263 Addr = NextAddr;
264 }
265 }
266}
267
268static bool ValidIdentifier(const char *pId, size_t MaxLength)
269{
270 if(pId[0] == '\0' || (size_t)str_length(str: pId) >= MaxLength)
271 {
272 return false;
273 }
274
275 for(int i = 0; pId[i] != '\0'; ++i)
276 {
277 if(pId[i] == '"' || pId[i] == '/' || pId[i] == '\\')
278 {
279 return false;
280 }
281 }
282 return true;
283}
284
285static bool ValidateIdentifier(const char *pId, size_t MaxLength, const char *pContext, IConsole *pConsole)
286{
287 if(!ValidIdentifier(pId, MaxLength))
288 {
289 char aError[32 + IConsole::CMDLINE_LENGTH];
290 str_format(buffer: aError, buffer_size: sizeof(aError), format: "%s '%s' is not valid", pContext, pId);
291 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowser", pStr: aError);
292 return false;
293 }
294 return true;
295}
296
297bool CServerBrowser::ValidateCommunityId(const char *pCommunityId) const
298{
299 return ValidateIdentifier(pId: pCommunityId, MaxLength: CServerInfo::MAX_COMMUNITY_ID_LENGTH, pContext: "Community ID", pConsole: m_pConsole);
300}
301
302bool CServerBrowser::ValidateCountryName(const char *pCountryName) const
303{
304 return ValidateIdentifier(pId: pCountryName, MaxLength: CServerInfo::MAX_COMMUNITY_COUNTRY_LENGTH, pContext: "Country name", pConsole: m_pConsole);
305}
306
307bool CServerBrowser::ValidateTypeName(const char *pTypeName) const
308{
309 return ValidateIdentifier(pId: pTypeName, MaxLength: CServerInfo::MAX_COMMUNITY_TYPE_LENGTH, pContext: "Type name", pConsole: m_pConsole);
310}
311
312int CServerBrowser::Players(const CServerInfo &Item) const
313{
314 return g_Config.m_BrFilterSpectators ? Item.m_NumPlayers : Item.m_NumClients;
315}
316
317int CServerBrowser::Max(const CServerInfo &Item) const
318{
319 return g_Config.m_BrFilterSpectators ? Item.m_MaxPlayers : Item.m_MaxClients;
320}
321
322const CServerInfo *CServerBrowser::SortedGet(int Index) const
323{
324 if(Index < 0 || Index >= (int)m_vSortedServerlist.size())
325 return nullptr;
326 return &m_vpServerlist[m_vSortedServerlist[Index]]->m_Info;
327}
328
329const CServerInfo *CServerBrowser::Get(int Index) const
330{
331 if(Index < 0 || Index >= (int)m_vpServerlist.size())
332 return nullptr;
333 return &m_vpServerlist[Index]->m_Info;
334}
335
336int CServerBrowser::GenerateToken(const NETADDR &Addr) const
337{
338 SHA256_CTX Sha256;
339 sha256_init(ctxt: &Sha256);
340 sha256_update(ctxt: &Sha256, data: m_aTokenSeed, data_len: sizeof(m_aTokenSeed));
341 sha256_update(ctxt: &Sha256, data: (unsigned char *)&Addr, data_len: sizeof(Addr));
342 SHA256_DIGEST Digest = sha256_finish(ctxt: &Sha256);
343 return (Digest.data[0] << 16) | (Digest.data[1] << 8) | Digest.data[2];
344}
345
346int CServerBrowser::GetBasicToken(int Token)
347{
348 return Token & 0xff;
349}
350
351int CServerBrowser::GetExtraToken(int Token)
352{
353 return Token >> 8;
354}
355
356bool CServerBrowser::SortCompareName(int Index1, int Index2) const
357{
358 CServerEntry *pIndex1 = m_vpServerlist[Index1];
359 CServerEntry *pIndex2 = m_vpServerlist[Index2];
360 // make sure empty entries are listed last
361 return (pIndex1->m_GotInfo && pIndex2->m_GotInfo) || (!pIndex1->m_GotInfo && !pIndex2->m_GotInfo) ? str_comp(a: pIndex1->m_Info.m_aName, b: pIndex2->m_Info.m_aName) < 0 :
362 pIndex1->m_GotInfo != 0;
363}
364
365bool CServerBrowser::SortCompareMap(int Index1, int Index2) const
366{
367 CServerEntry *pIndex1 = m_vpServerlist[Index1];
368 CServerEntry *pIndex2 = m_vpServerlist[Index2];
369 return str_comp(a: pIndex1->m_Info.m_aMap, b: pIndex2->m_Info.m_aMap) < 0;
370}
371
372bool CServerBrowser::SortComparePing(int Index1, int Index2) const
373{
374 CServerEntry *pIndex1 = m_vpServerlist[Index1];
375 CServerEntry *pIndex2 = m_vpServerlist[Index2];
376 return pIndex1->m_Info.m_Latency < pIndex2->m_Info.m_Latency;
377}
378
379bool CServerBrowser::SortCompareGametype(int Index1, int Index2) const
380{
381 CServerEntry *pIndex1 = m_vpServerlist[Index1];
382 CServerEntry *pIndex2 = m_vpServerlist[Index2];
383 return str_comp(a: pIndex1->m_Info.m_aGameType, b: pIndex2->m_Info.m_aGameType) < 0;
384}
385
386bool CServerBrowser::SortCompareNumPlayers(int Index1, int Index2) const
387{
388 CServerEntry *pIndex1 = m_vpServerlist[Index1];
389 CServerEntry *pIndex2 = m_vpServerlist[Index2];
390 return pIndex1->m_Info.m_NumFilteredPlayers > pIndex2->m_Info.m_NumFilteredPlayers;
391}
392
393bool CServerBrowser::SortCompareNumClients(int Index1, int Index2) const
394{
395 CServerEntry *pIndex1 = m_vpServerlist[Index1];
396 CServerEntry *pIndex2 = m_vpServerlist[Index2];
397 return pIndex1->m_Info.m_NumClients > pIndex2->m_Info.m_NumClients;
398}
399
400bool CServerBrowser::SortCompareNumFriends(int Index1, int Index2) const
401{
402 CServerEntry *pIndex1 = m_vpServerlist[Index1];
403 CServerEntry *pIndex2 = m_vpServerlist[Index2];
404
405 if(pIndex1->m_Info.m_FriendNum == pIndex2->m_Info.m_FriendNum)
406 return pIndex1->m_Info.m_NumFilteredPlayers > pIndex2->m_Info.m_NumFilteredPlayers;
407 else
408 return pIndex1->m_Info.m_FriendNum > pIndex2->m_Info.m_FriendNum;
409}
410
411bool CServerBrowser::SortCompareNumPlayersAndPing(int Index1, int Index2) const
412{
413 CServerEntry *pIndex1 = m_vpServerlist[Index1];
414 CServerEntry *pIndex2 = m_vpServerlist[Index2];
415
416 if(pIndex1->m_Info.m_NumFilteredPlayers == pIndex2->m_Info.m_NumFilteredPlayers)
417 return pIndex1->m_Info.m_Latency > pIndex2->m_Info.m_Latency;
418 else if(pIndex1->m_Info.m_NumFilteredPlayers == 0 || pIndex2->m_Info.m_NumFilteredPlayers == 0 || pIndex1->m_Info.m_Latency / 100 == pIndex2->m_Info.m_Latency / 100)
419 return pIndex1->m_Info.m_NumFilteredPlayers < pIndex2->m_Info.m_NumFilteredPlayers;
420 else
421 return pIndex1->m_Info.m_Latency > pIndex2->m_Info.m_Latency;
422}
423
424bool CServerBrowser::SortCompareFavoritesNumPlayersAndPing(int Index1, int Index2) const
425{
426 const CServerEntry *pIndex1 = m_vpServerlist[Index1];
427 const CServerEntry *pIndex2 = m_vpServerlist[Index2];
428 const bool IsFavorite1 = pIndex1->m_Info.m_Favorite != TRISTATE::NONE;
429 const bool IsFavorite2 = pIndex2->m_Info.m_Favorite != TRISTATE::NONE;
430 if(IsFavorite1 == IsFavorite2)
431 return SortCompareNumPlayersAndPing(Index1, Index2);
432 return IsFavorite1 && !IsFavorite2;
433}
434
435void CServerBrowser::Filter()
436{
437 m_NumSortedPlayers = 0;
438
439 m_vSortedServerlist.clear();
440 m_vSortedServerlist.reserve(n: m_vpServerlist.size());
441
442 for(auto &Community : m_vCommunities)
443 {
444 Community.m_NumPlayers = 0;
445 }
446
447 // filter the servers
448 for(int ServerIndex = 0; ServerIndex < (int)m_vpServerlist.size(); ServerIndex++)
449 {
450 CServerInfo &Info = m_vpServerlist[ServerIndex]->m_Info;
451 bool Filtered = false;
452
453 if(g_Config.m_BrFilterEmpty && Info.m_NumFilteredPlayers == 0)
454 Filtered = true;
455 else if(g_Config.m_BrFilterFull && Players(Item: Info) == Max(Item: Info))
456 Filtered = true;
457 else if(g_Config.m_BrFilterPw && Info.m_Flags & SERVER_FLAG_PASSWORD)
458 Filtered = true;
459 else if(g_Config.m_BrFilterServerAddress[0] && !str_find_nocase(haystack: Info.m_aAddress, needle: g_Config.m_BrFilterServerAddress))
460 Filtered = true;
461 else if(g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && str_comp_nocase(a: Info.m_aGameType, b: g_Config.m_BrFilterGametype))
462 Filtered = true;
463 else if(!g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && !str_utf8_find_nocase(haystack: Info.m_aGameType, needle: g_Config.m_BrFilterGametype))
464 Filtered = true;
465 else if(g_Config.m_BrFilterUnfinishedMap && Info.m_HasRank == CServerInfo::RANK_RANKED)
466 Filtered = true;
467 else if(g_Config.m_BrFilterLogin && Info.m_RequiresLogin)
468 Filtered = true;
469 else
470 {
471 if(!Communities().empty())
472 {
473 if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
474 {
475 Filtered = CommunitiesFilter().Filtered(pCommunityId: Info.m_aCommunityId);
476 }
477 if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES ||
478 (m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5))
479 {
480 Filtered = Filtered || CountriesFilter().Filtered(pCountryName: Info.m_aCommunityCountry);
481 Filtered = Filtered || TypesFilter().Filtered(pTypeName: Info.m_aCommunityType);
482 }
483 }
484
485 if(!Filtered && g_Config.m_BrFilterCountry)
486 {
487 Filtered = true;
488 // match against player country
489 for(const auto &Client : Info.m_vClients)
490 {
491 if(Client.m_Country == g_Config.m_BrFilterCountryIndex)
492 {
493 Filtered = false;
494 break;
495 }
496 }
497 }
498
499 if(!Filtered && g_Config.m_BrFilterString[0] != '\0')
500 {
501 Info.m_QuickSearchHit = 0;
502
503 const char *pStr = g_Config.m_BrFilterString;
504 char aFilterStr[sizeof(g_Config.m_BrFilterString)];
505 char aFilterStrTrimmed[sizeof(g_Config.m_BrFilterString)];
506 while((pStr = str_next_token(str: pStr, delim: IServerBrowser::SEARCH_EXCLUDE_TOKEN, buffer: aFilterStr, buffer_size: sizeof(aFilterStr))))
507 {
508 str_copy(dst&: aFilterStrTrimmed, src: str_utf8_skip_whitespaces(str: aFilterStr));
509 str_utf8_trim_right(param: aFilterStrTrimmed);
510
511 if(aFilterStrTrimmed[0] == '\0')
512 {
513 continue;
514 }
515 auto MatchesFn = MatchesPart;
516 const int FilterLen = str_length(str: aFilterStrTrimmed);
517 if(aFilterStrTrimmed[0] == '"' && aFilterStrTrimmed[FilterLen - 1] == '"')
518 {
519 aFilterStrTrimmed[FilterLen - 1] = '\0';
520 MatchesFn = MatchesExactly;
521 }
522
523 // match against server name
524 if(MatchesFn(Info.m_aName, aFilterStrTrimmed))
525 {
526 Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
527 }
528
529 // match against players
530 for(const auto &Client : Info.m_vClients)
531 {
532 if(MatchesFn(Client.m_aName, aFilterStrTrimmed) ||
533 MatchesFn(Client.m_aClan, aFilterStrTrimmed))
534 {
535 if(g_Config.m_BrFilterConnectingPlayers &&
536 str_comp(a: Client.m_aName, b: "(connecting)") == 0 &&
537 Client.m_aClan[0] == '\0')
538 {
539 continue;
540 }
541 Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
542 break;
543 }
544 }
545
546 // match against map
547 if(MatchesFn(Info.m_aMap, aFilterStrTrimmed))
548 {
549 Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
550 }
551 }
552
553 if(!Info.m_QuickSearchHit)
554 Filtered = true;
555 }
556
557 if(!Filtered && g_Config.m_BrExcludeString[0] != '\0')
558 {
559 const char *pStr = g_Config.m_BrExcludeString;
560 char aExcludeStr[sizeof(g_Config.m_BrExcludeString)];
561 char aExcludeStrTrimmed[sizeof(g_Config.m_BrExcludeString)];
562 while((pStr = str_next_token(str: pStr, delim: IServerBrowser::SEARCH_EXCLUDE_TOKEN, buffer: aExcludeStr, buffer_size: sizeof(aExcludeStr))))
563 {
564 str_copy(dst&: aExcludeStrTrimmed, src: str_utf8_skip_whitespaces(str: aExcludeStr));
565 str_utf8_trim_right(param: aExcludeStrTrimmed);
566
567 if(aExcludeStrTrimmed[0] == '\0')
568 {
569 continue;
570 }
571 auto MatchesFn = MatchesPart;
572 const int FilterLen = str_length(str: aExcludeStrTrimmed);
573 if(aExcludeStrTrimmed[0] == '"' && aExcludeStrTrimmed[FilterLen - 1] == '"')
574 {
575 aExcludeStrTrimmed[FilterLen - 1] = '\0';
576 MatchesFn = MatchesExactly;
577 }
578
579 // match against server name
580 if(MatchesFn(Info.m_aName, aExcludeStrTrimmed))
581 {
582 Filtered = true;
583 break;
584 }
585
586 // match against map
587 if(MatchesFn(Info.m_aMap, aExcludeStrTrimmed))
588 {
589 Filtered = true;
590 break;
591 }
592
593 // match against gametype
594 if(MatchesFn(Info.m_aGameType, aExcludeStrTrimmed))
595 {
596 Filtered = true;
597 break;
598 }
599 }
600 }
601 }
602
603 UpdateServerFriends(pInfo: &Info);
604
605 if(!Filtered)
606 {
607 if(!g_Config.m_BrFilterFriends || Info.m_FriendState != IFriends::FRIEND_NO)
608 {
609 m_NumSortedPlayers += Info.m_NumFilteredPlayers;
610 m_vSortedServerlist.push_back(x: ServerIndex);
611 }
612 }
613
614 if(Info.m_NumClients > 0)
615 {
616 auto Community = std::find_if(first: m_vCommunities.begin(), last: m_vCommunities.end(), pred: [Info](const auto &Elem) {
617 return str_comp(Elem.Id(), Info.m_aCommunityId) == 0;
618 });
619 if(Community != m_vCommunities.end())
620 {
621 Community->m_NumPlayers += Info.m_NumClients;
622 }
623 }
624 }
625
626 std::stable_sort(first: m_vCommunities.begin(), last: m_vCommunities.end(), comp: [](const CCommunity &Lhs, const CCommunity &Rhs) {
627 return Lhs.NumPlayers() > Rhs.NumPlayers();
628 });
629}
630
631int CServerBrowser::SortHash() const
632{
633 int i = g_Config.m_BrSort & 0xff;
634 i |= g_Config.m_BrFilterEmpty << 4;
635 i |= g_Config.m_BrFilterFull << 5;
636 i |= g_Config.m_BrFilterSpectators << 6;
637 i |= g_Config.m_BrFilterFriends << 7;
638 i |= g_Config.m_BrFilterPw << 8;
639 i |= g_Config.m_BrSortOrder << 9;
640 i |= g_Config.m_BrFilterGametypeStrict << 12;
641 i |= g_Config.m_BrFilterUnfinishedMap << 13;
642 i |= g_Config.m_BrFilterCountry << 14;
643 i |= g_Config.m_BrFilterConnectingPlayers << 15;
644 i |= g_Config.m_BrFilterLogin << 16;
645 return i;
646}
647
648void CServerBrowser::Sort()
649{
650 // update number of filtered players
651 for(CServerEntry *pEntry : m_vpServerlist)
652 {
653 UpdateServerFilteredPlayers(pInfo: &pEntry->m_Info);
654 }
655
656 // create filtered list
657 Filter();
658
659 // sort
660 if(g_Config.m_BrSortOrder == 2 && (g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS || g_Config.m_BrSort == IServerBrowser::SORT_PING))
661 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareNumPlayersAndPing));
662 else if(g_Config.m_BrSort == IServerBrowser::SORT_NAME)
663 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareName));
664 else if(g_Config.m_BrSort == IServerBrowser::SORT_PING)
665 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortComparePing));
666 else if(g_Config.m_BrSort == IServerBrowser::SORT_MAP)
667 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareMap));
668 else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMFRIENDS)
669 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareNumFriends));
670 else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS)
671 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareNumPlayers));
672 else if(g_Config.m_BrSort == IServerBrowser::SORT_GAMETYPE)
673 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareGametype));
674 else if(g_Config.m_BrSort == IServerBrowser::SORT_FAVORITES)
675 std::stable_sort(first: m_vSortedServerlist.begin(), last: m_vSortedServerlist.end(), comp: CSortWrap(this, &CServerBrowser::SortCompareFavoritesNumPlayersAndPing));
676
677 m_Sorthash = SortHash();
678}
679
680void CServerBrowser::RemoveRequest(CServerEntry *pEntry)
681{
682 if(pEntry->m_pPrevReq || pEntry->m_pNextReq || m_pFirstReqServer == pEntry)
683 {
684 if(pEntry->m_pPrevReq)
685 pEntry->m_pPrevReq->m_pNextReq = pEntry->m_pNextReq;
686 else
687 m_pFirstReqServer = pEntry->m_pNextReq;
688
689 if(pEntry->m_pNextReq)
690 pEntry->m_pNextReq->m_pPrevReq = pEntry->m_pPrevReq;
691 else
692 m_pLastReqServer = pEntry->m_pPrevReq;
693
694 pEntry->m_pPrevReq = nullptr;
695 pEntry->m_pNextReq = nullptr;
696 m_NumRequests--;
697 }
698}
699
700CServerBrowser::CServerEntry *CServerBrowser::Find(const NETADDR &Addr)
701{
702 auto Entry = m_ByAddr.find(key: Addr);
703 if(Entry == m_ByAddr.end())
704 {
705 return nullptr;
706 }
707 return m_vpServerlist[Entry->second];
708}
709
710void CServerBrowser::QueueRequest(CServerEntry *pEntry)
711{
712 // add it to the list of servers that we should request info from
713 pEntry->m_pPrevReq = m_pLastReqServer;
714 if(m_pLastReqServer)
715 m_pLastReqServer->m_pNextReq = pEntry;
716 else
717 m_pFirstReqServer = pEntry;
718 m_pLastReqServer = pEntry;
719 pEntry->m_pNextReq = nullptr;
720 m_NumRequests++;
721}
722
723static void ServerBrowserFormatAddresses(char *pBuffer, int BufferSize, NETADDR *pAddrs, int NumAddrs)
724{
725 pBuffer[0] = '\0';
726 for(int i = 0; i < NumAddrs; i++)
727 {
728 if(i != 0)
729 {
730 str_append(dst: pBuffer, src: ",", dst_size: BufferSize);
731 }
732 if(pAddrs[i].type & NETTYPE_TW7)
733 {
734 str_append(dst: pBuffer, src: "tw-0.7+udp://", dst_size: BufferSize);
735 }
736 char aIpAddr[NETADDR_MAXSTRSIZE];
737 net_addr_str(addr: &pAddrs[i], string: aIpAddr, max_length: sizeof(aIpAddr), add_port: true);
738 str_append(dst: pBuffer, src: aIpAddr, dst_size: BufferSize);
739 }
740}
741
742void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const
743{
744 const CServerInfo TmpInfo = pEntry->m_Info;
745 pEntry->m_Info = Info;
746 pEntry->m_Info.m_Favorite = TmpInfo.m_Favorite;
747 pEntry->m_Info.m_FavoriteAllowPing = TmpInfo.m_FavoriteAllowPing;
748 pEntry->m_Info.m_ServerIndex = TmpInfo.m_ServerIndex;
749 mem_copy(dest: pEntry->m_Info.m_aAddresses, source: TmpInfo.m_aAddresses, size: sizeof(pEntry->m_Info.m_aAddresses));
750 pEntry->m_Info.m_NumAddresses = TmpInfo.m_NumAddresses;
751 ServerBrowserFormatAddresses(pBuffer: pEntry->m_Info.m_aAddress, BufferSize: sizeof(pEntry->m_Info.m_aAddress), pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
752 str_copy(dst&: pEntry->m_Info.m_aCommunityId, src: TmpInfo.m_aCommunityId);
753 str_copy(dst&: pEntry->m_Info.m_aCommunityCountry, src: TmpInfo.m_aCommunityCountry);
754 str_copy(dst&: pEntry->m_Info.m_aCommunityType, src: TmpInfo.m_aCommunityType);
755 UpdateServerRank(pInfo: &pEntry->m_Info);
756 pEntry->m_Info.m_GametypeColor = CServerInfo::GametypeColor(pGametype: pEntry->m_Info.m_aGameType);
757
758 if(pEntry->m_Info.m_ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_UNSPECIFIED)
759 {
760 if(str_find_nocase(haystack: pEntry->m_Info.m_aGameType, needle: "race") || str_find_nocase(haystack: pEntry->m_Info.m_aGameType, needle: "fastcap"))
761 {
762 pEntry->m_Info.m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT;
763 }
764 else
765 {
766 pEntry->m_Info.m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_POINTS;
767 }
768 }
769
770 class CPlayerScoreNameLess
771 {
772 const int m_ScoreKind;
773
774 public:
775 CPlayerScoreNameLess(int ClientScoreKind) :
776 m_ScoreKind(ClientScoreKind)
777 {
778 }
779
780 bool operator()(const CServerInfo::CClient &Client0, const CServerInfo::CClient &Client1) const
781 {
782 // Sort players before non players
783 if(Client0.m_Player && !Client1.m_Player)
784 return true;
785 if(!Client0.m_Player && Client1.m_Player)
786 return false;
787
788 int Score0 = Client0.m_Score;
789 int Score1 = Client1.m_Score;
790
791 if(m_ScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME || m_ScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT)
792 {
793 // Sort unfinished (-9999) and still connecting players (-1) after others
794 if(Score0 < 0 && Score1 >= 0)
795 return false;
796 if(Score0 >= 0 && Score1 < 0)
797 return true;
798 }
799
800 if(Score0 != Score1)
801 {
802 // Handle the sign change introduced with CLIENT_SCORE_KIND_TIME
803 if(m_ScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME)
804 return Score0 < Score1;
805 else
806 return Score0 > Score1;
807 }
808
809 return str_comp_nocase(a: Client0.m_aName, b: Client1.m_aName) < 0;
810 }
811 };
812
813 std::sort(first: pEntry->m_Info.m_vClients.begin(), last: pEntry->m_Info.m_vClients.end(), comp: CPlayerScoreNameLess(pEntry->m_Info.m_ClientScoreKind));
814
815 pEntry->m_GotInfo = 1;
816}
817
818void CServerBrowser::SetLatency(NETADDR Addr, int Latency)
819{
820 m_pPingCache->CachePing(Addr, Ping: Latency);
821
822 Addr.port = 0;
823 for(CServerEntry *pEntry : m_vpServerlist)
824 {
825 if(!pEntry->m_GotInfo)
826 {
827 continue;
828 }
829 bool Found = false;
830 for(int i = 0; i < pEntry->m_Info.m_NumAddresses; i++)
831 {
832 NETADDR Other = pEntry->m_Info.m_aAddresses[i];
833 Other.port = 0;
834 if(Addr == Other)
835 {
836 Found = true;
837 break;
838 }
839 }
840 if(!Found)
841 {
842 continue;
843 }
844 int Ping = m_pPingCache->GetPing(pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
845 if(Ping == -1)
846 {
847 continue;
848 }
849 pEntry->m_Info.m_Latency = Ping;
850 pEntry->m_Info.m_LatencyIsEstimated = false;
851 }
852}
853
854CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR *pAddrs, int NumAddrs)
855{
856 // create new pEntry
857 CServerEntry *pEntry = &m_ServerlistStorage.emplace_back();
858
859 // set the info
860 mem_copy(dest: pEntry->m_Info.m_aAddresses, source: pAddrs, size: NumAddrs * sizeof(pAddrs[0]));
861 pEntry->m_Info.m_NumAddresses = NumAddrs;
862
863 pEntry->m_Info.m_Latency = 999;
864 pEntry->m_Info.m_HasRank = CServerInfo::RANK_UNAVAILABLE;
865 ServerBrowserFormatAddresses(pBuffer: pEntry->m_Info.m_aAddress, BufferSize: sizeof(pEntry->m_Info.m_aAddress), pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
866 UpdateServerCommunity(pInfo: &pEntry->m_Info);
867 str_copy(dst&: pEntry->m_Info.m_aName, src: pEntry->m_Info.m_aAddress);
868
869 // check if it's a favorite
870 pEntry->m_Info.m_Favorite = m_pFavorites->IsFavorite(pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
871 pEntry->m_Info.m_FavoriteAllowPing = m_pFavorites->IsPingAllowed(pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
872
873 int ServerIndex = m_vpServerlist.size();
874 for(int i = 0; i < NumAddrs; i++)
875 {
876 m_ByAddr[pAddrs[i]] = ServerIndex;
877 }
878
879 // add to list
880 pEntry->m_Info.m_ServerIndex = ServerIndex;
881 if(m_vpServerlist.capacity() == 0)
882 {
883 m_vpServerlist.reserve(n: 128);
884 }
885 m_vpServerlist.push_back(x: pEntry);
886
887 return pEntry;
888}
889
890CServerBrowser::CServerEntry *CServerBrowser::ReplaceEntry(CServerEntry *pEntry, const NETADDR *pAddrs, int NumAddrs)
891{
892 for(int i = 0; i < pEntry->m_Info.m_NumAddresses; i++)
893 {
894 m_ByAddr.erase(key: pEntry->m_Info.m_aAddresses[i]);
895 }
896
897 // set the info
898 mem_copy(dest: pEntry->m_Info.m_aAddresses, source: pAddrs, size: NumAddrs * sizeof(pAddrs[0]));
899 pEntry->m_Info.m_NumAddresses = NumAddrs;
900
901 pEntry->m_Info.m_Latency = 999;
902 pEntry->m_Info.m_HasRank = CServerInfo::RANK_UNAVAILABLE;
903 ServerBrowserFormatAddresses(pBuffer: pEntry->m_Info.m_aAddress, BufferSize: sizeof(pEntry->m_Info.m_aAddress), pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
904 UpdateServerCommunity(pInfo: &pEntry->m_Info);
905 str_copy(dst&: pEntry->m_Info.m_aName, src: pEntry->m_Info.m_aAddress);
906
907 pEntry->m_Info.m_Favorite = m_pFavorites->IsFavorite(pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
908 pEntry->m_Info.m_FavoriteAllowPing = m_pFavorites->IsPingAllowed(pAddrs: pEntry->m_Info.m_aAddresses, NumAddrs: pEntry->m_Info.m_NumAddresses);
909
910 for(int i = 0; i < NumAddrs; i++)
911 {
912 m_ByAddr[pAddrs[i]] = pEntry->m_Info.m_ServerIndex;
913 }
914
915 return pEntry;
916}
917
918void CServerBrowser::OnServerInfoUpdate(const NETADDR &Addr, int Token, const CServerInfo *pInfo)
919{
920 int BasicToken = Token;
921 int ExtraToken = 0;
922 if(pInfo->m_Type == SERVERINFO_EXTENDED)
923 {
924 BasicToken = Token & 0xff;
925 ExtraToken = Token >> 8;
926 }
927
928 CServerEntry *pEntry = Find(Addr);
929
930 if(m_ServerlistType == IServerBrowser::TYPE_LAN)
931 {
932 if(!pEntry)
933 {
934 NETADDR LookupAddr = Addr;
935 if(Addr.type & NETTYPE_TW7)
936 {
937 // don't add 0.7 server if 0.6 server with the same IP and port already exists
938 LookupAddr.type &= ~NETTYPE_TW7;
939 pEntry = Find(Addr: LookupAddr);
940 if(pEntry)
941 return;
942 }
943 else
944 {
945 // replace 0.7 bridge server with 0.6
946 LookupAddr.type |= NETTYPE_TW7;
947 pEntry = Find(Addr: LookupAddr);
948 if(pEntry)
949 pEntry = ReplaceEntry(pEntry, pAddrs: &Addr, NumAddrs: 1);
950 }
951 }
952
953 NETADDR Broadcast = NETADDR_ZEROED;
954 Broadcast.type = (m_pNetClient->NetType() & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6)) | NETTYPE_LINK_BROADCAST;
955 int TokenBC = GenerateToken(Addr: Broadcast);
956 bool Drop = false;
957 Drop = Drop || BasicToken != GetBasicToken(Token: TokenBC);
958 Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(Token: TokenBC));
959 if(Drop)
960 {
961 return;
962 }
963
964 if(!pEntry)
965 pEntry = Add(pAddrs: &Addr, NumAddrs: 1);
966 }
967 else
968 {
969 if(!pEntry)
970 {
971 return;
972 }
973 int TokenAddr = GenerateToken(Addr);
974 bool Drop = false;
975 Drop = Drop || BasicToken != GetBasicToken(Token: TokenAddr);
976 Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(Token: TokenAddr));
977 if(Drop)
978 {
979 return;
980 }
981 }
982
983 if(m_ServerlistType == IServerBrowser::TYPE_LAN)
984 {
985 SetInfo(pEntry, Info: *pInfo);
986 pEntry->m_Info.m_Latency = std::min(a: static_cast<int>((time_get() - m_BroadcastTime) * 1000 / time_freq()), b: 999);
987 }
988 else if(pEntry->m_RequestTime > 0)
989 {
990 if(!pEntry->m_RequestIgnoreInfo)
991 {
992 SetInfo(pEntry, Info: *pInfo);
993 }
994
995 int Latency = std::min(a: static_cast<int>((time_get() - pEntry->m_RequestTime) * 1000 / time_freq()), b: 999);
996 if(!pEntry->m_RequestIgnoreInfo)
997 {
998 pEntry->m_Info.m_Latency = Latency;
999 }
1000 else
1001 {
1002 char aAddr[NETADDR_MAXSTRSIZE];
1003 net_addr_str(addr: &Addr, string: aAddr, max_length: sizeof(aAddr), add_port: true);
1004 dbg_msg(sys: "serverbrowser", fmt: "received ping response from %s", aAddr);
1005 SetLatency(Addr, Latency);
1006 }
1007 pEntry->m_RequestTime = -1; // Request has been answered
1008 }
1009 RemoveRequest(pEntry);
1010 RequestResort();
1011}
1012
1013void CServerBrowser::Refresh(int Type, bool Force)
1014{
1015 bool ServerListTypeChanged = Force || m_ServerlistType != Type;
1016 int OldServerListType = m_ServerlistType;
1017 m_ServerlistType = Type;
1018 secure_random_fill(bytes: m_aTokenSeed, length: sizeof(m_aTokenSeed));
1019
1020 if(Type == IServerBrowser::TYPE_LAN || (ServerListTypeChanged && OldServerListType == IServerBrowser::TYPE_LAN))
1021 CleanUp();
1022
1023 if(Type == IServerBrowser::TYPE_LAN)
1024 {
1025 unsigned char aBuffer[sizeof(SERVERBROWSE_GETINFO) + 1];
1026 CNetChunk Packet;
1027
1028 /* do the broadcast version */
1029 mem_zero(block: &Packet, size: sizeof(Packet));
1030 Packet.m_Address.type = (m_pNetClient->NetType() & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6)) | NETTYPE_LINK_BROADCAST;
1031 Packet.m_Flags = NETSENDFLAG_CONNLESS | NETSENDFLAG_EXTENDED;
1032 Packet.m_DataSize = sizeof(aBuffer);
1033 Packet.m_pData = aBuffer;
1034
1035 int Token = GenerateToken(Addr: Packet.m_Address);
1036 mem_copy(dest: aBuffer, source: SERVERBROWSE_GETINFO, size: sizeof(SERVERBROWSE_GETINFO));
1037 aBuffer[sizeof(SERVERBROWSE_GETINFO)] = GetBasicToken(Token);
1038
1039 Packet.m_aExtraData[0] = GetExtraToken(Token) >> 8;
1040 Packet.m_aExtraData[1] = GetExtraToken(Token) & 0xff;
1041
1042 m_BroadcastTime = time_get();
1043
1044 CPacker Packer;
1045 Packer.Reset();
1046 Packer.AddRaw(pData: SERVERBROWSE_GETINFO, Size: sizeof(SERVERBROWSE_GETINFO));
1047 Packer.AddInt(i: Token);
1048
1049 CNetChunk Packet7;
1050 mem_zero(block: &Packet7, size: sizeof(Packet7));
1051 Packet7.m_Address.type = (m_pNetClient->NetType() & ~(NETTYPE_WEBSOCKET_IPV4 | NETTYPE_WEBSOCKET_IPV6)) | NETTYPE_TW7 | NETTYPE_LINK_BROADCAST;
1052 Packet7.m_Flags = NETSENDFLAG_CONNLESS;
1053 Packet7.m_DataSize = Packer.Size();
1054 Packet7.m_pData = Packer.Data();
1055
1056 for(int Port = LAN_PORT_BEGIN; Port <= LAN_PORT_END; Port++)
1057 {
1058 Packet.m_Address.port = Port;
1059 m_pNetClient->Send(pChunk: &Packet);
1060
1061 Packet7.m_Address.port = Port;
1062 m_pNetClient->Send(pChunk: &Packet7);
1063 }
1064
1065 if(g_Config.m_Debug)
1066 m_pConsole->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "serverbrowser", pStr: "broadcasting for servers");
1067 }
1068 else
1069 {
1070 m_pHttp->Refresh();
1071 m_pPingCache->Load();
1072 m_RefreshingHttp = true;
1073
1074 if(ServerListTypeChanged && m_pHttp->NumServers() > 0)
1075 {
1076 CleanUp();
1077 UpdateFromHttp();
1078 Sort();
1079 }
1080 }
1081}
1082
1083void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int *pBasicToken, int *pToken, bool RandomToken) const
1084{
1085 if(g_Config.m_Debug)
1086 {
1087 char aAddrStr[NETADDR_MAXSTRSIZE];
1088 net_addr_str(addr: &Addr, string: aAddrStr, max_length: sizeof(aAddrStr), add_port: true);
1089 char aBuf[256];
1090 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "requesting server info from %s", aAddrStr);
1091 m_pConsole->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "serverbrowser", pStr: aBuf);
1092 }
1093
1094 int Token = GenerateToken(Addr);
1095 if(RandomToken)
1096 {
1097 int AvoidBasicToken = GetBasicToken(Token);
1098 do
1099 {
1100 secure_random_fill(bytes: &Token, length: sizeof(Token));
1101 Token &= 0xffffff;
1102 } while(GetBasicToken(Token) == AvoidBasicToken);
1103 }
1104 if(pToken)
1105 {
1106 *pToken = Token;
1107 }
1108 if(pBasicToken)
1109 {
1110 *pBasicToken = GetBasicToken(Token);
1111 }
1112
1113 if(Addr.type & NETTYPE_TW7)
1114 {
1115 CPacker Packer;
1116 Packer.Reset();
1117 Packer.AddRaw(pData: SERVERBROWSE_GETINFO, Size: sizeof(SERVERBROWSE_GETINFO));
1118 Packer.AddInt(i: Token);
1119
1120 CNetChunk Packet;
1121 Packet.m_ClientId = -1;
1122 Packet.m_Address = Addr;
1123 Packet.m_Flags = NETSENDFLAG_CONNLESS;
1124 Packet.m_DataSize = Packer.Size();
1125 Packet.m_pData = Packer.Data();
1126 mem_zero(block: &Packet.m_aExtraData, size: sizeof(Packet.m_aExtraData));
1127
1128 m_pNetClient->Send(pChunk: &Packet);
1129 }
1130 else
1131 {
1132 unsigned char aBuffer[sizeof(SERVERBROWSE_GETINFO) + 1];
1133 mem_copy(dest: aBuffer, source: SERVERBROWSE_GETINFO, size: sizeof(SERVERBROWSE_GETINFO));
1134 aBuffer[sizeof(SERVERBROWSE_GETINFO)] = GetBasicToken(Token);
1135
1136 CNetChunk Packet;
1137 Packet.m_ClientId = -1;
1138 Packet.m_Address = Addr;
1139 Packet.m_Flags = NETSENDFLAG_CONNLESS | NETSENDFLAG_EXTENDED;
1140 Packet.m_DataSize = sizeof(aBuffer);
1141 Packet.m_pData = aBuffer;
1142 mem_zero(block: &Packet.m_aExtraData, size: sizeof(Packet.m_aExtraData));
1143 Packet.m_aExtraData[0] = GetExtraToken(Token) >> 8;
1144 Packet.m_aExtraData[1] = GetExtraToken(Token) & 0xff;
1145
1146 m_pNetClient->Send(pChunk: &Packet);
1147 }
1148
1149 if(pEntry)
1150 pEntry->m_RequestTime = time_get();
1151}
1152
1153void CServerBrowser::RequestCurrentServer(const NETADDR &Addr) const
1154{
1155 RequestImpl(Addr, pEntry: nullptr, pBasicToken: nullptr, pToken: nullptr, RandomToken: false);
1156}
1157
1158void CServerBrowser::RequestCurrentServerWithRandomToken(const NETADDR &Addr, int *pBasicToken, int *pToken) const
1159{
1160 RequestImpl(Addr, pEntry: nullptr, pBasicToken, pToken, RandomToken: true);
1161}
1162
1163void CServerBrowser::SetCurrentServerPing(const NETADDR &Addr, int Ping)
1164{
1165 SetLatency(Addr, Latency: std::min(a: Ping, b: 999));
1166}
1167
1168void CServerBrowser::UpdateFromHttp()
1169{
1170 const int OwnLocation = DetermineOwnLocation();
1171
1172 int NumServers = m_pHttp->NumServers();
1173 m_vpServerlist.reserve(n: NumServers);
1174 std::function<bool(const NETADDR *, int)> Want = [](const NETADDR *pAddrs, int NumAddrs) { return true; };
1175 if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
1176 {
1177 Want = [this](const NETADDR *pAddrs, int NumAddrs) -> bool {
1178 return m_pFavorites->IsFavorite(pAddrs, NumAddrs) != TRISTATE::NONE;
1179 };
1180 }
1181 else if(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5)
1182 {
1183 const size_t CommunityIndex = m_ServerlistType - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1;
1184 std::vector<const CCommunity *> vpFavoriteCommunities = FavoriteCommunities();
1185 dbg_assert(CommunityIndex < vpFavoriteCommunities.size(), "Invalid community index");
1186 const CCommunity *pWantedCommunity = vpFavoriteCommunities[CommunityIndex];
1187 const bool IsNoneCommunity = str_comp(a: pWantedCommunity->Id(), b: COMMUNITY_NONE) == 0;
1188 Want = [this, pWantedCommunity, IsNoneCommunity](const NETADDR *pAddrs, int NumAddrs) -> bool {
1189 for(int AddressIndex = 0; AddressIndex < NumAddrs; AddressIndex++)
1190 {
1191 const auto CommunityServer = m_CommunityServersByAddr.find(key: CommunityAddressKey(Addr: pAddrs[AddressIndex]));
1192 if(CommunityServer != m_CommunityServersByAddr.end())
1193 {
1194 if(IsNoneCommunity)
1195 {
1196 // Servers with community "none" are not present in m_CommunityServersByAddr, so we ignore
1197 // any server that is found in this map to determine only the servers without community.
1198 return false;
1199 }
1200 else if(str_comp(a: CommunityServer->second.CommunityId(), b: pWantedCommunity->Id()) == 0)
1201 {
1202 return true;
1203 }
1204 }
1205 }
1206 return IsNoneCommunity;
1207 };
1208 }
1209
1210 for(int i = 0; i < NumServers; i++)
1211 {
1212 CServerInfo Info = m_pHttp->Server(Index: i);
1213 if(!Want(Info.m_aAddresses, Info.m_NumAddresses))
1214 {
1215 continue;
1216 }
1217 UpdateServerLatency(pInfo: &Info, OwnLocation);
1218 CServerEntry *pEntry = Add(pAddrs: Info.m_aAddresses, NumAddrs: Info.m_NumAddresses);
1219 SetInfo(pEntry, Info);
1220 pEntry->m_RequestIgnoreInfo = true;
1221 }
1222
1223 if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
1224 {
1225 const IFavorites::CEntry *pFavorites;
1226 int NumFavorites;
1227 m_pFavorites->AllEntries(ppEntries: &pFavorites, pNumEntries: &NumFavorites);
1228 for(int i = 0; i < NumFavorites; i++)
1229 {
1230 bool Found = false;
1231 for(int j = 0; j < pFavorites[i].m_NumAddrs; j++)
1232 {
1233 if(Find(Addr: pFavorites[i].m_aAddrs[j]))
1234 {
1235 Found = true;
1236 break;
1237 }
1238 }
1239 if(Found)
1240 {
1241 continue;
1242 }
1243 // (Also add favorites we're not allowed to ping.)
1244 CServerEntry *pEntry = Add(pAddrs: pFavorites[i].m_aAddrs, NumAddrs: pFavorites[i].m_NumAddrs);
1245 if(pFavorites[i].m_AllowPing)
1246 {
1247 QueueRequest(pEntry);
1248 }
1249 }
1250 }
1251
1252 RequestResort();
1253}
1254
1255void CServerBrowser::CleanUp()
1256{
1257 // clear out everything
1258 m_vSortedServerlist.clear();
1259 m_vpServerlist.clear();
1260 m_ServerlistStorage.clear();
1261 m_NumSortedPlayers = 0;
1262 m_ByAddr.clear();
1263 m_pFirstReqServer = nullptr;
1264 m_pLastReqServer = nullptr;
1265 m_NumRequests = 0;
1266 m_CurrentMaxRequests = g_Config.m_BrMaxRequests;
1267}
1268
1269void CServerBrowser::Update()
1270{
1271 int64_t Timeout = time_freq();
1272 int64_t Now = time_get();
1273
1274 const char *pHttpBestUrl;
1275 if(!m_pHttp->GetBestUrl(pBestUrl: &pHttpBestUrl) && pHttpBestUrl != m_pHttpPrevBestUrl)
1276 {
1277 str_copy(dst&: g_Config.m_BrCachedBestServerinfoUrl, src: pHttpBestUrl);
1278 m_pHttpPrevBestUrl = pHttpBestUrl;
1279 }
1280
1281 m_pHttp->Update();
1282
1283 if(m_ServerlistType != TYPE_LAN && m_RefreshingHttp && !m_pHttp->IsRefreshing())
1284 {
1285 m_RefreshingHttp = false;
1286 CleanUp();
1287 UpdateFromHttp();
1288 // TODO: move this somewhere else
1289 Sort();
1290 return;
1291 }
1292
1293 {
1294 CServerEntry *pEntry = m_pFirstReqServer;
1295 int Count = 0;
1296 while(true)
1297 {
1298 if(!pEntry) // no more entries
1299 break;
1300 if(pEntry->m_RequestTime && pEntry->m_RequestTime + Timeout < Now)
1301 {
1302 pEntry = pEntry->m_pNextReq;
1303 continue;
1304 }
1305 // no more than 10 concurrent requests
1306 if(Count == m_CurrentMaxRequests)
1307 break;
1308
1309 if(pEntry->m_RequestTime == 0)
1310 {
1311 RequestImpl(Addr: pEntry->m_Info.m_aAddresses[0], pEntry, pBasicToken: nullptr, pToken: nullptr, RandomToken: false);
1312 }
1313
1314 Count++;
1315 pEntry = pEntry->m_pNextReq;
1316 }
1317
1318 if(m_pFirstReqServer && Count == 0 && m_CurrentMaxRequests > 1) //NO More current Server Requests
1319 {
1320 //reset old ones
1321 pEntry = m_pFirstReqServer;
1322 while(true)
1323 {
1324 if(!pEntry) // no more entries
1325 break;
1326 pEntry->m_RequestTime = 0;
1327 pEntry = pEntry->m_pNextReq;
1328 }
1329
1330 //update max-requests
1331 m_CurrentMaxRequests = m_CurrentMaxRequests / 2;
1332 if(m_CurrentMaxRequests < 1)
1333 m_CurrentMaxRequests = 1;
1334 }
1335 else if(Count == 0 && m_CurrentMaxRequests == 1) //we reached the limit, just release all left requests. IF a server sends us a packet, a new request will be added automatically, so we can delete all
1336 {
1337 pEntry = m_pFirstReqServer;
1338 while(true)
1339 {
1340 if(!pEntry) // no more entries
1341 break;
1342 CServerEntry *pNext = pEntry->m_pNextReq;
1343 RemoveRequest(pEntry); //release request
1344 pEntry = pNext;
1345 }
1346 }
1347 }
1348
1349 // check if we need to resort
1350 if(m_Sorthash != SortHash() || m_NeedResort)
1351 {
1352 for(CServerEntry *pEntry : m_vpServerlist)
1353 {
1354 CServerInfo *pInfo = &pEntry->m_Info;
1355 pInfo->m_Favorite = m_pFavorites->IsFavorite(pAddrs: pInfo->m_aAddresses, NumAddrs: pInfo->m_NumAddresses);
1356 pInfo->m_FavoriteAllowPing = m_pFavorites->IsPingAllowed(pAddrs: pInfo->m_aAddresses, NumAddrs: pInfo->m_NumAddresses);
1357 }
1358 Sort();
1359 m_NeedResort = false;
1360 }
1361}
1362
1363const json_value *CServerBrowser::LoadDDNetInfo()
1364{
1365 LoadDDNetInfoJson();
1366 const int PreviousOwnLocation = DetermineOwnLocation();
1367 LoadDDNetLocation();
1368 const int OwnLocation = DetermineOwnLocation();
1369 const bool UpdateLatency = PreviousOwnLocation != OwnLocation;
1370 LoadDDNetServers();
1371 for(CServerEntry *pEntry : m_vpServerlist)
1372 {
1373 UpdateServerCommunity(pInfo: &pEntry->m_Info);
1374 UpdateServerRank(pInfo: &pEntry->m_Info);
1375 if(UpdateLatency)
1376 {
1377 UpdateServerLatency(pInfo: &pEntry->m_Info, OwnLocation);
1378 }
1379 }
1380 ValidateServerlistType();
1381 RequestResort();
1382 return m_pDDNetInfo;
1383}
1384
1385void CServerBrowser::LoadDDNetInfoJson()
1386{
1387 void *pBuf;
1388 unsigned Length;
1389 if(!m_pStorage->ReadFile(pFilename: DDNET_INFO_FILE, Type: IStorage::TYPE_SAVE, ppResult: &pBuf, pResultLen: &Length))
1390 {
1391 // Keep old info if available
1392 return;
1393 }
1394
1395 m_DDNetInfoSha256 = sha256(message: pBuf, message_len: Length);
1396
1397 json_value_free(m_pDDNetInfo);
1398 json_settings JsonSettings{};
1399 char aError[256];
1400 m_pDDNetInfo = JsonParseEx(pSettings: &JsonSettings, pJson: static_cast<json_char *>(pBuf), Length, pError: aError);
1401 free(ptr: pBuf);
1402
1403 if(m_pDDNetInfo == nullptr)
1404 {
1405 log_error("serverbrowser", "invalid info json: '%s'", aError);
1406 }
1407 else if(m_pDDNetInfo->type != json_object)
1408 {
1409 log_error("serverbrowser", "invalid info root");
1410 json_value_free(m_pDDNetInfo);
1411 m_pDDNetInfo = nullptr;
1412 }
1413}
1414
1415void CServerBrowser::LoadDDNetLocation()
1416{
1417 m_OwnLocation = CServerInfo::LOC_UNKNOWN;
1418 if(m_pDDNetInfo)
1419 {
1420 const json_value &Location = (*m_pDDNetInfo)["location"];
1421 if(Location.type != json_string || CServerInfo::ParseLocation(pResult: &m_OwnLocation, pString: Location))
1422 {
1423 log_error("serverbrowser", "invalid location");
1424 }
1425 }
1426}
1427
1428bool CServerBrowser::ParseCommunityServers(CCommunity *pCommunity, const json_value &Servers)
1429{
1430 for(unsigned ServerIndex = 0; ServerIndex < Servers.u.array.length; ++ServerIndex)
1431 {
1432 // pServer - { name, flagId, servers }
1433 const json_value &Server = Servers[ServerIndex];
1434 if(Server.type != json_object)
1435 {
1436 log_error("serverbrowser", "invalid server (ServerIndex=%u)", ServerIndex);
1437 return false;
1438 }
1439
1440 const json_value &Name = Server["name"];
1441 const json_value &FlagId = Server["flagId"];
1442 const json_value &Types = Server["servers"];
1443 if(Name.type != json_string || FlagId.type != json_integer || Types.type != json_object)
1444 {
1445 log_error("serverbrowser", "invalid server attribute (ServerIndex=%u)", ServerIndex);
1446 return false;
1447 }
1448 if(Types.u.object.length == 0)
1449 continue;
1450
1451 if(str_has_cc(str: Name.u.string.ptr))
1452 {
1453 log_error("serverbrowser", "invalid community country name (ServerIndex=%u)", ServerIndex);
1454 return false;
1455 }
1456 if(!in_range(a: FlagId.u.integer, lower: (int64_t)CountryCode::MINIMUM, upper: (int64_t)CountryCode::MAXIMUM))
1457 {
1458 log_error("serverbrowser", "invalid community country code (ServerIndex=%u)", ServerIndex);
1459 return false;
1460 }
1461 pCommunity->m_vCountries.emplace_back(args: Name.u.string.ptr, args: FlagId.u.integer);
1462 CCommunityCountry *pCountry = &pCommunity->m_vCountries.back();
1463
1464 for(unsigned TypeIndex = 0; TypeIndex < Types.u.object.length; ++TypeIndex)
1465 {
1466 const json_value &Addresses = *Types.u.object.values[TypeIndex].value;
1467 if(Addresses.type != json_array)
1468 {
1469 log_error("serverbrowser", "invalid addresses (ServerIndex=%u, TypeIndex=%u)", ServerIndex, TypeIndex);
1470 return false;
1471 }
1472 if(Addresses.u.array.length == 0)
1473 continue;
1474
1475 const char *pTypeName = Types.u.object.values[TypeIndex].name;
1476
1477 // add type if it doesn't exist already
1478 const auto CommunityType = std::find_if(first: pCommunity->m_vTypes.begin(), last: pCommunity->m_vTypes.end(), pred: [pTypeName](const auto &Elem) {
1479 return str_comp(Elem.Name(), pTypeName) == 0;
1480 });
1481 if(CommunityType == pCommunity->m_vTypes.end())
1482 {
1483 pCommunity->m_vTypes.emplace_back(args&: pTypeName);
1484 }
1485
1486 // add addresses
1487 for(unsigned AddressIndex = 0; AddressIndex < Addresses.u.array.length; ++AddressIndex)
1488 {
1489 const json_value &Address = Addresses[AddressIndex];
1490 if(Address.type != json_string)
1491 {
1492 log_error("serverbrowser", "invalid address (ServerIndex=%u, TypeIndex=%u, AddressIndex=%u)", ServerIndex, TypeIndex, AddressIndex);
1493 return false;
1494 }
1495 NETADDR NetAddr;
1496 if(net_addr_from_str(addr: &NetAddr, string: Address.u.string.ptr))
1497 {
1498 log_error("serverbrowser", "invalid address (ServerIndex=%u, TypeIndex=%u, AddressIndex=%u)", ServerIndex, TypeIndex, AddressIndex);
1499 continue;
1500 }
1501 pCountry->m_vServers.emplace_back(args&: NetAddr, args&: pTypeName);
1502 }
1503 }
1504 }
1505 return true;
1506}
1507
1508bool CServerBrowser::ParseCommunityFinishes(CCommunity *pCommunity, const json_value &Finishes)
1509{
1510 for(unsigned FinishIndex = 0; FinishIndex < Finishes.u.array.length; ++FinishIndex)
1511 {
1512 const json_value &Finish = Finishes[FinishIndex];
1513 if(Finish.type != json_string)
1514 {
1515 log_error("serverbrowser", "invalid rank (FinishIndex=%u)", FinishIndex);
1516 return false;
1517 }
1518 pCommunity->m_FinishedMaps.emplace(args: (const char *)Finish);
1519 }
1520 return true;
1521}
1522
1523void CServerBrowser::LoadDDNetServers()
1524{
1525 // Parse communities
1526 m_vCommunities.clear();
1527 m_CommunityServersByAddr.clear();
1528
1529 if(!m_pDDNetInfo)
1530 {
1531 return;
1532 }
1533
1534 const json_value &Communities = (*m_pDDNetInfo)["communities"];
1535 if(Communities.type != json_array)
1536 {
1537 return;
1538 }
1539
1540 for(unsigned CommunityIndex = 0; CommunityIndex < Communities.u.array.length; ++CommunityIndex)
1541 {
1542 const json_value &Community = Communities[CommunityIndex];
1543 if(Community.type != json_object)
1544 {
1545 log_error("serverbrowser", "invalid community (CommunityIndex=%d)", (int)CommunityIndex);
1546 continue;
1547 }
1548 const json_value &Id = Community["id"];
1549 if(Id.type != json_string)
1550 {
1551 log_error("serverbrowser", "invalid community id (CommunityIndex=%d)", (int)CommunityIndex);
1552 continue;
1553 }
1554 const json_value &Icon = Community["icon"];
1555 const json_value &IconSha256 = Icon["sha256"];
1556 const json_value &IconUrl = Icon["url"];
1557 const json_value &Name = Community["name"];
1558 const json_value HasFinishes = Community["has_finishes"];
1559 const json_value *pFinishes = &Community["finishes"];
1560 const json_value *pServers = &Community["servers"];
1561 // We accidentally set finishes/servers to be part of icon in
1562 // the past, so support that, too. Can be removed once we make
1563 // a breaking change to the whole thing, necessitating a new
1564 // endpoint.
1565 if(pFinishes->type == json_none)
1566 {
1567 pServers = &Icon["finishes"];
1568 }
1569 if(pServers->type == json_none)
1570 {
1571 pServers = &Icon["servers"];
1572 }
1573 // Backward compatibility.
1574 if(pFinishes->type == json_none)
1575 {
1576 if(str_comp(a: Id, b: COMMUNITY_DDNET) == 0)
1577 {
1578 pFinishes = &(*m_pDDNetInfo)["maps"];
1579 }
1580 }
1581 if(pServers->type == json_none)
1582 {
1583 if(str_comp(a: Id, b: COMMUNITY_DDNET) == 0)
1584 {
1585 pServers = &(*m_pDDNetInfo)["servers"];
1586 }
1587 else if(str_comp(a: Id, b: "kog") == 0)
1588 {
1589 pServers = &(*m_pDDNetInfo)["servers-kog"];
1590 }
1591 }
1592 if(false ||
1593 Icon.type != json_object ||
1594 IconSha256.type != json_string ||
1595 IconUrl.type != json_string ||
1596 Name.type != json_string ||
1597 HasFinishes.type != json_boolean ||
1598 (pFinishes->type != json_array && pFinishes->type != json_none) ||
1599 pServers->type != json_array)
1600 {
1601 log_error("serverbrowser", "invalid community attribute (CommunityId=%s)", (const char *)Id);
1602 continue;
1603 }
1604 SHA256_DIGEST ParsedIconSha256;
1605 if(sha256_from_str(out: &ParsedIconSha256, str: IconSha256) != 0)
1606 {
1607 log_error("serverbrowser", "invalid community icon sha256 (CommunityId=%s)", (const char *)Id);
1608 continue;
1609 }
1610 CCommunity NewCommunity(Id, Name, ParsedIconSha256, IconUrl);
1611 if(!ParseCommunityServers(pCommunity: &NewCommunity, Servers: *pServers))
1612 {
1613 log_error("serverbrowser", "invalid community servers (CommunityId=%s)", NewCommunity.Id());
1614 continue;
1615 }
1616 NewCommunity.m_HasFinishes = HasFinishes;
1617 if(NewCommunity.m_HasFinishes && pFinishes->type == json_array && !ParseCommunityFinishes(pCommunity: &NewCommunity, Finishes: *pFinishes))
1618 {
1619 log_error("serverbrowser", "invalid community finishes (CommunityId=%s)", NewCommunity.Id());
1620 continue;
1621 }
1622
1623 for(const auto &Country : NewCommunity.Countries())
1624 {
1625 for(const auto &Server : Country.Servers())
1626 {
1627 m_CommunityServersByAddr.emplace(args: CommunityAddressKey(Addr: Server.Address()), args: CCommunityServer(NewCommunity.Id(), Country.Name(), Server.TypeName()));
1628 }
1629 }
1630 m_vCommunities.push_back(x: std::move(NewCommunity));
1631 }
1632
1633 // Add default none community
1634 {
1635 CCommunity NoneCommunity(COMMUNITY_NONE, "None", std::nullopt, "");
1636 NoneCommunity.m_vCountries.emplace_back(args: COMMUNITY_COUNTRY_NONE, args: CountryCode::DEFAULT);
1637 NoneCommunity.m_vTypes.emplace_back(args: COMMUNITY_TYPE_NONE);
1638 m_vCommunities.push_back(x: std::move(NoneCommunity));
1639 }
1640
1641 // Remove unknown elements from exclude lists
1642 CleanFilters();
1643}
1644
1645void CServerBrowser::UpdateServerFilteredPlayers(CServerInfo *pInfo) const
1646{
1647 pInfo->m_NumFilteredPlayers = g_Config.m_BrFilterSpectators ? pInfo->m_NumPlayers : pInfo->m_NumClients;
1648 if(g_Config.m_BrFilterConnectingPlayers)
1649 {
1650 for(const auto &Client : pInfo->m_vClients)
1651 {
1652 if((!g_Config.m_BrFilterSpectators || Client.m_Player) && str_comp(a: Client.m_aName, b: "(connecting)") == 0 && Client.m_aClan[0] == '\0')
1653 pInfo->m_NumFilteredPlayers--;
1654 }
1655 }
1656}
1657
1658void CServerBrowser::UpdateServerFriends(CServerInfo *pInfo) const
1659{
1660 pInfo->m_FriendState = IFriends::FRIEND_NO;
1661 pInfo->m_FriendNum = 0;
1662 for(auto &Client : pInfo->m_vClients)
1663 {
1664 Client.m_FriendState = m_pFriends->GetFriendState(pName: Client.m_aName, pClan: Client.m_aClan);
1665 pInfo->m_FriendState = std::max(a: pInfo->m_FriendState, b: Client.m_FriendState);
1666 if(Client.m_FriendState != IFriends::FRIEND_NO)
1667 pInfo->m_FriendNum++;
1668 }
1669}
1670
1671void CServerBrowser::UpdateServerCommunity(CServerInfo *pInfo) const
1672{
1673 for(int AddressIndex = 0; AddressIndex < pInfo->m_NumAddresses; AddressIndex++)
1674 {
1675 const auto Community = m_CommunityServersByAddr.find(key: CommunityAddressKey(Addr: pInfo->m_aAddresses[AddressIndex]));
1676 if(Community != m_CommunityServersByAddr.end())
1677 {
1678 str_copy(dst&: pInfo->m_aCommunityId, src: Community->second.CommunityId());
1679 str_copy(dst&: pInfo->m_aCommunityCountry, src: Community->second.CountryName());
1680 str_copy(dst&: pInfo->m_aCommunityType, src: Community->second.TypeName());
1681 return;
1682 }
1683 }
1684 str_copy(dst&: pInfo->m_aCommunityId, src: COMMUNITY_NONE);
1685 str_copy(dst&: pInfo->m_aCommunityCountry, src: COMMUNITY_COUNTRY_NONE);
1686 str_copy(dst&: pInfo->m_aCommunityType, src: COMMUNITY_TYPE_NONE);
1687}
1688
1689void CServerBrowser::UpdateServerRank(CServerInfo *pInfo) const
1690{
1691 const CCommunity *pCommunity = Community(pCommunityId: pInfo->m_aCommunityId);
1692 pInfo->m_HasRank = pCommunity == nullptr ? CServerInfo::RANK_UNAVAILABLE : pCommunity->HasRank(pMap: pInfo->m_aMap);
1693}
1694
1695void CServerBrowser::UpdateServerLatency(CServerInfo *pInfo, int OwnLocation) const
1696{
1697 int Ping = m_pPingCache->GetPing(pAddrs: pInfo->m_aAddresses, NumAddrs: pInfo->m_NumAddresses);
1698 pInfo->m_LatencyIsEstimated = Ping == -1;
1699 if(pInfo->m_LatencyIsEstimated)
1700 {
1701 pInfo->m_Latency = CServerInfo::EstimateLatency(Loc1: OwnLocation, Loc2: pInfo->m_Location);
1702 }
1703 else
1704 {
1705 pInfo->m_Latency = Ping;
1706 }
1707}
1708
1709int CServerBrowser::DetermineOwnLocation() const
1710{
1711 if(str_comp(a: g_Config.m_BrLocation, b: "auto") == 0)
1712 {
1713 return m_OwnLocation;
1714 }
1715
1716 int OwnLocation;
1717 if(CServerInfo::ParseLocation(pResult: &OwnLocation, pString: g_Config.m_BrLocation))
1718 {
1719 log_error("serverbrowser", "Cannot parse br_location: '%s'", g_Config.m_BrLocation);
1720 }
1721 return OwnLocation;
1722}
1723
1724void CServerBrowser::ValidateServerlistType()
1725{
1726 if(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 &&
1727 m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5)
1728 {
1729 const size_t CommunityIndex = m_ServerlistType - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1;
1730 if(CommunityIndex >= FavoriteCommunities().size())
1731 {
1732 // Reset to internet type if there is no favorite community for the current browser type,
1733 // in case communities have been removed.
1734 m_ServerlistType = IServerBrowser::TYPE_INTERNET;
1735 }
1736 }
1737}
1738
1739const char *CServerBrowser::GetTutorialServer()
1740{
1741 const CCommunity *pCommunity = Community(pCommunityId: COMMUNITY_DDNET);
1742 if(pCommunity == nullptr)
1743 return nullptr;
1744
1745 const char *pBestAddr = nullptr;
1746 int BestLatency = std::numeric_limits<int>::max();
1747 for(const auto &Country : pCommunity->Countries())
1748 {
1749 for(const auto &Server : Country.Servers())
1750 {
1751 if(str_comp(a: Server.TypeName(), b: "Tutorial") != 0)
1752 continue;
1753 const CServerEntry *pEntry = Find(Addr: Server.Address());
1754 if(!pEntry)
1755 continue;
1756 if(pEntry->m_Info.m_NumPlayers > pEntry->m_Info.m_MaxPlayers - 10)
1757 continue;
1758 if(pEntry->m_Info.m_Latency >= BestLatency)
1759 continue;
1760 BestLatency = pEntry->m_Info.m_Latency;
1761 pBestAddr = pEntry->m_Info.m_aAddress;
1762 }
1763 }
1764 return pBestAddr;
1765}
1766
1767bool CServerBrowser::IsRefreshing() const
1768{
1769 return m_pFirstReqServer != nullptr;
1770}
1771
1772bool CServerBrowser::IsGettingServerlist() const
1773{
1774 return m_pHttp->IsRefreshing();
1775}
1776
1777bool CServerBrowser::IsServerlistError() const
1778{
1779 return m_pHttp->IsError();
1780}
1781
1782int CServerBrowser::LoadingProgression() const
1783{
1784 if(m_vpServerlist.empty())
1785 return 0;
1786
1787 int Servers = m_vpServerlist.size();
1788 int Loaded = m_vpServerlist.size() - m_NumRequests;
1789 return 100.0f * Loaded / Servers;
1790}
1791
1792bool CCommunity::HasCountry(const char *pCountryName) const
1793{
1794 return std::find_if(first: Countries().begin(), last: Countries().end(), pred: [pCountryName](const auto &Elem) {
1795 return str_comp(Elem.Name(), pCountryName) == 0;
1796 }) != Countries().end();
1797}
1798
1799bool CCommunity::HasType(const char *pTypeName) const
1800{
1801 return std::find_if(first: Types().begin(), last: Types().end(), pred: [pTypeName](const auto &Elem) {
1802 return str_comp(Elem.Name(), pTypeName) == 0;
1803 }) != Types().end();
1804}
1805
1806CServerInfo::ERankState CCommunity::HasRank(const char *pMap) const
1807{
1808 if(!HasRanks())
1809 return CServerInfo::RANK_UNAVAILABLE;
1810 const CCommunityMap Needle(pMap);
1811 return !m_FinishedMaps.contains(x: Needle) ? CServerInfo::RANK_UNRANKED : CServerInfo::RANK_RANKED;
1812}
1813
1814const std::vector<CCommunity> &CServerBrowser::Communities() const
1815{
1816 return m_vCommunities;
1817}
1818
1819const CCommunity *CServerBrowser::Community(const char *pCommunityId) const
1820{
1821 const auto Community = std::find_if(first: Communities().begin(), last: Communities().end(), pred: [pCommunityId](const auto &Elem) {
1822 return str_comp(Elem.Id(), pCommunityId) == 0;
1823 });
1824 return Community == Communities().end() ? nullptr : &(*Community);
1825}
1826
1827std::vector<const CCommunity *> CServerBrowser::SelectedCommunities() const
1828{
1829 std::vector<const CCommunity *> vpSelected;
1830 for(const auto &Community : Communities())
1831 {
1832 if(!CommunitiesFilter().Filtered(pCommunityId: Community.Id()))
1833 {
1834 vpSelected.push_back(x: &Community);
1835 }
1836 }
1837 return vpSelected;
1838}
1839
1840std::vector<const CCommunity *> CServerBrowser::FavoriteCommunities() const
1841{
1842 // This is done differently than SelectedCommunities because the favorite
1843 // communities should be returned in the order specified by the user.
1844 std::vector<const CCommunity *> vpFavorites;
1845 for(const auto &CommunityId : FavoriteCommunitiesFilter().Entries())
1846 {
1847 const CCommunity *pCommunity = Community(pCommunityId: CommunityId.Id());
1848 if(pCommunity)
1849 {
1850 vpFavorites.push_back(x: pCommunity);
1851 }
1852 }
1853 return vpFavorites;
1854}
1855
1856std::vector<const CCommunity *> CServerBrowser::CurrentCommunities() const
1857{
1858 if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
1859 {
1860 return SelectedCommunities();
1861 }
1862 else if(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5)
1863 {
1864 const size_t CommunityIndex = m_ServerlistType - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1;
1865 std::vector<const CCommunity *> vpFavoriteCommunities = FavoriteCommunities();
1866 dbg_assert(CommunityIndex < vpFavoriteCommunities.size(), "Invalid favorite community serverbrowser type");
1867 return {vpFavoriteCommunities[CommunityIndex]};
1868 }
1869 else
1870 {
1871 return {};
1872 }
1873}
1874
1875unsigned CServerBrowser::CurrentCommunitiesHash() const
1876{
1877 unsigned Hash = 5381;
1878 for(const CCommunity *pCommunity : CurrentCommunities())
1879 {
1880 Hash = (Hash << 5) + Hash + str_quickhash(str: pCommunity->Id());
1881 }
1882 return Hash;
1883}
1884
1885void CCommunityCache::Update(bool Force)
1886{
1887 const unsigned CommunitiesHash = m_pServerBrowser->CurrentCommunitiesHash();
1888 const bool TypeChanged = m_LastType != m_pServerBrowser->GetCurrentType();
1889 const bool CurrentCommunitiesChanged = m_LastType == m_pServerBrowser->GetCurrentType() && m_SelectedCommunitiesHash != CommunitiesHash;
1890 if(CurrentCommunitiesChanged && m_pServerBrowser->GetCurrentType() >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_pServerBrowser->GetCurrentType() <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5)
1891 {
1892 // Favorite community was changed while its type is active,
1893 // refresh to get correct serverlist for updated community.
1894 m_pServerBrowser->Refresh(Type: m_pServerBrowser->GetCurrentType(), Force: true);
1895 }
1896
1897 if(!Force && m_InfoSha256 == m_pServerBrowser->DDNetInfoSha256() &&
1898 !CurrentCommunitiesChanged && !TypeChanged)
1899 {
1900 return;
1901 }
1902
1903 m_InfoSha256 = m_pServerBrowser->DDNetInfoSha256();
1904 m_LastType = m_pServerBrowser->GetCurrentType();
1905 m_SelectedCommunitiesHash = CommunitiesHash;
1906 m_vpSelectedCommunities = m_pServerBrowser->CurrentCommunities();
1907
1908 m_vpSelectableCountries.clear();
1909 m_vpSelectableTypes.clear();
1910 for(const CCommunity *pCommunity : m_vpSelectedCommunities)
1911 {
1912 for(const auto &Country : pCommunity->Countries())
1913 {
1914 const auto ExistingCountry = std::find_if(first: m_vpSelectableCountries.begin(), last: m_vpSelectableCountries.end(), pred: [&](const CCommunityCountry *pOther) {
1915 return str_comp(a: Country.Name(), b: pOther->Name()) == 0 && Country.FlagId() == pOther->FlagId();
1916 });
1917 if(ExistingCountry == m_vpSelectableCountries.end())
1918 {
1919 m_vpSelectableCountries.push_back(x: &Country);
1920 }
1921 }
1922 for(const auto &Type : pCommunity->Types())
1923 {
1924 const auto ExistingType = std::find_if(first: m_vpSelectableTypes.begin(), last: m_vpSelectableTypes.end(), pred: [&](const CCommunityType *pOther) {
1925 return str_comp(a: Type.Name(), b: pOther->Name()) == 0;
1926 });
1927 if(ExistingType == m_vpSelectableTypes.end())
1928 {
1929 m_vpSelectableTypes.push_back(x: &Type);
1930 }
1931 }
1932 }
1933
1934 m_AnyRanksAvailable = std::any_of(first: m_vpSelectedCommunities.begin(), last: m_vpSelectedCommunities.end(), pred: [](const CCommunity *pCommunity) {
1935 return pCommunity->HasRanks();
1936 });
1937
1938 // Country/type filters not shown if there are no countries and types, or if only the none-community is selected
1939 m_CountryTypesFilterAvailable = (!m_vpSelectableCountries.empty() || !m_vpSelectableTypes.empty()) &&
1940 (m_vpSelectedCommunities.size() != 1 || str_comp(a: m_vpSelectedCommunities[0]->Id(), b: IServerBrowser::COMMUNITY_NONE) != 0);
1941
1942 if(m_pServerBrowser->GetCurrentType() >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_pServerBrowser->GetCurrentType() <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5)
1943 {
1944 const size_t CommunityIndex = m_pServerBrowser->GetCurrentType() - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1;
1945 std::vector<const CCommunity *> vpFavoriteCommunities = m_pServerBrowser->FavoriteCommunities();
1946 dbg_assert(CommunityIndex < vpFavoriteCommunities.size(), "Invalid favorite community serverbrowser type");
1947 m_pCountryTypeFilterKey = vpFavoriteCommunities[CommunityIndex]->Id();
1948 }
1949 else
1950 {
1951 m_pCountryTypeFilterKey = IServerBrowser::COMMUNITY_ALL;
1952 }
1953
1954 m_pServerBrowser->CleanFilters();
1955}
1956
1957void CFavoriteCommunityFilterList::Add(const char *pCommunityId)
1958{
1959 // Remove community if it's already a favorite, so it will be added again at
1960 // the end of the list, to allow setting the entire list easier with binds.
1961 Remove(pCommunityId);
1962
1963 // Ensure maximum number of favorite communities, by removing the least-recently
1964 // added community from the beginning, when the maximum number of favorite
1965 // communities has been reached.
1966 constexpr size_t MaxFavoriteCommunities = IServerBrowser::TYPE_FAVORITE_COMMUNITY_5 - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 + 1;
1967 if(m_vEntries.size() >= MaxFavoriteCommunities)
1968 {
1969 dbg_assert(m_vEntries.size() == MaxFavoriteCommunities, "Maximum number of communities can never be exceeded");
1970 m_vEntries.erase(position: m_vEntries.begin());
1971 }
1972 m_vEntries.emplace_back(args&: pCommunityId);
1973}
1974
1975void CFavoriteCommunityFilterList::Remove(const char *pCommunityId)
1976{
1977 auto FoundCommunity = std::find(first: m_vEntries.begin(), last: m_vEntries.end(), val: CCommunityId(pCommunityId));
1978 if(FoundCommunity != m_vEntries.end())
1979 {
1980 m_vEntries.erase(position: FoundCommunity);
1981 }
1982}
1983
1984void CFavoriteCommunityFilterList::Clear()
1985{
1986 m_vEntries.clear();
1987}
1988
1989bool CFavoriteCommunityFilterList::Filtered(const char *pCommunityId) const
1990{
1991 return std::find(first: m_vEntries.begin(), last: m_vEntries.end(), val: CCommunityId(pCommunityId)) != m_vEntries.end();
1992}
1993
1994bool CFavoriteCommunityFilterList::Empty() const
1995{
1996 return m_vEntries.empty();
1997}
1998
1999void CFavoriteCommunityFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
2000{
2001 auto It = std::remove_if(first: m_vEntries.begin(), last: m_vEntries.end(), pred: [&](const auto &Community) {
2002 return std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) {
2003 return str_comp(Community.Id(), AllowedCommunity.Id()) == 0;
2004 }) == vAllowedCommunities.end();
2005 });
2006 m_vEntries.erase(first: It, last: m_vEntries.end());
2007}
2008
2009void CFavoriteCommunityFilterList::Save(IConfigManager *pConfigManager) const
2010{
2011 char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH];
2012 for(const auto &FavoriteCommunity : m_vEntries)
2013 {
2014 str_copy(dst&: aBuf, src: "add_favorite_community \"");
2015 str_append(dst&: aBuf, src: FavoriteCommunity.Id());
2016 str_append(dst&: aBuf, src: "\"");
2017 pConfigManager->WriteLine(pLine: aBuf);
2018 }
2019}
2020
2021const std::vector<CCommunityId> &CFavoriteCommunityFilterList::Entries() const
2022{
2023 return m_vEntries;
2024}
2025
2026template<typename TNamedElement, typename TElementName>
2027static bool IsSubsetEquals(const std::vector<const TNamedElement *> &vpLeft, const std::set<TElementName> &Right)
2028{
2029 return vpLeft.size() <= Right.size() && std::all_of(vpLeft.begin(), vpLeft.end(), [&](const TNamedElement *pElem) {
2030 return Right.contains(TElementName(pElem->Name()));
2031 });
2032}
2033
2034void CExcludedCommunityFilterList::Add(const char *pCommunityId)
2035{
2036 m_Entries.emplace(args&: pCommunityId);
2037}
2038
2039void CExcludedCommunityFilterList::Remove(const char *pCommunityId)
2040{
2041 m_Entries.erase(x: CCommunityId(pCommunityId));
2042}
2043
2044void CExcludedCommunityFilterList::Clear()
2045{
2046 m_Entries.clear();
2047}
2048
2049bool CExcludedCommunityFilterList::Filtered(const char *pCommunityId) const
2050{
2051 return std::find(first: m_Entries.begin(), last: m_Entries.end(), val: CCommunityId(pCommunityId)) != m_Entries.end();
2052}
2053
2054bool CExcludedCommunityFilterList::Empty() const
2055{
2056 return m_Entries.empty();
2057}
2058
2059void CExcludedCommunityFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
2060{
2061 for(auto It = m_Entries.begin(); It != m_Entries.end();)
2062 {
2063 const bool Found = std::find_if(first: vAllowedCommunities.begin(), last: vAllowedCommunities.end(), pred: [&](const CCommunity &AllowedCommunity) {
2064 return str_comp(a: It->Id(), b: AllowedCommunity.Id()) == 0;
2065 }) != vAllowedCommunities.end();
2066 if(Found)
2067 {
2068 ++It;
2069 }
2070 else
2071 {
2072 It = m_Entries.erase(position: It);
2073 }
2074 }
2075 // Prevent filter that would exclude all allowed communities
2076 if(m_Entries.size() == vAllowedCommunities.size())
2077 {
2078 m_Entries.clear();
2079 }
2080}
2081
2082void CExcludedCommunityFilterList::Save(IConfigManager *pConfigManager) const
2083{
2084 char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH];
2085 for(const auto &ExcludedCommunity : m_Entries)
2086 {
2087 str_copy(dst&: aBuf, src: "add_excluded_community \"");
2088 str_append(dst&: aBuf, src: ExcludedCommunity.Id());
2089 str_append(dst&: aBuf, src: "\"");
2090 pConfigManager->WriteLine(pLine: aBuf);
2091 }
2092}
2093
2094void CExcludedCommunityCountryFilterList::Add(const char *pCountryName)
2095{
2096 // Handle special case that all selectable entries are currently filtered,
2097 // where adding more entries to the exclusion list would have no effect.
2098 auto CommunityEntry = m_Entries.find(x: m_pCommunityCache->CountryTypeFilterKey());
2099 if(CommunityEntry != m_Entries.end() && IsSubsetEquals(vpLeft: m_pCommunityCache->SelectableCountries(), Right: CommunityEntry->second))
2100 {
2101 for(const CCommunityCountry *pSelectableCountry : m_pCommunityCache->SelectableCountries())
2102 {
2103 CommunityEntry->second.erase(x: pSelectableCountry->Name());
2104 }
2105 }
2106
2107 Add(pCommunityId: m_pCommunityCache->CountryTypeFilterKey(), pCountryName);
2108}
2109
2110void CExcludedCommunityCountryFilterList::Add(const char *pCommunityId, const char *pCountryName)
2111{
2112 CCommunityId CommunityId(pCommunityId);
2113 if(!m_Entries.contains(x: CommunityId))
2114 {
2115 m_Entries[CommunityId] = {};
2116 }
2117 m_Entries[CommunityId].emplace(args&: pCountryName);
2118}
2119
2120void CExcludedCommunityCountryFilterList::Remove(const char *pCountryName)
2121{
2122 Remove(pCommunityId: m_pCommunityCache->CountryTypeFilterKey(), pCountryName);
2123}
2124
2125void CExcludedCommunityCountryFilterList::Remove(const char *pCommunityId, const char *pCountryName)
2126{
2127 auto CommunityEntry = m_Entries.find(x: CCommunityId(pCommunityId));
2128 if(CommunityEntry != m_Entries.end())
2129 {
2130 CommunityEntry->second.erase(x: pCountryName);
2131 }
2132}
2133
2134void CExcludedCommunityCountryFilterList::Clear()
2135{
2136 auto CommunityEntry = m_Entries.find(x: m_pCommunityCache->CountryTypeFilterKey());
2137 if(CommunityEntry != m_Entries.end())
2138 {
2139 CommunityEntry->second.clear();
2140 }
2141}
2142
2143bool CExcludedCommunityCountryFilterList::Filtered(const char *pCountryName) const
2144{
2145 auto CommunityEntry = m_Entries.find(x: CCommunityId(m_pCommunityCache->CountryTypeFilterKey()));
2146 if(CommunityEntry == m_Entries.end())
2147 return false;
2148
2149 const auto &CountryEntries = CommunityEntry->second;
2150 return !IsSubsetEquals(vpLeft: m_pCommunityCache->SelectableCountries(), Right: CountryEntries) && CountryEntries.contains(x: CCommunityCountryName(pCountryName));
2151}
2152
2153bool CExcludedCommunityCountryFilterList::Empty() const
2154{
2155 auto CommunityEntry = m_Entries.find(x: CCommunityId(m_pCommunityCache->CountryTypeFilterKey()));
2156 return CommunityEntry == m_Entries.end() ||
2157 CommunityEntry->second.empty() ||
2158 IsSubsetEquals(vpLeft: m_pCommunityCache->SelectableCountries(), Right: CommunityEntry->second);
2159}
2160
2161void CExcludedCommunityCountryFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
2162{
2163 for(auto It = m_Entries.begin(); It != m_Entries.end();)
2164 {
2165 const bool AllEntry = str_comp(a: It->first.Id(), b: IServerBrowser::COMMUNITY_ALL) == 0;
2166 const bool Found = AllEntry || std::find_if(first: vAllowedCommunities.begin(), last: vAllowedCommunities.end(), pred: [&](const CCommunity &AllowedCommunity) {
2167 return str_comp(a: It->first.Id(), b: AllowedCommunity.Id()) == 0;
2168 }) != vAllowedCommunities.end();
2169 if(Found)
2170 {
2171 ++It;
2172 }
2173 else
2174 {
2175 It = m_Entries.erase(position: It);
2176 }
2177 }
2178
2179 for(const CCommunity &AllowedCommunity : vAllowedCommunities)
2180 {
2181 auto CommunityEntry = m_Entries.find(x: CCommunityId(AllowedCommunity.Id()));
2182 if(CommunityEntry != m_Entries.end())
2183 {
2184 auto &CountryEntries = CommunityEntry->second;
2185 for(auto It = CountryEntries.begin(); It != CountryEntries.end();)
2186 {
2187 if(AllowedCommunity.HasCountry(pCountryName: It->Name()))
2188 {
2189 ++It;
2190 }
2191 else
2192 {
2193 It = CountryEntries.erase(position: It);
2194 }
2195 }
2196 // Prevent filter that would exclude all allowed countries
2197 if(CountryEntries.size() == AllowedCommunity.Countries().size())
2198 {
2199 CountryEntries.clear();
2200 }
2201 }
2202 }
2203
2204 auto AllCommunityEntry = m_Entries.find(x: CCommunityId(IServerBrowser::COMMUNITY_ALL));
2205 if(AllCommunityEntry != m_Entries.end())
2206 {
2207 auto &CountryEntries = AllCommunityEntry->second;
2208 for(auto It = CountryEntries.begin(); It != CountryEntries.end();)
2209 {
2210 if(std::any_of(first: vAllowedCommunities.begin(), last: vAllowedCommunities.end(), pred: [&](const auto &Community) { return Community.HasCountry(It->Name()); }))
2211 {
2212 ++It;
2213 }
2214 else
2215 {
2216 It = CountryEntries.erase(position: It);
2217 }
2218 }
2219 // Prevent filter that would exclude all allowed countries
2220 std::set<CCommunityCountryName> UniqueCountries;
2221 for(const CCommunity &AllowedCommunity : vAllowedCommunities)
2222 {
2223 for(const CCommunityCountry &Country : AllowedCommunity.Countries())
2224 {
2225 UniqueCountries.emplace(args: Country.Name());
2226 }
2227 }
2228 if(CountryEntries.size() == UniqueCountries.size())
2229 {
2230 CountryEntries.clear();
2231 }
2232 }
2233}
2234
2235void CExcludedCommunityCountryFilterList::Save(IConfigManager *pConfigManager) const
2236{
2237 char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH + CServerInfo::MAX_COMMUNITY_COUNTRY_LENGTH];
2238 for(const auto &[Community, Countries] : m_Entries)
2239 {
2240 for(const auto &Country : Countries)
2241 {
2242 str_copy(dst&: aBuf, src: "add_excluded_country \"");
2243 str_append(dst&: aBuf, src: Community.Id());
2244 str_append(dst&: aBuf, src: "\" \"");
2245 str_append(dst&: aBuf, src: Country.Name());
2246 str_append(dst&: aBuf, src: "\"");
2247 pConfigManager->WriteLine(pLine: aBuf);
2248 }
2249 }
2250}
2251
2252void CExcludedCommunityTypeFilterList::Add(const char *pTypeName)
2253{
2254 // Handle special case that all selectable entries are currently filtered,
2255 // where adding more entries to the exclusion list would have no effect.
2256 auto CommunityEntry = m_Entries.find(x: m_pCommunityCache->CountryTypeFilterKey());
2257 if(CommunityEntry != m_Entries.end() && IsSubsetEquals(vpLeft: m_pCommunityCache->SelectableTypes(), Right: CommunityEntry->second))
2258 {
2259 for(const CCommunityType *pSelectableType : m_pCommunityCache->SelectableTypes())
2260 {
2261 CommunityEntry->second.erase(x: pSelectableType->Name());
2262 }
2263 }
2264
2265 Add(pCommunityId: m_pCommunityCache->CountryTypeFilterKey(), pTypeName);
2266}
2267
2268void CExcludedCommunityTypeFilterList::Add(const char *pCommunityId, const char *pTypeName)
2269{
2270 CCommunityId CommunityId(pCommunityId);
2271 if(!m_Entries.contains(x: CommunityId))
2272 {
2273 m_Entries[CommunityId] = {};
2274 }
2275 m_Entries[CommunityId].emplace(args&: pTypeName);
2276}
2277
2278void CExcludedCommunityTypeFilterList::Remove(const char *pTypeName)
2279{
2280 Remove(pCommunityId: m_pCommunityCache->CountryTypeFilterKey(), pTypeName);
2281}
2282
2283void CExcludedCommunityTypeFilterList::Remove(const char *pCommunityId, const char *pTypeName)
2284{
2285 auto CommunityEntry = m_Entries.find(x: CCommunityId(pCommunityId));
2286 if(CommunityEntry != m_Entries.end())
2287 {
2288 CommunityEntry->second.erase(x: pTypeName);
2289 }
2290}
2291
2292void CExcludedCommunityTypeFilterList::Clear()
2293{
2294 auto CommunityEntry = m_Entries.find(x: m_pCommunityCache->CountryTypeFilterKey());
2295 if(CommunityEntry != m_Entries.end())
2296 {
2297 CommunityEntry->second.clear();
2298 }
2299}
2300
2301bool CExcludedCommunityTypeFilterList::Filtered(const char *pTypeName) const
2302{
2303 auto CommunityEntry = m_Entries.find(x: CCommunityId(m_pCommunityCache->CountryTypeFilterKey()));
2304 if(CommunityEntry == m_Entries.end())
2305 return false;
2306
2307 const auto &TypeEntries = CommunityEntry->second;
2308 return !IsSubsetEquals(vpLeft: m_pCommunityCache->SelectableTypes(), Right: TypeEntries) && TypeEntries.contains(x: CCommunityTypeName(pTypeName));
2309}
2310
2311bool CExcludedCommunityTypeFilterList::Empty() const
2312{
2313 auto CommunityEntry = m_Entries.find(x: CCommunityId(m_pCommunityCache->CountryTypeFilterKey()));
2314 return CommunityEntry == m_Entries.end() ||
2315 CommunityEntry->second.empty() ||
2316 IsSubsetEquals(vpLeft: m_pCommunityCache->SelectableTypes(), Right: CommunityEntry->second);
2317}
2318
2319void CExcludedCommunityTypeFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
2320{
2321 for(auto It = m_Entries.begin(); It != m_Entries.end();)
2322 {
2323 const bool AllEntry = str_comp(a: It->first.Id(), b: IServerBrowser::COMMUNITY_ALL) == 0;
2324 const bool Found = AllEntry || std::find_if(first: vAllowedCommunities.begin(), last: vAllowedCommunities.end(), pred: [&](const CCommunity &AllowedCommunity) {
2325 return str_comp(a: It->first.Id(), b: AllowedCommunity.Id()) == 0;
2326 }) != vAllowedCommunities.end();
2327 if(Found)
2328 {
2329 ++It;
2330 }
2331 else
2332 {
2333 It = m_Entries.erase(position: It);
2334 }
2335 }
2336
2337 for(const CCommunity &AllowedCommunity : vAllowedCommunities)
2338 {
2339 auto CommunityEntry = m_Entries.find(x: CCommunityId(AllowedCommunity.Id()));
2340 if(CommunityEntry != m_Entries.end())
2341 {
2342 auto &TypeEntries = CommunityEntry->second;
2343 for(auto It = TypeEntries.begin(); It != TypeEntries.end();)
2344 {
2345 if(AllowedCommunity.HasType(pTypeName: It->Name()))
2346 {
2347 ++It;
2348 }
2349 else
2350 {
2351 It = TypeEntries.erase(position: It);
2352 }
2353 }
2354 // Prevent filter that would exclude all allowed types
2355 if(TypeEntries.size() == AllowedCommunity.Types().size())
2356 {
2357 TypeEntries.clear();
2358 }
2359 }
2360 }
2361
2362 auto AllCommunityEntry = m_Entries.find(x: CCommunityId(IServerBrowser::COMMUNITY_ALL));
2363 if(AllCommunityEntry != m_Entries.end())
2364 {
2365 auto &TypeEntries = AllCommunityEntry->second;
2366 for(auto It = TypeEntries.begin(); It != TypeEntries.end();)
2367 {
2368 if(std::any_of(first: vAllowedCommunities.begin(), last: vAllowedCommunities.end(), pred: [&](const auto &Community) { return Community.HasType(It->Name()); }))
2369 {
2370 ++It;
2371 }
2372 else
2373 {
2374 It = TypeEntries.erase(position: It);
2375 }
2376 }
2377 // Prevent filter that would exclude all allowed types
2378 std::unordered_set<CCommunityCountryName> UniqueTypes;
2379 for(const CCommunity &AllowedCommunity : vAllowedCommunities)
2380 {
2381 for(const CCommunityType &Type : AllowedCommunity.Types())
2382 {
2383 UniqueTypes.emplace(args: Type.Name());
2384 }
2385 }
2386 if(TypeEntries.size() == UniqueTypes.size())
2387 {
2388 TypeEntries.clear();
2389 }
2390 }
2391}
2392
2393void CExcludedCommunityTypeFilterList::Save(IConfigManager *pConfigManager) const
2394{
2395 char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH + CServerInfo::MAX_COMMUNITY_TYPE_LENGTH];
2396 for(const auto &[Community, Types] : m_Entries)
2397 {
2398 for(const auto &Type : Types)
2399 {
2400 str_copy(dst&: aBuf, src: "add_excluded_type \"");
2401 str_append(dst&: aBuf, src: Community.Id());
2402 str_append(dst&: aBuf, src: "\" \"");
2403 str_append(dst&: aBuf, src: Type.Name());
2404 str_append(dst&: aBuf, src: "\"");
2405 pConfigManager->WriteLine(pLine: aBuf);
2406 }
2407 }
2408}
2409
2410void CServerBrowser::CleanFilters()
2411{
2412 // Keep filters if we failed to load any communities
2413 if(Communities().empty())
2414 return;
2415 FavoriteCommunitiesFilter().Clean(vAllowedCommunities: Communities());
2416 CommunitiesFilter().Clean(vAllowedCommunities: Communities());
2417 CountriesFilter().Clean(vAllowedCommunities: Communities());
2418 TypesFilter().Clean(vAllowedCommunities: Communities());
2419}
2420
2421bool CServerBrowser::IsRegistered(const NETADDR &Addr)
2422{
2423 const int NumServers = m_pHttp->NumServers();
2424 for(int i = 0; i < NumServers; i++)
2425 {
2426 const CServerInfo &Info = m_pHttp->Server(Index: i);
2427 for(int j = 0; j < Info.m_NumAddresses; j++)
2428 {
2429 if(net_addr_comp(a: &Info.m_aAddresses[j], b: &Addr) == 0)
2430 {
2431 return true;
2432 }
2433 }
2434 }
2435 return false;
2436}
2437
2438int CServerInfo::EstimateLatency(int Loc1, int Loc2)
2439{
2440 if(Loc1 == LOC_UNKNOWN || Loc2 == LOC_UNKNOWN)
2441 {
2442 return 999;
2443 }
2444 if(Loc1 != Loc2)
2445 {
2446 return 199;
2447 }
2448 return 99;
2449}
2450
2451ColorRGBA CServerInfo::GametypeColor(const char *pGametype)
2452{
2453 ColorHSLA HslaColor;
2454 if(str_comp(a: pGametype, b: "DM") == 0 || str_comp(a: pGametype, b: "TDM") == 0 || str_comp(a: pGametype, b: "CTF") == 0 || str_comp(a: pGametype, b: "LMS") == 0 || str_comp(a: pGametype, b: "LTS") == 0)
2455 HslaColor = ColorHSLA(0.33f, 1.0f, 0.75f);
2456 else if(str_find_nocase(haystack: pGametype, needle: "catch"))
2457 HslaColor = ColorHSLA(0.17f, 1.0f, 0.75f);
2458 else if(str_find_nocase(haystack: pGametype, needle: "dm") || str_find_nocase(haystack: pGametype, needle: "tdm") || str_find_nocase(haystack: pGametype, needle: "ctf") || str_find_nocase(haystack: pGametype, needle: "lms") || str_find_nocase(haystack: pGametype, needle: "lts"))
2459 {
2460 if(pGametype[0] == 'i' || pGametype[0] == 'g')
2461 HslaColor = ColorHSLA(0.0f, 1.0f, 0.75f);
2462 else
2463 HslaColor = ColorHSLA(0.40f, 1.0f, 0.75f);
2464 }
2465 else if(str_find_nocase(haystack: pGametype, needle: "s-ddracex"))
2466 HslaColor = ColorHSLA(1.0f, 1.0f, 0.7f);
2467 else if(str_find_nocase(haystack: pGametype, needle: "f-ddrace") || str_find_nocase(haystack: pGametype, needle: "freeze"))
2468 HslaColor = ColorHSLA(0.0f, 1.0f, 0.75f);
2469 else if(str_find_nocase(haystack: pGametype, needle: "fng"))
2470 HslaColor = ColorHSLA(0.83f, 1.0f, 0.75f);
2471 else if(str_find_nocase(haystack: pGametype, needle: "gores"))
2472 HslaColor = ColorHSLA(0.525f, 1.0f, 0.75f);
2473 else if(str_find_nocase(haystack: pGametype, needle: "BW"))
2474 HslaColor = ColorHSLA(0.05f, 1.0f, 0.75f);
2475 else if(str_find_nocase(haystack: pGametype, needle: "ddracenet") || str_find_nocase(haystack: pGametype, needle: "ddnet") || str_find_nocase(haystack: pGametype, needle: "0xf"))
2476 HslaColor = ColorHSLA(0.58f, 1.0f, 0.75f);
2477 else if(str_find_nocase(haystack: pGametype, needle: "ddrace") || str_find_nocase(haystack: pGametype, needle: "mkrace"))
2478 HslaColor = ColorHSLA(0.75f, 1.0f, 0.75f);
2479 else if(str_find_nocase(haystack: pGametype, needle: "race") || str_find_nocase(haystack: pGametype, needle: "fastcap"))
2480 HslaColor = ColorHSLA(0.46f, 1.0f, 0.75f);
2481 else
2482 HslaColor = ColorHSLA(1.0f, 1.0f, 1.0f);
2483 return color_cast<ColorRGBA>(hsl: HslaColor);
2484}
2485
2486bool CServerInfo::ParseLocation(int *pResult, const char *pString)
2487{
2488 *pResult = LOC_UNKNOWN;
2489 int Length = str_length(str: pString);
2490 if(Length < 2)
2491 {
2492 return true;
2493 }
2494 // ISO continent code. Allow antarctica, but treat it as unknown.
2495 static const char s_apLocations[NUM_LOCS][6] = {
2496 "an", // LOC_UNKNOWN
2497 "af", // LOC_AFRICA
2498 "as", // LOC_ASIA
2499 "oc", // LOC_AUSTRALIA
2500 "eu", // LOC_EUROPE
2501 "na", // LOC_NORTH_AMERICA
2502 "sa", // LOC_SOUTH_AMERICA
2503 "as:cn", // LOC_CHINA
2504 };
2505 for(int i = std::size(s_apLocations) - 1; i >= 0; i--)
2506 {
2507 if(str_startswith(str: pString, prefix: s_apLocations[i]))
2508 {
2509 *pResult = i;
2510 return false;
2511 }
2512 }
2513 return true;
2514}
2515