/*
sv_client.c - client interactions
Copyright (C) 2008 Uncle Mike

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 3 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.
*/

#include "common.h"
#include "const.h"
#include "server.h"
#include "net_encode.h"
#include "net_api.h"

const char *clc_strings[clc_lastmsg+1] =
{
	"clc_bad",
	"clc_nop",
	"clc_move",
	"clc_stringcmd",
	"clc_delta",
	"clc_resourcelist",
	"clc_unused6",
	"clc_fileconsistency",
	"clc_voicedata",
	"clc_cvarvalue",
	"clc_cvarvalue2",
};

typedef struct ucmd_s
{
	const char	*name;
	qboolean		(*func)( sv_client_t *cl );
} ucmd_t;

static int	g_userid = 1;

static void SV_UserinfoChanged( sv_client_t *cl );
static void SV_ExecuteClientCommand( sv_client_t *cl, const char *s );

/*
=================
SV_GetPlayerCount

=================
*/
void SV_GetPlayerCount( int *players, int *bots )
{
	int i;

	*players = 0;
	*bots = 0;

	if( !svs.clients )
		return;

	for( i = 0; i < svs.maxclients; i++ )
	{
		if( svs.clients[i].state >= cs_connected )
		{
			if( FBitSet( svs.clients[i].flags, FCL_FAKECLIENT ))
				(*bots)++;
			else
				(*players)++;
		}

	}
}

/*
=================
SV_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.
=================
*/
static void SV_GetChallenge( netadr_t from )
{
	int	i, oldest = 0;
	double	oldestTime;

	oldestTime = 0x7fffffff;

	// see if we already have a challenge for this ip
	for( i = 0; i < MAX_CHALLENGES; i++ )
	{
		if( !svs.challenges[i].connected && NET_CompareAdr( from, svs.challenges[i].adr ))
			break;

		if( svs.challenges[i].time < oldestTime )
		{
			oldestTime = svs.challenges[i].time;
			oldest = i;
		}
	}

	if( i == MAX_CHALLENGES )
	{
		// this is the first time this client has asked for a challenge
		svs.challenges[oldest].challenge = (COM_RandomLong( 0, 0x7FFF ) << 16) | COM_RandomLong( 0, 0xFFFF );
		svs.challenges[oldest].adr = from;
		svs.challenges[oldest].time = host.realtime;
		svs.challenges[oldest].connected = false;
		i = oldest;
	}

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

static int SV_GetFragmentSize( void *pcl, fragsize_t mode )
{
	sv_client_t *cl = (sv_client_t*)pcl;
	int	cl_frag_size;

	if( Netchan_IsLocal( &cl->netchan ))
		return FRAGMENT_LOCAL_SIZE;

	if( mode == FRAGSIZE_UNRELIABLE )
	{
		// allow setting unreliable limit with "setinfo cl_urmax"
		cl_frag_size = Q_atoi( Info_ValueForKey( cl->userinfo, "cl_urmax" ));
		if( cl_frag_size == 0 )
			return NET_MAX_MESSAGE;
		return bound( FRAGMENT_MAX_SIZE, cl_frag_size, NET_MAX_MESSAGE );
	}

	cl_frag_size = Q_atoi( Info_ValueForKey( cl->userinfo, "cl_dlmax" ));
	cl_frag_size = bound( FRAGMENT_MIN_SIZE, cl_frag_size, FRAGMENT_MAX_SIZE );

	if( mode != FRAGSIZE_FRAG )
	{
		if( cl->extensions & NET_EXT_SPLITSIZE )
			return cl_frag_size;
		else
			return 0; // original engine behaviour
	}

	// get in-game fragmentation size
	if( cl->state == cs_spawned )
	{
		// allow setting in-game fragsize with "setinfo cl_frmax"
		int frmax = Q_atoi( Info_ValueForKey( cl->userinfo, "cl_frmax" ));

		if( frmax < FRAGMENT_MIN_SIZE || frmax > FRAGMENT_MAX_SIZE )
			cl_frag_size /= 2; // add window for unreliable
		else
			cl_frag_size = frmax;
	}

	return cl_frag_size - HEADER_BYTES;
}

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

Rejects connection request and sends back a message
================
*/
void SV_RejectConnection( netadr_t from, const char *fmt, ... )
{
	char	text[1024];
	va_list	argptr;

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

	Con_Reportf( "%s connection refused. Reason: %s\n", NET_AdrToString( from ), text );
	Netchan_OutOfBandPrint( NS_SERVER, from, "errormsg\n^1Server was reject the connection:^7 %s", text );
	Netchan_OutOfBandPrint( NS_SERVER, from, "print\n^1Server was reject the connection:^7 %s", text );
	Netchan_OutOfBandPrint( NS_SERVER, from, "disconnect\n" );
}

/*
================
SV_FailDownload

for some reasons file can't be downloaded
tell the client about this problem
================
*/
static void SV_FailDownload( sv_client_t *cl, const char *filename )
{
	if( !COM_CheckString( filename ))
		return;

	MSG_BeginServerCmd( &cl->netchan.message, svc_filetxferfailed );
	MSG_WriteString( &cl->netchan.message, filename );
}

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

Make sure connecting client is not spoofing
================
*/
static int SV_CheckChallenge( netadr_t from, int challenge )
{
	int	i;

	// see if the challenge is valid
	// don't care if it is a local address.
	if( NET_IsLocalAddress( from ))
		return 1;

	for( i = 0; i < MAX_CHALLENGES; i++ )
	{
		if( NET_CompareAdr( from, svs.challenges[i].adr ))
		{
			if( challenge == svs.challenges[i].challenge )
				break; // valid challenge
#if 0
			// g-cont. this breaks multiple connections from single machine
			SV_RejectConnection( from, "bad challenge %i\n", challenge );
			return 0;
#endif
		}
	}

	if( i == MAX_CHALLENGES )
	{
		SV_RejectConnection( from, "no challenge for your address\n" );
		return 0;
	}
	svs.challenges[i].connected = true;

	return 1;
}

/*
================
SV_CheckIPRestrictions

Determine if client is outside appropriate address range
================
*/
static int SV_CheckIPRestrictions( netadr_t from )
{
	if( sv_lan.value )
	{
		if( !NET_CompareClassBAdr( from, net_local ) && !NET_IsReservedAdr( from ))
			return 0;
	}
	return 1;
}

/*
================
SV_FindEmptySlot

Get slot # and set client_t pointer for player, if possible
We don't do this search on a "reconnect, we just reuse the slot
================
*/
static int SV_FindEmptySlot( netadr_t from, int *pslot, sv_client_t **ppClient )
{
	sv_client_t	*cl;
	int		i;

	for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
	{
		if( cl->state == cs_free )
		{
			*ppClient = cl;
			*pslot = i;
			return 1;
		}
	}

	SV_RejectConnection( from, "server is full\n" );
	return 0;
}

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

A connection request that did not come from the master
==================
*/
static void SV_ConnectClient( netadr_t from )
{
	char		userinfo[MAX_INFO_STRING];
	char		protinfo[MAX_INFO_STRING];
	sv_client_t	*cl, *newcl = NULL;
	qboolean		reconnect = false;
	int		nClientSlot = 0;
	int		qport, version;
	int		i, count = 0;
	int		challenge;
	const char		*s;
	int extensions;

	if( Cmd_Argc() < 5 )
	{
		SV_RejectConnection( from, "insufficient connection info\n" );
		return;
	}

	version = Q_atoi( Cmd_Argv( 1 ));

	if( version != PROTOCOL_VERSION )
	{
		SV_RejectConnection( from, "unsupported protocol (%i should be %i)\n", version, PROTOCOL_VERSION );
		return;
	}

	challenge = Q_atoi( Cmd_Argv( 2 )); // get challenge

	// see if the challenge is valid (local clients don't need to challenge)
	if( !SV_CheckChallenge( from, challenge ))
		return;

	s = Cmd_Argv( 3 );	// protocol info

	if( !Info_IsValid( s ))
	{
		SV_RejectConnection( from, "invalid protinfo in connect command\n" );
		return;
	}

	Q_strncpy( protinfo, s, sizeof( protinfo ));

	if( !SV_ProcessUserAgent( from, protinfo ) )
	{
		return;
	}

	// extract qport from protocol info
	qport = Q_atoi( Info_ValueForKey( protinfo, "qport" ));

	s = Info_ValueForKey( protinfo, "uuid" );
	if( Q_strlen( s ) != 32 )
	{
		SV_RejectConnection( from, "invalid authentication certificate length\n" );
		return;
	}

	extensions = Q_atoi( Info_ValueForKey( protinfo, "ext" ) );

	// LAN servers restrict to class b IP addresses
	if( !SV_CheckIPRestrictions( from ))
	{
		SV_RejectConnection( from, "LAN servers are restricted to local clients (class C)\n" );
		return;
	}

	s = Cmd_Argv( 4 );	// user info

	if( Q_strlen( s ) > MAX_INFO_STRING || !Info_IsValid( s ))
	{
		SV_RejectConnection( from, "invalid userinfo in connect command\n" );
		return;
	}

	Q_strncpy( userinfo, s, sizeof( userinfo ));

	// check connection password (don't verify local client)
	if( !NET_IsLocalAddress( from ) && sv_password.string[0] && Q_stricmp( sv_password.string, Info_ValueForKey( userinfo, "password" )))
	{
		SV_RejectConnection( from, "invalid password\n" );
		return;
	}

	// if there is already a slot for this ip, reuse it
	for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
	{
		if( cl->state == cs_free || cl->state == cs_zombie )
			continue;

		if( NET_CompareBaseAdr( from, cl->netchan.remote_address ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remote_address.port ))
		{
			reconnect = true;
			newcl = cl;
			break;
		}
	}

	// A reconnecting client will re-use the slot found above when checking for reconnection.
	// the slot will be wiped clean.
	if( !reconnect )
	{
		// connect the client if there are empty slots.
		if( !SV_FindEmptySlot( from, &nClientSlot, &newcl ))
			return;
	}
	else
	{
		Con_Reportf( S_NOTE "%s:reconnect\n", NET_AdrToString( from ));
	}

	// find a client slot
	ASSERT( newcl != NULL );

	// build a new connection
	// accept the new client

	sv.current_client = newcl;
	newcl->edict = EDICT_NUM( (newcl - svs.clients) + 1 );
	newcl->challenge = challenge; // save challenge for checksumming
	if( newcl->frames ) Mem_Free( newcl->frames );
	newcl->frames = (client_frame_t *)Z_Calloc( sizeof( client_frame_t ) * SV_UPDATE_BACKUP );
	newcl->userid = g_userid++;	// create unique userid
	newcl->state = cs_connected;
	newcl->extensions = extensions & (NET_EXT_SPLITSIZE);
	Q_strncpy( newcl->useragent, protinfo, sizeof( newcl->useragent ));

	// reset viewentities (from previous level)
	memset( newcl->viewentity, 0, sizeof( newcl->viewentity ));
	newcl->num_viewents = 0;
	// HACKHACK: can hear all players by default to avoid issues
	// with server.dll without voice game manager
	newcl->listeners = -1;

	// initailize netchan
	Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize );
	MSG_Init( &newcl->datagram, "Datagram", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf

	Q_strncpy( newcl->hashedcdkey, Info_ValueForKey( protinfo, "uuid" ), 32 );
	newcl->hashedcdkey[32] = '\0';

	// build protinfo answer
	protinfo[0] = '\0';
	Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions );

	// send the connect packet to the client
	Netchan_OutOfBandPrint( NS_SERVER, from, "client_connect %s", protinfo );

	newcl->upstate = us_inactive;
	newcl->connection_started = host.realtime;
	newcl->cl_updaterate = 0.05;	// 20 fps as default
	newcl->delta_sequence = -1;
	newcl->flags = 0;



	// reset any remaining events
	memset( &newcl->events, 0, sizeof( newcl->events ));

	// parse some info from the info strings (this can override cl_updaterate)
	Q_strncpy( newcl->userinfo, userinfo, sizeof( newcl->userinfo ));
	newcl->fullupdate_next_calltime = 0;
	newcl->userinfo_next_changetime = 0;
	newcl->userinfo_penalty = 0;
	newcl->userinfo_change_attempts = 0;

	SV_UserinfoChanged( newcl );
	SV_ClearResourceLists( newcl );
#if 0
	memset( &newcl->resourcesneeded, 0, sizeof( resource_t ));
	memset( &newcl->resourcesonhand, 0, sizeof( resource_t ));
	newcl->resourcesneeded.pNext = newcl->resourcesneeded.pPrev = &newcl->resourcesneeded;
	newcl->resourcesonhand.pNext = newcl->resourcesonhand.pPrev = &newcl->resourcesonhand;
#endif
	newcl->next_messagetime = host.realtime + newcl->cl_updaterate;
	newcl->next_sendinfotime = 0.0;
	newcl->ignored_ents = 0;
	newcl->chokecount = 0;

	// reset stats
	newcl->next_checkpingtime = -1.0;
	newcl->packet_loss = 0.0f;

	// if this was the first client on the server, or the last client
	// the server can hold, send a heartbeat to the master.
	for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
		if( cl->state >= cs_connected ) count++;

	Log_Printf( "\"%s<%i><%i><>\" connected, address \"%s\"\n", newcl->name, newcl->userid, i, NET_AdrToString( newcl->netchan.remote_address ));

	if( count == 1 || count == svs.maxclients )
		NET_MasterClear();
}

