ddnet/src/engine/client/serverbrowser.cpp
heinrich5991 436e202cbd Add client-side HTTP server info
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.
2021-05-14 01:00:39 +02:00

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, "+");
}