mirror of
https://github.com/ddnet/ddnet.git
synced 2024-09-21 18:14:19 +00:00
436e202cbd
Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
1494 lines
39 KiB
C++
1494 lines
39 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
|
#include <algorithm>
|
|
|
|
#include <base/hash_ctxt.h>
|
|
#include <base/math.h>
|
|
#include <base/system.h>
|
|
|
|
#include <engine/shared/config.h>
|
|
#include <engine/shared/json.h>
|
|
#include <engine/shared/memheap.h>
|
|
#include <engine/shared/network.h>
|
|
#include <engine/shared/protocol.h>
|
|
#include <engine/shared/serverinfo.h>
|
|
|
|
#include <engine/config.h>
|
|
#include <engine/console.h>
|
|
#include <engine/engine.h>
|
|
#include <engine/friends.h>
|
|
#include <engine/storage.h>
|
|
|
|
#include <mastersrv/mastersrv.h>
|
|
|
|
#include <engine/external/json-parser/json.h>
|
|
|
|
#include "serverbrowser.h"
|
|
|
|
#include "serverbrowser_http.h"
|
|
class SortWrap
|
|
{
|
|
typedef bool (CServerBrowser::*SortFunc)(int, int) const;
|
|
SortFunc m_pfnSort;
|
|
CServerBrowser *m_pThis;
|
|
|
|
public:
|
|
SortWrap(CServerBrowser *t, SortFunc f) :
|
|
m_pfnSort(f), m_pThis(t) {}
|
|
bool operator()(int a, int b) { return (g_Config.m_BrSortOrder ? (m_pThis->*m_pfnSort)(b, a) : (m_pThis->*m_pfnSort)(a, b)); }
|
|
};
|
|
|
|
CServerBrowser::CServerBrowser()
|
|
{
|
|
m_ppServerlist = 0;
|
|
m_pSortedServerlist = 0;
|
|
|
|
m_NumFavoriteServers = 0;
|
|
|
|
mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp));
|
|
|
|
m_pFirstReqServer = 0; // request list
|
|
m_pLastReqServer = 0;
|
|
m_NumRequests = 0;
|
|
|
|
m_NumSortedServers = 0;
|
|
m_NumSortedServersCapacity = 0;
|
|
m_NumServers = 0;
|
|
m_NumServerCapacity = 0;
|
|
|
|
m_Sorthash = 0;
|
|
m_aFilterString[0] = 0;
|
|
m_aFilterGametypeString[0] = 0;
|
|
|
|
m_ServerlistType = 0;
|
|
m_BroadcastTime = 0;
|
|
secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed));
|
|
m_RequestNumber = 0;
|
|
|
|
m_pDDNetInfo = 0;
|
|
|
|
m_SortOnNextUpdate = false;
|
|
}
|
|
|
|
CServerBrowser::~CServerBrowser()
|
|
{
|
|
if(m_ppServerlist)
|
|
free(m_ppServerlist);
|
|
|
|
if(m_pSortedServerlist)
|
|
free(m_pSortedServerlist);
|
|
|
|
if(m_pDDNetInfo)
|
|
json_value_free(m_pDDNetInfo);
|
|
|
|
delete m_pHttp;
|
|
m_pHttp = nullptr;
|
|
}
|
|
|
|
void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVersion)
|
|
{
|
|
m_pNetClient = pClient;
|
|
str_copy(m_aNetVersion, pNetVersion, sizeof(m_aNetVersion));
|
|
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
|
m_pEngine = Kernel()->RequestInterface<IEngine>();
|
|
m_pFriends = Kernel()->RequestInterface<IFriends>();
|
|
IConfigManager *pConfigManager = Kernel()->RequestInterface<IConfigManager>();
|
|
if(pConfigManager)
|
|
pConfigManager->RegisterCallback(ConfigSaveCallback, this);
|
|
m_pHttp = CreateServerBrowserHttp(m_pEngine);
|
|
}
|
|
|
|
const CServerInfo *CServerBrowser::SortedGet(int Index) const
|
|
{
|
|
if(Index < 0 || Index >= m_NumSortedServers)
|
|
return 0;
|
|
return &m_ppServerlist[m_pSortedServerlist[Index]]->m_Info;
|
|
}
|
|
|
|
int CServerBrowser::GenerateToken(const NETADDR &Addr) const
|
|
{
|
|
SHA256_CTX Sha256;
|
|
sha256_init(&Sha256);
|
|
sha256_update(&Sha256, m_aTokenSeed, sizeof(m_aTokenSeed));
|
|
sha256_update(&Sha256, (unsigned char *)&Addr, sizeof(Addr));
|
|
SHA256_DIGEST Digest = sha256_finish(&Sha256);
|
|
return (Digest.data[0] << 16) | (Digest.data[1] << 8) | Digest.data[2];
|
|
}
|
|
|
|
int CServerBrowser::GetBasicToken(int Token)
|
|
{
|
|
return Token & 0xff;
|
|
}
|
|
|
|
int CServerBrowser::GetExtraToken(int Token)
|
|
{
|
|
return Token >> 8;
|
|
}
|
|
|
|
bool CServerBrowser::SortCompareName(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
// make sure empty entries are listed last
|
|
return (a->m_GotInfo && b->m_GotInfo) || (!a->m_GotInfo && !b->m_GotInfo) ? str_comp(a->m_Info.m_aName, b->m_Info.m_aName) < 0 :
|
|
a->m_GotInfo ? true : false;
|
|
}
|
|
|
|
bool CServerBrowser::SortCompareMap(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
return str_comp(a->m_Info.m_aMap, b->m_Info.m_aMap) < 0;
|
|
}
|
|
|
|
bool CServerBrowser::SortComparePing(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
return a->m_Info.m_Latency < b->m_Info.m_Latency;
|
|
}
|
|
|
|
bool CServerBrowser::SortCompareGametype(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
return str_comp(a->m_Info.m_aGameType, b->m_Info.m_aGameType) < 0;
|
|
}
|
|
|
|
bool CServerBrowser::SortCompareNumPlayers(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
return a->m_Info.m_NumFilteredPlayers > b->m_Info.m_NumFilteredPlayers;
|
|
}
|
|
|
|
bool CServerBrowser::SortCompareNumClients(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
return a->m_Info.m_NumClients > b->m_Info.m_NumClients;
|
|
}
|
|
|
|
bool CServerBrowser::SortCompareNumPlayersAndPing(int Index1, int Index2) const
|
|
{
|
|
CServerEntry *a = m_ppServerlist[Index1];
|
|
CServerEntry *b = m_ppServerlist[Index2];
|
|
|
|
if(a->m_Info.m_NumFilteredPlayers == b->m_Info.m_NumFilteredPlayers)
|
|
return a->m_Info.m_Latency > b->m_Info.m_Latency;
|
|
else if(a->m_Info.m_NumFilteredPlayers == 0 || b->m_Info.m_NumFilteredPlayers == 0 || a->m_Info.m_Latency / 100 == b->m_Info.m_Latency / 100)
|
|
return a->m_Info.m_NumFilteredPlayers < b->m_Info.m_NumFilteredPlayers;
|
|
else
|
|
return a->m_Info.m_Latency > b->m_Info.m_Latency;
|
|
}
|
|
|
|
void CServerBrowser::Filter()
|
|
{
|
|
int i = 0, p = 0;
|
|
m_NumSortedServers = 0;
|
|
|
|
// allocate the sorted list
|
|
if(m_NumSortedServersCapacity < m_NumServers)
|
|
{
|
|
if(m_pSortedServerlist)
|
|
free(m_pSortedServerlist);
|
|
m_NumSortedServersCapacity = m_NumServers;
|
|
m_pSortedServerlist = (int *)calloc(m_NumSortedServersCapacity, sizeof(int));
|
|
}
|
|
|
|
// filter the servers
|
|
for(i = 0; i < m_NumServers; i++)
|
|
{
|
|
int Filtered = 0;
|
|
|
|
if(g_Config.m_BrFilterEmpty && m_ppServerlist[i]->m_Info.m_NumFilteredPlayers == 0)
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterFull && Players(m_ppServerlist[i]->m_Info) == Max(m_ppServerlist[i]->m_Info))
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterPw && m_ppServerlist[i]->m_Info.m_Flags & SERVER_FLAG_PASSWORD)
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterPing && g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency)
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0)
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterServerAddress[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aAddress, g_Config.m_BrFilterServerAddress))
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && str_comp_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype))
|
|
Filtered = 1;
|
|
else if(!g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype))
|
|
Filtered = 1;
|
|
else if(g_Config.m_BrFilterUnfinishedMap && m_ppServerlist[i]->m_Info.m_HasRank == 1)
|
|
Filtered = 1;
|
|
else
|
|
{
|
|
if(g_Config.m_BrFilterCountry)
|
|
{
|
|
Filtered = 1;
|
|
// match against player country
|
|
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
|
|
{
|
|
if(m_ppServerlist[i]->m_Info.m_aClients[p].m_Country == g_Config.m_BrFilterCountryIndex)
|
|
{
|
|
Filtered = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!Filtered && g_Config.m_BrFilterString[0] != 0)
|
|
{
|
|
int MatchFound = 0;
|
|
|
|
m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0;
|
|
|
|
// match against server name
|
|
if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString))
|
|
{
|
|
MatchFound = 1;
|
|
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
|
|
}
|
|
|
|
// match against players
|
|
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
|
|
{
|
|
if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) ||
|
|
str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, g_Config.m_BrFilterString))
|
|
{
|
|
MatchFound = 1;
|
|
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// match against map
|
|
if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString))
|
|
{
|
|
MatchFound = 1;
|
|
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
|
|
}
|
|
|
|
if(!MatchFound)
|
|
Filtered = 1;
|
|
}
|
|
|
|
if(!Filtered && g_Config.m_BrExcludeString[0] != 0)
|
|
{
|
|
int MatchFound = 0;
|
|
|
|
// match against server name
|
|
if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrExcludeString))
|
|
{
|
|
MatchFound = 1;
|
|
}
|
|
|
|
// match against map
|
|
if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrExcludeString))
|
|
{
|
|
MatchFound = 1;
|
|
}
|
|
|
|
// match against gametype
|
|
if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrExcludeString))
|
|
{
|
|
MatchFound = 1;
|
|
}
|
|
|
|
if(MatchFound)
|
|
Filtered = 1;
|
|
}
|
|
}
|
|
|
|
if(Filtered == 0)
|
|
{
|
|
// check for friend
|
|
m_ppServerlist[i]->m_Info.m_FriendState = IFriends::FRIEND_NO;
|
|
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
|
|
{
|
|
m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState = m_pFriends->GetFriendState(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName,
|
|
m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan);
|
|
m_ppServerlist[i]->m_Info.m_FriendState = maximum(m_ppServerlist[i]->m_Info.m_FriendState, m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState);
|
|
}
|
|
|
|
if(!g_Config.m_BrFilterFriends || m_ppServerlist[i]->m_Info.m_FriendState != IFriends::FRIEND_NO)
|
|
m_pSortedServerlist[m_NumSortedServers++] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
int CServerBrowser::SortHash() const
|
|
{
|
|
int i = g_Config.m_BrSort & 0xff;
|
|
i |= g_Config.m_BrFilterEmpty << 4;
|
|
i |= g_Config.m_BrFilterFull << 5;
|
|
i |= g_Config.m_BrFilterSpectators << 6;
|
|
i |= g_Config.m_BrFilterFriends << 7;
|
|
i |= g_Config.m_BrFilterPw << 8;
|
|
i |= g_Config.m_BrSortOrder << 9;
|
|
i |= g_Config.m_BrFilterCompatversion << 11;
|
|
i |= g_Config.m_BrFilterGametypeStrict << 12;
|
|
i |= g_Config.m_BrFilterUnfinishedMap << 13;
|
|
i |= g_Config.m_BrFilterCountry << 14;
|
|
i |= g_Config.m_BrFilterConnectingPlayers << 15;
|
|
return i;
|
|
}
|
|
|
|
void SetFilteredPlayers(const CServerInfo &Item)
|
|
{
|
|
Item.m_NumFilteredPlayers = g_Config.m_BrFilterSpectators ? Item.m_NumPlayers : Item.m_NumClients;
|
|
if(g_Config.m_BrFilterConnectingPlayers)
|
|
{
|
|
for(const auto &Client : Item.m_aClients)
|
|
{
|
|
if((!g_Config.m_BrFilterSpectators || Client.m_Player) && str_comp(Client.m_aName, "(connecting)") == 0 && Client.m_aClan[0] == '\0')
|
|
Item.m_NumFilteredPlayers--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::Sort()
|
|
{
|
|
int i;
|
|
|
|
// fill m_NumFilteredPlayers
|
|
for(i = 0; i < m_NumServers; i++)
|
|
{
|
|
SetFilteredPlayers(m_ppServerlist[i]->m_Info);
|
|
}
|
|
|
|
// create filtered list
|
|
Filter();
|
|
|
|
// sort
|
|
if(g_Config.m_BrSortOrder == 2 && (g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS || g_Config.m_BrSort == IServerBrowser::SORT_PING))
|
|
std::stable_sort(m_pSortedServerlist, m_pSortedServerlist + m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareNumPlayersAndPing));
|
|
else if(g_Config.m_BrSort == IServerBrowser::SORT_NAME)
|
|
std::stable_sort(m_pSortedServerlist, m_pSortedServerlist + m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareName));
|
|
else if(g_Config.m_BrSort == IServerBrowser::SORT_PING)
|
|
std::stable_sort(m_pSortedServerlist, m_pSortedServerlist + m_NumSortedServers, SortWrap(this, &CServerBrowser::SortComparePing));
|
|
else if(g_Config.m_BrSort == IServerBrowser::SORT_MAP)
|
|
std::stable_sort(m_pSortedServerlist, m_pSortedServerlist + m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareMap));
|
|
else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS)
|
|
std::stable_sort(m_pSortedServerlist, m_pSortedServerlist + m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareNumPlayers));
|
|
else if(g_Config.m_BrSort == IServerBrowser::SORT_GAMETYPE)
|
|
std::stable_sort(m_pSortedServerlist, m_pSortedServerlist + m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareGametype));
|
|
|
|
str_copy(m_aFilterGametypeString, g_Config.m_BrFilterGametype, sizeof(m_aFilterGametypeString));
|
|
str_copy(m_aFilterString, g_Config.m_BrFilterString, sizeof(m_aFilterString));
|
|
m_Sorthash = SortHash();
|
|
}
|
|
|
|
void CServerBrowser::RemoveRequest(CServerEntry *pEntry)
|
|
{
|
|
if(pEntry->m_pPrevReq || pEntry->m_pNextReq || m_pFirstReqServer == pEntry)
|
|
{
|
|
if(pEntry->m_pPrevReq)
|
|
pEntry->m_pPrevReq->m_pNextReq = pEntry->m_pNextReq;
|
|
else
|
|
m_pFirstReqServer = pEntry->m_pNextReq;
|
|
|
|
if(pEntry->m_pNextReq)
|
|
pEntry->m_pNextReq->m_pPrevReq = pEntry->m_pPrevReq;
|
|
else
|
|
m_pLastReqServer = pEntry->m_pPrevReq;
|
|
|
|
pEntry->m_pPrevReq = 0;
|
|
pEntry->m_pNextReq = 0;
|
|
m_NumRequests--;
|
|
}
|
|
}
|
|
|
|
CServerBrowser::CServerEntry *CServerBrowser::Find(const NETADDR &Addr)
|
|
{
|
|
CServerEntry *pEntry = m_aServerlistIp[Addr.ip[0]];
|
|
|
|
for(; pEntry; pEntry = pEntry->m_pNextIp)
|
|
{
|
|
if(net_addr_comp(&pEntry->m_Addr, &Addr) == 0)
|
|
return pEntry;
|
|
}
|
|
return (CServerEntry *)0;
|
|
}
|
|
|
|
void CServerBrowser::QueueRequest(CServerEntry *pEntry)
|
|
{
|
|
// add it to the list of servers that we should request info from
|
|
pEntry->m_pPrevReq = m_pLastReqServer;
|
|
if(m_pLastReqServer)
|
|
m_pLastReqServer->m_pNextReq = pEntry;
|
|
else
|
|
m_pFirstReqServer = pEntry;
|
|
m_pLastReqServer = pEntry;
|
|
pEntry->m_pNextReq = 0;
|
|
m_NumRequests++;
|
|
}
|
|
|
|
void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
|
|
{
|
|
bool Fav = pEntry->m_Info.m_Favorite;
|
|
bool Off = pEntry->m_Info.m_Official;
|
|
pEntry->m_Info = Info;
|
|
pEntry->m_Info.m_Favorite = Fav;
|
|
pEntry->m_Info.m_Official = Off;
|
|
pEntry->m_Info.m_NetAddr = pEntry->m_Addr;
|
|
net_addr_str(&pEntry->m_Info.m_NetAddr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), 1);
|
|
|
|
// all these are just for nice compatibility
|
|
if(pEntry->m_Info.m_aGameType[0] == '0' && pEntry->m_Info.m_aGameType[1] == 0)
|
|
str_copy(pEntry->m_Info.m_aGameType, "DM", sizeof(pEntry->m_Info.m_aGameType));
|
|
else if(pEntry->m_Info.m_aGameType[0] == '1' && pEntry->m_Info.m_aGameType[1] == 0)
|
|
str_copy(pEntry->m_Info.m_aGameType, "TDM", sizeof(pEntry->m_Info.m_aGameType));
|
|
else if(pEntry->m_Info.m_aGameType[0] == '2' && pEntry->m_Info.m_aGameType[1] == 0)
|
|
str_copy(pEntry->m_Info.m_aGameType, "CTF", sizeof(pEntry->m_Info.m_aGameType));
|
|
|
|
/*if(!request)
|
|
{
|
|
pEntry->m_Info.latency = (time_get()-pEntry->request_time)*1000/time_freq();
|
|
RemoveRequest(pEntry);
|
|
}*/
|
|
|
|
pEntry->m_GotInfo = 1;
|
|
}
|
|
|
|
CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
|
|
{
|
|
int Hash = Addr.ip[0];
|
|
CServerEntry *pEntry = 0;
|
|
int i;
|
|
|
|
// create new pEntry
|
|
pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry));
|
|
mem_zero(pEntry, sizeof(CServerEntry));
|
|
|
|
// set the info
|
|
pEntry->m_Addr = Addr;
|
|
pEntry->m_Info.m_NetAddr = Addr;
|
|
|
|
pEntry->m_Info.m_Latency = 999;
|
|
pEntry->m_Info.m_HasRank = -1;
|
|
net_addr_str(&Addr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), true);
|
|
str_copy(pEntry->m_Info.m_aName, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aName));
|
|
|
|
// check if it's a favorite
|
|
for(i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
|
|
{
|
|
pEntry->m_Info.m_Favorite = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check if it's an official server
|
|
for(auto &Network : m_aNetworks)
|
|
{
|
|
for(int i = 0; i < Network.m_NumCountries; i++)
|
|
{
|
|
CNetworkCountry *pCntr = &Network.m_aCountries[i];
|
|
for(int j = 0; j < pCntr->m_NumServers; j++)
|
|
{
|
|
if(net_addr_comp(&Addr, &pCntr->m_aServers[j]) == 0)
|
|
{
|
|
pEntry->m_Info.m_Official = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add to the hash list
|
|
pEntry->m_pNextIp = m_aServerlistIp[Hash];
|
|
m_aServerlistIp[Hash] = pEntry;
|
|
|
|
if(m_NumServers == m_NumServerCapacity)
|
|
{
|
|
CServerEntry **ppNewlist;
|
|
m_NumServerCapacity += 100;
|
|
ppNewlist = (CServerEntry **)calloc(m_NumServerCapacity, sizeof(CServerEntry *)); // NOLINT(bugprone-sizeof-expression)
|
|
if(m_NumServers > 0)
|
|
mem_copy(ppNewlist, m_ppServerlist, m_NumServers * sizeof(CServerEntry *)); // NOLINT(bugprone-sizeof-expression)
|
|
free(m_ppServerlist);
|
|
m_ppServerlist = ppNewlist;
|
|
}
|
|
|
|
// add to list
|
|
m_ppServerlist[m_NumServers] = pEntry;
|
|
pEntry->m_Info.m_ServerIndex = m_NumServers;
|
|
m_NumServers++;
|
|
|
|
return pEntry;
|
|
}
|
|
|
|
void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo)
|
|
{
|
|
CServerEntry *pEntry = 0;
|
|
if(Type == IServerBrowser::SET_MASTER_ADD)
|
|
{
|
|
if(m_ServerlistType != IServerBrowser::TYPE_INTERNET)
|
|
return;
|
|
m_LastPacketTick = 0;
|
|
if(!Find(Addr))
|
|
{
|
|
pEntry = Add(Addr);
|
|
QueueRequest(pEntry);
|
|
}
|
|
}
|
|
else if(Type == IServerBrowser::SET_FAV_ADD)
|
|
{
|
|
if(m_ServerlistType != IServerBrowser::TYPE_FAVORITES)
|
|
return;
|
|
|
|
if(!Find(Addr))
|
|
{
|
|
pEntry = Add(Addr);
|
|
QueueRequest(pEntry);
|
|
}
|
|
}
|
|
else if(Type == IServerBrowser::SET_DDNET_ADD)
|
|
{
|
|
if(m_ServerlistType != IServerBrowser::TYPE_DDNET)
|
|
return;
|
|
|
|
if(!Find(Addr))
|
|
{
|
|
pEntry = Add(Addr);
|
|
QueueRequest(pEntry);
|
|
}
|
|
}
|
|
else if(Type == IServerBrowser::SET_KOG_ADD)
|
|
{
|
|
if(m_ServerlistType != IServerBrowser::TYPE_KOG)
|
|
return;
|
|
|
|
if(!Find(Addr))
|
|
{
|
|
pEntry = Add(Addr);
|
|
QueueRequest(pEntry);
|
|
}
|
|
}
|
|
else if(Type == IServerBrowser::SET_HTTPINFO)
|
|
{
|
|
if(!pEntry)
|
|
{
|
|
pEntry = Add(Addr);
|
|
}
|
|
if(pEntry)
|
|
{
|
|
SetInfo(pEntry, *pInfo);
|
|
pEntry->m_Info.m_LatencyIsEstimated = true;
|
|
pEntry->m_Info.m_Latency = CServerInfo::EstimateLatency(CServerInfo::LOC_EUROPE, pEntry->m_Info.m_Location);
|
|
}
|
|
}
|
|
else if(Type == IServerBrowser::SET_TOKEN)
|
|
{
|
|
int BasicToken = Token;
|
|
int ExtraToken = 0;
|
|
if(pInfo->m_Type == SERVERINFO_EXTENDED)
|
|
{
|
|
BasicToken = Token & 0xff;
|
|
ExtraToken = Token >> 8;
|
|
}
|
|
|
|
pEntry = Find(Addr);
|
|
|
|
if(m_ServerlistType == IServerBrowser::TYPE_LAN)
|
|
{
|
|
NETADDR Broadcast;
|
|
mem_zero(&Broadcast, sizeof(Broadcast));
|
|
Broadcast.type = m_pNetClient->NetType() | NETTYPE_LINK_BROADCAST;
|
|
int Token = GenerateToken(Broadcast);
|
|
bool Drop = false;
|
|
Drop = Drop || BasicToken != GetBasicToken(Token);
|
|
Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(Token));
|
|
if(Drop)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!pEntry)
|
|
pEntry = Add(Addr);
|
|
}
|
|
else
|
|
{
|
|
if(!pEntry)
|
|
{
|
|
return;
|
|
}
|
|
int Token = GenerateToken(Addr);
|
|
bool Drop = false;
|
|
Drop = Drop || BasicToken != GetBasicToken(Token);
|
|
Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(Token));
|
|
if(Drop)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetInfo(pEntry, *pInfo);
|
|
if(m_ServerlistType == IServerBrowser::TYPE_LAN)
|
|
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get() - m_BroadcastTime) * 1000 / time_freq()), 999);
|
|
else if(pEntry->m_RequestTime > 0)
|
|
{
|
|
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get() - pEntry->m_RequestTime) * 1000 / time_freq()), 999);
|
|
pEntry->m_RequestTime = -1; // Request has been answered
|
|
}
|
|
RemoveRequest(pEntry);
|
|
}
|
|
|
|
m_SortOnNextUpdate = true;
|
|
}
|
|
|
|
void CServerBrowser::Refresh(int Type)
|
|
{
|
|
// clear out everything
|
|
m_ServerlistHeap.Reset();
|
|
m_NumServers = 0;
|
|
m_NumSortedServers = 0;
|
|
mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp));
|
|
m_pFirstReqServer = 0;
|
|
m_pLastReqServer = 0;
|
|
m_NumRequests = 0;
|
|
m_CurrentMaxRequests = g_Config.m_BrMaxRequests;
|
|
m_RequestNumber++;
|
|
|
|
m_ServerlistType = Type;
|
|
secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed));
|
|
|
|
if(Type == IServerBrowser::TYPE_LAN)
|
|
{
|
|
unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO) + 1];
|
|
CNetChunk Packet;
|
|
int i;
|
|
|
|
/* do the broadcast version */
|
|
Packet.m_ClientID = -1;
|
|
mem_zero(&Packet, sizeof(Packet));
|
|
Packet.m_Address.type = m_pNetClient->NetType() | NETTYPE_LINK_BROADCAST;
|
|
Packet.m_Flags = NETSENDFLAG_CONNLESS | NETSENDFLAG_EXTENDED;
|
|
Packet.m_DataSize = sizeof(Buffer);
|
|
Packet.m_pData = Buffer;
|
|
mem_zero(&Packet.m_aExtraData, sizeof(Packet.m_aExtraData));
|
|
|
|
int Token = GenerateToken(Packet.m_Address);
|
|
mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
|
|
Buffer[sizeof(SERVERBROWSE_GETINFO)] = GetBasicToken(Token);
|
|
|
|
Packet.m_aExtraData[0] = GetExtraToken(Token) >> 8;
|
|
Packet.m_aExtraData[1] = GetExtraToken(Token) & 0xff;
|
|
|
|
m_BroadcastTime = time_get();
|
|
|
|
for(i = 8303; i <= 8310; i++)
|
|
{
|
|
Packet.m_Address.port = i;
|
|
m_pNetClient->Send(&Packet);
|
|
}
|
|
|
|
if(g_Config.m_Debug)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", "broadcasting for servers");
|
|
}
|
|
else if(Type == IServerBrowser::TYPE_FAVORITES || Type == IServerBrowser::TYPE_INTERNET || Type == IServerBrowser::TYPE_DDNET || Type == IServerBrowser::TYPE_KOG)
|
|
{
|
|
m_pHttp->Refresh();
|
|
m_RefreshingHttp = true;
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const
|
|
{
|
|
unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO) + 1];
|
|
CNetChunk Packet;
|
|
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aAddrStr[NETADDR_MAXSTRSIZE];
|
|
net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "requesting server info from %s", aAddrStr);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
|
|
}
|
|
|
|
int Token = GenerateToken(Addr);
|
|
|
|
mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
|
|
Buffer[sizeof(SERVERBROWSE_GETINFO)] = GetBasicToken(Token);
|
|
|
|
Packet.m_ClientID = -1;
|
|
Packet.m_Address = Addr;
|
|
Packet.m_Flags = NETSENDFLAG_CONNLESS | NETSENDFLAG_EXTENDED;
|
|
Packet.m_DataSize = sizeof(Buffer);
|
|
Packet.m_pData = Buffer;
|
|
mem_zero(&Packet.m_aExtraData, sizeof(Packet.m_aExtraData));
|
|
Packet.m_aExtraData[0] = GetExtraToken(Token) >> 8;
|
|
Packet.m_aExtraData[1] = GetExtraToken(Token) & 0xff;
|
|
|
|
m_pNetClient->Send(&Packet);
|
|
|
|
if(pEntry)
|
|
pEntry->m_RequestTime = time_get();
|
|
}
|
|
|
|
void CServerBrowser::RequestImpl64(const NETADDR &Addr, CServerEntry *pEntry) const
|
|
{
|
|
unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO_64_LEGACY) + 1];
|
|
CNetChunk Packet;
|
|
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aAddrStr[NETADDR_MAXSTRSIZE];
|
|
net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "requesting server info 64 from %s", aAddrStr);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
|
|
}
|
|
|
|
mem_copy(Buffer, SERVERBROWSE_GETINFO_64_LEGACY, sizeof(SERVERBROWSE_GETINFO_64_LEGACY));
|
|
Buffer[sizeof(SERVERBROWSE_GETINFO_64_LEGACY)] = GetBasicToken(GenerateToken(Addr));
|
|
|
|
Packet.m_ClientID = -1;
|
|
Packet.m_Address = Addr;
|
|
Packet.m_Flags = NETSENDFLAG_CONNLESS;
|
|
Packet.m_DataSize = sizeof(Buffer);
|
|
Packet.m_pData = Buffer;
|
|
|
|
m_pNetClient->Send(&Packet);
|
|
|
|
if(pEntry)
|
|
pEntry->m_RequestTime = time_get();
|
|
}
|
|
|
|
void CServerBrowser::RequestCurrentServer(const NETADDR &Addr) const
|
|
{
|
|
RequestImpl(Addr, 0);
|
|
}
|
|
|
|
void CServerBrowser::UpdateFromHttp()
|
|
{
|
|
int NumServers = m_pHttp->NumServers();
|
|
int NumLegacyServers = m_pHttp->NumLegacyServers();
|
|
if(m_ServerlistType != IServerBrowser::TYPE_INTERNET)
|
|
{
|
|
std::vector<NETADDR> aWantedAddresses;
|
|
int LegacySetType;
|
|
if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
|
|
{
|
|
for(int i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
aWantedAddresses.push_back(m_aFavoriteServers[i]);
|
|
}
|
|
LegacySetType = IServerBrowser::SET_FAV_ADD;
|
|
}
|
|
else
|
|
{
|
|
int Network;
|
|
char *pExcludeCountries;
|
|
char *pExcludeTypes;
|
|
switch(m_ServerlistType)
|
|
{
|
|
case IServerBrowser::TYPE_DDNET:
|
|
Network = NETWORK_DDNET;
|
|
LegacySetType = IServerBrowser::SET_DDNET_ADD;
|
|
pExcludeCountries = g_Config.m_BrFilterExcludeCountries;
|
|
pExcludeTypes = g_Config.m_BrFilterExcludeTypes;
|
|
break;
|
|
case IServerBrowser::TYPE_KOG:
|
|
Network = NETWORK_KOG;
|
|
LegacySetType = IServerBrowser::SET_KOG_ADD;
|
|
pExcludeCountries = g_Config.m_BrFilterExcludeCountriesKoG;
|
|
pExcludeTypes = g_Config.m_BrFilterExcludeTypesKoG;
|
|
break;
|
|
default:
|
|
dbg_assert(0, "invalid network");
|
|
return;
|
|
}
|
|
// remove unknown elements of exclude list
|
|
CountryFilterClean(Network);
|
|
TypeFilterClean(Network);
|
|
|
|
int MaxServers = 0;
|
|
for(int i = 0; i < m_aNetworks[Network].m_NumCountries; i++)
|
|
{
|
|
CNetworkCountry *pCntr = &m_aNetworks[Network].m_aCountries[i];
|
|
MaxServers = maximum(MaxServers, pCntr->m_NumServers);
|
|
}
|
|
|
|
for(int g = 0; g < MaxServers; g++)
|
|
{
|
|
for(int i = 0; i < m_aNetworks[Network].m_NumCountries; i++)
|
|
{
|
|
CNetworkCountry *pCntr = &m_aNetworks[Network].m_aCountries[i];
|
|
|
|
// check for filter
|
|
if(DDNetFiltered(pExcludeCountries, pCntr->m_aName))
|
|
continue;
|
|
|
|
if(g >= pCntr->m_NumServers)
|
|
continue;
|
|
|
|
if(DDNetFiltered(pExcludeTypes, pCntr->m_aTypes[g]))
|
|
continue;
|
|
aWantedAddresses.push_back(pCntr->m_aServers[g]);
|
|
}
|
|
}
|
|
}
|
|
std::vector<int> aSortedServers;
|
|
std::vector<int> aSortedLegacyServers;
|
|
for(int i = 0; i < NumServers; i++)
|
|
{
|
|
aSortedServers.push_back(i);
|
|
}
|
|
for(int i = 0; i < NumLegacyServers; i++)
|
|
{
|
|
aSortedLegacyServers.push_back(i);
|
|
}
|
|
|
|
class CWantedAddrComparer
|
|
{
|
|
public:
|
|
bool operator()(const NETADDR &a, const NETADDR &b)
|
|
{
|
|
return net_addr_comp(&a, &b) < 0;
|
|
}
|
|
};
|
|
class CAddrComparer
|
|
{
|
|
public:
|
|
IServerBrowserHttp *m_pHttp;
|
|
bool operator()(int i, int j)
|
|
{
|
|
return net_addr_comp(&m_pHttp->ServerAddress(i), &m_pHttp->ServerAddress(j)) < 0;
|
|
}
|
|
};
|
|
class CLegacyAddrComparer
|
|
{
|
|
public:
|
|
IServerBrowserHttp *m_pHttp;
|
|
bool operator()(int i, int j)
|
|
{
|
|
return net_addr_comp(&m_pHttp->LegacyServer(i), &m_pHttp->LegacyServer(j)) < 0;
|
|
}
|
|
};
|
|
|
|
std::sort(aWantedAddresses.begin(), aWantedAddresses.end(), CWantedAddrComparer());
|
|
std::sort(aSortedServers.begin(), aSortedServers.end(), CAddrComparer{m_pHttp});
|
|
std::sort(aSortedLegacyServers.begin(), aSortedLegacyServers.end(), CLegacyAddrComparer{m_pHttp});
|
|
|
|
unsigned i = 0;
|
|
unsigned j = 0;
|
|
while(i < aWantedAddresses.size() && j < aSortedServers.size())
|
|
{
|
|
int Cmp = net_addr_comp(&aWantedAddresses[i], &m_pHttp->ServerAddress(aSortedServers[j]));
|
|
if(Cmp != 0)
|
|
{
|
|
if(Cmp < 0)
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
continue;
|
|
}
|
|
NETADDR Addr;
|
|
CServerInfo Info;
|
|
m_pHttp->Server(aSortedServers[j], &Addr, &Info);
|
|
Info.m_HasRank = HasRank(Info.m_aMap);
|
|
Set(Addr, IServerBrowser::SET_HTTPINFO, -1, &Info);
|
|
i++;
|
|
j++;
|
|
}
|
|
i = 0;
|
|
j = 0;
|
|
while(i < aWantedAddresses.size() && j < aSortedLegacyServers.size())
|
|
{
|
|
int Cmp = net_addr_comp(&aWantedAddresses[i], &m_pHttp->LegacyServer(aSortedLegacyServers[j]));
|
|
if(Cmp != 0)
|
|
{
|
|
if(Cmp < 0)
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
continue;
|
|
}
|
|
Set(m_pHttp->LegacyServer(aSortedLegacyServers[j]), LegacySetType, -1, nullptr);
|
|
i++;
|
|
j++;
|
|
}
|
|
return;
|
|
}
|
|
for(int i = 0; i < NumServers; i++)
|
|
{
|
|
NETADDR Addr;
|
|
CServerInfo Info;
|
|
m_pHttp->Server(i, &Addr, &Info);
|
|
Info.m_HasRank = HasRank(Info.m_aMap);
|
|
Set(Addr, IServerBrowser::SET_HTTPINFO, -1, &Info);
|
|
}
|
|
for(int i = 0; i < NumLegacyServers; i++)
|
|
{
|
|
NETADDR Addr = m_pHttp->LegacyServer(i);
|
|
Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, nullptr);
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::Update(bool ForceResort)
|
|
{
|
|
int64 Timeout = time_freq();
|
|
int64 Now = time_get();
|
|
|
|
m_pHttp->Update();
|
|
|
|
if(m_ServerlistType != TYPE_LAN && m_RefreshingHttp && !m_pHttp->IsRefreshing())
|
|
{
|
|
m_RefreshingHttp = false;
|
|
UpdateFromHttp();
|
|
// TODO: move this somewhere else
|
|
if(m_Sorthash != SortHash() || ForceResort)
|
|
Sort();
|
|
return;
|
|
}
|
|
|
|
CServerEntry *pEntry = m_pFirstReqServer;
|
|
int Count = 0;
|
|
while(1)
|
|
{
|
|
if(!pEntry) // no more entries
|
|
break;
|
|
if(pEntry->m_RequestTime && pEntry->m_RequestTime + Timeout < Now)
|
|
{
|
|
pEntry = pEntry->m_pNextReq;
|
|
continue;
|
|
}
|
|
// no more than 10 concurrent requests
|
|
if(Count == m_CurrentMaxRequests)
|
|
break;
|
|
|
|
if(pEntry->m_RequestTime == 0)
|
|
{
|
|
if(pEntry->m_Request64Legacy)
|
|
RequestImpl64(pEntry->m_Addr, pEntry);
|
|
else
|
|
RequestImpl(pEntry->m_Addr, pEntry);
|
|
}
|
|
|
|
Count++;
|
|
pEntry = pEntry->m_pNextReq;
|
|
}
|
|
|
|
if(m_pFirstReqServer && Count == 0 && m_CurrentMaxRequests > 1) //NO More current Server Requests
|
|
{
|
|
//reset old ones
|
|
pEntry = m_pFirstReqServer;
|
|
while(1)
|
|
{
|
|
if(!pEntry) // no more entries
|
|
break;
|
|
pEntry->m_RequestTime = 0;
|
|
pEntry = pEntry->m_pNextReq;
|
|
}
|
|
|
|
//update max-requests
|
|
m_CurrentMaxRequests = m_CurrentMaxRequests / 2;
|
|
if(m_CurrentMaxRequests < 1)
|
|
m_CurrentMaxRequests = 1;
|
|
}
|
|
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
|
|
{
|
|
pEntry = m_pFirstReqServer;
|
|
while(1)
|
|
{
|
|
if(!pEntry) // no more entries
|
|
break;
|
|
CServerEntry *pNext = pEntry->m_pNextReq;
|
|
RemoveRequest(pEntry); //release request
|
|
pEntry = pNext;
|
|
}
|
|
}
|
|
|
|
// check if we need to resort
|
|
if(m_Sorthash != SortHash() || ForceResort || m_SortOnNextUpdate)
|
|
{
|
|
Sort();
|
|
m_SortOnNextUpdate = false;
|
|
}
|
|
}
|
|
|
|
bool CServerBrowser::IsFavorite(const NETADDR &Addr) const
|
|
{
|
|
// search for the address
|
|
int i;
|
|
for(i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CServerBrowser::AddFavorite(const NETADDR &Addr)
|
|
{
|
|
CServerEntry *pEntry;
|
|
|
|
if(m_NumFavoriteServers == MAX_FAVORITES)
|
|
return;
|
|
|
|
// make sure that we don't already have the server in our list
|
|
for(int i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
|
|
return;
|
|
}
|
|
|
|
// add the server to the list
|
|
m_aFavoriteServers[m_NumFavoriteServers++] = Addr;
|
|
pEntry = Find(Addr);
|
|
if(pEntry)
|
|
pEntry->m_Info.m_Favorite = true;
|
|
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aAddrStr[NETADDR_MAXSTRSIZE];
|
|
net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "added fav, %s", aAddrStr);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::RemoveFavorite(const NETADDR &Addr)
|
|
{
|
|
int i;
|
|
CServerEntry *pEntry;
|
|
|
|
for(i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
|
|
{
|
|
mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i + 1], sizeof(NETADDR) * (m_NumFavoriteServers - (i + 1)));
|
|
m_NumFavoriteServers--;
|
|
|
|
pEntry = Find(Addr);
|
|
if(pEntry)
|
|
pEntry->m_Info.m_Favorite = false;
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::LoadDDNetServers()
|
|
{
|
|
if(!m_pDDNetInfo)
|
|
return;
|
|
|
|
// reset servers / countries
|
|
for(int Network = 0; Network < NUM_NETWORKS; Network++)
|
|
{
|
|
CNetwork *pNet = &m_aNetworks[Network];
|
|
|
|
// parse JSON
|
|
const json_value *pServers = json_object_get(m_pDDNetInfo, Network == NETWORK_DDNET ? "servers" : "servers-kog");
|
|
|
|
if(!pServers || pServers->type != json_array)
|
|
return;
|
|
|
|
pNet->m_NumCountries = 0;
|
|
pNet->m_NumTypes = 0;
|
|
|
|
for(int i = 0; i < json_array_length(pServers) && pNet->m_NumCountries < MAX_COUNTRIES; i++)
|
|
{
|
|
// pSrv - { name, flagId, servers }
|
|
const json_value *pSrv = json_array_get(pServers, i);
|
|
const json_value *pTypes = json_object_get(pSrv, "servers");
|
|
const json_value *pName = json_object_get(pSrv, "name");
|
|
const json_value *pFlagID = json_object_get(pSrv, "flagId");
|
|
|
|
if(pSrv->type != json_object || pTypes->type != json_object || pName->type != json_string || pFlagID->type != json_integer)
|
|
{
|
|
dbg_msg("client_srvbrowse", "invalid attributes");
|
|
continue;
|
|
}
|
|
|
|
// build structure
|
|
CNetworkCountry *pCntr = &pNet->m_aCountries[pNet->m_NumCountries];
|
|
|
|
pCntr->Reset();
|
|
|
|
str_copy(pCntr->m_aName, json_string_get(pName), sizeof(pCntr->m_aName));
|
|
pCntr->m_FlagID = json_int_get(pFlagID);
|
|
|
|
// add country
|
|
for(unsigned int t = 0; t < pTypes->u.object.length; t++)
|
|
{
|
|
const char *pType = pTypes->u.object.values[t].name;
|
|
const json_value *pAddrs = pTypes->u.object.values[t].value;
|
|
|
|
if(pAddrs->type != json_array)
|
|
{
|
|
dbg_msg("client_srvbrowse", "invalid attributes");
|
|
continue;
|
|
}
|
|
|
|
// add type
|
|
if(json_array_length(pAddrs) > 0 && pNet->m_NumTypes < MAX_TYPES)
|
|
{
|
|
int Pos;
|
|
for(Pos = 0; Pos < pNet->m_NumTypes; Pos++)
|
|
{
|
|
if(!str_comp(pNet->m_aTypes[Pos], pType))
|
|
break;
|
|
}
|
|
if(Pos == pNet->m_NumTypes)
|
|
{
|
|
str_copy(pNet->m_aTypes[pNet->m_NumTypes], pType, sizeof(pNet->m_aTypes[pNet->m_NumTypes]));
|
|
pNet->m_NumTypes++;
|
|
}
|
|
}
|
|
|
|
// add addresses
|
|
for(int g = 0; g < json_array_length(pAddrs); g++, pCntr->m_NumServers++)
|
|
{
|
|
const json_value *pAddr = json_array_get(pAddrs, g);
|
|
if(pAddr->type != json_string)
|
|
{
|
|
dbg_msg("client_srvbrowse", "invalid attributes");
|
|
continue;
|
|
}
|
|
const char *pStr = json_string_get(pAddr);
|
|
net_addr_from_str(&pCntr->m_aServers[pCntr->m_NumServers], pStr);
|
|
str_copy(pCntr->m_aTypes[pCntr->m_NumServers], pType, sizeof(pCntr->m_aTypes[pCntr->m_NumServers]));
|
|
}
|
|
}
|
|
|
|
pNet->m_NumCountries++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::RecheckOfficial()
|
|
{
|
|
for(auto &Network : m_aNetworks)
|
|
{
|
|
for(int i = 0; i < Network.m_NumCountries; i++)
|
|
{
|
|
CNetworkCountry *pCntr = &Network.m_aCountries[i];
|
|
for(int j = 0; j < pCntr->m_NumServers; j++)
|
|
{
|
|
CServerEntry *pEntry = Find(pCntr->m_aServers[j]);
|
|
if(pEntry)
|
|
{
|
|
pEntry->m_Info.m_Official = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::LoadDDNetRanks()
|
|
{
|
|
for(int i = 0; i < m_NumServers; i++)
|
|
{
|
|
if(m_ppServerlist[i]->m_Info.m_aMap[0])
|
|
m_ppServerlist[i]->m_Info.m_HasRank = HasRank(m_ppServerlist[i]->m_Info.m_aMap);
|
|
}
|
|
}
|
|
|
|
int CServerBrowser::HasRank(const char *pMap)
|
|
{
|
|
if(m_ServerlistType != IServerBrowser::TYPE_DDNET || !m_pDDNetInfo)
|
|
return -1;
|
|
|
|
const json_value *pDDNetRanks = json_object_get(m_pDDNetInfo, "maps");
|
|
|
|
if(!pDDNetRanks || pDDNetRanks->type != json_array)
|
|
return -1;
|
|
|
|
for(int i = 0; i < json_array_length(pDDNetRanks); i++)
|
|
{
|
|
const json_value *pJson = json_array_get(pDDNetRanks, i);
|
|
if(!pJson || pJson->type != json_string)
|
|
continue;
|
|
|
|
const char *pStr = json_string_get(pJson);
|
|
|
|
if(str_comp(pMap, pStr) == 0)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CServerBrowser::LoadDDNetInfoJson()
|
|
{
|
|
IStorage *pStorage = Kernel()->RequestInterface<IStorage>();
|
|
IOHANDLE File = pStorage->OpenFile(DDNET_INFO, IOFLAG_READ, IStorage::TYPE_SAVE);
|
|
|
|
if(!File)
|
|
return;
|
|
|
|
const int Length = io_length(File);
|
|
if(Length <= 0)
|
|
{
|
|
io_close(File);
|
|
return;
|
|
}
|
|
|
|
char *pBuf = (char *)malloc(Length);
|
|
pBuf[0] = '\0';
|
|
|
|
io_read(File, pBuf, Length);
|
|
io_close(File);
|
|
|
|
if(m_pDDNetInfo)
|
|
json_value_free(m_pDDNetInfo);
|
|
|
|
m_pDDNetInfo = json_parse(pBuf, Length);
|
|
|
|
free(pBuf);
|
|
|
|
if(m_pDDNetInfo && m_pDDNetInfo->type != json_object)
|
|
{
|
|
json_value_free(m_pDDNetInfo);
|
|
m_pDDNetInfo = 0;
|
|
}
|
|
}
|
|
|
|
const json_value *CServerBrowser::LoadDDNetInfo()
|
|
{
|
|
LoadDDNetInfoJson();
|
|
LoadDDNetServers();
|
|
|
|
RecheckOfficial();
|
|
LoadDDNetRanks();
|
|
|
|
return m_pDDNetInfo;
|
|
}
|
|
|
|
bool CServerBrowser::IsRefreshing() const
|
|
{
|
|
return m_pFirstReqServer != 0;
|
|
}
|
|
|
|
bool CServerBrowser::IsGettingServerlist() const
|
|
{
|
|
return m_pHttp->IsRefreshing();
|
|
}
|
|
|
|
int CServerBrowser::LoadingProgression() const
|
|
{
|
|
if(m_NumServers == 0)
|
|
return 0;
|
|
|
|
int Servers = m_NumServers;
|
|
int Loaded = m_NumServers - m_NumRequests;
|
|
return 100.0f * Loaded / Servers;
|
|
}
|
|
|
|
void CServerBrowser::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
|
|
{
|
|
CServerBrowser *pSelf = (CServerBrowser *)pUserData;
|
|
|
|
char aAddrStr[128];
|
|
char aBuffer[256];
|
|
for(int i = 0; i < pSelf->m_NumFavoriteServers; i++)
|
|
{
|
|
net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr), true);
|
|
str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr);
|
|
pConfigManager->WriteLine(aBuffer);
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::DDNetFilterAdd(char *pFilter, const char *pName)
|
|
{
|
|
if(DDNetFiltered(pFilter, pName))
|
|
return;
|
|
|
|
char aBuf[128];
|
|
str_format(aBuf, sizeof(aBuf), ",%s", pName);
|
|
str_append(pFilter, aBuf, 128);
|
|
}
|
|
|
|
void CServerBrowser::DDNetFilterRem(char *pFilter, const char *pName)
|
|
{
|
|
if(!DDNetFiltered(pFilter, pName))
|
|
return;
|
|
|
|
// rewrite exclude/filter list
|
|
char aBuf[128];
|
|
|
|
str_copy(aBuf, pFilter, sizeof(aBuf));
|
|
pFilter[0] = '\0';
|
|
|
|
char aToken[128];
|
|
for(const char *tok = aBuf; (tok = str_next_token(tok, ",", aToken, sizeof(aToken)));)
|
|
{
|
|
if(str_comp_nocase(pName, aToken) != 0)
|
|
{
|
|
char aBuf2[128];
|
|
str_format(aBuf2, sizeof(aBuf2), ",%s", aToken);
|
|
str_append(pFilter, aBuf2, 128);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CServerBrowser::DDNetFiltered(char *pFilter, const char *pName)
|
|
{
|
|
return str_in_list(pFilter, ",", pName); // country not excluded
|
|
}
|
|
|
|
void CServerBrowser::CountryFilterClean(int Network)
|
|
{
|
|
char *pExcludeCountries = Network == NETWORK_KOG ? g_Config.m_BrFilterExcludeCountriesKoG : g_Config.m_BrFilterExcludeCountries;
|
|
char aNewList[128];
|
|
aNewList[0] = '\0';
|
|
|
|
for(auto &Network : m_aNetworks)
|
|
{
|
|
for(int i = 0; i < Network.m_NumCountries; i++)
|
|
{
|
|
const char *pName = Network.m_aCountries[i].m_aName;
|
|
if(DDNetFiltered(pExcludeCountries, pName))
|
|
{
|
|
char aBuf[128];
|
|
str_format(aBuf, sizeof(aBuf), ",%s", pName);
|
|
str_append(aNewList, aBuf, sizeof(aNewList));
|
|
}
|
|
}
|
|
}
|
|
|
|
str_copy(pExcludeCountries, aNewList, sizeof(g_Config.m_BrFilterExcludeCountries));
|
|
}
|
|
|
|
void CServerBrowser::TypeFilterClean(int Network)
|
|
{
|
|
char *pExcludeTypes = Network == NETWORK_KOG ? g_Config.m_BrFilterExcludeTypesKoG : g_Config.m_BrFilterExcludeTypes;
|
|
char aNewList[128];
|
|
aNewList[0] = '\0';
|
|
|
|
for(int i = 0; i < m_aNetworks[Network].m_NumTypes; i++)
|
|
{
|
|
const char *pName = m_aNetworks[Network].m_aTypes[i];
|
|
if(DDNetFiltered(pExcludeTypes, pName))
|
|
{
|
|
char aBuf[128];
|
|
str_format(aBuf, sizeof(aBuf), ",%s", pName);
|
|
str_append(aNewList, aBuf, sizeof(aNewList));
|
|
}
|
|
}
|
|
|
|
str_copy(pExcludeTypes, aNewList, sizeof(g_Config.m_BrFilterExcludeTypes));
|
|
}
|
|
|
|
int CServerInfo::EstimateLatency(int Loc1, int Loc2)
|
|
{
|
|
if(Loc1 == LOC_UNKNOWN || Loc2 == LOC_UNKNOWN)
|
|
{
|
|
return 999;
|
|
}
|
|
if(Loc1 != Loc2)
|
|
{
|
|
return 149;
|
|
}
|
|
return 49;
|
|
}
|
|
bool CServerInfo::ParseLocation(int *pResult, const char *pString)
|
|
{
|
|
*pResult = LOC_UNKNOWN;
|
|
int Length = str_length(pString);
|
|
if(Length < 2)
|
|
{
|
|
return true;
|
|
}
|
|
// ISO continent code. Allow antarctica, but treat it as unknown.
|
|
static const char LOCATIONS[][3] = {
|
|
"an", // LOC_UNKNOWN
|
|
"af", // LOC_AFRICA
|
|
"as", // LOC_ASIA
|
|
"oc", // LOC_AUSTRALIA
|
|
"eu", // LOC_EUROPE
|
|
"na", // LOC_NORTH_AMERICA
|
|
"sa", // LOC_SOUTH_AMERICA
|
|
};
|
|
for(unsigned i = 0; i < sizeof(LOCATIONS) / sizeof(LOCATIONS[0]); i++)
|
|
{
|
|
if(str_comp_num(pString, LOCATIONS[i], 2) == 0)
|
|
{
|
|
*pResult = i;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsVanilla(const CServerInfo *pInfo)
|
|
{
|
|
return !str_comp(pInfo->m_aGameType, "DM") || !str_comp(pInfo->m_aGameType, "TDM") || !str_comp(pInfo->m_aGameType, "CTF");
|
|
}
|
|
|
|
bool IsCatch(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "catch");
|
|
}
|
|
|
|
bool IsInsta(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "idm") || str_find_nocase(pInfo->m_aGameType, "itdm") || str_find_nocase(pInfo->m_aGameType, "ictf");
|
|
}
|
|
|
|
bool IsFNG(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "fng");
|
|
}
|
|
|
|
bool IsRace(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "race") || str_find_nocase(pInfo->m_aGameType, "fastcap");
|
|
}
|
|
|
|
bool IsFastCap(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "fastcap");
|
|
}
|
|
|
|
bool IsBlockInfectionZ(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "blockz") ||
|
|
str_find_nocase(pInfo->m_aGameType, "infectionz");
|
|
}
|
|
|
|
bool IsBlockWorlds(const CServerInfo *pInfo)
|
|
{
|
|
return (str_comp_nocase_num(pInfo->m_aGameType, "bw ", 4) == 0) || (str_comp_nocase(pInfo->m_aGameType, "bw") == 0);
|
|
}
|
|
|
|
bool IsCity(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "city");
|
|
}
|
|
|
|
bool IsDDRace(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "ddrace") || str_find_nocase(pInfo->m_aGameType, "mkrace");
|
|
}
|
|
|
|
bool IsDDNet(const CServerInfo *pInfo)
|
|
{
|
|
return str_find_nocase(pInfo->m_aGameType, "ddracenet") || str_find_nocase(pInfo->m_aGameType, "ddnet");
|
|
}
|
|
|
|
// other
|
|
|
|
bool Is64Player(const CServerInfo *pInfo)
|
|
{
|
|
return str_find(pInfo->m_aGameType, "64") || str_find(pInfo->m_aName, "64") || IsDDNet(pInfo);
|
|
}
|
|
|
|
bool IsPlus(const CServerInfo *pInfo)
|
|
{
|
|
return str_find(pInfo->m_aGameType, "+");
|
|
}
|