mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-13 11:38:19 +00:00
6b65ccb945
Registering ----------- The idea is that game servers push their server info to the masterservers every 15 seconds or when the server info changes, but not more than once per second. The game servers do not support the old registering protocol anymore, the backward compatibility is handled by the masterserver. The register call is a HTTP POST to a URL like `https://master1.ddnet.tw/ddnet/15/register` and looks like this: ```json POST /ddnet/15/register HTTP/1.1 Address: tw-0.6+udp://connecting-address.invalid:8303 Secret: 81fa3955-6f83-4290-818d-31c0906b1118 Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6 Info-Serial: 0 { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } ``` The `Address` header declares that the server wants to register itself as a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible protocol. The free-form `Secret` header is used as a server identity, the server list will be deduplicated via this secret. The free-form `Challenge-Secret` is sent back via UDP for a port forward check. This might have security implications as the masterserver can be asked to send a UDP packet containing some user-controlled bytes. This is somewhat mitigated by the fact that it can only go to an attacker-controlled IP address. The `Info-Serial` header is an integer field that should increase each time the server info (in the body) changes. The masterserver uses that field to ensure that it doesn't use old server infos. The body is a free-form JSON object set by the game server. It should contain certain keys in the correct form to be accepted by clients. The body is optional if the masterserver already confirmed the reception of the info with the given `Info-Serial`. Not shown in this payload is the `Connless-Token` header that is used for Teeworlds 0.7 style communication. Also not shown is the `Challenge-Token` that should be included once the server receives the challenge token via UDP. The masterserver responds with a `200 OK` with a body like this: ``` {"status":"success"} ``` The `status` field can be `success` if the server was successfully registered on the masterserver, `need_challenge` if the masterserver wants the correct `Challenge-Token` header before the register process is successful, `need_info` if the server sent an empty body but the masterserver doesn't actually know the server info. It can also be `error` if the request was malformed, only in this case an HTTP status code except `200 OK` is sent. Synchronization --------------- The masterserver keeps state and outputs JSON files every second. ```json { "servers": [ { "addresses": [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ], "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } ] } ``` `servers.json` (or configured by `--out`) is a server list that is compatible with DDNet 15.5+ clients. It is a JSON object containing a single key `servers` with a list of game servers. Each game server is represented by a JSON object with an `addresses` key containing a list of all known addresses of the server and an `info` key containing the free-form server info sent by the game server. The free-form `info` JSON object re-encoded by the master server and thus canonicalized and stripped of any whitespace characters outside strings. ```json { "kind": "mastersrv", "now": 1816002, "secrets": { "tw-0.6+udp://127.0.0.1:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" }, "tw-0.6+udp://[::1]:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" } }, "servers": { "42d8f991-f2fa-46e5-a9ae-ebcc93846feb": { "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } } } ``` `--write-dump` outputs a JSON file compatible with `--read-dump-dir`, this can be used to synchronize servers across different masterservers. `--read-dump-dir` is also used to ingest servers from the backward compatibility layer that pings each server for their server info using the old protocol. The `kind` field describe that this is `mastersrv` output and not from a `backcompat`. This is used for prioritizing `mastersrv` information over `backcompat` information. The `now` field contains an integer describing the current time in milliseconds relative an unspecified epoch that is fixed for each JSON file. This is done instead of using the current time as the epoch for better compression of non-changing data. `secrets` is a map from each server address and to a JSON object containing the last ping time (`ping_time`) in milliseconds relative to the same epoch as before, and the server secret (`secret`) that is used to unify server infos from different addresses of the same logical server. `servers` is a map from the aforementioned `secret`s to the corresponding `info_serial` and `info`. ```json [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ] ``` `--write-addresses` outputs a JSON file containing all addresses corresponding to servers that are registered to HTTP masterservers. It does not contain the servers that are obtained via backward compatibility measures. This file can be used by an old-style masterserver to also list new-style servers without the game servers having to register there. An implementation of this can be found at https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat for Teeworlds 0.5/0.6 masterservers and at https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat for Teeworlds 0.7 masterservers. All these JSON files can be sent over the network in an efficient way using https://github.com/heinrich5991/twmaster-collect. It establishes a zstd-compressed TCP connection authenticated by a string token that is sent in plain-text. It watches the specified file and transmits it every time it changes. Due to the zstd-compression, the data sent over the network is similar to the size of a diff. Implementation -------------- The masterserver implementation was done in Rust. The current gameserver register implementation doesn't support more than one masterserver for registering.
1802 lines
48 KiB
C++
1802 lines
48 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 "serverbrowser.h"
|
|
|
|
#include "serverbrowser_http.h"
|
|
#include "serverbrowser_ping_cache.h"
|
|
|
|
#include <algorithm>
|
|
#include <climits>
|
|
#include <vector>
|
|
|
|
#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/masterserver.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/serverbrowser.h>
|
|
#include <engine/storage.h>
|
|
|
|
#include <engine/external/json-parser/json.h>
|
|
|
|
#include <game/client/components/menus.h> // PAGE_DDNET
|
|
|
|
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_pDDNetInfo = 0;
|
|
|
|
m_SortOnNextUpdate = false;
|
|
}
|
|
|
|
CServerBrowser::~CServerBrowser()
|
|
{
|
|
free(m_ppServerlist);
|
|
free(m_pSortedServerlist);
|
|
json_value_free(m_pDDNetInfo);
|
|
|
|
delete m_pHttp;
|
|
m_pHttp = nullptr;
|
|
delete m_pPingCache;
|
|
m_pPingCache = 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>();
|
|
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
|
IConfigManager *pConfigManager = Kernel()->RequestInterface<IConfigManager>();
|
|
if(pConfigManager)
|
|
pConfigManager->RegisterCallback(ConfigSaveCallback, this);
|
|
m_pPingCache = CreateServerBrowserPingCache(m_pConsole, m_pStorage);
|
|
|
|
RegisterCommands();
|
|
}
|
|
|
|
void CServerBrowser::OnInit()
|
|
{
|
|
m_pHttp = CreateServerBrowserHttp(m_pEngine, m_pConsole, m_pStorage, g_Config.m_BrCachedBestServerinfoUrl);
|
|
}
|
|
|
|
void CServerBrowser::RegisterCommands()
|
|
{
|
|
m_pConsole->Register("leak_ip_address_to_all_servers", "", CFGFLAG_CLIENT, Con_LeakIpAddress, this, "Leaks your IP address to all servers by pinging each of them, also acquiring the latency in the process");
|
|
}
|
|
|
|
void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CServerBrowser *pThis = (CServerBrowser *)pUserData;
|
|
|
|
std::vector<int> aSortedServers;
|
|
// Sort servers by IP address, ignoring port.
|
|
class CAddrComparer
|
|
{
|
|
public:
|
|
CServerBrowser *m_pThis;
|
|
bool operator()(int i, int j)
|
|
{
|
|
NETADDR Addr1 = m_pThis->m_ppServerlist[i]->m_Addr;
|
|
NETADDR Addr2 = m_pThis->m_ppServerlist[j]->m_Addr;
|
|
Addr1.port = 0;
|
|
Addr2.port = 0;
|
|
return net_addr_comp(&Addr1, &Addr2) < 0;
|
|
}
|
|
};
|
|
aSortedServers.reserve(pThis->m_NumServers);
|
|
for(int i = 0; i < pThis->m_NumServers; i++)
|
|
{
|
|
aSortedServers.push_back(i);
|
|
}
|
|
std::sort(aSortedServers.begin(), aSortedServers.end(), CAddrComparer{pThis});
|
|
|
|
// Group the servers into those with same IP address (but differing
|
|
// port).
|
|
NETADDR Addr;
|
|
int Start = -1;
|
|
for(int i = 0; i <= (int)aSortedServers.size(); i++)
|
|
{
|
|
NETADDR NextAddr;
|
|
if(i < (int)aSortedServers.size())
|
|
{
|
|
NextAddr = pThis->m_ppServerlist[aSortedServers[i]]->m_Addr;
|
|
NextAddr.port = 0;
|
|
}
|
|
bool New = Start == -1 || i == (int)aSortedServers.size() || net_addr_comp(&Addr, &NextAddr) != 0;
|
|
if(Start != -1 && New)
|
|
{
|
|
int Chosen = Start + secure_rand_below(i - Start);
|
|
CServerEntry *pChosen = pThis->m_ppServerlist[aSortedServers[Chosen]];
|
|
pChosen->m_RequestIgnoreInfo = true;
|
|
pThis->QueueRequest(pChosen);
|
|
char aAddr[NETADDR_MAXSTRSIZE];
|
|
net_addr_str(&pChosen->m_Addr, aAddr, sizeof(aAddr), true);
|
|
dbg_msg("serverbrowse/dbg", "queuing ping request for %s", aAddr);
|
|
}
|
|
if(i < (int)aSortedServers.size() && New)
|
|
{
|
|
Start = i;
|
|
Addr = NextAddr;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 != 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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_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_utf8_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_utf8_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_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) ||
|
|
str_utf8_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_utf8_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_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrExcludeString))
|
|
{
|
|
MatchFound = 1;
|
|
}
|
|
|
|
// match against map
|
|
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrExcludeString))
|
|
{
|
|
MatchFound = 1;
|
|
}
|
|
|
|
// match against gametype
|
|
if(str_utf8_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_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);
|
|
|
|
class CPlayerScoreNameLess
|
|
{
|
|
public:
|
|
bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1)
|
|
{
|
|
if(p0.m_Player && !p1.m_Player)
|
|
return true;
|
|
if(!p0.m_Player && p1.m_Player)
|
|
return false;
|
|
|
|
int Score0 = p0.m_Score;
|
|
int Score1 = p1.m_Score;
|
|
if(Score0 == -9999)
|
|
Score0 = INT_MIN;
|
|
if(Score1 == -9999)
|
|
Score1 = INT_MIN;
|
|
|
|
if(Score0 > Score1)
|
|
return true;
|
|
if(Score0 < Score1)
|
|
return false;
|
|
return str_comp_nocase(p0.m_aName, p1.m_aName) < 0;
|
|
}
|
|
};
|
|
|
|
std::sort(pEntry->m_Info.m_aClients, pEntry->m_Info.m_aClients + Info.m_NumReceivedClients, CPlayerScoreNameLess());
|
|
|
|
pEntry->m_GotInfo = 1;
|
|
}
|
|
|
|
void CServerBrowser::SetLatency(NETADDR Addr, int Latency)
|
|
{
|
|
Addr.port = 0;
|
|
for(CServerEntry *pEntry = m_aServerlistIp[Addr.ip[0]]; pEntry; pEntry = pEntry->m_pNextIp)
|
|
{
|
|
NETADDR Other = pEntry->m_Addr;
|
|
Other.port = 0;
|
|
if(net_addr_comp(&Addr, &Other) == 0 && pEntry->m_GotInfo)
|
|
{
|
|
pEntry->m_Info.m_Latency = Latency;
|
|
pEntry->m_Info.m_LatencyIsEstimated = false;
|
|
}
|
|
}
|
|
m_pPingCache->CachePing(Addr, Latency);
|
|
}
|
|
|
|
CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
|
|
{
|
|
int Hash = Addr.ip[0];
|
|
CServerEntry *pEntry = 0;
|
|
|
|
// 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
|
|
pEntry->m_Info.m_Favorite = IsFavorite(Addr);
|
|
|
|
// 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;
|
|
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_RequestIgnoreInfo = true;
|
|
}
|
|
}
|
|
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 TokenBC = GenerateToken(Broadcast);
|
|
bool Drop = false;
|
|
Drop = Drop || BasicToken != GetBasicToken(TokenBC);
|
|
Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(TokenBC));
|
|
if(Drop)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!pEntry)
|
|
pEntry = Add(Addr);
|
|
}
|
|
else
|
|
{
|
|
if(!pEntry)
|
|
{
|
|
return;
|
|
}
|
|
int TokenAddr = GenerateToken(Addr);
|
|
bool Drop = false;
|
|
Drop = Drop || BasicToken != GetBasicToken(TokenAddr);
|
|
Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(TokenAddr));
|
|
if(Drop)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(m_ServerlistType == IServerBrowser::TYPE_LAN)
|
|
{
|
|
SetInfo(pEntry, *pInfo);
|
|
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get() - m_BroadcastTime) * 1000 / time_freq()), 999);
|
|
if(pInfo->m_Type == SERVERINFO_VANILLA && Is64Player(pInfo))
|
|
{
|
|
pEntry->m_Request64Legacy = true;
|
|
// Force a quick update.
|
|
RequestImpl64(pEntry->m_Addr, pEntry);
|
|
}
|
|
}
|
|
else if(pEntry->m_RequestTime > 0)
|
|
{
|
|
if(!pEntry->m_RequestIgnoreInfo)
|
|
{
|
|
SetInfo(pEntry, *pInfo);
|
|
}
|
|
|
|
int Latency = minimum(static_cast<int>((time_get() - pEntry->m_RequestTime) * 1000 / time_freq()), 999);
|
|
if(!pEntry->m_RequestIgnoreInfo)
|
|
{
|
|
pEntry->m_Info.m_Latency = Latency;
|
|
}
|
|
else
|
|
{
|
|
char aAddr[NETADDR_MAXSTRSIZE];
|
|
net_addr_str(&Addr, aAddr, sizeof(aAddr), true);
|
|
dbg_msg("serverbrowse/dbg", "received ping response from %s", aAddr);
|
|
SetLatency(Addr, Latency);
|
|
}
|
|
pEntry->m_RequestTime = -1; // Request has been answered
|
|
|
|
if(!pEntry->m_RequestIgnoreInfo)
|
|
{
|
|
if(pInfo->m_Type == SERVERINFO_VANILLA && Is64Player(pInfo))
|
|
{
|
|
pEntry->m_Request64Legacy = true;
|
|
// Force a quick update.
|
|
RequestImpl64(pEntry->m_Addr, pEntry);
|
|
}
|
|
}
|
|
}
|
|
RemoveRequest(pEntry);
|
|
}
|
|
|
|
m_SortOnNextUpdate = true;
|
|
}
|
|
|
|
void CServerBrowser::Refresh(int Type)
|
|
{
|
|
bool ServerListTypeChanged = m_ServerlistType != Type;
|
|
int OldServerListType = m_ServerlistType;
|
|
m_ServerlistType = Type;
|
|
secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed));
|
|
|
|
if(Type == IServerBrowser::TYPE_LAN || (ServerListTypeChanged && OldServerListType == IServerBrowser::TYPE_LAN))
|
|
CleanUp();
|
|
|
|
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_pPingCache->Load();
|
|
m_RefreshingHttp = true;
|
|
|
|
if(ServerListTypeChanged && m_pHttp->NumServers() > 0)
|
|
{
|
|
CleanUp();
|
|
UpdateFromHttp();
|
|
Sort();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int *pBasicToken, int *pToken, bool RandomToken) 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);
|
|
if(RandomToken)
|
|
{
|
|
int AvoidBasicToken = GetBasicToken(Token);
|
|
do
|
|
{
|
|
secure_random_fill(&Token, sizeof(Token));
|
|
Token &= 0xffffff;
|
|
} while(GetBasicToken(Token) == AvoidBasicToken);
|
|
}
|
|
if(pToken)
|
|
{
|
|
*pToken = Token;
|
|
}
|
|
if(pBasicToken)
|
|
{
|
|
*pBasicToken = GetBasicToken(Token);
|
|
}
|
|
|
|
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, nullptr, nullptr, nullptr, false);
|
|
}
|
|
|
|
void CServerBrowser::RequestCurrentServerWithRandomToken(const NETADDR &Addr, int *pBasicToken, int *pToken) const
|
|
{
|
|
RequestImpl(Addr, nullptr, pBasicToken, pToken, true);
|
|
}
|
|
|
|
void CServerBrowser::SetCurrentServerPing(const NETADDR &Addr, int Ping)
|
|
{
|
|
SetLatency(Addr, minimum(Ping, 999));
|
|
}
|
|
|
|
void ServerBrowserFillEstimatedLatency(int OwnLocation, const IServerBrowserPingCache::CEntry *pEntries, int NumEntries, int *pIndex, NETADDR Addr, CServerInfo *pInfo)
|
|
{
|
|
Addr.port = 0;
|
|
while(*pIndex < NumEntries && net_addr_comp(&pEntries[*pIndex].m_Addr, &Addr) < 0)
|
|
{
|
|
*pIndex += 1;
|
|
}
|
|
if(*pIndex >= NumEntries || net_addr_comp(&pEntries[*pIndex].m_Addr, &Addr) != 0)
|
|
{
|
|
pInfo->m_LatencyIsEstimated = true;
|
|
pInfo->m_Latency = CServerInfo::EstimateLatency(OwnLocation, pInfo->m_Location);
|
|
return;
|
|
}
|
|
pInfo->m_LatencyIsEstimated = false;
|
|
pInfo->m_Latency = pEntries[*pIndex].m_Ping;
|
|
}
|
|
|
|
void CServerBrowser::UpdateFromHttp()
|
|
{
|
|
const IServerBrowserPingCache::CEntry *pPingEntries;
|
|
int NumPingEntries;
|
|
m_pPingCache->GetPingCache(&pPingEntries, &NumPingEntries);
|
|
int OwnLocation;
|
|
if(str_comp(g_Config.m_BrLocation, "auto") == 0)
|
|
{
|
|
OwnLocation = m_OwnLocation;
|
|
}
|
|
else
|
|
{
|
|
if(CServerInfo::ParseLocation(&OwnLocation, g_Config.m_BrLocation))
|
|
{
|
|
char aBuf[64];
|
|
str_format(aBuf, sizeof(aBuf), "cannot parse br_location: '%s'", g_Config.m_BrLocation);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse", aBuf);
|
|
}
|
|
}
|
|
|
|
int NumServers = m_pHttp->NumServers();
|
|
int NumLegacyServers = m_pHttp->NumLegacyServers();
|
|
if(m_ServerlistType != IServerBrowser::TYPE_INTERNET)
|
|
{
|
|
class CWantedAddr
|
|
{
|
|
public:
|
|
NETADDR m_Addr;
|
|
bool m_FallbackToPing;
|
|
bool m_Got;
|
|
};
|
|
std::vector<CWantedAddr> aWantedAddresses;
|
|
int LegacySetType;
|
|
if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
|
|
{
|
|
for(int i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
aWantedAddresses.push_back(CWantedAddr{m_aFavoriteServers[i], m_aFavoriteServersAllowPing[i], false});
|
|
}
|
|
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(CWantedAddr{pCntr->m_aServers[g], false, false});
|
|
}
|
|
}
|
|
}
|
|
std::vector<int> aSortedServers;
|
|
std::vector<int> aSortedLegacyServers;
|
|
aSortedServers.reserve(NumServers);
|
|
for(int i = 0; i < NumServers; i++)
|
|
{
|
|
aSortedServers.push_back(i);
|
|
}
|
|
aSortedLegacyServers.reserve(NumLegacyServers);
|
|
for(int i = 0; i < NumLegacyServers; i++)
|
|
{
|
|
aSortedLegacyServers.push_back(i);
|
|
}
|
|
|
|
class CWantedAddrComparer
|
|
{
|
|
public:
|
|
bool operator()(const CWantedAddr &a, const CWantedAddr &b)
|
|
{
|
|
return net_addr_comp(&a.m_Addr, &b.m_Addr) < 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;
|
|
int p = 0;
|
|
while(i < aWantedAddresses.size() && j < aSortedServers.size())
|
|
{
|
|
int Cmp = net_addr_comp(&aWantedAddresses[i].m_Addr, &m_pHttp->ServerAddress(aSortedServers[j]));
|
|
if(Cmp != 0)
|
|
{
|
|
if(Cmp < 0)
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
continue;
|
|
}
|
|
aWantedAddresses[i].m_Got = true;
|
|
NETADDR Addr;
|
|
CServerInfo Info;
|
|
m_pHttp->Server(aSortedServers[j], &Addr, &Info);
|
|
ServerBrowserFillEstimatedLatency(OwnLocation, pPingEntries, NumPingEntries, &p, 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_Addr, &m_pHttp->LegacyServer(aSortedLegacyServers[j]));
|
|
if(Cmp != 0)
|
|
{
|
|
if(Cmp < 0)
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
j++;
|
|
}
|
|
continue;
|
|
}
|
|
aWantedAddresses[i].m_Got = true;
|
|
Set(m_pHttp->LegacyServer(aSortedLegacyServers[j]), LegacySetType, -1, nullptr);
|
|
i++;
|
|
j++;
|
|
}
|
|
for(const CWantedAddr &Wanted : aWantedAddresses)
|
|
{
|
|
if(!Wanted.m_Got)
|
|
{
|
|
if(Wanted.m_FallbackToPing)
|
|
{
|
|
Set(Wanted.m_Addr, LegacySetType, -1, nullptr);
|
|
}
|
|
else
|
|
{
|
|
// Also add favorites we're not allowed to ping.
|
|
if(LegacySetType == IServerBrowser::SET_FAV_ADD && !Find(Wanted.m_Addr))
|
|
{
|
|
Add(Wanted.m_Addr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
int p = 0;
|
|
for(int i = 0; i < NumServers; i++)
|
|
{
|
|
NETADDR Addr;
|
|
CServerInfo Info;
|
|
m_pHttp->Server(i, &Addr, &Info);
|
|
ServerBrowserFillEstimatedLatency(OwnLocation, pPingEntries, NumPingEntries, &p, 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::CleanUp()
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
void CServerBrowser::Update(bool ForceResort)
|
|
{
|
|
int64_t Timeout = time_freq();
|
|
int64_t Now = time_get();
|
|
|
|
const char *pHttpBestUrl;
|
|
if(!m_pHttp->GetBestUrl(&pHttpBestUrl) && pHttpBestUrl != m_pHttpPrevBestUrl)
|
|
{
|
|
str_copy(g_Config.m_BrCachedBestServerinfoUrl, pHttpBestUrl, sizeof(g_Config.m_BrCachedBestServerinfoUrl));
|
|
m_pHttpPrevBestUrl = pHttpBestUrl;
|
|
}
|
|
|
|
m_pHttp->Update();
|
|
|
|
if(m_ServerlistType != TYPE_LAN && m_RefreshingHttp && !m_pHttp->IsRefreshing())
|
|
{
|
|
m_RefreshingHttp = false;
|
|
CleanUp();
|
|
UpdateFromHttp();
|
|
// TODO: move this somewhere else
|
|
Sort();
|
|
return;
|
|
}
|
|
|
|
CServerEntry *pEntry = m_pFirstReqServer;
|
|
int Count = 0;
|
|
while(true)
|
|
{
|
|
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, nullptr, nullptr, false);
|
|
}
|
|
|
|
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(true)
|
|
{
|
|
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(true)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
int CServerBrowser::FindFavorite(const NETADDR &Addr) const
|
|
{
|
|
// search for the address
|
|
for(int i = 0; i < m_NumFavoriteServers; i++)
|
|
{
|
|
if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool CServerBrowser::GotInfo(const NETADDR &Addr) const
|
|
{
|
|
CServerEntry *pEntry = ((CServerBrowser *)this)->Find(Addr);
|
|
return pEntry && pEntry->m_GotInfo;
|
|
}
|
|
|
|
bool CServerBrowser::IsFavorite(const NETADDR &Addr) const
|
|
{
|
|
return FindFavorite(Addr) >= 0;
|
|
}
|
|
|
|
bool CServerBrowser::IsFavoritePingAllowed(const NETADDR &Addr) const
|
|
{
|
|
int i = FindFavorite(Addr);
|
|
dbg_assert(i >= 0, "invalid favorite");
|
|
return i >= 0 && m_aFavoriteServersAllowPing[i];
|
|
}
|
|
|
|
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
|
|
if(IsFavorite(Addr))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// add the server to the list
|
|
m_aFavoriteServers[m_NumFavoriteServers] = Addr;
|
|
m_aFavoriteServersAllowPing[m_NumFavoriteServers] = false;
|
|
m_NumFavoriteServers++;
|
|
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::FavoriteAllowPing(const NETADDR &Addr, bool AllowPing)
|
|
{
|
|
int i = FindFavorite(Addr);
|
|
dbg_assert(i >= 0, "invalid favorite");
|
|
m_aFavoriteServersAllowPing[i] = AllowPing;
|
|
}
|
|
|
|
void CServerBrowser::RemoveFavorite(const NETADDR &Addr)
|
|
{
|
|
int i = FindFavorite(Addr);
|
|
if(i < 0)
|
|
{
|
|
return;
|
|
}
|
|
mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i + 1], sizeof(NETADDR) * (m_NumFavoriteServers - (i + 1)));
|
|
mem_move(&m_aFavoriteServersAllowPing[i], &m_aFavoriteServersAllowPing[i + 1], sizeof(bool) * (m_NumFavoriteServers - (i + 1)));
|
|
m_NumFavoriteServers--;
|
|
|
|
CServerEntry *pEntry = Find(Addr);
|
|
if(pEntry)
|
|
pEntry->m_Info.m_Favorite = false;
|
|
}
|
|
|
|
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()
|
|
{
|
|
IOHANDLE File = m_pStorage->OpenFile(DDNET_INFO, IOFLAG_READ | IOFLAG_SKIP_BOM, 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);
|
|
|
|
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;
|
|
}
|
|
|
|
m_OwnLocation = CServerInfo::LOC_UNKNOWN;
|
|
if(m_pDDNetInfo)
|
|
{
|
|
const json_value &Location = (*m_pDDNetInfo)["location"];
|
|
if(Location.type != json_string || CServerInfo::ParseLocation(&m_OwnLocation, Location))
|
|
{
|
|
char aBuf[64];
|
|
str_format(aBuf, sizeof(aBuf), "cannot parse location from info.json: '%s'", (const char *)Location);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse", aBuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *CServerBrowser::GetTutorialServer()
|
|
{
|
|
// Use DDNet tab as default after joining tutorial, also makes sure Find() actually works
|
|
// Note that when no server info has been loaded yet, this will not return a result immediately.
|
|
g_Config.m_UiPage = CMenus::PAGE_DDNET;
|
|
Refresh(IServerBrowser::TYPE_DDNET);
|
|
|
|
CNetwork *pNetwork = &m_aNetworks[NETWORK_DDNET];
|
|
const char *pBestAddr = nullptr;
|
|
int BestLatency = std::numeric_limits<int>::max();
|
|
|
|
for(int i = 0; i < pNetwork->m_NumCountries; i++)
|
|
{
|
|
CNetworkCountry *pCntr = &pNetwork->m_aCountries[i];
|
|
for(int j = 0; j < pCntr->m_NumServers; j++)
|
|
{
|
|
CServerEntry *pEntry = Find(pCntr->m_aServers[j]);
|
|
if(!pEntry)
|
|
continue;
|
|
if(str_find(pEntry->m_Info.m_aName, "(Tutorial)") == 0)
|
|
continue;
|
|
if(pEntry->m_Info.m_NumPlayers > pEntry->m_Info.m_MaxPlayers - 10)
|
|
continue;
|
|
if(pEntry->m_Info.m_Latency >= BestLatency)
|
|
continue;
|
|
BestLatency = pEntry->m_Info.m_Latency;
|
|
pBestAddr = pEntry->m_Info.m_aAddress;
|
|
}
|
|
}
|
|
return pBestAddr;
|
|
}
|
|
|
|
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);
|
|
if(!pSelf->m_aFavoriteServersAllowPing[i])
|
|
{
|
|
str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr);
|
|
}
|
|
else
|
|
{
|
|
// Add quotes to the first parameter for backward
|
|
// compatibility with versions that took a `r` console
|
|
// parameter.
|
|
str_format(aBuffer, sizeof(aBuffer), "add_favorite \"%s\" allow_ping", 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 &Net : m_aNetworks)
|
|
{
|
|
for(int i = 0; i < Net.m_NumCountries; i++)
|
|
{
|
|
const char *pName = Net.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 199;
|
|
}
|
|
return 99;
|
|
}
|
|
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[][6] = {
|
|
"an", // LOC_UNKNOWN
|
|
"af", // LOC_AFRICA
|
|
"as", // LOC_ASIA
|
|
"oc", // LOC_AUSTRALIA
|
|
"eu", // LOC_EUROPE
|
|
"na", // LOC_NORTH_AMERICA
|
|
"sa", // LOC_SOUTH_AMERICA
|
|
"as:cn", // LOC_CHINA
|
|
};
|
|
for(int i = std::size(LOCATIONS) - 1; i >= 0; i--)
|
|
{
|
|
if(str_startswith(pString, LOCATIONS[i]))
|
|
{
|
|
*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_startswith(pInfo->m_aGameType, "bw ")) || (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, "+");
|
|
}
|