/*
==================
SV_FakeConnect

A connection request that came from the game module
==================
*/
edict_t *GAME_EXPORT SV_FakeConnect( const char *netname )
{
	char		userinfo[MAX_INFO_STRING];
	int		i, count = 0;
	sv_client_t	*cl;

	if( !COM_CheckString( netname ))
		netname = "Bot";

	// find a client slot
	for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
	{
		if( cl->state == cs_free )
			break;
	}

	if( i == svs.maxclients )
		return NULL; // server is full

	userinfo[0] = '\0';

	// setup fake client params
	Info_SetValueForKey( userinfo, "name", netname, MAX_INFO_STRING );
	Info_SetValueForKey( userinfo, "model", "gordon", MAX_INFO_STRING );
	Info_SetValueForKey( userinfo, "topcolor", "1", MAX_INFO_STRING );
	Info_SetValueForKey( userinfo, "bottomcolor", "1", MAX_INFO_STRING );

	// build a new connection
	// accept the new client
	sv.current_client = cl;

	if( cl->frames ) Mem_Free( cl->frames );	// fakeclients doesn't have frames
	memset( cl, 0, sizeof( sv_client_t ));

	cl->edict = EDICT_NUM( (cl - svs.clients) + 1 );
	cl->userid = g_userid++;		// create unique userid
	SetBits( cl->flags, FCL_FAKECLIENT );

	// parse some info from the info strings
	Q_strncpy( cl->userinfo, userinfo, sizeof( cl->userinfo ));
	SV_UserinfoChanged( cl );
	SetBits( cl->flags, FCL_RESEND_USERINFO );
	cl->next_sendinfotime = 0.0;

	// if this was the first client on the server, or the last client
	// the server can hold, send a heartbeat to the master.
	for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
		if( cl->state >= cs_connected ) count++;
	cl = sv.current_client;

	Log_Printf( "\"%s<%i><%i><>\" connected, address \"local\"\n", cl->name, cl->userid, i );

	SetBits( cl->edict->v.flags, FL_CLIENT|FL_FAKECLIENT );	// mark it as fakeclient
	cl->connection_started = host.realtime;
	cl->state = cs_spawned;

	if( count == 1 || count == svs.maxclients )
		NET_MasterClear();

	return cl->edict;
}

/*
==================
SV_Kick_f

Kick a user off of the server
==================
*/
void SV_KickPlayer( sv_client_t *cl, const char *fmt, ... )
{
	const char *clientId;
	va_list va;
	char buf[MAX_VA_STRING];

	if( NET_IsLocalAddress( cl->netchan.remote_address ))
	{
		Con_Printf( "The local player cannot be kicked!\n" );
		return;
	}

	clientId = SV_GetClientIDString( cl );

	va_start( va, fmt );
	Q_vsnprintf( buf, sizeof( buf ), fmt, va );
	va_end( va );

	if( buf[0] )
	{
		Log_Printf( "Kick: \"%s<%i><%s><>\" was kicked by \"Console\" (message \"%s\")\n", cl->name, cl->userid, clientId, buf );
		SV_BroadcastPrintf( cl, "%s was kicked with message: \"%s\"\n", cl->name, buf );
		SV_ClientPrintf( cl, "You were kicked from the game with message: \"%s\"\n", buf );
		if( cl->useragent[0] )
			Netchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, "errormsg\nKicked with message:\n%s\n", buf );
	}
	else
	{
		Log_Printf( "Kick: \"%s<%i><%s><>\" was kicked by \"Console\"\n", cl->name, cl->userid, clientId );
		SV_BroadcastPrintf( cl, "%s was kicked\n", cl->name );
		SV_ClientPrintf( cl, "You were kicked from the game\n" );
		if( cl->useragent[0] )
			Netchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, "errormsg\nYou were kicked from the game\n" );
	}

	SV_DropClient( cl, false );
}

/*
=====================
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( sv_client_t *cl, qboolean crash )
{
	int	i;

	if( cl->state == cs_zombie )
		return;	// already dropped

	if( !crash )
	{
		// add the disconnect
		if( !FBitSet( cl->flags, FCL_FAKECLIENT ) )
		{
			MSG_BeginServerCmd( &cl->netchan.message, svc_disconnect );
		}

		if( cl->edict && cl->state == cs_spawned )
		{
			svgame.dllFuncs.pfnClientDisconnect( cl->edict );
		}

		if( !FBitSet( cl->flags, FCL_FAKECLIENT ) )
		{
			Netchan_TransmitBits( &cl->netchan, 0, NULL );
		}
	}

	ClearBits( cl->flags, FCL_FAKECLIENT );
	ClearBits( cl->flags, FCL_HLTV_PROXY );
	cl->state = cs_zombie; // become free in a few seconds
	cl->name[0] = 0;

	if( cl->frames )
		Mem_Free( cl->frames ); // release delta
	cl->frames = NULL;

	if( NET_CompareBaseAdr( cl->netchan.remote_address, host.rd.address ))
		SV_EndRedirect( &host.rd );

	// throw away any residual garbage in the channel.
	Netchan_Clear( &cl->netchan );

	// clean client data on disconnect
	memset( cl->userinfo, 0, MAX_INFO_STRING );
	memset( cl->physinfo, 0, MAX_INFO_STRING );
	COM_ClearCustomizationList( &cl->customdata, false );

	// don't send to other clients
	cl->edict = NULL;

	// send notification to all other clients
	SV_FullClientUpdate( cl, &sv.reliable_datagram );

	// if this was the last client on the server, send a heartbeat
	// to the master so it is known the server is empty
	// send a heartbeat now so the master will get up to date info
	// if there is already a slot for this ip, reuse it
	for( i = 0; i < svs.maxclients; i++ )
	{
		if( svs.clients[i].state >= cs_connected )
			break;
	}

	if( i == svs.maxclients )
		NET_MasterClear();
}

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

SVC COMMAND REDIRECT

==============================================================================
*/
static void SV_BeginRedirect( host_redirect_t *rd, netadr_t adr, rdtype_t target, char *buffer, size_t buffersize, void (*flush))
{
	rd->target = target;
	rd->buffer = buffer;
	rd->buffersize = buffersize;
	rd->flush = flush;
	rd->address = adr;
	rd->buffer[0] = 0;
	if( rd->lines == 0 )
		rd->lines = -1;
}

static void SV_FlushRedirect( netadr_t adr, int dest, char *buf )
{
	if( sv.current_client && FBitSet( sv.current_client->flags, FCL_FAKECLIENT ))
		return;

	switch( dest )
	{
	case RD_PACKET:
		Netchan_OutOfBandPrint( NS_SERVER, adr, "print\n%s", buf );
		break;
	case RD_CLIENT:
		if( !sv.current_client ) return; // client not set
		MSG_BeginServerCmd( &sv.current_client->netchan.message, svc_print );
		MSG_WriteString( &sv.current_client->netchan.message, buf );
		break;
	case RD_NONE:
		Con_Printf( S_ERROR "SV_FlushRedirect: %s: invalid destination\n", NET_AdrToString( adr ));
		break;
	}
}

void SV_EndRedirect( host_redirect_t *rd )
{
	if( rd->lines > 0 )
		return;

	if( rd->flush )
		rd->flush( rd->address, rd->target, rd->buffer );

	rd->target = RD_NONE;
	rd->buffer = NULL;
	rd->buffersize = 0;
	rd->flush = NULL;
}

/*
================
Rcon_Print

Print message to rcon buffer and send to rcon redirect target
================
*/
void Rcon_Print( host_redirect_t *rd, const char *pMsg )
{
	size_t len;

	if( !rd->target || !rd->lines || !rd->flush || !rd->buffer )
		return;

	len = Q_strncat( rd->buffer, pMsg, rd->buffersize );

	if( len && rd->buffer[len - 1] == '\n' )
	{
		rd->flush( rd->address, rd->target, rd->buffer );

		if( rd->lines > 0 )
			rd->lines--;

		rd->buffer[0] = 0;

		if( !rd->lines )
			Msg( "End of redirection!\n" );
	}
}

/*
===============
SV_GetClientIDString

Returns a pointer to a static char for most likely only printing.
===============
*/
const char *SV_GetClientIDString( sv_client_t *cl )
{
	static char	result[MAX_QPATH];

	if( !cl ) return "";

	if( FBitSet( cl->flags, FCL_FAKECLIENT ))
	{
		Q_strncpy( result, "ID_BOT", sizeof( result ));
	}
	else if( NET_IsLocalAddress( cl->netchan.remote_address ))
	{
		Q_strncpy( result, "ID_LOOPBACK", sizeof( result ));
	}
	else if( sv_lan.value )
	{
		Q_strncpy( result, "ID_LAN", sizeof( result ));
	}
	else
	{
		Q_snprintf( result, sizeof( result ), "ID_%s", MD5_Print( (byte *)cl->hashedcdkey ));
	}

	return result;
}

sv_client_t *SV_ClientById( int id )
{
	sv_client_t *cl;
	int i;

	ASSERT( id >= 0 );

	for( i = 0, cl = svs.clients; cl && i < svgame.globals->maxClients; i++, cl++ )
	{
		if( !cl->state )
			continue;

		if( cl->userid == id )
			return cl;
	}

	return NULL;
}

sv_client_t *SV_ClientByName( const char *name )
{
	sv_client_t *cl;
	int i;

	if( !COM_CheckString( name ))
		return NULL;

	for( i = 0, cl = svs.clients; cl && i < svgame.globals->maxClients; i++, cl++ )
	{
		if( !cl->state )
			continue;

		if( !Q_strcmp( cl->name, name ) )
			return cl;
	}

	return NULL;
}

/*
================
SV_TestBandWidth

================
*/
static void SV_TestBandWidth( netadr_t from )
{
	const int version = Q_atoi( Cmd_Argv( 1 ));
	const int packetsize = Q_atoi( Cmd_Argv( 2 ));
	uint32_t crc;
	int ofs;

	// don't waste time of protocol mismatched
	if( version != PROTOCOL_VERSION )
	{
		SV_RejectConnection( from, "unsupported protocol (%i should be %i)\n", version, PROTOCOL_VERSION );
		return;
	}

	// quickly reject invalid packets
	if( !sv_allow_testpacket.value || !svs.testpacket_buf ||
		( packetsize <= FRAGMENT_MIN_SIZE ) ||
		( packetsize > FRAGMENT_MAX_SIZE ))
	{
		// skip the test and just get challenge
		SV_GetChallenge( from );
		return;
	}

	// don't go out of bounds
	ofs = packetsize - svs.testpacket_filepos - 1;
	if(( ofs < 0 ) || ( ofs > svs.testpacket_filelen ))
	{
		SV_GetChallenge( from );
		return;
	}

	crc = svs.testpacket_crcs[ofs];
	memcpy( svs.testpacket_crcpos, &crc, sizeof( crc ));

	// send the datagram
	NET_SendPacket( NS_SERVER, packetsize, MSG_GetData( &svs.testpacket ), from );
}

/*
================
SV_Ack

================
*/
static void SV_Ack( netadr_t from )
{
	Con_Printf( "ping %s\n", NET_AdrToString( from ));
}

