Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4565 lines
128 KiB

  1. //========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // baseserver.cpp: implementation of the CBaseServer class.
  9. //
  10. //////////////////////////////////////////////////////////////////////
  11. #if defined(_WIN32) && !defined(_X360)
  12. #include "winlite.h" // FILETIME
  13. #elif defined(OSX) || defined(CYGWIN)
  14. #include <time.h>
  15. #include <sys/time.h>
  16. #include <sys/resource.h>
  17. #include <netinet/in.h>
  18. #elif defined(LINUX)
  19. #include <time.h>
  20. #include <sys/sysinfo.h>
  21. #include <asm/param.h> // for HZ
  22. #include <netinet/in.h>
  23. #elif defined(_X360)
  24. #elif defined(_PS3)
  25. #else
  26. #error "Includes for CPU usage calcs here"
  27. #endif
  28. #include "filesystem_engine.h"
  29. #include "baseserver.h"
  30. #include "hltvserver.h"
  31. #include "sysexternal.h"
  32. #include "quakedef.h"
  33. #include "host.h"
  34. #include "netmessages.h"
  35. #include "master.h"
  36. #include "sys.h"
  37. #include "framesnapshot.h"
  38. #include "sv_packedentities.h"
  39. #include "dt_send_eng.h"
  40. #include "dt_recv_eng.h"
  41. #include "networkstringtable.h"
  42. #include "sys_dll.h"
  43. #include "host_cmd.h"
  44. #include "sv_steamauth.h"
  45. #include "SteamUserIDValidation.h"
  46. #include <proto_oob.h>
  47. #include <vstdlib/random.h>
  48. #include <irecipientfilter.h>
  49. #include <keyvalues.h>
  50. #include <tier0/vprof.h>
  51. #include <cdll_int.h>
  52. #include <eiface.h>
  53. #include <client_class.h>
  54. #include "tier0/icommandline.h"
  55. #include "sv_steamauth.h"
  56. #include "sv_ipratelimit.h"
  57. #include "cl_steamauth.h"
  58. #include "fmtstr.h"
  59. #if defined( _X360 )
  60. #include "xbox/xbox_win32stubs.h"
  61. #endif
  62. #include "mathlib/IceKey.H"
  63. #include "matchmaking/imatchframework.h"
  64. #include "tier2/tier2.h"
  65. #include "fmtstr.h"
  66. #include "sv_plugin.h"
  67. // memdbgon must be the last include file in a .cpp file!!!
  68. #include "tier0/memdbgon.h"
  69. CThreadFastMutex g_svInstanceBaselineMutex;
  70. // BUGBUG: JAY: Leaving this here for some of the matchmaking code. I don't want to delete the code or enable it
  71. // in other games yet. (came over in the merge and will be rationalized later)
  72. #define IsLeft4Dead() false
  73. // Give new data to Steam's master server updater every N seconds.
  74. // This is NOT how often packets are sent to master servers, only how often the
  75. // game server talks to Steam's master server updater (which is on the game server's
  76. // machine, not the Steam servers).
  77. #define MASTER_SERVER_UPDATE_INTERVAL 2.0
  78. // Steam has a matching one in matchmakingtypes.h
  79. #define MAX_TAG_STRING_LENGTH 128
  80. bool g_bSteamMasterHeartbeatsEnabled = false;
  81. int SortServerTags( char* const *p1, char* const *p2 )
  82. {
  83. return ( Q_strcmp( *p1, *p2 ) > 0 );
  84. }
  85. struct NoReentry_t
  86. {
  87. NoReentry_t( int *pn ) : m_pn( pn ) { ++ *m_pn; }
  88. ~NoReentry_t() { -- *m_pn; }
  89. int *m_pn;
  90. };
  91. static void ServerTagsCleanUp( void )
  92. {
  93. static int s_nNoReentry = 0;
  94. if ( s_nNoReentry )
  95. return;
  96. NoReentry_t noReentry( &s_nNoReentry );
  97. CUtlVector<char*> TagList;
  98. ConVarRef sv_tags( "sv_tags" );
  99. if ( sv_tags.IsValid() )
  100. {
  101. int i;
  102. char tmptags[MAX_TAG_STRING_LENGTH];
  103. tmptags[0] = '\0';
  104. V_SplitString( sv_tags.GetString(), ",", TagList );
  105. // make a pass on the tags to eliminate preceding whitespace and empty tags
  106. for ( i = 0; i < TagList.Count(); i++ )
  107. {
  108. if ( i > 0 )
  109. {
  110. Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
  111. }
  112. char *pChar = TagList[i];
  113. while ( *pChar && *pChar == ' ' )
  114. {
  115. pChar++;
  116. }
  117. // make sure we don't have an empty string (all spaces or ,,)
  118. if ( *pChar )
  119. {
  120. Q_strncat( tmptags, pChar, MAX_TAG_STRING_LENGTH );
  121. }
  122. }
  123. // reset our lists and sort the tags
  124. TagList.PurgeAndDeleteElements();
  125. V_SplitString( tmptags, ",", TagList );
  126. TagList.Sort( SortServerTags );
  127. tmptags[0] = '\0';
  128. // create our new, sorted list of tags
  129. for ( i = 0; i < TagList.Count(); i++ )
  130. {
  131. if ( i > 0 )
  132. {
  133. Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
  134. }
  135. Q_strncat( tmptags, TagList[i], MAX_TAG_STRING_LENGTH );
  136. }
  137. // set our convar and purge our list
  138. if ( Q_strcmp( tmptags, sv_tags.GetString() ) )
  139. {
  140. sv_tags.SetValue( tmptags );
  141. }
  142. TagList.PurgeAndDeleteElements();
  143. }
  144. }
  145. static void SvTagsChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
  146. {
  147. sv.UpdateGameData();
  148. if ( sv.IsActive() )
  149. {
  150. Cbuf_AddText( CBUF_SERVER, "heartbeat\n" );
  151. }
  152. ServerTagsCleanUp();
  153. }
  154. static void SvGameDataChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
  155. {
  156. // TODO: sv.UpdateGameData();
  157. if ( sv.IsActive() )
  158. {
  159. Cbuf_AddText( CBUF_SERVER, "heartbeat\n" );
  160. }
  161. }
  162. extern ConVar sv_search_key;
  163. extern ConVar sv_lan;
  164. extern ConVar cl_hideserverip;
  165. ConVar sv_region( "sv_region","-1", FCVAR_NONE | FCVAR_RELEASE, "The region of the world to report this server in." );
  166. static ConVar sv_instancebaselines( "sv_instancebaselines", "1", FCVAR_DEVELOPMENTONLY, "Enable instanced baselines. Saves network overhead." );
  167. static ConVar sv_stats( "sv_stats", "1", 0, "Collect CPU usage stats" );
  168. static ConVar sv_enableoldqueries( "sv_enableoldqueries", "0", 0, "Enable support for old style (HL1) server queries" );
  169. static ConVar sv_reservation_tickrate_adjustment( "sv_reservation_tickrate_adjustment", "0", FCVAR_RELEASE, "Adjust server tickrate upon reservation" );
  170. static void SvPasswordChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
  171. {
  172. ConVarRef cvref( pConVar );
  173. bool bOldPassword = ( pOldValue && pOldValue[0] && Q_stricmp( pOldValue, "none" ) );
  174. char const *pNewValue = cvref.GetString();
  175. bool bNewPassword = ( pNewValue && pNewValue[0] && Q_stricmp( pNewValue, "none" ) );
  176. if ( ( sv.GetNumHumanPlayers() > 0 ) || sv.IsReserved() )
  177. {
  178. if ( !bOldPassword && bNewPassword )
  179. {
  180. Msg( "Cannot require sv_password when server is already reserved or clients connected!\n" );
  181. cvref.SetValue( "" );
  182. }
  183. }
  184. sv.OnPasswordChanged();
  185. }
  186. static ConVar sv_password( "sv_password", "", FCVAR_NOTIFY | FCVAR_PROTECTED | FCVAR_DONTRECORD | FCVAR_RELEASE, "Server password for entry into multiplayer games", SvPasswordChangeCallback );
  187. ConVar sv_tags( "sv_tags", "", FCVAR_NOTIFY | FCVAR_RELEASE, "Server tags. Used to provide extra information to clients when they're browsing for servers. Separate tags with a comma.", SvTagsChangeCallback );
  188. ConVar sv_visiblemaxplayers( "sv_visiblemaxplayers", "-1", FCVAR_RELEASE, "Overrides the max players reported to prospective clients" );
  189. ConVar sv_alternateticks( "sv_alternateticks", ( IsX360() ) ? "1" : "0", FCVAR_RELEASE, "If set, server only simulates entities on even numbered ticks.\n" );
  190. ConVar sv_allow_wait_command( "sv_allow_wait_command", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "Allow or disallow the wait command on clients connected to this server." );
  191. #if !defined( CSTRIKE15 )
  192. // We are switching CStrike to always have lobbies associated with servers for community matchmaking
  193. ConVar sv_allow_lobby_connect_only( "sv_allow_lobby_connect_only", "1", FCVAR_RELEASE, "If set, players may only join this server from matchmaking lobby, may not connect directly." );
  194. #endif
  195. static ConVar sv_reservation_timeout( "sv_reservation_timeout", "45", FCVAR_RELEASE, "Time in seconds before lobby reservation expires.", true, 5.0f, true, 180.0f );
  196. static ConVar sv_reservation_grace( "sv_reservation_grace", "5", 0, "Time in seconds given for a lobby reservation.", true, 3.0f, true, 30.0f );
  197. ConVar sv_steamgroup( "sv_steamgroup", "", FCVAR_NOTIFY | FCVAR_RELEASE, "The ID of the steam group that this server belongs to. You can find your group's ID on the admin profile page in the steam community.", SvGameDataChangeCallback );
  198. ConVar sv_steamgroup_exclusive( "sv_steamgroup_exclusive", "0", FCVAR_RELEASE, "If set, only members of Steam group will be able to join the server when it's empty, public people will be able to join the server only if it has players." );
  199. static void SvMmQueueReservationChanged( IConVar *pConVar, const char *pOldValue, float flOldValue )
  200. {
  201. if ( serverGameDLL )
  202. serverGameDLL->UpdateGCInformation();
  203. }
  204. ConVar sv_mmqueue_reservation( "sv_mmqueue_reservation", "", FCVAR_DEVELOPMENTONLY | FCVAR_DONTRECORD, "Server queue reservation", SvMmQueueReservationChanged );
  205. ConVar sv_mmqueue_reservation_timeout( "sv_mmqueue_reservation_timeout", "21", FCVAR_DEVELOPMENTONLY, "Time in seconds before mmqueue reservation expires.", true, 5.0f, true, 180.0f );
  206. ConVar sv_mmqueue_reservation_extended_timeout( "sv_mmqueue_reservation_extended_timeout", "21", FCVAR_DEVELOPMENTONLY, "Extended time in seconds before mmqueue reservation expires.", true, 5.0f, true, 180.0f );
  207. extern CNetworkStringTableContainer *networkStringTableContainerServer;
  208. extern ConVar sv_stressbots;
  209. int g_CurGameServerID = 1;
  210. static void SetMasterServerKeyValue( ISteamGameServer *pGameServer, IConVar *pConVar )
  211. {
  212. ConVarRef var( pConVar );
  213. // For protected cvars, don't send the string
  214. if ( var.IsFlagSet( FCVAR_PROTECTED ) )
  215. {
  216. // If it has a value string and the string is not "none"
  217. if ( ( strlen( var.GetString() ) > 0 ) &&
  218. stricmp( var.GetString(), "none" ) )
  219. {
  220. pGameServer->SetKeyValue( var.GetName(), "1" );
  221. }
  222. else
  223. {
  224. pGameServer->SetKeyValue( var.GetName(), "0" );
  225. }
  226. }
  227. else
  228. {
  229. pGameServer->SetKeyValue( var.GetName(), var.GetString() );
  230. }
  231. if ( Steam3Server().BIsActive() )
  232. {
  233. sv.RecalculateTags();
  234. }
  235. }
  236. static KeyValues *g_pKVrulesConvars = NULL;
  237. static void ServerNotifyVarChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue )
  238. {
  239. if ( !pConVar->IsFlagSet( FCVAR_NOTIFY ) )
  240. return;
  241. if ( !g_pKVrulesConvars->GetBool( pConVar->GetName() ) )
  242. return;
  243. ISteamGameServer *pGameServer = Steam3Server().SteamGameServer();
  244. if ( !pGameServer )
  245. {
  246. // This will force it to send all the rules whenever the master server updater is there.
  247. sv.SetMasterServerRulesDirty();
  248. return;
  249. }
  250. SetMasterServerKeyValue( pGameServer, pConVar );
  251. }
  252. //////////////////////////////////////////////////////////////////////
  253. // Construction/Destruction
  254. //////////////////////////////////////////////////////////////////////
  255. CBaseServer::CBaseServer() :
  256. m_ServerQueryChallenges( 0, 1024 ), // start with 1K of entries, and alloc in 1K chunks
  257. m_BaselineHandles( DefLessFunc( int ) ),
  258. m_flFlagForSteamIDReuseAfterShutdownTime( 0 )
  259. {
  260. // Just get a unique ID to talk to the steam master server updater.
  261. m_bRestartOnLevelChange = false;
  262. m_StringTables = NULL;
  263. m_pInstanceBaselineTable = NULL;
  264. m_pLightStyleTable = NULL;
  265. m_pUserInfoTable = NULL;
  266. m_pServerStartupTable = NULL;
  267. m_pDownloadableFileTable = NULL;
  268. m_fLastCPUCheckTime = 0;
  269. m_fStartTime = 0;
  270. m_fCPUPercent = 0;
  271. m_Socket = NS_SERVER;
  272. m_nTickCount = 0;
  273. m_szMapname[0] = 0;
  274. m_szBaseMapname[0] = 0;
  275. m_szMapGroupName[0] = 0;
  276. m_szSkyname[0] = 0;
  277. m_Password[0] = 0;
  278. worldmapCRC = 0;
  279. clientDllCRC = 0;
  280. stringTableCRC = 0;
  281. serverclasses = serverclassbits = 0;
  282. m_nMaxclients = m_nSpawnCount = 0;
  283. m_flTickInterval = 0.03;
  284. m_flTimescale = 1.0f;
  285. m_nUserid = 0;
  286. m_bIsDedicated = false;
  287. m_bIsDedicatedForXbox = false;
  288. m_bIsDedicatedForPS3 = false;
  289. m_fCPUPercent = 0;
  290. m_fLastCPUCheckTime = 0;
  291. m_bMasterServerRulesDirty = true;
  292. m_flLastMasterServerUpdateTime = 0;
  293. m_nReservationCookie = 0;
  294. m_pnReservationCookieSession = NULL;
  295. m_flReservationExpiryTime = -1.0f;
  296. m_flTimeLastClientLeft = -1.0f;
  297. m_numGameSlots = 0;
  298. m_flTimeReservationGraceStarted = -1.0f;
  299. m_GameDataVersion = 0;
  300. m_nMatchId = 0;
  301. }
  302. CBaseServer::~CBaseServer()
  303. {
  304. ClearBaselineHandles();
  305. }
  306. /*
  307. ================
  308. SV_CheckChallenge
  309. Make sure connecting client is not spoofing
  310. ================
  311. */
  312. bool CBaseServer::CheckChallengeNr( const ns_address &adr, int nChallengeValue )
  313. {
  314. // See if the challenge is valid
  315. // Don't care if it is a local address.
  316. if ( adr.IsLoopback() )
  317. return true;
  318. // X360TBD: network
  319. if ( IsX360() || IsDedicatedForXbox() )
  320. return true;
  321. for (int i=0 ; i<m_ServerQueryChallenges.Count() ; i++)
  322. {
  323. if ( adr.CompareAdr(m_ServerQueryChallenges[i].adr, true) ) // base adr only
  324. {
  325. if (nChallengeValue != m_ServerQueryChallenges[i].challenge)
  326. {
  327. return false;
  328. }
  329. if ( net_time > ( m_ServerQueryChallenges[i].time+ CHALLENGE_LIFETIME) ) // allow challenge values to last for 1 hour
  330. {
  331. m_ServerQueryChallenges.FastRemove(i);
  332. ConMsg( "Old challenge from %s.\n", ns_address_render( adr ).String() );
  333. return false;
  334. }
  335. return true;
  336. }
  337. // clean up any old entries
  338. if ( net_time > ( m_ServerQueryChallenges[i].time+ CHALLENGE_LIFETIME) )
  339. {
  340. m_ServerQueryChallenges.FastRemove(i);
  341. i--; // backup one as we just shifted the whole vector back by the deleted element
  342. }
  343. }
  344. if ( nChallengeValue != -1 )
  345. {
  346. ConDMsg( "No challenge from %s.\n", ns_address_render( adr ).String() ); // this is a common message
  347. }
  348. return false;
  349. }
  350. bool CBaseServer::CanAcceptChallengesFrom( const ns_address &adrFrom ) const
  351. {
  352. // check timeout
  353. if ( m_flTimeReservationGraceStarted < 0 )
  354. return true;
  355. if ( ( net_time - m_flTimeReservationGraceStarted ) > sv_reservation_grace.GetFloat() )
  356. return true;
  357. // otherwise can only accept from a single address
  358. return adrFrom.CompareAdr( m_adrReservationGraceStarted );
  359. }
  360. const char *CBaseServer::GetPassword() const
  361. {
  362. const char *password = sv_password.GetString();
  363. // if password is empty or "none", return NULL
  364. if ( !password[0] || !Q_stricmp(password, "none" ) )
  365. {
  366. return NULL;
  367. }
  368. return password;
  369. }
  370. void CBaseServer::SetPassword(const char *password)
  371. {
  372. if ( password != NULL )
  373. {
  374. Q_strncpy( m_Password, password, sizeof(m_Password) );
  375. }
  376. else
  377. {
  378. m_Password[0] = 0; // clear password
  379. }
  380. }
  381. int CBaseServer::GetNextUserID()
  382. {
  383. // Note: we'll usually exit on the first pass of this loop..
  384. for ( int i=0; i < m_Clients.Count()+1; i++ )
  385. {
  386. int nTestID = (m_nUserid + i + 1) % SHRT_MAX;
  387. // Make sure no client has this user ID.
  388. int iClient;
  389. for ( iClient=0; iClient < m_Clients.Count(); iClient++ )
  390. {
  391. if ( m_Clients[iClient]->GetUserID() == nTestID )
  392. break;
  393. }
  394. // Ok, no client has this ID, so return it.
  395. if ( iClient == m_Clients.Count() )
  396. return nTestID;
  397. }
  398. Assert( !"GetNextUserID: can't find a unique ID." );
  399. return m_nUserid + 1;
  400. }
  401. bool CBaseServer::IsSinglePlayerGame() const
  402. {
  403. #if !defined( DEDICATED )
  404. if ( sv.IsDedicated() )
  405. {
  406. return false;
  407. }
  408. if ( m_nMaxclients <= 1 )
  409. return true;
  410. #if !defined( PORTAL2 ) && !defined( CSTRIKE15 )
  411. //
  412. // Portal 2 offline splitscreen games should NOT be paused
  413. // transition movies during loading rely on player think functions
  414. // See bugbait 81253: https://bugbait.valvesoftware.com/show_bug.cgi?id=81253
  415. //
  416. if ( IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession() )
  417. {
  418. if ( KeyValues *pSettings = pIMatchSession->GetSessionSettings() )
  419. {
  420. char const *szNetwork = pSettings->GetString( "system/network", "" );
  421. if ( szNetwork && !Q_stricmp( szNetwork, "offline" ) )
  422. return true;
  423. }
  424. }
  425. #endif
  426. #endif
  427. return false;
  428. }
  429. /*
  430. ================
  431. SV_ConnectClient
  432. Initializes a CSVClient for a new net connection. This will only be called
  433. once for a player each game, not once for each level change.
  434. ================
  435. */
  436. IClient *CBaseServer::ConnectClient ( const ns_address &adr, int protocol, int challenge, int authProtocol,
  437. const char *name, const char *password, const char *hashedCDkey, int cdKeyLen,
  438. CUtlVector< CCLCMsg_SplitPlayerConnect_t * > & splitScreenClients, bool isClientLowViolence, CrossPlayPlatform_t clientPlatform,
  439. const byte *pbEncryptionKey, int nEncryptionKeyIndex )
  440. {
  441. ns_address_render sAdr( adr );
  442. #ifdef IHV_DEMO
  443. if ( !adr.IsLoopback() )
  444. {
  445. Warning( "This demo version only works as a listen server. Ignoring connection request from %s\n", adr.ToString() );
  446. return NULL;
  447. }
  448. else
  449. {
  450. DevMsg( "IHV Demo Version - Connected to loopback client.\n");
  451. }
  452. #endif
  453. COM_TimestampedLog( "CBaseServer::ConnectClient" );
  454. if ( !IsActive() )
  455. {
  456. DevMsg( "Server not active, ignoring %s\n", sAdr.String() );
  457. return NULL;
  458. }
  459. if ( !name || !password || !hashedCDkey )
  460. {
  461. DevMsg( "Bad auth data from %s\n", sAdr.String() );
  462. return NULL;
  463. }
  464. // Make sure protocols match up
  465. if ( !CheckProtocol( adr, protocol ) )
  466. {
  467. DevMsg( "Protocol error from %s\n", sAdr.String() );
  468. return NULL;
  469. }
  470. if ( !CheckChallengeNr( adr, challenge ) )
  471. {
  472. RejectConnection( adr, "Bad challenge.\n");
  473. return NULL;
  474. }
  475. bool bIsLocalConnection = adr.IsLocalhost() || adr.IsLoopback();
  476. #ifndef NO_STEAM
  477. if ( IsExclusiveToLobbyConnections() && !IsReserved() && !bIsLocalConnection )
  478. {
  479. RejectConnection( adr, "Server only accepting connections from game lobby %s %d.\n", sAdr.String(), challenge );
  480. return NULL;
  481. }
  482. #endif
  483. // Listen server level background map is always a single
  484. // player map, don't allow shenanigans or mayhem.
  485. // Also, if the user started a "single player" campaign, don't let anyone else join
  486. if ( !IsDedicated() &&
  487. !bIsLocalConnection )
  488. {
  489. if ( sv.IsLevelMainMenuBackground() )
  490. {
  491. RejectConnection( adr, "#Valve_Reject_Background_Map" );
  492. return NULL;
  493. }
  494. if ( IsSinglePlayerGame() )
  495. {
  496. RejectConnection( adr, "#Valve_Reject_Single_Player" );
  497. return NULL;
  498. }
  499. if ( ShouldHideServer() )
  500. {
  501. // Right now, hidden means commentary, "solo" or background map (l4d), the former of which are covered above.
  502. RejectConnection( adr, "#Valve_Reject_Hidden_Game" );
  503. return NULL;
  504. }
  505. }
  506. // SourceTV checks password & restrictions later once we know
  507. // if its a normal spectator client or a relay proxy
  508. if ( !IsHLTV() && !IsReplay() )
  509. {
  510. #ifndef NO_STEAM
  511. // LAN servers restrict to class b IP addresses
  512. if ( !CheckIPRestrictions( adr, authProtocol ) )
  513. {
  514. RejectConnection( adr, "#Valve_Reject_LAN_Game");
  515. return NULL;
  516. }
  517. #endif
  518. if ( !CheckPassword( adr, password, name ) )
  519. {
  520. // failed
  521. ConMsg ( "%s: password failed.\n", sAdr.String() );
  522. // Special rejection handler.
  523. RejectConnection( adr, "#Valve_Reject_Bad_Password" );
  524. return NULL;
  525. }
  526. }
  527. if ( m_numGameSlots )
  528. {
  529. int numSlotsRequested = splitScreenClients.Count();
  530. if ( GetNumClients() - GetNumFakeClients() + numSlotsRequested > m_numGameSlots )
  531. {
  532. bool bClientReconnecting = false;
  533. for ( int slot = 0 ; slot < m_Clients.Count() ; slot++ )
  534. {
  535. CBaseClient *client = m_Clients[slot];
  536. if ( client->IsFakeClient() )
  537. continue;
  538. if ( client->IsConnected() && adr.CompareAdr ( client->m_NetChannel->GetRemoteAddress() ) )
  539. {
  540. ConMsg ( "%s:reconnect\n", sAdr.String() );
  541. bClientReconnecting = true;
  542. break;
  543. }
  544. }
  545. if ( ( numSlotsRequested == 1 ) && bClientReconnecting )
  546. {
  547. // Allow client to proceed with the connection normally and take over their self
  548. }
  549. else
  550. {
  551. RejectConnection( adr, "#Valve_Reject_Server_Full" );
  552. return NULL; // cannot accept, exceeding game mode slot count
  553. }
  554. }
  555. }
  556. // Let a GOTV server redirect the client immediately without further
  557. // handshake and netchannel work if the redirect is known to be required
  558. // at this point.
  559. ns_address netAdrRedirect;
  560. if ( GetRedirectAddressForConnectClient( adr, splitScreenClients, &netAdrRedirect ) )
  561. {
  562. if ( netAdrRedirect.IsValid() )
  563. RejectConnection( adr, "ConnectRedirectAddress:%s\n", ns_address_render( netAdrRedirect ).String() );
  564. return NULL;
  565. }
  566. COM_TimestampedLog( "CBaseServer::ConnectClient: GetFreeClient" );
  567. CBaseClient *client = GetFreeClient( adr );
  568. if ( !client )
  569. {
  570. RejectConnection( adr, "#Valve_Reject_Server_Full" );
  571. return NULL; // no free slot found
  572. }
  573. int nNextUserID = GetNextUserID();
  574. if ( !CheckChallengeType( client, nNextUserID, adr, authProtocol, hashedCDkey, cdKeyLen ) ) // we use the client pointer to track steam requests
  575. {
  576. return NULL;
  577. }
  578. #ifndef _HLTVTEST
  579. #ifndef _REPLAYTEST
  580. if ( !FinishCertificateCheck( adr, authProtocol, hashedCDkey ) )
  581. {
  582. return NULL;
  583. }
  584. #endif
  585. #endif
  586. // Make sure client user info carries correct cookie
  587. bool bValidatedUserInfo = false;
  588. if ( GetReservationCookie() != 0u )
  589. {
  590. if ( splitScreenClients.Count() )
  591. {
  592. const CMsg_CVars& convars = splitScreenClients[0]->convars();
  593. for ( int i = 0; i< convars.cvars_size(); ++i )
  594. {
  595. const char *cvname = NetMsgGetCVarUsingDictionary( convars.cvars(i) );
  596. const char *value = convars.cvars(i).value().c_str();
  597. if ( stricmp( cvname, "cl_session" ) )
  598. continue;
  599. uint64 uid;
  600. if ( sscanf( value, "$%llx", &uid ) != 1 )
  601. {
  602. Warning( "failed to parse session id %s\n", value );
  603. }
  604. else
  605. {
  606. if ( uid == GetReservationCookie() )
  607. bValidatedUserInfo = true;
  608. else
  609. Warning( "mismatching cookie from %s, client %llx, server %llx!\n",
  610. sAdr.String(), uid, GetReservationCookie() );
  611. }
  612. }
  613. }
  614. }
  615. else
  616. {
  617. bValidatedUserInfo = true;
  618. }
  619. // If we failed to validate user info - reject
  620. if ( !bValidatedUserInfo )
  621. {
  622. RejectConnection( adr, "Invalid user info.\n" );
  623. return NULL;
  624. }
  625. // Final validation chance by server.dll
  626. if ( char const *szGameServerError = serverGameDLL->ClientConnectionValidatePreNetChan( ( this == &sv ), sAdr.String(), authProtocol, client->m_SteamID.ConvertToUint64() ) )
  627. {
  628. RejectConnection( adr, "%s", szGameServerError );
  629. return NULL;
  630. }
  631. COM_TimestampedLog( "CBaseServer::ConnectClient: NET_CreateNetChannel" );
  632. // Fix the empty name from FCVAR_USERINFO data
  633. // for ( int k = 0; !name[ 0 ] && ( k < splitScreenPlayers.Count() ); ++k )
  634. char const *pchClientConnectionName = name;
  635. if ( !pchClientConnectionName[ 0 ] && splitScreenClients.Count() )
  636. {
  637. for ( int iCvar = 0; iCvar < splitScreenClients[ 0 ]->convars().cvars().size(); ++iCvar )
  638. {
  639. CMsg_CVars::CVar const &rCvarInfo = splitScreenClients[ 0 ]->convars().cvars( iCvar );
  640. if ( !V_strcmp( "name", NetMsgGetCVarUsingDictionary( rCvarInfo ) ) )
  641. {
  642. pchClientConnectionName = rCvarInfo.value().c_str();
  643. break;
  644. }
  645. }
  646. }
  647. // Use an override for connection name
  648. pchClientConnectionName = serverGameClients->ClientNameHandler( client->m_SteamID.ConvertToUint64(), pchClientConnectionName );
  649. // create network channel
  650. // Encryption keys for the client must have been received previously
  651. INetChannel * netchan = NET_CreateNetChannel( m_Socket, &adr, pchClientConnectionName, client, pbEncryptionKey, false );
  652. if ( !netchan )
  653. {
  654. RejectConnection( adr, "Failed to create net channel.\n");
  655. return NULL;
  656. }
  657. // setup netchannl settings
  658. netchan->SetChallengeNr( challenge );
  659. COM_TimestampedLog( "CBaseServer::ConnectClient: client->Connect" );
  660. // make sure client is reset and clear
  661. client->Connect( pchClientConnectionName, nNextUserID, netchan,
  662. false, // real client
  663. clientPlatform,
  664. (splitScreenClients.Count() > 0) ? &splitScreenClients[0]->convars() : NULL ); // userinfo if supplied
  665. client->m_bLowViolence = isClientLowViolence;
  666. m_nUserid = nNextUserID;
  667. // Will get reset from userinfo, but this value comes from sv_updaterate ( the default )
  668. client->m_fSnapshotInterval = 1.0f/20.0f;
  669. client->m_fNextMessageTime = net_time + client->m_fSnapshotInterval;
  670. // Force a full delta update on first packet.
  671. client->m_nDeltaTick = -1;
  672. client->m_nSignonTick = 0;
  673. client->m_nStringTableAckTick = 0;
  674. client->m_pLastSnapshot = NULL;
  675. // Tell client connection worked, now use netchannels
  676. NET_OutOfBandPrintf ( m_Socket, adr, "%c.%08X.0000.0000.0000.0000.", S2C_CONNECTION, nEncryptionKeyIndex );
  677. // Set up client structure.
  678. if ( authProtocol == PROTOCOL_HASHEDCDKEY )
  679. {
  680. // use hased CD key as player GUID
  681. Q_strncpy ( client->m_GUID, hashedCDkey, SIGNED_GUID_LEN );
  682. client->m_GUID[SIGNED_GUID_LEN] = '\0';
  683. }
  684. else if ( authProtocol == PROTOCOL_STEAM )
  685. {
  686. // StartSteamValidation() above initialized the clients networkid
  687. }
  688. //now process the split screen clients who came in with this client
  689. for( int playerIndex = 1; playerIndex < splitScreenClients.Count(); ++ playerIndex )
  690. {
  691. ConMsg( "Processing Split Screen connection packet.\n" );
  692. client->CLCMsg_SplitPlayerConnect( *splitScreenClients[ playerIndex ] );
  693. }
  694. if ( netchan && !netchan->IsLoopback() )
  695. {
  696. ConMsg("Client \"%s\" connected (%s).\n", client->GetClientName(), cl_hideserverip.GetInt()>0 ? "<ip hidden>" : netchan->GetAddress() );
  697. }
  698. // Once someone is successfully on the server, it'll hibernate when client count goes to zero
  699. m_flReservationExpiryTime = 0.0f;
  700. m_flTimeLastClientLeft = -1.0f;
  701. return client;
  702. }
  703. /*
  704. ================
  705. RequireValidChallenge
  706. Return true if this server query must provide a valid challenge number
  707. ================
  708. */
  709. bool CBaseServer::RequireValidChallenge( const ns_address &adr )
  710. {
  711. if ( sv_enableoldqueries.GetBool() == true )
  712. {
  713. return false; // don't enforce challenge numbers
  714. }
  715. return true;
  716. }
  717. /*
  718. ================
  719. ValidChallenge
  720. Return true if this challenge number is correct for this host (for server queries)
  721. ================
  722. */
  723. bool CBaseServer::ValidChallenge( const ns_address & adr, int challengeNr )
  724. {
  725. if ( !IsActive() ) // Must be running a server.
  726. return false ;
  727. if ( !IsMultiplayer() ) // ignore in single player
  728. return false ;
  729. if ( RequireValidChallenge( adr) )
  730. {
  731. if ( !CheckChallengeNr( adr, challengeNr ) )
  732. {
  733. ReplyServerChallenge( adr );
  734. return false;
  735. }
  736. }
  737. return true;
  738. }
  739. bool CBaseServer::ValidInfoChallenge( const ns_address & adr, const char *nugget )
  740. {
  741. if ( !IsActive() ) // Must be running a server.
  742. return false ;
  743. if ( !IsMultiplayer() ) // ignore in single player
  744. return false ;
  745. if ( RequireValidChallenge( adr) )
  746. {
  747. if ( Q_stricmp( nugget, A2S_KEY_STRING ) ) // if the string isn't equal then fail out
  748. {
  749. return false;
  750. }
  751. }
  752. return true;
  753. }
  754. bool CBaseServer::ProcessConnectionlessPacket(netpacket_t * packet)
  755. {
  756. // NOTE: msg is copy-constructed from packet->message, so reading
  757. // from "msg" will not advance read-pointer in "packet->message"!!!
  758. // ... and vice-versa, hence passing "packet" to a nested function
  759. // will make that function receive the original unprocessed message.
  760. // [ this differs from client-side connectionless packet processing ]
  761. if ( !CheckConnectionLessRateLimits( packet->from ) )
  762. {
  763. return false;
  764. }
  765. bf_read msg = packet->message; // handy shortcut
  766. char c = msg.ReadChar();
  767. if ( !IsActive() || !Host_ShouldRun() )
  768. {
  769. // Server should not be processing most of the traffic in this
  770. // state except client LAN searches
  771. if ( IsDedicated() )
  772. return true;
  773. if ( c != 0 )
  774. return true;
  775. }
  776. switch ( c )
  777. {
  778. case A2A_PING : NET_OutOfBandPrintf (packet->source, packet->from, "%c00000000000000", A2A_ACK );
  779. break;
  780. case A2A_PRINT : // Don't spew to server console, this was being used to spam the server console since we publish server public IP addresses...
  781. break;
  782. case A2A_ACK : ConMsg ("A2A_ACK from %s\n", ns_address_render( packet->from ).String() );
  783. break;
  784. case A2S_GETCHALLENGE :
  785. #if !defined(NO_STEAM)
  786. // Drop packet if we don't yet have our Steam ID
  787. // because we're still logging on
  788. if ( !Steam3Server().BHasLogonResult() )
  789. break;
  790. #endif
  791. ReplyChallenge( packet->from, msg );
  792. break;
  793. case A2S_SERVERQUERY_GETCHALLENGE: ReplyServerChallenge( packet->from );
  794. break;
  795. #if ENGINE_CONNECT_VIA_MMS
  796. case C2S_VALIDATE_SESSION: {
  797. int protocol = msg.ReadLong();
  798. uint64 uiXuid = msg.ReadLongLong();
  799. uint64 uiSessionId = msg.ReadLongLong();
  800. Msg( "C2S_VALIDATE_SESSION from %llx, session %llx, protocol %d.\n", uiXuid, uiSessionId, protocol );
  801. if ( GetHostVersion() == protocol )
  802. {
  803. extern void HostValidateSessionImpl();
  804. HostValidateSessionImpl();
  805. }
  806. }
  807. break;
  808. #endif
  809. case C2S_CONNECT : { char cdkey[STEAM_KEYSIZE];
  810. char name[256] = {};
  811. char password[256] = {};
  812. int protocol = msg.ReadLong();
  813. int authProtocol = msg.ReadLong();
  814. int challengeNr = msg.ReadLong();
  815. msg.ReadString( name, sizeof(name) );
  816. msg.ReadString( password, sizeof(password) );
  817. //decode split screen player info from the message
  818. int numPlayers = msg.ReadByte();
  819. if ( numPlayers > host_state.max_splitscreen_players || numPlayers < 0 )
  820. { // Make sure we reject connection early, otherwise it will trigger Disconnect
  821. // freeing client inside ConnectClient and cause memory access violation
  822. DevMsg( "Rejecting connection request from %s, players = %u.\n", ns_address_render( packet->from ).String(), numPlayers );
  823. RejectConnection( packet->from, "No more split screen slots!" );
  824. break;
  825. }
  826. struct AutoCleanupVectorPlayers_t : public CUtlVector< CCLCMsg_SplitPlayerConnect_t * >
  827. { ~AutoCleanupVectorPlayers_t() { PurgeAndDeleteElements(); } }
  828. splitScreenPlayers;
  829. if( numPlayers > 0 )
  830. {
  831. for( int playerCount = 0; playerCount < numPlayers; ++ playerCount ) // get CLC_SplitPlayerConnect msg for all players even if only one is connecting
  832. {
  833. msg.ReadVarInt32(); //the packet type.
  834. CCLCMsg_SplitPlayerConnect_t *pSplitPlayerConnect = new CCLCMsg_SplitPlayerConnect_t;
  835. splitScreenPlayers.AddToTail( pSplitPlayerConnect );
  836. if ( !pSplitPlayerConnect->ReadFromBuffer( msg ) )
  837. {
  838. numPlayers = -1; // Trigger an error
  839. }
  840. else if ( pSplitPlayerConnect->convars().cvars_size() )
  841. { // Make sure convars are expanded using dictionary
  842. for ( int iCV = 0; iCV < pSplitPlayerConnect->convars().cvars_size(); ++ iCV )
  843. {
  844. CMsg_CVars::CVar *convar = pSplitPlayerConnect->mutable_convars()->mutable_cvars( iCV );
  845. NetMsgExpandCVarUsingDictionary( convar );
  846. }
  847. }
  848. }
  849. }
  850. if ( numPlayers < 0 )
  851. { // Catch malformed user info
  852. DevMsg( "Rejecting connection request from %s, malformed userinfo.\n", ns_address_render( packet->from ).String() );
  853. RejectConnection( packet->from, "No more split screen slots!" );
  854. break;
  855. }
  856. bool isClientLowViolence = msg.ReadOneBit() != 0;
  857. uint64 nReservationCookie = msg.ReadLongLong();
  858. CrossPlayPlatform_t clientPlatform = (CrossPlayPlatform_t)msg.ReadByte();
  859. if( (clientPlatform == CROSSPLAYPLATFORM_UNKNOWN) || (clientPlatform > CROSSPLAYPLATFORM_LAST) )
  860. {
  861. DevMsg( "Rejecting connection request from %s, client's cross-play platform is unrecognized.\n", ns_address_render( packet->from ).String() );
  862. RejectConnection( packet->from, "Invalid cross-play platform id\n" );
  863. break;
  864. }
  865. bool bIsLocalConnection = packet->from.IsLocalhost() || packet->from.IsLoopback();
  866. // On a listen server, local player always gets on regardless of cookie
  867. if ( IsDedicated() || !bIsLocalConnection )
  868. {
  869. if ( IsExclusiveToLobbyConnections() &&
  870. m_nReservationCookie != nReservationCookie )
  871. {
  872. DevMsg( "Rejecting connection request from %s, client's reservation cookie %llx does not match servers's cookie %llx.\n",
  873. ns_address_render( packet->from ).String(), nReservationCookie, m_nReservationCookie );
  874. RejectConnection( packet->from, "#Valve_Reject_Connect_From_Lobby" );
  875. break;
  876. }
  877. // if this server is currently reserved, only allow connection if the client provides the cookie the server was reserved with
  878. if ( IsReserved() && m_nReservationCookie != nReservationCookie )
  879. {
  880. DevMsg( "Rejecting connection request from %s (reservation cookie 0x%llx), server is reserved with reservation cookie 0x%llx "
  881. "for %.1f more seconds\n", ns_address_render( packet->from ).String(), nReservationCookie, m_nReservationCookie,
  882. ( m_flReservationExpiryTime - net_time ) );
  883. RejectConnection( packet->from, "#Valve_Reject_Reserved_For_Lobby" );
  884. break;
  885. }
  886. // Check if we are running with a special flag and not advertise to Steam
  887. if ( !V_strcmp( g_pLaunchOptions->GetFirstSubKey()->GetNextKey()->GetNextKey()->GetString(), "server_is_unavailable" ) )
  888. {
  889. DevMsg( "Rejecting connection request from %s (reservation cookie 0x%llx), server is reserved with reservation cookie 0x%llx "
  890. "for %.1f more seconds, running with tag server_is_unavailable\n", ns_address_render( packet->from ).String(), nReservationCookie, m_nReservationCookie,
  891. ( m_flReservationExpiryTime - net_time ) );
  892. RejectConnection( packet->from, "#Valve_Reject_Workshop_Loading" );
  893. break;
  894. }
  895. }
  896. //
  897. // Get client certificate
  898. //
  899. const byte *pbNetEncryptPrivateKey = NULL;
  900. int cbNetEncryptPrivateKey = 0;
  901. bool bNetEncryptPrivateKey = NET_CryptGetNetworkCertificate( k_ENetworkCertificate_PrivateKey, &pbNetEncryptPrivateKey, &cbNetEncryptPrivateKey );
  902. // Read whether the client would like to use encryption key?
  903. byte *pbClientPlainKey = NULL;
  904. int nEncryptionKeyIndex = msg.ReadLong();
  905. // Peek into client-supplied speculative accountid
  906. static bool s_bExternalCryptKeys = ( CommandLine()->CheckParm( "-externalnetworkcryptkey" ) != NULL );
  907. AccountID_t unAccountIDfor3rdParties = 0;
  908. if ( s_bExternalCryptKeys && splitScreenPlayers.Count() )
  909. {
  910. const CMsg_CVars& convars = splitScreenPlayers[ 0 ]->convars();
  911. for ( int i = 0; i < convars.cvars_size(); ++i )
  912. {
  913. const char *cvname = NetMsgGetCVarUsingDictionary( convars.cvars( i ) );
  914. const char *value = convars.cvars( i ).value().c_str();
  915. if ( stricmp( cvname, "accountid" ) )
  916. continue;
  917. if ( unAccountIDfor3rdParties )
  918. {
  919. unAccountIDfor3rdParties = 0;
  920. break;
  921. }
  922. unAccountIDfor3rdParties = Q_atoi( value );
  923. }
  924. }
  925. // Community-servers feature to manage encryption keys in their secure clients
  926. if ( s_bExternalCryptKeys )
  927. {
  928. bool bClientWantsToUseCryptKey = ( nEncryptionKeyIndex != 0 );
  929. bNetEncryptPrivateKey = g_pServerPluginHandler->BNetworkCryptKeyCheckRequired( packet->from.GetIP(), packet->from.GetPort(),
  930. unAccountIDfor3rdParties, bClientWantsToUseCryptKey );
  931. }
  932. if ( nEncryptionKeyIndex )
  933. {
  934. int numEncryptedBytes = msg.ReadLong();
  935. if ( ( numEncryptedBytes > 0 ) && ( numEncryptedBytes <= 256 ) )
  936. {
  937. byte *pbEncryptedKey = ( byte * ) stackalloc( numEncryptedBytes );
  938. msg.ReadBytes( pbEncryptedKey, numEncryptedBytes );
  939. // Community-servers feature to manage encryption keys in their secure clients
  940. if ( s_bExternalCryptKeys )
  941. {
  942. // Client can send whatever gibberish they want, plugins manage the encryption keys
  943. byte chPlainKey[ 1024 ] = {};
  944. if ( g_pServerPluginHandler->BNetworkCryptKeyValidate( packet->from.GetIP(), packet->from.GetPort(), unAccountIDfor3rdParties,
  945. nEncryptionKeyIndex, numEncryptedBytes, pbEncryptedKey, chPlainKey ) )
  946. {
  947. pbClientPlainKey = ( byte * ) stackalloc( NET_CRYPT_KEY_LENGTH );
  948. Q_memcpy( pbClientPlainKey, chPlainKey, NET_CRYPT_KEY_LENGTH );
  949. }
  950. }
  951. else
  952. {
  953. //
  954. // Server will send the certificate and its signature down to the client
  955. //
  956. byte chPlainKey[ 1024 ] = {};
  957. if ( bNetEncryptPrivateKey && NET_CryptVerifyClientSessionKey( serverGameDLL->IsValveDS(),
  958. pbNetEncryptPrivateKey, cbNetEncryptPrivateKey,
  959. pbEncryptedKey, numEncryptedBytes,
  960. chPlainKey, Q_ARRAYSIZE( chPlainKey ) ) )
  961. {
  962. pbClientPlainKey = ( byte * ) stackalloc( NET_CRYPT_KEY_LENGTH );
  963. Q_memcpy( pbClientPlainKey, chPlainKey, NET_CRYPT_KEY_LENGTH );
  964. }
  965. }
  966. }
  967. }
  968. if ( ( this == &sv ) && bNetEncryptPrivateKey && !pbClientPlainKey )
  969. {
  970. DevMsg( "Rejecting connection request from %s, no certificate.\n", ns_address_render( packet->from ).String() );
  971. RejectConnection( packet->from, "Invalid certificate\n" );
  972. break;
  973. }
  974. if ( authProtocol == PROTOCOL_STEAM )
  975. {
  976. int keyLen = msg.ReadShort();
  977. if ( keyLen < 0 || keyLen > sizeof(cdkey) )
  978. {
  979. RejectConnection( packet->from, "Invalid Steam key length\n" );
  980. break;
  981. }
  982. msg.ReadBytes( cdkey, keyLen );
  983. ConnectClient( packet->from, protocol,
  984. challengeNr, authProtocol, name, password, cdkey, keyLen, splitScreenPlayers, isClientLowViolence, clientPlatform,
  985. pbClientPlainKey, nEncryptionKeyIndex ); // cd key is actually a raw encrypted key
  986. }
  987. else
  988. {
  989. msg.ReadString( cdkey, sizeof(cdkey) );
  990. ConnectClient( packet->from, protocol,
  991. challengeNr, authProtocol, name, password, cdkey, strlen(cdkey), splitScreenPlayers, isClientLowViolence, clientPlatform,
  992. pbClientPlainKey, nEncryptionKeyIndex );
  993. }
  994. }
  995. break;
  996. case A2A_CUSTOM : // TODO fire game event with string and adr so 3rd party tool can get it
  997. break;
  998. #if ENGINE_CONNECT_VIA_MMS
  999. case A2S_RESERVE : ReplyReservationRequest( packet->from, msg );
  1000. break;
  1001. #endif
  1002. case A2S_RESERVE_CHECK : ReplyReservationCheckRequest( packet->from, msg );
  1003. break;
  1004. case A2S_PING:
  1005. {
  1006. if ( msg.ReadLong() == GetHostVersion() )
  1007. {
  1008. uint32 token = msg.ReadLong();
  1009. char buffer[64];
  1010. bf_write msg(buffer,sizeof(buffer));
  1011. msg.WriteLong( CONNECTIONLESS_HEADER );
  1012. msg.WriteByte( S2A_PING_RESPONSE );
  1013. msg.WriteLong( GetHostVersion() );
  1014. msg.WriteLong( token );
  1015. NET_SendPacket( NULL, m_Socket, packet->from, msg.GetData(), msg.GetNumBytesWritten() );
  1016. }
  1017. break;
  1018. }
  1019. case 0 :
  1020. {
  1021. // Feed into matchmaking
  1022. KeyValues *notify = new KeyValues( "OnNetLanConnectionlessPacket" );
  1023. notify->SetPtr( "rawpkt", packet );
  1024. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( notify );
  1025. }
  1026. break;
  1027. case A2S_INFO:
  1028. // Handle A2S_INFO ourselves, we are concealing SteamID of the game server and report players differently
  1029. if ( g_bSteamMasterHeartbeatsEnabled )
  1030. {
  1031. extern ConVar host_info_show;
  1032. switch ( host_info_show.GetInt() )
  1033. {
  1034. case 2:
  1035. goto steam3server_oob_packet_handler;
  1036. case 1:
  1037. {
  1038. // Validate challenge
  1039. char nugget[ 64 ];
  1040. if ( !msg.ReadString( nugget, sizeof( nugget ) ) )
  1041. break;
  1042. if ( !ValidInfoChallenge( packet->from, nugget ) )
  1043. break;
  1044. // Respond
  1045. extern AppId_t g_unSteamAppID;
  1046. AppId_t appIdResponse = g_unSteamAppID ? g_unSteamAppID : 730;
  1047. CUtlBuffer buf;
  1048. buf.EnsureCapacity( MAX_ROUTABLE_PAYLOAD );
  1049. buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) );
  1050. buf.PutUnsignedChar( S2A_INFO_SRC );
  1051. buf.PutUnsignedChar( 17 ); // Hardcoded protocol version number
  1052. extern ConVar host_name_store;
  1053. buf.PutString( host_name_store.GetBool() ? GetName() : "Counter-Strike: Global Offensive" );
  1054. buf.PutString( GetMapName() );
  1055. buf.PutString( "csgo" );
  1056. buf.PutString( "Counter-Strike: Global Offensive" );
  1057. // The next field is a 16-bit version of the AppID. If our AppID < 65536,
  1058. // then let's go ahead and put in in there, to maximize compatibility
  1059. // with old clients who might be only using this field but not the new one.
  1060. // However, if our AppID won't fit, there's no way we can be compatible,
  1061. // anyway, so just put in a zero, which is better than a bogus AppID.
  1062. uint16 usAppIdShort = ( uint16 ) appIdResponse;
  1063. buf.PutShort( LittleWord( usAppIdShort ) );
  1064. // player info
  1065. buf.PutUnsignedChar( GetNumPlayers() );
  1066. buf.PutUnsignedChar( GetMaxHumanPlayers() );
  1067. buf.PutUnsignedChar( MAX( 0, GetNumFakeClients() - GetNumProxies() ) );
  1068. // NOTE: This key's meaning is changed in the new version. Since we send gameport and specport,
  1069. // it knows whether we're running SourceTV or not. Then it only needs to know if we're a dedicated or listen server.
  1070. if ( IsDedicated() )
  1071. buf.PutUnsignedChar( 'd' ); // d = dedicated server
  1072. else
  1073. buf.PutUnsignedChar( 'l' ); // l = listen server
  1074. #if defined(_WIN32)
  1075. buf.PutUnsignedChar( 'w' );
  1076. #elif defined(OSX)
  1077. buf.PutUnsignedChar( 'm' );
  1078. #else // LINUX?
  1079. buf.PutUnsignedChar( 'l' );
  1080. #endif
  1081. // Password?
  1082. buf.PutUnsignedChar( GetPassword() ? 1 : 0 );
  1083. buf.PutUnsignedChar( Steam3Server().BSecure() ? 1 : 0 );
  1084. buf.PutString( GetHostVersionString() );
  1085. //
  1086. // NEW DATA.
  1087. //
  1088. // Write a byte with some flags that describe what is to follow.
  1089. byte nNewFlags = 0;
  1090. nNewFlags |= S2A_EXTRA_DATA_HAS_GAME_PORT;
  1091. // if ( GetSteamID().IsValid() )
  1092. // nNewFlags |= S2A_EXTRA_DATA_HAS_STEAMID;
  1093. extern bool CanShowHostTvStatus();
  1094. CHLTVServer *hltv = NULL;
  1095. for ( CActiveHltvServerIterator it; it; it.Next() )
  1096. {
  1097. if ( CanShowHostTvStatus() )
  1098. {
  1099. hltv = it;
  1100. // pass spectator data (port and name)
  1101. nNewFlags |= S2A_EXTRA_DATA_HAS_SPECTATOR_DATA;
  1102. }
  1103. break;
  1104. }
  1105. if ( m_GameType.String()[ 0 ] != '\0' )
  1106. nNewFlags |= S2A_EXTRA_DATA_HAS_GAMETAG_DATA;
  1107. nNewFlags |= S2A_EXTRA_DATA_GAMEID;
  1108. buf.PutUnsignedChar( nNewFlags );
  1109. // Write the rest of the data.
  1110. if ( nNewFlags & S2A_EXTRA_DATA_HAS_GAME_PORT )
  1111. {
  1112. buf.PutShort( LittleWord( GetUDPPort() ) );
  1113. }
  1114. if ( nNewFlags & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA )
  1115. {
  1116. buf.PutShort( LittleWord( hltv->GetUDPPort() ) );
  1117. buf.PutString( host_name_store.GetBool() ? GetName() : "Counter-Strike: Global Offensive" );
  1118. }
  1119. if ( nNewFlags & S2A_EXTRA_DATA_HAS_GAMETAG_DATA )
  1120. {
  1121. buf.PutString( m_GameType.String() );
  1122. }
  1123. if ( nNewFlags & S2A_EXTRA_DATA_GAMEID )
  1124. {
  1125. // !FIXME! Is there a reason we aren't using the other half
  1126. // of this field? Shouldn't we put the game mod ID in there, too?
  1127. // We have the game dir.
  1128. buf.PutInt64( LittleQWord( CGameID( appIdResponse ).ToUint64() ) );
  1129. }
  1130. NET_SendPacket( NULL, m_Socket, packet->from, ( unsigned char * ) buf.Base(), buf.TellPut() );
  1131. }
  1132. break;
  1133. }
  1134. }
  1135. break;
  1136. case A2S_PLAYER:
  1137. // Handle A2S_PLAYER ourselves, we are concealing SteamID of the game server and report players differently
  1138. if ( g_bSteamMasterHeartbeatsEnabled )
  1139. {
  1140. extern ConVar host_players_show;
  1141. switch ( host_players_show.GetInt() )
  1142. {
  1143. case 2:
  1144. goto steam3server_oob_packet_handler;
  1145. case 1:
  1146. {
  1147. CUtlBuffer buf;
  1148. buf.EnsureCapacity( MAX_ROUTABLE_PAYLOAD );
  1149. buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) );
  1150. buf.PutUnsignedChar( S2A_PLAYER );
  1151. buf.PutUnsignedChar( 1 );
  1152. buf.PutUnsignedChar( 0 );
  1153. buf.PutString( "Max Players" );
  1154. buf.PutInt( GetMaxHumanPlayers() );
  1155. if ( m_fStartTime == 0 ) // record when we started
  1156. {
  1157. m_fStartTime = Sys_FloatTime();
  1158. }
  1159. float flTime = Sys_FloatTime() - m_fStartTime;
  1160. float flLittleTime;
  1161. LittleFloat( &flLittleTime, &flTime );
  1162. buf.PutFloat( flLittleTime );
  1163. NET_SendPacket( NULL, m_Socket, packet->from, ( unsigned char * ) buf.Base(), buf.TellPut() );
  1164. }
  1165. break;
  1166. }
  1167. }
  1168. break;
  1169. case A2S_RULES:
  1170. if ( g_bSteamMasterHeartbeatsEnabled )
  1171. {
  1172. extern ConVar host_rules_show;
  1173. if ( host_rules_show.GetBool() )
  1174. goto steam3server_oob_packet_handler;
  1175. }
  1176. break;
  1177. default:
  1178. {
  1179. static bool s_bEnableLegacyPackets = !!CommandLine()->FindParm( "-enablelegacypackets" );
  1180. if ( !s_bEnableLegacyPackets )
  1181. break;
  1182. steam3server_oob_packet_handler:
  1183. // We don't understand it, let the master server updater at it.
  1184. if ( packet->from.IsType< netadr_t >() && Steam3Server().SteamGameServer() && Steam3Server().IsMasterServerUpdaterSharingGameSocket() )
  1185. {
  1186. const netadr_t &adr = packet->from.AsType< netadr_t>();
  1187. Steam3Server().SteamGameServer()->HandleIncomingPacket(
  1188. packet->message.GetBasePointer(),
  1189. packet->message.TotalBytesAvailable(),
  1190. adr.GetIPHostByteOrder(),
  1191. adr.GetPort()
  1192. );
  1193. // This is where it will usually want to respond to something immediately by sending some
  1194. // packets, so check for that immediately.
  1195. ForwardPacketsFromMasterServerUpdater();
  1196. }
  1197. }
  1198. break;
  1199. }
  1200. return true;
  1201. }
  1202. int CBaseServer::GetNumFakeClients() const
  1203. {
  1204. int count = 0;
  1205. for ( int i = 0; i < m_Clients.Count(); i++ )
  1206. {
  1207. if ( m_Clients[i]->IsConnected() &&
  1208. m_Clients[i]->IsFakeClient() )
  1209. {
  1210. count++;
  1211. }
  1212. }
  1213. return count;
  1214. }
  1215. /*
  1216. ==================
  1217. void SV_CountPlayers
  1218. Counts number of connections. Clients includes regular connections
  1219. ==================
  1220. */
  1221. int CBaseServer::GetNumClients( void ) const
  1222. {
  1223. int count = 0;
  1224. for (int i=0 ; i < m_Clients.Count() ; i++ )
  1225. {
  1226. if ( m_Clients[ i ]->IsConnected() )
  1227. {
  1228. count++;
  1229. }
  1230. }
  1231. return count;
  1232. }
  1233. /*
  1234. ==================
  1235. void SV_CountPlayers
  1236. Counts number of HLTV and Replay connections. Clients includes regular connections
  1237. ==================
  1238. */
  1239. int CBaseServer::GetNumProxies( void ) const
  1240. {
  1241. int count = 0;
  1242. for (int i=0 ; i < m_Clients.Count() ; i++ )
  1243. {
  1244. if ( m_Clients[ i ]->IsConnected() && (m_Clients[ i ]->IsHLTV()
  1245. #if defined( REPLAY_ENABLED )
  1246. || m_Clients[ i ]->IsReplay()
  1247. #endif
  1248. ) )
  1249. {
  1250. count++;
  1251. }
  1252. }
  1253. return count;
  1254. }
  1255. int CBaseServer::GetNumPlayers()
  1256. {
  1257. int count = 0;
  1258. if ( !GetUserInfoTable())
  1259. {
  1260. return 0;
  1261. }
  1262. const int maxPlayers = GetUserInfoTable()->GetNumStrings();
  1263. for ( int i=0; i < maxPlayers; i++ )
  1264. {
  1265. const player_info_t *pi = (const player_info_t *) m_pUserInfoTable->GetStringUserData( i, NULL );
  1266. // WARNING: using raw bytes access to player info instead of GetPlayerInfo to save server CPU when
  1267. // responding to server info queries. Structure not byteswapped, must use GetPlayerInfo.
  1268. if ( !pi )
  1269. continue;
  1270. if ( pi->fakeplayer )
  1271. continue; // don't count bots
  1272. count++;
  1273. }
  1274. return count;
  1275. }
  1276. bool CBaseServer::GetPlayerInfo( int nClientIndex, player_info_t *pinfo )
  1277. {
  1278. if ( !pinfo || !GetUserInfoTable() )
  1279. return false;
  1280. if ( nClientIndex < 0 || nClientIndex >= GetUserInfoTable()->GetNumStrings() )
  1281. {
  1282. Q_memset( pinfo, 0, sizeof( player_info_t ) );
  1283. return false;
  1284. }
  1285. player_info_t *pi = (player_info_t*) GetUserInfoTable()->GetStringUserData( nClientIndex, NULL );
  1286. if ( !pi )
  1287. {
  1288. Q_memset( pinfo, 0, sizeof( player_info_t ) );
  1289. return false;
  1290. }
  1291. Q_memcpy( pinfo, pi, sizeof( player_info_t ) );
  1292. // Fixup from network order (big endian)
  1293. CByteswap byteswap;
  1294. byteswap.SetTargetBigEndian( true );
  1295. byteswap.SwapFieldsToTargetEndian( pinfo );
  1296. return true;
  1297. }
  1298. void CBaseServer::UserInfoChanged( int nClientIndex )
  1299. {
  1300. if ( !m_pUserInfoTable )
  1301. return;
  1302. player_info_t pi;
  1303. bool oldlock = networkStringTableContainerServer->Lock( false );
  1304. if ( m_Clients[ nClientIndex ]->FillUserInfo( pi ) )
  1305. {
  1306. // Fixup to network order (big endian)
  1307. CByteswap byteswap;
  1308. byteswap.SetTargetBigEndian( true );
  1309. byteswap.SwapFieldsToTargetEndian( &pi );
  1310. // update user info settings
  1311. m_pUserInfoTable->SetStringUserData( nClientIndex, sizeof(pi), &pi );
  1312. }
  1313. else
  1314. {
  1315. // delete user data settings
  1316. m_pUserInfoTable->SetStringUserData( nClientIndex, 0, NULL );
  1317. }
  1318. networkStringTableContainerServer->Lock( oldlock );
  1319. }
  1320. void CBaseServer::FillServerInfo(CSVCMsg_ServerInfo &serverinfo)
  1321. {
  1322. char gamedir[MAX_OSPATH];
  1323. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  1324. serverinfo.set_protocol( GetHostVersion() );
  1325. serverinfo.set_server_count( GetSpawnCount() );
  1326. serverinfo.set_map_crc( worldmapCRC );
  1327. serverinfo.set_client_crc( clientDllCRC );
  1328. serverinfo.set_string_table_crc( CRC32_ConvertToUnsignedLong( &stringTableCRC ) );
  1329. serverinfo.set_max_clients( GetMaxClients() );
  1330. serverinfo.set_max_classes( serverclasses );
  1331. serverinfo.set_is_dedicated( IsDedicated() );
  1332. #ifdef _WIN32
  1333. serverinfo.set_c_os( 'W' );
  1334. #else
  1335. serverinfo.set_c_os( 'L' );
  1336. #endif
  1337. // HACK to signal that the server is "new"
  1338. serverinfo.set_c_os( tolower( serverinfo.c_os() ) );
  1339. serverinfo.set_tick_interval( GetTickInterval() );
  1340. serverinfo.set_game_dir( gamedir );
  1341. serverinfo.set_map_name( GetMapName() );
  1342. serverinfo.set_map_group_name( GetMapGroupName() );
  1343. serverinfo.set_sky_name( m_szSkyname );
  1344. extern ConVar host_name_store;
  1345. serverinfo.set_host_name( host_name_store.GetBool() ? GetName() : "Counter-Strike: Global Offensive" );
  1346. serverinfo.set_is_hltv( IsHLTV() );
  1347. serverinfo.set_is_redirecting_to_proxy_relay( false );
  1348. char szMapPath[MAX_PATH];
  1349. V_ComposeFileName( "maps", GetMapName(), szMapPath, sizeof(szMapPath) );
  1350. serverinfo.set_ugc_map_id( serverGameDLL->GetUGCMapFileID( szMapPath ) );
  1351. #if defined( REPLAY_ENABLED )
  1352. serverinfo.set_is_replay( IsReplay() );
  1353. #endif
  1354. // Don't expose server public IP in the server info, the client is already connected, so there's no reason to store it either
  1355. // if( Steam3Server().SteamGameServer() == NULL )
  1356. // serverinfo.set_public_ip( NULL );
  1357. // else
  1358. // serverinfo.set_public_ip( Steam3Server().SteamGameServer()->GetPublicIP() );
  1359. }
  1360. /*
  1361. =================
  1362. SVC_GetChallenge
  1363. Returns a challenge number that can be used
  1364. in a subsequent client_connect command.
  1365. We do this to prevent denial of service attacks that
  1366. flood the server with invalid connection IPs. With a
  1367. challenge, they must give a valid IP address.
  1368. =================
  1369. */
  1370. void CBaseServer::ReplyChallenge( const ns_address &adr, bf_read &inmsg )
  1371. {
  1372. if ( !CanAcceptChallengesFrom( adr ) )
  1373. // Silently swallow the challenge request
  1374. return;
  1375. // Check if we are running with a special flag and not advertise to Steam
  1376. if ( !V_strcmp( g_pLaunchOptions->GetFirstSubKey()->GetNextKey()->GetNextKey()->GetString(), "server_is_unavailable" ) )
  1377. {
  1378. DevMsg( "Server running with server_is_unavailable, ignoring challenge from %s\n", ns_address_render( adr ).String() );
  1379. return;
  1380. }
  1381. char ALIGN4 buffer[512] ALIGN4_POST;
  1382. bf_write msg(buffer,sizeof(buffer));
  1383. char context[ 256 ] = { 0 };
  1384. inmsg.ReadString( context, sizeof( context ) );
  1385. // get a free challenge number
  1386. int challengeNr = GetChallengeNr( adr );
  1387. int authprotocol = GetChallengeType( adr );
  1388. msg.WriteLong( CONNECTIONLESS_HEADER );
  1389. msg.WriteByte( S2C_CHALLENGE );
  1390. msg.WriteLong( challengeNr );
  1391. msg.WriteLong( authprotocol );
  1392. bool bWroteInfo = false;
  1393. uint64 ullSteamIDGS = 0ull;
  1394. #if !defined( NO_STEAM )
  1395. ullSteamIDGS = Steam3Server().GetGSSteamID().ConvertToUint64();
  1396. if ( authprotocol == PROTOCOL_STEAM )
  1397. {
  1398. msg.WriteShort( 0 ); // steam2 encryption key not there anymore
  1399. msg.WriteLongLong( ullSteamIDGS );
  1400. msg.WriteByte( Steam3Server().BSecure() );
  1401. bWroteInfo = true;
  1402. }
  1403. #endif
  1404. if ( !bWroteInfo )
  1405. {
  1406. msg.WriteShort( 1 );
  1407. msg.WriteLongLong( ullSteamIDGS );
  1408. msg.WriteByte( 0 );
  1409. }
  1410. bool bReloadServerMap = false;
  1411. if ( StringHasPrefix( context, "connect" ) )
  1412. {
  1413. bool bIsLocalConnection = adr.IsLoopback() || adr.IsLocalhost();
  1414. // A local player never has to enter a PW
  1415. bool bWillRequirePassword = ( GetPassword() != NULL ) &&
  1416. !bIsLocalConnection;
  1417. // Validate the challenge if the challenge was passed as part of connect
  1418. // packet:
  1419. unsigned long nChallenge = 0;
  1420. char const *pszValidate = context + Q_strlen( "connect" );
  1421. if ( pszValidate[0] == '0' && pszValidate[1] == 'x' )
  1422. {
  1423. char chValidateChallenge[ 9 ] = {0};
  1424. Q_strncpy( chValidateChallenge, pszValidate + 2, sizeof( chValidateChallenge ) );
  1425. nChallenge = strtoul( chValidateChallenge, NULL, 16 );
  1426. }
  1427. bool bAllowDC = !serverGameDLL->IsValveDS(); // Official DS direct connect disabled
  1428. if ( bAllowDC )
  1429. bAllowDC = serverGameDLL->ShouldAllowDirectConnect(); // let the ongoing game overrule direct connect
  1430. if ( bAllowDC )
  1431. {
  1432. //
  1433. // Game server must be logged on with a persistent GSLT unless LAN case
  1434. //
  1435. static const bool s_bAllowLanWhitelist = !CommandLine()->FindParm( "-ignorelanwhitelist" );
  1436. bool bWhitelistedClient = bIsLocalConnection // localhost/loopback
  1437. || ( s_bAllowLanWhitelist && (
  1438. adr.IsReservedAdr() // LAN RFC 1918
  1439. || ( ( adr.GetAddressType() == NSAT_NETADR ) && ( adr.GetIP() == Steam3Server().GetPublicIP() ) ) // same public IP (pinhole NAT or same host in DMZ)
  1440. ) );
  1441. bool bPersistentGameServerAccount = !sv_lan.GetBool() && !Steam3Server().BLanOnly()
  1442. && Steam3Server().GetGSSteamID().IsValid() && Steam3Server().GetGSSteamID().BPersistentGameServerAccount();
  1443. if ( IsDedicated() && !bWhitelistedClient && !bPersistentGameServerAccount )
  1444. bAllowDC = false; // just fail direct connect
  1445. }
  1446. if ( IsExclusiveToLobbyConnections() && !GetReservationCookie() )
  1447. {
  1448. // Server is in the state when it requires lobby connection, but
  1449. // is unreserved.
  1450. // For untrusted clients: we need to tell them to retry with
  1451. // a valid challenge
  1452. if ( nChallenge != ( unsigned long ) challengeNr )
  1453. {
  1454. msg.WriteString( "connect-retry" );
  1455. }
  1456. // For trusted clients: give them a grace period to establish
  1457. // the required lobby and submit a reservation request
  1458. else
  1459. {
  1460. if ( !bAllowDC )
  1461. {
  1462. if ( serverGameDLL->IsValveDS() )
  1463. {
  1464. // Do not allow direct-connect to Valve dedicated servers
  1465. msg.WriteString( "connect-matchmaking-only" );
  1466. DevMsg( "Cannot direct-connect to Valve CS:GO Server\n" );
  1467. }
  1468. else
  1469. {
  1470. // Do not allow direct-connect to this community server
  1471. msg.WriteString( "connect-lan-only" );
  1472. DevMsg( "Cannot direct-connect to community CS:GO Server, visit http://steamcommunity.com/dev/managegameservers\n" );
  1473. static bool s_bPrintedInfo = false;
  1474. if ( sv.IsDedicated() && !s_bPrintedInfo )
  1475. {
  1476. s_bPrintedInfo = true;
  1477. Warning( "****************************************************\n" );
  1478. Warning( "* Client connections are restricted to LAN only. *\n" );
  1479. Warning( "* Double-check your sv_setsteamaccount setting. *\n" );
  1480. Warning( "* To create a game server account go to *\n" );
  1481. Warning( "* http://steamcommunity.com/dev/managegameservers *\n" );
  1482. Warning( "****************************************************\n" );
  1483. }
  1484. }
  1485. }
  1486. else
  1487. {
  1488. #if ENGINE_CONNECT_VIA_MMS
  1489. msg.WriteString( "connect-granted" );
  1490. m_flTimeReservationGraceStarted = net_time;
  1491. m_adrReservationGraceStarted = adr;
  1492. DevMsg( "Server requires lobby reservation and is unreserved: granting reservation grace period to %s\n", ns_address_render( adr ).String() );
  1493. #else
  1494. if ( m_pnReservationCookieSession && *m_pnReservationCookieSession )
  1495. {
  1496. // Reserve in place
  1497. m_flReservationExpiryTime = net_time + sv_mmqueue_reservation_timeout.GetFloat();
  1498. SetReservationCookie( *m_pnReservationCookieSession, "[R] Connect from %s", ns_address_render( adr ).String() );
  1499. bReloadServerMap = true;
  1500. // Reply with whatever context was requested
  1501. msg.WriteString( context );
  1502. }
  1503. else
  1504. {
  1505. DevMsg( "Server is not connected to matchmaking backend, ignoring challenge from %s\n", ns_address_render( adr ).String() );
  1506. return;
  1507. }
  1508. #endif
  1509. }
  1510. }
  1511. }
  1512. else
  1513. {
  1514. // Otherwise just reply with whatever context was requested
  1515. msg.WriteString( context );
  1516. }
  1517. char const *pszLobbyType = "";
  1518. if ( IsExclusiveToLobbyConnections() )
  1519. {
  1520. pszLobbyType = "public";
  1521. if ( sv_steamgroup_exclusive.GetBool() || GetPassword() )
  1522. {
  1523. pszLobbyType = "friends";
  1524. }
  1525. }
  1526. msg.WriteLong( GetHostVersion() );
  1527. msg.WriteString( pszLobbyType );
  1528. msg.WriteByte( bWillRequirePassword ? 1 : 0 );
  1529. #if ENGINE_CONNECT_VIA_MMS
  1530. if ( !bAllowDC )
  1531. {
  1532. msg.WriteLongLong( (uint64)-1 );
  1533. msg.WriteByte( 0 );
  1534. }
  1535. else
  1536. {
  1537. if ( !IsHLTV() )
  1538. {
  1539. msg.WriteLongLong( sv.GetReservationCookie() );
  1540. }
  1541. else
  1542. {
  1543. msg.WriteLongLong( 0 );
  1544. }
  1545. static ConVar *dcFriendsReqd = cvar->FindVar( "sv_dc_friends_reqd" );
  1546. if ( !IsHLTV() && dcFriendsReqd )
  1547. {
  1548. msg.WriteByte( dcFriendsReqd->GetInt() );
  1549. }
  1550. else
  1551. {
  1552. msg.WriteByte( 0 );
  1553. }
  1554. }
  1555. #else
  1556. msg.WriteLongLong( ( uint64 ) -1 );
  1557. msg.WriteByte( 0 );
  1558. #endif
  1559. msg.WriteByte( serverGameDLL->IsValveDS() ? 1 : 0 );
  1560. //
  1561. // Server will send the certificate and its signature down to the client
  1562. //
  1563. const byte *pbNetEncryptPublic = NULL;
  1564. int cbNetEncryptPublic = 0;
  1565. const byte *pbNetEncryptSignature = NULL;
  1566. int cbNetEncryptSignature = 0;
  1567. bool bServerRequiresEncryptedChannels = ( ( this == &sv ) &&
  1568. NET_CryptGetNetworkCertificate( k_ENetworkCertificate_PublicKey, &pbNetEncryptPublic, &cbNetEncryptPublic ) &&
  1569. NET_CryptGetNetworkCertificate( k_ENetworkCertificate_Signature, &pbNetEncryptSignature, &cbNetEncryptSignature ) );
  1570. msg.WriteByte( bServerRequiresEncryptedChannels ? 1 : 0 );
  1571. if ( bServerRequiresEncryptedChannels )
  1572. {
  1573. msg.WriteLong( cbNetEncryptPublic );
  1574. msg.WriteBytes( pbNetEncryptPublic, cbNetEncryptPublic );
  1575. msg.WriteLong( cbNetEncryptSignature );
  1576. msg.WriteBytes( pbNetEncryptSignature, cbNetEncryptSignature );
  1577. }
  1578. }
  1579. else
  1580. {
  1581. msg.WriteString( context );
  1582. }
  1583. NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
  1584. //
  1585. // Force the map to be reloaded upon first connection
  1586. //
  1587. if ( bReloadServerMap && IsDedicated() )
  1588. {
  1589. Cbuf_AddText( CBUF_SERVER, CFmtStr( "nextlevel %s\n", GetMapName() ) ); // gamerules will clean it up when they construct for the next map
  1590. Cbuf_AddText( CBUF_SERVER, CFmtStr( "map %s reserved\n", GetMapName() ) );
  1591. Cbuf_Execute();
  1592. }
  1593. }
  1594. /*
  1595. =================
  1596. ReplyServerChallenge
  1597. Returns a challenge number that can be used
  1598. in a subsequent server query commands.
  1599. We do this to prevent DDoS attacks via bandwidth
  1600. amplification.
  1601. =================
  1602. */
  1603. void CBaseServer::ReplyServerChallenge(const ns_address &adr)
  1604. {
  1605. char buffer[16];
  1606. bf_write msg(buffer,sizeof(buffer));
  1607. // get a free challenge number
  1608. int challengeNr = GetChallengeNr( adr );
  1609. msg.WriteLong( CONNECTIONLESS_HEADER );
  1610. msg.WriteByte( S2C_CHALLENGE );
  1611. msg.WriteLong( challengeNr );
  1612. NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
  1613. }
  1614. //-----------------------------------------------------------------------------
  1615. // Purpose: encrypts an 8-byte sequence
  1616. //-----------------------------------------------------------------------------
  1617. static inline void Decrypt8ByteSequence( IceKey& cipher, const unsigned char *cipherText, unsigned char *plainText )
  1618. {
  1619. cipher.decrypt( cipherText, plainText );
  1620. }
  1621. //-----------------------------------------------------------------------------
  1622. // Purpose:
  1623. //-----------------------------------------------------------------------------
  1624. static void DecryptBuffer( IceKey& cipher, unsigned char *bufData, uint bufferSize)
  1625. {
  1626. unsigned char *cipherText = bufData;
  1627. unsigned char *plainText = bufData;
  1628. uint bytesEncrypted = 0;
  1629. while (bytesEncrypted < bufferSize)
  1630. {
  1631. // encrypt 8 byte section
  1632. Decrypt8ByteSequence( cipher, cipherText, plainText );
  1633. bytesEncrypted += 8;
  1634. cipherText += 8;
  1635. plainText += 8;
  1636. }
  1637. }
  1638. //-----------------------------------------------------------------------------
  1639. // Purpose: Replies to a reservation request sent by the client
  1640. //-----------------------------------------------------------------------------
  1641. void CBaseServer::ReplyReservationRequest( const ns_address &adr, bf_read &msgIn )
  1642. {
  1643. if ( msgIn.ReadLong() != GetHostVersion() )
  1644. return;
  1645. byte decrypted[ 1024 ];
  1646. bool bAccepted = false;
  1647. static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
  1648. if ( s_pchTournamentServer )
  1649. {
  1650. DevMsg( "Reservation request from %s rejected: server running in tournament mode for '%s'\n",
  1651. ns_address_render( adr ).String(), s_pchTournamentServer );
  1652. }
  1653. else if ( IsReserved() )
  1654. {
  1655. // Special case -- server is reserved, but takes too long
  1656. // to switch the map, client is retrying meanwhile, we don't want
  1657. // to blow them off with a failure code!
  1658. if ( m_ReservationStatus.m_bActive && m_ReservationStatus.m_bSuccess && m_ReservationStatus.m_Remote.CompareAdr( adr ) )
  1659. {
  1660. DevMsg( "Ignoring follow-up reservation request from %s: server reservation transition in progress (for %d more seconds)\n",
  1661. ns_address_render( adr ).String(), (int) ( m_flReservationExpiryTime - net_time ) );
  1662. SendReservationStatus( kEReservationStatusPending );
  1663. return;
  1664. }
  1665. DevMsg( "Reservation request from %s rejected: server already reserved (for %d more seconds)\n",
  1666. ns_address_render( adr ).String(), (int) ( m_flReservationExpiryTime - net_time ) );
  1667. }
  1668. else if ( GetNumClients() - GetNumFakeClients() > 0 )
  1669. {
  1670. DevMsg( "Reservation request from %s rejected: server not empty\n",
  1671. ns_address_render( adr ).String() );
  1672. }
  1673. else if ( !V_strcmp( g_pLaunchOptions->GetFirstSubKey()->GetNextKey()->GetNextKey()->GetString(), "server_is_unavailable" ) )
  1674. {
  1675. DevMsg( "Reservation request from %s rejected: server is running with flag server_is_unavailable\n",
  1676. ns_address_render( adr ).String() );
  1677. }
  1678. else
  1679. {
  1680. bool bOkay = true;
  1681. if ( !IsX360() && !IsDedicatedForXbox() )
  1682. {
  1683. int nChallengeNr = GetChallengeNr( adr );
  1684. if ( !CanAcceptChallengesFrom( adr ) )
  1685. {
  1686. DevMsg( "Reservation request from address %s, but challenges exclusive for %s\n",
  1687. ns_address_render( adr ).String(), m_adrReservationGraceStarted.ToString() );
  1688. bOkay = false;
  1689. }
  1690. else if ( !nChallengeNr )
  1691. {
  1692. DevMsg( "Reservation request from unknown address %s\n", ns_address_render( adr ).String() );
  1693. bOkay = false;
  1694. }
  1695. else
  1696. {
  1697. int payloadSize = msgIn.ReadLong();
  1698. if ( payloadSize <= 0 || payloadSize > sizeof( decrypted ) || ( payloadSize % 8 ) )
  1699. {
  1700. DevMsg( "ReplyReservationRequest: Reservation request with bogus payload size from %s [%d bytes]\n", ns_address_render( adr ).String(), payloadSize );
  1701. bOkay = false;
  1702. }
  1703. else
  1704. {
  1705. IceKey cipher(1); /* medium encryption level */
  1706. unsigned char ucEncryptionKey[8] = { 0 };
  1707. *( int * )&ucEncryptionKey[ 0 ] = LittleDWord( nChallengeNr ^ 0x5ef8ce12 );
  1708. *( int * )&ucEncryptionKey[ 4 ] = LittleDWord( nChallengeNr ^ 0xaa98e42c );
  1709. cipher.set( ucEncryptionKey );
  1710. msgIn.ReadBytes( decrypted, payloadSize );
  1711. // Try and decrypt it
  1712. DecryptBuffer( cipher, decrypted, payloadSize );
  1713. // Rewind and use decrypted payload
  1714. msgIn.StartReading( decrypted, payloadSize );
  1715. unsigned int nMagic = msgIn.ReadLong();
  1716. if ( nMagic == 0xfeedbeef )
  1717. {
  1718. bOkay = true;
  1719. }
  1720. else
  1721. {
  1722. Msg( "ReplyReservationRequest: Reservation request with bogus payload data from %s [%d bytes]\n", ns_address_render( adr ).String(), payloadSize );
  1723. }
  1724. }
  1725. }
  1726. }
  1727. if ( bOkay )
  1728. {
  1729. //
  1730. // Parse the incoming message
  1731. //
  1732. uint64 nReservationCookie;
  1733. int nSettingsSize;
  1734. nReservationCookie = msgIn.ReadLongLong();
  1735. nSettingsSize = msgIn.ReadLong();
  1736. float flExpiryTime = sv_reservation_timeout.GetFloat();
  1737. DevMsg( "Reservation request from %s accepted: server reserved with reservation cookie 0x%llx for %.1f seconds\n",
  1738. ns_address_render( adr ).String(), nReservationCookie, flExpiryTime );
  1739. DevMsg( " settings size = %d\n", nSettingsSize );
  1740. //
  1741. // Establish reservation time
  1742. //
  1743. bAccepted = true;
  1744. // This time will be held until the first client connects, at which time the reservation will stay
  1745. // in effect until the last client leaves the server
  1746. m_flReservationExpiryTime = net_time + flExpiryTime;
  1747. SetReservationCookie( nReservationCookie, "ReplyReservationRequest" );
  1748. //
  1749. // Process game settings
  1750. //
  1751. m_numGameSlots = 0;
  1752. if ( nSettingsSize > 0 )
  1753. {
  1754. KeyValues *pKV = new KeyValues("");
  1755. KeyValues::AutoDelete autodelete_pKV( pKV );
  1756. byte tmp[MAX_OOB_KEYVALUES];
  1757. Assert( nSettingsSize <= sizeof( tmp ) );
  1758. if ( nSettingsSize <= sizeof( tmp ) )
  1759. {
  1760. // convert from binary to keyvalues
  1761. CUtlBuffer buf;
  1762. msgIn.ReadBytes( tmp, nSettingsSize );
  1763. buf.Put( tmp, nSettingsSize );
  1764. pKV->ReadAsBinary( buf );
  1765. if ( sv_reservation_tickrate_adjustment.GetInt() > 0 )
  1766. {
  1767. float fDesiredTickInterval = 1.0f / sv_reservation_tickrate_adjustment.GetInt();
  1768. // quantize tick intervals to the nearest N / 512 fraction
  1769. float fNumerator = floorf(fDesiredTickInterval * 512.0f + 0.5f);
  1770. host_state.interval_per_tick = fNumerator / 512.0f;
  1771. host_state.interval_per_tick = clamp( host_state.interval_per_tick, MINIMUM_TICK_INTERVAL, MAXIMUM_TICK_INTERVAL );
  1772. DevMsg( "Reservation tickrate adjustment to %.0f ticks %.6f ms\n", floorf( 1.0f/host_state.interval_per_tick ), host_state.interval_per_tick );
  1773. if ( host_state.interval_per_tick < MINIMUM_TICK_INTERVAL ||
  1774. host_state.interval_per_tick > MAXIMUM_TICK_INTERVAL )
  1775. {
  1776. Sys_Error( "GetTickInterval returned bogus tick interval (%f)[%f to %f is valid range]", host_state.interval_per_tick,
  1777. MINIMUM_TICK_INTERVAL, MAXIMUM_TICK_INTERVAL );
  1778. }
  1779. extern ConVar sv_maxupdaterate;
  1780. extern ConVar sv_maxcmdrate;
  1781. sv_maxupdaterate.SetValue(1.0f / host_state.interval_per_tick);
  1782. sv_maxcmdrate.SetValue(1.0f / host_state.interval_per_tick);
  1783. }
  1784. // let the game know the settings
  1785. serverGameDLL->ApplyGameSettings( pKV );
  1786. // adjust the game slots
  1787. m_numGameSlots = pKV->GetInt( "members/numSlots", 0 );
  1788. #ifdef PORTAL2 // HACK: PORTAL2 uses maxclients instead of GAMERULES
  1789. SetMaxClients( m_numGameSlots );
  1790. #endif
  1791. }
  1792. }
  1793. }
  1794. }
  1795. m_ReservationStatus.m_bActive = true;
  1796. m_ReservationStatus.m_bSuccess = bAccepted;
  1797. m_ReservationStatus.m_Remote = adr;
  1798. // Send the status right away
  1799. SendReservationStatus( bAccepted ? kEReservationStatusPending : kEReservationStatusRejected );
  1800. }
  1801. void CBaseServer::ReplyReservationCheckRequest( const ns_address &adr, bf_read &msgIn )
  1802. {
  1803. if ( msgIn.ReadLong() != GetHostVersion() )
  1804. return;
  1805. uint32 token = msgIn.ReadLong();
  1806. uint32 uiReservationStage = msgIn.ReadLong();
  1807. uint64 nReservationCookie= msgIn.ReadLongLong();
  1808. bool reservationMatch = ( nReservationCookie == GetReservationCookie() );
  1809. uint64 uiClientSteamID = msgIn.ReadLongLong();
  1810. uint8 uiAwaitingClients = 0x7F; // large positive int8 number
  1811. if ( uiReservationStage && reservationMatch && uiClientSteamID && sv_mmqueue_reservation.GetString()[0] == 'Q' )
  1812. {
  1813. DevMsg( "Reservation from client %u: %u\n", uint32( uiClientSteamID ), uiReservationStage );
  1814. // For queue reservation client ID must match
  1815. bool bThisClientShouldJoin = false, bThisClientJustConfirmed = false;
  1816. uint8 uiActualAwaitingClients = 0;
  1817. for ( int k = 0; k < m_arrReservationPlayers.Count(); ++ k )
  1818. {
  1819. QueueMatchPlayer_t &qmp = m_arrReservationPlayers[k];
  1820. if ( qmp.m_uiAccountID == CSteamID( uiClientSteamID ).GetAccountID() )
  1821. {
  1822. bThisClientShouldJoin = true;
  1823. qmp.m_adr = adr;
  1824. qmp.m_uiToken = token;
  1825. if ( qmp.m_uiReservationStage < uiReservationStage )
  1826. bThisClientJustConfirmed = true;
  1827. qmp.m_uiReservationStage = uiReservationStage;
  1828. }
  1829. if ( qmp.m_uiReservationStage < uiReservationStage )
  1830. uiActualAwaitingClients += 1;
  1831. }
  1832. // Reservation matches, report to this client how many more clients we are awaiting
  1833. if ( bThisClientShouldJoin )
  1834. {
  1835. uiAwaitingClients = uiActualAwaitingClients;
  1836. if ( bThisClientJustConfirmed && serverGameDLL )
  1837. {
  1838. // Report to the GC the new level of confirmations
  1839. CUtlVector< uint32 > arrConfirmedAccounts;
  1840. arrConfirmedAccounts.EnsureCapacity( m_arrReservationPlayers.Count() );
  1841. uint32 uiMinReservationLevel = 0xFFFFu;
  1842. for ( int jj = 0; jj < m_arrReservationPlayers.Count(); ++ jj )
  1843. {
  1844. if ( m_arrReservationPlayers[jj].m_uiReservationStage < uiMinReservationLevel )
  1845. uiMinReservationLevel = m_arrReservationPlayers[jj].m_uiReservationStage;
  1846. }
  1847. for ( int jj = 0; jj < m_arrReservationPlayers.Count(); ++ jj )
  1848. {
  1849. if ( ( m_arrReservationPlayers[jj].m_uiReservationStage >= 2 ) ||
  1850. ( m_arrReservationPlayers[jj].m_uiReservationStage > uiMinReservationLevel ) )
  1851. arrConfirmedAccounts.AddToTail( m_arrReservationPlayers[jj].m_uiAccountID );
  1852. }
  1853. DevMsg( "Match start status: %u/%u\n", uiMinReservationLevel, arrConfirmedAccounts.Count() );
  1854. serverGameDLL->ReportGCQueuedMatchStart( uiMinReservationLevel, arrConfirmedAccounts.Base(), arrConfirmedAccounts.Count() );
  1855. if ( !uiActualAwaitingClients )
  1856. {
  1857. // Extend the reservation a little bit more since the match is now confirmed to be starting
  1858. m_flReservationExpiryTime = net_time + sv_mmqueue_reservation_extended_timeout.GetFloat();
  1859. DevMsg( "Reservation time extended +%d sec\n", sv_mmqueue_reservation_extended_timeout.GetInt() );
  1860. }
  1861. }
  1862. }
  1863. }
  1864. if ( uiReservationStage && reservationMatch && uiClientSteamID && sv_mmqueue_reservation.GetString()[ 0 ] == 'G' )
  1865. {
  1866. DevMsg( "Reservation from in-progress client %u: %u\n", uint32( uiClientSteamID ), uiReservationStage );
  1867. // Reservation matches, report to this client how many more clients we are awaiting
  1868. uiAwaitingClients = 0;
  1869. // Extend the reservation a little bit more since the match is now confirmed to be starting
  1870. m_flReservationExpiryTime = net_time + sv_mmqueue_reservation_extended_timeout.GetFloat();
  1871. DevMsg( "Reservation time extended +%d sec\n", sv_mmqueue_reservation_extended_timeout.GetInt() );
  1872. }
  1873. //
  1874. // Send response to this client
  1875. //
  1876. char buffer[64];
  1877. bf_write msg(buffer,sizeof(buffer));
  1878. msg.WriteLong( CONNECTIONLESS_HEADER );
  1879. msg.WriteByte( S2A_RESERVE_CHECK_RESPONSE );
  1880. msg.WriteLong( GetHostVersion() );
  1881. msg.WriteLong( token );
  1882. msg.WriteLong( uiReservationStage );
  1883. msg.WriteByte( uiAwaitingClients );
  1884. NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
  1885. //
  1886. // Send response to all clients too when this packet is really important
  1887. //
  1888. if ( !uiAwaitingClients )
  1889. {
  1890. // Make sure we send this notification to all clients to force them to connect
  1891. // to our server as soon as everybody confirmed
  1892. for ( int jj = 0; jj < m_arrReservationPlayers.Count(); ++ jj )
  1893. {
  1894. char buffer[64];
  1895. bf_write msg(buffer,sizeof(buffer));
  1896. msg.WriteLong( CONNECTIONLESS_HEADER );
  1897. msg.WriteByte( S2A_RESERVE_CHECK_RESPONSE );
  1898. msg.WriteLong( GetHostVersion() );
  1899. msg.WriteLong( m_arrReservationPlayers[jj].m_uiToken );
  1900. msg.WriteLong( uiReservationStage );
  1901. msg.WriteByte( uiAwaitingClients );
  1902. NET_SendPacket( NULL, m_Socket, m_arrReservationPlayers[jj].m_adr, msg.GetData(), msg.GetNumBytesWritten() );
  1903. }
  1904. }
  1905. }
  1906. void CBaseServer::ClearReservationStatus()
  1907. {
  1908. m_ReservationStatus.m_bActive = false;
  1909. }
  1910. void CBaseServer::FlagForSteamIDReuseAfterShutdown()
  1911. {
  1912. m_flFlagForSteamIDReuseAfterShutdownTime = Plat_FloatTime();
  1913. }
  1914. void CBaseServer::SendReservationStatus( EReservationStatus_t kEReservationStatus )
  1915. {
  1916. if ( !m_ReservationStatus.m_bActive )
  1917. {
  1918. return;
  1919. }
  1920. char buffer[32];
  1921. bf_write msg(buffer,sizeof(buffer));
  1922. msg.WriteLong( CONNECTIONLESS_HEADER );
  1923. msg.WriteByte( S2A_RESERVE_RESPONSE );
  1924. msg.WriteLong( GetHostVersion() );
  1925. msg.WriteByte( kEReservationStatus );
  1926. msg.WriteOneBit( ( serverGameDLL && serverGameDLL->IsValveDS() ) ? 1 : 0 ); // whether we are running ValveDS
  1927. msg.WriteLong( m_numGameSlots ); // number of game slots actually set on the server
  1928. DevMsg( "Reservation response to %s: %u, server reserved for %d more seconds\n",
  1929. ns_address_render( m_ReservationStatus.m_Remote ).String(), kEReservationStatus, (int) ( m_flReservationExpiryTime - net_time ) );
  1930. NET_SendPacket( NULL, m_Socket, m_ReservationStatus.m_Remote, msg.GetData(), msg.GetNumBytesWritten() );
  1931. if ( kEReservationStatus != kEReservationStatusPending )
  1932. ClearReservationStatus();
  1933. }
  1934. const char *CBaseServer::GetName( void ) const
  1935. {
  1936. return host_name.GetString();
  1937. }
  1938. bool UseCDKeyAuth()
  1939. {
  1940. #ifdef NO_STEAM
  1941. return true;
  1942. #endif
  1943. // if we are physically on a 360 (360 listen server) or we are on a PC dedicated server for 360 clients, don't require Steam auth
  1944. if ( IsX360() || NET_IsDedicatedForXbox() )
  1945. return true;
  1946. // for single player games that don't use Steam features, don't require Steam auth
  1947. if ( !serverGameDLL->ShouldPreferSteamAuth() && Host_IsSinglePlayerGame() )
  1948. return true;
  1949. #ifndef DEDICATED
  1950. // for listen servers, if the Steam interface is not available (Steam not running), OK to not require Steam auth
  1951. if ( !sv.IsDedicated() && !Steam3Client().SteamUser() )
  1952. return true;
  1953. #endif
  1954. // require Steam auth
  1955. return false;
  1956. }
  1957. int CBaseServer::GetChallengeType( const ns_address &adr)
  1958. {
  1959. if ( UseCDKeyAuth() )
  1960. {
  1961. return PROTOCOL_HASHEDCDKEY;
  1962. }
  1963. else
  1964. {
  1965. return PROTOCOL_STEAM;
  1966. }
  1967. }
  1968. int CBaseServer::GetChallengeNr ( const ns_address &adr)
  1969. {
  1970. int oldest = 0;
  1971. float oldestTime = FLT_MAX;
  1972. // see if we already have a challenge for this ip
  1973. for (int i = 0 ; i < m_ServerQueryChallenges.Count() ; i++)
  1974. {
  1975. if ( adr.CompareAdr (m_ServerQueryChallenges[i].adr, true) )
  1976. {
  1977. // reuse challenge number, but update time
  1978. m_ServerQueryChallenges[i].time = net_time;
  1979. return m_ServerQueryChallenges[i].challenge;
  1980. }
  1981. if (m_ServerQueryChallenges[i].time < oldestTime)
  1982. {
  1983. // remember oldest challenge
  1984. oldestTime = m_ServerQueryChallenges[i].time;
  1985. oldest = i;
  1986. }
  1987. }
  1988. if ( m_ServerQueryChallenges.Count() > MAX_CHALLENGES )
  1989. {
  1990. m_ServerQueryChallenges.FastRemove( oldest );
  1991. }
  1992. int newEntry = m_ServerQueryChallenges.AddToTail();
  1993. // note the 0x0FFF of the top 16 bits, so that -1 will never be sent as a challenge
  1994. m_ServerQueryChallenges[newEntry].challenge = (RandomInt(0,0x0FFF) << 16) | RandomInt(0,0xFFFF);
  1995. m_ServerQueryChallenges[newEntry].adr = adr;
  1996. m_ServerQueryChallenges[newEntry].time = net_time;
  1997. return m_ServerQueryChallenges[newEntry].challenge;
  1998. }
  1999. void CBaseServer::GetNetStats( float &avgIn, float &avgOut )
  2000. {
  2001. avgIn = avgOut = 0.0f;
  2002. for (int i = 0; i < m_Clients.Count(); i++ )
  2003. {
  2004. CBaseClient *cl = m_Clients[ i ];
  2005. // Fake clients get killed in here.
  2006. if ( cl->IsFakeClient() )
  2007. continue;
  2008. if ( !cl->IsConnected() )
  2009. continue;
  2010. INetChannel *netchan = cl->GetNetChannel();
  2011. avgIn += netchan->GetAvgData(FLOW_INCOMING);
  2012. avgOut += netchan->GetAvgData(FLOW_OUTGOING);
  2013. }
  2014. }
  2015. void CBaseServer::CalculateCPUUsage( void )
  2016. {
  2017. if ( !sv_stats.GetBool() )
  2018. {
  2019. return;
  2020. }
  2021. if(m_fStartTime==0)
  2022. // record when we started
  2023. {
  2024. m_fStartTime=Sys_FloatTime();
  2025. }
  2026. if( Sys_FloatTime () > m_fLastCPUCheckTime+1)
  2027. // only do this every 1 second
  2028. {
  2029. #if defined ( _WIN32 )
  2030. static float lastAvg=0;
  2031. static __int64 lastTotalTime=0,lastNow=0;
  2032. HANDLE handle;
  2033. FILETIME creationTime, exitTime, kernelTime, userTime, nowTime;
  2034. __int64 totalTime,now;
  2035. handle = GetCurrentProcess ();
  2036. // get CPU time
  2037. GetProcessTimes (handle, &creationTime, &exitTime,
  2038. &kernelTime, &userTime);
  2039. GetSystemTimeAsFileTime(&nowTime);
  2040. if(lastNow==0)
  2041. {
  2042. memcpy(&lastNow,&creationTime,sizeof(__int64));;
  2043. }
  2044. memcpy(&totalTime,&userTime,sizeof(__int64));;
  2045. memcpy(&now,&kernelTime,sizeof(__int64));;
  2046. totalTime+=now;
  2047. memcpy(&now,&nowTime,sizeof(__int64));;
  2048. m_fCPUPercent = (double)(totalTime-lastTotalTime)/(double)(now-lastNow);
  2049. // now save this away for next time
  2050. if(Sys_FloatTime () > lastAvg+5)
  2051. // only do it every 5 seconds, so we keep a moving average
  2052. {
  2053. memcpy(&lastNow,&nowTime,sizeof(__int64));
  2054. memcpy(&lastTotalTime,&totalTime,sizeof(__int64));
  2055. lastAvg=m_fLastCPUCheckTime;
  2056. }
  2057. #elif defined ( _PS3 )
  2058. // FAKE
  2059. m_fCPUPercent = 0.1;
  2060. #elif defined ( LINUX )
  2061. // FAKE
  2062. m_fCPUPercent = 0.1;
  2063. #elif defined ( POSIX )
  2064. /*
  2065. // linux CPU % code here :)
  2066. static int32 lastrunticks,lastcputicks;
  2067. static float lastAvg=0;
  2068. struct sysinfo infos;
  2069. int32 dummy;
  2070. int length;
  2071. char statFile[PATH_MAX];
  2072. int32 now = time(NULL);
  2073. int32 ctime,stime,start_time;
  2074. FILE *pFile;
  2075. int32 runticks,cputicks;
  2076. snprintf(statFile,PATH_MAX,"/proc/%i/stat",getpid());
  2077. // we can't use FS_Open() cause its outside our dir
  2078. pFile = fopen(statFile, "r");
  2079. if ( pFile == NULL )
  2080. {
  2081. goto end;
  2082. }
  2083. sysinfo(&infos);
  2084. fscanf(pFile,
  2085. "%d %s %c %d %d %d %d %d %lu %lu \
  2086. %lu %lu %lu %ld %ld %ld %ld %ld %ld %lu \
  2087. %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu \
  2088. %lu %lu %lu %lu %lu %lu",
  2089. &dummy,statFile,&dummy,&dummy,&dummy,&dummy,
  2090. &dummy,&dummy,&dummy,&dummy, // end of first line
  2091. &dummy,&dummy,&dummy,&ctime,&stime,
  2092. &dummy,&dummy,&dummy,&dummy,&dummy, // end of second
  2093. &start_time,&dummy,&dummy,&dummy,&dummy,
  2094. &dummy,&dummy,&dummy,&dummy,&dummy, // end of third
  2095. &dummy,&dummy,&dummy,&dummy,&dummy,&dummy);
  2096. fclose(pFile);
  2097. runticks = infos.uptime*HZ -start_time; // time the process has been running
  2098. cputicks = stime+ctime;
  2099. if(lastcputicks==0)
  2100. {
  2101. lastcputicks=cputicks;
  2102. }
  2103. if(lastrunticks==0)
  2104. {
  2105. lastrunticks=runticks;
  2106. }
  2107. else
  2108. {
  2109. m_fCPUPercent = (float)(cputicks-lastcputicks)/(float)(runticks-lastrunticks);
  2110. }
  2111. */
  2112. //ConMsg("%f %li %li %li %li\n",cpuPercent,
  2113. // cputicks,(cputicks-lastcputicks),
  2114. // (runticks-lastrunticks),runticks);
  2115. static struct rusage s_lastUsage;
  2116. static float lastAvg = 0;
  2117. struct rusage currentUsage;
  2118. if ( getrusage( RUSAGE_SELF, &currentUsage ) == 0 )
  2119. {
  2120. double flTimeDiff = (double)( currentUsage.ru_utime.tv_sec - s_lastUsage.ru_utime.tv_sec ) + (double)(( currentUsage.ru_utime.tv_usec - s_lastUsage.ru_utime.tv_usec )/1000000);
  2121. m_fCPUPercent = flTimeDiff/(m_fLastCPUCheckTime - lastAvg);
  2122. // now save this away for next time
  2123. if( m_fLastCPUCheckTime > lastAvg+5)
  2124. {
  2125. s_lastUsage = currentUsage;
  2126. lastAvg = m_fLastCPUCheckTime;
  2127. }
  2128. }
  2129. // limit checking :)
  2130. if( m_fCPUPercent > 0.999 )
  2131. m_fCPUPercent = 0.999;
  2132. if( m_fCPUPercent < 0 )
  2133. m_fCPUPercent = 0;
  2134. end:
  2135. #else
  2136. #error
  2137. #endif
  2138. m_fLastCPUCheckTime=Sys_FloatTime();
  2139. }
  2140. }
  2141. //-----------------------------------------------------------------------------
  2142. // Purpose: Prepare for level transition, etc.
  2143. //-----------------------------------------------------------------------------
  2144. void CBaseServer::InactivateClients( void )
  2145. {
  2146. for (int i = 0; i < m_Clients.Count(); i++ )
  2147. {
  2148. CBaseClient *cl = m_Clients[ i ];
  2149. // Fake clients get killed in here (but split screen users don't)
  2150. if ( cl->IsFakeClient() && !cl->IsSplitScreenUser() && !cl->IsHLTV()
  2151. #if defined( REPLAY_ENABLED )
  2152. && !cl->IsReplay()
  2153. #endif
  2154. )
  2155. {
  2156. // If we don't do this, it'll have a bunch of extra steam IDs for unauthenticated users.
  2157. Steam3Server().NotifyClientDisconnect( cl );
  2158. cl->Clear();
  2159. continue;
  2160. }
  2161. else if ( !cl->IsConnected() )
  2162. {
  2163. continue;
  2164. }
  2165. cl->Inactivate();
  2166. }
  2167. }
  2168. void CBaseServer::ReconnectClients( void )
  2169. {
  2170. for (int i=0 ; i< m_Clients.Count() ; i++ )
  2171. {
  2172. CBaseClient *cl = m_Clients[i];
  2173. if ( cl->IsConnected() )
  2174. {
  2175. cl->SetSignonState( SIGNONSTATE_CONNECTED );
  2176. CNETMsg_SignonState_t signon( cl->GetSignonState(), -1 );
  2177. cl->FillSignOnFullServerInfo( signon );
  2178. cl->SendNetMsg( signon );
  2179. }
  2180. }
  2181. }
  2182. /*
  2183. ==================
  2184. SV_CheckTimeouts
  2185. If a packet has not been received from a client in sv_timeout.GetFloat()
  2186. seconds, drop the conneciton.
  2187. When a client is normally dropped, the CSVClient goes into a zombie state
  2188. for a few seconds to make sure any final reliable message gets resent
  2189. if necessary
  2190. ==================
  2191. */
  2192. void CBaseServer::CheckTimeouts (void)
  2193. {
  2194. int i;
  2195. for ( i = 0 ; i < m_Clients.Count() ; ++i )
  2196. {
  2197. IClient *cl = m_Clients[ i ];
  2198. if ( cl->IsFakeClient() || !cl->IsConnected() )
  2199. continue;
  2200. INetChannel *netchan = cl->GetNetChannel();
  2201. if ( !netchan )
  2202. continue;
  2203. // Don't timeout in _DEBUG builds
  2204. #if !defined( _DEBUG )
  2205. if ( netchan->IsTimedOut() )
  2206. {
  2207. if ( this == &sv )
  2208. {
  2209. if ( CGameClient *pGameClient = dynamic_cast< CGameClient * >( cl ) )
  2210. {
  2211. serverGameDLL->OnEngineClientNetworkEvent( pGameClient->edict, pGameClient->m_SteamID.ConvertToUint64(), INetChannelInfo::k_ENetworkEventType_TimedOut, NULL );
  2212. }
  2213. }
  2214. cl->Disconnect( CFmtStr( "%s timed out", cl->GetClientName() ) );
  2215. }
  2216. else
  2217. #endif
  2218. // Handle steam socket disconnection
  2219. if ( netchan->IsRemoteDisconnected() )
  2220. {
  2221. cl->Disconnect( CFmtStr( "%s disconnected", cl->GetClientName() ) );
  2222. }
  2223. else if ( netchan->IsOverflowed() )
  2224. {
  2225. cl->Disconnect( CFmtStr( "%s overflowed reliable channel", cl->GetClientName() ) );
  2226. }
  2227. }
  2228. }
  2229. // ==================
  2230. // check if clients update thier user setting (convars) and call
  2231. // ==================
  2232. void CBaseServer::UpdateUserSettings(void)
  2233. {
  2234. for (int i=0 ; i< m_Clients.Count() ; i++ )
  2235. {
  2236. CBaseClient *cl = m_Clients[ i ];
  2237. if ( cl->m_bConVarsChanged )
  2238. {
  2239. cl->UpdateUserSettings();
  2240. }
  2241. }
  2242. }
  2243. // ==================
  2244. // check if clients need the serverinfo packet sent
  2245. // ==================
  2246. void CBaseServer::SendPendingServerInfo()
  2247. {
  2248. for (int i=0 ; i< m_Clients.Count() ; i++ )
  2249. {
  2250. CBaseClient *cl = m_Clients[ i ];
  2251. if ( cl->m_bSendServerInfo )
  2252. {
  2253. cl->SendServerInfo();
  2254. }
  2255. }
  2256. }
  2257. void CBaseServer::OnSteamServerLogonSuccess( uint32 externalIP )
  2258. {
  2259. for (int i=0 ; i< m_Clients.Count() ; i++ )
  2260. {
  2261. CBaseClient *cl = m_Clients[ i ];
  2262. cl->OnSteamServerLogonSuccess( externalIP );
  2263. }
  2264. }
  2265. /*
  2266. ================
  2267. SV_CheckProtocol
  2268. Make sure connecting client is using proper protocol
  2269. ================
  2270. */
  2271. bool CBaseServer::CheckProtocol( const ns_address &adr, int nProtocol )
  2272. {
  2273. if ( nProtocol != GetHostVersion() )
  2274. {
  2275. // Client is newer than server
  2276. if ( nProtocol > GetHostVersion() )
  2277. {
  2278. RejectConnection( adr, "This server is using an older protocol ( %i ) than your client ( %i ).\n",
  2279. GetHostVersion(), nProtocol );
  2280. }
  2281. else
  2282. // Server is newer than client
  2283. {
  2284. RejectConnection( adr, "This server is using a newer protocol ( %i ) than your client ( %i ).\n",
  2285. GetHostVersion(), nProtocol );
  2286. }
  2287. return false;
  2288. }
  2289. // Success
  2290. return true;
  2291. }
  2292. /*
  2293. ================
  2294. SV_CheckKeyInfo
  2295. Determine if client is outside appropriate address range
  2296. ================
  2297. */
  2298. bool CBaseServer::CheckChallengeType( CBaseClient * client, int nNewUserID, const ns_address &adr, int nAuthProtocol, const char *pchLogonCookie, int cbCookie )
  2299. {
  2300. // Check protocol ID
  2301. if ( ( nAuthProtocol <= 0 ) || ( nAuthProtocol > PROTOCOL_LASTVALID ) )
  2302. {
  2303. RejectConnection( adr, "Invalid connection.\n");
  2304. return false;
  2305. }
  2306. if ( ( nAuthProtocol == PROTOCOL_HASHEDCDKEY ) && (Q_strlen( pchLogonCookie ) <= 0 || Q_strlen(pchLogonCookie) != 32 ) )
  2307. {
  2308. RejectConnection( adr, "Invalid authentication certificate length.\n" );
  2309. return false;
  2310. }
  2311. if ( IsDedicatedForXbox() )
  2312. {
  2313. return true;
  2314. }
  2315. if ( nAuthProtocol == PROTOCOL_STEAM )
  2316. {
  2317. // Dev hack to allow 360/Steam PC cross platform play
  2318. // int ip0 = 207;
  2319. // int ip1 = 173;
  2320. // int ip2 = 179;
  2321. // int ip3Min = 230;
  2322. // int ip3Max = 245;
  2323. //
  2324. // if ( adr.ip[0] == ip0 &&
  2325. // adr.ip[1] == ip1 &&
  2326. // adr.ip[2] == ip2 &&
  2327. // adr.ip[3] >= ip3Min &&
  2328. // adr.ip[3] <= ip3Max )
  2329. // {
  2330. // return true;
  2331. // }
  2332. client->SetSteamID( CSteamID() ); // set an invalid SteamID
  2333. // Convert raw certificate back into data
  2334. if ( cbCookie <= 0 || cbCookie >= STEAM_KEYSIZE )
  2335. {
  2336. RejectConnection( adr, "STEAM certificate length error! %i/%i\n", cbCookie, STEAM_KEYSIZE );
  2337. return false;
  2338. }
  2339. ns_address checkAdr = adr;
  2340. if ( adr.IsLoopback() || adr.IsLocalhost() )
  2341. {
  2342. checkAdr.AsType<netadr_t>().SetIP( net_local_adr.GetIPHostByteOrder() );
  2343. }
  2344. if ( !Steam3Server().NotifyClientConnect( client, nNewUserID, checkAdr, pchLogonCookie, cbCookie )
  2345. && !Steam3Server().BLanOnly() ) // the userID isn't alloc'd yet so we need to fill it in manually
  2346. {
  2347. RejectConnection( adr, "STEAM validation rejected\n" );
  2348. return false;
  2349. }
  2350. }
  2351. else
  2352. {
  2353. if ( !Steam3Server().NotifyLocalClientConnect( client ) ) // the userID isn't alloc'd yet so we need to fill it in manually
  2354. {
  2355. RejectConnection( adr, "GSCreateLocalUser failed\n" );
  2356. return false;
  2357. }
  2358. }
  2359. return true;
  2360. }
  2361. bool CBaseServer::CheckIPRestrictions( const ns_address &adr, int nAuthProtocol )
  2362. {
  2363. // Determine if client is outside appropriate address range
  2364. if ( adr.IsLoopback() )
  2365. return true;
  2366. // X360TBD: network
  2367. if ( IsX360() )
  2368. return true;
  2369. // allow other users if they're on the same ip range
  2370. if ( Steam3Server().BLanOnly() )
  2371. {
  2372. if ( !adr.IsType<netadr_t>() )
  2373. return false;
  2374. // allow connection, if client is in the same subnet
  2375. if ( adr.AsType<netadr_t>().CompareClassBAdr( net_local_adr ) )
  2376. return true;
  2377. // allow connection, if client has a private IP
  2378. if ( adr.AsType<netadr_t>().IsReservedAdr() )
  2379. return true;
  2380. // reject connection
  2381. return false;
  2382. }
  2383. return true;
  2384. }
  2385. void CBaseServer::SetMasterServerRulesDirty()
  2386. {
  2387. m_bMasterServerRulesDirty = true;
  2388. }
  2389. bool CBaseServer::CheckPassword( const ns_address &adr, const char *password, const char *name )
  2390. {
  2391. // If a server is reserved, then we ignore PW checks
  2392. // if ( GetReservationCookie() != 0ull )
  2393. // return true;
  2394. const char *server_password = GetPassword();
  2395. if ( !server_password )
  2396. return true; // no password set
  2397. if ( adr.IsLocalhost() || adr.IsLoopback() )
  2398. {
  2399. return true; // local client can always connect
  2400. }
  2401. int iServerPassLen = Q_strlen(server_password);
  2402. if ( iServerPassLen != Q_strlen(password) )
  2403. {
  2404. return false; // different length cannot be equal
  2405. }
  2406. if ( Q_strncmp( password, server_password, iServerPassLen ) == 0)
  2407. {
  2408. return true; // passwords are equal
  2409. }
  2410. return false; // all test failed
  2411. }
  2412. float CBaseServer::GetTime() const
  2413. {
  2414. return m_nTickCount * m_flTickInterval;
  2415. }
  2416. float CBaseServer::GetFinalTickTime() const
  2417. {
  2418. return (m_nTickCount + (host_frameticks - host_currentframetick)) * m_flTickInterval;
  2419. }
  2420. void CBaseServer::DisconnectClient(IClient *client, const char *reason )
  2421. {
  2422. client->Disconnect( reason );
  2423. }
  2424. void CBaseServer::ClearBaselineHandles( void )
  2425. {
  2426. FOR_EACH_MAP_FAST( m_BaselineHandles, i )
  2427. {
  2428. g_pSerializedEntities->ReleaseSerializedEntity( m_BaselineHandles[ i ] );
  2429. }
  2430. m_BaselineHandles.Purge();
  2431. }
  2432. void CBaseServer::Clear( void )
  2433. {
  2434. if ( m_StringTables )
  2435. {
  2436. m_StringTables->RemoveAllTables();
  2437. m_StringTables = NULL;
  2438. }
  2439. m_pInstanceBaselineTable = NULL;
  2440. m_pLightStyleTable = NULL;
  2441. m_pUserInfoTable = NULL;
  2442. m_pServerStartupTable = NULL;
  2443. ClearBaselineHandles();
  2444. m_State = ss_dead;
  2445. m_nTickCount = 0;
  2446. Q_memset( m_szMapname, 0, sizeof( m_szMapname ) );
  2447. Q_memset( m_szBaseMapname, 0, sizeof( m_szBaseMapname ) );
  2448. Q_memset( m_szMapGroupName, 0, sizeof( m_szMapGroupName ) );
  2449. Q_memset( m_szSkyname, 0, sizeof( m_szSkyname ) );
  2450. clientDllCRC = 0;
  2451. worldmapCRC = 0;
  2452. stringTableCRC = 0;
  2453. MEM_ALLOC_CREDIT();
  2454. // Use a different limit on the signon buffer, so we can save some memory in SP (for xbox).
  2455. if ( IsMultiplayer() || IsDedicated() )
  2456. {
  2457. m_SignonBuffer.EnsureCapacity( NET_MAX_PAYLOAD );
  2458. }
  2459. else
  2460. {
  2461. m_SignonBuffer.EnsureCapacity( 16384 );
  2462. }
  2463. m_Signon.StartWriting( m_SignonBuffer.Base(), m_SignonBuffer.Count() );
  2464. m_Signon.SetDebugName( "m_Signon" );
  2465. serverclasses = 0;
  2466. serverclassbits = 0;
  2467. }
  2468. /*
  2469. ================
  2470. SV_RejectConnection
  2471. Rejects connection request and sends back a message
  2472. ================
  2473. */
  2474. void CBaseServer::RejectConnection( const ns_address &adr, const char *fmt, ... )
  2475. {
  2476. va_list argptr;
  2477. char text[1024];
  2478. va_start (argptr, fmt);
  2479. Q_vsnprintf ( text, sizeof( text ), fmt, argptr);
  2480. va_end (argptr);
  2481. NET_OutOfBandPrintf( m_Socket, adr, "%c%s", S2C_CONNREJECT, text );
  2482. Warning( "RejectConnection: %s - %s\n", ns_address_render( adr ).String(), text );
  2483. }
  2484. void CBaseServer::SetPaused( bool paused )
  2485. {
  2486. // pause request can be rejected, unpause needs to fall through
  2487. if ( m_State != ss_paused && !IsPausable() )
  2488. {
  2489. return;
  2490. }
  2491. if ( !IsActive() )
  2492. return;
  2493. if ( paused )
  2494. {
  2495. m_State = ss_paused;
  2496. }
  2497. else
  2498. {
  2499. m_State = ss_active;
  2500. }
  2501. CSVCMsg_SetPause_t setpause;
  2502. setpause.set_paused( paused );
  2503. BroadcastMessage( setpause );
  2504. }
  2505. void CBaseServer::SetTimescale( float flTimescale )
  2506. {
  2507. m_flTimescale = flTimescale;
  2508. }
  2509. //-----------------------------------------------------------------------------
  2510. // Purpose: General initialization of the server
  2511. //-----------------------------------------------------------------------------
  2512. void CBaseServer::Init( bool bIsDedicated )
  2513. {
  2514. m_nMaxclients = 0;
  2515. m_nSpawnCount = 0;
  2516. m_nUserid = 1;
  2517. m_bIsDedicated = bIsDedicated;
  2518. m_bIsDedicatedForXbox = bIsDedicated && ( CommandLine()->FindParm( "-xlsp" ) != 0 );
  2519. m_bIsDedicatedForPS3 = bIsDedicated && ( CommandLine()->FindParm( "-ps3ds" ) != 0 );
  2520. m_Socket = NS_SERVER;
  2521. m_Signon.SetDebugName( "m_Signon" );
  2522. if ( !g_pKVrulesConvars )
  2523. {
  2524. g_pKVrulesConvars = new KeyValues( "NotifyRulesCvars" );
  2525. if ( !g_pKVrulesConvars->LoadFromFile( g_pFullFileSystem, "gamerulescvars.txt", "MOD" ) )
  2526. {
  2527. Warning( "Failed to load gamerulescvars.txt, game rules cvars might not be reported to management tools.\n" );
  2528. }
  2529. }
  2530. g_pCVar->InstallGlobalChangeCallback( ServerNotifyVarChangeCallback );
  2531. SetMasterServerRulesDirty();
  2532. Clear();
  2533. }
  2534. INetworkStringTable *CBaseServer::GetInstanceBaselineTable( void )
  2535. {
  2536. if ( m_pInstanceBaselineTable == NULL )
  2537. {
  2538. m_pInstanceBaselineTable = m_StringTables->FindTable( INSTANCE_BASELINE_TABLENAME );
  2539. }
  2540. return m_pInstanceBaselineTable;
  2541. }
  2542. INetworkStringTable *CBaseServer::GetLightStyleTable( void )
  2543. {
  2544. if ( m_pLightStyleTable == NULL )
  2545. {
  2546. m_pLightStyleTable= m_StringTables->FindTable( LIGHT_STYLES_TABLENAME );
  2547. }
  2548. return m_pLightStyleTable;
  2549. }
  2550. INetworkStringTable *CBaseServer::GetUserInfoTable( void )
  2551. {
  2552. if ( m_pUserInfoTable == NULL )
  2553. {
  2554. if ( m_StringTables == NULL )
  2555. {
  2556. return NULL;
  2557. }
  2558. m_pUserInfoTable = m_StringTables->FindTable( USER_INFO_TABLENAME );
  2559. }
  2560. return m_pUserInfoTable;
  2561. }
  2562. bool CBaseServer::GetClassBaseline( ServerClass *pClass, SerializedEntityHandle_t *pHandle )
  2563. {
  2564. if ( sv_instancebaselines.GetInt() )
  2565. {
  2566. ErrorIfNot( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX,
  2567. ("SV_GetInstanceBaseline: missing instance baseline for class '%s'", pClass->m_pNetworkName)
  2568. );
  2569. AUTO_LOCK_FM( g_svInstanceBaselineMutex );
  2570. int nSlot = m_BaselineHandles.Find( pClass->m_InstanceBaselineIndex );
  2571. ErrorIfNot( nSlot != m_BaselineHandles.InvalidIndex(), ( "CBaseServer::GetClassBaseline: for %s failed\n", pClass->GetName() ) );
  2572. *pHandle = m_BaselineHandles[ nSlot ];
  2573. Assert( *pHandle != SERIALIZED_ENTITY_HANDLE_INVALID );
  2574. return *pHandle != SERIALIZED_ENTITY_HANDLE_INVALID;
  2575. }
  2576. *pHandle = SERIALIZED_ENTITY_HANDLE_INVALID;
  2577. return true;
  2578. }
  2579. bool CBaseServer::ShouldUpdateMasterServer()
  2580. {
  2581. // If the game server itself is ever running, then it's the one who gets to update the master server.
  2582. // (SourceTV will not update it in this case).
  2583. return true;
  2584. }
  2585. void CBaseServer::CheckMasterServerRequestRestart()
  2586. {
  2587. if ( !( CommandLine()->FindParm( "-fake_stale_server" ) ) )
  2588. {
  2589. if ( !Steam3Server().SteamGameServer() || !Steam3Server().SteamGameServer()->WasRestartRequested() )
  2590. return;
  2591. }
  2592. // Connection was rejected by the HLMaster (out of date version)
  2593. // hack, vgui console looks for this string;
  2594. Msg("%cMasterRequestRestart\n", 3);
  2595. #ifndef _WIN32
  2596. if (CommandLine()->FindParm(AUTO_RESTART))
  2597. {
  2598. Msg("Your server is out of date and will be shutdown during hibernation or changelevel, whichever comes first.\n");
  2599. Log_Msg( LOG_SERVER_LOG, "Your server is out of date and will be shutdown during hibernation or changelevel, whichever comes first.\n");
  2600. SetRestartOnLevelChange( true );
  2601. Cbuf_AddText(CBUF_SERVER, "sv_shutdown\n");
  2602. Cbuf_Execute();
  2603. }
  2604. #endif
  2605. #ifdef _WIN32
  2606. if (g_pFileSystem->IsSteam())
  2607. #else
  2608. else if ( 1 ) // under linux assume steam
  2609. #endif
  2610. {
  2611. Msg("Your server needs to be restarted in order to receive the latest update.\n");
  2612. Log_Msg( LOG_SERVER_LOG, "Your server needs to be restarted in order to receive the latest update.\n");
  2613. }
  2614. else
  2615. {
  2616. Msg("Your server is out of date. Please update and restart.\n");
  2617. }
  2618. if ( serverGameDLL && serverGameDLL->IsValveDS() && !CommandLine()->FindParm( "-noautoupdate" ) )
  2619. { // Valve DS always posts sv_shutdown when out of date
  2620. Cbuf_AddText( CBUF_SERVER, "sv_shutdown\n" );
  2621. Cbuf_Execute();
  2622. }
  2623. }
  2624. void CBaseServer::UpdateMasterServer()
  2625. {
  2626. if ( !ShouldUpdateMasterServer() )
  2627. return;
  2628. if ( !Steam3Server().SteamGameServer() )
  2629. return;
  2630. // Call this every frame
  2631. ForwardPacketsFromMasterServerUpdater();
  2632. // Only update every so often.
  2633. double flCurTime = Plat_FloatTime();
  2634. if ( flCurTime - m_flLastMasterServerUpdateTime < MASTER_SERVER_UPDATE_INTERVAL )
  2635. return;
  2636. m_flLastMasterServerUpdateTime = flCurTime;
  2637. // If we are not in legacy mode then make sure server tags still get updated every so often.
  2638. // In legacy mode this is done by heartbeat code
  2639. UpdateGameData();
  2640. CheckMasterServerRequestRestart();
  2641. if ( NET_IsDedicated() && sv_region.GetInt() == -1 )
  2642. {
  2643. sv_region.SetValue( 255 ); // HACK!HACK! undo me once we want to enforce regions
  2644. //Log_Printf( "You must set sv_region in your server.cfg or use +sv_region on the command line\n" );
  2645. //Con_Printf( "You must set sv_region in your server.cfg or use +sv_region on the command line\n" );
  2646. //Cbuf_AddText( "quit\n" );
  2647. //return;
  2648. }
  2649. static bool bUpdateMasterServers = !CommandLine()->FindParm( "-nomaster" );
  2650. if ( bUpdateMasterServers )
  2651. {
  2652. bool bActive = IsActive() && IsMultiplayer() && g_bEnableMasterServerUpdater && !IsSinglePlayerGame();
  2653. if ( ShouldHideFromMasterServer() )
  2654. bActive = false;
  2655. g_bSteamMasterHeartbeatsEnabled = bActive;
  2656. Steam3Server().SteamGameServer()->EnableHeartbeats( bActive );
  2657. if ( bActive )
  2658. {
  2659. UpdateMasterServerRules();
  2660. UpdateMasterServerPlayers();
  2661. Steam3Server().SendUpdatedServerDetails();
  2662. }
  2663. }
  2664. if ( serverGameDLL && Steam3Server().GetGSSteamID().IsValid() )
  2665. serverGameDLL->UpdateGCInformation();
  2666. }
  2667. void CBaseServer::UpdateMasterServerRules()
  2668. {
  2669. // Only do this if the rules vars are dirty.
  2670. if ( !m_bMasterServerRulesDirty )
  2671. return;
  2672. ISteamGameServer *pGameServer = Steam3Server().SteamGameServer();
  2673. if ( !pGameServer )
  2674. return;
  2675. pGameServer->ClearAllKeyValues();
  2676. // Need to respond with game directory, game name, and any server variables that have been set that
  2677. // affect rules. Also, probably need a hook into the .dll to respond with additional rule information.
  2678. ICvar::Iterator iter( g_pCVar );
  2679. for ( iter.SetFirst() ; iter.IsValid() ; iter.Next() )
  2680. {
  2681. ConCommandBase *var = iter.Get();
  2682. if ( !(var->IsFlagSet( FCVAR_NOTIFY ) ) )
  2683. continue;
  2684. ConVar *pConVar = dynamic_cast< ConVar* >( var );
  2685. if ( !pConVar )
  2686. continue;
  2687. if ( !g_pKVrulesConvars->GetBool( pConVar->GetName() ) )
  2688. continue;
  2689. SetMasterServerKeyValue( pGameServer, pConVar );
  2690. }
  2691. if ( Steam3Server().SteamGameServer() )
  2692. {
  2693. RecalculateTags();
  2694. }
  2695. // Ok.. it's all updated, only send incremental updates now until we decide they're all dirty.
  2696. m_bMasterServerRulesDirty = false;
  2697. }
  2698. void CBaseServer::ForwardPacketsFromMasterServerUpdater()
  2699. {
  2700. ISteamGameServer *p = Steam3Server().SteamGameServer();
  2701. if ( !p )
  2702. return;
  2703. while ( 1 )
  2704. {
  2705. uint32 netadrAddress;
  2706. uint16 netadrPort;
  2707. unsigned char packetData[16 * 1024];
  2708. int len = p->GetNextOutgoingPacket( packetData, sizeof( packetData ), &netadrAddress, &netadrPort );
  2709. if ( len <= 0 )
  2710. break;
  2711. // Send this packet for them..
  2712. netadr_t adr( netadrAddress, netadrPort );
  2713. NET_SendPacket( NULL, m_Socket, adr, packetData, len );
  2714. }
  2715. }
  2716. /*
  2717. =================
  2718. SV_ReadPackets
  2719. Read's packets from clients and executes messages as appropriate.
  2720. =================
  2721. */
  2722. #define PARENT_PROCESS_UPDATE_INTERVAL ( 10.0 ) // every 10 seconds
  2723. void UpdateParentProcess( void )
  2724. {
  2725. #ifdef _LINUX
  2726. static double s_flNextParentProcessUpdate = -1;
  2727. float flNow = Sys_FloatTime();
  2728. if ( flNow >= s_flNextParentProcessUpdate )
  2729. {
  2730. s_flNextParentProcessUpdate = flNow + PARENT_PROCESS_UPDATE_INTERVAL;
  2731. char sbuf[2048];
  2732. sprintf( sbuf, "status map=%s;players=%d", sv.GetMapName(), sv.GetNumClients() );
  2733. SendStringToParentProcess( sbuf );
  2734. }
  2735. #endif
  2736. }
  2737. void CBaseServer::RunFrame( void )
  2738. {
  2739. VPROF_BUDGET( "CBaseServer::RunFrame", VPROF_BUDGETGROUP_OTHER_NETWORKING );
  2740. NET_ProcessSocket( m_Socket, this );
  2741. CheckTimeouts(); // drop clients that timeed out
  2742. UpdateUserSettings(); // update client settings
  2743. SendPendingServerInfo(); // send outstanding signon packets after ALL user settings have been updated
  2744. CalculateCPUUsage(); // update CPU usage
  2745. UpdateMasterServer();
  2746. if ( IsChildProcess() )
  2747. {
  2748. UpdateParentProcess(); // sned status to the parent process occasionally
  2749. }
  2750. if ( m_bMasterServerRulesDirty )
  2751. {
  2752. m_bMasterServerRulesDirty = false;
  2753. RecalculateTags();
  2754. }
  2755. ProcessSplitScreenDisconnects();
  2756. }
  2757. void CBaseServer::ProcessVoice( void )
  2758. {
  2759. //This call will improve voice transmission with a slowed host_timescale. But will also send data to the clients too frequently depending on network throttling.
  2760. //It can cause prediction errors when not throttled. There is no throttling for listen server loopback connections. Thusly, listen server hosts have prediction errors.
  2761. //SendClientMessages( false );
  2762. }
  2763. CBaseClient * CBaseServer::GetFreeClient( const ns_address &adr )
  2764. {
  2765. CBaseClient *client = GetFreeClientInternal( adr );
  2766. return client;
  2767. }
  2768. //-----------------------------------------------------------------------------
  2769. // Purpose:
  2770. // Input : *adr -
  2771. // *pslot -
  2772. // **ppClient -
  2773. // Output : int
  2774. //-----------------------------------------------------------------------------
  2775. CBaseClient * CBaseServer::GetFreeClientInternal( const ns_address &adr )
  2776. {
  2777. CBaseClient *freeclient = NULL;
  2778. for ( int slot = 0 ; slot < m_Clients.Count() ; slot++ )
  2779. {
  2780. CBaseClient *client = m_Clients[slot];
  2781. if ( client->IsFakeClient() )
  2782. continue;
  2783. if ( client->IsConnected() )
  2784. {
  2785. if ( adr.CompareAdr ( client->m_NetChannel->GetRemoteAddress() ) )
  2786. {
  2787. ConMsg ( "%s:reconnect\n", ns_address_render( adr ).String() );
  2788. for ( int k = host_state.max_splitscreen_players; k -- > 1; )
  2789. {
  2790. // Processing split screen users in reverse order
  2791. CBaseClient *& pSsUser = client->m_SplitScreenUsers[ k ];
  2792. if ( !pSsUser )
  2793. continue;
  2794. RemoveClientFromGame( pSsUser );
  2795. // perform a silent netchannel shutdown, don't send disconnect msg
  2796. pSsUser->m_NetChannel->Shutdown( NULL );
  2797. pSsUser->m_NetChannel = NULL;
  2798. pSsUser->Clear();
  2799. // Mark the split screen slot as empty
  2800. pSsUser = NULL;
  2801. }
  2802. RemoveClientFromGame( client );
  2803. // perform a silent netchannel shutdown, don't send disconnect msg
  2804. client->m_NetChannel->Shutdown( NULL );
  2805. client->m_NetChannel = NULL;
  2806. client->Clear();
  2807. return client;
  2808. }
  2809. }
  2810. else
  2811. {
  2812. // use first found free slot
  2813. if ( !freeclient )
  2814. {
  2815. freeclient = client;
  2816. }
  2817. }
  2818. }
  2819. if ( !freeclient )
  2820. {
  2821. int count = m_Clients.Count();
  2822. if ( count >= m_nMaxclients )
  2823. {
  2824. return NULL; // server full
  2825. }
  2826. // we have to create a new client slot
  2827. freeclient = CreateNewClient( count );
  2828. m_Clients.AddToTail( freeclient );
  2829. }
  2830. // Success
  2831. return freeclient;
  2832. }
  2833. void CBaseServer::SendClientMessages ( bool bSendSnapshots )
  2834. {
  2835. VPROF_BUDGET( "SendClientMessages", VPROF_BUDGETGROUP_OTHER_NETWORKING );
  2836. SNPROF("SendClientMessages");
  2837. for (int i=0; i< m_Clients.Count(); i++ )
  2838. {
  2839. CBaseClient* client = m_Clients[i];
  2840. // Update Host client send state...
  2841. if ( !client->ShouldSendMessages() )
  2842. continue;
  2843. // Connected, but inactive, just send reliable, sequenced info.
  2844. if ( client->m_NetChannel )
  2845. {
  2846. client->m_NetChannel->Transmit();
  2847. client->UpdateSendState();
  2848. }
  2849. else
  2850. {
  2851. Msg("Client has no netchannel.\n");
  2852. }
  2853. }
  2854. }
  2855. CBaseClient *CBaseServer::CreateFakeClient(const char *name)
  2856. {
  2857. ns_address adr; // it's an empty address
  2858. adr.Clear(); // sets NA_NULL
  2859. CBaseClient *fakeclient = GetFreeClient( adr );
  2860. if ( !fakeclient )
  2861. {
  2862. // server is full
  2863. return NULL;
  2864. }
  2865. INetChannel *netchan = NULL;
  2866. if ( sv_stressbots.GetBool() )
  2867. {
  2868. netchan = NET_CreateNetChannel( m_Socket, &adr, ns_address_render( adr ).String(), fakeclient, NULL, true );
  2869. }
  2870. // a NULL netchannel signals a fakeclient
  2871. m_nUserid = GetNextUserID();
  2872. fakeclient->Connect( name, m_nUserid, netchan, true, CROSSPLAYPLATFORM_THISPLATFORM );
  2873. // fake some cvar settings
  2874. //fakeclient->SetUserCVar( "name", name ); // set already by Connect()
  2875. fakeclient->SetUserCVar( "rate", CFmtStr( "%d", DEFAULT_RATE ) );
  2876. fakeclient->SetUserCVar( "cl_updaterate", CFmtStr( "%d", (int)( 1.0f / host_state.interval_per_tick ) ) );
  2877. fakeclient->SetUserCVar( "cl_cmdrate", CFmtStr( "%d", (int)( 1.0f / host_state.interval_per_tick ) ) );
  2878. fakeclient->SetUserCVar( "cl_interp_ratio", "1.0" );
  2879. fakeclient->SetUserCVar( "cl_interp", "0.1" );
  2880. fakeclient->SetUserCVar( "cl_interpolate", "0" );
  2881. fakeclient->SetUserCVar( "cl_predict", "1" );
  2882. fakeclient->SetUserCVar( "cl_predictweapons", "1" );
  2883. fakeclient->SetUserCVar( "cl_lagcompensation", "1" );
  2884. fakeclient->SetUserCVar( "closecaption","0" );
  2885. fakeclient->SetUserCVar( "english", "1" );
  2886. fakeclient->SetUserCVar( "cl_clanid", "0" );
  2887. // fakeclient->SetUserCVar( "cl_team", "blue" );
  2888. fakeclient->SetUserCVar( "hud_classautokill", "1" );
  2889. fakeclient->SetUserCVar( "tf_medigun_autoheal", "0" );
  2890. fakeclient->SetUserCVar( "cl_autorezoom", "1" );
  2891. fakeclient->SetUserCVar( "fov_desired", "75" );
  2892. // create client in game.dll
  2893. fakeclient->ActivatePlayer();
  2894. fakeclient->m_nSignonTick = m_nTickCount;
  2895. return fakeclient;
  2896. }
  2897. void CBaseServer::Shutdown( void )
  2898. {
  2899. g_pCVar->RemoveGlobalChangeCallback( ServerNotifyVarChangeCallback );
  2900. if ( !IsActive() )
  2901. return;
  2902. m_State = ss_dead;
  2903. CUtlVector< CBaseClient * > vecDelete;
  2904. // Let reliable messages go out
  2905. for ( int retry = 0; retry < 3; ++ retry )
  2906. {
  2907. for ( int i = 0; i < m_Clients.Count(); ++ i )
  2908. {
  2909. CBaseClient *cl = m_Clients[i];
  2910. if ( INetChannel *pChannel = cl->GetNetChannel() )
  2911. {
  2912. pChannel->Transmit();
  2913. }
  2914. }
  2915. Sys_Sleep( 10 );
  2916. }
  2917. // Only drop clients if we have not cleared out entity data prior to this.
  2918. for( int i=m_Clients.Count()-1; i>=0; i-- )
  2919. {
  2920. CBaseClient * cl = m_Clients[ i ];
  2921. if ( cl->IsConnected() )
  2922. {
  2923. // Hack, but this forces instant cleanup
  2924. if ( cl->IsSplitScreenUser() )
  2925. {
  2926. cl->m_bSplitScreenUser = false;
  2927. }
  2928. cl->Disconnect( "Server shutting down" );
  2929. }
  2930. else
  2931. {
  2932. // free any memory do this out side here in case the reason the server is shutting down
  2933. // is because the listen server client typed disconnect, in which case we won't call
  2934. // cl->DropClient, but the client might have some frame snapshot references left over, etc.
  2935. cl->Clear();
  2936. }
  2937. vecDelete.AddToTail( cl );
  2938. m_Clients.Remove( i );
  2939. }
  2940. // Let drop messages go out
  2941. Sys_Sleep( 100 );
  2942. #if !defined( _X360 ) && !defined( NO_STEAM )
  2943. if ( !IsHLTV() )
  2944. {
  2945. if ( m_flFlagForSteamIDReuseAfterShutdownTime && ( Plat_FloatTime() - m_flFlagForSteamIDReuseAfterShutdownTime < 1.0 ) )
  2946. ;// Server was flagged for shutdown and SteamID reuse, don't LogOff
  2947. else
  2948. Steam3Server().DeactivateAndLogoff();
  2949. // Reset the shutdown flag
  2950. m_flFlagForSteamIDReuseAfterShutdownTime = 0;
  2951. }
  2952. #endif
  2953. // Let drop messages go out
  2954. Sys_Sleep( 100 );
  2955. for ( int i = 0; i < vecDelete.Count(); ++i )
  2956. {
  2957. delete vecDelete[ i ];
  2958. }
  2959. // clear everthing
  2960. Clear();
  2961. }
  2962. //-----------------------------------------------------------------------------
  2963. // Purpose: Sends text to all active clients
  2964. // Input : *fmt -
  2965. // ... -
  2966. //-----------------------------------------------------------------------------
  2967. void CBaseServer::BroadcastPrintf (const char *fmt, ...)
  2968. {
  2969. va_list argptr;
  2970. char string[1024];
  2971. va_start (argptr,fmt);
  2972. Q_vsnprintf (string, sizeof( string ), fmt,argptr);
  2973. va_end (argptr);
  2974. CSVCMsg_Print_t print;
  2975. print.set_text( string );
  2976. BroadcastMessage( print );
  2977. }
  2978. void CBaseServer::BroadcastMessage( INetMessage &msg, bool onlyActive, bool reliable )
  2979. {
  2980. for ( int i = 0; i < m_Clients.Count(); i++ )
  2981. {
  2982. CBaseClient *cl = m_Clients[ i ];
  2983. if ( (onlyActive && !cl->IsActive()) || !cl->IsSpawned() )
  2984. {
  2985. continue;
  2986. }
  2987. if ( !cl->SendNetMsg( msg, reliable ) )
  2988. {
  2989. if ( msg.IsReliable() || reliable )
  2990. {
  2991. DevMsg( "BroadcastMessage: Reliable broadcast message overflow for client %s", cl->GetClientName() );
  2992. }
  2993. }
  2994. }
  2995. }
  2996. void CBaseServer::BroadcastMessage( INetMessage &msg, IRecipientFilter &filter )
  2997. {
  2998. if ( filter.IsInitMessage() )
  2999. {
  3000. // This really only applies to the first player to connect, but that works in single player well enought
  3001. if ( IsActive() )
  3002. {
  3003. ConDMsg( "SV_BroadcastMessage: Init message being created after signon buffer has been transmitted\n" );
  3004. }
  3005. if ( !msg.WriteToBuffer( m_Signon ) )
  3006. {
  3007. Sys_Error( "SV_BroadcastMessage: Init message would overflow signon buffer!\n" );
  3008. return;
  3009. }
  3010. }
  3011. else
  3012. {
  3013. msg.SetReliable( filter.IsReliable() );
  3014. int num = filter.GetRecipientCount();
  3015. for ( int i = 0; i < num; i++ )
  3016. {
  3017. int index = filter.GetRecipientIndex( i );
  3018. if ( index < 1 || index > m_Clients.Count() )
  3019. {
  3020. Msg( "SV_BroadcastMessage: Recipient Filter for message type %i (reliable: %s, init: %s) with bogus client index (%i) in list of %i clients\n",
  3021. msg.GetType(),
  3022. filter.IsReliable() ? "yes" : "no",
  3023. filter.IsInitMessage() ? "yes" : "no",
  3024. index, num );
  3025. if ( msg.IsReliable() )
  3026. Host_Error( "Reliable message (type %i) discarded.", msg.GetType() );
  3027. continue;
  3028. }
  3029. CBaseClient *cl = m_Clients[ index - 1 ];
  3030. if ( !cl->IsSpawned() )
  3031. {
  3032. continue;
  3033. }
  3034. if ( !cl->SendNetMsg( msg ) )
  3035. {
  3036. if ( msg.IsReliable() )
  3037. {
  3038. DevMsg( "BroadcastMessage: Reliable filter message overflow for client %s\n", cl->GetClientName() );
  3039. }
  3040. }
  3041. }
  3042. }
  3043. }
  3044. //-----------------------------------------------------------------------------
  3045. // Purpose: Writes events to the client's network buffer
  3046. // Input : *cl -
  3047. // *pack -
  3048. // *msg -
  3049. //-----------------------------------------------------------------------------
  3050. static ConVar sv_debugtempentities( "sv_debugtempentities", "0", 0, "Show temp entity bandwidth usage." );
  3051. // 8 KB should be far more than is needed -- in fact less than 100 bytes seems to be sufficient
  3052. const int kTempEntityBufferSize = 8192;
  3053. void CBaseServer::WriteTempEntities( CBaseClient *client, CFrameSnapshot *pCurrentSnapshot, CFrameSnapshot *pLastSnapshot,
  3054. CSVCMsg_TempEntities_t &msg, int ev_max )
  3055. {
  3056. msg.Clear();
  3057. // allocate the temp buffer for the temp ents
  3058. char ALIGN4 tempEntityData[kTempEntityBufferSize] ALIGN4_POST;
  3059. bf_write buffer( &tempEntityData[0], ARRAYSIZE(tempEntityData) );
  3060. bool bDebug = sv_debugtempentities.GetBool();
  3061. // Container which calls ReleaseReference on all snapshots in list on exit of function scope
  3062. CReferencedSnapshotList snapshotlist;
  3063. // Builds list and calls AddReference on each item in list (uses a mutex to be thread safe)
  3064. framesnapshotmanager->BuildSnapshotList( pCurrentSnapshot, pLastSnapshot, CFrameSnapshotManager::knDefaultSnapshotSet, snapshotlist );
  3065. //keep count of the number of entities that we write out (since some can be omitted by the client)
  3066. int32 nNumEntitiesWritten = 0;
  3067. CEventInfo *pLastEvent = NULL;
  3068. // Build list of events sorted by send table classID (makes the delta work better in cases with a lot of the same message type )
  3069. for ( int nSnapShotIndex = 0;
  3070. nSnapShotIndex < snapshotlist.m_vecSnapshots.Count();
  3071. ++nSnapShotIndex )
  3072. {
  3073. CFrameSnapshot *pSnapshot = snapshotlist.m_vecSnapshots[ nSnapShotIndex ];
  3074. for( int i = 0; i < pSnapshot->m_nTempEntities; ++i )
  3075. {
  3076. CEventInfo *event = pSnapshot->m_pTempEntities[ i ];
  3077. if ( client->IgnoreTempEntity( event ) )
  3078. continue; // event is not seen by this player
  3079. //we are writing this entity so update our count so the receiver knows how many to parse
  3080. nNumEntitiesWritten++;
  3081. if ( event->fire_delay == 0.0f )
  3082. {
  3083. buffer.WriteOneBit( 0 );
  3084. }
  3085. else
  3086. {
  3087. buffer.WriteOneBit( 1 );
  3088. buffer.WriteSBitLong( event->fire_delay*100.0f, 8 );
  3089. }
  3090. if ( pLastEvent &&
  3091. pLastEvent->classID == event->classID )
  3092. {
  3093. buffer.WriteOneBit( 0 ); // delta against last temp entity
  3094. int startBit = bDebug ? buffer.GetNumBitsWritten() : 0;
  3095. SendTable_WriteAllDeltaProps( event->pSendTable, pLastEvent->m_Packed, event->m_Packed, -1, &buffer );
  3096. if ( bDebug )
  3097. {
  3098. int length = buffer.GetNumBitsWritten() - startBit;
  3099. DevMsg("TE %s delta bits: %i\n", event->pSendTable->GetName(), length );
  3100. }
  3101. }
  3102. else
  3103. {
  3104. // full update, just compressed against zeros in MP
  3105. buffer.WriteOneBit( 1 );
  3106. int startBit = bDebug ? buffer.GetNumBitsWritten() : 0;
  3107. buffer.WriteUBitLong( event->classID, GetClassBits() );
  3108. // Will write all non-zero fields
  3109. SendTable_WriteAllDeltaProps( event->pSendTable, SERIALIZED_ENTITY_HANDLE_INVALID, event->m_Packed, -1, &buffer );
  3110. if ( bDebug )
  3111. {
  3112. int length = buffer.GetNumBitsWritten() - startBit;
  3113. DevMsg("TE %s full bits: %i\n", event->pSendTable->GetName(), length );
  3114. }
  3115. }
  3116. if ( IsMultiplayer() )
  3117. {
  3118. // in single player, don't used delta compression, lastEvent remains NULL
  3119. pLastEvent = event;
  3120. }
  3121. }
  3122. }
  3123. //don't do any more work if we didn't write anything out
  3124. if( nNumEntitiesWritten <= 0 )
  3125. return;
  3126. if ( buffer.IsOverflowed() )
  3127. {
  3128. Warning( "WriteTempOverflow! Discarding all ents!\n" );
  3129. return;
  3130. }
  3131. // Copy the data to the message buffer. This copying is unfortunate but at least
  3132. // it has a cost that is proportional to the message size. The alternative is to
  3133. // initially set the buffer size larger and then resize it down, but the initial
  3134. // setting of the buffer size will require zeroing all of its bytes which is
  3135. // more expensive (as seen on CS:GO server profiles on Linux).
  3136. const int nBytesWritten = Bits2Bytes( buffer.GetNumBitsWritten() );
  3137. msg.set_entity_data( &tempEntityData[0], nBytesWritten );
  3138. // set num entries
  3139. msg.set_num_entries( nNumEntitiesWritten );
  3140. }
  3141. void CBaseServer::SetMaxClients( int number )
  3142. {
  3143. m_nMaxclients = clamp( number, 1, ABSOLUTE_PLAYER_LIMIT );
  3144. }
  3145. //-----------------------------------------------------------------------------
  3146. // Purpose:
  3147. //-----------------------------------------------------------------------------
  3148. void CBaseServer::RecalculateTags( void )
  3149. {
  3150. if ( IsHLTV() || IsReplay() )
  3151. return;
  3152. // We're going to modify the sv_tags convar here, which will cause this to be called again. Prevent recursion.
  3153. static bool bRecalculatingTags = false;
  3154. if ( bRecalculatingTags )
  3155. return;
  3156. bRecalculatingTags = true;
  3157. // Games without this interface will have no tagged cvars besides "increased_maxplayers"
  3158. if ( serverGameTags )
  3159. {
  3160. KeyValues *pKV = new KeyValues( "GameTags" );
  3161. serverGameTags->GetTaggedConVarList( pKV );
  3162. KeyValues *p = pKV->GetFirstSubKey();
  3163. while ( p )
  3164. {
  3165. ConVar *pConVar = g_pCVar->FindVar( p->GetString("convar") );
  3166. if ( pConVar )
  3167. {
  3168. const char *pszDef = pConVar->GetDefault();
  3169. const char *pszCur = pConVar->GetString();
  3170. if ( Q_strcmp( pszDef, pszCur ) )
  3171. {
  3172. AddTag( p->GetString("tag") );
  3173. }
  3174. else
  3175. {
  3176. RemoveTag( p->GetString("tag") );
  3177. }
  3178. }
  3179. p = p->GetNextKey();
  3180. }
  3181. pKV->deleteThis();
  3182. }
  3183. // Check maxplayers
  3184. int minmaxplayers = 1;
  3185. int maxmaxplayers = ABSOLUTE_PLAYER_LIMIT;
  3186. int defaultmaxplayers = 1;
  3187. serverGameClients->GetPlayerLimits( minmaxplayers, maxmaxplayers, defaultmaxplayers );
  3188. int nHumans;
  3189. int nMaxHumans;
  3190. int nBots;
  3191. GetMasterServerPlayerCounts( nHumans, nMaxHumans, nBots );
  3192. if ( nMaxHumans > maxmaxplayers )
  3193. {
  3194. AddTag( "increased_maxplayers" );
  3195. }
  3196. else
  3197. {
  3198. RemoveTag( "increased_maxplayers" );
  3199. }
  3200. if ( g_bLowViolence )
  3201. {
  3202. AddTag( "low_violence" );
  3203. }
  3204. else
  3205. {
  3206. RemoveTag( "low_violence" );
  3207. }
  3208. // Group name
  3209. const char *pszGroupName = sv_steamgroup.GetString();
  3210. if ( !pszGroupName || !pszGroupName[0] )
  3211. {
  3212. RemoveTag( "*grp:", true );
  3213. }
  3214. else
  3215. {
  3216. char chGroupNameBuf[64] = {0};
  3217. Q_snprintf( chGroupNameBuf, sizeof( chGroupNameBuf ) - 2, "%si", pszGroupName );
  3218. AddTag( "*grp:", chGroupNameBuf );
  3219. }
  3220. bRecalculatingTags = false;
  3221. }
  3222. //-----------------------------------------------------------------------------
  3223. // Purpose:
  3224. //-----------------------------------------------------------------------------
  3225. void CBaseServer::AddTag( const char *pszTag, const char *pszSubTagValue )
  3226. {
  3227. CSplitString TagList( sv_tags.GetString(), "," );
  3228. for ( int i = 0; i < TagList.Count(); i++ )
  3229. {
  3230. if ( pszSubTagValue )
  3231. {
  3232. // See if the subtag matches
  3233. if ( StringHasPrefix( TagList[i], pszTag ) )
  3234. {
  3235. // Ok, see if the tag value matches
  3236. if ( Q_stricmp(TagList[i]+strlen(pszTag),pszSubTagValue) == 0 )
  3237. return;
  3238. // They have a subtag specified, but it's wrong. Remove it.
  3239. RemoveTag( pszTag, true );
  3240. }
  3241. }
  3242. else
  3243. {
  3244. // Already in the tag list?
  3245. if ( !Q_stricmp(TagList[i],pszTag) )
  3246. return;
  3247. }
  3248. }
  3249. // Append it
  3250. char tmptags[MAX_TAG_STRING_LENGTH];
  3251. tmptags[0] = '\0';
  3252. Q_strncpy( tmptags, pszTag, MAX_TAG_STRING_LENGTH );
  3253. if ( pszSubTagValue )
  3254. {
  3255. Q_strncat( tmptags, pszSubTagValue, MAX_TAG_STRING_LENGTH );
  3256. }
  3257. Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
  3258. Q_strncat( tmptags, sv_tags.GetString(), MAX_TAG_STRING_LENGTH );
  3259. sv_tags.SetValue( tmptags );
  3260. }
  3261. //-----------------------------------------------------------------------------
  3262. // Purpose:
  3263. //-----------------------------------------------------------------------------
  3264. void CBaseServer::RemoveTag( const char *pszTag, bool bSubTag )
  3265. {
  3266. const char *pszTags = sv_tags.GetString();
  3267. if ( !pszTags || !pszTags[0] )
  3268. return;
  3269. char tmptags[MAX_TAG_STRING_LENGTH];
  3270. tmptags[0] = '\0';
  3271. CSplitString TagList ( sv_tags.GetString(), "," );
  3272. bool bFoundIt = false;
  3273. for ( int i = 0; i < TagList.Count(); i++ )
  3274. {
  3275. bool bMatched = false;
  3276. if ( bSubTag )
  3277. {
  3278. bMatched = StringHasPrefix( TagList[i], pszTag );
  3279. }
  3280. else
  3281. {
  3282. bMatched = Q_stricmp(TagList[i],pszTag) == 0;
  3283. }
  3284. // Keep any tags other than the specified one
  3285. if ( !bMatched )
  3286. {
  3287. Q_strncat( tmptags, TagList[i], MAX_TAG_STRING_LENGTH );
  3288. Q_strncat( tmptags, ",", MAX_TAG_STRING_LENGTH );
  3289. }
  3290. else
  3291. {
  3292. bFoundIt = true;
  3293. }
  3294. }
  3295. // Didn't find it in our list?
  3296. if ( !bFoundIt )
  3297. return;
  3298. sv_tags.SetValue( tmptags );
  3299. }
  3300. CBaseClient *CBaseServer::CreateSplitClient( const CMsg_CVars& vecUserInfo, CBaseClient *pAttachedTo )
  3301. {
  3302. // 0.0.0.0:0 signifies a split client. It'll plumb all the way down to winsock calls but it won't make them.
  3303. ns_address adr;
  3304. adr.Clear();
  3305. CBaseClient *pSplitClient = GetFreeClient( adr );
  3306. if ( !pSplitClient )
  3307. {
  3308. // server is full
  3309. return NULL;
  3310. }
  3311. INetChannel *netchan = NULL;
  3312. // [ NET ENCRYPT ] Split clients don't configure an encryption key because the master channel will network
  3313. netchan = NET_CreateNetChannel( m_Socket, &adr, ns_address_render( adr ).String(), pSplitClient, NULL, true );
  3314. m_nUserid = GetNextUserID();
  3315. // Name will be pulled from vecUserInfo!!!
  3316. pSplitClient->Connect( "split", m_nUserid, netchan, true, pAttachedTo->m_ClientPlatform, &vecUserInfo );
  3317. Assert( pSplitClient->m_bFakePlayer == true );
  3318. pSplitClient->m_bSplitScreenUser = true;
  3319. pSplitClient->m_pAttachedTo = pAttachedTo;
  3320. pSplitClient->m_nSignonTick = m_nTickCount;
  3321. pSplitClient->m_bSplitAllowFastDisconnect = true;
  3322. if ( !pSplitClient->CheckConnect() )
  3323. {
  3324. pSplitClient->m_bSplitAllowFastDisconnect = false;
  3325. return NULL;
  3326. }
  3327. pSplitClient->m_bSplitAllowFastDisconnect = false;
  3328. return pSplitClient;
  3329. }
  3330. CBaseClient *CBaseServer::GetBaseUserForSplitClient( CBaseClient *pSplitUser )
  3331. {
  3332. if ( pSplitUser->m_pAttachedTo )
  3333. return pSplitUser->m_pAttachedTo;
  3334. return pSplitUser;
  3335. }
  3336. void CBaseServer::QueueSplitScreenDisconnect( CBaseClient *pSplitHost, CBaseClient *pSplitUser )
  3337. {
  3338. SplitDisconnect_t disc;
  3339. disc.m_pUser = pSplitHost;
  3340. disc.m_pSplit = pSplitUser;
  3341. m_QueuedForDisconnect.AddToTail( disc );
  3342. }
  3343. void CBaseServer::ProcessSplitScreenDisconnects()
  3344. {
  3345. // Destroy it
  3346. for ( int i = 0; i < m_QueuedForDisconnect.Count(); ++i )
  3347. {
  3348. SplitDisconnect_t &disc = m_QueuedForDisconnect[ i ];
  3349. CBaseClient *pClient = disc.m_pUser;
  3350. for ( int j = 0; j < host_state.max_splitscreen_players; ++j )
  3351. {
  3352. if ( pClient->m_SplitScreenUsers[ j ] != disc.m_pSplit )
  3353. continue;
  3354. pClient->m_SplitScreenUsers[ j ] = NULL;
  3355. disc.m_pSplit->m_bSplitAllowFastDisconnect = true;
  3356. disc.m_pSplit->Disconnect( "leaving splitscreen" );
  3357. disc.m_pSplit->m_bSplitScreenUser = false;
  3358. }
  3359. }
  3360. m_QueuedForDisconnect.Purge();
  3361. }
  3362. // Exposing state of the server to the client code
  3363. ConVar sv_hosting_lobby( "sv_hosting_lobby", "0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
  3364. void CBaseServer::UpdateReservedState()
  3365. {
  3366. if ( ( m_flReservationExpiryTime && !IsReserved() ) ||
  3367. ( m_flReservationExpiryTime < net_time ) )
  3368. {
  3369. m_flReservationExpiryTime = 0.0f;
  3370. sv.UpdateHibernationState();
  3371. }
  3372. if ( m_flTimeLastClientLeft != -1.0f )
  3373. {
  3374. sv.UpdateHibernationState();
  3375. }
  3376. }
  3377. uint64 CBaseServer::GetReservationCookie() const
  3378. {
  3379. return m_nReservationCookie;
  3380. }
  3381. bool CBaseServer::ReserveServerForQueuedGame( char const *szReservationPayload )
  3382. {
  3383. if ( !szReservationPayload )
  3384. return false;
  3385. switch ( szReservationPayload[0] )
  3386. {
  3387. case 'Q': // Queued competitive, locked game from the start
  3388. case 'G': // Joinable in progress game
  3389. break;
  3390. case 'R':
  3391. sscanf( szReservationPayload + 1, "%p", &m_pnReservationCookieSession );
  3392. return true;
  3393. default:
  3394. return false;
  3395. }
  3396. extern bool sv_ShutDown_WasRequested();
  3397. if ( sv_ShutDown_WasRequested() )
  3398. {
  3399. Warning( "Rejecting reservation because sv_shutdown was requested.\n" );
  3400. return false; // don't accept new reservations if we are in sv_shutdown mode
  3401. }
  3402. uint64 uiReservationCookie = 0;
  3403. uint64 uiMatchID = 0;
  3404. int32 bReserve = 0;
  3405. sscanf( szReservationPayload+1, "%llx,%llx,%d:", &uiReservationCookie, &uiMatchID, &bReserve );
  3406. if ( !uiReservationCookie || !uiMatchID )
  3407. return false;
  3408. m_nMatchId = uiMatchID;
  3409. if ( bReserve )
  3410. {
  3411. if ( !IsReserved() || ( GetReservationCookie() == uiReservationCookie ) )
  3412. {
  3413. m_flReservationExpiryTime = net_time + sv_mmqueue_reservation_timeout.GetFloat();
  3414. sv_mmqueue_reservation.SetValue( szReservationPayload );
  3415. SetReservationCookie( uiReservationCookie, "ReserveServerForQueuedGame: %s", szReservationPayload );
  3416. return true;
  3417. }
  3418. else
  3419. return false;
  3420. }
  3421. else
  3422. {
  3423. if ( IsReserved() && ( GetReservationCookie() == uiReservationCookie ) )
  3424. {
  3425. Unreserve();
  3426. return true;
  3427. }
  3428. else
  3429. return !IsReserved();
  3430. }
  3431. }
  3432. void CBaseServer::SetReservationCookie( uint64 uiCookie, char const *pchReasonFormat, ... )
  3433. {
  3434. if ( uiCookie != m_nReservationCookie )
  3435. {
  3436. char reason[ 256 ] = { 0 };
  3437. va_list argptr;
  3438. va_start( argptr, pchReasonFormat );
  3439. Q_vsnprintf( reason, sizeof( reason ), pchReasonFormat, argptr );
  3440. va_end( argptr );
  3441. ConColorMsg( Color( 255, 0, 255, 255 ), "-> Reservation cookie %llx: reason %s\n", uiCookie, reason );
  3442. if ( !uiCookie )
  3443. {
  3444. sv_mmqueue_reservation.SetValue( "" );
  3445. m_arrReservationPlayers.RemoveAll();
  3446. }
  3447. else if ( StringHasPrefix( sv_mmqueue_reservation.GetString(), CFmtStr( "Q%llx,", uiCookie ) ) )
  3448. {
  3449. // Set reservation players
  3450. m_arrReservationPlayers.RemoveAll();
  3451. for ( char const *pszPrev = sv_mmqueue_reservation.GetString(), *pszNext = pszPrev;
  3452. ( pszNext = strchr( pszPrev, '[' ) ) != NULL; pszPrev = pszNext + 1 )
  3453. {
  3454. uint32 uiAccountId = 0;
  3455. sscanf( pszNext, "[%x]", &uiAccountId );
  3456. if ( uiAccountId )
  3457. {
  3458. QueueMatchPlayer_t qmp;
  3459. qmp.m_uiAccountID = uiAccountId;
  3460. qmp.m_uiToken = 0;
  3461. qmp.m_uiReservationStage = 0;
  3462. m_arrReservationPlayers.AddToTail( qmp );
  3463. }
  3464. }
  3465. // Set the number of expected humans
  3466. m_numGameSlots = m_arrReservationPlayers.Count();
  3467. // Tournament servers need additional slots for casters
  3468. static char const * s_pchTournamentServer = CommandLine()->ParmValue( "-tournament", ( char const * ) NULL );
  3469. if ( s_pchTournamentServer )
  3470. {
  3471. int numCasters = 0;
  3472. for ( char const *pszPrev = sv_mmqueue_reservation.GetString(), *pszNext = pszPrev;
  3473. ( pszNext = strchr( pszPrev, '{' ) ) != NULL; pszPrev = pszNext + 1 )
  3474. {
  3475. uint32 uiAccountId = 0;
  3476. sscanf( pszNext, "{%x}", &uiAccountId );
  3477. if ( uiAccountId )
  3478. ++ numCasters;
  3479. }
  3480. // Allow launch parameter to limit number of caster slots
  3481. static int s_nTournamentExtraCastersSlots = CommandLine()->ParmValue( "-tournament_extra_casters_slots", 2 );
  3482. numCasters = MAX( 0, MIN( s_nTournamentExtraCastersSlots, numCasters ) );
  3483. m_numGameSlots += numCasters;
  3484. }
  3485. }
  3486. else if ( StringHasPrefix( sv_mmqueue_reservation.GetString(), CFmtStr( "G%llx,", uiCookie ) ) )
  3487. {
  3488. // Set reservation players
  3489. m_arrReservationPlayers.RemoveAll();
  3490. // Set the number of expected humans
  3491. m_numGameSlots = 0;
  3492. }
  3493. else
  3494. {
  3495. // Set exposed reservation variable
  3496. sv_mmqueue_reservation.SetValue( CFmtStr( "0x%llx", uiCookie ) );
  3497. }
  3498. }
  3499. m_nReservationCookie = uiCookie;
  3500. UpdateGameData();
  3501. // Expose the current reservation state via a replicated convar to clients
  3502. sv_hosting_lobby.SetValue( IsReserved() );
  3503. }
  3504. void CBaseServer::Unreserve()
  3505. {
  3506. if ( IsReserved() )
  3507. {
  3508. Msg( "Server was reserved for %d more seconds. Reservation cleared.\n", (int) ( m_flReservationExpiryTime - net_time ) );
  3509. m_flReservationExpiryTime = 0.0f;
  3510. sv.UpdateHibernationState();
  3511. }
  3512. else
  3513. {
  3514. Msg( "Server is not currently reserved.\n" );
  3515. }
  3516. UpdateGameData();
  3517. }
  3518. char const *CBaseServer::GetGameType() const
  3519. {
  3520. return m_GameType.String();
  3521. }
  3522. char const *CBaseServer::GetGameData() const
  3523. {
  3524. return m_GameData.Base();
  3525. }
  3526. int CBaseServer::GetGameDataVersion() const
  3527. {
  3528. return m_GameDataVersion;
  3529. }
  3530. void CBaseServer::ClearTagStrings()
  3531. {
  3532. m_GameType = "";
  3533. }
  3534. static void BuildTokenList( char const *pchString, char chDelim, CUtlVector< CUtlString > &list )
  3535. {
  3536. // Have to split up the string
  3537. int len = Q_strlen( pchString );
  3538. char *szCritBuf = (char *)stackalloc( len + 1 );
  3539. Q_strncpy( szCritBuf, pchString, len + 1 );
  3540. char *pszTok = strchr( szCritBuf, chDelim );
  3541. char *pszPrevTok = szCritBuf;
  3542. while ( pszTok && pszPrevTok )
  3543. {
  3544. // Save character
  3545. char szTemp = *pszTok;
  3546. *pszTok = 0;
  3547. if ( *pszPrevTok )
  3548. {
  3549. list.AddToTail( CUtlString( pszPrevTok ) );
  3550. }
  3551. // Restore and advance to next token after delim
  3552. *pszTok = szTemp;
  3553. pszPrevTok = pszTok + 1;
  3554. pszTok = strchr( pszPrevTok, chDelim );
  3555. }
  3556. // Close off any trailing token w/o a delim at the end
  3557. if ( pszPrevTok && *pszPrevTok )
  3558. {
  3559. list.AddToTail( CUtlString( pszPrevTok ) );
  3560. }
  3561. }
  3562. void CBaseServer::AddTagString( CUtlString &dest, char const *pchString )
  3563. {
  3564. if ( !pchString || !*pchString )
  3565. return;
  3566. char *chDelim = ",";
  3567. if ( Q_strstr( pchString, chDelim ) )
  3568. {
  3569. CUtlVector< CUtlString > list;
  3570. BuildTokenList( pchString, *chDelim, list );
  3571. for ( int i = 0; i < list.Count(); ++i )
  3572. {
  3573. AddTagString( dest, list[ i ].String() );
  3574. }
  3575. return;
  3576. }
  3577. if ( !dest.IsEmpty() )
  3578. {
  3579. dest += chDelim;
  3580. }
  3581. dest += pchString;
  3582. }
  3583. void CBaseServer::UpdateGameType()
  3584. {
  3585. ClearTagStrings();
  3586. CUtlString tags;
  3587. if ( serverGameDLL )
  3588. {
  3589. char szMatchMakingTags[ 1024 ] = { 0 };
  3590. serverGameDLL->GetMatchmakingTags( szMatchMakingTags, sizeof( szMatchMakingTags ) );
  3591. if ( szMatchMakingTags[ 0 ] )
  3592. {
  3593. AddTagString( m_GameType, szMatchMakingTags );
  3594. }
  3595. }
  3596. bool bHaveAnyClients = GetNumHumanPlayers() > 0;
  3597. if ( !bHaveAnyClients )
  3598. {
  3599. AddTagString( m_GameType, "empty" );
  3600. }
  3601. static ConVarRef var( "sv_tags" );
  3602. if ( var.IsValid() && var.GetString()[ 0 ] )
  3603. {
  3604. AddTagString( m_GameType, var.GetString() );
  3605. }
  3606. // Is this server "secure"?
  3607. #if !defined( NO_STEAM ) && !defined( _GAMECONSOLE )
  3608. {
  3609. AddTagString( m_GameType, Steam3Server().BSecure() ? "secure" : "insecure" );
  3610. }
  3611. #endif
  3612. // if ( IsDedicated() && serverGameDLL->IsValveDS() && !IsX360() && !IsDedicatedForXbox() &&
  3613. // !bHaveAnyClients &&
  3614. // !IsReserved() && !( GetNumClients() - GetNumFakeClients() ) &&
  3615. // !sv_steamgroup_exclusive.GetBool() )
  3616. // {
  3617. // AddTagString( va( "*sv_search_key_%s%d", sv_search_key.GetString(), GetHostVersion() ) );
  3618. // }
  3619. if ( Steam3Server().SteamGameServer() )
  3620. {
  3621. Steam3Server().SteamGameServer()->SetGameTags( m_GameType.String() );
  3622. }
  3623. }
  3624. void CBaseServer::UpdateGameData()
  3625. {
  3626. UpdateGameType();
  3627. const int nPacketSize = MAX_ROUTABLE_PAYLOAD - 128; // The packet has to fit into NET_SendPacket and there's some data sent before the tags too
  3628. // Remember old data
  3629. CUtlVector< char > oldGameData;
  3630. oldGameData.EnsureCapacity( m_GameData.Count() );
  3631. oldGameData.AddMultipleToTail( m_GameData.Count(), m_GameData.Base() );
  3632. //
  3633. // Generate new data
  3634. m_GameData.RemoveAll();
  3635. m_GameData.EnsureCapacity( nPacketSize );
  3636. m_GameData.AddMultipleToTail( nPacketSize );
  3637. memset( m_GameData.Base(), 0, m_GameData.Count() );
  3638. // Add search key
  3639. CUtlString utlKey;
  3640. if ( serverGameDLL && IsDedicated() && !IsX360() && !IsDedicatedForXbox() &&
  3641. ( GetNumHumanPlayers() <= 0 ) &&
  3642. !IsReserved() && !( GetNumClients() - GetNumFakeClients() ) &&
  3643. !sv_steamgroup_exclusive.GetBool() && !GetPassword() )
  3644. {
  3645. AddTagString( utlKey, CFmtStr( "%skey:%s%d",
  3646. serverGameDLL->IsValveDS() ? "v" : "c",
  3647. sv_search_key.GetString(), GetHostVersion() ) );
  3648. }
  3649. if ( utlKey.Length() > m_GameData.Count() - 3 )
  3650. {
  3651. Warning( "GameData: sv_search_key too long, cannot advertise server!\n" );
  3652. return;
  3653. }
  3654. // Group name
  3655. CUtlString utlGroups;
  3656. const char *pszGroupName = sv_steamgroup.GetString();
  3657. {
  3658. CUtlVector< CUtlString > list;
  3659. char *chDelim = ",";
  3660. BuildTokenList( pszGroupName, *chDelim, list );
  3661. for ( int i = 0; i < list.Count(); ++i )
  3662. {
  3663. AddTagString( utlGroups, CFmtStr( "grp:%si", list[ i ].String() ) );
  3664. }
  3665. }
  3666. if ( utlKey.Length() + utlGroups.Length() > m_GameData.Count() - 3 )
  3667. {
  3668. Warning( "GameData: Too many Steam groups set for sv_steamgroup, not advertising Steam groups affiliation.\n" );
  3669. utlGroups.Purge();
  3670. }
  3671. if ( serverGameDLL )
  3672. {
  3673. serverGameDLL->GetMatchmakingGameData( m_GameData.Base(), m_GameData.Count() - 1 - utlKey.Length() - utlGroups.Length() - 2 );
  3674. }
  3675. int nLen = Q_strlen( m_GameData.Base() );
  3676. char *pszWrite = m_GameData.Base() + nLen;
  3677. int numBytes = m_GameData.Count() - nLen;
  3678. Q_snprintf( pszWrite, numBytes - 1, "%s%s%s%s",
  3679. nLen ? "," : "",
  3680. utlGroups.Get(), utlGroups.Length() ? "," : "",
  3681. utlKey.Get() );
  3682. // Always update Steam
  3683. if ( Steam3Server().SteamGameServer() )
  3684. {
  3685. Steam3Server().SteamGameServer()->SetGameData( m_GameData.Base() );
  3686. }
  3687. // Increment data version if changed
  3688. if ( oldGameData.Count() != m_GameData.Count() ||
  3689. memcmp( oldGameData.Base(), m_GameData.Base(), m_GameData.Count() ) )
  3690. {
  3691. ++ m_GameDataVersion;
  3692. }
  3693. }
  3694. bool CBaseServer::IsPlayingSoloAgainstBots() const
  3695. {
  3696. if ( sv.IsActive() && sv.IsMultiplayer() )
  3697. {
  3698. int nNumHumanPlayers = sv.GetNumClients() - sv.GetNumFakeClients();
  3699. if ( nNumHumanPlayers == 1 )
  3700. {
  3701. return true;
  3702. }
  3703. }
  3704. return false;
  3705. }
  3706. bool CBaseServer::IsExclusiveToLobbyConnections() const
  3707. {
  3708. #ifndef NO_STEAM
  3709. if ( !IsDedicated() )
  3710. return false;
  3711. #if !defined( CSTRIKE15 )
  3712. // We are switching CStrike to always have lobbies associated with servers for community matchmaking
  3713. if ( !sv_allow_lobby_connect_only.GetBool() )
  3714. return false;
  3715. #endif
  3716. if ( sv_lan.GetBool() )
  3717. return false;
  3718. return true;
  3719. #else
  3720. return false;
  3721. #endif
  3722. }
  3723. // CON_COMMAND( sv_unreserve, "Clears any lobby reservation for this server\n" )
  3724. // {
  3725. // sv.Unreserve();
  3726. // }
  3727. bool CBaseServer::ShouldHideServer() const
  3728. {
  3729. if ( serverGameDLL &&
  3730. serverGameDLL->ShouldHideServer() )
  3731. {
  3732. return true;
  3733. }
  3734. return false;
  3735. }
  3736. // If a server is hidden from master server it won't show up on public internet (server browser, steam server list)
  3737. // but it can still respond to LAN info, players, rules queries etc.
  3738. bool CBaseServer::ShouldHideFromMasterServer() const
  3739. {
  3740. // UNDONE: MATCHMAKING: Left4Dead keeps passworded, listen and cheat servers off the master server. TF2 does not.
  3741. extern ConVar sv_cheats;
  3742. if ( !IsDedicated() && (!IsHLTV()) )
  3743. {
  3744. return true;
  3745. }
  3746. return ShouldHideServer();
  3747. }
  3748. void CBaseServer::OnPasswordChanged()
  3749. {
  3750. if ( IsActive() )
  3751. {
  3752. Cbuf_AddText( CBUF_SERVER, "heartbeat\n" );
  3753. }
  3754. }
  3755. int CBaseServer::GetMaxClients() const
  3756. {
  3757. return m_nMaxclients;
  3758. }
  3759. int CBaseServer::GetMaxHumanPlayers() const
  3760. {
  3761. if ( serverGameClients )
  3762. {
  3763. int nMaxHuman = serverGameClients->GetMaxHumanPlayers();
  3764. if ( nMaxHuman != -1 )
  3765. {
  3766. return nMaxHuman;
  3767. }
  3768. }
  3769. return GetMaxClients();
  3770. }
  3771. int CBaseServer::GetNumHumanPlayers( void ) const
  3772. {
  3773. int count = 0;
  3774. for (int i=0 ; i < m_Clients.Count() ; i++ )
  3775. {
  3776. if ( m_Clients[ i ]->IsHumanPlayer() )
  3777. {
  3778. count++;
  3779. }
  3780. }
  3781. return count;
  3782. }
  3783. void CBaseServer::GetMasterServerPlayerCounts( int &nHumans, int &nMaxHumanSlots, int &nBots )
  3784. {
  3785. // count active users
  3786. nHumans = GetNumHumanPlayers();
  3787. nMaxHumanSlots = GetMaxHumanPlayers();
  3788. nBots = sv.GetNumFakeClients();
  3789. if ( sv_visiblemaxplayers.GetInt() > 0 )
  3790. {
  3791. nMaxHumanSlots = sv_visiblemaxplayers.GetInt();
  3792. }
  3793. extern bool CanShowHostTvStatus();
  3794. if ( !CanShowHostTvStatus() && ( nBots > 0 ) )
  3795. {
  3796. for ( CActiveHltvServerIterator hltv; hltv; hltv.Next() )
  3797. {
  3798. nBots--; // reduce the bot count by HLTV bot
  3799. }
  3800. }
  3801. }
  3802. void CBaseServer::ShowTags() const
  3803. {
  3804. Msg( "Tags:\n" );
  3805. Msg( "Public : %s\n", GetGameType() );
  3806. Msg( "Private: %s\n", GetGameData() );
  3807. }
  3808. CON_COMMAND( sv_showtags, "Describe current gametags." )
  3809. {
  3810. sv.ShowTags();
  3811. }
  3812. const ConVar &GetIndexedConVar( const ConVar &cv, int nIndex )
  3813. {
  3814. if ( nIndex == 0 )
  3815. return cv;
  3816. const ConVar *pIndexedCV = g_pCVar->FindVar( va( "%s%d", cv.GetBaseName(), nIndex ) );
  3817. if ( pIndexedCV )
  3818. return *pIndexedCV;
  3819. else
  3820. return cv;
  3821. }