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