/*
================
SV_Info

Responds with short info for broadcast scans
The second parameter should be the current protocol version number.
================
*/
static void SV_Info( netadr_t from, int protocolVersion )
{
	char s[512];

	// ignore in single player
	if( svs.maxclients == 1 || !svs.initialized )
		return;

	s[0] = '\0';

	if( protocolVersion != PROTOCOL_VERSION )
	{
		Q_snprintf( s, sizeof( s ), "%s: wrong version\n", hostname.string );
	}
	else
	{
		int count;
		int bots;
		int remaining;
		char temp[sizeof( s )];
		qboolean have_password = COM_CheckStringEmpty( sv_password.string );

		SV_GetPlayerCount( &count, &bots );

		// a1ba: send protocol version to distinguish old engine and new
		Info_SetValueForKeyf( s, "p", sizeof( s ), "%i", PROTOCOL_VERSION );
		Info_SetValueForKey( s, "map", sv.name, sizeof( s ));
		Info_SetValueForKey( s, "dm", svgame.globals->deathmatch ? "1" : "0", sizeof( s ));
		Info_SetValueForKey( s, "team", svgame.globals->teamplay ? "1" : "0", sizeof( s ));
		Info_SetValueForKey( s, "coop", svgame.globals->coop ? "1" : "0", sizeof( s ));
		Info_SetValueForKeyf( s, "numcl", sizeof( s ), "%i", count );
		Info_SetValueForKeyf( s, "maxcl", sizeof( s ), "%i", svs.maxclients );
		Info_SetValueForKey( s, "gamedir", GI->gamefolder, sizeof( s ));
		Info_SetValueForKey( s, "password", have_password ? "1" : "0", sizeof( s ));

		// write host last so we can try to cut off too long hostnames
		// TODO: value size limit for infostrings
		remaining = sizeof( s ) - Q_strlen( s ) - sizeof( "\\host\\" ) - 1;
		if( remaining < 0 )
		{
			// should never happen?
			Con_Printf( S_ERROR "SV_Info: infostring overflow!\n" );
			return;
		}
		Q_strncpy( temp, hostname.string, remaining );
		Info_SetValueForKey( s, "host", temp, sizeof( s ));
	}

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

/*
================
SV_BuildNetAnswer

Responds with long info for local and broadcast requests
================
*/
static void SV_BuildNetAnswer( netadr_t from )
{
	char	string[MAX_INFO_STRING];
	int	version, context, type;
	int	i, count = 0;

	// ignore in single player
	if( svs.maxclients == 1 || !svs.initialized )
		return;

	version = Q_atoi( Cmd_Argv( 1 ));
	context = Q_atoi( Cmd_Argv( 2 ));
	type = Q_atoi( Cmd_Argv( 3 ));

	if( version != PROTOCOL_VERSION )
	{
		// handle the unsupported protocol
		string[0] = '\0';
		Info_SetValueForKey( string, "neterror", "protocol", MAX_INFO_STRING );

		// send error unsupported protocol
		Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string );
		return;
	}

	if( type == NETAPI_REQUEST_PING )
	{
		Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, "" );
	}
	else if( type == NETAPI_REQUEST_RULES )
	{
		// send serverinfo
		Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, svs.serverinfo );
	}
	else if( type == NETAPI_REQUEST_PLAYERS )
	{
		size_t len = 0;

		string[0] = '\0';

		for( i = 0; i < svs.maxclients; i++ )
		{
			if( svs.clients[i].state >= cs_connected )
			{
				int ret;
				edict_t *ed = svs.clients[i].edict;
				float time = host.realtime - svs.clients[i].connection_started;
				ret = Q_snprintf( &string[len], sizeof( string ) - len, "%c\\%s\\%i\\%f\\", count, svs.clients[i].name, (int)ed->v.frags, time );

				if( ret == -1 )
				{
					Con_DPrintf( S_WARN "SV_BuildNetAnswer: NETAPI_REQUEST_PLAYERS: buffer overflow!\n" );
					break;
				}

				len += ret;
				count++;
			}
		}

		// send playernames
		Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string );
	}
	else if( type == NETAPI_REQUEST_DETAILS )
	{
		for( i = 0; i < svs.maxclients; i++ )
			if( svs.clients[i].state >= cs_connected )
				count++;

		string[0] = '\0';
		Info_SetValueForKey( string, "hostname", hostname.string, MAX_INFO_STRING );
		Info_SetValueForKey( string, "gamedir", GI->gamefolder, MAX_INFO_STRING );
		Info_SetValueForKeyf( string, "current", MAX_INFO_STRING, "%i", count );
		Info_SetValueForKeyf( string, "max", MAX_INFO_STRING, "%i", svs.maxclients );
		Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING );

		// send serverinfo
		Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string );
	}
	else
	{
		string[0] = '\0';
		Info_SetValueForKey( string, "neterror", "undefined", MAX_INFO_STRING );

		// send error undefined request type
		Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string );
	}
}

/*
================
SV_Ping

Just responds with an acknowledgement
================
*/
static void SV_Ping( netadr_t from )
{
	Netchan_OutOfBandPrint( NS_SERVER, from, "ack" );
}

/*
================
Rcon_Validate
================
*/
static qboolean Rcon_Validate( void )
{
	if( !COM_CheckString( rcon_password.string ))
		return false;
	if( Q_strcmp( Cmd_Argv( 1 ), rcon_password.string ))
		return false;
	return true;
}

/*
===============
SV_RemoteCommand

A client issued an rcon command.
Shift down the remaining args
Redirect all printfs
===============
*/
void SV_RemoteCommand( netadr_t from, sizebuf_t *msg )
{
	static char	outputbuf[2048];
	const char	*adr;
	char		remaining[1024];
	char		*p = remaining;
	int		i;

	if( !rcon_enable.value || !COM_CheckStringEmpty( rcon_password.string ))
		return;

	adr = NET_AdrToString( from );

	Con_Printf( "Rcon from %s:\n%s\n", adr, MSG_GetData( msg ) + 4 );
	Log_Printf( "Rcon: \"%s\" from \"%s\"\n", MSG_GetData( msg ) + 4, adr );

	if( Rcon_Validate( ))
	{
		SV_BeginRedirect( &host.rd, from, RD_PACKET, outputbuf, sizeof( outputbuf ) - 16, SV_FlushRedirect );

		remaining[0] = 0;
		for( i = 2; i < Cmd_Argc(); i++ )
		{
			p += Q_strncpy( p, "\"", sizeof( remaining ) - ( p - remaining ));
			p += Q_strncpy( p, Cmd_Argv( i ), sizeof( remaining ) - ( p - remaining ));
			p += Q_strncpy( p, "\" ", sizeof( remaining ) - ( p - remaining ));
		}
		Cmd_ExecuteString( remaining );

		SV_EndRedirect( &host.rd );
	}
	else Con_Printf( S_ERROR "Bad rcon_password.\n" );
}

/*
===================
SV_CalcPing

recalc ping on current client
===================
*/
int SV_CalcPing( sv_client_t *cl )
{
	float		ping = 0;
	int		i, count;
	int		idx, back;
	client_frame_t	*frame;

	// bots don't have a real ping
	if( FBitSet( cl->flags, FCL_FAKECLIENT ) || !cl->frames )
		return 0;

	if( SV_UPDATE_BACKUP <= 31 )
	{
		back = SV_UPDATE_BACKUP / 2;
		if( back <= 0 ) return 0;
	}
	else back = 16;

	count = 0;

	for( i = 0; i < back; i++ )
	{
		idx = cl->netchan.incoming_acknowledged + ~i;
		frame = &cl->frames[idx & SV_UPDATE_MASK];

		if( frame->ping_time > 0.0f )
		{
			ping += frame->ping_time;
			count++;
		}
	}

	if( count > 0 )
		return (( ping / count ) * 1000.0f );
	return 0;
}

/*
===================
SV_EstablishTimeBase

Finangles latency and the like.
===================
*/
static void SV_EstablishTimeBase( sv_client_t *cl, usercmd_t *cmds, int dropped, int numbackup, int numcmds )
{
	double	runcmd_time = 0.0;
	int	i, cmdnum = dropped;

	if( dropped < 24 )
	{
		while( dropped > numbackup )
		{
			runcmd_time = (double)cl->lastcmd.msec / 1000.0;
			dropped--;
		}

		while( dropped > 0 )
		{
			cmdnum = dropped + numcmds - 1;
			runcmd_time += (double)cmds[cmdnum].msec / 1000.0;
			dropped--;
		}
	}

	for( i = numcmds - 1; i >= 0; i-- )
		runcmd_time += cmds[i].msec / 1000.0;

	cl->timebase = sv.time + sv.frametime - runcmd_time;
}

/*
===================
SV_CalcClientTime

compute latency for client
===================
*/
static float SV_CalcClientTime( sv_client_t *cl )
{
	float	minping, maxping;
	float	ping = 0.0f;
	int	i, count = 0;
	int	backtrack;

	backtrack = (int)sv_unlagsamples.value;
	if( backtrack < 1 ) backtrack = 1;

	if( backtrack >= (SV_UPDATE_BACKUP <= 16 ? SV_UPDATE_BACKUP : 16 ))
		backtrack = ( SV_UPDATE_BACKUP <= 16 ? SV_UPDATE_BACKUP : 16 );

	if( backtrack <= 0 )
		return 0.0f;

	for( i = 0; i < backtrack; i++ )
	{
		client_frame_t	*frame = &cl->frames[SV_UPDATE_MASK & (cl->netchan.incoming_acknowledged - i)];
		if( frame->ping_time <= 0.0f )
			continue;

		ping += frame->ping_time;
		count++;
	}

	if( !count ) return 0.0f;

	minping =  9999.0f;
	maxping = -9999.0f;
	ping /= count;

	for( i = 0; i < ( SV_UPDATE_BACKUP <= 4 ? SV_UPDATE_BACKUP : 4 ); i++ )
	{
		client_frame_t	*frame = &cl->frames[SV_UPDATE_MASK & (cl->netchan.incoming_acknowledged - i)];
		if( frame->ping_time <= 0.0f )
			continue;

		if( frame->ping_time < minping )
			minping = frame->ping_time;

		if( frame->ping_time > maxping )
			maxping = frame->ping_time;
	}

	if( maxping < minping || fabs( maxping - minping ) <= 0.2f )
		return ping;

	return 0.0f;
}

/*
===================
SV_FullClientUpdate

Writes all update values to a bitbuf
===================
*/
void SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg )
{
	char		info[MAX_INFO_STRING];
	char		digest[16];
	MD5Context_t	ctx;
	int		i;

	// process userinfo before updating
	SV_UserinfoChanged( cl );

	i = cl - svs.clients;

	MSG_BeginServerCmd( msg, svc_updateuserinfo );
	MSG_WriteUBitLong( msg, i, MAX_CLIENT_BITS );
	MSG_WriteLong( msg, cl->userid );

	if( cl->name[0] )
	{
		MSG_WriteOneBit( msg, 1 );

		Q_strncpy( info, cl->userinfo, sizeof( info ));

		// remove server passwords, etc.
		Info_RemovePrefixedKeys( info, '_' );
		MSG_WriteString( msg, info );

		MD5Init( &ctx );
		MD5Update( &ctx, (byte *)cl->hashedcdkey, sizeof( cl->hashedcdkey ));
		MD5Final( digest, &ctx );

		MSG_WriteBytes( msg, digest, sizeof( digest ));
	}
	else MSG_WriteOneBit( msg, 0 );
}

/*
===================
SV_RefreshUserinfo

===================
*/
void SV_RefreshUserinfo( void )
{
	sv_client_t	*cl;
	int		i;

	for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
	{
		if( cl->state >= cs_connected )
			SetBits( cl->flags, FCL_RESEND_USERINFO );
	}
}

/*
===================
SV_FullUpdateMovevars

this is send all movevars values when client connected
otherwise see code SV_UpdateMovevars()
===================
*/
void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg )
{
	movevars_t	nullmovevars;

	memset( &nullmovevars, 0, sizeof( nullmovevars ));
	MSG_WriteDeltaMovevars( msg, &nullmovevars, &svgame.movevars );
}

/*
===================
SV_ShouldUpdatePing

determine should we recalculate
ping times now
===================
*/
qboolean SV_ShouldUpdatePing( sv_client_t *cl )
{
	if( FBitSet( cl->flags, FCL_HLTV_PROXY ))
	{
		if( host.realtime < cl->next_checkpingtime )
			return false;

		cl->next_checkpingtime = host.realtime + 2.0;
		return true;
	}

	// they are viewing the scoreboard.  Send them pings.
	return FBitSet( cl->lastcmd.buttons, IN_SCORE ) ? true : false;
}

/*
===================
SV_IsPlayerIndex

===================
*/
qboolean SV_IsPlayerIndex( int idx )
{
	if( idx > 0 && idx <= svs.maxclients )
		return true;
	return false;
}

/*
===================
SV_GetPlayerStats

This function and its static vars track some of the networking
conditions.  I haven't bothered to trace it beyond that, because
this fucntion sucks pretty badly.
===================
*/
void SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss )
{
	static int	last_ping[MAX_CLIENTS];
	static int	last_loss[MAX_CLIENTS];
	int		i;

	i = cl - svs.clients;

	if( host.realtime >= cl->next_checkpingtime )
	{
		cl->next_checkpingtime = host.realtime + 2.0;
		last_ping[i] = SV_CalcPing( cl );
		last_loss[i] = cl->packet_loss;
	}

	if( ping ) *ping = last_ping[i];
	if( packet_loss ) *packet_loss = last_loss[i];
}

