Ejemplo n.º 1
0
/*
* SV_SetServerConfigStrings
*/
void SV_SetServerConfigStrings( void )
{
	Q_snprintfz( sv.configstrings[CS_MAXCLIENTS], sizeof( sv.configstrings[0] ), "%i", sv_maxclients->integer );
	Q_strncpyz( sv.configstrings[CS_TVSERVER], "0", sizeof( sv.configstrings[0] ) );
	Q_strncpyz( sv.configstrings[CS_HOSTNAME], Cvar_String( "sv_hostname" ), sizeof( sv.configstrings[0] ) );
	Q_strncpyz( sv.configstrings[CS_MODMANIFEST], Cvar_String( "sv_modmanifest" ), sizeof( sv.configstrings[0] ) );
}
Ejemplo n.º 2
0
// will return short version of player's nick for teamplay messages
LOCAL char *TP_ShortNick(void)
{
	static char buf[64];
 
	if (*(cl_fakename.string)) return "";
	else {
		if (*(Cvar_String("nick"))) { // isn't cl_fakename and name enough?
			snprintf(buf, sizeof(buf), "$\\%s%s", Cvar_String("nick"), Cvar_String("cl_fakename_suffix"));
		} else {
			snprintf(buf, sizeof(buf), "$\\%.3s%s", TP_PlayerName(), Cvar_String("cl_fakename_suffix"));
		}
		return buf;
	}
}      
Ejemplo n.º 3
0
Archivo: cl_main.c Proyecto: luaman/zq
/*
================
CL_Connect_f

MAUTH version -- kick off an authentication sequence as first part of
connection routine.
================
*/
void CL_Connect_f (void)
{
	char	*server;
	char	*masterserver;
    char    data[2048];

	if (Cmd_Argc() != 3)
	{
		Com_Printf ("usage: connect <master> <server>\n");
		return;
	}
	
	Host_EndGame ();
	
    server = Cmd_Argv (1);
	strlcpy (cls.servername, server, sizeof(cls.servername));

    // Have to check for valid server address here as we must send to master...
	if (!NET_StringToAdr (cls.servername, &cls.server_adr))
	{
		Com_Printf ("Bad server address\n");
		return;
	}
	if (cls.server_adr.port == 0)
		cls.server_adr.port = BigShort (PORT_SERVER);

	masterserver = Cmd_Argv (2);
	strlcpy (cls.masterservername, masterserver, sizeof(cls.masterservername));

    // Start off auth sequence before trying to connect...
	if (!NET_StringToAdr (cls.masterservername, &cls.masterserver_adr))
	{
		Com_Printf ("Bad master server address\n");
		return;
	}
	if (cls.masterserver_adr.port == 0)
		cls.masterserver_adr.port = BigShort (27000);  // master port

	Com_Printf ("Attempting to auth with %s...\n", cls.masterservername);
	sprintf (data, "%c\n%s\n%s:%d\n",
            C2M_AUTH_INIT,
            Cvar_String("name"),
            cls.servername,
            cls.server_adr.port);
	NET_SendPacket (NS_CLIENT, strlen(data), data, cls.masterserver_adr);

    // Normal connection procedure...
    // FIXME wait?
    // FIXME use some qbools to work out if we get a NACK to stop trying to connect -- ie preserve state
	CL_BeginServerConnect();
}
Ejemplo n.º 4
0
/*
* CL_CvarInfoRequest_f
*/
static void CL_CvarInfoRequest_f( void )
{
	char string[MAX_STRING_CHARS];
	char *cvarName;
	const char *cvarString;

	if( cls.demo.playing )
		return;

	if( Cmd_Argc() < 1 )
		return;

	cvarName = Cmd_Argv( 1 );

	string[0] = 0;
	Q_strncatz( string, "cvarinfo \"", sizeof( string ) );

	if( strlen( string ) + strlen( cvarName ) + 1 /*quote*/ + 1 /*space*/ >= MAX_STRING_CHARS - 1 )
	{
		CL_AddReliableCommand( "cvarinfo \"invalid\"" );
		return;
	}

	Q_strncatz( string, cvarName, sizeof( string ) );
	Q_strncatz( string, "\" ", sizeof( string ) );

	cvarString = Cvar_String( cvarName );
	if( !cvarString[0] )
		cvarString = "not found";

	if( strlen( string ) + strlen( cvarString ) + 2 /*quotes*/ >= MAX_STRING_CHARS - 1 )
	{
		if( strlen( string ) + strlen( " \"too long\"" ) < MAX_STRING_CHARS - 1 )
			CL_AddReliableCommand( va( "%s\"too long\"", string ) );
		else
			CL_AddReliableCommand( "cvarinfo \"invalid\"" );
			
		return;
	}

	Q_strncatz( string, "\"", sizeof( string ) );
	Q_strncatz( string, cvarString, sizeof( string ) );
	Q_strncatz( string, "\"", sizeof( string ) );

	CL_AddReliableCommand( string );
}
Ejemplo n.º 5
0
/**
 * Writes the tags of the server for filtering in the Steam server browser.
 *
 * @param tags string where to write the tags (at least MAX_STEAMQUERY_TAG_STRING bytes)
 */
