/* * 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] ) ); }
// 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; } }
/* ================ 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(); }
/* * 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 ); }
/** * 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). }
/* * 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 ); } }
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; }
/* * 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(); }
/* * 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 ); } } }
/** * 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; }