/*
===========
PutClientInServer

Called when a player connects to a server or respawns in
a deathmatch.
============
*/
static void SV_PutClientInServer( sv_client_t *cl )
{
	static byte    	msg_buf[MAX_INIT_MSG + 0x200];	// MAX_INIT_MSG + some space
	edict_t		*ent = cl->edict;
	sizebuf_t		msg;

	MSG_Init( &msg, "Spawn", msg_buf, sizeof( msg_buf ));

	if( sv.loadgame )
	{
		// NOTE: we needs to setup angles on restore here
		if( ent->v.fixangle == 1 )
		{
			MSG_BeginServerCmd( &msg, svc_setangle );
			MSG_WriteVec3Angles( &msg, ent->v.angles );
			ent->v.fixangle = 0;
		}

		if( svgame.dllFuncs.pfnParmsChangeLevel )
		{
			SAVERESTOREDATA	levelData;
			string		name;
			int		i;

			memset( &levelData, 0, sizeof( levelData ));
			svgame.globals->pSaveData = &levelData;
			svgame.dllFuncs.pfnParmsChangeLevel();

			MSG_BeginServerCmd( &msg, svc_restore );
			Q_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY "%s.HL2", sv.name );
			COM_FixSlashes( name );
			MSG_WriteString( &msg, name );
			MSG_WriteByte( &msg, levelData.connectionCount );

			for( i = 0; i < levelData.connectionCount; i++ )
				MSG_WriteString( &msg, levelData.levelList[i].mapName );

			svgame.globals->pSaveData = NULL;
		}

		// reset weaponanim
		MSG_BeginServerCmd( &msg, svc_weaponanim );
		MSG_WriteByte( &msg, 0 );
		MSG_WriteByte( &msg, 0 );

		sv.loadgame = false;
		sv.paused = false;
	}
	else
	{
		if( Q_atoi( Info_ValueForKey( cl->userinfo, "hltv" )))
			SetBits( cl->flags, FCL_HLTV_PROXY );

		// need to realloc private data for client
		SV_InitEdict( ent );

		if( FBitSet( cl->flags, FCL_HLTV_PROXY ))
			SetBits( ent->v.flags, FL_PROXY );
		else ent->v.flags = 0;

		ent->v.netname = MAKE_STRING( cl->name );
		ent->v.colormap = NUM_FOR_EDICT( ent );	// ???

		// fisrt entering
		svgame.globals->time = sv.time;
		svgame.dllFuncs.pfnClientPutInServer( ent );

		if( sv.background )	// don't attack player in background mode
			SetBits( ent->v.flags, FL_GODMODE|FL_NOTARGET );

		cl->pViewEntity = NULL; // reset pViewEntity
	}

	if( svgame.globals->cdAudioTrack )
	{
		MSG_BeginServerCmd( &msg, svc_stufftext );
		MSG_WriteStringf( &msg, "cd loop %3d\n", svgame.globals->cdAudioTrack );
		svgame.globals->cdAudioTrack = 0;
	}

#ifdef HACKS_RELATED_HLMODS
	// enable dev-mode to prevent crash cheat-protecting from Invasion mod
	if( FBitSet( ent->v.flags, FL_GODMODE|FL_NOTARGET ) && !Q_stricmp( GI->gamefolder, "invasion" ))
		SV_ExecuteClientCommand( cl, "test\n" );
#endif
	// refresh the userinfo and movevars
	// NOTE: because movevars can be changed during the connection process
	SetBits( cl->flags, FCL_RESEND_USERINFO|FCL_RESEND_MOVEVARS );

	// reset client times
	cl->connecttime = 0.0;
	cl->ignorecmdtime = 0.0;
	cl->cmdtime = 0.0;

	if( !FBitSet( cl->flags, FCL_FAKECLIENT ))
	{
		int	viewEnt;

		// NOTE: it's will be fragmented automatically in right ordering
		MSG_WriteBits( &msg, MSG_GetData( &sv.signon ), MSG_GetNumBitsWritten( &sv.signon ));

		if( cl->pViewEntity )
			viewEnt = NUM_FOR_EDICT( cl->pViewEntity );
		else viewEnt = NUM_FOR_EDICT( cl->edict );

		MSG_BeginServerCmd( &msg, svc_setview );
		MSG_WriteWord( &msg, viewEnt );

		MSG_BeginServerCmd( &msg, svc_signonnum );
		MSG_WriteByte( &msg, 1 );

		if( MSG_CheckOverflow( &msg ))
		{
			if( svs.maxclients == 1 )
				Host_Error( "spawn player: overflowed\n" );
			else SV_DropClient( cl, false );
		}
		else
		{
			// send initialization data
			Netchan_CreateFragments( &cl->netchan, &msg );
			Netchan_FragSend( &cl->netchan );
		}
	}
}

/*
===========
SV_UpdateClientView

Resend the client viewentity (used for demos)
============
*/
static void SV_UpdateClientView( sv_client_t *cl )
{
	int	viewEnt;

	if( cl->pViewEntity )
		viewEnt = NUM_FOR_EDICT( cl->pViewEntity );
	else viewEnt = NUM_FOR_EDICT( cl->edict );

	MSG_BeginServerCmd( &cl->netchan.message, svc_setview );
	MSG_WriteWord( &cl->netchan.message, viewEnt );
}

/*
==================
SV_TogglePause
==================
*/
void SV_TogglePause( const char *msg )
{
	if( sv.background ) return;

	sv.paused ^= 1;

	if( COM_CheckString( msg ))
		SV_BroadcastPrintf( NULL, "%s", msg );

	// send notification to all clients
	MSG_BeginServerCmd( &sv.reliable_datagram, svc_setpause );
	MSG_WriteOneBit( &sv.reliable_datagram, sv.paused );
}

/*
================
SV_SendReconnect

Tell all the clients that the server is changing levels
================
*/
void SV_BuildReconnect( sizebuf_t *msg )
{
	MSG_BeginServerCmd( msg, svc_stufftext );
	MSG_WriteString( msg, "reconnect\n" );
}

/*
================
SV_SendServerdata

Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/
void SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl )
{
	string	message;
	int	i;

	// Only send this message to developer console, or multiplayer clients.
	if(( host_developer.value ) || ( svs.maxclients > 1 ))
	{
		MSG_BeginServerCmd( msg, svc_print );
		Q_snprintf( message, sizeof( message ), "\n^3BUILD %d SERVER (%i CRC)\nServer #%i\n", Q_buildnum(), sv.progsCRC, svs.spawncount );
		MSG_WriteString( msg, message );
	}

	// send the serverdata
	MSG_BeginServerCmd( msg, svc_serverdata );
	MSG_WriteLong( msg, PROTOCOL_VERSION );
	MSG_WriteLong( msg, svs.spawncount );
	MSG_WriteLong( msg, sv.worldmapCRC );
	MSG_WriteByte( msg, cl - svs.clients );
	MSG_WriteByte( msg, svs.maxclients );
	MSG_WriteWord( msg, GI->max_edicts );
	MSG_WriteWord( msg, MAX_MODELS );
	MSG_WriteString( msg, sv.name );
	MSG_WriteString( msg, STRING( svgame.edicts->v.message )); // Map Message
	MSG_WriteOneBit( msg, sv.background ); // tell client about background map
	MSG_WriteString( msg, GI->gamefolder );
	MSG_WriteLong( msg, host.features );

	// send the player hulls
	for( i = 0; i < MAX_MAP_HULLS * 3; i++ )
	{
		MSG_WriteChar( msg, host.player_mins[i/3][i%3] );
		MSG_WriteChar( msg, host.player_maxs[i/3][i%3] );
	}

	// send delta-encoding
	Delta_WriteDescriptionToClient( msg );

	// now client know delta and can reading encoded messages
	SV_FullUpdateMovevars( cl, msg );

	// send the user messages registration
	for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ )
		SV_SendUserReg( msg, &svgame.msg[i] );

	for( i = 0; i < MAX_LIGHTSTYLES; i++ )
	{
		if( !sv.lightstyles[i].pattern[0] )
			continue;	// unused style

		MSG_BeginServerCmd( msg, svc_lightstyle );
		MSG_WriteByte( msg, i ); // stylenum
		MSG_WriteString( msg, sv.lightstyles[i].pattern );
		MSG_WriteFloat( msg, sv.lightstyles[i].time );
	}
}

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

CLIENT COMMAND EXECUTION

============================================================
*/
/*
================
SV_New_f

Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/
static qboolean SV_New_f( sv_client_t *cl )
{
	byte		msg_buf[MAX_INIT_MSG];
	char		szRejectReason[128];
	char		szAddress[128];
	char		szName[32];
	sv_client_t	*cur;
	sizebuf_t		msg;
	int		i;

	memset( msg_buf, 0, sizeof( msg_buf ));
	MSG_Init( &msg, "New", msg_buf, sizeof( msg_buf ));

	if( cl->state != cs_connected )
		return false;

	// send the serverdata
	SV_SendServerdata( &msg, cl );

	// if the client was connected, tell the game .dll to disconnect him/her.
	if(( cl->state == cs_spawned ) && cl->edict )
		svgame.dllFuncs.pfnClientDisconnect( cl->edict );

	Q_strncpy( szName, cl->name, sizeof( szName ) );
	Q_strncpy( szAddress, NET_AdrToString( cl->netchan.remote_address ), sizeof( szAddress ) );
	Q_strncpy( szRejectReason, "Connection rejected by game\n", sizeof( szRejectReason ) );

	// Allow the game dll to reject this client.
	if( !svgame.dllFuncs.pfnClientConnect( cl->edict, szName, szAddress, szRejectReason ))
	{
		// reject the connection and drop the client.
		SV_RejectConnection( cl->netchan.remote_address, "%s\n", szRejectReason );
		SV_DropClient( cl, false );
		return true;
	}

	// server info string
	MSG_BeginServerCmd( &msg, svc_stufftext );
	MSG_WriteStringf( &msg, "fullserverinfo \"%s\"\n", svs.serverinfo );

	// collect the info about all the players and send to me
	for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ )
	{
		if( !cur->edict || cur->state != cs_spawned )
			continue;	// not in game yet
		SV_FullClientUpdate( cur, &msg );
	}

	// g-cont. why this is there?
	memset( &cl->lastcmd, 0, sizeof( cl->lastcmd ));

	Netchan_CreateFragments( &cl->netchan, &msg );
	Netchan_FragSend( &cl->netchan );

	return true;
}

/*
=================
SV_Disconnect_f

The client is going to disconnect, so remove the connection immediately
=================
*/
static qboolean SV_Disconnect_f( sv_client_t *cl )
{
	SV_DropClient( cl, false );
	return true;
}

/*
==================
SV_ShowServerinfo_f

Dumps the serverinfo info string
==================
*/
static qboolean SV_ShowServerinfo_f( sv_client_t *cl )
{
	Info_Print( svs.serverinfo );
	return true;
}

/*
==================
SV_Pause_f
==================
*/
static qboolean SV_Pause_f( sv_client_t *cl )
{
	string	message;

	if( UI_CreditsActive( ))
		return true;

	if( !sv_pausable.value )
	{
		SV_ClientPrintf( cl, "Pause not allowed.\n" );
		return true;
	}

	if( FBitSet( cl->flags, FCL_HLTV_PROXY ))
	{
		SV_ClientPrintf( cl, "Spectators can not pause.\n" );
		return true;
	}

	if( !sv.paused ) Q_snprintf( message, MAX_STRING, "^2%s^7 paused the game\n", cl->name );
	else Q_snprintf( message, MAX_STRING, "^2%s^7 unpaused the game\n", cl->name );

	SV_TogglePause( message );

	return true;
}

static qboolean SV_ShouldUpdateUserinfo( sv_client_t *cl )
{
	qboolean allow = true; // predict state

	if( !sv_userinfo_enable_penalty.value )
		return allow;

	if( FBitSet( cl->flags, FCL_FAKECLIENT ))
		return allow;

	if( Host_IsLocalGame( ))
		return allow;

	// start from 1 second
	if( !cl->userinfo_penalty )
		cl->userinfo_penalty = sv_userinfo_penalty_time.value;

	// player changes userinfo after limit time window, but before
	// next timewindow
	// he seems to be spammer, so just increase change attempts
	if( host.realtime < cl->userinfo_next_changetime + cl->userinfo_penalty * sv_userinfo_penalty_multiplier.value )
	{
		// player changes userinfo too quick! ignore!
		if( host.realtime < cl->userinfo_next_changetime )
		{
			Con_Reportf( "SV_ShouldUpdateUserinfo: ignore userinfo update for %s: penalty %f, attempts %i\n",
				cl->name, cl->userinfo_penalty, cl->userinfo_change_attempts );
			allow = false;
		}

		cl->userinfo_change_attempts++;
	}

	// they spammed too fast, increase penalty
	if( cl->userinfo_change_attempts > sv_userinfo_penalty_attempts.value )
	{
		Con_Reportf( "SV_ShouldUpdateUserinfo: penalty set %f for %s\n",
			cl->userinfo_penalty, cl->name );
		cl->userinfo_penalty *= sv_userinfo_penalty_multiplier.value;
		cl->userinfo_change_attempts = 0;
	}

	cl->userinfo_next_changetime = host.realtime + cl->userinfo_penalty;

	return allow;
}

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

