//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// baseserver.cpp: implementation of the CBaseServer class.
//
//////////////////////////////////////////////////////////////////////



#if defined(_WIN32) && !defined(_X360)
#include "winlite.h"		// FILETIME
#elif defined(POSIX)
#include <time.h>
/*
#include <sys/sysinfo.h>          
#include <asm/param.h> // for HZ
*/
#include <sys/resource.h>
#include <netinet/in.h>
#elif defined(_X360)
#else
#error "Includes for CPU usage calcs here"
#endif

#include "filesystem_engine.h"
#include "baseserver.h"
#include "sysexternal.h"
#include "quakedef.h"
#include "host.h"
#include "netmessages.h"
#include "sys.h"
#include "framesnapshot.h"
#include "sv_packedentities.h"
#include "dt_send_eng.h"
#include "dt_recv_eng.h"
#include "networkstringtable.h"
#include "sys_dll.h"
#include "host_cmd.h"
#include "sv_steamauth.h"

#include <proto_oob.h>
#include <vstdlib/random.h>
#include <irecipientfilter.h>
#include <KeyValues.h>
#include <tier0/vprof.h>
#include <cdll_int.h>
#include <eiface.h>
#include <client_class.h>
#include "tier0/icommandline.h"
#include "sv_steamauth.h"
#include "tier0/vcrmode.h"
#include "sv_ipratelimit.h"
#include "cl_steamauth.h"
#include "sv_filter.h"

#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

CThreadFastMutex g_svInstanceBaselineMutex;
extern	CGlobalVars g_ServerGlobalVariables;

static ConVar	sv_max_queries_sec( "sv_max_queries_sec", "3.0", 0, "Maximum queries per second to respond to from a single IP address." );
static ConVar	sv_max_queries_window( "sv_max_queries_window", "30", 0, "Window over which to average queries per second averages." );
static ConVar	sv_max_queries_sec_global( "sv_max_queries_sec_global", "3000", 0, "Maximum queries per second to respond to from anywhere." );

static ConVar	sv_max_connects_sec( "sv_max_connects_sec", "2.0", 0, "Maximum connections per second to respond to from a single IP address." );
static ConVar	sv_max_connects_window( "sv_max_connects_window", "4", 0, "Window over which to average connections per second averages." );
// This defaults to zero so that somebody spamming the server with packets cannot lock out other clients.
static ConVar	sv_max_connects_sec_global( "sv_max_connects_sec_global", "0", 0, "Maximum connections per second to respond to from anywhere." );

static CIPRateLimit s_queryRateChecker( &sv_max_queries_sec, &sv_max_queries_window, &sv_max_queries_sec_global );
static CIPRateLimit s_connectRateChecker( &sv_max_connects_sec, &sv_max_connects_window, &sv_max_connects_sec_global );

// Give new data to Steam's master server updater every N seconds.
// This is NOT how often packets are sent to master servers, only how often the
// game server talks to Steam's master server updater (which is on the game server's
// machine, not the Steam servers).
#define MASTER_SERVER_UPDATE_INTERVAL		2.0

// Steam has a matching one in matchmakingtypes.h
#define MAX_TAG_STRING_LENGTH		128

int SortServerTags( char* const *p1, char* const *p2 )
{
	return ( Q_strcmp( *p1, *p2 ) > 0 );
}

static void ServerTagsCleanUp( void )
{
	CUtlVector<char*> TagList;
	ConVarRef sv_tags( "sv_tags" );
	if ( sv_tags.IsValid() )
	{
		int i;
		char tmptags[MAX_TAG_STRING_LENGTH];
		tmptags[0] = '\0';

		V_SplitString( sv_tags.GetString(), ",", TagList );

		// make a pass on the tags to eliminate preceding whitespace and empty tags
		for ( i = 0; i < TagList.Count(); i++ )
		{
			if ( i > 0 )
			{
				Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
			}

			char *pChar = TagList[i];
			while ( *pChar && *pChar == ' ' )
			{
				pChar++;
			}

			// make sure we don't have an empty string (all spaces or ,,)
			if ( *pChar )
			{
				Q_strncat( tmptags, pChar, MAX_TAG_STRING_LENGTH );
			}
		}

		// reset our lists and sort the tags
		TagList.PurgeAndDeleteElements();
		V_SplitString( tmptags, ",", TagList );
		TagList.Sort( SortServerTags );
		tmptags[0] = '\0';

		// create our new, sorted list of tags
		for ( i = 0; i < TagList.Count(); i++ )
		{
			if ( i > 0 )
			{
				Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
			}

			Q_strncat( tmptags, TagList[i], MAX_TAG_STRING_LENGTH );
		}

		// set our convar and purge our list
		sv_tags.SetValue( tmptags );
		TagList.PurgeAndDeleteElements();
	}
}

static void SvTagsChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
{
	// We're going to modify the sv_tags convar here, which will cause this to be called again. Prevent recursion.
	static bool bTagsChangeCallback = false;
	if ( bTagsChangeCallback )
		return;

	bTagsChangeCallback = true;

	ServerTagsCleanUp();

	ConVarRef var( pConVar );
	if ( Steam3Server().SteamGameServer() )
	{
		Steam3Server().SteamGameServer()->SetGameTags( var.GetString() );
	}

	bTagsChangeCallback = false;
}

ConVar			sv_region( "sv_region","-1", FCVAR_NONE, "The region of the world to report this server in." );
static ConVar	sv_instancebaselines( "sv_instancebaselines", "1", FCVAR_DEVELOPMENTONLY, "Enable instanced baselines. Saves network overhead." );
static ConVar	sv_stats( "sv_stats", "1", 0, "Collect CPU usage stats" );
static ConVar	sv_enableoldqueries( "sv_enableoldqueries", "0", 0, "Enable support for old style (HL1) server queries" );
static ConVar	sv_password( "sv_password", "", FCVAR_NOTIFY | FCVAR_PROTECTED | FCVAR_DONTRECORD, "Server password for entry into multiplayer games" );
ConVar			sv_tags( "sv_tags", "", FCVAR_NOTIFY, "Server tags. Used to provide extra information to clients when they're browsing for servers. Separate tags with a comma.", SvTagsChangeCallback );
ConVar			sv_visiblemaxplayers( "sv_visiblemaxplayers", "-1", 0, "Overrides the max players reported to prospective clients" );
ConVar			sv_alternateticks( "sv_alternateticks", ( IsX360() ) ? "1" : "0", FCVAR_SPONLY, "If set, server only simulates entities on even numbered ticks.\n" );
ConVar			sv_allow_wait_command( "sv_allow_wait_command", "1", FCVAR_REPLICATED, "Allow or disallow the wait command on clients connected to this server." );
ConVar			sv_allow_color_correction( "sv_allow_color_correction", "1", FCVAR_REPLICATED, "Allow or disallow clients to use color correction on this server." );

extern CNetworkStringTableContainer *networkStringTableContainerServer;
extern ConVar sv_stressbots;

int g_CurGameServerID = 1;

// #define ALLOW_DEBUG_DEDICATED_SERVER_OUTSIDE_STEAM

bool AllowDebugDedicatedServerOutsideSteam()
{
#if defined( ALLOW_DEBUG_DEDICATED_SERVER_OUTSIDE_STEAM )
	return true;
#else
	return false;
#endif
}


static void SetMasterServerKeyValue( ISteamGameServer *pUpdater, IConVar *pConVar )
{
	ConVarRef var( pConVar );

	// For protected cvars, don't send the string
	if ( var.IsFlagSet( FCVAR_PROTECTED ) )
	{
		// If it has a value string and the string is not "none"
		if ( ( strlen( var.GetString() ) > 0 ) &&
				stricmp( var.GetString(), "none" ) )
		{
			pUpdater->SetKeyValue( var.GetName(), "1" );
		}
		else
		{
			pUpdater->SetKeyValue( var.GetName(), "0" );
		}
	}
	else
	{
		pUpdater->SetKeyValue( var.GetName(), var.GetString() );
	}

	if ( Steam3Server().BIsActive() )
	{
		sv.RecalculateTags();
	}
}


static void ServerNotifyVarChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
{
	if ( !pConVar->IsFlagSet( FCVAR_NOTIFY ) )
		return;
	
	ISteamGameServer *pUpdater = Steam3Server().SteamGameServer();
	if ( !pUpdater )
	{
		// This will force it to send all the rules whenever the master server updater is there.
		sv.SetMasterServerRulesDirty();
		return;
	}

	SetMasterServerKeyValue( pUpdater, pConVar );
}


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CBaseServer::CBaseServer() 
{
	// Just get a unique ID to talk to the steam master server updater.
	m_bRestartOnLevelChange = false;
	
	m_StringTables = NULL;
	m_pInstanceBaselineTable = NULL;
	m_pLightStyleTable = NULL;
	m_pUserInfoTable = NULL;
	m_pServerStartupTable = NULL;
	m_pDownloadableFileTable = NULL;

	m_fLastCPUCheckTime = 0;
	m_fStartTime = 0;
	m_fCPUPercent = 0;
	m_Socket = NS_SERVER;
	m_nTickCount = 0;
	
	m_szMapname[0] = 0;
	m_szSkyname[0] = 0;
	m_Password[0] = 0;
	V_memset( worldmapMD5.bits, 0, MD5_DIGEST_LENGTH );

	serverclasses = serverclassbits = 0;
	m_nMaxclients = m_nSpawnCount = 0;
	m_flTickInterval = 0.03;
	m_nUserid = 0;
	m_nNumConnections = 0;
	m_bIsDedicated = false;
	m_fCPUPercent = 0;
	m_fStartTime = 0;
	m_fLastCPUCheckTime = 0;
	
	m_bMasterServerRulesDirty = true;
	m_flLastMasterServerUpdateTime = 0;
	m_CurrentRandomNonce = 0;
	m_LastRandomNonce = 0;
	m_flLastRandomNumberGenerationTime = -3.0f; // force it to calc first frame

	m_bReportNewFakeClients = true;
	m_flPausedTimeEnd = -1.f;
}

