/*
Copyright (C) 1997-2001 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "server.h"

netadr_t	master_adr[MAX_MASTERS];	// address of group servers

mempool_t	*sv_mempool;

client_t	*sv_client;			// current client

cvar_t	*sv_enforcetime;

cvar_t	*sv_timeout;				// seconds without any message
cvar_t	*sv_zombietime;			// seconds to sink messages after disconnect

cvar_t	*rcon_password;			// password for remote server commands

cvar_t	*sv_uploads;
cvar_t	*sv_uploads_from_server;
cvar_t	*sv_uploads_baseurl;

cvar_t	*sv_noreload;			// don't reload level state when reentering

cvar_t	*sv_maxclients;
cvar_t	*sv_showclamp;

cvar_t	*sv_hostname;
cvar_t	*sv_public;			// should heartbeats be sent

cvar_t	*sv_reconnectlimit;	// minimum seconds between connect messages

// wsw : jal

cvar_t	*sv_maxrate;
cvar_t	*sv_masterservers;
cvar_t	*sv_skilllevel;

// wsw : debug netcode
cvar_t	*sv_debug_serverCmd;

#ifdef BATTLEYE
cvar_t	*sv_battleye;
#endif
//============================================================================


/*
=====================
SV_DropClient

Called when the player is totally leaving the server, either willingly
or unwillingly.  This is NOT called if the entire server is quiting
or crashing.
=====================
*/
void SV_DropClient( client_t *drop, int type, char *fmt, ... )
{
	va_list		argptr;
	char		string[1024];
	
	va_start( argptr, fmt );
	vsnprintf( string, sizeof(string), fmt, argptr );
	va_end( argptr );

	if( dedicated->integer )
		Com_Printf( "SV_DropClient: \"%s\"\n", string );

	// add the disconnect
	if( drop->edict && (drop->edict->r.svflags & SVF_FAKECLIENT) ) {
		//if( drop->state == CS_SPAWNED )
			ge->ClientDisconnect( drop->edict );

		// reset the reliable commands chain
		drop->clientCommandExecuted = 0;
		drop->reliableAcknowledge = 0;
		drop->reliableSequence = 0;
		drop->reliableSent = 0;
		MSG_Init( &drop->soundsmsg, drop->soundsmsgData, sizeof(drop->soundsmsgData) );
		drop->soundsmsg.allowoverflow = qtrue;
		drop->lastPacketReceivedTime = svs.realtime;
		drop->lastconnect = svs.realtime;
#ifdef BATTLEYE
		memset( &drop->BE, 0, sizeof(drop->BE) );
#endif
	}
	else {
		MSG_Clear( &tmpMessage );
		SV_SendServerCommand( drop, "disconnect %i \"%s\"", type, string );
		SV_AddReliableCommandsToMessage( drop, &tmpMessage );
		
		SV_SendMessageToClient( drop, &tmpMessage );
		Netchan_PushAllFragments( &drop->netchan );

		if( drop->state >= CS_CONNECTED )
		{
			// call the prog function for removing a client
			// this will remove the body, among other things
			ge->ClientDisconnect( drop->edict );
#ifdef BATTLEYE
			if (sv_battleye->integer && drop->battleye)
			{
				// inform BE Master about removed player
				qbyte removeplayer_packet[] = { 1 /*packet type*/, drop - svs.clients /*player id*/ };
				SV_BE_SendToMaster(removeplayer_packet, sizeof(removeplayer_packet));
			}
#endif
		}
	}

	if( drop->download )
	{
		FS_FreeFile( drop->download );
		drop->download = NULL;
		drop->downloadname[0] = 0;
		drop->downloadsize = 0;
	}
	
	drop->state = CS_ZOMBIE;		// become free in a few seconds
	drop->name[0] = 0;
}



/*
==============================================================================

CONNECTIONLESS COMMANDS

==============================================================================
*/

/*
===============
SV_StatusString

Builds the string that is sent as heartbeats and status replies
===============
*/
char *SV_StatusString( void )
{
	char		tempstr[1024];
	static char	status[MAX_MSGLEN - 16];
	int			i, bots, count;
	client_t	*cl;
	size_t		statusLength;
	size_t		tempstrLength;

	Q_strncpyz( status, Cvar_Serverinfo(), sizeof(status) );
	statusLength = strlen(status);

	bots = 0;
	count = 0;
	for( i = 0; i < sv_maxclients->integer; i++ )
	{
		cl = &svs.clients[i];
		if( cl->state >= CS_CONNECTED ) {
			if( cl->edict->r.svflags & SVF_FAKECLIENT )
				bots++;
			count++;
		}
	}

	if( bots )
		Q_snprintfz( tempstr, sizeof(tempstr), "\\bots\\%i", bots );
	Q_snprintfz( tempstr, sizeof(tempstr), "\\clients\\%i\n", count );
	tempstrLength = strlen(tempstr);
	if( statusLength + tempstrLength >= sizeof(status) )
		return status;		// can't hold any more
	Q_strncpyz( status + statusLength, tempstr, sizeof(status) - statusLength );
	statusLength += tempstrLength;

	for( i = 0; i < sv_maxclients->integer; i++ )
	{
		cl = &svs.clients[i];
		if( cl->state == CS_CONNECTED || cl->state == CS_SPAWNED )
		{
			Q_snprintfz( tempstr, sizeof(tempstr), "%i %i \"%s\" %i\n", 
				cl->edict->r.client->r.frags, cl->ping, cl->name, cl->edict->s.team );
			tempstrLength = strlen(tempstr);
			if( statusLength + tempstrLength >= sizeof(status) )
				break;		// can't hold any more
			Q_strncpyz( status + statusLength, tempstr, sizeof(status) - statusLength );
			statusLength += tempstrLength;
		}
	}

	return status;
}

/*
================
SVC_Ack

================
*/
void SVC_Ack( void )
{
	Com_Printf( "Ping acknowledge from %s\n", NET_AdrToString(&net_from) );
}