Pull specific info from a newly changed userinfo string
into a more C freindly form.
=================
*/
static void SV_UserinfoChanged( sv_client_t *cl )
{
	int		i, dupc = 1;
	edict_t		*ent = cl->edict;
	string		name1, name2;
	sv_client_t	*current;
	const char		*val;

	if( !COM_CheckString( cl->userinfo ))
		return;

	if( !SV_ShouldUpdateUserinfo( cl ))
		return;

	if( !Info_IsValid( cl->userinfo ))
		return;

	val = Info_ValueForKey( cl->userinfo, "name" );
	Q_strncpy( name2, val, sizeof( name2 ));
	COM_TrimSpace( name2, name1 );

	if( !Q_stricmp( name1, "console" ))
	{
		Info_SetValueForKey( cl->userinfo, "name", "unnamed", MAX_INFO_STRING );
		val = Info_ValueForKey( cl->userinfo, "name" );
	}
	else if( Q_strcmp( name1, val ))
	{
		Info_SetValueForKey( cl->userinfo, "name", name1, MAX_INFO_STRING );
		val = Info_ValueForKey( cl->userinfo, "name" );
	}

	if( !COM_CheckStringEmpty( name1 ) )
	{
		Info_SetValueForKey( cl->userinfo, "name", "unnamed", MAX_INFO_STRING );
		val = Info_ValueForKey( cl->userinfo, "name" );
		Q_strncpy( name2, "unnamed", sizeof( name2 ));
		Q_strncpy( name1, "unnamed", sizeof( name1 ));
	}

	// check to see if another user by the same name exists
	while( 1 )
	{
		for( i = 0, current = svs.clients; i < svs.maxclients; i++, current++ )
		{
			if( current == cl || current->state != cs_spawned )
				continue;

			if( !Q_stricmp( current->name, val ))
				break;
		}

		if( i != svs.maxclients )
		{
			// dup name
			Q_snprintf( name2, sizeof( name2 ), "%s (%u)", name1, dupc++ );
			Info_SetValueForKey( cl->userinfo, "name", name2, MAX_INFO_STRING );
			val = Info_ValueForKey( cl->userinfo, "name" );
			Q_strncpy( cl->name, name2, sizeof( cl->name ));
		}
		else
		{
			if( dupc == 1 ) // unchanged
				Q_strncpy( cl->name, name1, sizeof( cl->name ));
			break;
		}
	}

	// rate command
	val = Info_ValueForKey( cl->userinfo, "rate" );
	if( COM_CheckString( val ) )
		cl->netchan.rate = bound( sv_minrate.value, Q_atoi( val ), sv_maxrate.value );
	else cl->netchan.rate = DEFAULT_RATE;

	// movement prediction
	if( Q_atoi( Info_ValueForKey( cl->userinfo, "cl_nopred" )))
		ClearBits( cl->flags, FCL_PREDICT_MOVEMENT );
	else SetBits( cl->flags, FCL_PREDICT_MOVEMENT );

	// lag compensation
	if( Q_atoi( Info_ValueForKey( cl->userinfo, "cl_lc" )))
		SetBits( cl->flags, FCL_LAG_COMPENSATION );
	else ClearBits( cl->flags, FCL_LAG_COMPENSATION );

	// weapon perdiction
	if( Q_atoi( Info_ValueForKey( cl->userinfo, "cl_lw" )))
		SetBits( cl->flags, FCL_LOCAL_WEAPONS );
	else ClearBits( cl->flags, FCL_LOCAL_WEAPONS );

	val = Info_ValueForKey( cl->userinfo, "cl_updaterate" );

	if( COM_CheckString( val ) )
	{
		if( Q_atoi( val ) != 0 )
		{
			cl->cl_updaterate = 1.0 / bound( sv_minupdaterate.value, Q_atoi( val ), sv_maxupdaterate.value );
		}
		else cl->cl_updaterate = 0.0;
	}

	// call prog code to allow overrides
	svgame.dllFuncs.pfnClientUserInfoChanged( cl->edict, cl->userinfo );

	val = Info_ValueForKey( cl->userinfo, "name" );
	Q_strncpy( cl->name, val, sizeof( cl->name ));
	ent->v.netname = MAKE_STRING( cl->name );
}

/*
==================
SV_UpdateUserinfo_f
==================
*/
static qboolean SV_UpdateUserinfo_f( sv_client_t *cl )
{
	Q_strncpy( cl->userinfo, Cmd_Argv( 1 ), sizeof( cl->userinfo ));

	if( cl->state >= cs_connected )
		SetBits( cl->flags, FCL_RESEND_USERINFO ); // needs for update client info
	return true;
}

/*
==================
SV_SetInfo_f
==================
*/
static qboolean SV_SetInfo_f( sv_client_t *cl )
{
	Info_SetValueForKey( cl->userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_INFO_STRING );

	if( cl->state >= cs_connected )
		SetBits( cl->flags, FCL_RESEND_USERINFO ); // needs for update client info
	return true;
}

/*
==================
SV_Noclip_f
==================
*/
static qboolean SV_Noclip_f( sv_client_t *cl )
{
	edict_t	*pEntity = cl->edict;

	if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background )
		return true;

	if( pEntity->v.movetype != MOVETYPE_NOCLIP )
	{
		SV_ClientPrintf( cl, "noclip ON\n" );
		pEntity->v.movetype = MOVETYPE_NOCLIP;
	}
	else
	{
		SV_ClientPrintf( cl, "noclip OFF\n" );
		pEntity->v.movetype =  MOVETYPE_WALK;
	}

	return true;
}

/*
==================
SV_Godmode_f
==================
*/
static qboolean SV_Godmode_f( sv_client_t *cl )
{
	edict_t	*pEntity = cl->edict;

	if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background )
		return true;

	pEntity->v.flags = pEntity->v.flags ^ FL_GODMODE;

	if( !FBitSet( pEntity->v.flags, FL_GODMODE ))
		SV_ClientPrintf( cl, "godmode OFF\n" );
	else SV_ClientPrintf( cl, "godmode ON\n" );

	return true;
}

/*
==================
SV_Notarget_f
==================
*/
static qboolean SV_Notarget_f( sv_client_t *cl )
{
	edict_t	*pEntity = cl->edict;

	if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background )
		return true;

	pEntity->v.flags = pEntity->v.flags ^ FL_NOTARGET;

	if( !FBitSet( pEntity->v.flags, FL_NOTARGET ))
		SV_ClientPrintf( cl, "notarget OFF\n" );
	else SV_ClientPrintf( cl, "notarget ON\n" );

	return true;
}

/*
==================
SV_Kill_f
==================
*/
static qboolean SV_Kill_f( sv_client_t *cl )
{
	if( !SV_IsValidEdict( cl->edict ))
		return true;

	if( cl->state != cs_spawned )
	{
		SV_ClientPrintf( cl, "Can't suicide - not connected!\n" );
		return true;
	}

	if( cl->edict->v.health <= 0.0f )
	{
		SV_ClientPrintf( cl, "Can't suicide - already dead!\n");
		return true;
	}

	svgame.dllFuncs.pfnClientKill( cl->edict );

	return true;
}

/*
==================
SV_SendRes_f
==================
*/
static qboolean SV_SendRes_f( sv_client_t *cl )
{
	byte	buffer[MAX_INIT_MSG];
	sizebuf_t	msg;

	if( cl->state != cs_connected )
		return false;

	memset( buffer, 0, sizeof( buffer ));
	MSG_Init( &msg, "SendResources", buffer, sizeof( buffer ));

	if( svs.maxclients > 1 && FBitSet( cl->flags, FCL_SEND_RESOURCES ))
		return true;

	SetBits( cl->flags, FCL_SEND_RESOURCES );
	SV_SendResources( cl, &msg );

	Netchan_CreateFragments( &cl->netchan, &msg );
	Netchan_FragSend( &cl->netchan );

	return true;
}

/*
==================
SV_DownloadFile_f
==================
*/
static qboolean SV_DownloadFile_f( sv_client_t *cl )
{
	const char	*name;

	if( Cmd_Argc() < 2 )
		return true;

	name = Cmd_Argv( 1 );

	if( !COM_CheckString( name ))
		return true;

	if( !COM_IsSafeFileToDownload( name ) || !sv_allow_download.value )
	{
		SV_FailDownload( cl, name );
		return true;
	}

	// g-cont. now we supports hot precache
	if( name[0] != '!' )
	{
		if( sv_send_resources.value )
		{
			int i;

			// security: allow download only precached resources
			for( i = 0; i < sv.num_resources; i++ )
			{
				const char *cmpname = name;

				if( sv.resources[i].type == t_sound )
					cmpname += sizeof( DEFAULT_SOUNDPATH ) - 1; // cut "sound/" off

				if( !Q_strncmp( sv.resources[i].szFileName, cmpname, 64 ) )
					break;
			}

			if( i == sv.num_resources )
			{
				SV_FailDownload( cl, name );
				return true;
			}

			// also check the model textures
			if( !Q_stricmp( COM_FileExtension( name ), "mdl" ))
			{
				if( FS_FileExists( Mod_StudioTexName( name ), false ) > 0 )
					Netchan_CreateFileFragments( &cl->netchan, Mod_StudioTexName( name ));
			}

			if( Netchan_CreateFileFragments( &cl->netchan, name ))
			{
				Netchan_FragSend( &cl->netchan );
				return true;
			}
		}

		SV_FailDownload( cl, name );
		return true;
	}

	if( Q_strlen( name ) == 36 && !Q_strnicmp( name, "!MD5", 4 ) && sv_send_logos.value )
	{
		resource_t	custResource;
		byte		md5[32];
		byte		*pbuf;
		int		size;

		memset( &custResource, 0, sizeof( custResource ) );
		COM_HexConvert( name + 4, 32, md5 );

		if( HPAK_ResourceForHash( CUSTOM_RES_PATH, md5, &custResource ))
		{
			if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &custResource, &pbuf, &size ))
			{
				if( size )
				{
					Netchan_CreateFileFragmentsFromBuffer( &cl->netchan, name, pbuf, size );
					Netchan_FragSend( &cl->netchan );
					Mem_Free( pbuf );
				}
			}
		}
	}
	else
	{
		SV_FailDownload( cl, name );
	}

	return true;
}

/*
==================
SV_Spawn_f
==================
*/
static qboolean SV_Spawn_f( sv_client_t *cl )
{
	if( cl->state != cs_connected )
		return false;

	// handle the case of a level changing while a client was connecting
	if( Q_atoi( Cmd_Argv( 1 )) != svs.spawncount )
	{
		SV_New_f( cl );
		return true;
	}

	SV_PutClientInServer( cl );

	// if we are paused, tell the clients
	if( sv.paused )
	{
		MSG_BeginServerCmd( &sv.reliable_datagram, svc_setpause );
		MSG_WriteByte( &sv.reliable_datagram, sv.paused );
		SV_ClientPrintf( cl, "Server is paused.\n" );
	}
	return true;
}

/*
==================
SV_Begin_f
==================
*/
static qboolean SV_Begin_f( sv_client_t *cl )
{
	if( cl->state != cs_connected )
		return false;

	// now client is spawned
	cl->state = cs_spawned;
	cl->connecttime = host.realtime;

	return true;
}

/*
==================
SV_SendBuildInfo_f
==================
*/
static qboolean SV_SendBuildInfo_f( sv_client_t *cl )
{
	SV_ClientPrintf( cl, "Server running " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n",
		Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() );
	return true;
}

/*
==================
SV_GetCrossEnt
==================
*/
static edict_t *SV_GetCrossEnt( edict_t *player )
{
	edict_t *ent = EDICT_NUM(1);
	edict_t *closest = NULL;
	float flMaxDot = 0.94;
	vec3_t forward;
	vec3_t viewPos;
	int i;
	float maxLen = 1000;

	AngleVectors( player->v.v_angle, forward, NULL, NULL );
	VectorAdd( player->v.origin, player->v.view_ofs, viewPos );

	// find bmodels by trace
	{
		trace_t trace;
		vec3_t target;

		VectorMA( viewPos, 1000, forward, target );
		trace = SV_Move( viewPos, vec3_origin, vec3_origin, target, 0, player, false );
		closest = trace.ent;
		VectorSubtract( viewPos, trace.endpos, target );
		maxLen = VectorLength(target) + 30;
	}

	// check untraceable entities
	for ( i = 1; i < svgame.numEntities; i++, ent++ )
	{
		vec3_t vecLOS;
		vec3_t vecOrigin;
		float flDot, traceLen;
		vec3_t boxSize;
		trace_t trace;
		vec3_t vecTrace;

		if( ent->free )
			continue;

		if( ent->v.solid == SOLID_BSP || ent->v.movetype == MOVETYPE_PUSHSTEP )
			continue; // bsp models will be found by trace later

		// do not touch following weapons
		if( ent->v.movetype == MOVETYPE_FOLLOW )
			continue;

		if( ent == player )
			continue;

		VectorAdd( ent->v.absmin, ent->v.absmax, vecOrigin );
		VectorScale( vecOrigin, 0.5, vecOrigin );

		VectorSubtract( vecOrigin, viewPos, vecLOS );
		traceLen = VectorLength(vecLOS);

		if( traceLen > maxLen )
			continue;

		VectorCopy( ent->v.size, boxSize);
		VectorScale( boxSize, 0.5, boxSize );

		if ( vecLOS[0] > boxSize[0] )
			vecLOS[0] -= boxSize[0];
		else if ( vecLOS[0] < -boxSize[0] )
			vecLOS[0] += boxSize[0];
		else
			vecLOS[0] = 0;

		if ( vecLOS[1] > boxSize[1] )
			vecLOS[1] -= boxSize[1];
		else if ( vecLOS[1] < -boxSize[1] )
			vecLOS[1] += boxSize[1];
		else
			vecLOS[1] = 0;

		if ( vecLOS[2] > boxSize[2] )
			vecLOS[2] -= boxSize[2];
		else if ( vecLOS[2] < -boxSize[2] )
			vecLOS[2] += boxSize[2];
		else
			vecLOS[2] = 0;
		VectorNormalize( vecLOS );

		flDot = DotProduct (vecLOS , forward);
		if ( flDot <= flMaxDot )
			continue;

		trace = SV_Move( viewPos, vec3_origin, vec3_origin, vecOrigin, 0, player, false );
		VectorSubtract( trace.endpos, viewPos, vecTrace );
		if( VectorLength( vecTrace ) + 30 < traceLen )
			continue;
		closest = ent, flMaxDot = flDot;
	}

	return closest;
}