static void SV_GetSteamTags( char *tags )
{
	// Currently there is no way to filter by tag in the game itself,
	// so this is mostly to make sure the tags aren't empty on old servers if they are added.

	Q_strncpyz( tags, Cvar_String( "g_gametype" ), MAX_STEAMQUERY_TAG_STRING );

	if( Cvar_Value( "g_instagib" ) )
	{
		if( tags[0] )
			Q_strncatz( tags, ",", MAX_STEAMQUERY_TAG_STRING );
		Q_strncatz( tags, "instagib", MAX_STEAMQUERY_TAG_STRING );
	}

	// If sv_tags cvar is added, every comma-separated tag from the cvar must be added separately
	// (so the last tag exceeding MAX_STEAMQUERY_TAG_STRING isn't cut off)
	// and validated not to contain any characters disallowed in userinfo (CVAR_SERVERINFO).
}
Ejemplo n.º 6
0
/*
* CL_MasterAddressCache_Init
*/
static void CL_MasterAddressCache_Init( void )
{
	char *master;
	const char *mlist;

	serverlist_masters_head = NULL;
	Trie_Create( TRIE_CASE_INSENSITIVE, &serverlist_masters_trie );

	// synchronous DNS queries cause interruption of sounds, background music, etc
	// so precache results of resolution queries for all master servers
	mlist = Cvar_String( "masterservers" );
	while( mlist )
	{
		master = COM_Parse( &mlist );
		if( !*master )
			break;
		CL_ResolveMasterAddress( master, NULL );
	}
}
Ejemplo n.º 7
0
static char *SV_ShortInfoString( void )
{
	static char string[MAX_STRING_SVCINFOSTRING];
	char hostname[64];
	char entry[20];
	size_t len;
	int i, count, bots;
	const char *password;

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

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

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

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

	if( Q_stricmp( FS_GameDirectory(), FS_BaseGameDirectory() ) )
	{
		Q_snprintfz( entry, sizeof( entry ), "mo\\\\%8s\\\\", FS_GameDirectory() );
		if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) )
		{
			Q_strncatz( string, entry, sizeof( string ) );
			len = strlen( string );
		}
	}

	if( Cvar_Value( "g_instagib" ) )
	{
		Q_snprintfz( entry, sizeof( entry ), "ig\\\\1\\\\" );
		if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) )
		{
			Q_strncatz( string, entry, sizeof( string ) );
			len = strlen( string );
		}
	}


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

	password = Cvar_String( "password" );
	if( password[0] != '\0' )
	{
		Q_snprintfz( entry, sizeof( entry ), "p\\\\1\\\\" );
		if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) )
		{
			Q_strncatz( string, entry, sizeof( string ) );
			len = strlen( string );
		}
	}

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

	if( SV_MM_Initialized() )
	{
		Q_snprintfz( entry, sizeof( entry ), "mm\\\\1\\\\" );
		if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) )
		{
			Q_strncatz( string, entry, sizeof( string ) );
			len = strlen( string );
		}
	}

	if( Cvar_Value( "g_race_gametype" ) )
	{
		Q_snprintfz( entry, sizeof( entry ), "r\\\\1\\\\" );
		if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) )
		{
			Q_strncatz( string, entry, sizeof( string ) );
			len = strlen( string );
		}
	}

	// finish it
	Q_strncatz( string, "EOT", sizeof( string ) );
	return string;
}
Ejemplo n.º 8
0
/*
* TV_Init
* 
* Only called at plat.exe startup, not for each game
*/
void TV_Init( void )
{
	Com_Printf( "Initializing " APPLICATION " TV server\n" );

	tv_mempool = Mem_AllocPool( NULL, "TV" );

	TV_AddCommands();

	Cvar_Get( "protocol", va( "%i", APP_PROTOCOL_VERSION ), CVAR_SERVERINFO | CVAR_NOSET );
	Cvar_Get( "gamename", Cvar_String( "gamename" ), CVAR_SERVERINFO | CVAR_NOSET );

	tv_password = Cvar_Get( "tv_password", "", 0 );

	tv_ip = Cvar_Get( "tv_ip", "", CVAR_ARCHIVE | CVAR_NOSET );
	tv_port = Cvar_Get( "tv_port", va( "%i", PORT_TV_SERVER ), CVAR_ARCHIVE | CVAR_NOSET );
	tv_ip6 = Cvar_Get( "tv_ip6", "::", CVAR_ARCHIVE | CVAR_NOSET );
	tv_port6 = Cvar_Get( "tv_port6", va( "%i", PORT_TV_SERVER ), CVAR_ARCHIVE | CVAR_NOSET );
#ifdef TCP_ALLOW_TVCONNECT
	tv_udp = Cvar_Get( "tv_udp", "1", CVAR_SERVERINFO | CVAR_NOSET );
	tv_tcp = Cvar_Get( "tv_tcp", "1", CVAR_SERVERINFO | CVAR_NOSET );
#else
	tv_udp = Cvar_Get( "tv_udp", "1", CVAR_NOSET );
#endif

#ifndef TCP_ALLOW_TVCONNECT
	Cvar_FullSet( "tv_tcp", "0", CVAR_READONLY, true );
#endif

	tv_reconnectlimit = Cvar_Get( "tv_reconnectlimit", "3", CVAR_ARCHIVE );
	tv_timeout = Cvar_Get( "tv_timeout", "125", 0 );
	tv_zombietime = Cvar_Get( "tv_zombietime", "2", 0 );
	tv_name = Cvar_Get( "tv_name", APPLICATION "[TV]", CVAR_SERVERINFO | CVAR_ARCHIVE );
	tv_compresspackets = Cvar_Get( "tv_compresspackets", "1", 0 );
	tv_maxclients = Cvar_Get( "tv_maxclients", "32", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_NOSET );
	tv_maxmvclients = Cvar_Get( "tv_maxmvclients", "4", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_NOSET );
	tv_public = Cvar_Get( "tv_public", "1", CVAR_ARCHIVE | CVAR_SERVERINFO );
	tv_rcon_password = Cvar_Get( "tv_rcon_password", "", 0 );
	tv_autorecord = Cvar_Get( "tv_autorecord", "", CVAR_ARCHIVE );
	tv_lobbymusic = Cvar_Get( "tv_lobbymusic", "", CVAR_ARCHIVE );

	tv_masterservers = Cvar_Get( "tv_masterservers", DEFAULT_MASTER_SERVERS_IPS, CVAR_LATCH );
	tv_masterservers_steam = Cvar_Get( "tv_masterservers_steam", DEFAULT_MASTER_SERVERS_STEAM_IPS, CVAR_LATCH );

	// flood control
	tv_floodprotection_messages = Cvar_Get( "tv_floodprotection_messages", "10", 0 );
	tv_floodprotection_messages->modified = true;
	tv_floodprotection_seconds = Cvar_Get( "tv_floodprotection_seconds", "4", 0 );
	tv_floodprotection_seconds->modified = true;
	tv_floodprotection_penalty = Cvar_Get( "tv_floodprotection_delay", "20", 0 );
	tv_floodprotection_penalty->modified = true;

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

	if( tv_maxclients->integer )
	{
		tvs.clients = ( client_t * )Mem_Alloc( tv_mempool, sizeof( client_t ) * tv_maxclients->integer );
	}
	else
	{
		tvs.clients = NULL;
	}
	tvs.lobby.spawncount = rand();
	tvs.lobby.snapFrameTime = 100;

	// IPv4
	if( !NET_StringToAddress( tv_ip->string, &tvs.address ) )
		Com_Error( ERR_FATAL, "Couldn't understand address of tv_ip cvar: %s\n", NET_ErrorString() );
	NET_SetAddressPort( &tvs.address, tv_port->integer );

	if( tv_udp->integer )
	{
		if( !NET_OpenSocket( &tvs.socket_udp, SOCKET_UDP, &tvs.address, true ) )
		{
			Com_Printf( "Error: Couldn't open UDP socket: %s\n", NET_ErrorString() );
			Cvar_ForceSet( tv_udp->name, "0" );
		}
	}

	// IPv6
	if( !NET_StringToAddress( tv_ip6->string, &tvs.addressIPv6 ) )
		Com_Error( ERR_FATAL, "Couldn't understand address of tv_ip6 cvar: %s\n", NET_ErrorString() );
	NET_SetAddressPort( &tvs.addressIPv6, tv_port6->integer );

	if( tvs.addressIPv6.type != NA_NOTRANSMIT )
	{
		if( !NET_OpenSocket( &tvs.socket_udp6, SOCKET_UDP, &tvs.addressIPv6, true ) )
		{
			Com_Printf( "Error: Couldn't open UDP6 socket: %s\n", NET_ErrorString() );
		}
	}

#ifdef TCP_ALLOW_TVCONNECT
	if( tv_tcp->integer )
	{
		bool err = true;

		if( !NET_OpenSocket( &tvs.socket_tcp, SOCKET_TCP, &tvs.address, true ) )
		{
			Com_Printf( "Error: Couldn't open TCP socket: %s\n", NET_ErrorString() );
		}
		else
		{
			NET_SetSocketNoDelay( &tvs.socket_tcp, 1 );
			if( !NET_Listen( &tvs.socket_tcp ) )
			{
				Com_Printf( "Error: Couldn't listen to TCP socket: %s\n", NET_ErrorString() );
			}
			else
			{
				err = false;
			}
		}

		if( tvs.addressIPv6.type != NA_NOTRANSMIT )
		{
			if( !NET_OpenSocket( &tvs.socket_tcp6, SOCKET_TCP, &tvs.addressIPv6, true ) )
			{
				Com_Printf( "Error: Couldn't open TCP6 socket: %s\n", NET_ErrorString() );
			}
			else
			{
				NET_SetSocketNoDelay( &tvs.socket_tcp6, 1 );
				if( !NET_Listen( &tvs.socket_tcp6 ) )
				{
					Com_Printf( "Error: Couldn't listen to TCP6 socket: %s\n", NET_ErrorString() );
				}
				else
				{
					err = false;
				}
			}
		}

		if( err ) {
			Cvar_ForceSet( tv_tcp->name, "0" );
		}
	}
#endif

	TV_Downstream_InitMaster();
}
Ejemplo n.º 9
0
/*
* CL_MasterAddressCache_Init
*/
static void CL_MasterAddressCache_Init( void )
{
	int numMasters;
	const char *ptr;
	const char *master;
	const char *masterservers;

	Trie_Create( TRIE_CASE_INSENSITIVE, &serverlist_masters_trie );

	resolverThreads = NULL;

	masterservers = Cvar_String( "masterservers" );
	if( !*masterservers ) {
		return;
	}

	// count the number of master servers
	numMasters = 0;
	for( ptr = masterservers; ptr; ) {
		master = COM_Parse( &ptr );
		if( !*master ) {
			break;
		}
		numMasters++;
	}

	// don't allow too many as each will spawn its own resolver thread
	if( numMasters > MAX_MASTER_SERVERS )
		numMasters = MAX_MASTER_SERVERS;

	resolveLock = QMutex_Create();
	if( resolveLock != NULL )
	{
		unsigned numResolverThreads;

		numResolverThreads = 0;
		resolverThreads = malloc( sizeof( *resolverThreads ) * (numMasters+1) );
		memset( resolverThreads, 0, sizeof( *resolverThreads ) * (numMasters+1) );

		for( ptr = masterservers; ptr; )
		{
			char *master_copy;
			qthread_t *thread;

			master = COM_Parse( &ptr );
			if( !*master )
				break;

			master_copy = ( char * )malloc( strlen( master ) + 1 );
			memcpy( master_copy, master, strlen( master ) + 1 );

			thread = QThread_Create( CL_MasterResolverThreadEntry, ( void * )master_copy );
			if( thread != NULL ) {
				resolverThreads[numResolverThreads++] = thread;
				continue;
			}

			// we shouldn't get here if all goes well with the resolving thread
			free( master_copy );
		}
	}
}
Ejemplo n.º 10
0
/**
 * Responds to a Steam server query.
 *
 * @param s       query string
 * @param socket  response socket
 * @param address response address
 * @param inmsg   message for arguments
 * @return whether the request was handled as a Steam query
 */