CBaseServer::~CBaseServer()
{

}

/*
================
SV_CheckChallenge

Make sure connecting client is not spoofing
================
*/
bool CBaseServer::CheckChallengeNr( netadr_t &adr, int nChallengeValue )
{
	// See if the challenge is valid
	// Don't care if it is a local address.
	if ( adr.IsLoopback() )
		return true;

	// X360TBD: network
	if ( IsX360() )
		return true;

	uint64 challenge = ((uint64)adr.GetIPNetworkByteOrder() << 32) + m_CurrentRandomNonce;
	CRC32_t hash;
	CRC32_Init( &hash );
	CRC32_ProcessBuffer( &hash, &challenge, sizeof(challenge) );
	CRC32_Final( &hash );
	if ( (int)hash == nChallengeValue )
		return true;

	// try with the old random nonce
	challenge &= 0xffffffff00000000ull;
	challenge += m_LastRandomNonce;
	hash = 0;
	CRC32_Init( &hash );
	CRC32_ProcessBuffer( &hash, &challenge, sizeof(challenge) );
	CRC32_Final( &hash );
	if ( (int)hash == nChallengeValue )
		return true;

	return false;
}


const char *CBaseServer::GetPassword() const
{
	const char *password = sv_password.GetString();

	// if password is empty or "none", return NULL
	if ( !password[0] || !Q_stricmp(password, "none" ) )
	{
		return NULL;
	}

	return password;
}


void CBaseServer::SetPassword(const char *password)
{
	if ( password != NULL )
	{
		Q_strncpy( m_Password, password, sizeof(m_Password) );
	}
	else
	{
		m_Password[0] = 0; // clear password
	}
}

#define MAX_REUSE_PER_IP 5 // 5 outstanding connect request within timeout window, to account for NATs

/*
================
CheckIPConnectionReuse

Determine if this IP requesting the connect is connecting too often
================
*/
bool CBaseServer::CheckIPConnectionReuse( netadr_t &adr )
{
	int nSimultaneouslyConnections = 0;

	for ( int slot = 0 ; slot < m_Clients.Count() ; slot++ )
	{
		CBaseClient *client = m_Clients[slot];

		// if the user is connected but not fully in AND the addr's match
		if ( client->IsConnected() &&
			 !client->IsActive() &&
			 !client->IsFakeClient() && 
			 adr.CompareAdr ( client->m_NetChannel->GetRemoteAddress(), true ) )
		{
			nSimultaneouslyConnections++;
		}
	}
	
	if ( nSimultaneouslyConnections > MAX_REUSE_PER_IP ) 
	{
		Msg ("Too many connect packets from %s\n", adr.ToString( true ) );	
		return false; // too many connect packets!!!!
	}
	return true; // this IP is okay
}


int CBaseServer::GetNextUserID()
{
	// Note: we'll usually exit on the first pass of this loop..
	for ( int i=0; i < m_Clients.Count()+1; i++ )
	{
		int nTestID = (m_nUserid + i + 1) % SHRT_MAX;

		// Make sure no client has this user ID.		
		int iClient;
		for ( iClient=0; iClient < m_Clients.Count(); iClient++ )
		{
			if ( m_Clients[iClient]->GetUserID() == nTestID )
				break;
		}

		// Ok, no client has this ID, so return it.		
		if ( iClient == m_Clients.Count() )
			return nTestID;
	}
	
	Assert( !"GetNextUserID: can't find a unique ID." );
	return m_nUserid + 1;
}


