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