/*
================
SVC_Info

Responds with short info for broadcast scans
The second parameter should be the current protocol version number.
================
*/
#define MAX_STRING_SVCINFOSTRING 160
#define MAX_SVCINFOSTRING_LEN (MAX_STRING_SVCINFOSTRING - 4)
void SVC_Info( void )
{
	char	string[MAX_STRING_SVCINFOSTRING];
	char	hostname[64];
	char	entry[16];
	size_t	len;
	int		i, count, bots;
	int		version;
	qboolean allow_empty=qfalse, allow_full=qfalse;

	if( sv_maxclients->integer == 1 )
		return;		// ignore in single player

	version = atoi (Cmd_Argv(1));

	if( version != PROTOCOL_VERSION ) {
		Q_snprintfz( string, sizeof(string), "%s: wrong version\n", sv_hostname->string, sizeof(string) );
		return;
	}

	bots = 0;
	count = 0;
	for( i = 0; i < sv_maxclients->integer; i++ ) {
		if( svs.clients[i].state >= CS_CONNECTED ) {
			if( svs.clients[i].edict->r.svflags & SVF_FAKECLIENT )
				bots++;
			count++;
		}
	}

	for( i = 0; i < Cmd_Argc(); i++ ) {
		if( !Q_stricmp( Cmd_Argv(i), "full" ) )
			allow_full = qtrue;
		
		if( !Q_stricmp( Cmd_Argv(i), "empty" ) )
			allow_empty = qtrue;
	}

	if( (count == sv_maxclients->integer) && !allow_full ) {
		return;
	}

	if( (count == 0) && !allow_empty ) {
		return;
	}

	//format:
	//" \377\377\377\377info\\n\\server_name\\m\\map name\\u\\clients/maxclients\\g\\gametype\\s\\skill\\EOT "

	if( sv_skilllevel->integer > 2 )
		Cvar_ForceSet( "sv_skilllevel", "2" );
	if( sv_skilllevel->integer < 0 )
		Cvar_ForceSet( "sv_skilllevel", "0" );

	Q_strncpyz( hostname, sv_hostname->string, sizeof(hostname) );
	Q_snprintfz( string, sizeof(string), 
		"\\\\n\\\\%s\\\\m\\\\%8s\\\\u\\\\%2i/%2i\\\\",
		hostname, 
		sv.name, 
		count > 99 ? 99 : count, 
		sv_maxclients->integer > 99 ? 99 : sv_maxclients->integer
		);

	len = strlen(string);
	*entry = 0;
	Q_snprintfz( entry, sizeof(entry), "g\\\\%5s\\\\", Cvar_VariableString( "g_gametype" ) );
	if( MAX_SVCINFOSTRING_LEN - len > strlen(entry) ) {
		Q_strncatz( string, entry, sizeof(string) );
		len = strlen(string);
	}

	*entry = 0;
	Q_snprintfz( entry, sizeof(entry), "s\\\\%1d\\\\", sv_skilllevel->integer );
	if( MAX_SVCINFOSTRING_LEN - len > strlen(entry) ) {
		Q_strncatz( string, entry, sizeof(string) );
		len = strlen(string);
	}

	if( (strlen(Cvar_VariableString( "password" )) > 0) ) {
		*entry = 0;
		Q_snprintfz( entry, sizeof(entry), "p\\\\%i\\\\", (strlen(Cvar_VariableString( "password" )) > 0) );
		if( MAX_SVCINFOSTRING_LEN - len > strlen(entry) ) {
			Q_strncatz( string, entry, sizeof(string) );
			len = strlen(string);
		}
	}

	if( bots ) {
		*entry = 0;
		Q_snprintfz( entry, sizeof(entry), "b\\\\%2i\\\\", bots > 99 ? 99 : bots );
		if( MAX_SVCINFOSTRING_LEN - len > strlen(entry) ) {
			Q_strncatz( string, entry, sizeof(string) );
			len = strlen(string);
		}
	}

#ifdef BATTLEYE
	if( sv_battleye->integer ) {
		*entry = 0;
		Q_snprintfz( entry, sizeof(entry), "be\\\\%i\\\\", sv_battleye->integer );
		if( MAX_SVCINFOSTRING_LEN - len > strlen(entry) ) {
			Q_strncatz( string, entry, sizeof(string) );
			len = strlen(string);
		}
	}
#endif

	// finish it
	Q_strncatz( string, "EOT", sizeof(string) );

	Netchan_OutOfBandPrint( NS_SERVER, net_from, "info\n%s", string );
}

/*
================
SVC_Ping

Just responds with an acknowledgement
================
*/
void SVC_Ping( void )
{
	Netchan_OutOfBandPrint( NS_SERVER, net_from, "ack" );
}


/*
=================
SVC_GetChallenge

Returns a challenge number that can be used
in a subsequent client_connect command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs.  With a
challenge, they must give a valid IP address.
=================
*/
void SVC_GetChallenge( void )
{
	int		i;
	int		oldest;
	int		oldestTime;

	oldest = 0;
	oldestTime = 0x7fffffff;

	// see if we already have a challenge for this ip
	for( i = 0 ; i < MAX_CHALLENGES ; i++ )
	{
		if( NET_CompareBaseAdr (&net_from, &svs.challenges[i].adr) )
			break;
		if( svs.challenges[i].time < oldestTime )
		{
			oldestTime = svs.challenges[i].time;
			oldest = i;
		}
	}

	if( i == MAX_CHALLENGES )
	{
		// overwrite the oldest
		svs.challenges[oldest].challenge = rand() & 0x7fff;
		svs.challenges[oldest].adr = net_from;
		svs.challenges[oldest].time = curtime;
		i = oldest;
	}

	// send it back
	Netchan_OutOfBandPrint( NS_SERVER, net_from, "challenge %i", svs.challenges[i].challenge );
}

/*
==================
SVC_ClientConnect
==================
*/
qboolean SVC_ClientConnect( client_t *cl, char *userinfo, int challenge, qboolean fakeClient )
{
	edict_t		*ent;
	int			edictnum;

	// build a new connection
	// accept the new client
	// this is the only place a client_t is ever initialized
	memset( cl, 0, sizeof( *cl ) );
	sv_client = cl;
	edictnum = (cl - svs.clients) + 1;
	ent = EDICT_NUM(edictnum);
	cl->edict = ent;
	cl->challenge = challenge; // save challenge for checksumming

	if( !ge->ClientConnect( ent, userinfo, fakeClient ) )
		return qfalse;

#ifdef BATTLEYE
	if( sv_battleye->integer )
	{
		cl->battleye = (atoi(Info_ValueForKey( userinfo, "cl_battleye" )) != 0);
		if( sv_battleye->integer == 2 && !cl->battleye ) {
			Info_SetValueForKey( userinfo, "rejtype", va("%i", DROP_TYPE_GENERAL) );
			Info_SetValueForKey( userinfo, "rejflag", va("%i", 0) );
			Info_SetValueForKey( userinfo, "rejmsg", "BattlEye is required in this server" );
			Com_DPrintf( "Rejected a non-BattlEye client.\n" );
			return qfalse;
		}

		memset( &cl->BE, 0, sizeof(cl->BE) );

		if( cl->battleye ) {
			// inform BE Master about new player
			qbyte addplayer_packet[] = { 1 /*packet type*/, cl - svs.clients /*player id*/ };
			SV_BE_SendToMaster(addplayer_packet, sizeof(addplayer_packet));
		}
	}
#endif

	// get the game a chance to reject this connection or modify the userinfo
	return qtrue;
}

