cnq3/code/client/cl_browser.cpp

900 lines
21 KiB
C++

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "client.h"
static ping_t cl_pinglist[MAX_PINGREQUESTS];
typedef struct serverStatus_s
{
char string[BIG_INFO_STRING];
netadr_t address;
int time, startTime;
qbool pending;
qbool print;
qbool retrieved;
} serverStatus_t;
static serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
static int serverStatusCount;
static void CL_InitServerInfo( serverInfo_t *server, const serverAddress_t* address )
{
server->adr.type = NA_IP;
server->adr.ip[0] = address->ip[0];
server->adr.ip[1] = address->ip[1];
server->adr.ip[2] = address->ip[2];
server->adr.ip[3] = address->ip[3];
server->adr.port = address->port;
server->clients = 0;
server->hostName[0] = '\0';
server->mapName[0] = '\0';
server->maxClients = 0;
server->maxPing = 0;
server->minPing = 0;
server->ping = -1;
server->game[0] = '\0';
server->gameType = 0;
server->netType = 0;
}
#define MAX_SERVERSPERPACKET 256
void CL_ServersResponsePacket( const netadr_t& from, msg_t *msg )
{
int i, count, max, total;
serverAddress_t addresses[MAX_SERVERSPERPACKET];
int numservers;
byte* buffptr;
byte* buffend;
Com_Printf("CL_ServersResponsePacket\n");
if (cls.numglobalservers == -1) {
// state to detect lack of servers or lack of response
cls.numglobalservers = 0;
cls.numGlobalServerAddresses = 0;
}
if (cls.nummplayerservers == -1) {
cls.nummplayerservers = 0;
}
// parse through server response string
numservers = 0;
buffptr = msg->data;
buffend = buffptr + msg->cursize;
while (buffptr+1 < buffend) {
// advance to initial token
do {
if (*buffptr++ == '\\')
break;
}
while (buffptr < buffend);
if ( buffptr >= buffend - 6 ) {
break;
}
// parse out ip
addresses[numservers].ip[0] = *buffptr++;
addresses[numservers].ip[1] = *buffptr++;
addresses[numservers].ip[2] = *buffptr++;
addresses[numservers].ip[3] = *buffptr++;
// parse out port
addresses[numservers].port = (*buffptr++)<<8;
addresses[numservers].port += *buffptr++;
addresses[numservers].port = BigShort( addresses[numservers].port );
// syntax check
if (*buffptr != '\\') {
break;
}
Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers,
addresses[numservers].ip[0],
addresses[numservers].ip[1],
addresses[numservers].ip[2],
addresses[numservers].ip[3],
addresses[numservers].port );
numservers++;
if (numservers >= MAX_SERVERSPERPACKET) {
break;
}
// parse out EOT
if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') {
break;
}
}
if (cls.masterNum == 0) {
count = cls.numglobalservers;
max = MAX_GLOBAL_SERVERS;
} else {
count = cls.nummplayerservers;
max = MAX_OTHER_SERVERS;
}
for (i = 0; i < numservers && count < max; i++) {
// build net address
serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count];
CL_InitServerInfo( server, &addresses[i] );
// advance to next slot
count++;
}
// if getting the global list
if (cls.masterNum == 0) {
if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) {
// if we couldn't store the servers in the main list anymore
for (; i < numservers && count >= max; i++) {
serverAddress_t *addr;
// just store the addresses in an additional list
addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++];
addr->ip[0] = addresses[i].ip[0];
addr->ip[1] = addresses[i].ip[1];
addr->ip[2] = addresses[i].ip[2];
addr->ip[3] = addresses[i].ip[3];
addr->port = addresses[i].port;
}
}
}
if (cls.masterNum == 0) {
cls.numglobalservers = count;
total = count + cls.numGlobalServerAddresses;
} else {
cls.nummplayerservers = count;
total = count;
}
Com_Printf("%d servers parsed (total %d)\n", numservers, total);
}
void CL_ServerStatusResponse( const netadr_t& from, msg_t *msg )
{
int i;
serverStatus_t* serverStatus = NULL;
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
serverStatus = &cl_serverStatusList[i];
break;
}
}
// if we didn't request this server status
if (!serverStatus) {
return;
}
const char* s = MSG_ReadStringLine( msg );
int len = 0;
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s);
if (serverStatus->print) {
char info[MAX_INFO_STRING];
int l(0);
Com_Printf("Server settings:\n");
// print cvars
while (*s) {
for (i = 0; i < 2 && *s; i++) {
if (*s == '\\')
s++;
l = 0;
while (*s) {
info[l++] = *s;
if (l >= MAX_INFO_STRING-1)
break;
s++;
if (*s == '\\') {
break;
}
}
info[l] = '\0';
if (i) {
Com_Printf("%s\n", info);
}
else {
Com_Printf("%-24s", info);
}
}
}
}
len = strlen(serverStatus->string);
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
if (serverStatus->print) {
Com_Printf("\nPlayers:\n");
Com_Printf("num: score: ping: name:\n");
}
for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) {
len = strlen(serverStatus->string);
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s);
if (serverStatus->print) {
int score(0), ping(0);
sscanf(s, "%d %d", &score, &ping);
s = strchr(s, ' ');
if (s)
s = strchr(s+1, ' ');
if (s)
s++;
else
s = "unknown";
Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s );
}
}
len = strlen(serverStatus->string);
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
serverStatus->time = Com_Milliseconds();
serverStatus->address = from;
serverStatus->pending = qfalse;
if (serverStatus->print) {
serverStatus->retrieved = qtrue;
}
}
static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) {
if (server) {
if (info) {
server->clients = atoi(Info_ValueForKey(info, "clients"));
Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH);
Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH);
server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH);
server->gameType = atoi(Info_ValueForKey(info, "gametype"));
server->netType = atoi(Info_ValueForKey(info, "nettype"));
server->minPing = atoi(Info_ValueForKey(info, "minping"));
server->maxPing = atoi(Info_ValueForKey(info, "maxping"));
server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster"));
}
server->ping = ping;
}
}
static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) {
int i;
for (i = 0; i < MAX_OTHER_SERVERS; i++) {
if (NET_CompareAdr(from, cls.localServers[i].adr)) {
CL_SetServerInfo(&cls.localServers[i], info, ping);
}
}
for (i = 0; i < MAX_OTHER_SERVERS; i++) {
if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) {
CL_SetServerInfo(&cls.mplayerServers[i], info, ping);
}
}
for (i = 0; i < MAX_GLOBAL_SERVERS; i++) {
if (NET_CompareAdr(from, cls.globalServers[i].adr)) {
CL_SetServerInfo(&cls.globalServers[i], info, ping);
}
}
for (i = 0; i < MAX_OTHER_SERVERS; i++) {
if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) {
CL_SetServerInfo(&cls.favoriteServers[i], info, ping);
}
}
}
/*
===================
CL_ServerInfoPacket
===================
*/
void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) {
int i, type;
char info[MAX_INFO_STRING];
char *infoString;
int prot;
infoString = MSG_ReadString( msg );
// if this isn't the correct protocol version, ignore it
prot = atoi( Info_ValueForKey( infoString, "protocol" ) );
if ( prot != PROTOCOL_VERSION ) {
Com_DPrintf( "Different protocol info packet: %s\n", infoString );
return;
}
// iterate servers waiting for ping response
for (i=0; i<MAX_PINGREQUESTS; i++)
{
if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) )
{
// calc ping time
cl_pinglist[i].time = cls.realtime - cl_pinglist[i].start + 1;
Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) );
// save of info
Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) );
// tack on the net type
// NOTE: make sure these types are in sync with the netnames strings in the UI
switch (from.type)
{
case NA_BROADCAST:
case NA_IP:
type = 1;
break;
default:
type = 0;
break;
}
Info_SetValueForKey( cl_pinglist[i].info, "nettype", va("%d", type) );
CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time);
return;
}
}
// if not just sent a local broadcast or pinging local servers
if (cls.pingUpdateSource != AS_LOCAL) {
return;
}
for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) {
// empty slot
if ( cls.localServers[i].adr.port == 0 ) {
break;
}
// avoid duplicate
if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) {
return;
}
}
if ( i == MAX_OTHER_SERVERS ) {
Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" );
return;
}
// add this to the list
cls.numlocalservers = i+1;
cls.localServers[i].adr = from;
cls.localServers[i].clients = 0;
cls.localServers[i].hostName[0] = '\0';
cls.localServers[i].mapName[0] = '\0';
cls.localServers[i].maxClients = 0;
cls.localServers[i].maxPing = 0;
cls.localServers[i].minPing = 0;
cls.localServers[i].ping = -1;
cls.localServers[i].game[0] = '\0';
cls.localServers[i].gameType = 0;
cls.localServers[i].netType = from.type;
cls.localServers[i].punkbuster = 0;
Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING );
if (info[0]) {
if (info[strlen(info)-1] != '\n') {
strncat(info, "\n", sizeof(info));
}
Com_Printf( "%s: %s", NET_AdrToString( from ), info );
}
}
/*
===================
CL_GetServerStatus
===================
*/
serverStatus_t *CL_GetServerStatus( netadr_t from ) {
serverStatus_t *serverStatus;
int i, oldest, oldestTime;
serverStatus = NULL;
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
return &cl_serverStatusList[i];
}
}
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if ( cl_serverStatusList[i].retrieved ) {
return &cl_serverStatusList[i];
}
}
oldest = -1;
oldestTime = 0;
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) {
oldest = i;
oldestTime = cl_serverStatusList[i].startTime;
}
}
if (oldest != -1) {
return &cl_serverStatusList[oldest];
}
serverStatusCount++;
return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)];
}
/*
===================
CL_ServerStatus
===================
*/
int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) {
int i;
netadr_t to;
serverStatus_t *serverStatus;
// if no server address then reset all server status requests
if ( !serverAddress ) {
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
cl_serverStatusList[i].address.port = 0;
cl_serverStatusList[i].retrieved = qtrue;
}
return qfalse;
}
// get the address
if ( !NET_StringToAdr( serverAddress, &to ) ) {
return qfalse;
}
serverStatus = CL_GetServerStatus( to );
// if no server status string then reset the server status request for this address
if ( !serverStatusString ) {
serverStatus->retrieved = qtrue;
return qfalse;
}
// if this server status request has the same address
if ( NET_CompareAdr( to, serverStatus->address) ) {
// if we recieved an response for this server status request
if (!serverStatus->pending) {
Q_strncpyz(serverStatusString, serverStatus->string, maxLen);
serverStatus->retrieved = qtrue;
serverStatus->startTime = 0;
return qtrue;
}
// resend the request regularly
else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) {
serverStatus->print = qfalse;
serverStatus->pending = qtrue;
serverStatus->retrieved = qfalse;
serverStatus->time = 0;
serverStatus->startTime = Com_Milliseconds();
NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
return qfalse;
}
}
// if retrieved
else if ( serverStatus->retrieved ) {
serverStatus->address = to;
serverStatus->print = qfalse;
serverStatus->pending = qtrue;
serverStatus->retrieved = qfalse;
serverStatus->startTime = Com_Milliseconds();
serverStatus->time = 0;
NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
return qfalse;
}
return qfalse;
}
/*
==================
CL_LocalServers_f
==================
*/
void CL_LocalServers_f( void ) {
char *message;
int i, j;
netadr_t to;
Com_Printf( "Scanning for servers on the local network...\n");
// reset the list, waiting for response
cls.numlocalservers = 0;
cls.pingUpdateSource = AS_LOCAL;
for (i = 0; i < MAX_OTHER_SERVERS; i++) {
qbool b = cls.localServers[i].visible;
Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i]));
cls.localServers[i].visible = b;
}
Com_Memset( &to, 0, sizeof( to ) );
// The 'xxx' in the message is a challenge that will be echoed back
// by the server. We don't care about that here, but master servers
// can use that to prevent spoofed server responses from invalid ip
message = "\377\377\377\377getinfo xxx";
// send each message twice in case one is dropped
for ( i = 0 ; i < 2 ; i++ ) {
// send a broadcast packet on each server port
// we support multiple server ports so a single machine
// can nicely run multiple servers
for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) {
to.port = BigShort( (short)(PORT_SERVER + j) );
to.type = NA_BROADCAST;
NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
}
}
}
/*
==================
CL_GlobalServers_f
==================
*/
void CL_GlobalServers_f( void ) {
netadr_t to;
int i;
int count;
char *buffptr;
char command[1024];
if ( Cmd_Argc() < 3) {
Com_Printf( "usage: globalservers <master# 0-1> <protocol> [keywords]\n");
return;
}
cls.masterNum = atoi( Cmd_Argv(1) );
Com_Printf( "Requesting servers from the master...\n");
// reset the list, waiting for response
// -1 is used to distinguish a "no response"
if( cls.masterNum == 1 ) {
NET_StringToAdr( MASTER_SERVER_NAME, &to );
cls.nummplayerservers = -1;
cls.pingUpdateSource = AS_MPLAYER;
}
else {
NET_StringToAdr( MASTER_SERVER_NAME, &to );
cls.numglobalservers = -1;
cls.pingUpdateSource = AS_GLOBAL;
}
to.type = NA_IP;
to.port = BigShort(PORT_MASTER);
sprintf( command, "getservers %s", Cmd_Argv(2) );
// tack on keywords
buffptr = command + strlen( command );
count = Cmd_Argc();
for (i=3; i<count; i++)
buffptr += sprintf( buffptr, " %s", Cmd_Argv(i) );
NET_OutOfBandPrint( NS_SERVER, to, command );
}
/*
==================
CL_GetPing
==================
*/
void CL_GetPing( int n, char *buf, int buflen, int *pingtime )
{
const char *str;
int time;
int maxPing;
if (!cl_pinglist[n].adr.port)
{
// empty slot
buf[0] = '\0';
*pingtime = 0;
return;
}
str = NET_AdrToString( cl_pinglist[n].adr );
Q_strncpyz( buf, str, buflen );
time = cl_pinglist[n].time;
if (!time)
{
// check for timeout
time = cls.realtime - cl_pinglist[n].start;
maxPing = Cvar_VariableIntegerValue( "cl_maxPing" );
if (time < maxPing)
{
// not timed out yet
time = 0;
}
}
CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time);
*pingtime = time;
}
/*
==================
CL_GetPingInfo
==================
*/
void CL_GetPingInfo( int n, char *buf, int buflen )
{
if (!cl_pinglist[n].adr.port)
{
// empty slot
if (buflen)
buf[0] = '\0';
return;
}
Q_strncpyz( buf, cl_pinglist[n].info, buflen );
}
/*
==================
CL_ClearPing
==================
*/
void CL_ClearPing( int n )
{
if (n < 0 || n >= MAX_PINGREQUESTS)
return;
cl_pinglist[n].adr.port = 0;
}
int CL_GetPingQueueCount()
{
int count = 0;
const ping_t* pingptr = cl_pinglist;
for ( int i = 0; i < MAX_PINGREQUESTS; ++i, ++pingptr ) {
if (pingptr->adr.port) {
++count;
}
}
return count;
}
static ping_t* CL_GetFreePing()
{
ping_t* pingptr;
ping_t* best;
int oldest;
int i;
int time;
pingptr = cl_pinglist;
for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ )
{
// find free ping slot
if (pingptr->adr.port)
{
if (!pingptr->time)
{
if (cls.realtime - pingptr->start < 500)
{
// still waiting for response
continue;
}
}
else if (pingptr->time < 500)
{
// results have not been queried
continue;
}
}
// clear it
pingptr->adr.port = 0;
return (pingptr);
}
// use oldest entry
pingptr = cl_pinglist;
best = cl_pinglist;
oldest = INT_MIN;
for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ )
{
// scan for oldest
time = cls.realtime - pingptr->start;
if (time > oldest)
{
oldest = time;
best = pingptr;
}
}
return (best);
}
/*
==================
CL_UpdateVisiblePings_f
==================
*/
qbool CL_UpdateVisiblePings_f(int source) {
int slots, i;
char buff[MAX_STRING_CHARS];
int pingTime;
int max;
qbool status = qfalse;
if (source < 0 || source > AS_FAVORITES) {
return qfalse;
}
cls.pingUpdateSource = source;
slots = CL_GetPingQueueCount();
if (slots < MAX_PINGREQUESTS) {
serverInfo_t *server = NULL;
max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS;
switch (source) {
case AS_LOCAL :
server = &cls.localServers[0];
max = cls.numlocalservers;
break;
case AS_MPLAYER :
server = &cls.mplayerServers[0];
max = cls.nummplayerservers;
break;
case AS_GLOBAL :
server = &cls.globalServers[0];
max = cls.numglobalservers;
break;
case AS_FAVORITES :
server = &cls.favoriteServers[0];
max = cls.numfavoriteservers;
break;
}
for (i = 0; i < max; i++) {
if (server[i].visible) {
if (server[i].ping == -1) {
int j;
if (slots >= MAX_PINGREQUESTS) {
break;
}
for (j = 0; j < MAX_PINGREQUESTS; j++) {
if (!cl_pinglist[j].adr.port) {
continue;
}
if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) {
// already on the list
break;
}
}
if (j >= MAX_PINGREQUESTS) {
status = qtrue;
for (j = 0; j < MAX_PINGREQUESTS; j++) {
if (!cl_pinglist[j].adr.port) {
break;
}
}
memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t));
cl_pinglist[j].start = cls.realtime;
cl_pinglist[j].time = 0;
NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" );
slots++;
}
}
// if the server has a ping higher than cl_maxPing or
// the ping packet got lost
else if (server[i].ping == 0) {
// if we are updating global servers
if (source == AS_GLOBAL) {
//
if ( cls.numGlobalServerAddresses > 0 ) {
// overwrite this server with one from the additional global servers
cls.numGlobalServerAddresses--;
CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]);
// NOTE: the server[i].visible flag stays untouched
}
}
}
}
}
}
if (slots) {
status = qtrue;
}
for (i = 0; i < MAX_PINGREQUESTS; i++) {
if (!cl_pinglist[i].adr.port) {
continue;
}
CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime );
if (pingTime != 0) {
CL_ClearPing(i);
status = qtrue;
}
}
return status;
}
void CL_Ping_f()
{
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: ping [server]\n");
return;
}
netadr_t to;
const char* server = Cmd_Argv(1);
if ( !NET_StringToAdr( server, &to ) )
return;
ping_t* pingptr = CL_GetFreePing();
memcpy( &pingptr->adr, &to, sizeof (netadr_t) );
pingptr->start = cls.realtime;
pingptr->time = 0;
CL_SetServerInfoByAddress(pingptr->adr, NULL, 0);
NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" );
}
void CL_ServerStatus_f()
{
const char* server;
if ( Cmd_Argc() != 2 ) {
if ( cls.state != CA_ACTIVE || clc.demoplaying ) {
Com_Printf ("Not connected to a server.\n");
Com_Printf( "Usage: serverstatus [server]\n");
return;
}
server = cls.servername;
}
else {
server = Cmd_Argv(1);
}
netadr_t to;
if ( !NET_StringToAdr( server, &to ) )
return;
NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
serverStatus_t* serverStatus = CL_GetServerStatus( to );
serverStatus->address = to;
serverStatus->print = qtrue;
serverStatus->pending = qtrue;
}