/*
================
SV_ConnectClient

Initializes a CSVClient for a new net connection.  This will only be called
once for a player each game, not once for each level change.
================
*/
IClient *CBaseServer::ConnectClient ( netadr_t &adr, int protocol, int challenge, int clientChallenge, int authProtocol, 
							    const char *name, const char *password, const char *hashedCDkey, int cdKeyLen )
{
	COM_TimestampedLog( "CBaseServer::ConnectClient" );

	if ( !IsActive() )
	{
		return NULL;
	}

	if ( !name || !password || !hashedCDkey )
	{
		return NULL;
	}

	// Make sure protocols match up
	if ( !CheckProtocol( adr, protocol, clientChallenge ) )
	{
		return NULL;
	}


	if ( !CheckChallengeNr( adr, challenge ) )
	{
		RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectBadChallenge" );
		return NULL;
	}

	// SourceTV checks password & restrictions later once we know
	// if its a normal spectator client or a relay proxy
	if ( !IsHLTV() && !IsReplay() )
	{
#ifndef NO_STEAM
		// LAN servers restrict to class b IP addresses
		if ( !CheckIPRestrictions( adr, authProtocol ) )
		{
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectLANRestrict");
			return NULL;
		}
#endif

		if ( !CheckPassword( adr, password, name ) )
		{
			// failed
			ConMsg ( "%s:  password failed.\n", adr.ToString() );
			// Special rejection handler.
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectBadPassword" );
			return NULL;
		}
	}

	COM_TimestampedLog( "CBaseServer::ConnectClient:  GetFreeClient" );

	CBaseClient	*client = GetFreeClient( adr );

	if ( !client )
	{
		RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectServerFull" );
		return NULL;	// no free slot found
	}

	int nNextUserID = GetNextUserID();
	if ( !CheckChallengeType( client, nNextUserID, adr, authProtocol, hashedCDkey, cdKeyLen, clientChallenge ) ) // we use the client pointer to track steam requests
	{
		return NULL;
	}

	ISteamGameServer *pSteamGameServer = Steam3Server().SteamGameServer();
	if ( !pSteamGameServer && authProtocol == PROTOCOL_STEAM )
	{
		Warning("NULL ISteamGameServer in ConnectClient. Steam authentication may fail.\n");
	}

	if ( Filter_IsUserBanned( client->GetNetworkID() ) )
	{
		// Need to make sure the master server is updated with the rejected connection because
		// we called Steam3Server().NotifyClientConnect() in CheckChallengeType() above.
		if ( pSteamGameServer && authProtocol == PROTOCOL_STEAM )
			pSteamGameServer->SendUserDisconnect( client->m_SteamID ); 

		RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectBanned" );
		return NULL;
	}

#if !defined( _HLTVTEST ) && !defined( _REPLAYTEST )
	if ( !FinishCertificateCheck( adr, authProtocol, hashedCDkey, clientChallenge ) )
	{
		// Need to make sure the master server is updated with the rejected connection because
		// we called Steam3Server().NotifyClientConnect() in CheckChallengeType() above.
		if ( pSteamGameServer && authProtocol == PROTOCOL_STEAM )
			pSteamGameServer->SendUserDisconnect( client->m_SteamID ); 

		return NULL;
	}
#endif

	COM_TimestampedLog( "CBaseServer::ConnectClient:  NET_CreateNetChannel" );

	// create network channel
	INetChannel * netchan = NET_CreateNetChannel( m_Socket, &adr, adr.ToString(), client );

	if ( !netchan )
	{
		// Need to make sure the master server is updated with the rejected connection because
		// we called Steam3Server().NotifyClientConnect() in CheckChallengeType() above.
		if ( pSteamGameServer && authProtocol == PROTOCOL_STEAM )
			pSteamGameServer->SendUserDisconnect( client->m_SteamID ); 

		RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectFailedChannel" );
		return NULL;
	}

	// setup netchannl settings
	netchan->SetChallengeNr( challenge );
	
	COM_TimestampedLog( "CBaseServer::ConnectClient:  client->Connect" );

	// make sure client is reset and clear
	client->Connect( name, nNextUserID, netchan, false, clientChallenge );

	m_nUserid = nNextUserID;
	m_nNumConnections++;

	// Will get reset from userinfo, but this value comes from sv_updaterate ( the default )
	client->m_fSnapshotInterval = 1.0f/20.0f;
	client->m_fNextMessageTime = net_time + client->m_fSnapshotInterval;
	// Force a full delta update on first packet.
	client->m_nDeltaTick = -1;
	client->m_nSignonTick = 0;
	client->m_nStringTableAckTick = 0;
	client->m_pLastSnapshot = NULL;
	
	// Tell client connection worked, now use netchannels
	{
		ALIGN4 char		msg_buffer[MAX_ROUTABLE_PAYLOAD] ALIGN4_POST;
		bf_write	msg( msg_buffer, sizeof(msg_buffer) );

		msg.WriteLong( CONNECTIONLESS_HEADER );
		msg.WriteByte( S2C_CONNECTION );
		msg.WriteLong( clientChallenge );
		msg.WriteString( "0000000000" ); // pad out

		NET_SendPacket ( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
	}

	// Set up client structure.
	if ( authProtocol == PROTOCOL_HASHEDCDKEY )
	{
		// use hased CD key as player GUID
		Q_strncpy ( client->m_GUID, hashedCDkey, SIGNED_GUID_LEN );
		client->m_GUID[SIGNED_GUID_LEN] = '\0';
	}
	else if ( authProtocol == PROTOCOL_STEAM )
	{
		// StartSteamValidation() above initialized the clients networkid
	}

	if ( netchan && !netchan->IsLoopback() )
		ConMsg("Client \"%s\" connected (%s).\n", client->GetClientName(), netchan->GetAddress() );

	return client;
}

/*
================
RequireValidChallenge

Return true if this server query must provide a valid challenge number
================
*/
bool CBaseServer::RequireValidChallenge( netadr_t &adr )
{
	if ( sv_enableoldqueries.GetBool() == true )
	{
		return false; // don't enforce challenge numbers
	}

	return true;
}

/*
================
ValidChallenge

Return true if this challenge number is correct for this host (for server queries)
================
*/
bool CBaseServer::ValidChallenge( netadr_t & adr, int challengeNr )
{
	if ( !IsActive() )            // Must be running a server.
		return false ;

	if ( !IsMultiplayer() )   // ignore in single player
		return false ;

	if ( RequireValidChallenge( adr) )
	{
		if ( !CheckChallengeNr( adr, challengeNr ) )
		{
			ReplyServerChallenge( adr );
			return false;
		}
	}

	return true; 
}

bool CBaseServer::ValidInfoChallenge( netadr_t & adr, const char *nugget )
{
	if ( !IsActive() )            // Must be running a server.
		return false ;

	if ( !IsMultiplayer() )   // ignore in single player
		return false ;

	if ( IsReplay() )
		return false;

	if ( RequireValidChallenge( adr) )
	{
		if ( Q_stricmp( nugget, A2S_KEY_STRING ) ) // if the string isn't equal then fail out
		{
			return false;
		}
	}

	return true; 
}


bool CBaseServer::ProcessConnectionlessPacket(netpacket_t * packet)
{
	bf_read msg = packet->message;	// handy shortcut 

	char c = msg.ReadChar();

	if ( c== 0  )
	{
		return false;
	}

	switch ( c )
	{
		case A2S_GETCHALLENGE :
			{
				int clientChallenge = msg.ReadLong();
				ReplyChallenge( packet->from, clientChallenge );
			}

			break;
		
		case A2S_SERVERQUERY_GETCHALLENGE:
			ReplyServerChallenge( packet->from );
			break;

		case C2S_CONNECT :
			{
				char cdkey[STEAM_KEYSIZE];
				char name[256];
				char password[256];
				char productVersion[32];
				
				int protocol = msg.ReadLong();
				int authProtocol = msg.ReadLong();
				int challengeNr = msg.ReadLong();
				int clientChallenge = msg.ReadLong();

				// pull the challenge number check early before we do any expensive processing on the connect
				if ( !CheckChallengeNr( packet->from, challengeNr ) )
				{
					RejectConnection( packet->from, clientChallenge, "#GameUI_ServerRejectBadChallenge" );
					break;
				}

				// rate limit the connections
				if ( !s_connectRateChecker.CheckIP( packet->from ) )
					return false;

				msg.ReadString( name, sizeof(name) );
				msg.ReadString( password, sizeof(password) );
				msg.ReadString( productVersion, sizeof(productVersion) );
				
//				bool bClientPlugins = ( msg.ReadByte() > 0 );

				// There's a magic number we use in the steam.inf in P4 that we don't update.
				// We can use this to detect if they are running out of P4, and if so, don't do any version
				// checking.
				const char *pszVersionInP4 = "2000";
				const char *pszVersionString = GetSteamInfIDVersionInfo().szVersionString;
				if ( V_strcmp( pszVersionString, pszVersionInP4 ) && V_strcmp( productVersion, pszVersionInP4 ) )
				{
					int nVersionCheck = Q_strncmp( pszVersionString, productVersion, V_strlen( pszVersionString ) );
					if ( nVersionCheck < 0 )
					{
						RejectConnection( packet->from, clientChallenge, "#GameUI_ServerRejectOldVersion" );
						break;
					}
					if ( nVersionCheck > 0 )
					{
						RejectConnection( packet->from, clientChallenge, "#GameUI_ServerRejectNewVersion" );
						break;
					}
				}

// 				if ( Steam3Server().BSecure() && bClientPlugins )
// 				{
// 					RejectConnection( packet->from, "Cannot connect to a secure server while plug-ins are\nloaded on your client\n" );
//					break;
// 				}

				if ( authProtocol == PROTOCOL_STEAM )
				{
					int keyLen = msg.ReadShort();
					if ( keyLen < 0 || keyLen > sizeof(cdkey) )
					{
						RejectConnection( packet->from, clientChallenge, "#GameUI_ServerRejectBadSteamKey" );
						break;
					}
					msg.ReadBytes( cdkey, keyLen );

					ConnectClient( packet->from, protocol, challengeNr, clientChallenge, authProtocol, name, password, cdkey, keyLen );	// cd key is actually a raw encrypted key	
				}
				else
				{
					msg.ReadString( cdkey, sizeof(cdkey) );
					ConnectClient( packet->from, protocol, challengeNr, clientChallenge, authProtocol, name, password, cdkey, strlen(cdkey) );
				}
			}

			break;
		
		default:
			{
				// rate limit the more expensive server query packets
				if ( !s_queryRateChecker.CheckIP( packet->from ) )
					return false;

				// We don't understand it, let the master server updater at it.
				if ( Steam3Server().SteamGameServer() && Steam3Server().IsMasterServerUpdaterSharingGameSocket() )
				{
					Steam3Server().SteamGameServer()->HandleIncomingPacket( 
						packet->message.GetBasePointer(), 
						packet->message.TotalBytesAvailable(),
						packet->from.GetIPHostByteOrder(),
						packet->from.GetPort()
						);
				
					// This is where it will usually want to respond to something immediately by sending some
					// packets, so check for that immediately.
					ForwardPacketsFromMasterServerUpdater();
				}
			}

			break;
	}

	return true;
}

int CBaseServer::GetNumFakeClients() const
{
	int count = 0; 
	for ( int i = 0; i < m_Clients.Count(); i++ )
	{
		if ( m_Clients[i]->IsFakeClient() )
		{
			count++;
		}
	}
	return count;
}

/*
==================
void SV_CountPlayers

Counts number of connections.  Clients includes regular connections
==================
*/
int CBaseServer::GetNumClients( void ) const
{
	int count	= 0;

	for (int i=0 ; i < m_Clients.Count() ; i++ )
	{
		if ( m_Clients[ i ]->IsConnected() )
		{
			count++;
		}
	}

	return count;
}

/*
==================
void SV_CountPlayers

Counts number of HLTV and Replay connections.  Clients includes regular connections
==================
*/
int CBaseServer::GetNumProxies( void ) const
{
	int count	= 0;

	for (int i=0 ; i < m_Clients.Count() ; i++ )
	{
#if defined( REPLAY_ENABLED )
		if ( m_Clients[ i ]->IsConnected() && (m_Clients[ i ]->IsHLTV() || m_Clients[ i ]->IsReplay() ) )
#else
		if ( m_Clients[ i ]->IsConnected() && m_Clients[ i ]->IsHLTV() )
#endif
		{
			count++;
		}
	}

	return count;
}

int CBaseServer::GetNumPlayers()
{
	int count = 0;
	if ( !GetUserInfoTable())
	{
		return 0;
	}

	const int maxPlayers = GetUserInfoTable()->GetNumStrings();

	for ( int i=0; i < maxPlayers; i++ )
	{
		const player_info_t *pi = (const player_info_t *) m_pUserInfoTable->GetStringUserData( i, NULL );

		if ( !pi )
			continue;

		if ( pi->fakeplayer )
			continue;	// don't count bots

		count++;
	}

	return count;
}

bool CBaseServer::GetPlayerInfo( int nClientIndex, player_info_t *pinfo )
{
	if ( !pinfo )
		return false;

	if ( nClientIndex < 0 || !GetUserInfoTable() || nClientIndex >= GetUserInfoTable()->GetNumStrings() )
	{
		Q_memset( pinfo, 0, sizeof( player_info_t ) );
		return false;
	}

	player_info_t *pi = (player_info_t*) GetUserInfoTable()->GetStringUserData( nClientIndex, NULL );

	if ( !pi )
	{
		Q_memset( pinfo, 0, sizeof( player_info_t ) );
		return false;
	}

	Q_memcpy( pinfo, pi, sizeof( player_info_t ) );

	// Fixup from network order (little endian)
	CByteswap byteswap;
	byteswap.SetTargetBigEndian( false );
	byteswap.SwapFieldsToTargetEndian( pinfo );

	return true;
}


void CBaseServer::UserInfoChanged( int nClientIndex )
{
	player_info_t pi;

	bool oldlock = networkStringTableContainerServer->Lock( false );
	if ( m_Clients[ nClientIndex ]->FillUserInfo( pi ) )
	{
		// Fixup to little endian for networking
		CByteswap byteswap;
		byteswap.SetTargetBigEndian( false );
		byteswap.SwapFieldsToTargetEndian( &pi );

		// update user info settings
		m_pUserInfoTable->SetStringUserData( nClientIndex, sizeof(pi), &pi );
	}
	else
	{
		// delete user data settings
		m_pUserInfoTable->SetStringUserData( nClientIndex, 0, NULL );
	}
	networkStringTableContainerServer->Lock( oldlock );
	
}

void CBaseServer::FillServerInfo(SVC_ServerInfo &serverinfo)
{
	static char gamedir[MAX_OSPATH];
	Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );

	serverinfo.m_nProtocol		= PROTOCOL_VERSION;
	serverinfo.m_nServerCount	= GetSpawnCount();
	V_memcpy( serverinfo.m_nMapMD5.bits, worldmapMD5.bits, MD5_DIGEST_LENGTH );
	serverinfo.m_nMaxClients	= GetMaxClients();
	serverinfo.m_nMaxClasses	= serverclasses;
	serverinfo.m_bIsDedicated	= IsDedicated();
#ifdef _WIN32
	serverinfo.m_cOS			= 'W';
#else
	serverinfo.m_cOS			= 'L';
#endif

	// HACK to signal that the server is "new"
	serverinfo.m_cOS = tolower( serverinfo.m_cOS );

	serverinfo.m_fTickInterval	= GetTickInterval();
	serverinfo.m_szGameDir		= gamedir;
	serverinfo.m_szMapName		= GetMapName();
	serverinfo.m_szSkyName		= m_szSkyname;
	serverinfo.m_szHostName		= GetName();
	serverinfo.m_bIsHLTV		= IsHLTV();
#if defined( REPLAY_ENABLED )
	serverinfo.m_bIsReplay		= IsReplay();
#endif
}