/*
==================
SVC_DirectConnect

A connection request that did not come from the master
==================
*/
void SVC_DirectConnect( void )
{
	int			i;
	char		userinfo[MAX_INFO_STRING];
	netadr_t	adr;
	client_t	*cl, *newcl;
	int			version;
	int			qport;
	int			challenge;

	adr = net_from;

	Com_DPrintf( "SVC_DirectConnect ()\n" );

	version = atoi( Cmd_Argv(1) );
	if( version != PROTOCOL_VERSION )
	{
		if( version <= 6 ) { // before reject packet was added
			Netchan_OutOfBandPrint( NS_SERVER, adr, "print\nServer is version %4.2f. Protocol %3i\n",
					VERSION, PROTOCOL_VERSION );
		} else {
			Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nServer and client don't have the same version\n",
					DROP_TYPE_GENERAL, 0 );
		}
		Com_DPrintf( "    rejected connect from protocol %i\n", version );
		return;
	}

	qport = atoi( Cmd_Argv(2) );

	challenge = atoi( Cmd_Argv(3) );

	// wsw : jal : check size of userinfo + ip before adding it
	if( strlen(Cmd_Argv(4)) + strlen( NET_AdrToString(&net_from) ) >= MAX_INFO_STRING )
	{
		Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nUserinfo string too large\n", DROP_TYPE_GENERAL );
		Com_Printf( "ClientConnect: userinfo string exceeded max valid size. Connection refused.\n" );
		return;
	}

	Q_strncpyz( userinfo, Cmd_Argv(4), sizeof(userinfo) );

	// wsw : jal : check for empty userinfo
	if( !(*userinfo) ) {
		Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nInvalid userinfo string\n", DROP_TYPE_GENERAL, 0 );
		Com_Printf( "ClientConnect: Empty userinfo string. Connection refused.\n" );
		return;
	}

	// force the IP key/value pair so the game can filter based on ip
	Info_SetValueForKey( userinfo, "ip", NET_AdrToString(&net_from) );

	// see if the challenge is valid
	// wsw: mdr: removing this fixed the loopback stuff
	// probably because first client_connect is accepted by server, but client still sends another
	// and that's seen as a reconnect by server
	//if( !NET_IsLocalAddress(&adr) )
	//{
		for( i = 0; i < MAX_CHALLENGES; i++ )
		{
			if( NET_CompareBaseAdr( &net_from, &svs.challenges[i].adr ) )
			{
				if( challenge == svs.challenges[i].challenge ) {
					svs.challenges[i].challenge = 0; // wsw : r1q2 : reset challenge
					break;		// good
				}
				Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nBad challenge\n",
						DROP_TYPE_GENERAL, DROP_FLAG_AUTORECONNECT );
				return;
			}
		}
		if( i == MAX_CHALLENGES )
		{
			Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nNo challenge for address\n",
					DROP_TYPE_GENERAL, DROP_FLAG_AUTORECONNECT );
			return;
		}
	//}

	// if there is already a slot for this ip, reuse it
	for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
	{
		if( cl->state == CS_FREE )
			continue;
		if( NET_CompareBaseAdr(&adr, &cl->netchan.remoteAddress)
			&& ( cl->netchan.qport == qport 
			|| adr.port == cl->netchan.remoteAddress.port ) )
		{
			if( !NET_IsLocalAddress(&adr) && (svs.realtime - cl->lastconnect) < (unsigned)(sv_reconnectlimit->integer * 1000) )
			{
				Com_DPrintf( "%s:reconnect rejected : too soon\n", NET_AdrToString(&adr) );
				return;
			}
			Com_Printf( "%s:reconnect\n", NET_AdrToString(&adr) );
			newcl = cl;
			goto gotnewcl;
		}
	}

	// find a client slot
	newcl = NULL;
	for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
	{
		if( cl->state == CS_FREE )
		{
			newcl = cl;
			break;
		}
	}
	if( !newcl )
	{
		Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nServer is full\n",
				DROP_TYPE_GENERAL, DROP_FLAG_AUTORECONNECT );
		Com_DPrintf( "Server is full. Rejected a connection.\n" );
		return;
	}

gotnewcl:

	// wsw : r1q2[start] : check for end-of-message-in-string exploit
	if( strchr(userinfo, '\xFF') )
	{
		Com_Printf( "wsw : r1q2[start] : check for end-of-message-in-string exploit. Connection refused.\n" );
		Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%i\n%i\nInvalid userinfo string\n", DROP_TYPE_GENERAL, 0 );
		return;
	}
	// wsw : r1q2[end]

	// get the game a chance to reject this connection or modify the userinfo
	if( !SVC_ClientConnect(newcl, userinfo, challenge, qfalse) )
	{
		char	*rejtypeflag, *rejmsg;

		// hax because Info_ValueForKey can only be called twice in a row
		rejtypeflag = va("%s\n%s", Info_ValueForKey(userinfo, "rejtype"), Info_ValueForKey(userinfo, "rejflag"));
		rejmsg = Info_ValueForKey(userinfo, "rejmsg");

		Netchan_OutOfBandPrint( NS_SERVER, adr, "reject\n%s\n%s\n", rejtypeflag, rejmsg );

		Com_DPrintf( "Game rejected a connection.\n");
		return;
	}

	// parse some info from the info strings
	Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) );
	SV_UserinfoChanged( newcl );

	// send the connect packet to the client
	Netchan_OutOfBandPrint( NS_SERVER, adr, "client_connect" );

	Netchan_Setup( NS_SERVER, &newcl->netchan, adr, qport );

	newcl->state = CS_CONNECTED;

	newcl->clientCommandExecuted = 0;
	newcl->reliableAcknowledge = 0;
	newcl->reliableSequence = 0;
	newcl->reliableSent = 0;
	memset( &newcl->reliableCommands, 0, sizeof(newcl->reliableCommands) );
