/* (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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "serverbrowser.h"
inline int AddrHash(const NETADDR *pAddr)
{
if(pAddr->type==NETTYPE_IPV4)
return (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF;
else
return (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3]+pAddr->ip[4]+pAddr->ip[5]+pAddr->ip[6]+pAddr->ip[7]+
pAddr->ip[8]+pAddr->ip[9]+pAddr->ip[10]+pAddr->ip[11]+pAddr->ip[12]+pAddr->ip[13]+pAddr->ip[14]+pAddr->ip[15])&0xFF;
}
inline int GetNewToken()
{
return random_int() & 0x7FFFFFFF;
}
//
CServerBrowser::CServerBrowser()
{
m_pMasterServer = 0;
//
mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp));
m_pFirstReqServer = 0; // request list
m_pLastReqServer = 0;
m_NumRequests = 0;
m_NeedRefresh = 0;
m_NumServers = 0;
m_NumServerCapacity = 0;
m_NumPlayers = 0;
// the token is to keep server refresh separated from each other
m_CurrentLanToken = 1;
m_ServerlistType = 0;
m_BroadcastTime = 0;
}
void CServerBrowser::Init(class CNetClient *pNetClient, const char *pNetVersion)
{
m_pConsole = Kernel()->RequestInterface();
m_pMasterServer = Kernel()->RequestInterface();
m_pNetClient = pNetClient;
m_ServerBrowserFavorites.Init(pNetClient, m_pConsole, Kernel()->RequestInterface(), Kernel()->RequestInterface());
m_ServerBrowserFilter.Init(Kernel()->RequestInterface(), pNetVersion);
}
void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo)
{
CServerEntry *pEntry = 0;
if(Type == SET_MASTER_ADD)
{
if(m_ServerlistType != IServerBrowser::TYPE_INTERNET)
return;
if(!Find(Addr))
{
pEntry = Add(Addr);
QueueRequest(pEntry);
}
}
/*else if(Type == SET_FAV_ADD)
{
if(m_ServerlistType != IServerBrowser::TYPE_FAVORITES)
return;
if(!Find(Addr))
{
pEntry = Add(Addr);
QueueRequest(pEntry);
}
}*/
else if(Type == SET_TOKEN)
{
if(m_ServerlistType == IServerBrowser::TYPE_LAN && Token != m_CurrentLanToken)
return;
pEntry = Find(Addr);
if(!pEntry && m_ServerlistType == IServerBrowser::TYPE_LAN && m_BroadcastTime+time_freq() >= time_get())
pEntry = Add(Addr);
if(pEntry && ((pEntry->m_InfoState == CServerEntry::STATE_PENDING && Token == pEntry->m_CurrentToken) || m_ServerlistType == IServerBrowser::TYPE_LAN))
{
SetInfo(pEntry, *pInfo);
if(m_ServerlistType == IServerBrowser::TYPE_LAN)
pEntry->m_Info.m_Latency = min(static_cast((time_get()-m_BroadcastTime)*1000/time_freq()), 999);
else
pEntry->m_Info.m_Latency = min(static_cast((time_get()-pEntry->m_RequestTime)*1000/time_freq()), 999);
RemoveRequest(pEntry);
}
}
m_ServerBrowserFilter.Sort(m_ppServerlist, m_NumServers, CServerBrowserFilter::RESORT_FLAG_FORCE);
}
void CServerBrowser::Update(bool ForceResort)
{
int64 Timeout = time_freq();
int64 Now = time_get();
int Count;
CServerEntry *pEntry, *pNext;
// do server list requests
if(m_NeedRefresh && !m_pMasterServer->IsRefreshing())
{
CNetChunk Packet;
m_NeedRefresh = 0;
mem_zero(&Packet, sizeof(Packet));
Packet.m_ClientID = -1;
Packet.m_Flags = NETSENDFLAG_CONNLESS;
Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST);
Packet.m_pData = SERVERBROWSE_GETLIST;
for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++)
{
if(!m_pMasterServer->IsValid(i))
continue;
Packet.m_Address = m_pMasterServer->GetAddr(i);
m_pNetClient->Send(&Packet);
}
if(g_Config.m_Debug)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", "requesting server list");
}
// do timeouts
pEntry = m_pFirstReqServer;
while(1)
{
if(!pEntry) // no more entries
break;
pNext = pEntry->m_pNextReq;
if(pEntry->m_RequestTime && pEntry->m_RequestTime+Timeout < Now)
{
// timeout
RemoveRequest(pEntry);
}
pEntry = pNext;
}
// do timeouts
pEntry = m_pFirstReqServer;
Count = 0;
while(1)
{
if(!pEntry) // no more entries
break;
// no more then 10 concurrent requests
if(Count == g_Config.m_BrMaxRequests)
break;
if(pEntry->m_RequestTime == 0)
RequestImpl(pEntry->m_Addr, pEntry);
Count++;
pEntry = pEntry->m_pNextReq;
}
// update favorite
const NETADDR *pFavAddr = m_ServerBrowserFavorites.UpdateFavorites();
if(pFavAddr)
{
CServerEntry *pEntry = Find(*pFavAddr);
if(pEntry)
pEntry->m_Info.m_Favorite = 1;
}
m_ServerBrowserFilter.Sort(m_ppServerlist, m_NumServers, ForceResort ? CServerBrowserFilter::RESORT_FLAG_FORCE : 0);
}
// interface functions
void CServerBrowser::Refresh(int Type)
{
// clear out everything
m_ServerlistHeap.Reset();
m_NumServers = 0;
m_NumPlayers = 0;
m_ServerBrowserFilter.Clear();
mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp));
m_pFirstReqServer = 0;
m_pLastReqServer = 0;
m_NumRequests = 0;
// next token
m_CurrentLanToken = GetNewToken();
//
m_ServerlistType = Type;
if(Type == IServerBrowser::TYPE_LAN)
{
CPacker Packer;
Packer.Reset();
Packer.AddRaw(SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
Packer.AddInt(m_CurrentLanToken);
/* do the broadcast version */
CNetChunk Packet;
mem_zero(&Packet, sizeof(Packet));
Packet.m_Address.type = m_pNetClient->NetType()|NETTYPE_LINK_BROADCAST;
Packet.m_ClientID = -1;
Packet.m_Flags = NETSENDFLAG_CONNLESS|NETSENDFLAG_STATELESS;
Packet.m_DataSize = Packer.Size();
Packet.m_pData = Packer.Data();
m_BroadcastTime = time_get();
for(int 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_INTERNET)
m_NeedRefresh = 1;
/*else if(Type == IServerBrowser::TYPE_FAVORITES)
{
for(int i = 0; i < m_NumFavoriteServers; i++)
if(m_aFavoriteServers[i].m_State >= FAVSTATE_ADDR)
Set(m_aFavoriteServers[i].m_Addr, SET_FAV_ADD, -1, 0);
}*/
}
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::AddFavorite(const CServerInfo *pInfo)
{
if(m_ServerBrowserFavorites.AddFavoriteEx(pInfo->m_aHostname, &pInfo->m_NetAddr, true))
{
CServerEntry *pEntry = Find(pInfo->m_NetAddr);
if(pEntry)
pEntry->m_Info.m_Favorite = 1;
// refresh servers in all filters where favorites are filtered
m_ServerBrowserFilter.Sort(m_ppServerlist, m_NumServers, CServerBrowserFilter::RESORT_FLAG_FAV);
}
}
void CServerBrowser::RemoveFavorite(const CServerInfo *pInfo)
{
if(m_ServerBrowserFavorites.RemoveFavoriteEx(pInfo->m_aHostname, &pInfo->m_NetAddr))
{
CServerEntry *pEntry = Find(pInfo->m_NetAddr);
if(pEntry)
pEntry->m_Info.m_Favorite = 0;
// refresh servers in all filters where favorites are filtered
m_ServerBrowserFilter.Sort(m_ppServerlist, m_NumServers, CServerBrowserFilter::RESORT_FLAG_FAV);
}
}
// manipulate entries
CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
{
// create new pEntry
CServerEntry *pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry));
mem_zero(pEntry, sizeof(CServerEntry));
// set the info
pEntry->m_Addr = Addr;
pEntry->m_InfoState = CServerEntry::STATE_INVALID;
pEntry->m_CurrentToken = GetNewToken();
pEntry->m_Info.m_NetAddr = Addr;
pEntry->m_Info.m_Latency = 999;
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));
str_copy(pEntry->m_Info.m_aHostname, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aHostname));
// check if it's a favorite
if(m_ServerBrowserFavorites.FindFavoriteByAddr(Addr, 0))
pEntry->m_Info.m_Favorite = 1;
// add to the hash list
int Hash = AddrHash(&Addr);
pEntry->m_pNextIp = m_aServerlistIp[Hash];
m_aServerlistIp[Hash] = pEntry;
if(m_NumServers == m_NumServerCapacity)
{
if(m_NumServerCapacity == 0)
{
// alloc start size
m_NumServerCapacity = 1000;
m_ppServerlist = (CServerEntry **)mem_alloc(m_NumServerCapacity*sizeof(CServerEntry*), 1);
}
else
{
// increase size
m_NumServerCapacity += 100;
CServerEntry **ppNewlist = (CServerEntry **)mem_alloc(m_NumServerCapacity*sizeof(CServerEntry*), 1);
mem_copy(ppNewlist, m_ppServerlist, m_NumServers*sizeof(CServerEntry*));
mem_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;
}
CServerEntry *CServerBrowser::Find(const NETADDR &Addr)
{
for(CServerEntry *pEntry = m_aServerlistIp[AddrHash(&Addr)]; 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;
m_NumRequests++;
}
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--;
}
}
void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const
{
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);
}
CPacker Packer;
Packer.Reset();
Packer.AddRaw(SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
Packer.AddInt(pEntry ? pEntry->m_CurrentToken : m_CurrentLanToken);
CNetChunk Packet;
Packet.m_ClientID = -1;
Packet.m_Address = Addr;
Packet.m_Flags = NETSENDFLAG_CONNLESS|NETSENDFLAG_STATELESS;
Packet.m_DataSize = Packer.Size();
Packet.m_pData = Packer.Data();
m_pNetClient->Send(&Packet);
if(pEntry)
{
pEntry->m_RequestTime = time_get();
pEntry->m_InfoState = CServerEntry::STATE_PENDING;
}
}
void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
{
int Fav = pEntry->m_Info.m_Favorite;
pEntry->m_Info = Info;
pEntry->m_Info.m_Flags &= FLAG_PASSWORD;
if(str_comp(pEntry->m_Info.m_aGameType, "DM") == 0 || str_comp(pEntry->m_Info.m_aGameType, "TDM") == 0 || str_comp(pEntry->m_Info.m_aGameType, "CTF") == 0 ||
str_comp(pEntry->m_Info.m_aGameType, "SUR") == 0 || str_comp(pEntry->m_Info.m_aGameType, "LMS") == 0)
pEntry->m_Info.m_Flags |= FLAG_PURE;
if(str_comp(pEntry->m_Info.m_aMap, "dm1") == 0 || str_comp(pEntry->m_Info.m_aMap, "dm2") == 0 || str_comp(pEntry->m_Info.m_aMap, "dm6") == 0 ||
str_comp(pEntry->m_Info.m_aMap, "dm7") == 0 || str_comp(pEntry->m_Info.m_aMap, "dm8") == 0 || str_comp(pEntry->m_Info.m_aMap, "dm9") == 0 ||
str_comp(pEntry->m_Info.m_aMap, "ctf1") == 0 || str_comp(pEntry->m_Info.m_aMap, "ctf2") == 0 || str_comp(pEntry->m_Info.m_aMap, "ctf3") == 0 ||
str_comp(pEntry->m_Info.m_aMap, "ctf4") == 0 || str_comp(pEntry->m_Info.m_aMap, "ctf5") == 0 || str_comp(pEntry->m_Info.m_aMap, "ctf6") == 0 ||
str_comp(pEntry->m_Info.m_aMap, "ctf7") == 0)
pEntry->m_Info.m_Flags |= FLAG_PUREMAP;
pEntry->m_Info.m_Favorite = Fav;
pEntry->m_Info.m_NetAddr = pEntry->m_Addr;
m_NumPlayers += pEntry->m_Info.m_NumPlayers;
pEntry->m_InfoState = CServerEntry::STATE_READY;
}