/*
==================
SV_EntFindSingle
==================
*/
static edict_t *SV_EntFindSingle( sv_client_t *cl, const char *pattern )
{
	edict_t	*ent = NULL;
	int	i = 0;

	if( Q_isdigit( pattern ) )
	{
		i = Q_atoi( pattern );

		if( i >= svgame.numEntities )
			return NULL;
	}
	else if( !Q_stricmp( pattern, "!cross" ) )
	{
		ent = SV_GetCrossEnt( cl->edict );

		if( !SV_IsValidEdict( ent ) )
			return NULL;

		i = NUM_FOR_EDICT( ent );
	}
	else if( pattern[0] == '!' ) // check for correct instance with !(num)_(serial)
	{
		const char *p = pattern + 1;
		i = Q_atoi( p );

		while( Q_isdigit( p )) p++;

		if( *p++ != '_' )
			return NULL;

		if( i >= svgame.numEntities )
			return NULL;

		ent = EDICT_NUM( i );

		if( ent->serialnumber != Q_atoi( p ) )
			return NULL;
	}
	else
	{
		for( i = svgame.globals->maxClients + 1; i < svgame.numEntities; i++ )
		{
			ent = EDICT_NUM( i );

			if( !SV_IsValidEdict( ent ) )
				continue;

			if( Q_stricmpext( pattern, STRING( ent->v.targetname ) ) )
				break;
		}
	}

	ent = EDICT_NUM( i );

	if( !SV_IsValidEdict( ent ) )
		return NULL;

	return ent;
}

/*
===============
SV_EntList_f

Print list of entities to client
===============
*/
static qboolean SV_EntList_f( sv_client_t *cl )
{
	vec3_t borigin;
	edict_t	*ent = NULL;
	int	i;

	for( i = 0; i < svgame.numEntities; i++ )
	{
		ent = EDICT_NUM( i );
		if( !SV_IsValidEdict( ent ))
			continue;

		// filter by string
		if( Cmd_Argc() > 1 )
		{
			if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) )
				continue;
		}

		VectorAdd( ent->v.absmin, ent->v.absmax, borigin );
		VectorScale( borigin, 0.5, borigin );

		SV_ClientPrintf( cl, "%5i origin: %.f %.f %.f", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );
		SV_ClientPrintf( cl, "%5i borigin: %.f %.f %.f", i, borigin[0], borigin[1], borigin[2] );

		if( ent->v.classname )
			SV_ClientPrintf( cl, ", class: %s", STRING( ent->v.classname ));

		if( ent->v.globalname )
			SV_ClientPrintf( cl, ", global: %s", STRING( ent->v.globalname ));

		if( ent->v.targetname )
			SV_ClientPrintf( cl, ", name: %s", STRING( ent->v.targetname ));

		if( ent->v.target )
			SV_ClientPrintf( cl, ", target: %s", STRING( ent->v.target ));

		if( ent->v.model )
			SV_ClientPrintf( cl, ", model: %s", STRING( ent->v.model ));

		SV_ClientPrintf( cl, "\n" );
	}
	return true;
}

/*
===============
SV_EntInfo_f

Print specified entity information to client
===============
*/
static qboolean SV_EntInfo_f( sv_client_t *cl )
{
	edict_t	*ent = NULL;
	vec3_t borigin;

	if( Cmd_Argc() != 2 )
	{
		SV_ClientPrintf( cl, "Use ent_info <index|name|inst>\n" );
		return false;
	}

	ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) );

	if( !SV_IsValidEdict( ent ))
		return false;

	VectorAdd( ent->v.absmin, ent->v.absmax, borigin );
	VectorScale( borigin, 0.5, borigin );

	SV_ClientPrintf( cl, "origin: %.f %.f %.f\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );
	SV_ClientPrintf( cl, "angles: %.f %.f %.f\n", ent->v.angles[0], ent->v.angles[1], ent->v.angles[2] );
	SV_ClientPrintf( cl, "borigin: %.f %.f %.f\n", borigin[0], borigin[1], borigin[2] );

	if( ent->v.classname )
		SV_ClientPrintf( cl, "class: %s\n", STRING( ent->v.classname ));

	if( ent->v.globalname )
		SV_ClientPrintf( cl, "global: %s\n", STRING( ent->v.globalname ));

	if( ent->v.targetname )
		SV_ClientPrintf( cl, "name: %s\n", STRING( ent->v.targetname ));

	if( ent->v.target )
		SV_ClientPrintf( cl, "target: %s\n", STRING( ent->v.target ));

	if( ent->v.model )
		SV_ClientPrintf( cl, "model: %s\n", STRING( ent->v.model ));

	SV_ClientPrintf( cl, "health: %.f\n", ent->v.health );

	if( ent->v.gravity != 1.0f )
		SV_ClientPrintf( cl, "gravity: %.2f\n", ent->v.gravity );

	SV_ClientPrintf( cl, "movetype: %d\n", ent->v.movetype );
	SV_ClientPrintf( cl, "rendermode: %d\n", ent->v.rendermode );
	SV_ClientPrintf( cl, "renderfx: %d\n", ent->v.renderfx );
	SV_ClientPrintf( cl, "renderamt: %f\n", ent->v.renderamt );
	SV_ClientPrintf( cl, "rendercolor: %f %f %f\n", ent->v.rendercolor[0], ent->v.rendercolor[1], ent->v.rendercolor[2] );
	SV_ClientPrintf( cl, "maxspeed: %f\n", ent->v.maxspeed );

	if( ent->v.solid )
		SV_ClientPrintf( cl, "solid: %d\n", ent->v.solid );

	SV_ClientPrintf( cl, "flags: 0x%x\n", ent->v.flags );
	SV_ClientPrintf( cl, "spawnflags: 0x%x\n", ent->v.spawnflags );
	return true;
}

/*
===============
SV_EntFire_f

Perform some actions
===============
*/
static qboolean SV_EntFire_f( sv_client_t *cl )
{
	edict_t	*ent = NULL;
	int	i = 1, count = 0;
	qboolean single; // true if user specified something that match single entity

	if( Cmd_Argc() < 3 )
	{
		SV_ClientPrintf( cl, "Use ent_fire <index||pattern> <command> [<values>]\n"
			"Use ent_fire 0 help to get command list\n" );
		return false;
	}

	if( ( single = Q_isdigit( Cmd_Argv( 1 ) ) ) )
	{
		i = Q_atoi( Cmd_Argv( 1 ) );

		if( i < 0 || i >= svgame.numEntities )
			return false;

		ent = EDICT_NUM( i );
	}
	else if( ( single = !Q_stricmp( Cmd_Argv( 1 ), "!cross" ) ) )
	{
		ent = SV_GetCrossEnt( cl->edict );

		if (!SV_IsValidEdict(ent))
			return false;

		i = NUM_FOR_EDICT( ent );
	}
	else if( ( single = ( Cmd_Argv( 1 )[0] == '!') ) ) // check for correct instance with !(num)_(serial)
	{
		const char *cmd = Cmd_Argv( 1 ) + 1;
		i = Q_atoi( cmd );

		while( Q_isdigit( cmd )) cmd++;

		if( *cmd++ != '_' )
			return false;

		if( i < 0 || i >= svgame.numEntities )
			return false;

		ent = EDICT_NUM( i );
		if( ent->serialnumber != Q_atoi( cmd ) )
			return false;
	}
	else
	{
		i = svgame.globals->maxClients + 1;
	}

	for( ; ( i <  svgame.numEntities ) && ( count < sv_enttools_maxfire.value ); i++ )
	{
		ent = EDICT_NUM( i );
		if( !SV_IsValidEdict( ent ))
		{
			// SV_ClientPrintf( cl, PRINT_LOW, "Got invalid entity\n" );
			if( single )
				break;
			continue;
		}

		// if user specified not a number, try find such entity
		if( !single )
		{
			if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ))
				continue;
		}

		SV_ClientPrintf( cl, "entity %i\n", i );

		count++;

		if( !Q_stricmp( Cmd_Argv( 2 ), "health" ) )
			ent->v.health = Q_atoi( Cmd_Argv ( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "gravity" ) )
			ent->v.gravity = Q_atof( Cmd_Argv ( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "movetype" ) )
			ent->v.movetype = Q_atoi( Cmd_Argv ( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "solid" ) )
			ent->v.solid = Q_atoi( Cmd_Argv ( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "rename" ) )
			ent->v.targetname = ALLOC_STRING( Cmd_Argv ( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "settarget" ) )
			ent->v.target = ALLOC_STRING( Cmd_Argv ( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "setmodel" ) )
			SV_SetModel( ent, Cmd_Argv( 3 ) );
		else if( !Q_stricmp( Cmd_Argv( 2 ), "set" ) )
		{
			string keyname;
			string value;
			KeyValueData	pkvd;
			if( Cmd_Argc() != 5 )
				return false;

			pkvd.szClassName = (char*)STRING( ent->v.classname );
			Q_strncpy( keyname, Cmd_Argv( 3 ), sizeof( keyname ));
			Q_strncpy( value, Cmd_Argv( 4 ), sizeof( value ));
			pkvd.szKeyName = keyname;
			pkvd.szValue = value;
			pkvd.fHandled = false;
			svgame.dllFuncs.pfnKeyValue( ent, &pkvd );

			if( pkvd.fHandled )
				SV_ClientPrintf( cl, "value set successfully!\n" );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "touch" ) )
		{
			if( Cmd_Argc() == 4 )
			{
				edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
				if( other && other->pvPrivateData )
					svgame.dllFuncs.pfnTouch( ent, other  );
			}
			else
				svgame.dllFuncs.pfnTouch( ent, cl->edict );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "use" ) )
		{
			if( Cmd_Argc() == 4 )
			{
				edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
				if( other && other->pvPrivateData )
					svgame.dllFuncs.pfnUse( ent, other );
			}
			else
				svgame.dllFuncs.pfnUse( ent, cl->edict );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "movehere" ) )
		{
			ent->v.origin[2] = cl->edict->v.origin[2] + 25;
			ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) );
			ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) );
			SV_LinkEdict( ent, true );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "drop2floor" ) )
		{
			pfnDropToFloor( ent );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "moveup" ) )
		{
			float dist = 25;
			if( Cmd_Argc() >= 4 )
				dist = Q_atof( Cmd_Argv( 3 ) );
			ent->v.origin[2] +=  dist;
			if( Cmd_Argc() >= 5 )
			{
				dist = Q_atof( Cmd_Argv( 4 ) );
				ent->v.origin[0] += dist * cos( DEG2RAD( cl->edict->v.angles[1] ) );
				ent->v.origin[1] += dist * sin( DEG2RAD( cl->edict->v.angles[1] ) );
			}

		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeowner" ) )
		{
			if( Cmd_Argc() == 4 )
				ent->v.owner = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
			else
				ent->v.owner = cl->edict;
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeenemy" ) )
		{
			if( Cmd_Argc() == 4 )
				ent->v.enemy = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
			else
				ent->v.enemy = cl->edict;
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeaiment" ) )
		{
			if( Cmd_Argc() == 4 )
				ent->v.aiment= SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
			else
				ent->v.aiment = cl->edict;
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmin" ) )
		{
			if( Cmd_Argc() != 6 )
				return false;
			ent->v.mins[0] = Q_atof( Cmd_Argv( 3 ) );
			ent->v.mins[1] = Q_atof( Cmd_Argv( 4 ) );
			ent->v.mins[2] = Q_atof( Cmd_Argv( 5 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmax" ) )
		{
			if( Cmd_Argc() != 6 )
				return false;
			ent->v.maxs[0] = Q_atof( Cmd_Argv( 3 ) );
			ent->v.maxs[1] = Q_atof( Cmd_Argv( 4 ) );
			ent->v.maxs[2] = Q_atof( Cmd_Argv( 5 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "rendercolor" ) )
		{
			if( Cmd_Argc() != 6 )
				return false;
			ent->v.rendercolor[0] = Q_atof( Cmd_Argv( 3 ) );
			ent->v.rendercolor[1] = Q_atof( Cmd_Argv( 4 ) );
			ent->v.rendercolor[2] = Q_atof( Cmd_Argv( 5 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "renderamt" ) )
		{
			ent->v.renderamt = Q_atof( Cmd_Argv( 3 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "renderfx" ) )
		{
			ent->v.renderfx = Q_atoi( Cmd_Argv( 3 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "rendermode" ) )
		{
			ent->v.rendermode = Q_atoi( Cmd_Argv( 3 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "angles" ) )
		{
			ent->v.angles[0] = Q_atof( Cmd_Argv( 3 ) );
			ent->v.angles[1] = Q_atof( Cmd_Argv( 4 ) );
			ent->v.angles[2] = Q_atof( Cmd_Argv( 5 ) );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "setflag" ) )
		{
			ent->v.flags |= 1U << Q_atoi( Cmd_Argv ( 3 ) );
			SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "clearflag" ) )
		{
			ent->v.flags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) );
			SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "setspawnflag" ) )
		{
			ent->v.spawnflags |= 1U << Q_atoi( Cmd_Argv ( 3 ) );
			SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.spawnflags );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "clearspawnflag" ) )
		{
			ent->v.spawnflags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) );
			SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.flags );
		}
		else if( !Q_stricmp( Cmd_Argv( 2 ), "help" ) )
		{
			SV_ClientPrintf( cl, "Available commands:\n"
				"Set fields:\n"
				"        (Only set entity field, does not call any functions)\n"
				"    health\n"
				"    gravity\n"
				"    movetype\n"
				"    solid\n"
				"    rendermode\n"
				"    rendercolor (vector)\n"
				"    renderfx\n"
				"    renderamt\n"
				"    hullmin (vector)\n"
				"    hullmax (vector)\n"
				"Actions\n"
				"    rename: set entity targetname\n"
				"    settarget: set entity target (only targetnames)\n"
				"    setmodel: set entity model\n"
				"    set: set <key> <value> by server library\n"
				"        See game FGD to get list.\n"
				"        command takes two arguments\n"
				"    touch: touch entity by current player.\n"
				"    use: use entity by current player.\n"
				"    movehere: place entity in player fov.\n"
				"    drop2floor: place entity to nearest floor surface\n"
				"    moveup: move entity to 25 units up\n"
				"Flags:\n"
				"        (Set/clear specified flag bit, arg is bit number)\n"
				"    setflag\n"
				"    clearflag\n"
				"    setspawnflag\n"
				"    clearspawnflag\n"
			);
			return true;
		}
		else
		{
			SV_ClientPrintf( cl, "Unknown command %s!\nUse \"ent_fire 0 help\" to list commands.\n", Cmd_Argv( 2 ) );
			return false;
		}
		if( single )
			break;
	}
	return true;
}