/*
=================
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 CBaseServer::ReplyChallenge(netadr_t &adr, int clientChallenge )
{
	ALIGN4 char	buffer[STEAM_KEYSIZE+32] ALIGN4_POST;
	bf_write msg(buffer,sizeof(buffer));

	// get a free challenge number
	int challengeNr = GetChallengeNr( adr );
	int	authprotocol = GetChallengeType( adr );

	msg.WriteLong( CONNECTIONLESS_HEADER );
	
	msg.WriteByte( S2C_CHALLENGE );
	msg.WriteLong( S2C_MAGICVERSION ); // This makes it so we can detect that this server is correct
	msg.WriteLong( challengeNr ); // Server to client challenge
	msg.WriteLong( clientChallenge ); // Client to server challenge to ensure our reply is what they asked
	msg.WriteLong( authprotocol );

#if !defined( NO_STEAM ) //#ifndef _XBOX
	if ( authprotocol == PROTOCOL_STEAM )
	{
		msg.WriteShort( 0 ); //  steam2 encryption key not there anymore
		CSteamID steamID = Steam3Server().GetGSSteamID();
		uint64 unSteamID = steamID.ConvertToUint64();
		msg.WriteBytes( &unSteamID, sizeof(unSteamID) );
		msg.WriteByte( Steam3Server().BSecure() );
	}
#else
	msg.WriteShort( 1 );
	msg.WriteByte( 0 );
	uint64 unSteamID = 0;
	msg.WriteBytes( &unSteamID, sizeof(unSteamID) );
	msg.WriteByte( 0 );
#endif
	msg.WriteString( "000000" );	// padding bytes

	NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
}


/*
=================
ReplyServerChallenge

Returns a challenge number that can be used
in a subsequent server query commands.
We do this to prevent DDoS attacks via bandwidth
amplification.
=================
*/
void CBaseServer::ReplyServerChallenge(netadr_t &adr)
{
	ALIGN4 char	buffer[16] ALIGN4_POST;
	bf_write msg(buffer,sizeof(buffer));

	// get a free challenge number
	int challengeNr = GetChallengeNr( adr );
	
	msg.WriteLong( CONNECTIONLESS_HEADER );
	msg.WriteByte( S2C_CHALLENGE );
	msg.WriteLong( challengeNr );
	NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
}

const char *CBaseServer::GetName( void ) const
{
	return host_name.GetString();
}

int CBaseServer::GetChallengeType(netadr_t &adr)
{
	if ( AllowDebugDedicatedServerOutsideSteam() )
		return PROTOCOL_HASHEDCDKEY;

#ifndef SWDS	
	// don't auth SP games or local mp games if steam isn't running
	if ( Host_IsSinglePlayerGame() || ( !Steam3Client().SteamUser() && !IsDedicated() ))
	{
		return PROTOCOL_HASHEDCDKEY;
	}
	else
#endif
	{
		return PROTOCOL_STEAM;
	}
}

int CBaseServer::GetChallengeNr (netadr_t &adr)
{
	uint64 challenge = ((uint64)adr.GetIPNetworkByteOrder() << 32) + m_CurrentRandomNonce;
	CRC32_t hash;
	CRC32_Init( &hash );
	CRC32_ProcessBuffer( &hash, &challenge, sizeof(challenge) );
	CRC32_Final( &hash );
	return (int)hash;
}

void CBaseServer::GetNetStats( float &avgIn, float &avgOut )
{
	avgIn = avgOut = 0.0f;

	for (int i = 0; i < m_Clients.Count(); i++ )
	{
		CBaseClient	*cl = m_Clients[ i ];

		// Fake clients get killed in here.
		if ( cl->IsFakeClient() )
			continue;
	
		if ( !cl->IsConnected() )
			continue;

		INetChannel *netchan = cl->GetNetChannel();

		avgIn += netchan->GetAvgData(FLOW_INCOMING);
		avgOut += netchan->GetAvgData(FLOW_OUTGOING);
	}
}

void CBaseServer::CalculateCPUUsage( void )
{
	if ( !sv_stats.GetBool() )
	{
		return;
	}

	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	float curtime = Sys_FloatTime();

	if ( m_fStartTime == 0 )
	// record when we started
	{
		m_fStartTime = curtime;
	}

	if( curtime > m_fLastCPUCheckTime + 1 )
	// only do this every 1 second
	{
#if defined ( _WIN32 ) 
		static float lastAvg=0;
		static __int64 lastTotalTime=0,lastNow=0;

		HANDLE handle;
		FILETIME creationTime, exitTime, kernelTime, userTime, nowTime;
 		__int64 totalTime,now;
			
		handle = GetCurrentProcess();

		// get CPU time
		GetProcessTimes (handle, &creationTime, &exitTime,
						&kernelTime, &userTime);
		GetSystemTimeAsFileTime(&nowTime);

		if ( lastNow == 0 )
		{
			memcpy(&lastNow, &creationTime, sizeof(__int64));
		}

		memcpy(&totalTime, &userTime, sizeof(__int64));
		memcpy(&now, &kernelTime, sizeof(__int64));
		totalTime+=now;

		memcpy(&now, &nowTime, sizeof(__int64));

		m_fCPUPercent = (double)(totalTime-lastTotalTime)/(double)(now-lastNow);
		
		// now save this away for next time
		if ( curtime > lastAvg+5 ) 
		// only do it every 5 seconds, so we keep a moving average
		{
			memcpy(&lastNow,&nowTime,sizeof(__int64));
			memcpy(&lastTotalTime,&totalTime,sizeof(__int64));
			lastAvg=m_fLastCPUCheckTime;
		}
#elif defined ( POSIX )
		static struct rusage s_lastUsage;
		static float s_lastAvg = 0;
		struct rusage currentUsage;

		if ( getrusage( RUSAGE_SELF, &currentUsage ) == 0 )
		{
			double flTimeDiff = (double)( currentUsage.ru_utime.tv_sec - s_lastUsage.ru_utime.tv_sec ) +
				(double)(( currentUsage.ru_utime.tv_usec - s_lastUsage.ru_utime.tv_usec ) / 1000000); 
			m_fCPUPercent = flTimeDiff / ( m_fLastCPUCheckTime - s_lastAvg );

			// now save this away for next time
			if( m_fLastCPUCheckTime > s_lastAvg + 5) 
			{
				s_lastUsage = currentUsage;
				s_lastAvg = m_fLastCPUCheckTime;
			}
		}

		// limit checking :)
		if( m_fCPUPercent > 0.9999 )
			m_fCPUPercent = 0.9999;
		if( m_fCPUPercent < 0 )
			m_fCPUPercent = 0;
#else
#error
#endif
		m_fLastCPUCheckTime = curtime; 
	}
}


//-----------------------------------------------------------------------------
// Purpose: Prepare for level transition, etc.
//-----------------------------------------------------------------------------
void CBaseServer::InactivateClients( void )
{
	for (int i = 0; i < m_Clients.Count(); i++ )
	{
		CBaseClient	*cl = m_Clients[ i ];

		// Fake clients get killed in here.
#if defined( REPLAY_ENABLED )
		if ( cl->IsFakeClient() && !cl->IsHLTV() && !cl->IsReplay() )
#else
		if ( cl->IsFakeClient() && !cl->IsHLTV() )
#endif
		{
			// If we don't do this, it'll have a bunch of extra steam IDs for unauthenticated users.
			Steam3Server().NotifyClientDisconnect( cl );
			cl->Clear();
			continue;
		}
		else if ( !cl->IsConnected() )
		{
			continue;
		}

		cl->Inactivate();
	}
}

void CBaseServer::ReconnectClients( void )
{
	for (int i=0 ; i< m_Clients.Count() ; i++ )
	{
		CBaseClient *cl = m_Clients[i];
		
		if ( cl->IsConnected() )
		{
			cl->m_nSignonState = SIGNONSTATE_CONNECTED;
			NET_SignonState signon( cl->m_nSignonState, -1 );
			cl->SendNetMsg( signon );
		}
	}
}

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

If a packet has not been received from a client in sv_timeout.GetFloat()
seconds, drop the conneciton.

