Implement multi-master queries and heartbeats (#717)
Some checks are pending
Build branch / build-all (push) Waiting to run
CodeQL / Analyze (push) Waiting to run

Implement multi-master server list query and multi-master heartbeats. The server list is fetched from multiple masters to improve redundancy, reliability and performance. This improves multiplayer availability by implementing support for using multiple masters.
- The server sends an heartbeat to all masters at once
- The client can query up to 4 masters in parallel depending on their rate
This commit is contained in:
smallmodel 2025-04-27 22:14:08 +02:00 committed by GitHub
parent 08a985d183
commit cce81cabff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 603 additions and 142 deletions

View file

@ -132,7 +132,12 @@ qboolean g_NeedAdditionalLANSearch = qfalse;
qboolean g_bDoneUpdating[2];
ServerListInstance g_ServerListInst[2];
void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, void *param1, void *param2);
// Fixed in OPM
// It was a static vaariable inside UpdateServerListCallBack
// that was set to 0 when the mode changed. This caused some issues
static int g_iServerQueryCount = 0;
static int g_iServerTotalCount = 0;
static void AddFilter(char *filter, const char *value);
FAKKServerListItem::FAKKServerListItem(
@ -713,6 +718,9 @@ void UIFAKKServerList::NewServerList(void)
iNumConcurrent = 15;
}
g_iServerQueryCount = 0;
g_iServerTotalCount = 0;
if (com_target_game->integer < target_game_e::TG_MOHTT) {
game_name = GS_GetCurrentGameName();
secret_key = GS_GetCurrentGameKey();
@ -1021,7 +1029,7 @@ void UIFAKKServerList::SortByColumn(int column)
}
}
void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, void *param1, void *param2)
void UIFAKKServerList::UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, void *param1, void *param2)
{
int i, j;
int iPort, iGameSpyPort;
@ -1029,8 +1037,6 @@ void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, v
str sAddress;
GServer server;
FAKKServerListItem *pNewServerItem;
static int iServerQueryCount = 0;
static int iServerTotalCount = 0;
UIFAKKServerList *uiServerList;
int iServerType;
// filters
@ -1073,7 +1079,10 @@ void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, v
return;
}
Cvar_Set("dm_serverstatusbar", va("%i", (int)(uintptr_t)param2));
//Cvar_Set("dm_serverstatusbar", va("%i", (int)(uintptr_t)param2));
// Fixed in OPM
// As both lists are combined, show the correct percentage
Cvar_Set("dm_serverstatusbar", va("%i", 100 * g_iServerQueryCount / g_iServerTotalCount));
}
if (msg == LIST_PROGRESS) {
@ -1163,8 +1172,8 @@ void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, v
pNewServerItem->SetQueried(true);
pNewServerItem->SetNumPlayers(ServerGetIntValue(server, "numplayers", 0));
iServerQueryCount++;
Cvar_Set("dm_servercount", va("%d/%d", iServerQueryCount, iServerTotalCount));
g_iServerQueryCount++;
Cvar_Set("dm_servercount", va("%d/%d", g_iServerQueryCount, g_iServerTotalCount));
uiServerList->SortByLastSortColumn();
} else if (msg == LIST_STATECHANGED) {
@ -1203,7 +1212,6 @@ void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, v
uiServerList->m_bGettingList[1] = false;
}
uiServerList->m_bUpdatingList = true;
iServerQueryCount = 0;
return;
case GServerListState::sl_lanlist:
Cvar_Set("dm_serverstatus", "Searching LAN.");
@ -1212,18 +1220,22 @@ void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, v
case GServerListState::sl_querying:
Cvar_Set("dm_serverstatus", "Querying Servers.");
uiServerList->m_bUpdatingList = true;
iServerQueryCount = 0;
iServerTotalCount = 0;
break;
default:
break;
}
if (!uiServerList->m_bGettingList[0] && !uiServerList->m_bGettingList[1]) {
return;
}
//if (!uiServerList->m_bGettingList[0] && !uiServerList->m_bGettingList[1]) {
// return;
//}
iServerTotalCount += ServerListCount(serverlist);
// Rebuild the number of servers
g_iServerTotalCount = 0;
for(i = 0; i < ARRAY_LEN(uiServerList->m_serverList); i++) {
if (uiServerList->m_bGettingList[i] && uiServerList->m_serverList[i]) {
g_iServerTotalCount += ServerListCount(uiServerList->m_serverList[i]);
}
}
// Removed in 2.0
// Only add entries for servers that are queried successfully

View file