/*
===============
SV_EntSendVars
===============
*/
static void SV_EntSendVars( sv_client_t *cl, edict_t *ent )
{
	if( !ent )
		return;

	MSG_WriteByte( &cl->netchan.message, svc_stufftext );
	MSG_WriteStringf( &cl->netchan.message, "set ent_last_name \"%s\"\n", STRING( ent->v.targetname ));
	MSG_WriteByte( &cl->netchan.message, svc_stufftext );
	MSG_WriteStringf( &cl->netchan.message, "set ent_last_num %i\n", NUM_FOR_EDICT( ent ));
	MSG_WriteByte( &cl->netchan.message, svc_stufftext );
	MSG_WriteStringf( &cl->netchan.message, "set ent_last_inst !%i_%i\n", NUM_FOR_EDICT( ent ), ent->serialnumber );
	MSG_WriteByte( &cl->netchan.message, svc_stufftext );
	MSG_WriteStringf( &cl->netchan.message, "set ent_last_origin \"%f %f %f\"\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );
	MSG_WriteByte( &cl->netchan.message, svc_stufftext );
	MSG_WriteStringf( &cl->netchan.message, "set ent_last_class \"%s\"\n", STRING( ent->v.classname ));
	MSG_WriteByte( &cl->netchan.message, svc_stufftext );
	MSG_WriteString( &cl->netchan.message, "ent_getvars_cb\n" ); // why do we need this?
}

/*
===============
SV_EntCreate_f

Create new entity with specified name.
===============
*/
static qboolean SV_EntCreate_f( sv_client_t *cl )
{
	edict_t	*ent = NULL;
	int	i = 0;
	string_t classname;

	if( Cmd_Argc() < 2 )
	{
		SV_ClientPrintf( cl, "Use ent_create <classname> <key1> <value1> <key2> <value2> ...\n" );
		return false;
	}

	classname = ALLOC_STRING( Cmd_Argv( 1 ) );

	ent = SV_CreateNamedEntity( 0, classname );

	// Xash3D extension
	if( !ent && svgame.physFuncs.SV_CreateEntity )
	{
		ent = SV_AllocEdict();
		ent->v.classname = classname;
		if( svgame.physFuncs.SV_CreateEntity( ent, (char*)STRING( classname ) ) == -1 )
		{
			if( ent && !ent->free )
				SV_FreeEdict( ent );
			ent = NULL;
		}
	}

	// XashXT does not implement SV_CreateEntity, use saverestore export
	if( !ent && svgame.physFuncs.pfnCreateEntitiesInRestoreList )
	{
		SAVERESTOREDATA data = { 0 };
		ENTITYTABLE table = { 0 };
		data.tableCount = 1;
		data.pTable = &table;
		table.classname = classname;
		table.id = -1;
		table.size = 1;
		svgame.physFuncs.pfnCreateEntitiesInRestoreList( &data, 0, false );
		ent = table.pent;
	}

	if( !ent )
	{
		SV_ClientPrintf( cl, "Invalid entity!\n" );
		return false;
	}

	// choose default origin
	ent->v.origin[2] = cl->edict->v.origin[2] + 25;
	ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) );
	ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) );

	SV_LinkEdict( ent, false );

	// apply keyvalues if supported
	if( svgame.dllFuncs.pfnKeyValue )
	{
		for( i = 2; i < Cmd_Argc() - 1; i++ )
		{
			string keyname;
			string value;
			KeyValueData pkvd;

			// allow split keyvalues to prespawn and postspawn
			if( !Q_strcmp( Cmd_Argv( i ), "|" ) )
				break;

			Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname ));
			Q_strncpy( value, Cmd_Argv( i ), sizeof( value ));
			pkvd.fHandled = false;
			pkvd.szClassName = (char*)STRING( ent->v.classname );
			pkvd.szKeyName = keyname;
			pkvd.szValue = value;
			svgame.dllFuncs.pfnKeyValue( ent, &pkvd );

			if( pkvd.fHandled )
				SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue );
		}
	}

	// set default targetname
	if( !ent->v.targetname )
	{
		string newname, clientname;
		int j;

		for( j = 0; j < sizeof( cl->name ); j++ )
		{
			char c = Q_tolower( cl->name[j] );
			if( c < 'a' || c > 'z' )
				c = '_';
			if( !cl->name[j] )
			{
				clientname[j] = 0;
				break;
			}
			clientname[j] = c;
		}

		// generate name based on nick name and index
		Q_snprintf( newname, sizeof( newname ), "%s_%i_e%i", clientname, cl->userid, NUM_FOR_EDICT( ent ));

		// i know, it may break strict aliasing rules
		// but we will not lose anything in this case.
		Q_strnlwr( newname, newname, sizeof( newname ));
		ent->v.targetname = ALLOC_STRING( newname );
		SV_EntSendVars( cl, ent );
	}

	SV_ClientPrintf( cl, "Created %i: %s, targetname %s\n", NUM_FOR_EDICT( ent ), Cmd_Argv( 1 ), STRING( ent->v.targetname ) );

	if( svgame.dllFuncs.pfnSpawn )
		svgame.dllFuncs.pfnSpawn( ent );

	// now drop entity to floor.
	pfnDropToFloor( ent );

	// force think. Otherwise given weapon may crash server if player touch it before.
	svgame.dllFuncs.pfnThink( ent );
	pfnDropToFloor( ent );

	// apply postspawn keyvales if supported
	if( svgame.dllFuncs.pfnKeyValue )
	{
		for( i = i + 1; i < Cmd_Argc() - 1; i++ )
		{
			string keyname;
			string value;
			KeyValueData pkvd;

			Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname ));
			Q_strncpy( value, Cmd_Argv( i ), sizeof( value ));
			pkvd.fHandled = false;
			pkvd.szClassName = (char*)STRING( ent->v.classname );
			pkvd.szKeyName = keyname;
			pkvd.szValue = value;
			svgame.dllFuncs.pfnKeyValue( ent, &pkvd );

			if( pkvd.fHandled )
				SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue );
		}
	}
	return true;
}

static qboolean SV_EntGetVars_f( sv_client_t *cl )
{
	edict_t *ent = NULL;

	if( Cmd_Argc() != 2 )
	{
		SV_ClientPrintf( cl, "Use ent_getvars <index|name|inst>\n" );
		return false;
	}

	ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) );
	if( Cmd_Argc() )
	{
		if( !SV_IsValidEdict( ent ))
			return false;
	}

	SV_EntSendVars( cl, ent );
	return true;
}

ucmd_t ucmds[] =
{
{ "new", SV_New_f },
{ "god", SV_Godmode_f },
{ "kill", SV_Kill_f },
{ "begin", SV_Begin_f },
{ "spawn", SV_Spawn_f },
{ "pause", SV_Pause_f },
{ "noclip", SV_Noclip_f },
{ "setinfo", SV_SetInfo_f },
{ "sendres", SV_SendRes_f },
{ "notarget", SV_Notarget_f },
{ "info", SV_ShowServerinfo_f },
{ "dlfile", SV_DownloadFile_f },
{ "disconnect", SV_Disconnect_f },
{ "userinfo", SV_UpdateUserinfo_f },
{ "_sv_build_info", SV_SendBuildInfo_f },
{ NULL, NULL }
};

ucmd_t enttoolscmds[] =
{
{ "ent_list", SV_EntList_f },
{ "ent_info", SV_EntInfo_f },
{ "ent_fire", SV_EntFire_f },
{ "ent_create", SV_EntCreate_f },
{ "ent_getvars", SV_EntGetVars_f },
{ NULL, NULL }
};

/*
==================
SV_ExecuteUserCommand
==================
*/
static void SV_ExecuteClientCommand( sv_client_t *cl, const char *s )
{
	ucmd_t	*u;

	Cmd_TokenizeString( s );

	for( u = ucmds; u->name; u++ )
	{
		if( !Q_strcmp( Cmd_Argv( 0 ), u->name ))
		{
			if( !u->func( cl ))
				Con_Printf( "'%s' is not valid from the console\n", u->name );
			else Con_Reportf( "ucmd->%s()\n", u->name );
			break;
		}
	}

	if( !u->name && sv_enttools_enable.value > 0.0f && !sv.background )
	{
		for( u = enttoolscmds; u->name; u++ )
		{
			if( !Q_strcmp( Cmd_Argv( 0 ), u->name ))
			{
				Con_Reportf( "enttools->%s(): %s\n", u->name, s );
				Log_Printf( "\"%s<%i><%s><>\" performed: %s\n", Info_ValueForKey( cl->userinfo, "name" ),
							cl->userid, SV_GetClientIDString( cl ), s );

				if( u->func )
					u->func( cl );

				break;
			}
		}
	}

	if( !u->name && sv.state == ss_active )
	{
		qboolean fullupdate = !Q_strcmp( Cmd_Argv( 0 ), "fullupdate" );

		if( fullupdate )
		{
			if( sv_fullupdate_penalty_time.value && host.realtime < cl->fullupdate_next_calltime )
				return;
		}

		// custom client commands
		svgame.dllFuncs.pfnClientCommand( cl->edict );

		if( fullupdate )
		{
			// resend the ambient sounds for demo recording
			SV_RestartAmbientSounds();
			// resend all the decals for demo recording
			SV_RestartDecals();
			// resend all the static ents for demo recording
			SV_RestartStaticEnts();
			// resend the viewentity
			SV_UpdateClientView( cl );

			if( sv_fullupdate_penalty_time.value )
				cl->fullupdate_next_calltime = host.realtime + sv_fullupdate_penalty_time.value;
		}
	}
}

