869 lines
25 KiB
C++
869 lines
25 KiB
C++
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "mm_framework.h"
|
|
|
|
#include "fmtstr.h"
|
|
#include "vstdlib/random.h"
|
|
|
|
#include "protocol.h"
|
|
#include "proto_oob.h"
|
|
#include "bitbuf.h"
|
|
#include "checksum_crc.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar mm_dedicated_allow( "mm_dedicated_allow", "1", FCVAR_DEVELOPMENTONLY, "1 = allow searches for dedicated servers" );
|
|
ConVar mm_dedicated_fake( "mm_dedicated_fake", "0", FCVAR_DEVELOPMENTONLY, "1 = pretend like search is going, but abort after some time" );
|
|
ConVar mm_dedicated_force_servers( "mm_dedicated_force_servers", "", FCVAR_RELEASE,
|
|
"Comma delimited list of ip:port of servers used to search for dedicated servers instead of searching for public servers.\n"
|
|
"Use syntax `publicip1:port|privateip1:port,publicip2:port|privateip2:port` if your server is behind NAT.\n"
|
|
"If the server is behind NAT, you can specify `0.0.0.0|privateip:port` and if server port is in the list of `mm_server_search_lan_ports` its public address should be automatically detected." );
|
|
ConVar mm_dedicated_ip( "mm_dedicated_ip", "", FCVAR_DEVELOPMENTONLY, "IP address of dedicated servers to consider available" );
|
|
ConVar mm_dedicated_timeout_request( "mm_dedicated_timeout_request", "20", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_dedicated_search_maxping( "mm_dedicated_search_maxping", IsX360() ? "200" : "150", FCVAR_RELEASE | FCVAR_ARCHIVE, "Longest preferred ping to dedicated servers for games", true, 25.f, true, 350.f );
|
|
ConVar mm_dedicated_search_maxresults( "mm_dedicated_search_maxresults", "75", FCVAR_DEVELOPMENTONLY );
|
|
|
|
extern ConVar mm_dedicated_xlsp_timeout;
|
|
|
|
CDsSearcher::CDsSearcher( KeyValues *pSettings, uint64 uiReserveCookie, IMatchSession *pMatchSession, uint64 ullCrypt ) :
|
|
m_pSettings( pSettings ),
|
|
m_uiReserveCookie( uiReserveCookie ),
|
|
m_pReserveSettings( g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForReservation( m_pSettings ) ),
|
|
m_autodelete_pReserveSettings( m_pReserveSettings ),
|
|
#ifdef _X360
|
|
m_pTitleServers( NULL ),
|
|
#elif !defined( NO_STEAM )
|
|
m_pServerListListener( NULL ),
|
|
m_nSearchPass( 0 ),
|
|
#endif
|
|
m_eState( STATE_INIT ),
|
|
m_flTimeout( 0.0f ),
|
|
m_pAsyncOperation( NULL ),
|
|
m_pMatchSession( pMatchSession ),
|
|
m_ullCrypt( ullCrypt )
|
|
{
|
|
#ifdef _X360
|
|
ZeroMemory( m_chDatacenterQuery, sizeof( m_chDatacenterQuery ) );
|
|
ZeroMemory( &m_dc, sizeof( m_dc ) );
|
|
#endif
|
|
|
|
DevMsg( "Created DS searcher\n" );
|
|
KeyValuesDumpAsDevMsg( m_pSettings );
|
|
|
|
memset( &m_Result, 0, sizeof( m_Result ) );
|
|
|
|
// Build the reservation settings
|
|
|
|
// Load test
|
|
m_bLoadTest = (m_pSettings->GetInt( "options/sv_load_test", 0) == 1);
|
|
}
|
|
|
|
CDsSearcher::~CDsSearcher()
|
|
{
|
|
;
|
|
}
|
|
|
|
void CDsSearcher::Update()
|
|
{
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_INIT:
|
|
{
|
|
char const *szNetwork = m_pSettings->GetString( "system/network", "" );
|
|
char const *szServer = m_pSettings->GetString( "options/server", "listen" );
|
|
if ( m_pSettings->GetString( "server/server", NULL ) )
|
|
{
|
|
InitWithKnownServer();
|
|
}
|
|
else if ( mm_dedicated_allow.GetBool() &&
|
|
!Q_stricmp( "LIVE", szNetwork ) &&
|
|
Q_stricmp( "listen", szServer ) &&
|
|
!( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_SETTING_NODEDICATED ) )
|
|
{
|
|
InitDedicatedSearch();
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_WAITING:
|
|
{
|
|
if ( Plat_FloatTime() > m_flTimeout )
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
break;
|
|
|
|
#ifdef _X360
|
|
|
|
case STATE_XLSP_ENUMERATE_DCS:
|
|
m_pTitleServers->Update();
|
|
if ( m_pTitleServers->IsSearchCompleted() )
|
|
Xlsp_OnEnumerateDcsCompleted();
|
|
break;
|
|
|
|
case STATE_XLSP_NEXT_DC:
|
|
Xlsp_StartNextDc();
|
|
break;
|
|
|
|
case STATE_XLSP_REQUESTING_SERVERS:
|
|
if ( Plat_FloatTime() > m_flTimeout )
|
|
{
|
|
DevWarning( "XLSP datacenter `%s` timed out.\n", m_dc.m_szGatewayName );
|
|
m_dc.Destroy();
|
|
m_eState = STATE_XLSP_NEXT_DC;
|
|
}
|
|
break;
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
case STATE_STEAM_REQUESTING_SERVERS:
|
|
if ( Plat_FloatTime() > m_flTimeout )
|
|
{
|
|
DevWarning( "Steam search for dedicated servers timed out.\n" );
|
|
Steam_OnDedicatedServerListFetched();
|
|
}
|
|
break;
|
|
|
|
case STATE_STEAM_NEXT_SEARCH_PASS:
|
|
Steam_SearchPass();
|
|
break;
|
|
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void CDsSearcher::OnEvent( KeyValues *pEvent )
|
|
{
|
|
char const *szEvent = pEvent->GetName();
|
|
|
|
#ifdef _X360
|
|
if ( m_eState == STATE_XLSP_REQUESTING_SERVERS &&
|
|
!Q_stricmp( "M2A_SERVER_BATCH", szEvent ) )
|
|
{
|
|
void const *pData = pEvent->GetPtr( "ptr" );
|
|
int numBytes = pEvent->GetInt( "size" );
|
|
|
|
Xlsp_OnDcServerBatch( pData, numBytes );
|
|
}
|
|
#elif !defined( NO_STEAM )
|
|
szEvent;
|
|
#endif
|
|
}
|
|
|
|
void CDsSearcher::Destroy()
|
|
{
|
|
if ( m_pAsyncOperation )
|
|
{
|
|
m_pAsyncOperation->Release();
|
|
m_pAsyncOperation = NULL;
|
|
}
|
|
|
|
#ifdef _X360
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_XLSP_ENUMERATE_DCS:
|
|
if ( m_pTitleServers )
|
|
m_pTitleServers->Destroy();
|
|
m_pTitleServers = NULL;
|
|
break;
|
|
case STATE_XLSP_REQUESTING_SERVERS:
|
|
case STATE_RESERVING:
|
|
m_dc.Destroy();
|
|
break;
|
|
}
|
|
#elif !defined( NO_STEAM )
|
|
if ( m_pServerListListener )
|
|
{
|
|
m_pServerListListener->Destroy();
|
|
m_pServerListListener = NULL;
|
|
}
|
|
#endif
|
|
|
|
delete this;
|
|
}
|
|
|
|
bool CDsSearcher::IsFinished()
|
|
{
|
|
return m_eState == STATE_FINISHED;
|
|
}
|
|
|
|
CDsSearcher::DsResult_t const & CDsSearcher::GetResult()
|
|
{
|
|
return m_Result;
|
|
}
|
|
|
|
void CDsSearcher::DsResult_t::CopyToServerKey( KeyValues *pKvServer, uint64 ullCrypt ) const
|
|
{
|
|
Assert( m_bDedicated );
|
|
pKvServer->SetString( "server", "dedicated" );
|
|
|
|
#ifdef _X360
|
|
pKvServer->SetString( "adrInsecure", m_szInsecureSendableServerAddress );
|
|
#elif !defined( NO_STEAM )
|
|
if ( char const *szEncrypted = MatchSession_EncryptAddressString( m_szPublicConnectionString, ullCrypt ) )
|
|
pKvServer->SetString( "adronline", szEncrypted );
|
|
else
|
|
pKvServer->SetString( "adronline", m_szPublicConnectionString );
|
|
|
|
if ( m_szPrivateConnectionString[0] )
|
|
{
|
|
if ( char const *szEncrypted = MatchSession_EncryptAddressString( m_szPrivateConnectionString, ullCrypt ) )
|
|
pKvServer->SetString( "adrlocal", szEncrypted );
|
|
else
|
|
pKvServer->SetString( "adrlocal", m_szPrivateConnectionString );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Implementation
|
|
//
|
|
|
|
void CDsSearcher::InitDedicatedSearch()
|
|
{
|
|
if ( mm_dedicated_fake.GetBool() )
|
|
{
|
|
// Special fake of the search - it just spins for some time and
|
|
// pretends like it was aborted
|
|
m_flTimeout = Plat_FloatTime() + mm_dedicated_timeout_request.GetFloat();
|
|
m_eState = STATE_WAITING;
|
|
m_Result.m_bAborted = true;
|
|
return;
|
|
}
|
|
|
|
#ifdef _X360
|
|
Xlsp_EnumerateDcs();
|
|
#elif !defined( NO_STEAM )
|
|
m_flTimeout = Plat_FloatTime() + mm_dedicated_timeout_request.GetFloat();
|
|
Steam_SearchPass();
|
|
#endif
|
|
}
|
|
|
|
void CDsSearcher::InitWithKnownServer()
|
|
{
|
|
#ifdef _X360
|
|
Assert( 0 );
|
|
m_eState = STATE_FINISHED;
|
|
return;
|
|
#elif !defined( NO_STEAM )
|
|
if ( m_pSettings->GetInt( "server/reserved" ) )
|
|
{
|
|
m_Result.m_bDedicated = true;
|
|
|
|
char const *szAdrOnline = m_pSettings->GetString( "server/adronline", "" );
|
|
if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAdrOnline, m_ullCrypt ) )
|
|
szAdrOnline = szDecrypted;
|
|
Q_strncpy( m_Result.m_szConnectionString, szAdrOnline, ARRAYSIZE( m_Result.m_szConnectionString ) );
|
|
Q_strncpy( m_Result.m_szPublicConnectionString, szAdrOnline, ARRAYSIZE( m_Result.m_szPublicConnectionString ) );
|
|
|
|
char const *szAdrLocal = m_pSettings->GetString( "server/adrlocal", "" );
|
|
if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAdrLocal, m_ullCrypt ) )
|
|
szAdrLocal = szDecrypted;
|
|
Q_strncpy( m_Result.m_szPrivateConnectionString, szAdrLocal, ARRAYSIZE( m_Result.m_szPrivateConnectionString ) );
|
|
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
else
|
|
{
|
|
DsServer_t dsResult(
|
|
m_pSettings->GetString( "server/adronline", "" ),
|
|
m_pSettings->GetString( "server/adrlocal", "" ),
|
|
0
|
|
);
|
|
if ( char const *szDecrypted = MatchSession_DecryptAddressString( dsResult.m_szConnectionString, m_ullCrypt ) )
|
|
Q_strncpy( dsResult.m_szConnectionString, szDecrypted, ARRAYSIZE( dsResult.m_szConnectionString ) );
|
|
if ( char const *szDecrypted = MatchSession_DecryptAddressString( dsResult.m_szPrivateConnectionString, m_ullCrypt ) )
|
|
Q_strncpy( dsResult.m_szPrivateConnectionString, szDecrypted, ARRAYSIZE( dsResult.m_szPrivateConnectionString ) );
|
|
|
|
m_arrServerList.AddToTail( dsResult );
|
|
|
|
ReserveNextServer();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
void CDsSearcher::Xlsp_EnumerateDcs()
|
|
{
|
|
m_eState = STATE_XLSP_ENUMERATE_DCS;
|
|
m_pTitleServers = new CXlspTitleServers( mm_dedicated_search_maxping.GetInt(), false );
|
|
}
|
|
|
|
void CDsSearcher::Xlsp_OnEnumerateDcsCompleted()
|
|
{
|
|
DevMsg( "Xlsp_OnEnumerateDcsCompleted - analyzing QOS results...\n" );
|
|
|
|
CUtlVector< CXlspDatacenter > &arrDcs = m_pTitleServers->GetDatacenters();
|
|
m_arrDatacenters.AddMultipleToTail( arrDcs.Count(), arrDcs.Base() );
|
|
|
|
m_pTitleServers->Destroy();
|
|
m_pTitleServers = NULL;
|
|
|
|
//
|
|
// Sort and randomize the accepted results
|
|
//
|
|
m_arrDatacenters.Sort( CXlspDatacenter::Compare );
|
|
for ( int k = 0; k < m_arrDatacenters.Count() - 1; ++ k )
|
|
{
|
|
CXlspDatacenter &dc1 = m_arrDatacenters[ k ];
|
|
CXlspDatacenter &dc2 = m_arrDatacenters[ k + 1 ];
|
|
if ( dc1.m_nPingBucket == dc2.m_nPingBucket && RandomInt( 0, 1 ) )
|
|
{
|
|
CXlspDatacenter dcSwap = dc1;
|
|
dc1 = dc2;
|
|
dc2 = dcSwap;
|
|
}
|
|
}
|
|
|
|
DevMsg( "Xlsp_OnEnumerateDcsCompleted - accepted %d datacenters.\n", m_arrDatacenters.Count() );
|
|
for ( int k = 0; k < m_arrDatacenters.Count(); ++ k )
|
|
{
|
|
DevMsg( " %d. `%s`\n", k, m_arrDatacenters[k].m_szGatewayName );
|
|
}
|
|
|
|
// Prepare the datacenter query
|
|
Xlsp_PrepareDatacenterQuery();
|
|
|
|
// Go to the next datacenter
|
|
m_eState = STATE_XLSP_NEXT_DC;
|
|
}
|
|
|
|
void CDsSearcher::Xlsp_PrepareDatacenterQuery()
|
|
{
|
|
// Compute CRC of primary user's gamertag
|
|
byte bSult = RandomInt( 5, 100 );
|
|
CRC32_t crc32 = 0;
|
|
if ( IPlayerLocal * player = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() ) )
|
|
{
|
|
char const *szPlayerName = player->GetName();
|
|
crc32 = CRC32_ProcessSingleBuffer( szPlayerName, strlen( szPlayerName ) );
|
|
|
|
uint32 sult32 = bSult | ( bSult << 8 ) | ( bSult << 16 ) | ( bSult << 24 );
|
|
crc32 ^= sult32;
|
|
}
|
|
if ( !crc32 )
|
|
bSult = 0;
|
|
|
|
// Search key
|
|
static ConVarRef sv_search_key( "sv_search_key" );
|
|
char const *szPrivateKey = sv_search_key.IsValid() ? sv_search_key.GetString() : "";
|
|
if ( !*szPrivateKey )
|
|
szPrivateKey = "default";
|
|
|
|
//
|
|
// Build query
|
|
//
|
|
Q_snprintf( m_chDatacenterQuery, ARRAYSIZE( m_chDatacenterQuery ),
|
|
"\\empty\\1"
|
|
"\\private\\%s"
|
|
"\\players\\%d"
|
|
"\\slots\\%d"
|
|
"\\perm\\%s"
|
|
"\\acct\\%02x%08x",
|
|
szPrivateKey,
|
|
m_pSettings->GetInt( "members/numPlayers", 0 ),
|
|
m_pSettings->GetInt( "members/numSlots", 0 ),
|
|
m_pSettings->GetString( "system/access", "public" ),
|
|
bSult, crc32
|
|
);
|
|
|
|
DevMsg( "Datacenters query: %s\n", m_chDatacenterQuery );
|
|
}
|
|
|
|
void CDsSearcher::Xlsp_StartNextDc()
|
|
{
|
|
if ( !m_arrDatacenters.Count() )
|
|
{
|
|
m_eState = STATE_FINISHED;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get the next datacenter off the list
|
|
//
|
|
m_dc = m_arrDatacenters.Head();
|
|
m_arrDatacenters.RemoveMultipleFromHead( 1 );
|
|
m_flTimeout = Plat_FloatTime() + mm_dedicated_xlsp_timeout.GetFloat();
|
|
|
|
DevMsg( "[XLSP] Requesting server batch from %s:%d (%d masters) - ping %d [<= %d]\n"
|
|
" ProbesXmit=%3d ProbesRecv=%3d\n"
|
|
" RttMinInMsecs=%3d RttMedInMsecs=%3d\n"
|
|
" UpBitsPerSec=%6d DnBitsPerSec=%6d\n",
|
|
m_dc.m_szGatewayName, m_dc.m_nMasterServerPortStart, m_dc.m_numMasterServers, m_dc.m_qos.wRttMedInMsecs, m_dc.m_nPingBucket,
|
|
m_dc.m_qos.cProbesXmit, m_dc.m_qos.cProbesRecv,
|
|
m_dc.m_qos.wRttMinInMsecs, m_dc.m_qos.wRttMedInMsecs,
|
|
m_dc.m_qos.dwUpBitsPerSec, m_dc.m_qos.dwDnBitsPerSec );
|
|
|
|
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
|
|
{
|
|
m_dc.m_adrSecure = m_dc.m_xsi.inaServer;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Resolve the secure address
|
|
//
|
|
DWORD ret = g_pMatchExtensions->GetIXOnline()->XNetServerToInAddr( m_dc.m_xsi.inaServer, g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(), &m_dc.m_adrSecure );
|
|
if ( ret != ERROR_SUCCESS )
|
|
{
|
|
DevWarning( "Failed to resolve XLSP secure address (code = 0x%08X)!\n", ret );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Convert to netadr_t on a random master port
|
|
netadr_t inetAddr;
|
|
inetAddr.SetType( NA_IP );
|
|
inetAddr.SetIPAndPort( m_dc.m_adrSecure.s_addr,
|
|
m_dc.m_nMasterServerPortStart + RandomInt( 0, m_dc.m_numMasterServers - 1 ) );
|
|
|
|
//
|
|
// Prepare the request payload
|
|
//
|
|
char msg_buffer[ INetSupport::NC_MAX_ROUTABLE_PAYLOAD ];
|
|
bf_write msg( msg_buffer, sizeof( msg_buffer ) );
|
|
|
|
msg.WriteByte( A2M_GET_SERVERS_BATCH2 );
|
|
msg.WriteByte( '\n' );
|
|
msg.WriteLong( 0 ); // batch starts at 0
|
|
msg.WriteLong( m_dc.m_adrSecure.s_addr ); // datacenter's challenge
|
|
msg.WriteString( m_chDatacenterQuery ); // datacenter query
|
|
msg.WriteByte( '\n' );
|
|
|
|
g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, INetSupport::NS_SOCK_CLIENT,
|
|
inetAddr, msg.GetData(), msg.GetNumBytesWritten() );
|
|
|
|
m_eState = STATE_XLSP_REQUESTING_SERVERS;
|
|
}
|
|
|
|
void CDsSearcher::Xlsp_OnDcServerBatch( void const *pData, int numBytes )
|
|
{
|
|
if ( numBytes < 8 )
|
|
return;
|
|
|
|
bf_read msg( pData, numBytes );
|
|
|
|
int nNextId = msg.ReadLong();
|
|
nNextId;
|
|
|
|
uint nChallenge = msg.ReadLong();
|
|
if ( nChallenge != m_dc.m_adrSecure.s_addr )
|
|
return;
|
|
|
|
//
|
|
// Get master server reply message or Secure Gateway name (must match request)
|
|
//
|
|
char szReply[ MAX_PATH ] = {0};
|
|
msg.ReadString( szReply, ARRAYSIZE( szReply ), true );
|
|
|
|
if ( !szReply[0] )
|
|
{
|
|
DevWarning( "XLSP master server: empty response.\n" );
|
|
m_dc.Destroy();
|
|
m_eState = STATE_XLSP_NEXT_DC;
|
|
return;
|
|
}
|
|
|
|
if ( !Q_stricmp( "##full", szReply ) )
|
|
{
|
|
DevWarning( "XLSP master server: full.\n" );
|
|
m_dc.Destroy();
|
|
m_eState = STATE_XLSP_NEXT_DC;
|
|
return;
|
|
}
|
|
|
|
if ( !Q_stricmp( "##local", szReply ) )
|
|
{
|
|
DevWarning( "XLSP master server: game is not eligible for dedicated server.\n" );
|
|
m_dc.Destroy();
|
|
m_eState = STATE_FINISHED;
|
|
return;
|
|
}
|
|
|
|
// Bypass the gateway name check if we're faking it.
|
|
if ( !CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
|
|
{
|
|
if ( Q_stricmp( m_dc.m_szGatewayName, szReply ) )
|
|
{
|
|
DevWarning( "XLSP master server: wrong reply `%s`, expected gateway `%s`.\n", szReply, m_dc.m_szGatewayName );
|
|
m_dc.Destroy();
|
|
m_eState = STATE_XLSP_NEXT_DC;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process all the servers in the batch
|
|
//
|
|
m_arrServerPorts.RemoveAll();
|
|
for ( ; ; )
|
|
{
|
|
uint16 nPort = msg.ReadWord();
|
|
if ( !nPort || nPort == 0xFFFF )
|
|
{
|
|
// end of list
|
|
break;
|
|
}
|
|
|
|
m_arrServerPorts.AddToTail( nPort );
|
|
}
|
|
DevWarning( "XLSP master server: returned %d servers in batch.\n", m_arrServerPorts.Count() );
|
|
|
|
// Go ahead and start reserving
|
|
ReserveNextServer();
|
|
}
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
void CDsSearcher::Steam_SearchPass()
|
|
{
|
|
if ( mm_dedicated_force_servers.GetString()[0] )
|
|
{
|
|
// if convar is on to force dedicated server choices, pretend we got search results of just those servers
|
|
CSplitString serverList( mm_dedicated_force_servers.GetString(), "," );
|
|
|
|
for ( int i = 0; i < serverList.Count(); i++ )
|
|
{
|
|
// Check if the specification has a private IP address
|
|
char const * adrsStrings[2] = { serverList[i], "" };
|
|
if ( char *pchDelim = strchr( serverList[i], '|' ) )
|
|
{
|
|
*( pchDelim ++ ) = 0;
|
|
adrsStrings[1] = pchDelim;
|
|
}
|
|
|
|
netadr_t adrsForced[2];
|
|
adrsForced[0].SetFromString( adrsStrings[0] );
|
|
adrsForced[1].SetFromString( adrsStrings[1] );
|
|
|
|
// Check if a locally discovered server is known with
|
|
// either public or private address that is being forced
|
|
int numServers = g_pServerManager->GetNumServers();
|
|
for ( int iServer = 0; iServer < numServers; ++ iServer )
|
|
{
|
|
IMatchServer *pMatchServer = g_pServerManager->GetServerByIndex( iServer );
|
|
if ( !pMatchServer )
|
|
continue;
|
|
|
|
KeyValues *pServerDetails = pMatchServer->GetGameDetails();
|
|
netadr_t adrsKnown[2];
|
|
char const * adrsStringsKnown[2] = { pServerDetails->GetString( "server/adronline" ), pServerDetails->GetString( "server/adrlocal" ) };
|
|
adrsKnown[0].SetFromString( adrsStringsKnown[0] );
|
|
adrsKnown[1].SetFromString( adrsStringsKnown[1] );
|
|
|
|
for ( int iAdrForced = 0; iAdrForced < ARRAYSIZE( adrsForced ); ++ iAdrForced )
|
|
{
|
|
for ( int iAdrKnown = 0; iAdrKnown < ARRAYSIZE( adrsKnown ); ++ iAdrKnown )
|
|
{
|
|
if ( adrsForced[iAdrForced].GetIPHostByteOrder() && adrsKnown[iAdrKnown].GetIPHostByteOrder() &&
|
|
adrsForced[iAdrForced].CompareAdr( adrsKnown[iAdrKnown] ) )
|
|
{
|
|
if ( !adrsForced[!iAdrForced].GetIPHostByteOrder() ) // user not forcing other address, but we know it
|
|
adrsStrings[!iAdrForced] = adrsStringsKnown[!iAdrKnown];
|
|
goto finished_server_lookup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finished_server_lookup:
|
|
|
|
m_arrServerList.AddToTail( CDsSearcher::DsServer_t(
|
|
adrsStrings[0], adrsStrings[1],
|
|
0 ) ); // you get no accurate ping to forced servers
|
|
}
|
|
|
|
Steam_OnDedicatedServerListFetched();
|
|
return;
|
|
}
|
|
|
|
CUtlVector< MatchMakingKeyValuePair_t > filters;
|
|
filters.EnsureCapacity( 10 );
|
|
|
|
// filter by game and require empty server
|
|
filters.AddToTail( MatchMakingKeyValuePair_t( "gamedir", COM_GetModDirectory() ) );
|
|
filters.AddToTail( MatchMakingKeyValuePair_t( "noplayers", "1" ) );
|
|
|
|
// Official servers
|
|
|
|
bool bNeedOfficialServer = false;
|
|
char const *szServerType = m_pSettings->GetString( "options/server", NULL );
|
|
|
|
if ( szServerType && !Q_stricmp( szServerType, "official" ) )
|
|
{
|
|
bNeedOfficialServer = true;
|
|
filters.AddToTail( MatchMakingKeyValuePair_t( "white", "1" ) );
|
|
}
|
|
|
|
// Allow the game to extend the filters
|
|
if ( KeyValues *pExtra = g_pMMF->GetMatchTitleGameSettingsMgr()->DefineDedicatedSearchKeys( m_pSettings, bNeedOfficialServer, m_nSearchPass ) )
|
|
{
|
|
if ( !mm_dedicated_ip.GetString()[0] )
|
|
{
|
|
for ( KeyValues *val = pExtra->GetFirstValue(); val; val = val->GetNextValue() )
|
|
{
|
|
char const *szValue = val->GetString();
|
|
if ( !szValue || !*szValue )
|
|
continue;
|
|
filters.AddToTail( MatchMakingKeyValuePair_t( val->GetName(), szValue ) );
|
|
}
|
|
}
|
|
pExtra->deleteThis();
|
|
}
|
|
|
|
// Load test
|
|
if (m_bLoadTest && m_nSearchPass == 0)
|
|
{
|
|
// Add to the "gametype" filter
|
|
for ( int i=0; i < filters.Count(); i++ )
|
|
{
|
|
MatchMakingKeyValuePair_t *pKV = &(filters[i]);
|
|
if ( !Q_stricmp( pKV->m_szKey, "gametype" ) )
|
|
{
|
|
Q_strncat( pKV->m_szValue, ",sv_load_test", sizeof(pKV->m_szValue) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// request the server list. We will get called back at ServerResponded, ServerFailedToRespond, and RefreshComplete
|
|
m_eState = STATE_STEAM_REQUESTING_SERVERS;
|
|
m_pServerListListener = new CServerListListener( this, filters );
|
|
++m_nSearchPass;
|
|
}
|
|
|
|
CDsSearcher::CServerListListener::CServerListListener( CDsSearcher *pDsSearcher, CUtlVector< MatchMakingKeyValuePair_t > &filters ) :
|
|
m_pOuter( pDsSearcher ),
|
|
m_hRequest( NULL )
|
|
{
|
|
MatchMakingKeyValuePair_t *pFilter = filters.Base();
|
|
DevMsg( 1, "Requesting dedicated server list...\n" );
|
|
for (int i = 0; i < filters.Count(); i++)
|
|
{
|
|
DevMsg("Filter %d: %s=%s\n", i, filters.Element(i).m_szKey, filters.Element(i).m_szValue);
|
|
}
|
|
m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestInternetServerList(
|
|
( AppId_t ) g_pMatchFramework->GetMatchTitle()->GetTitleID(), &pFilter,
|
|
filters.Count(), this );
|
|
}
|
|
|
|
void CDsSearcher::CServerListListener::Destroy()
|
|
{
|
|
m_pOuter = NULL;
|
|
|
|
if ( m_hRequest )
|
|
steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest );
|
|
m_hRequest = NULL;
|
|
|
|
delete this;
|
|
}
|
|
|
|
void CDsSearcher::CServerListListener::ServerResponded( HServerListRequest hReq, int iServer )
|
|
{
|
|
gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()
|
|
->GetServerDetails( hReq, iServer );
|
|
|
|
Msg( "[MM] Server responded '%s', dist %d\n",
|
|
pServer->m_NetAdr.GetConnectionAddressString(), pServer->m_nPing );
|
|
|
|
// Check if the server IP address matches
|
|
char const *szDedicatedIp = mm_dedicated_ip.GetString();
|
|
if ( szDedicatedIp && szDedicatedIp[0] )
|
|
{
|
|
char const *szServerConnString = pServer->m_NetAdr.GetConnectionAddressString();
|
|
szServerConnString = StringAfterPrefix( szServerConnString, szDedicatedIp );
|
|
if ( !szServerConnString ||
|
|
( szServerConnString[0] &&
|
|
szServerConnString[0] != ':' ) )
|
|
{
|
|
DevMsg( " rejected dedicated server '%s' due to ip filter '%s'\n",
|
|
pServer->m_NetAdr.GetConnectionAddressString(), szDedicatedIp );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Register the dedicated server as acceptable
|
|
// netadr_t adrPublic, adrPrivate;
|
|
// adrPublic.SetFromString( pServer->m_NetAdr.GetConnectionAddressString() );
|
|
// TODO: bool bHasPrivate = FindLANServerPrivateIPByPublicIP( adrPublic, adrPrivate );
|
|
|
|
// Don't reserve servers with human players playing
|
|
int nHumanPlayers = pServer->m_nPlayers - pServer->m_nBotPlayers;
|
|
if ( nHumanPlayers > 0 )
|
|
return;
|
|
|
|
// Don't reserve servers with too high ping
|
|
if ( ( mm_dedicated_search_maxping.GetInt() > 0 ) && ( pServer->m_nPing > mm_dedicated_search_maxping.GetInt() ) )
|
|
return;
|
|
|
|
// Register the result
|
|
if ( m_pOuter )
|
|
{
|
|
// See if maybe we know about a private address for this server
|
|
char const *szPrivateAddress = "";
|
|
netadr_t adrPublic;
|
|
adrPublic.SetFromString( pServer->m_NetAdr.GetConnectionAddressString() );
|
|
|
|
XUID xuidOnline = uint64( adrPublic.GetIPNetworkByteOrder() ) | ( uint64( adrPublic.GetPort() ) << 32ull );
|
|
if ( IMatchServer *pMatchServer = g_pServerManager->GetServerByOnlineId( xuidOnline ) )
|
|
{
|
|
szPrivateAddress = pMatchServer->GetGameDetails()->GetString( "server/adrlocal" );
|
|
}
|
|
|
|
m_pOuter->m_arrServerList.AddToTail( CDsSearcher::DsServer_t(
|
|
pServer->m_NetAdr.GetConnectionAddressString(),
|
|
szPrivateAddress,
|
|
pServer->m_nPing ) );
|
|
|
|
if ( m_pOuter->m_arrServerList.Count() > mm_dedicated_search_maxresults.GetInt() ||
|
|
Plat_FloatTime() > m_pOuter->m_flTimeout )
|
|
{
|
|
steamapicontext->SteamMatchmakingServers()->CancelQuery( hReq );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDsSearcher::CServerListListener::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response )
|
|
{
|
|
if ( m_pOuter )
|
|
{
|
|
m_pOuter->Steam_OnDedicatedServerListFetched();
|
|
}
|
|
}
|
|
|
|
void CDsSearcher::Steam_OnDedicatedServerListFetched()
|
|
{
|
|
if ( m_bLoadTest && m_nSearchPass < 2 )
|
|
{
|
|
m_eState = STATE_INIT;
|
|
}
|
|
else
|
|
{
|
|
if ( m_pServerListListener )
|
|
{
|
|
m_pServerListListener->Destroy();
|
|
m_pServerListListener = NULL;
|
|
}
|
|
|
|
DevMsg( 1, "Dedicated server list fetched %d servers.\n", m_arrServerList.Count() );
|
|
|
|
ReserveNextServer();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void CDsSearcher::ReserveNextServer()
|
|
{
|
|
m_eState = STATE_RESERVING;
|
|
|
|
#ifdef _X360
|
|
|
|
if ( !m_arrServerPorts.Count() )
|
|
{
|
|
m_dc.Destroy();
|
|
m_eState = STATE_XLSP_NEXT_DC;
|
|
return;
|
|
}
|
|
|
|
uint16 nPort = m_arrServerPorts.Head();
|
|
m_arrServerPorts.RemoveMultipleFromHead( 1 );
|
|
|
|
netadr_t inetAddrSecure;
|
|
inetAddrSecure.SetType( NA_IP );
|
|
inetAddrSecure.SetIPAndPort( m_dc.m_adrSecure.s_addr, nPort );
|
|
|
|
netadr_t inetAddrInsecureSendable;
|
|
inetAddrInsecureSendable.SetType( NA_IP );
|
|
inetAddrInsecureSendable.SetIPAndPort( m_dc.m_xsi.inaServer.s_addr, nPort );
|
|
|
|
Q_strncpy( m_Result.m_szConnectionString, inetAddrSecure.ToString(), ARRAYSIZE( m_Result.m_szConnectionString ) );
|
|
Q_strncpy( m_Result.m_szInsecureSendableServerAddress, inetAddrInsecureSendable.ToString(), ARRAYSIZE( m_Result.m_szInsecureSendableServerAddress ) );
|
|
|
|
netadr_t addrPublic, addrPrivate;
|
|
addrPrivate.SetType( NA_NULL );
|
|
addrPublic = inetAddrSecure;
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
if ( !m_arrServerList.Count() )
|
|
{
|
|
if ( Plat_FloatTime() < m_flTimeout )
|
|
{
|
|
m_eState = STATE_STEAM_NEXT_SEARCH_PASS;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_FINISHED;
|
|
return;
|
|
}
|
|
}
|
|
|
|
DsServer_t dss = m_arrServerList.Head();
|
|
m_arrServerList.RemoveMultipleFromHead( 1 );
|
|
|
|
Q_strncpy( m_Result.m_szPublicConnectionString, dss.m_szConnectionString, ARRAYSIZE( m_Result.m_szPublicConnectionString ) );
|
|
Q_strncpy( m_Result.m_szPrivateConnectionString, dss.m_szPrivateConnectionString, ARRAYSIZE( m_Result.m_szPrivateConnectionString ) );
|
|
|
|
netadr_t addrPublic, addrPrivate;
|
|
|
|
addrPublic.SetFromString( dss.m_szConnectionString );
|
|
|
|
if ( dss.m_szPrivateConnectionString[0] )
|
|
addrPrivate.SetFromString( dss.m_szPrivateConnectionString );
|
|
else
|
|
addrPrivate.SetType( NA_NULL );
|
|
|
|
#else
|
|
|
|
netadr_t addrPublic, addrPrivate;
|
|
|
|
#endif
|
|
|
|
g_pMatchExtensions->GetINetSupport()->ReserveServer( addrPublic, addrPrivate,
|
|
m_uiReserveCookie, m_pReserveSettings,
|
|
this, &m_pAsyncOperation );
|
|
}
|
|
|
|
void CDsSearcher::OnOperationFinished( IMatchAsyncOperation *pOperation )
|
|
{
|
|
Assert( pOperation == m_pAsyncOperation );
|
|
|
|
uint64 uiResult = m_pAsyncOperation->GetResult();
|
|
if ( m_pAsyncOperation->GetState() == AOS_FAILED || !uiResult )
|
|
{
|
|
ReserveNextServer();
|
|
}
|
|
else
|
|
{
|
|
m_Result.m_bDedicated = true;
|
|
char const *szReservedAddr = ( char const * )( int )uiResult;
|
|
Q_strncpy( m_Result.m_szConnectionString, szReservedAddr, ARRAYSIZE( m_Result.m_szConnectionString ) );
|
|
|
|
// If this server reservation reported number of game slots then
|
|
// force that setting into session data
|
|
uint32 numGameSlots = uint32( pOperation->GetResultExtraInfo() & 0xFF );
|
|
if ( m_pMatchSession && ( numGameSlots > 0 ) )
|
|
{
|
|
KeyValues *kvUpdate = new KeyValues( "update" );
|
|
KeyValues::AutoDelete autodelete( kvUpdate );
|
|
kvUpdate->SetInt( "update/members/numSlots", numGameSlots );
|
|
|
|
m_pMatchSession->UpdateSessionSettings( kvUpdate );
|
|
}
|
|
|
|
m_eState = STATE_FINISHED;
|
|
}
|
|
}
|
|
|