#ifdef BATTLEYE
	memset( &newcl->BE, 0, sizeof(newcl->BE) );
#endif

	MSG_Init( &newcl->soundsmsg, newcl->soundsmsgData, sizeof(newcl->soundsmsgData) );
	newcl->soundsmsg.allowoverflow = qtrue;

	newcl->lastPacketReceivedTime = svs.realtime;	// don't timeout
	newcl->lastconnect = svs.realtime;
}

/*
==================
SVC_FakeConnect

A connection request that came from the game module
==================
*/
void SVC_FakeConnect( char *fakeUserinfo, char *fakeIP )
{
	int			i;
	char		userinfo[MAX_INFO_STRING];
	client_t	*cl, *newcl;

	Com_DPrintf( "SVC_FakeConnect ()\n" );

	if( !fakeUserinfo )
		fakeUserinfo = "";
	if( !fakeIP )
		fakeIP = "127.0.0.1";

	Q_strncpyz( userinfo, fakeUserinfo, sizeof(userinfo) );

	// force the IP key/value pair so the game can filter based on ip
	Info_SetValueForKey( userinfo, "ip", fakeIP );
	
	// find a client slot
	newcl = NULL;
	for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
	{
		if( cl->state == CS_FREE )
		{
			newcl = cl;
			break;
		}
	}
	if( !newcl )
	{
		Com_DPrintf( "Rejected a connection.\n" );
		return;
	}

	// get the game a chance to reject this connection or modify the userinfo
	if( !SVC_ClientConnect(newcl, userinfo, -1, qtrue) )
	{
		Com_DPrintf( "Game rejected a connection.\n" );
		return;
	}

	// parse some info from the info strings
	Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) );
	SV_UserinfoChanged( newcl );

	newcl->state = CS_SPAWNED;
	newcl->lastPacketReceivedTime = svs.realtime;	// don't timeout
	newcl->lastconnect = svs.realtime;
	// wsw : jal : fakeclients can't transmit
	newcl->netchan.remoteAddress.type = NA_NOTRANSMIT;

	// call the game begin function
	ge->ClientBegin( newcl->edict );
}

/*
===============
Rcon_Validate
===============
*/
int Rcon_Validate( void )
{
	if( !strlen(rcon_password->string) )
		return 0;

	if( strcmp( Cmd_Argv(1), rcon_password->string ) )
		return 0;

	return 1;
}

/*
===============
SVC_RemoteCommand

A client issued an rcon command.
Shift down the remaining args
Redirect all printfs
===============
*/
void SVC_RemoteCommand( msg_t *msg )
{
	int		i;
	char	remaining[1024];

	i = Rcon_Validate();

	if( !msg || !msg->data || msg->cursize < 5 ) {
		Com_Printf( "Bad rcon from %s:\n", NET_AdrToString (&net_from) );
	}

	if( i == 0 )
		Com_Printf( "Bad rcon from %s:\n%s\n", NET_AdrToString (&net_from), msg->data+4 );
	else
		Com_Printf( "Rcon from %s:\n%s\n", NET_AdrToString (&net_from), msg->data+4 );

	Com_BeginRedirect( RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect );

	if( !Rcon_Validate() )
	{
		Com_Printf( "Bad rcon_password.\n" );
	}
	else
	{
		remaining[0] = 0;

		for( i = 2; i < Cmd_Argc(); i++ )
		{
			Q_strncatz( remaining, Cmd_Argv(i), sizeof(remaining) );
			Q_strncatz( remaining, " ", sizeof(remaining) );
		}

		Cmd_ExecuteString( remaining );
	}

	Com_EndRedirect();
}

/*
=================
SV_ConnectionlessPacket

A connectionless packet has four leading 0xff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*/
void SV_ConnectionlessPacket( msg_t *msg )
{
	char	*s;
	char	*c;

	MSG_BeginReading( msg );
	MSG_ReadLong( msg );		// skip the -1 marker

	s = MSG_ReadStringLine( msg );

	Cmd_TokenizeString( s, qfalse );

	c = Cmd_Argv(0);
	Com_DPrintf( "Packet %s : %s\n", NET_AdrToString(&net_from), c );

	if( !strcmp(c, "ping") )
		SVC_Ping();
	else if( !strcmp(c, "ack") )
		SVC_Ack();
	else if( !strcmp(c, "info") )
		SVC_Info();
	else if( !strcmp(c, "getinfo") )
		SVC_MasterInfoResponse();
	else if( !strcmp(c, "getchallenge") )
		SVC_GetChallenge();
	else if( !strcmp(c, "connect") )
		SVC_DirectConnect();
	else if( !strcmp(c, "rcon") )
		SVC_RemoteCommand( msg );
	else
		Com_Printf( "bad connectionless packet from %s:\n%s\n"
		, NET_AdrToString(&net_from), s );
}


//============================================================================

/*
===================
SV_CalcPings

Updates the cl->ping variables
===================
*/
void SV_CalcPings( void )
{
	int			i;
	client_t	*cl;
	int			total, count, j;

	for( i = 0 ; i < sv_maxclients->integer; i++ )
	{
		cl = &svs.clients[i];
		if( cl->state != CS_SPAWNED )
			continue;
		total = 0;
		count = 0;
		for( j = 0; j<LATENCY_COUNTS; j++ )
		{
			if( cl->frame_latency[j] > 0 )
			{
				count++;
				total += cl->frame_latency[j];
			}
		}
		if( !count )
			cl->ping = 0;
		else
#if 0
			cl->ping = total*100/count - 100;
#else
			cl->ping = total / count;
#endif
		// let the game dll know about the ping
		cl->edict->r.client->r.ping = cl->ping;
	}
}


/*
===================
SV_GiveMsec

Every few frames, gives all clients an allotment of milliseconds
for their command moves.  If they exceed it, assume cheating.
===================
*/
void SV_GiveMsec( void )
{
	int			i;
	client_t	*cl;

	if( sv.framenum & 15 )
		return;

	for( i = 0 ; i < sv_maxclients->integer; i++ )
	{
		cl = &svs.clients[i];
		if( cl->state == CS_FREE )
			continue;
		
		cl->commandMsec = 1800;		// 1600 + some slop
	}
}