/*
=================
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( netadr_t from, sizebuf_t *msg )
{
	char	*args;
	const char	*pcmd;
	char buf[MAX_SYSPATH];
	int	len = sizeof( buf );

	// prevent flooding from banned address
	if( SV_CheckIP( &from ) )
		return;

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

	args = MSG_ReadStringLine( msg );
	Cmd_TokenizeString( args );

	pcmd = Cmd_Argv( 0 );

	if( sv_log_outofband.value )
		Con_Reportf( "SV_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), pcmd );

	if( !Q_strcmp( pcmd, "ping" )) SV_Ping( from );
	else if( !Q_strcmp( pcmd, "ack" )) SV_Ack( from );
	else if( !Q_strcmp( pcmd, "info" )) SV_Info( from, Q_atoi( Cmd_Argv( 1 )));
	else if( !Q_strcmp( pcmd, "bandwidth" )) SV_TestBandWidth( from );
	else if( !Q_strcmp( pcmd, "getchallenge" )) SV_GetChallenge( from );
	else if( !Q_strcmp( pcmd, "connect" )) SV_ConnectClient( from );
	else if( !Q_strcmp( pcmd, "rcon" )) SV_RemoteCommand( from, msg );
	else if( !Q_strcmp( pcmd, "netinfo" )) SV_BuildNetAnswer( from );
	else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg );
	else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING
	else if( SV_SourceQuery_HandleConnnectionlessPacket( pcmd, from )) { } // function handles replies
	else if( !Q_strcmp( pcmd, "c" ) && sv_nat.value && NET_IsMasterAdr( from ))
	{
		netadr_t to;
		if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to ))
			SV_Info( to, PROTOCOL_VERSION );
	}
	else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len ))
	{
		// user out of band message (must be handled in CL_ConnectionlessPacket)
		if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, (byte*)buf );
	}
	else Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args );
}

/*
==================
SV_ParseClientMove

The message usually contains all the movement commands
that were in the last three packets, so that the information
in dropped packets can be recovered.

On very fast clients, there may be multiple usercmd packed into
each of the backup packets.
==================
*/
static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg )
{
	client_frame_t	*frame;
	int		key, size, checksum1, checksum2;
	int		i, numbackup, totalcmds, numcmds;
	usercmd_t		nullcmd, *to, *from;
	usercmd_t		cmds[CMD_BACKUP];
	float		packet_loss;
	edict_t		*player;
	model_t		*model;

	player = cl->edict;

	frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];
	memset( &nullcmd, 0, sizeof( usercmd_t ));
	memset( cmds, 0, sizeof( cmds ));

	key = MSG_GetRealBytesRead( msg );
	checksum1 = MSG_ReadByte( msg );
	packet_loss = MSG_ReadByte( msg );

	numbackup = MSG_ReadByte( msg );
	numcmds = MSG_ReadByte( msg );

	totalcmds = numcmds + numbackup;
	net_drop -= (numcmds - 1);

	if( totalcmds < 0 || totalcmds >= CMD_MASK )
	{
		Con_Reportf( S_ERROR "SV_ParseClientMove: %s sending too many commands %i\n", cl->name, totalcmds );
		SV_DropClient( cl, false );
		return;
	}

	from = &nullcmd;	// first cmd are starting from null-compressed usercmd_t

	for( i = totalcmds - 1; i >= 0; i-- )
	{
		to = &cmds[i];
		MSG_ReadDeltaUsercmd( msg, from, to );
		from = to; // get new baseline
	}

	if( cl->state != cs_spawned )
		return;

	// if the checksum fails, ignore the rest of the packet
	size = MSG_GetRealBytesRead( msg ) - key - 1;
	checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence );

	if( checksum2 != checksum1 )
	{
		Con_Reportf( S_ERROR "SV_UserMove: failed command checksum for %s (%d != %d)\n", cl->name, checksum2, checksum1 );
		return;
	}

	cl->packet_loss = packet_loss;

	// freeze player for some reasons if loadgame was executed
	if( GameState->loadGame )
		return;

	// check for pause or frozen
	if( sv.paused || !CL_IsInGame() || SV_PlayerIsFrozen( player ))
	{
		for( i = 0; i < numcmds; i++ )
		{
			cmds[i].msec = 0;
			cmds[i].forwardmove = 0;
			cmds[i].sidemove = 0;
			cmds[i].upmove = 0;
			cmds[i].buttons = 0;

			if( SV_PlayerIsFrozen( player ))
				cmds[i].impulse = 0;

			VectorCopy( cmds[i].viewangles, player->v.v_angle );
		}
		net_drop = 0;
	}
	else
	{
		if( !player->v.fixangle )
			VectorCopy( cmds[0].viewangles, player->v.v_angle );
	}

	SV_EstablishTimeBase( cl, cmds, net_drop, numbackup, numcmds );

	if( net_drop < 24 )
	{
		while( net_drop > numbackup )
		{
			SV_RunCmd( cl, &cl->lastcmd, 0 );
			net_drop--;
		}

		while( net_drop > 0 )
		{
			i = numcmds + net_drop - 1;
			SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );
			net_drop--;
		}
	}

	for( i = numcmds - 1; i >= 0; i-- )
	{
		SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );
	}

	cl->lastcmd = cmds[0];

	// adjust latency time by 1/2 last client frame since
	// the message probably arrived 1/2 through client's frame loop
	frame->ping_time -= ( cl->lastcmd.msec * 0.5f ) / 1000.0f;
	frame->ping_time = Q_max( 0.0f, frame->ping_time );
	model = SV_ModelHandle( player->v.modelindex );

	if( model && model->type == mod_studio )
	{
		// g-cont. yes we using svgame.globals->time instead of sv.time
		if( player->v.animtime > svgame.globals->time + sv.frametime )
			player->v.animtime = svgame.globals->time + sv.frametime;
	}
}

/*
===================
SV_ParseResourceList

Parse resource list
===================
*/
static void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg )
{
	int		totalsize;
	resource_t	*resource;
	int		i, total;
	resourceinfo_t	ri;

	total = MSG_ReadShort( msg );

	SV_ClearResourceList( &cl->resourcesneeded );
	SV_ClearResourceList( &cl->resourcesonhand );

	for( i = 0; i < total; i++ )
	{
		resource = Z_Calloc( sizeof( resource_t ) );
		Q_strncpy( resource->szFileName, MSG_ReadString( msg ), sizeof( resource->szFileName ));
		resource->type = MSG_ReadByte( msg );
		resource->nIndex = MSG_ReadShort( msg );
		resource->nDownloadSize = MSG_ReadLong( msg );
		resource->ucFlags = MSG_ReadByte( msg );
		resource->pNext = NULL;
		resource->pPrev = NULL;
		ClearBits( resource->ucFlags, RES_WASMISSING );

		if( FBitSet( resource->ucFlags, RES_CUSTOM ))
			MSG_ReadBytes( msg, resource->rgucMD5_hash, 16 );

		if( resource->type > t_world || resource->nDownloadSize > 1024 * 1024 * 1024 )
		{
			SV_ClearResourceList( &cl->resourcesneeded );
			SV_ClearResourceList( &cl->resourcesonhand );
			return;
		}
		SV_AddToResourceList( resource, &cl->resourcesneeded );
	}

	totalsize = COM_SizeofResourceList( &cl->resourcesneeded, &ri );

	if( totalsize != 0 && sv_allow_upload.value )
	{
		Con_DPrintf( "Verifying and uploading resources...\n" );

		if( totalsize != 0 )
		{
			Con_DPrintf( "Custom resources total %.2fK\n", totalsize / 1024.0 );

			if ( ri.info[t_model].size != 0 )
				Con_DPrintf( "  Models:  %.2fK\n", ri.info[t_model].size / 1024.0 );

			if ( ri.info[t_sound].size != 0 )
				Con_DPrintf( "  Sounds:  %.2fK\n", ri.info[t_sound].size / 1024.0 );

			if ( ri.info[t_decal].size != 0 )
				Con_DPrintf( "  Decals:  %.2fK\n", ri.info[t_decal].size / 1024.0 );

			if ( ri.info[t_skin].size != 0 )
				Con_DPrintf( "  Skins :  %.2fK\n", ri.info[t_skin].size / 1024.0 );

			if ( ri.info[t_generic].size != 0 )
				Con_DPrintf( "  Generic :  %.2fK\n", ri.info[t_generic].size / 1024.0 );

			if ( ri.info[t_eventscript].size != 0 )
				Con_DPrintf( "  Events  :  %.2fK\n", ri.info[t_eventscript].size / 1024.0 );

			Con_DPrintf( "----------------------\n" );
		}

		totalsize = SV_EstimateNeededResources( cl );

		if( totalsize > sv_uploadmax.value * 1024 * 1024 )
		{
			SV_ClearResourceList( &cl->resourcesneeded );
			SV_ClearResourceList( &cl->resourcesonhand );
			return;
		}
		Con_DPrintf( "resources to request: %s\n", Q_memprint( totalsize ));
	}

	cl->upstate = us_processing;
	SV_BatchUploadRequest( cl );
}

/*
===================
SV_ParseCvarValue

Parse a requested value from client cvar
===================
*/
static void SV_ParseCvarValue( sv_client_t *cl, sizebuf_t *msg )
{
	const char *value = MSG_ReadString( msg );

	if( svgame.dllFuncs2.pfnCvarValue != NULL )
		svgame.dllFuncs2.pfnCvarValue( cl->edict, value );
	Con_Reportf( "Cvar query response: name:%s, value:%s\n", cl->name, value );
}

/*
===================
SV_ParseCvarValue2

Parse a requested value from client cvar
===================
*/
static void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg )
{
	string	name, value;
	int	requestID = MSG_ReadLong( msg );

	Q_strncpy( name, MSG_ReadString( msg ), sizeof( name ));
	Q_strncpy( value, MSG_ReadString( msg ), sizeof( value ));

	if( svgame.dllFuncs2.pfnCvarValue2 != NULL )
		svgame.dllFuncs2.pfnCvarValue2( cl->edict, requestID, name, value );
	Con_Reportf( "Cvar query response: name:%s, request ID %d, cvar:%s, value:%s\n", cl->name, requestID, name, value );
}

/*
===================
SV_ParseVoiceData
===================
*/
static void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg )
{
	char received[4096];
	sv_client_t	*cur;
	int i, client;
	uint length, size, frames;

	cl->m_bLoopback = MSG_ReadByte( msg );

	frames = MSG_ReadByte( msg );

	size = MSG_ReadShort( msg );
	client = cl - svs.clients;

	if( size > sizeof( received ))
	{
		Con_DPrintf( "SV_ParseVoiceData: invalid incoming packet.\n" );
		SV_DropClient( cl, false );
		return;
	}

	MSG_ReadBytes( msg, received, size );

	if( !sv_voiceenable.value || svs.maxclients <= 1 )
		return;

	for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ )
	{
		if( cl != cur )
		{
			if( cur->state < cs_connected )
				continue;

			if( !FBitSet( cur->listeners, BIT( client )))
				continue;
		}

		length = size;

		// 6 is a number of bytes for other parts of message
		if( MSG_GetNumBytesLeft( &cur->datagram ) < length + 6 )
			continue;

		if( cl == cur && !cur->m_bLoopback )
			length = 0;

		MSG_BeginServerCmd( &cur->datagram, svc_voicedata );
		MSG_WriteByte( &cur->datagram, client );
		MSG_WriteByte( &cur->datagram, frames );
		MSG_WriteShort( &cur->datagram, length );
		MSG_WriteBytes( &cur->datagram, received, length );
	}
}

/*
===================
SV_ExecuteClientMessage

Parse a client packet
===================
*/
void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg )
{
	qboolean		move_issued = false;
	client_frame_t	*frame;
	int		c;

	ASSERT( cl->frames != NULL );

	// calc ping time
	frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];

	// ping time doesn't factor in message interval, either
	frame->ping_time = host.realtime - frame->senttime - cl->cl_updaterate;

	// on first frame ( no senttime ) don't skew ping
	if( frame->senttime == 0.0f ) frame->ping_time = 0.0f;

	// don't skew ping based on signon stuff either
	if(( host.realtime - cl->connection_started ) < 2.0f && ( frame->ping_time > 0.0f ))
		frame->ping_time = 0.0f;

	cl->latency = SV_CalcClientTime( cl );
	cl->delta_sequence = -1; // no delta unless requested

	// read optional clientCommand strings
	while( cl->state != cs_zombie )
	{
		if( MSG_CheckOverflow( msg ))
		{
			Con_DPrintf( S_ERROR "incoming overflow for %s\n", cl->name );
			SV_DropClient( cl, false );
			return;
		}

		// end of message
		if( MSG_GetNumBitsLeft( msg ) < 8 )
			break;

		c = MSG_ReadClientCmd( msg );

		switch( c )
		{
		case clc_nop:
			break;
		case clc_delta:
			cl->delta_sequence = MSG_ReadByte( msg );
			break;
		case clc_move:
			if( move_issued ) return; // someone is trying to cheat...
			move_issued = true;
			SV_ParseClientMove( cl, msg );
			break;
		case clc_stringcmd:
			SV_ExecuteClientCommand( cl, MSG_ReadString( msg ));
			if( cl->state == cs_zombie )
				return; // disconnect command
			break;
		case clc_resourcelist:
			SV_ParseResourceList( cl, msg );
			break;
		case clc_fileconsistency:
			SV_ParseConsistencyResponse( cl, msg );
			break;
		case clc_voicedata:
			SV_ParseVoiceData( cl, msg );
			break;
		case clc_requestcvarvalue:
			SV_ParseCvarValue( cl, msg );
			break;
		case clc_requestcvarvalue2:
			SV_ParseCvarValue2( cl, msg );
			break;
		default:
			Con_DPrintf( S_ERROR "%s: clc_bad\n", cl->name );
			SV_DropClient( cl, false );
			return;
		}
	}
 }