@ -22,6 +22,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#pragma once
#include "../gamespy/goaceng.h"
class UIFAKKServerList : public UIListCtrl {
protected:
// need a new struct instead of gamespy
@ -47,6 +49,7 @@ protected:
void MakeLANListing( Event *ev );
void UpdateServer( Event *ev );
static int ServerCompareFunction( const UIListCtrlItem *i1, const UIListCtrlItem *i2, int columnname );
static void UpdateServerListCallBack(GServerList serverlist, int msg, void *instance, void *param1, void *param2);
public:
UIFAKKServerList();

View file

@ -25,12 +25,25 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "cl_gamespy.h"
#include "q_gamespy.h"
const char *ServerListGetHost()
unsigned int ServerListGetNumMasters()
{
return Com_GetMasterHost();
return Com_GetNumMasterEntries();
}
int ServerListGetMsPort()
const char *ServerListGetHost(int index)
{
return Com_GetMasterQueryPort();
master_entry_t entry;
Com_GetMasterEntry(index, &entry);
return entry.host;
}
int ServerListGetMsPort(int index)
{
master_entry_t entry;
Com_GetMasterEntry(index, &entry);
return entry.queryport;
}
void CL_RestartGamespy_f() {}

View file

@ -23,3 +23,5 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// cl_gamespy.h -- Game-specific client GameSpy code
#pragma once
void CL_RestartGamespy_f();

View file

@ -77,7 +77,7 @@ lanlist - we are waiting for replies from servers on the LAN
querying - the servers on the list are being queried
connecting (added in OPM) - socket is currently connecting to the master server
*/
typedef enum {sl_idle, sl_listxfer, sl_lanlist, sl_querying, sl_connecting} GServerListState;
typedef enum {sl_idle, sl_listxfer, sl_lanlist, sl_querying} GServerListState;
/* Comparision types for the ServerListSort function
int - assume the values are int and do an integer compare

View file

@ -19,6 +19,24 @@ Fax(714)549-0757
for this SDK. It also has a change history for the SDK.
******/
/**
* The following modifications were made:
*
* 1) Non-blocking server list sockets. List requests are asynchronous and won't block the game. The only blocking request could be due to DNS resolution.
* 2) Server list sockets will timeout after 10 seconds of no reply.
* 3) Parallel server list requests.
* 3.1) A pool of sockets is initially created
* 3.2) The server list connects idling sockets to available masters, afterwards the server list state is set to `sl_listxfer`.
* 3.3) The server list sends the list request to connected masters, and receives the data for each.
* 3.4) When finished fetching 1 master (got `\final`), the server list state transitions to `sl_querying`.
* 3.5) The server list queries all fetched servers. It can still continue fetching list from currently connected masters
* but it will not connect to other masters while servers are being queried. It won't be sending more queries if remaining masters replied in under 500 ms.
* 3.6) When finished querying all fetched servers, the server list restarts from 3.2 and queries next masters.
*
* The idea here is to ensure the redundancy of the server list between multiple masters even if the results are combined.
* Optionally for best results, the game should return a list of masters from the same community.
*/
#include "goaceng.h"
#include "gserver.h"
#if defined(applec) || defined(THINK_C) || defined(__MWERKS__) && !defined(__KATANA__)
@ -53,13 +71,16 @@ Fax(714)549-0757
*
* @return const char* The master host
*/
extern const char *ServerListGetHost();
extern int ServerListGetMsPort();
extern unsigned int ServerListGetNumMasters();
extern const char *ServerListGetHost(int index);
extern int ServerListGetMsPort(int index);
#define MSHOST ServerListGetHost()
#define MSPORT ServerListGetMsPort()
//#define MSHOST ServerListGetHost(0)
//#define MSPORT ServerListGetMsPort(0)
#define SERVER_GROWBY 64
#define LAN_SEARCH_TIME 3000 //3 sec
#define SERVER_QUERY_MAX_PAUSE 500
#define INTERNET_SEARCH_TIME 10000 //10 sec
#define LIST_NUMKEYBUCKETS 500
#define LIST_NUMKEYCHAINS 4
@ -78,6 +99,17 @@ typedef struct
} UpdateInfo;
typedef enum { pi_fieldcount, pi_fields, pi_servers } GParseInfoState;
typedef enum { ls_none, ls_connecting, ls_connected } GListSocketState;
typedef struct GServerListSocketImplementation
{
SOCKET s;
GListSocketState socketstate;
char data[2048];
int oldlen;
gsi_time lastreplytime;
GCryptInfo cryptinfo;
} *GServerListSocket;
struct GServerListImplementation
{
@ -95,7 +127,10 @@ struct GServerListImplementation
void *instance;
char *sortkey;
gbool sortascending;
SOCKET slsocket;
GServerListSocket slsockets;
int numslsockets;
int startslindex;
gsi_time lastsltime;
unsigned long lanstarttime;
GQueryType querytype;
HashTable keylist;
@ -103,9 +138,10 @@ struct GServerListImplementation
// Added in 2.0
int numservers;
// Added in 2.0
GCryptInfo cryptinfo;
//GCryptInfo cryptinfo;
// Added in OPM
int encryptdata;
gbool async;
GParseInfoState pistate;
};
@ -121,7 +157,7 @@ const int querylengths[NUM_QUERYTYPES] = {7,6,7,9,13,8};
static void KeyValFree(void *elem);
static int KeyValCompareKeyA(const void *entry1, const void *entry2);
static int KeyValHashKeyA(const void *elem, int numbuckets);
static gbool ServerListHasFinishedFetchingList(GServerList serverlist);
/* ServerListNew
----------------
@ -150,6 +186,19 @@ GServerList ServerListNew(const char *gamename, const char *enginename, const ch
list->numservers = 0;
// Added in OPM
list->encryptdata = 1;
list->async = 0;
list->numslsockets = maxconcupdates / 4;
if (list->numslsockets < 1) {
list->numslsockets = 1;
} else if (list->numslsockets > 4) {
// Above 4 seems too much
list->numslsockets = 4;
}
list->slsockets = (GServerListSocket)malloc(sizeof(struct GServerListSocketImplementation) * list->numslsockets);
memset(list->slsockets, 0, sizeof(struct GServerListSocketImplementation) * list->numslsockets);
list->startslindex = 0;
list->lastsltime = current_time();
SocketStartUp();
return list;
@ -163,6 +212,7 @@ void ServerListFree(GServerList serverlist)
ArrayFree(serverlist->servers);
TableFree(serverlist->keylist);
free(serverlist->updatelist);
free(serverlist->slsockets);
free(serverlist);
SocketShutDown();
@ -205,53 +255,90 @@ static GError FreeUpdateList(GServerList serverlist)
}
//create and connect a server list socket
static GError CreateServerListSocket(GServerList serverlist, gbool async)
static GError CreateServerListSocket(GServerList serverlist, GServerListSocket slsocket, gbool async)
{
struct sockaddr_in saddr;
struct hostent *hent;
int lasterr;
int i;
int port;
const char *host;
if (serverlist->startslindex >= ServerListGetNumMasters()) {
return GE_NODNS;
}
if (slsocket->socketstate != ls_none) {
closesocket(slsocket->s);
slsocket->s = INVALID_SOCKET;
}
port = ServerListGetMsPort(serverlist->startslindex);
host = ServerListGetHost(serverlist->startslindex);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(MSPORT);
saddr.sin_addr.s_addr = inet_addr(MSHOST);
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = inet_addr(host);
if (saddr.sin_addr.s_addr == INADDR_NONE)
{
hent = gethostbyname(MSHOST);
hent = gethostbyname(host);
if (!hent)
return GE_NODNS;
saddr.sin_addr.s_addr = *(u_long *)hent->h_addr_list[0];
}
serverlist->slsocket = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (serverlist->slsocket == INVALID_SOCKET)
slsocket->s = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (slsocket->s == INVALID_SOCKET)
return GE_NOSOCKET;
if (async) {
SetSockBlocking(serverlist->slsocket, 0);
SetSockBlocking(slsocket->s, 0);
}
if (connect ( serverlist->slsocket, (struct sockaddr *) &saddr, sizeof saddr ) != 0
&& (lasterr = GOAGetLastError(serverlist->slsocket), lasterr != WSAEWOULDBLOCK && lasterr != WSAEINPROGRESS))
if (connect ( slsocket->s, (struct sockaddr *) &saddr, sizeof saddr ) != 0
&& (lasterr = GOAGetLastError(slsocket->slsocket), lasterr != WSAEWOULDBLOCK && lasterr != WSAEINPROGRESS))
{
closesocket(serverlist->slsocket);
closesocket(slsocket->s);
slsocket->s = INVALID_SOCKET;
return GE_NOCONNECT;
}
slsocket->socketstate = ls_connecting;
slsocket->oldlen = 0;
slsocket->cryptinfo.offset = -1;
slsocket->lastreplytime = current_time();
serverlist->startslindex++;
//else we are connected
return 0;
}
//create and connect a server list sockets
static GError CreateServerListForAvailableSockets(GServerList serverlist, gbool async)
{
int i;
for(i = 0; i < serverlist->numslsockets; i++) {
if (serverlist->slsockets[i].socketstate == ls_none) {
CreateServerListSocket(serverlist, &serverlist->slsockets[i], async);
}
}
return 0;
}
//create and connect a server list socket
static GError CreateServerListLANSocket(GServerList serverlist)
{
int optval = 1;
GServerListSocket slsocket = &serverlist->slsockets[0];
serverlist->slsocket = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if (serverlist->slsocket == INVALID_SOCKET)
slsocket->s = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if (slsocket->s == INVALID_SOCKET)
return GE_NOSOCKET;
if (setsockopt(serverlist->slsocket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0)
if (setsockopt(slsocket->s, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0)
return GE_NOSOCKET;
//else we are ready to broadcast
@ -271,14 +358,14 @@ static void ServerListModeChange(GServerList serverlist, GServerListState newsta
// validate us to the master and send a list request
#define SECURE "\\secure\\"
static GError SendListRequest(GServerList serverlist, char *filter)
static GError SendListRequest(GServerList serverlist, GServerListSocket slsocket, char *filter)
{
char data[256], *ptr, result[64];
int len;
int i;
char *modifier;
len = recv(serverlist->slsocket, data, sizeof(data) - 1, 0);
len = recv(slsocket->s, data, sizeof(data) - 1, 0);
if (gsiSocketIsError(len)) {
return GE_NOCONNECT;
}
@ -309,7 +396,7 @@ static GError SendListRequest(GServerList serverlist, char *filter)
serverlist->enginename, ENGINE_VERSION, result); //validate us
}
len = send ( serverlist->slsocket, data, strlen(data), 0 );
len = send ( slsocket->s, data, strlen(data), 0 );
if (gsiSocketIsError(len) || len == 0)
return GE_NOCONNECT;
@ -330,11 +417,13 @@ static GError SendListRequest(GServerList serverlist, char *filter)
sprintf(data, "\\list\\%s\\gamename\\%s\\where\\%s\\final\\", modifier, serverlist->gamename, filter);
else
sprintf(data, "\\list\\%s\\gamename\\%s\\final\\", modifier, serverlist->gamename);
len = send ( serverlist->slsocket, data, strlen(data), 0 );
len = send ( slsocket->s, data, strlen(data), 0 );
if (gsiSocketIsError(len) || len == 0)
return GE_NOCONNECT;
ServerListModeChange(serverlist, sl_listxfer);
if (serverlist->state == sl_idle) {
ServerListModeChange(serverlist, sl_listxfer);
}
return 0;
}
@ -349,7 +438,7 @@ static GError SendBroadcastRequest(GServerList serverlist, int startport, int en
for (i = startport ; i <= endport ; i += delta)
{
saddr.sin_port = htons(i);
sendto(serverlist->slsocket, "\\status\\",8,0,(struct sockaddr *)&saddr,sizeof(saddr));
sendto(serverlist->slsockets[0].s, "\\status\\",8,0,(struct sockaddr *)&saddr,sizeof(saddr));
}
ServerListModeChange(serverlist, sl_lanlist);
serverlist->lanstarttime = current_time();
@ -379,45 +468,58 @@ GError ServerListUpdate(GServerList serverlist, gbool async)
return ServerListUpdate2(serverlist, async, NULL, qt_status);
}
/* ServerListCheckSocketError
-------------------------
Verify if the server list socket has an error or has timed out, and abort. */
GError ServerListCheckSocketError(GServerList serverlist, GServerListSocket slsocket)
{
int so_error;
int len = sizeof(so_error);
getsockopt(slsocket->s, SOL_SOCKET, SO_ERROR, &so_error, &len);
// Abort if the socket has an error or if it connect/recv has timed out
if (so_error || current_time() >= slsocket->lastreplytime + INTERNET_SEARCH_TIME) {
closesocket(slsocket->s);
slsocket->s = INVALID_SOCKET;
slsocket->socketstate = ls_none;
return GE_NOCONNECT;
}
return 0;
}
/* ServerListStartQuery
-------------------------
Start querying a GServerList. */
GError ServerListStartQuery(GServerList serverlist, gbool async)
GError ServerListStartQuery(GServerList serverlist, GServerListSocket slsocket, gbool async)
{
GError error;
int i;
if (serverlist->state != sl_connecting) {
if (slsocket->socketstate != ls_connecting) {
return GE_BUSY;
}
if (serverlist->abortupdate) {
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
for(i = 0; i < serverlist->numslsockets; i++) {
closesocket(serverlist->slsockets[i].s);
serverlist->slsockets[i].s = INVALID_SOCKET;
}
ServerListModeChange(serverlist, sl_idle);
return GE_NOCONNECT;
}
if (async) {
// Abort if the socket is in error
int so_error;
int len = sizeof(so_error);
getsockopt(serverlist->slsocket, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error) {
serverlist->state = sl_idle;
return GE_NOCONNECT;
}
// Check for pending data
char buf;
if (recv(serverlist->slsocket, &buf, 1, MSG_PEEK) == -1) {
if (recv(slsocket->s, &buf, 1, MSG_PEEK) == -1) {
return GE_BUSY;
}
}
serverlist->state = sl_idle;
slsocket->socketstate = ls_connected;
error = SendListRequest(serverlist, serverlist->filter);
error = SendListRequest(serverlist, slsocket, serverlist->filter);
if (error) return error;
if (serverlist->querytype != qt_grouprooms && serverlist->querytype != qt_masterinfo) {
error = InitUpdateList(serverlist);
@ -436,29 +538,38 @@ Start updating a GServerList. */
GError ServerListUpdate2(GServerList serverlist, gbool async, char *filter, GQueryType querytype)
{
GError error;
int i;
if (serverlist->state != sl_idle)
return GE_BUSY;
serverlist->querytype = querytype;
error = CreateServerListSocket(serverlist, async);
if (error) return error;
error = CreateServerListForAvailableSockets(serverlist, async);
//if (error) return error;
serverlist->nextupdate = 0;
serverlist->abortupdate = 0;
// Added in 2.0
serverlist->numservers = ServerListCount(serverlist);
// Added in 2.0
serverlist->cryptinfo.offset = -1;
strncpy(serverlist->filter, filter, sizeof(serverlist->filter));
//serverlist->cryptinfo.offset = -1;
if (filter) {
strncpy(serverlist->filter, filter, sizeof(serverlist->filter));
} else {
serverlist->filter[0] = 0;
}
serverlist->state = sl_connecting;
serverlist->async = async;
if (async) {
// As it's asynchronous, set the state to transfering
ServerListModeChange(serverlist, sl_listxfer);
return 0;
}
ServerListStartQuery(serverlist, async);
for(i = 0; i < serverlist->numslsockets; i++) {
ServerListStartQuery(serverlist, &serverlist->slsockets[i], async);
}
return 0;
}
@ -647,7 +758,7 @@ GError ServerListAuxUpdate(GServerList serverlist, const char *ip, int port, gbo
return 0;
}
static GError ServerListLANList(GServerList serverlist)
static GError ServerListLANList(GServerList serverlist, GServerListSocket slsocket)
{
struct timeval timeout = {0,0};
fd_set set;
@ -659,11 +770,11 @@ static GError ServerListLANList(GServerList serverlist)
while (1) //we break if the select fails
{
FD_ZERO(&set);
FD_SET( serverlist->slsocket, &set);
FD_SET( slsocket->s, &set);
error = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
if (gsiSocketIsError(error) || 0 == error) //no data
break;
error = recvfrom(serverlist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen );
error = recvfrom(slsocket->s, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen );
if (gsiSocketIsError(error))
continue;
//we got data, add the server to the list to update
@ -672,8 +783,8 @@ static GError ServerListLANList(GServerList serverlist)
}
if (current_time() - serverlist->lanstarttime > LAN_SEARCH_TIME) //done waiting for replies
{
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
closesocket(slsocket->s);
slsocket->s = INVALID_SOCKET;
ServerListModeChange(serverlist, sl_querying);
}
return 0;
@ -769,10 +880,10 @@ static int ServerListParseInfoList(GServerList serverlist, char *data, int len)
}
//reads the server list from the socket and parses it
static GError ServerListReadList(GServerList serverlist)
static GError ServerListReadList(GServerList serverlist, GServerListSocket slsocket)
{
static char data[2048]; //static input buffer
static int oldlen = 0;
//static char data[2048]; //static input buffer
//static int oldlen = 0;
fd_set set;
struct timeval timeout = {0,0};
int len, i;
@ -781,7 +892,7 @@ static GError ServerListReadList(GServerList serverlist)
unsigned short port;
FD_ZERO(&set);
FD_SET(serverlist->slsocket, &set);
FD_SET(slsocket->s, &set);
#ifndef KGTRN_ACCESS
i = select( FD_SETSIZE, &set, NULL, NULL, &timeout );
if (i <= 0)
@ -789,52 +900,57 @@ static GError ServerListReadList(GServerList serverlist)
#endif
//append to data
len = recv(serverlist->slsocket, data + oldlen, sizeof(data) - oldlen - 1, 0);
len = recv(slsocket->s, slsocket->data + slsocket->oldlen, sizeof(slsocket->data) - slsocket->oldlen - 1, 0);
if (gsiSocketIsError(len) || len == 0)
{
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
oldlen = 0; //clear data so it can be used again
ServerListHalt(serverlist);
closesocket(slsocket->s);
slsocket->s = INVALID_SOCKET;
slsocket->socketstate = ls_none;
slsocket->oldlen = 0; //clear data so it can be used again
//ServerListHalt(serverlist);
ServerListModeChange(serverlist, sl_querying);
return GE_NOCONNECT;
}
if (serverlist->encryptdata && serverlist->cryptinfo.offset != -1) {
slsocket->lastreplytime = current_time();
serverlist->lastsltime = slsocket->lastreplytime;
if (serverlist->encryptdata && slsocket->cryptinfo.offset != -1) {
// Added in 2.0
crypt_docrypt(&serverlist->cryptinfo, data + oldlen, len);
crypt_docrypt(&slsocket->cryptinfo, slsocket->data + slsocket->oldlen, len);
}
oldlen += len;
slsocket->oldlen += len;
p = data;
p = slsocket->data;
if (!serverlist->encryptdata) {
serverlist->cryptinfo.offset = 0;
} else if (serverlist->cryptinfo.offset == -1) {
slsocket->cryptinfo.offset = 0;
} else if (slsocket->cryptinfo.offset == -1) {
// Added in 2.0
if (oldlen > (*p ^ 0xEC)) {
if (slsocket->oldlen > (*p ^ 0xEC)) {
*p ^= 0xEC;
len = strlen(serverlist->seckey);
for (i = 0; i < len; i++) {
p[i + 1] ^= serverlist->seckey[i];
}
init_crypt_key((unsigned char *)p + 1, *p, &serverlist->cryptinfo);
init_crypt_key((unsigned char *)p + 1, *p, &slsocket->cryptinfo);
p += *p + 1;
crypt_docrypt(&serverlist->cryptinfo, (unsigned char *)p, oldlen - (p - data));
crypt_docrypt(&slsocket->cryptinfo, (unsigned char *)p, slsocket->oldlen - (p - slsocket->data));
}
}
if (serverlist->cryptinfo.offset != -1)
if (slsocket->cryptinfo.offset != -1)
{
while (p - data <= oldlen - 6)
while (p - slsocket->data <= slsocket->oldlen - 6)
{
if (strncmp(p,"\\final\\",7) == 0 || serverlist->abortupdate)
{
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
oldlen = 0; //clear data so it can be used again
closesocket(slsocket->s);
slsocket->s = INVALID_SOCKET;
slsocket->socketstate = ls_none;
slsocket->oldlen = 0; //clear data so it can be used again
if (serverlist->querytype == qt_grouprooms || serverlist->querytype == qt_masterinfo) {
ServerListModeChange(serverlist, sl_idle);
} else {
@ -842,11 +958,11 @@ static GError ServerListReadList(GServerList serverlist)
}
return 0; //get out!!
}
if (oldlen < 6) //no way it could be a full IP, quit
if (slsocket->oldlen < 6) //no way it could be a full IP, quit
break;
if (serverlist->querytype == qt_grouprooms || serverlist->querytype == qt_masterinfo) {
i = ServerListParseInfoList(serverlist, p, oldlen - (p - data));
i = ServerListParseInfoList(serverlist, p, slsocket->oldlen - (p - slsocket->data));
if (i < 0) {
serverlist->abortupdate = 1;
} else if (!i) {
@ -860,14 +976,14 @@ static GError ServerListReadList(GServerList serverlist)
p += 2;
// Added in 2.0
// Skip adding the server if already exists
if (ServerListFindServerMax(serverlist, ip, ntohs(port), serverlist->numservers) == -1) {
if (ServerListFindServer(serverlist, ip, ntohs(port)) == -1) {
ServerListAddServer(serverlist, ip, ntohs(port), serverlist->querytype);
}
}
}
}
oldlen = oldlen - (p - data);
memmove(data,p,oldlen); //shift it over
slsocket->oldlen = slsocket->oldlen - (p - slsocket->data);
memmove(slsocket->data,p,slsocket->oldlen); //shift it over
return 0;
}
@ -946,10 +1062,26 @@ static GError ServerListQueryLoop(GServerList serverlist)
if (serverlist->abortupdate || (serverlist->nextupdate >= ArrayLength(serverlist->servers) && scount == 0))
{ //we are done!!
FreeUpdateList(serverlist);
ServerListModeChange(serverlist, sl_idle);
if (serverlist->abortupdate) {
ServerListModeChange(serverlist, sl_idle);
return 0;
}
if (serverlist->startslindex < ServerListGetNumMasters() || !ServerListHasFinishedFetchingList(serverlist)) {
ServerListModeChange(serverlist, sl_listxfer);
} else {
// No more masters
ServerListModeChange(serverlist, sl_idle);
}
return 0;
}
if (!ServerListHasFinishedFetchingList(serverlist) && current_time() < serverlist->lastsltime + SERVER_QUERY_MAX_PAUSE) {
// Make sure to not send out other queries if currently fetching from other lists
// to avoid overloading the network
return GE_BUSY;
}
//now, send out queries on available sockets
for (i = 0 ; i < serverlist->maxupdates && serverlist->nextupdate < ArrayLength(serverlist->servers) ; i++)
if (serverlist->updatelist[i].currentserver == NULL) //it's availalbe
@ -968,31 +1100,96 @@ static GError ServerListQueryLoop(GServerList serverlist)
return 0;
}
/* ServerListThink
/* ServerListThinkSocket
------------------
For use with Async Updates. This needs to be called every ~10ms for list processing and
updating to occur during async server list updates */
GError ServerListThink(GServerList serverlist)
GError ServerListThinkSocket(GServerList serverlist, GServerListSocket slsocket)
{
GError socketerror;
if (slsocket->s == INVALID_SOCKET) {
return GE_NOSOCKET;
}
socketerror = ServerListCheckSocketError(serverlist, slsocket);
if (socketerror) {
return socketerror;
}
switch(slsocket->socketstate)
{
case ls_connecting:
ServerListStartQuery(serverlist, slsocket, 1);
break;
case ls_connected:
//read the data
return ServerListReadList(serverlist, slsocket);
break;
default:
break;
}
switch (serverlist->state)
{
case sl_idle: return 0;
case sl_listxfer:
//read the data
return ServerListReadList(serverlist);
//return ServerListReadList(serverlist, slsocket);
break;
case sl_lanlist:
return ServerListLANList(serverlist);
case sl_querying:
//do some queries
return ServerListQueryLoop(serverlist);
break;
case sl_connecting:
return ServerListStartQuery(serverlist, 1);
break;
return ServerListLANList(serverlist, slsocket);
}
return 0;
}
/* ServerListHasFinishedFetchingList
------------------
Whether or not the list has finished fetching */
static gbool ServerListHasFinishedFetchingList(GServerList serverlist)
{
int i;
for(i = 0; i < serverlist->numslsockets; i++) {
if (serverlist->slsockets[i].s != INVALID_SOCKET) {
return 0;
}
}
return 1;
}
/* ServerListThink
------------------
For use with Async Updates. This needs to be called every ~10ms for list processing and
updating to occur during async server list updates */
GError ServerListThink(GServerList serverlist)
{
int i;
for(i = 0; i < serverlist->numslsockets; i++) {
ServerListThinkSocket(serverlist, &serverlist->slsockets[i]);
}
switch(serverlist->state)
{
case sl_idle:
case sl_listxfer:
if (!serverlist->abortupdate) {
if (serverlist->startslindex < ServerListGetNumMasters()) {
CreateServerListForAvailableSockets(serverlist, serverlist->async);
} else if (serverlist->state == sl_listxfer && ServerListHasFinishedFetchingList(serverlist)) {
ServerListModeChange(serverlist, sl_idle);
}
}
break;
case sl_querying:
//do some queries
return ServerListQueryLoop(serverlist);
break;
default: break;
}
return 0;
}

View file

@ -23,22 +23,148 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#pragma once
#include "q_gamespy.h"
#include "../qcommon/qcommon.h"
#include "cl_gamespy.h"
#include "sv_gamespy.h"
#define MAX_MASTERS 8
#define MASTER_DEFAULT_MSPORT 28900
#define MASTER_DEFAULT_HBPORT 27900
cvar_t *com_master_host[MAX_MASTERS];
cvar_t *com_master_msport[MAX_MASTERS];
cvar_t *com_master_hbport[MAX_MASTERS];
master_entry_t entries[MAX_MASTERS];
int num_entries = 0;
static void Com_RestartGameSpy_f(void);
static qboolean ShouldRefreshMasters()
{
int i;
for (i = 0; i < MAX_MASTERS; i++) {
if (com_master_host[i] && com_master_host[i]->latchedString) {
return qtrue;
}
if (com_master_msport[i] && com_master_msport[i]->latchedString) {
return qtrue;
}
if (com_master_hbport[i] && com_master_hbport[i]->latchedString) {
return qtrue;
}
}
return qfalse;
}
static void CreateMasterVar(int index, const char *host, int msport, int hbport)
{
assert(index < MAX_MASTERS);
//
// These variables should be modified for testing purposes only.
// So prevent them to be saved in the configuration file.
//
com_master_host[index] = Cvar_Get(va("com_master%d_host", index), host, CVAR_LATCH | CVAR_TEMP);
com_master_msport[index] = Cvar_Get(va("com_master%d_msport", index), va("%d", msport), CVAR_LATCH | CVAR_TEMP);
com_master_hbport[index] = Cvar_Get(va("com_master%d_hbport", index), va("%d", hbport), CVAR_LATCH | CVAR_TEMP);
com_master_host[index]->flags &= ~CVAR_ARCHIVE;
com_master_msport[index]->flags &= ~CVAR_ARCHIVE;
com_master_hbport[index]->flags &= ~CVAR_ARCHIVE;
}
qboolean Com_RefreshGameSpyMasters()
{
int msIndex = 0;
int i;
qboolean shouldRestart;
shouldRestart = ShouldRefreshMasters();
//
// These masters come from the 333networks community and use the same software
// that emulate the GameSpy protocol -- see https://333networks.com/
// They are managed by different entities, are independent and sync with eachother.
//
CreateMasterVar(msIndex++, "master.333networks.com", MASTER_DEFAULT_MSPORT, MASTER_DEFAULT_HBPORT);
CreateMasterVar(msIndex++, "master.errorist.eu", MASTER_DEFAULT_MSPORT, MASTER_DEFAULT_HBPORT);
CreateMasterVar(msIndex++, "master.noccer.de", MASTER_DEFAULT_MSPORT, MASTER_DEFAULT_HBPORT);
CreateMasterVar(msIndex++, "master-au.unrealarchive.org", MASTER_DEFAULT_MSPORT, MASTER_DEFAULT_HBPORT);
CreateMasterVar(msIndex++, "master.frag-net.com", MASTER_DEFAULT_MSPORT, MASTER_DEFAULT_HBPORT);
for (; msIndex < MAX_MASTERS; msIndex++) {
CreateMasterVar(msIndex, "", MASTER_DEFAULT_MSPORT, MASTER_DEFAULT_HBPORT);
}
num_entries = 0;
//
// Find and insert valid entries
//
for (i = 0; i < MAX_MASTER_SERVERS; i++) {
master_entry_t *entry = &entries[num_entries];
if (com_master_host[i]->string && com_master_host[i]->string[0]) {
entry->host = com_master_host[i]->string;
entry->queryport = com_master_msport[i]->integer;
entry->hbport = com_master_hbport[i]->integer;
num_entries++;
}
}
return shouldRestart;
}
void Com_InitGameSpy()
{
Com_RefreshGameSpyMasters();
Cmd_AddCommand("net_gamespy_restart", Com_RestartGameSpy_f);
}
static void Com_RestartGameSpy_f(void)
{
Com_RefreshGameSpyMasters();
#ifndef DEDICATED
CL_RestartGamespy_f();
#endif
SV_RestartGamespy_f();
}
unsigned int Com_GetNumMasterEntries()
{
return num_entries;
}
void Com_GetMasterEntry(int index, master_entry_t *entry)
{
if (index >= num_entries) {
entry->host = NULL;
entry->hbport = 0;
entry->queryport = 0;
return;
}
entry->host = com_master_host[index]->string;
entry->queryport = com_master_msport[index]->integer;
entry->hbport = com_master_hbport[index]->integer;
}
const char *Com_GetMasterHost()
{
return "master.333networks.com";
return com_master_host[0]->string;
}
int Com_GetMasterQueryPort()
{
return 28900;
return com_master_msport[0]->integer;
}
int Com_GetMasterHeartbeatPort()
{
return 27900;
return com_master_hbport[0]->integer;
}

View file

@ -24,8 +24,19 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#pragma once
void Com_InitGameSpy();
#include "../qcommon/q_shared.h"
typedef struct {
const char *host;
int queryport;
int hbport;
} master_entry_t;
void Com_InitGameSpy();
qboolean Com_RefreshGameSpyMasters();
unsigned int Com_GetNumMasterEntries();
void Com_GetMasterEntry(int index, master_entry_t* entry);
const char *Com_GetMasterHost();
int Com_GetMasterQueryPort();
int Com_GetMasterHeartbeatPort();

View file

@ -34,8 +34,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
static char gamemode[128];
static qboolean gcdInitialized = qfalse;
static qboolean gcdValid = qfalse;
static qboolean gsRunning = qfalse;
extern GSIACResult __GSIACResult;
cvar_t *net_ip = NULL;
cvar_t *net_gamespy_port = NULL;
static const char *SECRET_GS_KEYS[] =
{
"M5Fdwc",
@ -302,6 +306,12 @@ void SV_ShutdownGamespy()
return;
}
if (!gsRunning) {
// Added in OPM
// Gamespy is not running
return;
}
strcpy(gamemode, "exiting");
if (gcdInitialized) {
@ -311,12 +321,12 @@ void SV_ShutdownGamespy()
qr_send_statechanged(NULL);
qr_shutdown(NULL);
gsRunning = qfalse;
}
qboolean SV_InitGamespy()
{
cvar_t *net_ip;
cvar_t *net_gamespy_port;
char secret_key[9];
const char *secret_gs_key;
const char *gs_game_name;
@ -378,6 +388,8 @@ qboolean SV_InitGamespy()
gcdInitialized = qtrue;
}
gsRunning = qtrue;
return qtrue;
}
@ -503,12 +515,57 @@ void SV_GamespyAuthorize(netadr_t from, const char *response)
}
}
const char *qr_get_master_host()
void SV_RestartGamespy()
{
return Com_GetMasterHost();
if (gsRunning) {
//
// Reinitialize Gamespy
//
SV_ShutdownGamespy();
SV_InitGamespy();
}
}
int qr_get_master_port()
void SV_RestartGamespy_f()
{
return Com_GetMasterHeartbeatPort();
SV_RestartGamespy();
}
void SV_TryRestartGamespy()
{
if (Com_RefreshGameSpyMasters()) {
SV_RestartGamespy();
return;
}
if (net_gamespy_port && net_gamespy_port->latchedString) {
SV_RestartGamespy();
return;
}
if (sv_gamespy && sv_gamespy->latchedString) {
SV_RestartGamespy();
return;
}
}
unsigned int qr_get_num_masters()
{
return Com_GetNumMasterEntries();
}
const char *qr_get_master_host(int index)
{
master_entry_t entry;
Com_GetMasterEntry(index, &entry);
return entry.host;
}
int qr_get_master_port(int index)
{
master_entry_t entry;
Com_GetMasterEntry(index, &entry);
return entry.hbport;
}

View file

@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#pragma once
#include "q_gamespy.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -37,6 +39,13 @@ extern const char* GS_GetCurrentGameName();
void SV_CreateGamespyChallenge(char* challenge);
void SV_GamespyAuthorize(netadr_t from, const char* response);
void SV_GamespyHeartbeat();
void SV_ProcessGamespyQueries();
qboolean SV_InitGamespy();
void SV_ShutdownGamespy();
void SV_RestartGamespy();
void SV_RestartGamespy_f();
void SV_TryRestartGamespy();
#ifdef __cplusplus
}

View file

@ -163,9 +163,11 @@ struct qr_implementation_s static_rec = {INVALID_SOCKET, INVALID_SOCKET};
static qr_t current_rec = &static_rec;
//char qr_hostname[64] = MASTER_ADDR;
static struct sockaddr_in MasterList[8];
//static struct sockaddr_in MasterList[8];
static struct sockaddr_in *MasterList = NULL;
static char keyvalue[8192];
static int MasterCount;
static int MasterMaxCount;
/********
PROTOTYPES
@ -173,6 +175,7 @@ PROTOTYPES
static void send_heartbeat(qr_t qrec, int statechanged);
static void qr_parse_query(qr_t qrec, char* query, int len, struct sockaddr* sender);
static int do_connect(SOCKET sock, char* addr, int port, struct sockaddr_in* hbaddr);
static int do_connect_multi();
void qr_check_queries(qr_t qrec);
void qr_check_send_heartbeat(qr_t qrec);
@ -270,7 +273,7 @@ int qr_init(qr_t* qrec,
qrec = &current_rec;
}
return do_connect(hbsock, MASTER_ADDR, MASTER_PORT, &(*qrec)->hbaddr);
return do_connect_multi();
}
void init_qrec(qr_t* qrec,
@ -339,7 +342,7 @@ int qr_init_socket(qr_t* qrec,
qr_players_callback,
userdata);
return do_connect(s, MASTER_ADDR, MASTER_PORT, &(*qrec)->hbaddr);
return do_connect_multi();
}
/* qr_process_queries: Processes any waiting queries, and sends a
@ -445,6 +448,14 @@ void qr_shutdown(qr_t qrec)
{
gsifree(qrec);
}
if (MasterList) {
gsifree(MasterList);
MasterList = NULL;
}
MasterCount = 0;
SocketShutDown();
}
@ -469,6 +480,30 @@ static int do_connect(SOCKET sock, char* addr, int port, struct sockaddr_in* hba
return 0;
}
static int do_connect_multi()
{
int i;
MasterMaxCount = qr_get_num_masters();
if (MasterList) {
gsifree(MasterList);
MasterList = NULL;
}
MasterList = gsimalloc(sizeof(struct sockaddr_in) * MasterMaxCount);
MasterCount = 0;
for(i = 0; i < MasterMaxCount; i++) {
struct sockaddr_in hbaddr;
if (get_sockaddrin(qr_get_master_host(i), qr_get_master_port(i), &hbaddr, NULL)) {
// Valid, add it
add_master(&hbaddr);
}
}
return 0;
}
/* Return a sockaddrin for the given host (numeric or DNS) and port)
Returns the hostent in savehent if it is not NULL */
int get_sockaddrin(const char* host, int port, struct sockaddr_in* saddr, struct hostent** savehent)
@ -817,7 +852,7 @@ void add_master(struct sockaddr_in* addr)
return;
}
}
if (i == 8) {
if (i == MasterMaxCount) {
return;
} else {
MasterList[i] = *addr;

View file

@ -65,9 +65,9 @@ as the base port. Generally there is no reason to modify this value.
/********
DEFINES
********/
#define MASTER_PORT qr_get_master_port()
#define MASTER_PORT qr_get_master_port(0)
//#define MASTER_ADDR "master." GSI_DOMAIN_NAME
#define MASTER_ADDR qr_get_master_host()
#define MASTER_ADDR qr_get_master_host(0)
#define FIRST_HB_TIME 30000 /* 30 sec */
#define HB_TIME 300000 /* 5 minutes */
#define MAX_FIRST_COUNT 10 /* 10 tries = 5 minutes */
@ -86,8 +86,9 @@ extern char qr_hostname[64];
*
* @return const char* The full master server address
*/
extern const char *qr_get_master_host();
extern int qr_get_master_port();
extern unsigned int qr_get_num_masters();
extern const char *qr_get_master_host(int index);
extern int qr_get_master_port(int index);
/********
qr_querycallback_t

View file

@ -1996,6 +1996,8 @@ void Com_Init( char *commandLine ) {
RecoverLostAutodialData();
// Added in OPM
// Initialize GameSpy related stuff
Com_InitGameSpy();
iEnd = Sys_Milliseconds();

View file

@ -633,14 +633,6 @@ void SV_NET_UpdateClientNetProfileInfo(netprofclient_t* netprofile, int rate);
void SV_NET_UpdateAllNetProfileInfo();
void SV_NET_CalcTotalNetProfile(netprofclient_t* netprofile, qboolean server);
//
// sv_gamespy.c
//
void SV_GamespyHeartbeat();
void SV_ProcessGamespyQueries();
qboolean SV_InitGamespy();
void SV_ShutdownGamespy();
#ifdef __cplusplus
}
#endif

View file

@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "../client/client.h"
#include "../qcommon/tiki.h"
#include "../qcommon/bg_compat.h"
#include "../gamespy/sv_gamespy.h"
static char last_mapname[ MAX_QPATH ];
static int g_iSvsTimeFixupCount;