/* * SVC_SendInfoString */ static void SVC_SendInfoString( const socket_t *socket, const netadr_t *address, const char *requestType, const char *responseType, qboolean fullStatus ) { char *string; if( sv_showInfoQueries->integer ) Com_Printf( "%s Packet %s\n", requestType, NET_AddressToString( address ) ); // KoFFiE: When not public and coming from a LAN address // assume broadcast and respond anyway, otherwise ignore if( ( ( !sv_public->integer ) && ( !NET_IsLANAddress( address ) ) ) || ( sv_maxclients->integer == 1 ) ) { return; } // ignore when in invalid server state if( sv.state < ss_loading || sv.state > ss_game ) return; // don't reply when we are locked for mm // if( SV_MM_IsLocked() ) // return; // send the same string that we would give for a status OOB command string = SV_LongInfoString( fullStatus ); if( string ) Netchan_OutOfBandPrint( socket, address, "%s\n\\challenge\\%s%s", responseType, Cmd_Argv( 1 ), string ); }
/* * CL_ParseStatusMessage * Handle a reply from a ping */ void CL_ParseStatusMessage( const socket_t *socket, const netadr_t *address, msg_t *msg ) { char *s = MSG_ReadString( msg ); serverlist_t *pingserver; char adrString[64]; Com_DPrintf( "%s\n", s ); Q_strncpyz( adrString, NET_AddressToString( address ), sizeof( adrString ) ); // ping response pingserver = CL_ServerFindInList( masterList, adrString ); if( !pingserver ) pingserver = CL_ServerFindInList( favoritesList, adrString ); if( pingserver && pingserver->pingTimeStamp ) // valid ping { unsigned int ping = Sys_Milliseconds() - pingserver->pingTimeStamp; CL_UIModule_AddToServerList( adrString, va( "\\\\ping\\\\%i%s", ping, s ) ); pingserver->pingTimeStamp = 0; pingserver->lastValidPing = Com_DaysSince1900(); return; } // assume LAN response if( NET_IsLANAddress( address ) && ( localQueryTimeStamp + LAN_SERVER_PINGING_TIMEOUT > Sys_Milliseconds() ) ) { unsigned int ping = Sys_Milliseconds() - localQueryTimeStamp; CL_UIModule_AddToServerList( adrString, va( "\\\\ping\\\\%i%s", ping, s ) ); return; } // add the server info, but ignore the ping, cause it's not valid CL_UIModule_AddToServerList( adrString, s ); }
/* * TV_Downstream_SendInfoString */ static void TV_Downstream_SendInfoString( const socket_t *socket, const netadr_t *address, const char *responseType, bool fullStatus ) { char *string; // KoFFiE: When not public and coming from a LAN address // assume broadcast and respond anyway, otherwise ignore if( ( ( !tv_public->integer ) && ( !NET_IsLANAddress( address ) ) ) || ( tv_maxclients->integer == 1 ) ) return; // send the same string that we would give for a status OOB command string = TV_Downstream_LongInfoString( fullStatus ); if( string ) Netchan_OutOfBandPrint( socket, address, "%s\n\\challenge\\%s%s", responseType, Cmd_Argv( 1 ), string ); }
/* * TV_Downstream_InfoResponse * * Responds with short info for broadcast scans * The second parameter should be the current protocol version number. */ static void TV_Downstream_InfoResponse( const socket_t *socket, const netadr_t *address ) { int i, count; char *string; bool allow_empty = false, allow_full = false; // KoFFiE: When not public and coming from a LAN address // assume broadcast and respond anyway, otherwise ignore if( ( ( !tv_public->integer ) && ( !NET_IsLANAddress( address ) ) ) || ( tv_maxclients->integer == 1 ) ) return; // different protocol version if( atoi( Cmd_Argv( 1 ) ) != APP_PROTOCOL_VERSION ) return; // check for full/empty filtered states for( i = 0; i < Cmd_Argc(); i++ ) { if( !Q_stricmp( Cmd_Argv( i ), "full" ) ) allow_full = true; if( !Q_stricmp( Cmd_Argv( i ), "empty" ) ) allow_empty = true; } count = 0; for( i = 0; i < tv_maxclients->integer; i++ ) if( tvs.clients[i].state >= CS_CONNECTED ) count++; if( ( count == tv_maxclients->integer ) && !allow_full ) return; if( ( count == 0 ) && !allow_empty ) return; string = TV_Downstream_ShortInfoString(); if( string ) Netchan_OutOfBandPrint( socket, address, "info\n%s", string ); }
/* * SVC_InfoResponse * * Responds with short info for broadcast scans * The second parameter should be the current protocol version number. */ static void SVC_InfoResponse( const socket_t *socket, const netadr_t *address ) { int i, count; char *string; qboolean allow_empty = qfalse, allow_full = qfalse; if( sv_showInfoQueries->integer ) Com_Printf( "Info Packet %s\n", NET_AddressToString( address ) ); // KoFFiE: When not public and coming from a LAN address // assume broadcast and respond anyway, otherwise ignore if( ( ( !sv_public->integer ) && ( !NET_IsLANAddress( address ) ) ) || ( sv_maxclients->integer == 1 ) ) { return; } // ignore when in invalid server state if( sv.state < ss_loading || sv.state > ss_game ) return; // don't reply when we are locked for mm // if( SV_MM_IsLocked() ) // return; // different protocol version if( atoi( Cmd_Argv( 1 ) ) != APP_PROTOCOL_VERSION ) return; // check for full/empty filtered states for( i = 0; i < Cmd_Argc(); i++ ) { if( !Q_stricmp( Cmd_Argv( i ), "full" ) ) allow_full = qtrue; if( !Q_stricmp( Cmd_Argv( i ), "empty" ) ) allow_empty = qtrue; } count = 0; for( i = 0; i < sv_maxclients->integer; i++ ) { if( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if( ( count == sv_maxclients->integer ) && !allow_full ) { return; } if( ( count == 0 ) && !allow_empty ) { return; } string = SV_ShortInfoString(); if( string ) Netchan_OutOfBandPrint( socket, address, "info\n%s", string ); }
/** * 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; }
/* * SV_UserinfoChanged * * Pull specific info from a newly changed userinfo string * into a more C friendly form. */ void SV_UserinfoChanged( client_t *client ) { char *val; int ival; assert( client ); assert( Info_Validate( client->userinfo ) ); if( !client->edict || !( client->edict->r.svflags & SVF_FAKECLIENT ) ) { // force the IP key/value pair so the game can filter based on ip if( !Info_SetValueForKey( client->userinfo, "socket", NET_SocketTypeToString( client->netchan.socket->type ) ) ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (socket)\n" ); return; } if( !Info_SetValueForKey( client->userinfo, "ip", NET_AddressToString( &client->netchan.remoteAddress ) ) ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (ip)\n" ); return; } } // mm session ival = 0; val = Info_ValueForKey( client->userinfo, "cl_mm_session" ); if( val ) ival = atoi( val ); if( !val || ival != client->mm_session ) Info_SetValueForKey( client->userinfo, "cl_mm_session", va("%d", client->mm_session ) ); // mm login if( client->mm_login[0] != '\0' ) { Info_SetValueForKey( client->userinfo, "cl_mm_login", client->mm_login ); } else { Info_RemoveKey( client->userinfo, "cl_mm_login" ); } // call prog code to allow overrides ge->ClientUserinfoChanged( client->edict, client->userinfo ); if( !Info_Validate( client->userinfo ) ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error: Invalid userinfo (after game)" ); return; } // we assume that game module deals with setting a correct name val = Info_ValueForKey( client->userinfo, "name" ); if( !val || !val[0] ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error: No name set" ); return; } Q_strncpyz( client->name, val, sizeof( client->name ) ); #ifndef RATEKILLED // rate command if( NET_IsLANAddress( &client->netchan.remoteAddress ) ) { client->rate = 99999; // lans should not rate limit } else { val = Info_ValueForKey( client->userinfo, "rate" ); if( val && val[0] ) { int newrate; newrate = atoi( val ); if( sv_maxrate->integer && newrate > sv_maxrate->integer ) newrate = sv_maxrate->integer; else if( newrate > 90000 ) newrate = 90000; if( newrate < 1000 ) newrate = 1000; if( client->rate != newrate ) { client->rate = newrate; Com_Printf( "%s%s has rate %i\n", client->name, S_COLOR_WHITE, client->rate ); } } else client->rate = 5000; } #endif }
/** * 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 TV_Downstream_SteamServerQuery( const char *s, const socket_t *socket, const netadr_t *address, msg_t *inmsg ) { #if APP_STEAMID if( ( !tv_public->integer && !NET_IsLANAddress( address ) ) || ( tv_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, "TSource Engine Query" ) ) { // server info char hostname[MAX_INFO_VALUE]; char gamedir[MAX_QPATH]; char version[32]; int i, count = 0; msg_t msg; uint8_t msgbuf[MAX_STEAMQUERY_PACKETLEN - sizeof( int32_t )]; Q_strncpyz( hostname, COM_RemoveColorTokens( tv_name->string ), sizeof( hostname ) ); if( !hostname[0] ) Q_strncpyz( hostname, tv_name->dvalue, sizeof( hostname ) ); Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) ); for( i = 0; i < tv_maxclients->integer; i++ ) { if( tvs.clients[i].state >= CS_CONNECTED ) { count++; if( count == 99 ) break; } } Q_snprintfz( version, sizeof( version ), "%i.%i.0.0", APP_VERSION_MAJOR, APP_VERSION_MINOR ); MSG_Init( &msg, msgbuf, sizeof( msgbuf ) ); MSG_WriteByte( &msg, 'I' ); MSG_WriteByte( &msg, APP_PROTOCOL_VERSION ); MSG_WriteString( &msg, hostname ); MSG_WriteString( &msg, "" ); // no map MSG_WriteString( &msg, gamedir ); MSG_WriteString( &msg, APPLICATION " TV" ); MSG_WriteShort( &msg, 0 ); // app ID specified later MSG_WriteByte( &msg, count ); MSG_WriteByte( &msg, min( tv_maxclients->integer, 99 ) ); MSG_WriteByte( &msg, 0 ); // no bots MSG_WriteByte( &msg, 'p' ); MSG_WriteByte( &msg, STEAMQUERY_OS ); MSG_WriteByte( &msg, tv_password->string[0] ? 1 : 0 ); MSG_WriteByte( &msg, 0 ); // VAC insecure MSG_WriteString( &msg, version ); MSG_WriteByte( &msg, 0x40 | 0x1 ); // spectator data | game ID containing app ID // spectator data MSG_WriteShort( &msg, tv_port->integer ); MSG_WriteString( &msg, hostname ); // 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( !strcmp( s, "s" ) ) { // master server query, terminated by \n, followed by the challenge bool isSteamMaster = false; int challenge; char gamedir[MAX_QPATH], basedir[MAX_QPATH]; int i, count = 0; char msg[MAX_STEAMQUERY_PACKETLEN]; if( !TV_Downstream_IsMaster( address, &isSteamMaster ) || !isSteamMaster ) return true; challenge = MSG_ReadLong( inmsg ); Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) ); Q_strncpyz( basedir, FS_BaseGameDirectory(), sizeof( basedir ) ); for( i = 0; i < tv_maxclients->integer; i++ ) { if( tvs.clients[i].state >= CS_CONNECTED ) { count++; if( count == 99 ) break; } } Q_snprintfz( msg, sizeof( msg ), "0\n\\protocol\\7\\challenge\\%i" // protocol must be 7 to match Source "\\players\\%i\\max\\%i\\bots\\0" "\\gamedir\\%s" "\\password\\%i\\os\\%c" "\\lan\\%i\\region\\255" "\\type\\p\\secure\\0" "\\version\\%i.%i.0.0" "\\product\\%s\n", challenge, count, min( tv_maxclients->integer, 99 ), gamedir, tv_password->string[0] ? 1 : 0, STEAMQUERY_OS, tv_public->integer ? 0 : 1, 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 ) { bool isSteamMaster = false; if( TV_Downstream_IsMaster( address, &isSteamMaster ) && isSteamMaster ) { Com_Printf( "Server is out of date and cannot be added to the Steam master servers.\n" ); printed = true; } } return true; } #endif return false; }