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