//=================
// SV_ProcessPacket
//=================
qboolean SV_ProcessPacket( netchan_t *netchan, msg_t *msg ) {
	if( !Netchan_Process( netchan, msg ) )
		return qfalse;		// wasn't accepted for some reason

	{
		int			sequence, sequence_ack;
		int			qport = -1;
		int			zerror;

		// now if compressed, expand it
		MSG_BeginReading( msg );
		sequence = MSG_ReadLong( msg );
		sequence_ack = MSG_ReadLong( msg ); 
		qport = MSG_ReadShort( msg );
		//if( sequence_ack & FRAGMENT_BIT ) // it is compressed
		if( msg->compressed ) {
			zerror = Netchan_DecompressMessage( msg );
			if( zerror < 0 ) { // compression error. Drop the packet
				Com_DPrintf( "SV_ProcessPacket: Compression error %i. Dropping packet\n", zerror );
				return qfalse;
			}
		}
	}

	return qtrue;
}

/*
=================
SV_ReadPackets
=================
*/
void SV_ReadPackets( void )
{
	int			i;
	client_t	*cl;
	int			qport;

	static msg_t		msg;
	static qbyte		msgData[MAX_MSGLEN];

	MSG_Init( &msg, msgData, sizeof(msgData) );
	MSG_Clear( &msg );

	while( NET_GetPacket( NS_SERVER, &net_from, &msg ) )
	{
		// check for connectionless packet (0xffffffff) first
		if( *(int *)msg.data == -1 )
		{
			SV_ConnectionlessPacket( &msg );
			continue;
		}

		// read the qport out of the message so we can fix up
		// stupid address translating routers
		MSG_BeginReading( &msg );
		MSG_ReadLong( &msg );		// sequence number
		MSG_ReadLong( &msg );		// sequence number
		qport = MSG_ReadShort( &msg ) & 0xffff;
		// data follows

		// check for packets from connected clients
		for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
		{
			if( cl->state == CS_FREE )
				continue;
			if( cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT) )
				continue;
			if( !NET_CompareBaseAdr(&net_from, &cl->netchan.remoteAddress) )
				continue;
			if( cl->netchan.qport != qport )
				continue;
			if( cl->netchan.remoteAddress.port != net_from.port )
			{
				Com_Printf( "SV_ReadPackets: fixing up a translated port\n" );
				cl->netchan.remoteAddress.port = net_from.port;
			}

			if( SV_ProcessPacket( &cl->netchan, &msg ) )
			{	// this is a valid, sequenced packet, so process it
				if( cl->state != CS_ZOMBIE )
				{
					cl->lastPacketReceivedTime = svs.realtime;	// don't timeout
					SV_ExecuteClientMessage( cl, &msg );
				}
			}
			break;
		}
		
		if( i != sv_maxclients->integer )
			continue;
	}
}


/*
==================
SV_CheckTimeouts

If a packet has not been received from a client for timeout->value
seconds, drop the conneciton.  Server frames are used instead of
realtime to avoid dropping the local client while debugging.

When a client is normally dropped, the client_t goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*/
void SV_CheckTimeouts( void )
{
	int		i;
	client_t	*cl;

	for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
	{
		// fake clients do not timeout
		if( cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT) )
			cl->lastPacketReceivedTime = svs.realtime;
		// message times may be wrong across a changelevel
		else if( cl->lastPacketReceivedTime > svs.realtime )
			cl->lastPacketReceivedTime = svs.realtime;

		if( cl->state == CS_ZOMBIE &&
			cl->lastPacketReceivedTime + 1000*sv_zombietime->value < svs.realtime )
		{
			cl->state = CS_FREE;	// can now be reused
			continue;
		}

		if( (cl->state != CS_FREE && cl->state != CS_ZOMBIE) &&
			( cl->lastPacketReceivedTime + 1000*sv_timeout->value < svs.realtime ) )
		{
			SV_DropClient( cl, DROP_TYPE_GENERAL, "Connection timed out" );
			cl->state = CS_FREE;	// don't bother with zombie state
			SV_ClientPrintf( NULL, "%s timed out\n", cl->name );
		}

		// timeout downloads left open
		if( (cl->state != CS_FREE && cl->state != CS_ZOMBIE) && (cl->download && cl->downloadtimeout < svs.realtime) )
		{
			Com_Printf( "Download of %s to %s timed out\n", cl->downloadname, cl->name );
			FS_FreeFile( cl->download );
			cl->download = NULL;
			cl->downloadtimeout = 0;
			cl->downloadname[0] = 0;
			cl->downloadsize = 0;
		}
	}
}

/*
================
SV_PrepWorldFrame

This has to be done before the world logic, because
player processing happens outside RunWorldFrame
================
*/
void SV_PrepWorldFrame( void )
{
	edict_t	*ent;
	int		i;

	for( i = 0; i < sv.num_edicts; i++, ent++ )
	{
		ent = EDICT_NUM(i);

		// events only last for a single message
		ent->s.events[0] = ent->s.events[1] = 0;
		ent->s.eventParms[0] = ent->s.eventParms[1] = 0;
	}
}


/*
=================
SV_RunGameFrame
=================
*/
qboolean SV_RunGameFrame( void )
{
	// move autonomous things around if enough time has passed
	if( svs.realtime < sv.time )
	{
		// never let the time get too far off
		if( sv.time - svs.realtime > svc.frametime )
		{
			if( sv_showclamp->integer )
				Com_Printf( "sv lowclamp\n" );
			sv.time = svs.realtime + svc.frametime;
		}

		if( !SV_SendClientsFragments() )
			NET_Sleep( sv.time - svs.realtime );
		return qfalse;
	}

	// update ping based on the last known frame from all clients
	SV_CalcPings();

	if( host_speeds->integer )
		time_before_game = Sys_Milliseconds();

	// we always need to bump framenum, even if we
	// don't run the world, otherwise the delta
	// compression can get confused when a client
	// has the "current" frame
	sv.framenum++;
	sv.time = svs.realtime + svc.frametime;

	ge->RunFrame( svc.frametime );

	if( host_speeds->integer )
		time_after_game = Sys_Milliseconds();

	return qtrue;
}