bool SV_SteamServerQuery( const char *s, const socket_t *socket, const netadr_t *address, msg_t *inmsg )
{
#if APP_STEAMID
	if( sv.state < ss_loading || sv.state > ss_game )
		return false; // server not running

	if( ( !sv_public->integer && !NET_IsLANAddress( address ) ) || ( sv_maxclients->integer == 1 ) )
		return false;

	if( !strcmp( s, "i" ) )
	{
		// ping
		const char pingResponse[] = "j00000000000000";
		Netchan_OutOfBand( socket, address, sizeof( pingResponse ), ( const uint8_t * )pingResponse );
		return true;
	}

	if( !strcmp( s, "W" ) || !strcmp( s, "U\xFF\xFF\xFF\xFF" ) )
	{
		// challenge - security feature, but since we don't send multiple packets always return 0
		const uint8_t challengeResponse[] = { 'A', 0, 0, 0, 0 };
		Netchan_OutOfBand( socket, address, sizeof( challengeResponse ), ( const uint8_t * )challengeResponse );
		return true;
	}

	if( !strcmp( s, "TSource Engine Query" ) )
	{
		// server info
		char hostname[MAX_INFO_VALUE];
		char gamedir[MAX_QPATH];
		char gamename[128];
		char version[32];
		char tags[MAX_STEAMQUERY_TAG_STRING];
		int i, players = 0, bots = 0, maxclients = 0;
		int flags = 0x80 | 0x01; // game port | game ID containing app ID
		client_t *cl;
		msg_t msg;
		uint8_t msgbuf[MAX_STEAMQUERY_PACKETLEN - sizeof( int32_t )];

		if( sv_showInfoQueries->integer )
			Com_Printf( "Steam Info Packet %s\n", NET_AddressToString( address ) );

		Q_strncpyz( hostname, COM_RemoveColorTokens( sv_hostname->string ), sizeof( hostname ) );
		if( !hostname[0] )
			Q_strncpyz( hostname, sv_hostname->dvalue, sizeof( hostname ) );
		Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) );

		Q_strncpyz( gamename, APPLICATION, sizeof( gamename ) );
		if( Cvar_Value( "g_instagib" ) )
			Q_strncatz( gamename, " IG", sizeof( gamename ) );
		if( sv.configstrings[CS_GAMETYPETITLE][0] || sv.configstrings[CS_GAMETYPENAME][0] )
		{
			Q_strncatz( gamename, " ", sizeof( gamename ) );
			Q_strncatz( gamename,
				sv.configstrings[sv.configstrings[CS_GAMETYPETITLE][0] ? CS_GAMETYPETITLE : CS_GAMETYPENAME],
				sizeof( gamename ) );
		}

		for( i = 0; i < sv_maxclients->integer; i++ )
		{
			cl = &svs.clients[i];
			if( cl->state >= CS_CONNECTED )
			{
				if( cl->tvclient ) // exclude TV from the max players count
					continue;
				if( cl->edict->r.svflags & SVF_FAKECLIENT )
					bots++;
				players++;
			}
			maxclients++;
		}

		Q_snprintfz( version, sizeof( version ), "%i.%i.0.0", APP_VERSION_MAJOR, APP_VERSION_MINOR );

		SV_GetSteamTags( tags );
		if( tags[0] )
			flags |= 0x20;

		MSG_Init( &msg, msgbuf, sizeof( msgbuf ) );
		MSG_WriteByte( &msg, 'I' );
		MSG_WriteByte( &msg, APP_PROTOCOL_VERSION );
		MSG_WriteString( &msg, hostname );
		MSG_WriteString( &msg, sv.mapname );
		MSG_WriteString( &msg, gamedir );
		MSG_WriteString( &msg, gamename );
		MSG_WriteShort( &msg, 0 ); // app ID specified later
		MSG_WriteByte( &msg, min( players, 99 ) );
		MSG_WriteByte( &msg, min( maxclients, 99 ) );
		MSG_WriteByte( &msg, min( bots, 99 ) );
		MSG_WriteByte( &msg, ( dedicated && dedicated->integer ) ? 'd' : 'l' );
		MSG_WriteByte( &msg, STEAMQUERY_OS );
		MSG_WriteByte( &msg, Cvar_String( "password" )[0] ? 1 : 0 );
		MSG_WriteByte( &msg, 0 ); // VAC insecure
		MSG_WriteString( &msg, version );
		MSG_WriteByte( &msg, flags );
		// port
		MSG_WriteShort( &msg, sv_port->integer );
		// tags
		if( flags & 0x20 )
			MSG_WriteString( &msg, tags );
		// 64-bit game ID - needed to specify app ID
		MSG_WriteLong( &msg, APP_STEAMID & 0xffffff );
		MSG_WriteLong( &msg, 0 );
		Netchan_OutOfBand( socket, address, msg.cursize, msg.data );
		return true;
	}

	if( s[0] == 'U' )
	{
		// players
		msg_t msg;
		uint8_t msgbuf[MAX_STEAMQUERY_PACKETLEN - sizeof( int32_t )];
		int i, players = 0;
		client_t *cl;
		char name[MAX_NAME_BYTES];
		unsigned int time = Sys_Milliseconds();

		if( sv_showInfoQueries->integer )
			Com_Printf( "Steam Players Packet %s\n", NET_AddressToString( address ) );

		MSG_Init( &msg, msgbuf, sizeof( msgbuf ) );
		MSG_WriteByte( &msg, 'D' );
		MSG_WriteByte( &msg, 0 );

		for( i = 0; i < sv_maxclients->integer; i++ )
		{
			cl = &svs.clients[i];
			if( ( cl->state < CS_CONNECTED ) || cl->tvclient )
				continue;

			Q_strncpyz( name, COM_RemoveColorTokens( cl->name ), sizeof( name ) );
			if( ( msg.cursize + 10 + strlen( name ) ) > sizeof( msgbuf ) )
				break;

			MSG_WriteByte( &msg, i );
			MSG_WriteString( &msg, name );
			MSG_WriteLong( &msg, cl->edict->r.client->r.frags );
			MSG_WriteFloat( &msg, ( float )( time - cl->lastconnect ) * 0.001f );

			players++;
			if( players == 99 )
				break;
		}

		msgbuf[1] = players;
		Netchan_OutOfBand( socket, address, msg.cursize, msg.data );
		return true;
	}

	if( !strcmp( s, "s" ) )
	{
		// master server query, terminated by \n, followed by the challenge
		int i;
		bool fromMaster = false;
		int challenge;
		char gamedir[MAX_QPATH], basedir[MAX_QPATH], tags[MAX_STEAMQUERY_TAG_STRING];
		int players = 0, bots = 0, maxclients = 0;
		client_t *cl;
		char msg[MAX_STEAMQUERY_PACKETLEN];

		for( i = 0; i < MAX_MASTERS; i++ )
		{
			if( sv_masters[i].steam && NET_CompareAddress( address, &sv_masters[i].address ) )
			{
				fromMaster = true;
				break;
			}
		}
		if( !fromMaster )
			return true;

		if( sv_showInfoQueries->integer )
			Com_Printf( "Steam Master Server Info Packet %s\n", NET_AddressToString( address ) );

		challenge = MSG_ReadLong( inmsg );

		Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) );
		Q_strncpyz( basedir, FS_BaseGameDirectory(), sizeof( basedir ) );
		SV_GetSteamTags( tags );

		for( i = 0; i < sv_maxclients->integer; i++ )
		{
			cl = &svs.clients[i];
			if( cl->state >= CS_CONNECTED )
			{
				if( cl->tvclient ) // exclude TV from the max players count
					continue;
				if( cl->edict->r.svflags & SVF_FAKECLIENT )
					bots++;
				players++;
			}
			maxclients++;
		}

		Q_snprintfz( msg, sizeof( msg ),
			"0\n\\protocol\\7\\challenge\\%i" // protocol must be 7 to match Source
			"\\players\\%i\\max\\%i\\bots\\%i"
			"\\gamedir\\%s\\map\\%s"
			"\\password\\%i\\os\\%c"
			"\\lan\\%i\\region\\255"
			"%s%s"
			"\\type\\%c\\secure\\0"
			"\\version\\%i.%i.0.0"
			"\\product\\%s\n",
			challenge,
			min( players, 99 ), min( maxclients, 99 ), min( bots, 99 ),
			gamedir, sv.mapname,
			Cvar_String( "password" )[0] ? 1 : 0, STEAMQUERY_OS,
			sv_public->integer ? 0 : 1,
			tags[0] ? "\\gametype\\" /* legacy - "gametype", not "tags" */ : "", tags,
			( dedicated && dedicated->integer ) ? 'd' : 'l',
			APP_VERSION_MAJOR, APP_VERSION_MINOR,
			basedir );
		NET_SendPacket( socket, ( const uint8_t * )msg, strlen( msg ), address );

		return true;
	}

	if( s[0] == 'O' )
	{
		// out of date message
		static bool printed = false;
		if( !printed )
		{
			int i;
			for( i = 0; i < MAX_MASTERS; i++ )
			{
				if( sv_masters[i].steam && NET_CompareAddress( address, &sv_masters[i].address ) )
				{
					Com_Printf( "Server is out of date and cannot be added to the Steam master servers.\n" );
					printed = true;
					return true;
				}
			}
		}
		return true;
	}
#endif

	return false;
}