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