/*
==================
SV_Frame

==================
*/
void SV_Frame( int msec )
{
	time_before_game = time_after_game = 0;

	// if server is not active, do nothing
	if( !svs.initialized )
		return;

	svs.realtime += msec;

	// check timeouts
	SV_CheckTimeouts();

	// get packets from clients
	SV_ReadPackets();

#ifdef BATTLEYE
	// check for connection to and incoming packets from BattlEye Master Server
	if (sv_battleye->integer)
		SV_BattlEyeFrame(msec);
#endif

	// let everything in the world think and move
	if( SV_RunGameFrame() ) {

		// give the clients some timeslices
		SV_GiveMsec();
#ifdef COLLISION4D
		// backup current frame data for 4Dcollision
		SV_BackUpCollisionFrame();
#endif
		// send messages back to the clients that had packets read this frame
		SV_SendClientMessages();

#ifdef SERVERSIDE_DEMOS
		// save the entire world state if recording a serverdemo
		SV_RecordDemoMessage();
#endif

		// send a heartbeat to the master if needed
		SV_MasterHeartbeat();

		// clear teleport flags, etc for next frame
		SV_PrepWorldFrame();
	}
}

#ifdef BATTLEYE
/*
==================
SV_BattlEyeFrame
==================
*/
void SV_BattlEyeFrame( int msec )
{
	// connectstate: -1 = disconnected, 0 = connecting, 1 = connected
	static int connectstate = -1;
	static int nextconnect_countdown = 0;
	int i;
	client_t* cl;

	if (connectstate == -1)
	{
		if ((nextconnect_countdown -= msec) <= 0)
		{
			int numplayers = 0;

			// only use BE if there are (human) players on the server
			for (i = 0; i < sv_maxclients->integer; i++)
			{
				cl = &svs.clients[i];
				if (cl->state >= CS_CONNECTED && !(cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT)) && cl->battleye)
					numplayers++;
			}
			if (numplayers > 0)
				connectstate = (SV_BE_ConnectToMaster() ? 0 : -1);
		}
		// do not re-set nextconnect_countdown below
		else
			return;
	}
	else if (connectstate == 0)
	{
		connectstate = SV_BE_CheckConnectAttempt();

		if (connectstate == 1)
		{
			char version_packet[20];

			Com_Printf("Connected to BattlEye Master Server\n");

			// send game version info
			version_packet[0] = (char)sprintf(version_packet+1, "%.3f", VERSION) + 1;
			SV_BE_SendToMaster(version_packet, version_packet[0]+1);

			// re-inform BE Master about (human) players on the server (after a disconnect)
			for (i = 0; i < sv_maxclients->integer; i++)
			{
				cl = &svs.clients[i];
				if (cl->state >= CS_CONNECTED && !(cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT)) && cl->battleye)
				{
					qbyte addplayer_packet[] = { 1 /*packet type*/, i /*player id*/ };
					SV_BE_SendToMaster(addplayer_packet, sizeof(addplayer_packet));
				}
			}
		}
		else if (connectstate == -1)
			Com_Printf("Couldn't connect to BattlEye Master Server: %s\n", NET_ErrorString());
	}
	else
	{
		qbyte packetid;
		int result;
		qboolean firstreceived = qfalse;

		// result: -1 = failed or not enough data (!= param len), 0 = no data, 1 = succeeded
		if ((result = SV_BE_ReceiveFromMaster(&packetid, sizeof(packetid))) == 1)
		{
			firstreceived = qtrue;

			// distribute packet to all connected (human) players
			if (packetid == 0)
			{
				short packetlen;
				static qbyte packet[BE_MAX_PACKET_SIZE];

				// receive actual packet
				if ((result = SV_BE_ReceiveFromMaster(&packetlen, sizeof(packetlen))) != 1) goto disconnect;
				if (packetlen <= 0 || packetlen > BE_MAX_PACKET_SIZE) { result = -1; goto disconnect; }
				if ((result = SV_BE_ReceiveFromMaster(packet, packetlen)) != 1) goto disconnect;

				for (i = 0; i < sv_maxclients->integer; i++)
				{
					cl = &svs.clients[i];
					if (cl->state >= CS_CONNECTED && !(cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT)) && cl->battleye)
					{
						// send packet to client
						memcpy(cl->BE.packets[cl->BE.headPacket & BE_UPDATE_MASK], packet, packetlen);
						cl->BE.packetLens[cl->BE.headPacket & BE_UPDATE_MASK] = packetlen;
						cl->BE.headPacket++;
					}
				}
			}
			// violation: display a server message and kick specified player (with that msg)
			else if (packetid == 1)
			{
				qbyte playerid;
				qbyte messagelen;
				static char message[255];

				if ((result = SV_BE_ReceiveFromMaster(&playerid, sizeof(playerid))) != 1) goto disconnect;
				if (playerid >= sv_maxclients->integer) { result = -1; goto disconnect; }
				if ((result = SV_BE_ReceiveFromMaster(&messagelen, sizeof(messagelen))) != 1) goto disconnect;
				if ((result = SV_BE_ReceiveFromMaster(message, messagelen)) != 1) goto disconnect;

				cl = &svs.clients[playerid];
				if( cl->state >= CS_CONNECTED ) {
					SV_ClientPrintf(NULL, "%s^7 was kicked by BattlEye: ^3%s\n", cl->name, message);
					SV_DropClient(cl, DROP_TYPE_NORECONNECT, "BattlEye: %s", message);
				}
			}
		}
disconnect:
		// disconnect if failed, not enough or no at all (after first recv call) returned data
		if (result == -1 || (firstreceived && result == 0))
		{
			SV_BE_DisconnectFromMaster();
			Com_Printf("Disconnected from BattlEye Master Server\n");
			connectstate = -1;
		}
	}
	// when disconnected, try to reconnect every 60 secs
	if (connectstate == -1)
		nextconnect_countdown = 60000;
}
#endif

//============================================================================


/*
====================
SV_AddMaster_f

Add a master server to the list
====================
*/
void SV_AddMaster_f( char *master )
{
	int		i;

	if( !master || !master[0] )
		return;

	if( !sv_public || !sv_public->integer ) {
		Com_Printf( "'SV_AddMaster_f' Only public servers use masters.\n" );
		return;
	}

	//never go public when not acting as a game server
	if( sv.state > ss_game )
		return;

	for( i=0; i<MAX_MASTERS; i++ )
	{
		if( master_adr[i].port )
			continue;

		if( !NET_StringToAdr( master, &master_adr[i]) )
		{
			Com_Printf( "'SV_AddMaster_f' Bad Master server address: %s\n", master );
			return;
		}
		if( master_adr[i].port == 0 )
			master_adr[i].port = BigShort(PORT_MASTER);

		Com_Printf( "Added new master server #%i at %s\n", i, NET_AdrToString(&master_adr[i]) );
		return;
	}

	Com_Printf( "'SV_AddMaster_f' List of master servers is already full\n" );
}