When a client is normally dropped, the CSVClient goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*/
void CBaseServer::CheckTimeouts (void)
{
	VPROF_BUDGET( "CBaseServer::CheckTimeouts", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	// Don't timeout in _DEBUG builds
	int i;

#if !defined( _DEBUG )
		
	for (i=0 ; i< m_Clients.Count() ; i++ )
	{
		IClient	*cl = m_Clients[ i ];
		
		if ( cl->IsFakeClient() || !cl->IsConnected() )
			continue;

		INetChannel *netchan = cl->GetNetChannel();

		if ( !netchan )
			continue;

	

		if ( netchan->IsTimedOut() )
		{
			cl->Disconnect( CLIENTNAME_TIMED_OUT, cl->GetClientName() );
		}
	}
#endif

	for (i=0 ; i< m_Clients.Count() ; i++ )
	{
		IClient	*cl = m_Clients[ i ];
		
		if ( cl->IsFakeClient() || !cl->IsConnected() )
			continue;
		
		if ( cl->GetNetChannel() && cl->GetNetChannel()->IsOverflowed() )
		{
			cl->Disconnect( "Client %d overflowed reliable channel.", i );
		}
	}
}

// ==================
// check if clients update thier user setting (convars) and call 
// ==================
void CBaseServer::UpdateUserSettings(void)
{
	VPROF_BUDGET( "CBaseServer::UpdateUserSettings", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	for (int i=0 ; i< m_Clients.Count() ; i++ )
	{
		CBaseClient	*cl = m_Clients[ i ];

		cl->CheckFlushNameChange();

		if ( cl->m_bConVarsChanged )
		{
			cl->UpdateUserSettings();
		}
	}
}

// ==================
// check if clients need the serverinfo packet sent
// ==================
void CBaseServer::SendPendingServerInfo()
{
	VPROF_BUDGET( "CBaseServer::SendPendingServerInfo", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	for (int i=0 ; i< m_Clients.Count() ; i++ )
	{
		CBaseClient	*cl = m_Clients[ i ];

		if ( cl->m_bSendServerInfo )
		{
			cl->SendServerInfo();
		}
	}
}

// compresses a packed entity, returns data & bits
const char *CBaseServer::CompressPackedEntity(ServerClass *pServerClass, const char *data, int &bits)
{
	ALIGN4 static char s_packedData[MAX_PACKEDENTITY_DATA] ALIGN4_POST;

	bf_write writeBuf( "CompressPackedEntity", s_packedData, sizeof( s_packedData ) );

	const void *pBaselineData = NULL;
	int nBaselineBits = 0;

	Assert( pServerClass != NULL );
		
	GetClassBaseline( pServerClass, &pBaselineData, &nBaselineBits );
	nBaselineBits *= 8;

	Assert( pBaselineData != NULL );

	SendTable_WriteAllDeltaProps(
		pServerClass->m_pTable,
		pBaselineData,
		nBaselineBits,
		data,
		bits,
		-1,
		&writeBuf );
	
	//overwrite in bits with out bits
	bits = writeBuf.GetNumBitsWritten();

	return s_packedData;
}

// uncompresses a 
const char* CBaseServer::UncompressPackedEntity(PackedEntity *pPackedEntity, int &bits)
{
	UnpackedDataCache_t *pdc = framesnapshotmanager->GetCachedUncompressedEntity( pPackedEntity );

	if ( pdc->bits > 0 )
	{
		// found valid uncompressed version in cache
		bits= pdc->bits;
		return pdc->data;
	}

	// not in cache, so uncompress it

	const void *pBaseline;
	int nBaselineBytes = 0;

	GetClassBaseline( pPackedEntity->m_pServerClass, &pBaseline, &nBaselineBytes );

	Assert( pBaseline != NULL );

	// store this baseline in u.m_pUpdateBaselines
	bf_read oldBuf( "UncompressPackedEntity1", pBaseline, nBaselineBytes );
	bf_read newBuf( "UncompressPackedEntity2", pPackedEntity->GetData(), Bits2Bytes(pPackedEntity->GetNumBits()) );
	bf_write outBuf( "UncompressPackedEntity3", pdc->data, MAX_PACKEDENTITY_DATA );

	Assert( pPackedEntity->m_pClientClass );

	RecvTable_MergeDeltas( 
		pPackedEntity->m_pClientClass->m_pRecvTable,
		&oldBuf,
		&newBuf,
		&outBuf );

	bits = pdc->bits = outBuf.GetNumBitsWritten();
		
	return pdc->data;
}

/*
================
SV_CheckProtocol

Make sure connecting client is using proper protocol
================
*/
bool CBaseServer::CheckProtocol( netadr_t &adr, int nProtocol, int clientChallenge )
{
	if ( nProtocol != PROTOCOL_VERSION )
	{
		// Client is newer than server
		if ( nProtocol > PROTOCOL_VERSION )
		{
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectOldProtocol" );
		}
		else
		// Server is newer than client
		{
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectNewProtocol" );
		}
		return false;
	}

	// Success
	return true;
}

/*
================
SV_CheckKeyInfo

Determine if client is outside appropriate address range
================
*/
bool CBaseServer::CheckChallengeType( CBaseClient * client, int nNewUserID, netadr_t & adr, int nAuthProtocol, const char *pchLogonCookie, int cbCookie, int clientChallenge )
{
	if ( AllowDebugDedicatedServerOutsideSteam() )
		return true;

	// Check protocol ID
	if ( ( nAuthProtocol <= 0 ) || ( nAuthProtocol > PROTOCOL_LASTVALID ) )
	{
		RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectInvalidConnection");
		return false;
	}

	if ( ( nAuthProtocol == PROTOCOL_HASHEDCDKEY ) && (Q_strlen( pchLogonCookie ) <= 0 ||  Q_strlen(pchLogonCookie) != 32 ) )
	{
		RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectInvalidCertLen" );
		return false;
	}

	Assert( !IsReplay() );

	if ( IsHLTV() )
	{
		// Don't authenticate spectators or add them to the
		// player list in the singleton Steam3Server()
		Assert( nAuthProtocol == PROTOCOL_HASHEDCDKEY );
		Assert( !client->m_SteamID.IsValid() );
	}
	else if ( nAuthProtocol == PROTOCOL_STEAM )
	{
		// Dev hack to allow 360/Steam PC cross platform play
// 		int ip0 = 207;
// 		int ip1 = 173;
// 		int ip2 = 179;
// 		int ip3Min = 230;
// 		int ip3Max = 245;
// 
// 		if ( adr.ip[0] == ip0 &&
// 			adr.ip[1] == ip1 &&
// 			adr.ip[2] == ip2 &&
// 			adr.ip[3] >= ip3Min &&
// 			adr.ip[3] <= ip3Max )
// 		{
// 			return true;
// 		}

		client->SetSteamID( CSteamID() ); // set an invalid SteamID

		// Convert raw certificate back into data
		if ( cbCookie <= 0 || cbCookie >= STEAM_KEYSIZE )
		{
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectInvalidSteamCertLen" );
			return false;
		}
		netadr_t checkAdr = adr;
		if ( adr.GetType() == NA_LOOPBACK || adr.IsLocalhost() )
		{
			checkAdr.SetIP( net_local_adr.GetIPHostByteOrder() );
		}

		if ( !Steam3Server().NotifyClientConnect( client, nNewUserID, checkAdr, pchLogonCookie, cbCookie ) 
			&& !Steam3Server().BLanOnly() ) // the userID isn't alloc'd yet so we need to fill it in manually
		{
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectSteam" );
			return false;
		}

		//
		// Any rejections below this must call SendUserDisconnect
		//

		// Now that we have auth'd with steam, client->GetSteamID() is now valid and we can verify against the GC lobby
		bool bHasGCLobby = g_iServerGameDLLVersion >= 8 && serverGameDLL->GetServerGCLobby();
		if ( bHasGCLobby )
		{
			if ( !serverGameDLL->GetServerGCLobby()->SteamIDAllowedToConnect( client->m_SteamID ) )
			{
				ISteamGameServer *pSteamGameServer = Steam3Server().SteamGameServer();
				if ( pSteamGameServer )
					pSteamGameServer->SendUserDisconnect( client->m_SteamID);

				RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectMustUseMatchmaking" );
				return false;
			}
		}
	}
	else
	{
		if ( !Steam3Server().NotifyLocalClientConnect( client ) ) // the userID isn't alloc'd yet so we need to fill it in manually
		{
			RejectConnection( adr, clientChallenge, "#GameUI_ServerRejectGS" );
			return false;
		}
	}

	return true;
}

bool CBaseServer::CheckIPRestrictions( const netadr_t &adr, int nAuthProtocol )
{
	// Determine if client is outside appropriate address range
	if ( adr.IsLoopback() )
		return true;

	// X360TBD: network
	if ( IsX360() )
		return true;

	// allow other users if they're on the same ip range
	if ( Steam3Server().BLanOnly() )
	{
		// allow connection, if client is in the same subnet 
		if ( adr.CompareClassBAdr( net_local_adr ) )
			return true;

		// allow connection, if client has a private IP
		if ( adr.IsReservedAdr() )
			return true;
		
		// reject connection
		return false;
	}

	return true;
}

void CBaseServer::SetMasterServerRulesDirty()
{
	m_bMasterServerRulesDirty = true;
}

bool CBaseServer::CheckPassword( netadr_t &adr, const char *password, const char *name )
{
	const char *server_password = GetPassword();

	if ( !server_password )
		return true;	// no password set

	if ( adr.IsLocalhost() || adr.IsLoopback() )
	{
		return true; // local client can always connect
	}

	int iServerPassLen = Q_strlen(server_password);

	if ( iServerPassLen != Q_strlen(password) )
	{
		return false; // different length cannot be equal
	}

	if ( Q_strncmp( password, server_password, iServerPassLen ) == 0)
	{
		return true; // passwords are equal
	}

	return false; // all test failed
}

float CBaseServer::GetTime() const
{
	return m_nTickCount * m_flTickInterval;
}

float CBaseServer::GetFinalTickTime() const
{
	return (m_nTickCount + (host_frameticks - host_currentframetick)) * m_flTickInterval;
}

void CBaseServer::DisconnectClient(IClient *client, const char *reason )
{
	client->Disconnect( reason );
}

void CBaseServer::Clear( void )
{
	if ( m_StringTables )
	{
		m_StringTables->RemoveAllTables();
		m_StringTables = NULL;
	}

	m_pInstanceBaselineTable = NULL;
	m_pLightStyleTable = NULL;
	m_pUserInfoTable = NULL;
	m_pServerStartupTable = NULL;

	m_State = ss_dead;
	
	m_nTickCount = 0;
	
	Q_memset( m_szMapname, 0, sizeof( m_szMapname ) );
	Q_memset( m_szSkyname, 0, sizeof( m_szSkyname ) );

	V_memset( worldmapMD5.bits, 0, MD5_DIGEST_LENGTH );

	MEM_ALLOC_CREDIT();

	// Use a different limit on the signon buffer, so we can save some memory in SP (for xbox).
	if ( IsMultiplayer() || IsDedicated() )
	{
		m_SignonBuffer.EnsureCapacity( NET_MAX_PAYLOAD );
	}
	else
	{
		m_SignonBuffer.EnsureCapacity( 16384 );
	}
	
	m_Signon.StartWriting( m_SignonBuffer.Base(), m_SignonBuffer.Count() );
	m_Signon.SetDebugName( "m_Signon" );

	serverclasses = 0;
	serverclassbits = 0;

	m_LastRandomNonce = m_CurrentRandomNonce = 0;
	m_flPausedTimeEnd = -1.f;
}

/*
================
SV_RejectConnection

Rejects connection request and sends back a message
================
*/
void CBaseServer::RejectConnection( const netadr_t &adr, int clientChallenge, const char *s )
{
	ALIGN4 char		msg_buffer[MAX_ROUTABLE_PAYLOAD] ALIGN4_POST;
	bf_write	msg( msg_buffer, sizeof(msg_buffer) );

	msg.WriteLong( CONNECTIONLESS_HEADER );
	msg.WriteByte( S2C_CONNREJECT );
	msg.WriteLong( clientChallenge );
	msg.WriteString( s );

	NET_SendPacket ( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
}

void CBaseServer::SetPaused(bool paused)
{
	if ( !IsPausable() )
	{
		return;
	}

	if ( !IsActive() )
		return;

	if ( paused )
	{
		m_State = ss_paused;
	}
	else
	{
		m_State = ss_active;
	}

	SVC_SetPause setpause( paused );
	BroadcastMessage( setpause );
}

//-----------------------------------------------------------------------------
// Purpose: General initialization of the server
//-----------------------------------------------------------------------------
void CBaseServer::Init (bool bIsDedicated)
{
	m_nMaxclients = 0;
	m_nSpawnCount = 0;
	m_nUserid = 1;
	m_nNumConnections = 0;
	m_bIsDedicated = bIsDedicated;
	m_Socket = NS_SERVER;	
	
	m_Signon.SetDebugName( "m_Signon" );
	
	g_pCVar->InstallGlobalChangeCallback( ServerNotifyVarChangeCallback );
	SetMasterServerRulesDirty();
	
	Clear();
}

INetworkStringTable *CBaseServer::GetInstanceBaselineTable( void )
{
	if ( m_pInstanceBaselineTable == NULL )
	{
		m_pInstanceBaselineTable = m_StringTables->FindTable( INSTANCE_BASELINE_TABLENAME );
	}

	return m_pInstanceBaselineTable;
}

INetworkStringTable *CBaseServer::GetLightStyleTable( void )
{
	if ( m_pLightStyleTable == NULL )
	{
		m_pLightStyleTable= m_StringTables->FindTable( LIGHT_STYLES_TABLENAME );
	}

	return m_pLightStyleTable;
}

INetworkStringTable *CBaseServer::GetUserInfoTable( void )
{
	if ( m_pUserInfoTable == NULL )
	{
		if ( m_StringTables == NULL )
		{
			return NULL;
		}
		m_pUserInfoTable = m_StringTables->FindTable( USER_INFO_TABLENAME );
	}

	return m_pUserInfoTable;
}

bool CBaseServer::GetClassBaseline( ServerClass *pClass, void const **pData, int *pDatalen )
{
	if ( sv_instancebaselines.GetInt() )
	{
		ErrorIfNot( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX,
			("SV_GetInstanceBaseline: missing instance baseline for class '%s'", pClass->m_pNetworkName)
		);
		
		AUTO_LOCK( g_svInstanceBaselineMutex );
		*pData = GetInstanceBaselineTable()->GetStringUserData(
			pClass->m_InstanceBaselineIndex,
			pDatalen );
		
		return *pData != NULL;
	}
	else
	{
		static char dummy[1] = {0};
		*pData = dummy;
		*pDatalen = 1;
		return true;
	}
}


bool CBaseServer::ShouldUpdateMasterServer()
{
	// If the game server itself is ever running, then it's the one who gets to update the master server.
	// (SourceTV will not update it in this case).
	return true;
}


void CBaseServer::CheckMasterServerRequestRestart()
{
	if ( !Steam3Server().SteamGameServer() || !Steam3Server().SteamGameServer()->WasRestartRequested() )
		return;
	
	// Connection was rejected by the HLMaster (out of date version)

	// hack, vgui console looks for this string; 
	Msg("%cMasterRequestRestart\n", 3);

#ifndef _WIN32
	if (CommandLine()->FindParm(AUTO_RESTART))
	{
		Msg("Your server will be restarted on map change.\n");
		Log("Your server will be restarted on map change.\n");
		SetRestartOnLevelChange( true );
	}
#endif
	
	if ( sv.IsDedicated() ) // under linux assume steam
	{
		Msg("Your server needs to be restarted in order to receive the latest update.\n");
		Log("Your server needs to be restarted in order to receive the latest update.\n");
	}
	else
	{
		Msg("Your server is out of date.  Please update and restart.\n");
	}
}


void CBaseServer::UpdateMasterServer()
{
	VPROF_BUDGET( "CBaseServer::UpdateMasterServer", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	if ( !ShouldUpdateMasterServer() )
		return;

	if ( !Steam3Server().SteamGameServer() )
		return;
	
	// Only update every so often.
	double flCurTime = Plat_FloatTime();
	if ( flCurTime - m_flLastMasterServerUpdateTime < MASTER_SERVER_UPDATE_INTERVAL )
		return;

	m_flLastMasterServerUpdateTime = flCurTime;


	ForwardPacketsFromMasterServerUpdater();
	CheckMasterServerRequestRestart();
	

	if ( NET_IsDedicated() && sv_region.GetInt() == -1 )
    {
		sv_region.SetValue( 255 ); // HACK!HACK! undo me once we want to enforce regions

        //Log_Printf( "You must set sv_region in your server.cfg or use +sv_region on the command line\n" );
		//Con_Printf( "You must set sv_region in your server.cfg or use +sv_region on the command line\n" );
        //Cbuf_AddText( "quit\n" );
        //return;
    }

	static bool bUpdateMasterServers = !CommandLine()->FindParm( "-nomaster" );
	if ( !bUpdateMasterServers )
		return;

	bool bActive = IsActive() && IsMultiplayer();
	if ( serverGameDLL && serverGameDLL->ShouldHideServer() )
		bActive = false;
	
	Steam3Server().SteamGameServer()->EnableHeartbeats( bActive );

	if ( !bActive )
		return;

	UpdateMasterServerRules();
	UpdateMasterServerPlayers();
	Steam3Server().SendUpdatedServerDetails();
}


void CBaseServer::UpdateMasterServerRules()
{
	// Only do this if the rules vars are dirty.
	if ( !m_bMasterServerRulesDirty )
		return;

	ISteamGameServer *pUpdater = Steam3Server().SteamGameServer();
	if ( !pUpdater )
		return;

	pUpdater->ClearAllKeyValues();
	
	// Need to respond with game directory, game name, and any server variables that have been set that
	//  effect rules.  Also, probably need a hook into the .dll to respond with additional rule information.
	ConCommandBase *var;
	for ( var = g_pCVar->GetCommands() ; var ; var=var->GetNext() )
	{
		if ( !(var->IsFlagSet( FCVAR_NOTIFY ) ) )
			continue;
		if ( var->IsCommand() )
			continue;

		ConVar *pConVar = static_cast< ConVar* >( var );
		if ( !pConVar )
			continue;

		SetMasterServerKeyValue( pUpdater, pConVar );
	}

	if (  Steam3Server().SteamGameServer() )
	{
		RecalculateTags();
	}

	// Ok.. it's all updated, only send incremental updates now until we decide they're all dirty.
	m_bMasterServerRulesDirty = false;
}


void CBaseServer::ForwardPacketsFromMasterServerUpdater()
{
	ISteamGameServer *p = Steam3Server().SteamGameServer();
	if ( !p )
		return;
	
	while ( 1 )
	{
		uint32 netadrAddress;
		uint16 netadrPort;
		unsigned char packetData[16 * 1024];
 		int len = p->GetNextOutgoingPacket( packetData, sizeof( packetData ), &netadrAddress, &netadrPort );
		if ( len <= 0 )
			break;
		
		// Send this packet for them..
		netadr_t adr( netadrAddress, netadrPort );
		NET_SendPacket( NULL, m_Socket, adr, packetData, len );
	}
}


/*
=================
SV_ReadPackets

Read's packets from clients and executes messages as appropriate.
=================
*/

void CBaseServer::RunFrame( void )
{
	VPROF_BUDGET( "CBaseServer::RunFrame", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "CBaseServer::RunFrame" );

	NET_ProcessSocket( m_Socket, this );	

#ifdef LINUX
	// Process the linux sv lan port if it's open.
	if ( NET_GetUDPPort( NS_SVLAN ) )
		NET_ProcessSocket( NS_SVLAN, this );
#endif

	CheckTimeouts();	// drop clients that timeed out

	UpdateUserSettings(); // update client settings 
	
	SendPendingServerInfo(); // send outstanding signon packets after ALL user settings have been updated

	CalculateCPUUsage(); // update CPU usage

	UpdateMasterServer();

	if ( m_flLastRandomNumberGenerationTime < 0 || (m_flLastRandomNumberGenerationTime + CHALLENGE_NONCE_LIFETIME) < g_ServerGlobalVariables.realtime  )
	{
		m_LastRandomNonce = m_CurrentRandomNonce;

		// RandomInt maps a uniform distribution on the interval [0,INT_MAX], so make two calls to get the random number.
		// RandomInt will always return the minimum value if the difference in min and max is greater than or equal to INT_MAX.
		m_CurrentRandomNonce = ( ( (uint32)RandomInt( 0, 0xFFFF ) ) << 16 ) | RandomInt( 0, 0xFFFF );
		m_flLastRandomNumberGenerationTime = g_ServerGlobalVariables.realtime;
	}

	// Timed pause - resume game when time expires
	if ( m_flPausedTimeEnd >= 0.f && m_State == ss_paused && Sys_FloatTime() >= m_flPausedTimeEnd )
	{
		SetPausedForced( false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *adr - 
//			*pslot - 
//			**ppClient - 
// Output : int
//-----------------------------------------------------------------------------
CBaseClient * CBaseServer::GetFreeClient( netadr_t &adr )
{
	CBaseClient *freeclient = NULL;
	
	for ( int slot = 0 ; slot < m_Clients.Count() ; slot++ )
	{
		CBaseClient *client = m_Clients[slot];

		if ( client->IsFakeClient() )
			continue;

		if ( client->IsConnected() )
		{
			if ( adr.CompareAdr ( client->m_NetChannel->GetRemoteAddress() ) )
			{
				ConMsg ( "%s:reconnect\n", adr.ToString() );

				RemoveClientFromGame( client );

				// perform a silent netchannel shutdown, don't send disconnect msg
				client->m_NetChannel->Shutdown( NULL );
				client->m_NetChannel = NULL;
		
				client->Clear();
				return client;
			}
		}
		else
		{
			// use first found free slot 
			if ( !freeclient )
			{
				freeclient = client; 
			}
		}
	}

	if ( !freeclient )
	{
		int count = m_Clients.Count();

		if ( count >= m_nMaxclients )
		{
			return NULL; // server full
		}

		// we have to create a new client slot
		freeclient = CreateNewClient( count );
		
		m_Clients.AddToTail( freeclient );
	}
	
	// Success
	return freeclient;
}

void CBaseServer::SendClientMessages ( bool bSendSnapshots )
{
	VPROF_BUDGET( "SendClientMessages", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	
	for (int i=0; i< m_Clients.Count(); i++ )
	{
		CBaseClient* client = m_Clients[i];
		
		// Update Host client send state...
		if ( !client->ShouldSendMessages() )
			continue;

		// Connected, but inactive, just send reliable, sequenced info.
		if ( client->m_NetChannel )
		{
			client->m_NetChannel->Transmit();
			client->UpdateSendState();
		}
		else
		{
			Msg("Client has no netchannel.\n");
		}
	}
}

CBaseClient *CBaseServer::CreateFakeClient( const char *name )
{
	netadr_t adr; // it's an empty address

	CBaseClient *fakeclient = GetFreeClient( adr );
				
	if ( !fakeclient )
	{
		// server is full
		return NULL;		
	}

	INetChannel *netchan = NULL;
	if ( sv_stressbots.GetBool() )
	{
		netadr_t adrNull( 0, 0 ); // 0.0.0.0:0 signifies a bot. It'll plumb all the way down to winsock calls but it won't make them.
		netchan = NET_CreateNetChannel( m_Socket, &adrNull, adrNull.ToString(), fakeclient, true );
	}

	// a NULL netchannel signals a fakeclient
	m_nUserid = GetNextUserID();
	m_nNumConnections++;

	fakeclient->SetReportThisFakeClient( m_bReportNewFakeClients );
	fakeclient->Connect( name, m_nUserid, netchan, true, 0 );

	// fake some cvar settings
	//fakeclient->SetUserCVar( "name", name ); // set already by Connect()
	fakeclient->SetUserCVar( "rate", "30000" );
	fakeclient->SetUserCVar( "cl_updaterate", "20" );
	fakeclient->SetUserCVar( "cl_interp_ratio", "1.0" );
	fakeclient->SetUserCVar( "cl_interp", "0.1" );
	fakeclient->SetUserCVar( "cl_interpolate", "0" );
	fakeclient->SetUserCVar( "cl_predict", "1" );
	fakeclient->SetUserCVar( "cl_predictweapons", "1" );
	fakeclient->SetUserCVar( "cl_lagcompensation", "1" );
	fakeclient->SetUserCVar( "closecaption","0" );
	fakeclient->SetUserCVar( "english", "1" );

	fakeclient->SetUserCVar( "cl_clanid", "0" );
	fakeclient->SetUserCVar( "cl_team", "blue" );
	fakeclient->SetUserCVar( "hud_classautokill", "1" );
	fakeclient->SetUserCVar( "tf_medigun_autoheal", "0" );
	fakeclient->SetUserCVar( "cl_autorezoom", "1" );
	fakeclient->SetUserCVar( "fov_desired", "75" );
	fakeclient->SetUserCVar( "tf_remember_lastswitched", "0" );

	fakeclient->SetUserCVar( "cl_autoreload", "0" );
	fakeclient->SetUserCVar( "tf_remember_activeweapon", "0" );
	fakeclient->SetUserCVar( "hud_combattext", "0" );
	fakeclient->SetUserCVar( "cl_flipviewmodels", "0" );

	// create client in game.dll
	fakeclient->ActivatePlayer();

	fakeclient->m_nSignonTick = m_nTickCount;
			
	return fakeclient;
}

void CBaseServer::Shutdown( void )
{
	if ( !IsActive() )
		return;

	m_State = ss_dead;

	// Only drop clients if we have not cleared out entity data prior to this.
	for(  int i=m_Clients.Count()-1; i>=0; i-- )
	{
		CBaseClient * cl = m_Clients[ i ];
		if ( cl->IsConnected() )
		{
			cl->Disconnect( "Server shutting down" );
		}
		else
		{
			// free any memory do this out side here in case the reason the server is shutting down 
			// is because the listen server client typed disconnect, in which case we won't call
			// cl->DropClient, but the client might have some frame snapshot references left over, etc.
			cl->Clear();	
		}

		delete cl;

		m_Clients.Remove( i );
	}

	// Let drop messages go out
	Sys_Sleep( 100 );

	// clear everything
	Clear();
}

//-----------------------------------------------------------------------------
// Purpose: Sends text to all active clients
// Input  : *fmt -
//			... -
//-----------------------------------------------------------------------------
void CBaseServer::BroadcastPrintf (const char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];

	va_start (argptr,fmt);
	Q_vsnprintf (string, sizeof( string ), fmt,argptr);
	va_end (argptr);

	SVC_Print print( string );
	BroadcastMessage( print );	
}

void CBaseServer::BroadcastMessage( INetMessage &msg, bool onlyActive, bool reliable )
{
	for ( int i = 0; i < m_Clients.Count(); i++ )
	{
		CBaseClient *cl = m_Clients[ i ];

		if ( (onlyActive && !cl->IsActive()) || !cl->IsSpawned() )
		{
			continue;
		}

		if ( !cl->SendNetMsg( msg, reliable ) )
		{
			if ( msg.IsReliable() || reliable )
			{
				DevMsg( "BroadcastMessage: Reliable broadcast message overflow for client %s", cl->GetClientName() );
			}
		}
	}
}

void CBaseServer::BroadcastMessage( INetMessage &msg, IRecipientFilter &filter )
{
	if ( filter.IsInitMessage() )
	{
		// This really only applies to the first player to connect, but that works in single player well enought
		if ( IsActive() )
		{
			ConDMsg( "SV_BroadcastMessage: Init message being created after signon buffer has been transmitted\n" );
		}
		
		if ( !msg.WriteToBuffer( m_Signon ) )
		{
			Sys_Error( "SV_BroadcastMessage: Init message would overflow signon buffer!\n" );
			return;
		}
	}
	else
	{
		msg.SetReliable( filter.IsReliable() );

		int num = filter.GetRecipientCount();
	
		for ( int i = 0; i < num; i++ )
		{
			int index = filter.GetRecipientIndex( i );

			if ( index < 1 || index > m_Clients.Count() )
			{
				Msg( "SV_BroadcastMessage:  Recipient Filter for message type %i (reliable: %s, init: %s) with bogus client index (%i) in list of %i clients\n", 
						msg.GetType(), 
						filter.IsReliable() ? "yes" : "no",
						filter.IsInitMessage() ? "yes" : "no",
						index, num );

				if ( msg.IsReliable() )
					Host_Error( "Reliable message (type %i) discarded.", msg.GetType() );

				continue;
			}

			CBaseClient *cl = m_Clients[ index - 1 ];

			if ( !cl->IsSpawned() )
			{
				continue;
			}

			if ( !cl->SendNetMsg( msg ) )
			{
				if ( msg.IsReliable() )
				{
					DevMsg( "BroadcastMessage: Reliable filter message overflow for client %s", cl->GetClientName() );
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Writes events to the client's network buffer
// Input  : *cl -
//			*pack -
//			*msg -
//-----------------------------------------------------------------------------
static ConVar sv_debugtempentities( "sv_debugtempentities", "0", 0, "Show temp entity bandwidth usage." );

static bool CEventInfo_LessFunc( CEventInfo * const &lhs, CEventInfo * const &rhs )
{
	return lhs->classID < rhs->classID;
}

void CBaseServer::WriteTempEntities( CBaseClient *client, CFrameSnapshot *pCurrentSnapshot, CFrameSnapshot *pLastSnapshot, bf_write &buf, int ev_max )
{
	VPROF_BUDGET( "CBaseServer::WriteTempEntities", VPROF_BUDGETGROUP_OTHER_NETWORKING );

	ALIGN4 char data[NET_MAX_PAYLOAD] ALIGN4_POST;
	SVC_TempEntities msg;
	msg.m_DataOut.StartWriting( data, sizeof(data) );
	bf_write &buffer = msg.m_DataOut; // shortcut
	
	CFrameSnapshot *pSnapshot;
	CEventInfo *pLastEvent = NULL;

	bool bDebug = sv_debugtempentities.GetBool();

	// limit max entities to field bit length
	ev_max = min( ev_max, ((1<<CEventInfo::EVENT_INDEX_BITS)-1) );

	if ( pLastSnapshot )
	{
		pSnapshot = pLastSnapshot->NextSnapshot();
	} 
	else
	{
		pSnapshot = pCurrentSnapshot;
	}

	CUtlRBTree< CEventInfo * >	sorted( 0, ev_max, CEventInfo_LessFunc );

	// Build list of events sorted by send table classID (makes the delta work better in cases with a lot of the same message type )
	while ( pSnapshot && ((int)sorted.Count() < ev_max) )
	{
		for( int i = 0; i < pSnapshot->m_nTempEntities; ++i )
		{
			CEventInfo *event = pSnapshot->m_pTempEntities[ i ];

			if ( client->IgnoreTempEntity( event ) )
				continue; // event is not seen by this player
			
			sorted.Insert( event );
			// More space still
			if ( (int)sorted.Count() >= ev_max )
				break;	
		}

		// stop, we reached our current snapshot
		if ( pSnapshot == pCurrentSnapshot )
			break; 

		// got to next snapshot
		pSnapshot = framesnapshotmanager->NextSnapshot( pSnapshot );
	}

	if ( sorted.Count() <= 0 )
		return;

	for ( int i = sorted.FirstInorder(); 
		i != sorted.InvalidIndex(); 
		i = sorted.NextInorder( i ) )
	{
		CEventInfo *event = sorted[ i ];

		if ( event->fire_delay == 0.0f )
		{
			buffer.WriteOneBit( 0 );
		} 
		else
		{
			buffer.WriteOneBit( 1 );
			buffer.WriteSBitLong( event->fire_delay*100.0f, 8 );
		}

		if ( pLastEvent && 
			pLastEvent->classID == event->classID )
		{
			buffer.WriteOneBit( 0 ); // delta against last temp entity

			int startBit = bDebug ? buffer.GetNumBitsWritten() : 0;

			SendTable_WriteAllDeltaProps( event->pSendTable, 
				pLastEvent->pData,
				pLastEvent->bits,
				event->pData,
				event->bits,
				-1,
				&buffer );

			if ( bDebug )
			{
				int length = buffer.GetNumBitsWritten() - startBit;
				DevMsg("TE %s delta bits: %i\n", event->pSendTable->GetName(), length );
			}
		}
		else
		{
			 // full update, just compressed against zeros in MP

			buffer.WriteOneBit( 1 );

			int startBit = bDebug ? buffer.GetNumBitsWritten() : 0;

			buffer.WriteUBitLong( event->classID, GetClassBits() );

			if ( IsMultiplayer() )
			{
				SendTable_WriteAllDeltaProps( event->pSendTable, 
					NULL,	// will write only non-zero elements
					0,
					event->pData,
					event->bits,
					-1,
					&buffer );
			}
			else
			{
				// write event with zero properties
				buffer.WriteBits( event->pData, event->bits );
			}

			if ( bDebug )
			{
				int length = buffer.GetNumBitsWritten() - startBit;
				DevMsg("TE %s full bits: %i\n", event->pSendTable->GetName(), length );
			}
		}

		if ( IsMultiplayer() )
		{
			// in single player, don't used delta compression, lastEvent remains NULL
			pLastEvent = event;
		}
	}

	// set num entries
	msg.m_nNumEntries = sorted.Count();
	msg.WriteToBuffer( buf );
}

void CBaseServer::SetMaxClients( int number )
{
	m_nMaxclients = clamp( number, 1, ABSOLUTE_PLAYER_LIMIT );
}

extern ConVar tv_enable;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseServer::RecalculateTags( void )
{
	if ( IsHLTV() || IsReplay() )
		return;

	// We're going to modify the sv_tags convar here, which will cause this to be called again. Prevent recursion.
	static bool bRecalculatingTags = false;
	if ( bRecalculatingTags )
		return;

	bRecalculatingTags = true;

	// Games without this interface will have no tagged cvars besides "increased_maxplayers"
	if ( serverGameTags )
	{
		KeyValues *pKV = new KeyValues( "GameTags" );

		serverGameTags->GetTaggedConVarList( pKV );

		KeyValues *p = pKV->GetFirstSubKey();
		while ( p )
		{
			ConVar *pConVar = g_pCVar->FindVar( p->GetString("convar") );
			if ( pConVar )
			{
				const char *pszDef = pConVar->GetDefault();
				const char *pszCur = pConVar->GetString();
				if ( Q_strcmp( pszDef, pszCur ) )
				{
					AddTag( p->GetString("tag") );
				}
				else
				{
					RemoveTag( p->GetString("tag") );
				}
			}

			p = p->GetNextKey();
		}

		pKV->deleteThis();
	}

	// Check maxplayers
	int minmaxplayers = 1;
	int maxmaxplayers = ABSOLUTE_PLAYER_LIMIT;
	int defaultmaxplayers = 1;
	serverGameClients->GetPlayerLimits( minmaxplayers, maxmaxplayers, defaultmaxplayers );
	int nMaxReportedClients = GetMaxClients() - GetNumProxies();
	if ( sv_visiblemaxplayers.GetInt() > 0 && sv_visiblemaxplayers.GetInt() < nMaxReportedClients )
	{
		nMaxReportedClients = sv_visiblemaxplayers.GetInt();
	}
	if ( nMaxReportedClients > defaultmaxplayers )
	{
		AddTag( "increased_maxplayers" );
	}
	else
	{
		RemoveTag( "increased_maxplayers" );
	}

#if defined( REPLAY_ENABLED )
	ConVarRef replay_enable( "replay_enable", true );
	if ( replay_enable.IsValid() && replay_enable.GetBool() )
	{
		AddTag( "replays" );
	}
	else
	{
		RemoveTag( "replays" );
	}
#endif

	bRecalculatingTags = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseServer::AddTag( const char *pszTag )
{
	CUtlVector<char*> TagList;
	V_SplitString( sv_tags.GetString(), ",", TagList );
	for ( int i = 0; i < TagList.Count(); i++ )
	{
		// Already in the tag list?
		if ( !Q_stricmp(TagList[i],pszTag) )
			return;
	}
	TagList.PurgeAndDeleteElements();

	// Append it
	char tmptags[MAX_TAG_STRING_LENGTH];
	tmptags[0] = '\0';
	Q_strncpy( tmptags, pszTag, MAX_TAG_STRING_LENGTH );
	Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
	Q_strncat( tmptags, sv_tags.GetString(), MAX_TAG_STRING_LENGTH );
	sv_tags.SetValue( tmptags );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseServer::RemoveTag( const char *pszTag )
{
	const char *pszTags = sv_tags.GetString();
	if ( !pszTags || !pszTags[0] )
		return;

	char tmptags[MAX_TAG_STRING_LENGTH];
	tmptags[0] = '\0';

	CUtlVector<char*> TagList;
	bool bFoundIt = false;
	V_SplitString( sv_tags.GetString(), ",", TagList );
	for ( int i = 0; i < TagList.Count(); i++ )
	{
		// Keep any tags other than the specified one
		if ( Q_stricmp(TagList[i],pszTag) )
		{
			Q_strncat( tmptags, TagList[i], MAX_TAG_STRING_LENGTH );
			Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
		}
		else
		{
			bFoundIt = true;
		}
	}
	TagList.PurgeAndDeleteElements();

	// Didn't find it in our list?
	if ( !bFoundIt )
		return;

	sv_tags.SetValue( tmptags );
}

//-----------------------------------------------------------------------------
// Purpose: Server-only override (ignores sv_pausable).  Can be on a timer.
//-----------------------------------------------------------------------------
void CBaseServer::SetPausedForced( bool bPaused, float flDuration /*= -1.f*/ )
{
	if ( !IsActive() )
		return;

	m_State = ( bPaused ) ? ss_paused : ss_active;
	m_flPausedTimeEnd = ( bPaused && flDuration > 0.f ) ? Sys_FloatTime() + flDuration : -1.f;

	SVC_SetPauseTimed setpause( bPaused, m_flPausedTimeEnd );
	BroadcastMessage( setpause );
}