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