/*
====================
SV_InitMaster
Set up the main master server
====================
*/
void SV_InitMaster( void )
{
	int	i;
	char	*master, *mlist;

	// wsw : jal : initialize masters list
	for( i = 0; i < MAX_MASTERS; i++ )
		memset( &master_adr[i], 0, sizeof(master_adr[i]) );

	//never go public when not acting as a game server
	if( sv.state > ss_game )
		return;

	if( !sv_public || !sv_public->integer )
		return;

	mlist = sv_masterservers->string;
	if( *mlist )
	{
		while( mlist )
		{
			master = COM_Parse( &mlist );
			if( !master || !master[0] )
				break;
			
			SV_AddMaster_f( master );
		}
	}

	svc.last_heartbeat = HEARTBEAT_SECONDS * 1000; // wait a while before sending first heartbeat
}


/*
================
SV_MasterHeartbeat

Send a message to the master every few minutes to
let it know we are alive, and log information
================
*/
void SV_MasterHeartbeat( void )
{
	int			i;

	svc.last_heartbeat -= svc.frametime;
	if( svc.last_heartbeat  > 0 )
		return;

	svc.last_heartbeat = HEARTBEAT_SECONDS * 1000;

	if( !sv_public || !sv_public->integer )
		return;

	// never go public when not acting as a game server
	if( sv.state > ss_game )
		return;

	// send to group master
	for( i = 0; i < MAX_MASTERS; i++ ) {
		if( master_adr[i].port )
		{
			if( dedicated && dedicated->integer )
				Com_Printf( "Sending heartbeat to %s\n", NET_AdrToString(&master_adr[i]) );
			Netchan_OutOfBandPrint( NS_SERVER, master_adr[i], "heartbeat %s\n", APPLICATION );
		}
	}
}

/*
================
SVC_MasterInfoResponse

================
*/
void SVC_MasterInfoResponse( void )
{
	char		*string;

	if( !sv_public || !sv_public->integer )
		return;		// a private dedicated game

	// never go public when not acting as a game server
	if( sv.state > ss_game )
		return;

	// send the same string that we would give for a status OOB command
	string = SV_StatusString();
	Netchan_OutOfBandPrint( NS_SERVER, net_from, "infoResponse\n%s\\challenge\\%s", string, Cmd_Argv(1) );
}

//============================================================================


/*
=================
SV_UserinfoChanged

Pull specific info from a newly changed userinfo string
into a more C friendly form.
=================
*/
void SV_UserinfoChanged( client_t *cl )
{
	char	*val;
	char	*s;
	int		i;

	// wsw : r1q2 : check userinfo string for end-of-message-in-string exploit
	if( strchr (cl->userinfo, '\xFF') )
	{
		SV_DropClient( cl, DROP_TYPE_GENERAL, "Tried to use end-of-message-in-string exploit" );
		return;
	}
	
	// wsw : jal : validate model and skin names ( can't contain "/" )
	// skin
	val = Info_ValueForKey( cl->userinfo, "skin" );
	if( strchr(val, '/') ) {
		Com_Printf( "SV_UserinfoChanged: Fixing invalid skin name %s\n", val );
		s = strchr(val, '/');
		*s = 0;
	}
	if( !strlen(val) )
		val = DEFAULT_PLAYERSKIN;
	
	Info_SetValueForKey( cl->userinfo, "skin", val );
	
	// model
	val = Info_ValueForKey( cl->userinfo, "model" );
	if( strchr(val, '/') ) {
		Com_Printf( "SV_UserinfoChanged: Fixing invalid model name %s\n", val );
		s = strchr(val, '/');
		*s = 0;
	}
	if( !strlen(val) )
		val = DEFAULT_PLAYERMODEL;
	
	Info_SetValueForKey( cl->userinfo, "model", val );
	

	// call prog code to allow overrides
	ge->ClientUserinfoChanged( cl->edict, cl->userinfo );

	// wsw : r1q2[start] : verify, validate, truncate and print name changes
	val = Info_ValueForKey( cl->userinfo, "name" );
	if( !val || !val[0] )
		cl->name[0] = 0;
	else {
		//truncate
		val[sizeof(cl->name)-1] = 0;	//val[15] = 0;

		//print to server console
		if( dedicated->integer && cl->name[0] && strcmp(cl->name, val) ) {
			Com_Printf( "%s[%s] changed name to %s.\n", cl->name, NET_AdrToString(&cl->netchan.remoteAddress), val );
		}

		// name for C code
		Q_strncpyz( cl->name, val, sizeof(cl->name) );
	}
	// wsw : r1q2[end]

	// mask off high bit
	for( i = 0; i < sizeof(cl->name); i++ )
		cl->name[i] &= 127;

	// rate command
	if( Sys_IsLANAddress( cl->netchan.remoteAddress ) && sv_public->integer != 1 ) { 
#ifndef RATEKILLED
		cl->rate = 99999;	// lans should not rate limit
#endif
	} else {
		val = Info_ValueForKey( cl->userinfo, "rate" );
		if( strlen(val) )
		{
			int newrate;

			newrate = atoi(val);
			if( sv_maxrate->integer && newrate > sv_maxrate->integer )
				newrate = sv_maxrate->integer;
			else if( newrate > 90000 )
				newrate = 90000;
			if( newrate < 1000 )
				newrate = 1000;
#ifndef RATEKILLED
			if( cl->rate != newrate ) {
				cl->rate = newrate;
				Com_Printf( "%s%s has rate %i\n", cl->name, S_COLOR_WHITE, cl->rate );
			}
#endif
		}
#ifndef RATEKILLED
		else
			cl->rate = 5000;
#endif
	}
}


//============================================================================

/*
===============
SV_Init

Only called at plat.exe startup, not for each game
===============
*/
void SV_Init( void )
{
	cvar_t		*sv_pps;

	SV_InitOperatorCommands();

	sv_mempool = Mem_AllocPool( NULL, "Server" );

	Cvar_Get( "dmflags", 0, CVAR_SERVERINFO );
	Cvar_Get( "sv_cheats", "0", CVAR_SERVERINFO|CVAR_LATCH );
	Cvar_Get( "protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO|CVAR_NOSET );

	rcon_password =				Cvar_Get( "rcon_password", "", 0 );
	sv_hostname =				Cvar_Get( "sv_hostname", "warsow server", CVAR_SERVERINFO | CVAR_ARCHIVE );
	sv_timeout =				Cvar_Get( "sv_timeout", "125", 0 );
	sv_zombietime =				Cvar_Get( "sv_zombietime", "2", 0 );
	sv_showclamp =				Cvar_Get( "sv_showclamp", "0", 0 );
	sv_enforcetime =			Cvar_Get( "sv_enforcetime", "0", 0 );

	sv_uploads =				Cvar_Get( "sv_uploads", "1", CVAR_ARCHIVE );
	sv_uploads_from_server =	Cvar_Get( "sv_uploads_from_server", "1", CVAR_ARCHIVE );
	sv_uploads_baseurl =		Cvar_Get( "sv_uploads_baseurl", "", CVAR_ARCHIVE );

#ifdef BATTLEYE
	if( dedicated->integer )
		sv_battleye =			Cvar_Get( "sv_battleye", "1", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_LATCH );
	else
		sv_battleye =			Cvar_Get( "sv_battleye", "0", CVAR_SERVERINFO | CVAR_READONLY );
#endif

	sv_noreload =				Cvar_Get( "sv_noreload", "0", 0 );
	
	if( dedicated->integer )
		sv_public =				Cvar_Get( "sv_public", "1", CVAR_ARCHIVE|CVAR_NOSET );
	else
		sv_public =				Cvar_Get( "sv_public", "0", CVAR_ARCHIVE );
	sv_reconnectlimit =			Cvar_Get( "sv_reconnectlimit", "3", CVAR_ARCHIVE );
	sv_maxclients =				Cvar_Get( "sv_maxclients", "1", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_LATCH );

	// fix invalid sv_maxclients values
	if( sv_maxclients->integer < 1 )
		Cvar_FullSet( "sv_maxclients", "1", CVAR_ARCHIVE|CVAR_SERVERINFO|CVAR_LATCH, qtrue );
	else if( sv_maxclients->integer > MAX_CLIENTS )
		Cvar_FullSet( "sv_maxclients", va("%i", MAX_CLIENTS), CVAR_ARCHIVE|CVAR_SERVERINFO|CVAR_LATCH, qtrue );

	// wsw : jal : cap client's exceding server rules
	sv_maxrate =				Cvar_Get( "sv_maxrate", "15000", CVAR_SERVERINFO|CVAR_ARCHIVE );
	sv_skilllevel =				Cvar_Get( "sv_skilllevel", "1", CVAR_SERVERINFO|CVAR_ARCHIVE );
	if( dedicated->integer )
		sv_masterservers =			Cvar_Get( "masterservers", DEFAULT_MASTER_SERVERS_IPS, CVAR_NOSET );
	else
		sv_masterservers =			Cvar_Get( "masterservers", DEFAULT_MASTER_SERVERS_IPS, 0 );
	sv_debug_serverCmd =		Cvar_Get( "sv_debug_serverCmd", "0", CVAR_ARCHIVE );

	// this is a message holder for shared use
	MSG_Init( &tmpMessage, tmpMessageData, sizeof(tmpMessageData) );

	// init server updates ratio
	if( dedicated->integer )
		sv_pps = Cvar_Get( "sv_pps", "20", CVAR_SERVERINFO|CVAR_NOSET );
	else
		sv_pps = Cvar_Get( "sv_pps", "20", CVAR_SERVERINFO );
	svc.frametime = (int)( 1000 / sv_pps->value );
	if( svc.frametime > 200 ) {	// too slow, also, netcode uses a byte
		Cvar_ForceSet( "sv_pps", "5" );
		svc.frametime = 200;
	} else if( svc.frametime < 10 ) { // abusive
		Cvar_ForceSet( "sv_pps", "100" );
		svc.frametime = 10;
	}
	Com_Printf( "Server running at %i pps\n", sv_pps->integer );

	// wsw : jal
	if( dedicated->integer ) {
		Com_Printf( "sv_maxrate is %i\n", sv_maxrate->integer );
	}

	//init the master servers list
	SV_InitMaster();

}

/*
==================
SV_FinalMessage

Used by SV_Shutdown to send a final message to all
connected clients before the server goes down.  The messages are sent immediately,
not just stuck on the outgoing message list, because the server is going
to totally exit after returning from this function.
==================
*/
void SV_FinalMessage( char *message, qboolean reconnect )
{
	int			i;
	client_t	*sv_client;

	int j;

	for( i = 0, sv_client = svs.clients; i < sv_maxclients->integer; i++, sv_client++ ) {
		if( sv_client->edict && (sv_client->edict->r.svflags & SVF_FAKECLIENT) )
			continue;
		if( sv_client->state >= CS_CONNECTED ) {
			SV_ClientPrintf( sv_client, "\"%s\"", message );
			if( reconnect )
				SV_SendServerCommand( sv_client, "reconnect" );
			else
				SV_SendServerCommand( sv_client, "disconnect" );

			MSG_Clear( &tmpMessage );
			SV_AddReliableCommandsToMessage( sv_client, &tmpMessage );
			for( j = 0; j < 2; j++ ) { // send it twice
				SV_SendMessageToClient( sv_client, &tmpMessage );
			}
		}
	}
}

/*
================
SV_Shutdown

Called when each game quits,
before Sys_Quit or Sys_Error
================
*/
void SV_Shutdown( char *finalmsg, qboolean reconnect )
{
	if( svs.clients )
		SV_FinalMessage( finalmsg, reconnect );

	SV_FreeCachedChecksums();

	SV_ShutdownGameProgs();

	// get any latched variable changes (sv_maxclients, etc)
	Cvar_GetLatchedVars( CVAR_LATCH );

	memset( &sv, 0, sizeof(sv) );
	Com_SetServerState( sv.state );

	if( sv_mempool )
		Mem_EmptyPool( sv_mempool );
	memset( &svs, 0, sizeof(svs) );

#ifdef BATTLEYE
	if( sv_battleye->integer )
		SV_BE_DisconnectFromMaster();
#endif

#ifdef SERVERSIDE_DEMOS
	if (svs.demofile)
		fclose (svs.demofile);
#endif
}
