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.

1220 lines
33 KiB

  1. //========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #ifdef _WIN32
  9. #if !defined( _X360 )
  10. #include <winsock.h>
  11. #else
  12. #include "winsockx.h"
  13. #endif
  14. #elif POSIX
  15. #define INVALID_SOCKET -1
  16. #define SOCKET_ERROR -1
  17. #include <sys/types.h>
  18. #include <sys/socket.h>
  19. #include <netinet/in.h>
  20. #ifdef OSX
  21. #include <uuid/uuid.h>
  22. #endif
  23. #ifdef _PS3
  24. #include "basetypes.h"
  25. #include "ps3/ps3_core.h"
  26. #else
  27. #include <pwd.h>
  28. #define closesocket close
  29. #endif
  30. #include "quakedef.h" // build_number()
  31. #endif
  32. #include "net.h"
  33. #include "quakedef.h"
  34. #include "host.h"
  35. #include "host_phonehome.h"
  36. #include "mathlib/IceKey.H"
  37. #include "bitbuf.h"
  38. #include "tier0/icommandline.h"
  39. #include "blockingudpsocket.h"
  40. #include "cserserverprotocol_engine.h"
  41. #include "utlbuffer.h"
  42. #include "eiface.h"
  43. #include "FindSteamServers.h"
  44. #include <vstdlib/random.h>
  45. #include "iregistry.h"
  46. #include "filesystem_engine.h"
  47. #include "checksum_md5.h"
  48. #include "cl_steamauth.h"
  49. #include "steam/steam_gameserver.h"
  50. #include "materialsystem/imaterialsystemhardwareconfig.h"
  51. #include "tier2/tier2.h"
  52. #include "server.h"
  53. #include "sv_steamauth.h"
  54. #include "threadtools.h"
  55. #if defined( _X360 )
  56. #include "xbox/xbox_win32stubs.h"
  57. #endif
  58. #if defined( _PS3 )
  59. #include "ps3/ps3_win32stubs.h"
  60. #define closesocket socketclose
  61. #endif
  62. // memdbgon must be the last include file in a .cpp file!!!
  63. #include "tier0/memdbgon.h"
  64. typedef unsigned int u32;
  65. typedef unsigned char u8;
  66. typedef unsigned short u16;
  67. namespace GameStatsHarvester
  68. {
  69. enum EFileType
  70. {
  71. eFileTypeGameStats,
  72. eFILETYPECOUNT // Count number of legal values
  73. };
  74. enum ESendMethod
  75. {
  76. eSendMethodWholeRawFileNoBlocks,
  77. eSendMethodCompressedBlocks, // TODO: Reenable compressed sending of minidumps
  78. eSENDMETHODCOUNT // Count number of legal values
  79. };
  80. }
  81. using namespace GameStatsHarvester;
  82. // TODO: cut protocol version down to u8 if possible, to reduce bandwidth usage
  83. // for very frequent but tiny commands.
  84. typedef u32 ProtocolVersion_t;
  85. typedef u8 ProtocolAcceptanceFlag_t;
  86. typedef u8 ProtocolUnacceptableAck_t;
  87. typedef u32 MessageSequenceId_t;
  88. typedef u32 ServerSessionHandle_t;
  89. typedef u32 ClientSessionHandle_t;
  90. typedef u32 NetworkTransactionId_t;
  91. // Command codes are intentionally as small as possible to minimize bandwidth usage
  92. // for very frequent but tiny commands (e.g. GDS 'FindServer' commands).
  93. typedef u8 Command_t;
  94. // ... likewise response codes are as small as possible - we use this when we
  95. // ... can and revert to large types on a case by case basis.
  96. typedef u8 CommandResponse_t;
  97. // This define our standard type for length prefix for variable length messages
  98. // in wire protocols.
  99. // This is specifically used by CWSABUFWrapper::PrepareToReceiveLengthPrefixedMessage()
  100. // and its supporting functions.
  101. // It is defined here for generic (portable) network code to use when constructing
  102. // messages to be sent to peers that use the above function.
  103. // e.g. SteamValidateUserIDTickets.dll uses this for that purpose.
  104. // We support u16 or u32 (obviously switching between them breaks existing protocols
  105. // unless all components are switched simultaneously).
  106. typedef u32 NetworkMessageLengthPrefix_t;
  107. // Similarly, strings should be preceeded by their length.
  108. typedef u16 StringLengthPrefix_t;
  109. const ProtocolAcceptanceFlag_t cuProtocolIsNotAcceptable
  110. = static_cast<ProtocolAcceptanceFlag_t>( 0 );
  111. const ProtocolAcceptanceFlag_t cuProtocolIsAcceptable
  112. = static_cast<ProtocolAcceptanceFlag_t>( 1 );
  113. const Command_t cuMaxCommand
  114. = static_cast<Command_t>(255);
  115. const CommandResponse_t cuMaxCommandResponse
  116. = static_cast<CommandResponse_t>(255);
  117. // This is for mapping requests back to error ids for placing into the database appropriately.
  118. typedef u32 ContextID_t;
  119. // This is the version of the protocol used by latest-build clients.
  120. const ProtocolVersion_t cuCurrentProtocolVersion = 1;
  121. // This is the minimum protocol version number that the client must
  122. // be able to speak in order to communicate with the server.
  123. // The client sends its protocol version this before every command, and if we
  124. // don't support that version anymore then we tell it nicely. The client
  125. // should respond by doing an auto-update.
  126. const ProtocolVersion_t cuRequiredProtocolVersion = 1;
  127. namespace Commands
  128. {
  129. const Command_t cuGracefulClose = 0;
  130. const Command_t cuSendGameStats = 1;
  131. const Command_t cuNumCommands = 2;
  132. const Command_t cuNoCommandReceivedYet = cuMaxCommand;
  133. }
  134. namespace HarvestFileCommand
  135. {
  136. typedef u32 SenderTypeId_t;
  137. typedef u32 SenderTypeUniqueId_t;
  138. typedef u32 SenderSourceCodeControlId_t;
  139. typedef u32 FileSize_t;
  140. // Legal values defined by EFileType
  141. typedef u32 FileType_t;
  142. // Legal values defined by ESendMethod
  143. typedef u32 SendMethod_t;
  144. const CommandResponse_t cuOkToSendFile = 0;
  145. const CommandResponse_t cuFileTooBig = 1;
  146. const CommandResponse_t cuInvalidSendMethod = 2;
  147. const CommandResponse_t cuInvalidMaxCompressedChunkSize = 3;
  148. const CommandResponse_t cuInvalidGameStatsContext = 4;
  149. const uint cuNumCommandResponses = 5;
  150. }
  151. //#############################################################################
  152. //
  153. // Class declaration: CWin32UploadGameStats
  154. //
  155. //#############################################################################
  156. //
  157. // Authors:
  158. //
  159. // Yahn Bernier
  160. //
  161. // Description and general notes:
  162. //
  163. // Handles uploading game stats data blobs to the CSERServer
  164. // (Client Stats & Error Reporting Server)
  165. typedef enum
  166. {
  167. // General status
  168. eGameStatsUploadSucceeded = 0,
  169. eGameStatsUploadFailed,
  170. // Specific status
  171. eGameStatsBadParameter,
  172. eGameStatsUnknownStatus,
  173. eGameStatsSendingGameStatsHeaderSucceeded,
  174. eGameStatsSendingGameStatsHeaderFailed,
  175. eGameStatsReceivingResponseSucceeded,
  176. eGameStatsReceivingResponseFailed,
  177. eGameStatsConnectToCSERServerSucceeded,
  178. eGameStatsConnectToCSERServerFailed,
  179. eGameStatsUploadingGameStatsSucceeded,
  180. eGameStatsUploadingGameStatsFailed
  181. } EGameStatsUploadStatus;
  182. struct TGameStatsProgress
  183. {
  184. // A text string describing the current progress
  185. char m_sStatus[ 512 ];
  186. };
  187. typedef void ( *GAMESTATSREPORTPROGRESSFUNC )( u32 uContext, const TGameStatsProgress & rGameStatsProgress );
  188. struct TGameStatsParameters
  189. {
  190. TGameStatsParameters() :
  191. m_uAppId( 0 )
  192. {
  193. }
  194. // IP Address of the CSERServer to send the report to
  195. netadr_t m_ipCSERServer;
  196. // Source Control Id (or build_number) of the product
  197. u32 m_uEngineBuildNumber;
  198. // Name of the .exe
  199. char m_sExecutableName[ 64 ];
  200. // Game directory
  201. char m_sGameDirectory[ 64 ];
  202. // Map name the server wants to upload statistics about
  203. char m_sMapName[ 64 ];
  204. // Version id for stats blob
  205. u32 m_uStatsBlobVersion;
  206. u32 m_uStatsBlobSize;
  207. void *m_pStatsBlobData;
  208. u32 m_uProgressContext;
  209. GAMESTATSREPORTPROGRESSFUNC m_pOptionalProgressFunc;
  210. u32 m_uAppId;
  211. };
  212. // Note that this API is blocking, though the callback, if passed, can occur during execution.
  213. EGameStatsUploadStatus Win32UploadGameStatsBlocking
  214. (
  215. const TGameStatsParameters & rGameStatsParameters // Input
  216. );
  217. class CUploadGameStats : public IUploadGameStats
  218. {
  219. public:
  220. #define GAMESTATSUPLOADER_CONNECT_RETRY_TIME 1.0
  221. //-----------------------------------------------------------------------------
  222. // Purpose: Initializes the connection to the CSER
  223. //-----------------------------------------------------------------------------
  224. void InitConnection( void )
  225. {
  226. m_bConnected = false;
  227. m_Adr.Clear();
  228. m_Adr.SetType( NA_IP );
  229. m_flNextConnectAttempt = 0;
  230. // don't call UpdateConnection here, does bad things
  231. }
  232. void UpdateConnection( void )
  233. {
  234. #ifndef DEDICATED
  235. if ( m_bConnected )
  236. return;
  237. // try getting client SteamUtils interface
  238. ISteamUtils *pSteamUtils = NULL;
  239. #if !defined( DEDICATED )
  240. pSteamUtils = Steam3Client().SteamUtils();
  241. #endif
  242. // if that fails, try the game server SteamUtils interface
  243. if ( !pSteamUtils )
  244. {
  245. pSteamUtils = Steam3Server().SteamGameServerUtils();
  246. }
  247. // can't determine CSER if Steam not running
  248. if ( !pSteamUtils )
  249. return;
  250. float curTime = Sys_FloatTime();
  251. if ( curTime < m_flNextConnectAttempt )
  252. return;
  253. uint32 unIP = 0;
  254. uint16 usPort = 0;
  255. pSteamUtils->GetCSERIPPort( &unIP, &usPort );
  256. if ( unIP == 0 )
  257. {
  258. m_flNextConnectAttempt = curTime + GAMESTATSUPLOADER_CONNECT_RETRY_TIME;
  259. return;
  260. }
  261. else
  262. {
  263. m_Adr.SetIP( unIP );
  264. m_Adr.SetPort( usPort );
  265. m_Adr.SetType( NA_IP );
  266. m_bConnected = true;
  267. }
  268. #endif // DEDICATED
  269. }
  270. virtual bool UploadGameStats( char const *mapname,
  271. unsigned int blobversion, unsigned int blobsize, const void *pvBlobData )
  272. {
  273. // Attempt connection, for backwards compatibility
  274. UpdateConnection();
  275. if ( !m_bConnected )
  276. return false;
  277. unsigned int useAppId = GetSteamAppID();
  278. if ( useAppId == 0 )
  279. return false;
  280. TGameStatsParameters params;
  281. Q_memset( &params, 0, sizeof( params ) );
  282. params.m_ipCSERServer = m_Adr;
  283. params.m_uEngineBuildNumber = build_number();
  284. Q_strncpy( params.m_sExecutableName, "hl2.exe", sizeof( params.m_sExecutableName ) );
  285. Q_FileBase( com_gamedir, params.m_sGameDirectory, sizeof( params.m_sGameDirectory ) );
  286. Q_FileBase( mapname, params.m_sMapName, sizeof( params.m_sMapName ) );
  287. params.m_uStatsBlobVersion = blobversion;
  288. params.m_uStatsBlobSize = blobsize;
  289. params.m_pStatsBlobData = ( void * )pvBlobData;
  290. ////////////////////////////////////////////////////////////////////////////
  291. // New protocol sorts things by Steam AppId (4/6/06 ywb)
  292. params.m_uAppId = useAppId;
  293. ////////////////////////////////////////////////////////////////////////////
  294. EGameStatsUploadStatus result = Win32UploadGameStatsBlocking( params );
  295. return ( result == eGameStatsUploadSucceeded ) ? true : false;
  296. }
  297. // If user has disabled stats tracking, do nothing
  298. virtual bool IsGameStatsLoggingEnabled()
  299. {
  300. if ( CommandLine()->FindParm( "-nogamestats" ) )
  301. return false;
  302. #ifdef DEDICATED
  303. return true;
  304. #else
  305. IRegistry *temp = InstanceRegistry( "Steam" );
  306. Assert( temp );
  307. // Check registry
  308. int iDisable = temp->ReadInt( "DisableGameStats", 0 );
  309. ReleaseInstancedRegistry( temp );
  310. if ( iDisable != 0 )
  311. {
  312. return false;
  313. }
  314. return true;
  315. #endif
  316. }
  317. // Gets a non-personally identifiable unique ID for this steam user, used for tracking total gameplay time across
  318. // multiple stats sessions, but isn't trackable back to their Steam account or id.
  319. // Buffer should be 16 bytes, ID will come back as a hexadecimal string version of a GUID
  320. virtual void GetPseudoUniqueId( char *buf, size_t bufsize )
  321. {
  322. Q_memset( buf, 0, bufsize );
  323. Q_strncpy( buf, "unknown", bufsize );
  324. #ifndef DEDICATED
  325. // If running at Valve, copy in the users name here
  326. if ( Steam3Client().SteamUtils() && ( Steam3Client().SteamUser()->BLoggedOn() ) && ( k_EUniverseBeta == Steam3Client().SteamUtils()->GetConnectedUniverse() ) )
  327. {
  328. bool bOk = true;
  329. char username[ 64 ] = {0};
  330. #if defined( _WIN32 )
  331. Q_memset( username, 0, sizeof( username ) );
  332. DWORD length = sizeof( username ) - 1;
  333. if ( !GetUserName( username, &length ) )
  334. {
  335. bOk = false;
  336. }
  337. #elif defined( _GAMECONSOLE )
  338. Q_strncpy( username, "console", sizeof( username ) );
  339. #else
  340. struct passwd *pass = getpwuid( getuid() );
  341. if ( pass )
  342. {
  343. Q_strncpy( username, pass->pw_name, sizeof( username ) );
  344. }
  345. else
  346. {
  347. bOk = false;
  348. }
  349. #endif
  350. // we have a valid user name (on Windows) or password (Not Windows)
  351. if ( bOk )
  352. {
  353. username[sizeof(username)-1] = '\0';
  354. Q_strncpy( buf, username, bufsize );
  355. }
  356. else
  357. {
  358. // For Linux dedicated servers, where we won't get a unique ID: set the ID to "unknown" so we have something. (If there's no ID,
  359. // stats don't get sent.) This will later get altered to be a hash of IP&port, but this gets called early before IP is determined
  360. // so we can't make the hash now.
  361. Q_strncpy( buf, "unknown", bufsize );
  362. }
  363. }
  364. // customer so generate pseudo name
  365. else
  366. {
  367. IRegistry *temp = InstanceRegistry( "Steam" );
  368. Assert( temp );
  369. // Check registry
  370. char const *uuid = temp->ReadString( "PseudoUUID", "" );
  371. if ( !uuid || !*uuid )
  372. {
  373. // Create a new one
  374. #ifdef WIN32
  375. UUID newId;
  376. UuidCreate( &newId );
  377. #elif defined(OSX)
  378. uuid_t newId;
  379. uuid_generate( newId );
  380. #else
  381. char newId[32] = {0}; // TODO: add platform-specific UUID generation
  382. #endif
  383. char hex[ 17 ];
  384. Q_memset( hex, 0, sizeof( hex ) );
  385. Q_binarytohex( (const byte *)&newId, sizeof( newId ), hex, sizeof( hex ) );
  386. temp->WriteString( "PseudoUUID", hex );
  387. Q_strncpy( buf, hex, bufsize );
  388. }
  389. else
  390. {
  391. Q_strncpy( buf, uuid, bufsize );
  392. }
  393. ReleaseInstancedRegistry( temp );
  394. if ( ( buf[0] == 0 ) && sv.IsDedicated() )
  395. {
  396. // For Linux dedicated servers, where we won't get a unique ID: set the ID to "unknown" so we have something. (If there's no ID,
  397. // stats don't get sent.) This will later get altered to be a hash of IP&port, but this gets called early before IP is determined
  398. // so we can't make the hash now.
  399. Q_strncpy( buf, "unknown", bufsize );
  400. }
  401. }
  402. #endif
  403. if ( ( buf[0] == 0 ) && sv.IsDedicated() )
  404. {
  405. // For Linux dedicated servers, where we won't get a unique ID: set the ID to "unknown" so we have something. (If there's no ID,
  406. // stats don't get sent.) This will later get altered to be a hash of IP&port, but this gets called early before IP is determined
  407. // so we can't make the hash now.
  408. Q_strncpy( buf, "unknown", bufsize );
  409. }
  410. }
  411. virtual bool IsCyberCafeUser( void )
  412. {
  413. // TODO: convert this to be aware of proper Steam3'ified cafes once we actually implement that
  414. return false;
  415. }
  416. // Only works in single player
  417. virtual bool IsHDREnabled( void )
  418. {
  419. #if defined( DEDICATED ) || defined( _X360 )
  420. return false;
  421. #else
  422. return g_pMaterialSystemHardwareConfig->GetHDREnabled();
  423. #endif
  424. }
  425. private:
  426. netadr_t m_Adr;
  427. float m_flNextConnectAttempt;
  428. bool m_bConnected;
  429. };
  430. static CUploadGameStats g_UploadGameStats;
  431. IUploadGameStats *g_pUploadGameStats = &g_UploadGameStats;
  432. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CUploadGameStats, IUploadGameStats, INTERFACEVERSION_UPLOADGAMESTATS, g_UploadGameStats );
  433. void UpdateProgress( const TGameStatsParameters & params, char const *fmt, ... )
  434. {
  435. if ( !params.m_pOptionalProgressFunc )
  436. {
  437. return;
  438. }
  439. char str[ 2048 ];
  440. va_list argptr;
  441. va_start( argptr, fmt );
  442. _vsnprintf( str, sizeof( str ) - 1, fmt, argptr );
  443. va_end( argptr );
  444. char outstr[ 2060 ];
  445. Q_snprintf( outstr, sizeof( outstr ), "(%u): %s", params.m_uProgressContext, str );
  446. TGameStatsProgress progress;
  447. Q_strncpy( progress.m_sStatus, outstr, sizeof( progress.m_sStatus ) );
  448. // Invoke the callback
  449. ( *params.m_pOptionalProgressFunc )( params.m_uProgressContext, progress );
  450. }
  451. class CWin32UploadGameStats
  452. {
  453. public:
  454. explicit CWin32UploadGameStats(
  455. const netadr_t & harvester,
  456. const TGameStatsParameters & rGameStatsParameters,
  457. u32 contextid );
  458. ~CWin32UploadGameStats();
  459. EGameStatsUploadStatus Upload( CUtlBuffer& buf );
  460. private:
  461. enum States
  462. {
  463. eCreateTCPSocket = 0,
  464. eConnectToHarvesterServer,
  465. eSendProtocolVersion,
  466. eReceiveProtocolOkay,
  467. eSendUploadCommand,
  468. eReceiveOKToSendFile,
  469. eSendWholeFile, // This could push chunks onto the wire, but we'll just use a whole buffer for now.
  470. eReceiveFileUploadSuccess,
  471. eSendGracefulClose,
  472. eCloseTCPSocket
  473. };
  474. bool CreateTCPSocket( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  475. bool ConnectToHarvesterServer( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  476. bool SendProtocolVersion( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  477. bool ReceiveProtocolOkay( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  478. bool SendUploadCommand( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  479. bool ReceiveOKToSendFile( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  480. bool SendWholeFile( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  481. bool ReceiveFileUploadSuccess( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  482. bool SendGracefulClose( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  483. bool CloseTCPSocket( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  484. typedef bool ( CWin32UploadGameStats::*pfnProtocolStateHandler )( EGameStatsUploadStatus& status, CUtlBuffer& buf );
  485. struct FSMState_t
  486. {
  487. FSMState_t( uint f, pfnProtocolStateHandler s ) :
  488. first( f ),
  489. second( s )
  490. {
  491. }
  492. uint first;
  493. pfnProtocolStateHandler second;
  494. };
  495. void AddState( uint StateIndex, pfnProtocolStateHandler handler );
  496. void SetNextState( uint StateIndex );
  497. bool DoBlockingReceive( uint bytesExpected, CUtlBuffer& buf );
  498. CUtlVector< FSMState_t > m_States;
  499. uint m_uCurrentState;
  500. struct sockaddr_in m_HarvesterSockAddr;
  501. uint m_SocketTCP;
  502. const TGameStatsParameters &m_rCrashParameters; //lint !e1725
  503. u32 m_ContextID;
  504. };
  505. CWin32UploadGameStats::CWin32UploadGameStats(
  506. const netadr_t & harvester,
  507. const TGameStatsParameters & rGameStatsParameters,
  508. u32 contextid ) :
  509. m_States(),
  510. m_uCurrentState( eCreateTCPSocket ),
  511. m_HarvesterSockAddr(),
  512. m_SocketTCP( 0 ),
  513. m_rCrashParameters( rGameStatsParameters ),
  514. m_ContextID( contextid )
  515. {
  516. harvester.ToSockadr( (struct sockaddr *)&m_HarvesterSockAddr );
  517. AddState( eCreateTCPSocket, &CWin32UploadGameStats::CreateTCPSocket );
  518. AddState( eConnectToHarvesterServer, &CWin32UploadGameStats::ConnectToHarvesterServer );
  519. AddState( eSendProtocolVersion, &CWin32UploadGameStats::SendProtocolVersion );
  520. AddState( eReceiveProtocolOkay, &CWin32UploadGameStats::ReceiveProtocolOkay );
  521. AddState( eSendUploadCommand, &CWin32UploadGameStats::SendUploadCommand );
  522. AddState( eReceiveOKToSendFile, &CWin32UploadGameStats::ReceiveOKToSendFile );
  523. AddState( eSendWholeFile, &CWin32UploadGameStats::SendWholeFile );
  524. AddState( eReceiveFileUploadSuccess, &CWin32UploadGameStats::ReceiveFileUploadSuccess );
  525. AddState( eSendGracefulClose, &CWin32UploadGameStats::SendGracefulClose );
  526. AddState( eCloseTCPSocket, &CWin32UploadGameStats::CloseTCPSocket );
  527. }
  528. CWin32UploadGameStats::~CWin32UploadGameStats()
  529. {
  530. if ( m_SocketTCP != 0 )
  531. {
  532. closesocket( m_SocketTCP ); //lint !e534
  533. m_SocketTCP = 0;
  534. }
  535. }
  536. //-----------------------------------------------------------------------------
  537. //
  538. // Function: DoBlockingReceive()
  539. //
  540. //-----------------------------------------------------------------------------
  541. bool CWin32UploadGameStats::DoBlockingReceive( uint bytesExpected, CUtlBuffer& buf )
  542. {
  543. uint totalReceived = 0;
  544. buf.Purge();
  545. for ( ;; )
  546. {
  547. char temp[ 8192 ];
  548. int bytesReceived = recv( m_SocketTCP, temp, sizeof( temp ), 0 );
  549. if ( bytesReceived <= 0 )
  550. return false;
  551. buf.Put( ( const void * )temp, (u32)bytesReceived );
  552. totalReceived = buf.TellPut();
  553. if ( totalReceived >= bytesExpected )
  554. break;
  555. }
  556. return true;
  557. }
  558. void CWin32UploadGameStats::AddState( uint StateIndex, pfnProtocolStateHandler handler )
  559. {
  560. FSMState_t newState( StateIndex, handler );
  561. m_States.AddToTail( newState );
  562. }
  563. EGameStatsUploadStatus CWin32UploadGameStats::Upload( CUtlBuffer& buf )
  564. {
  565. UpdateProgress( m_rCrashParameters, "Commencing game stats upload connection." );
  566. EGameStatsUploadStatus result = eGameStatsUploadSucceeded;
  567. // Run the state machine
  568. while ( 1 )
  569. {
  570. Assert( m_States[ m_uCurrentState ].first == m_uCurrentState );
  571. pfnProtocolStateHandler handler = m_States[ m_uCurrentState ].second;
  572. if ( !(this->*handler)( result, buf ) )
  573. {
  574. return result;
  575. }
  576. }
  577. }
  578. void CWin32UploadGameStats::SetNextState( uint StateIndex )
  579. {
  580. Assert( StateIndex > m_uCurrentState );
  581. m_uCurrentState = StateIndex;
  582. }
  583. bool CWin32UploadGameStats::CreateTCPSocket( EGameStatsUploadStatus& status, CUtlBuffer& /*buf*/ )
  584. {
  585. UpdateProgress( m_rCrashParameters, "Creating game stats upload socket." );
  586. m_SocketTCP = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
  587. if ( m_SocketTCP == (uint)SOCKET_ERROR )
  588. {
  589. UpdateProgress( m_rCrashParameters, "Socket creation failed." );
  590. status = eGameStatsUploadFailed;
  591. return false;
  592. }
  593. SetNextState( eConnectToHarvesterServer );
  594. return true;
  595. }
  596. bool CWin32UploadGameStats::ConnectToHarvesterServer( EGameStatsUploadStatus& status, CUtlBuffer& /*buf*/ )
  597. {
  598. UpdateProgress( m_rCrashParameters, "Connecting to game stats harvesting server." );
  599. if ( connect( m_SocketTCP, (const sockaddr *)&m_HarvesterSockAddr, sizeof( m_HarvesterSockAddr ) ) == SOCKET_ERROR )
  600. {
  601. UpdateProgress( m_rCrashParameters, "Connection failed." );
  602. status = eGameStatsConnectToCSERServerFailed;
  603. return false;
  604. }
  605. SetNextState( eSendProtocolVersion );
  606. return true;
  607. }
  608. bool CWin32UploadGameStats::SendProtocolVersion( EGameStatsUploadStatus& status, CUtlBuffer& buf )
  609. {
  610. UpdateProgress( m_rCrashParameters, "Sending game stats harvester protocol info." );
  611. buf.SetBigEndian( true );
  612. // Send protocol version
  613. buf.Purge();
  614. buf.PutInt( cuCurrentProtocolVersion );
  615. if ( send( m_SocketTCP, (const char *)buf.Base(), (int)buf.TellPut(), 0 ) == SOCKET_ERROR )
  616. {
  617. UpdateProgress( m_rCrashParameters, "Send failed." );
  618. status = eGameStatsUploadFailed;
  619. return false;
  620. }
  621. SetNextState( eReceiveProtocolOkay );
  622. return true;
  623. }
  624. bool CWin32UploadGameStats::ReceiveProtocolOkay( EGameStatsUploadStatus& status, CUtlBuffer& buf )
  625. {
  626. UpdateProgress( m_rCrashParameters, "Receiving harvesting protocol acknowledgement." );
  627. buf.Purge();
  628. // Now receive the protocol is acceptable token from the server
  629. if ( !DoBlockingReceive( 1, buf ) )
  630. {
  631. UpdateProgress( m_rCrashParameters, "Didn't receive protocol failure data." );
  632. status = eGameStatsUploadFailed;
  633. return false;
  634. }
  635. bool protocolokay = buf.GetChar() ? true : false;
  636. if ( !protocolokay )
  637. {
  638. UpdateProgress( m_rCrashParameters, "Server rejected protocol." );
  639. status = eGameStatsUploadFailed;
  640. return false;
  641. }
  642. UpdateProgress( m_rCrashParameters, "Protocol OK." );
  643. SetNextState( eSendUploadCommand );
  644. return true;
  645. }
  646. bool CWin32UploadGameStats::SendUploadCommand( EGameStatsUploadStatus& status, CUtlBuffer& buf )
  647. {
  648. UpdateProgress( m_rCrashParameters, "Sending harvesting protocol upload request." );
  649. // Send upload command
  650. buf.Purge();
  651. NetworkMessageLengthPrefix_t messageSize
  652. (
  653. sizeof( Command_t )
  654. + sizeof( ContextID_t )
  655. + sizeof( HarvestFileCommand::FileSize_t )
  656. + sizeof( HarvestFileCommand::SendMethod_t )
  657. + sizeof( HarvestFileCommand::FileSize_t )
  658. );
  659. // Prefix the length to the command
  660. buf.PutInt( (int)messageSize );
  661. buf.PutChar( Commands::cuSendGameStats );
  662. buf.PutInt( (int)m_ContextID );
  663. buf.PutInt( (int)m_rCrashParameters.m_uStatsBlobSize );
  664. buf.PutInt( static_cast<HarvestFileCommand::SendMethod_t>( eSendMethodWholeRawFileNoBlocks ) );
  665. buf.PutInt( static_cast<HarvestFileCommand::FileSize_t>( 0 ) );
  666. // Send command to server
  667. if ( send( m_SocketTCP, (const char *)buf.Base(), (int)buf.TellPut(), 0 ) == SOCKET_ERROR )
  668. {
  669. UpdateProgress( m_rCrashParameters, "Send failed." );
  670. status = eGameStatsUploadFailed;
  671. return false;
  672. }
  673. SetNextState( eReceiveOKToSendFile );
  674. return true;
  675. }
  676. bool CWin32UploadGameStats::ReceiveOKToSendFile( EGameStatsUploadStatus& status, CUtlBuffer& buf )
  677. {
  678. UpdateProgress( m_rCrashParameters, "Receive game stats harvesting protocol upload permissible." );
  679. // Now receive the protocol is acceptable token from the server
  680. if ( !DoBlockingReceive( 1, buf ) )
  681. {
  682. UpdateProgress( m_rCrashParameters, "Receive failed." );
  683. status = eGameStatsUploadFailed;
  684. return false;
  685. }
  686. bool dosend = false;
  687. CommandResponse_t cmd = (CommandResponse_t)buf.GetChar();
  688. switch ( cmd )
  689. {
  690. case HarvestFileCommand::cuOkToSendFile:
  691. {
  692. dosend = true;
  693. }
  694. break;
  695. case HarvestFileCommand::cuFileTooBig:
  696. case HarvestFileCommand::cuInvalidSendMethod:
  697. case HarvestFileCommand::cuInvalidMaxCompressedChunkSize:
  698. case HarvestFileCommand::cuInvalidGameStatsContext:
  699. default:
  700. break;
  701. }
  702. if ( !dosend )
  703. {
  704. UpdateProgress( m_rCrashParameters, "Server rejected upload command." );
  705. status = eGameStatsUploadFailed;
  706. return false;
  707. }
  708. SetNextState( eSendWholeFile );
  709. return true;
  710. }
  711. bool CWin32UploadGameStats::SendWholeFile( EGameStatsUploadStatus& status, CUtlBuffer& /*buf*/ )
  712. {
  713. UpdateProgress( m_rCrashParameters, "Uploading game stats data." );
  714. // Send to server
  715. bool bret = true;
  716. if ( send( m_SocketTCP, (const char *)m_rCrashParameters.m_pStatsBlobData, (int)m_rCrashParameters.m_uStatsBlobSize, 0 ) == SOCKET_ERROR )
  717. {
  718. bret = false;
  719. UpdateProgress( m_rCrashParameters, "Send failed." );
  720. status = eGameStatsUploadFailed;
  721. }
  722. else
  723. {
  724. SetNextState( eReceiveFileUploadSuccess );
  725. }
  726. return bret;
  727. }
  728. bool CWin32UploadGameStats::ReceiveFileUploadSuccess( EGameStatsUploadStatus& status, CUtlBuffer& buf )
  729. {
  730. UpdateProgress( m_rCrashParameters, "Receiving game stats upload success/fail message." );
  731. // Now receive the protocol is acceptable token from the server
  732. if ( !DoBlockingReceive( 1, buf ) )
  733. {
  734. UpdateProgress( m_rCrashParameters, "Receive failed." );
  735. status = eGameStatsUploadFailed;
  736. return false;
  737. }
  738. bool success = buf.GetChar() == 1 ? true : false;
  739. if ( !success )
  740. {
  741. UpdateProgress( m_rCrashParameters, "Upload failed." );
  742. status = eGameStatsUploadFailed;
  743. return false;
  744. }
  745. UpdateProgress( m_rCrashParameters, "Upload OK." );
  746. SetNextState( eSendGracefulClose );
  747. return true;
  748. }
  749. bool CWin32UploadGameStats::SendGracefulClose( EGameStatsUploadStatus& status, CUtlBuffer& buf )
  750. {
  751. UpdateProgress( m_rCrashParameters, "Closing connection to server." );
  752. // Now send disconnect command
  753. buf.Purge();
  754. size_t messageSize = sizeof( Command_t );
  755. buf.PutInt( (int)messageSize );
  756. buf.PutChar( Commands::cuGracefulClose );
  757. if ( send( m_SocketTCP, (const char *)buf.Base(), (int)buf.TellPut(), 0 ) == SOCKET_ERROR )
  758. {
  759. UpdateProgress( m_rCrashParameters, "Send failed." );
  760. status = eGameStatsUploadFailed;
  761. return false;
  762. }
  763. SetNextState( eCloseTCPSocket );
  764. return true;
  765. }
  766. bool CWin32UploadGameStats::CloseTCPSocket( EGameStatsUploadStatus& status, CUtlBuffer& /*buf*/ )
  767. {
  768. UpdateProgress( m_rCrashParameters, "Closing socket, upload succeeded." );
  769. closesocket( m_SocketTCP );//lint !e534
  770. m_SocketTCP = 0;
  771. status = eGameStatsUploadSucceeded;
  772. // NOTE: Returning false here ends the state machine!!!
  773. return false;
  774. }
  775. EGameStatsUploadStatus Win32UploadGameStatsBlocking
  776. (
  777. const TGameStatsParameters & rGameStatsParameters
  778. )
  779. {
  780. #ifdef _PS3
  781. return eGameStatsUploadFailed;
  782. #else
  783. EGameStatsUploadStatus status = eGameStatsUploadSucceeded;
  784. CUtlBuffer buf( rGameStatsParameters.m_uStatsBlobSize + 4096 );
  785. UpdateProgress( rGameStatsParameters, "Creating initial report." );
  786. buf.SetBigEndian( false );
  787. buf.Purge();
  788. buf.PutChar( C2M_REPORT_GAMESTATISTICS );
  789. buf.PutChar( '\n' );
  790. buf.PutChar( C2M_REPORT_GAMESTATISTICS_PROTOCOL_VERSION );
  791. // See CSERServerProtocol.h for format
  792. if ( 0 ) // This is the old protocol
  793. {
  794. buf.PutInt( (int)rGameStatsParameters.m_uEngineBuildNumber );
  795. buf.PutString( rGameStatsParameters.m_sExecutableName ); // exe name
  796. buf.PutString( rGameStatsParameters.m_sGameDirectory ); // gamedir
  797. buf.PutString( rGameStatsParameters.m_sMapName );
  798. buf.PutInt( (int)rGameStatsParameters.m_uStatsBlobVersion ); // game stats blob version
  799. buf.PutInt( (int)rGameStatsParameters.m_uStatsBlobSize ); // game stats blob size
  800. }
  801. else
  802. {
  803. buf.PutInt( (int)rGameStatsParameters.m_uAppId );
  804. buf.PutInt( (int)rGameStatsParameters.m_uStatsBlobSize ); // game stats blob size
  805. }
  806. CBlockingUDPSocket bcs;
  807. if ( !bcs.IsValid() )
  808. {
  809. return eGameStatsUploadFailed;
  810. }
  811. struct sockaddr_in sa;
  812. rGameStatsParameters.m_ipCSERServer.ToSockadr( (struct sockaddr *)&sa );
  813. UpdateProgress( rGameStatsParameters, "Sending game stats to server %s.", rGameStatsParameters.m_ipCSERServer.ToString() );
  814. bcs.SendSocketMessage( sa, (const u8 *)buf.Base(), buf.TellPut() ); //lint !e534
  815. UpdateProgress( rGameStatsParameters, "Waiting for response." );
  816. if ( bcs.WaitForMessage( 2.0f ) )
  817. {
  818. UpdateProgress( rGameStatsParameters, "Received response." );
  819. struct sockaddr_in replyaddress;
  820. buf.EnsureCapacity( 4096 );
  821. uint bytesReceived = bcs.ReceiveSocketMessage( &replyaddress, (u8 *)buf.Base(), 4096 );
  822. if ( bytesReceived > 0 )
  823. {
  824. // Fixup actual size
  825. buf.SeekPut( CUtlBuffer::SEEK_HEAD, bytesReceived );
  826. UpdateProgress( rGameStatsParameters, "Checking response." );
  827. // Parse out data
  828. u8 msgtype = (u8)buf.GetChar();
  829. if ( M2C_ACKREPORT_GAMESTATISTICS != msgtype )
  830. {
  831. UpdateProgress( rGameStatsParameters, "Request denied, invalid message type." );
  832. return eGameStatsSendingGameStatsHeaderFailed;
  833. }
  834. bool validProtocol = (u8)buf.GetChar() == 1 ? true : false;
  835. if ( !validProtocol )
  836. {
  837. UpdateProgress( rGameStatsParameters, "Request denied, invalid message protocol." );
  838. return eGameStatsSendingGameStatsHeaderFailed;
  839. }
  840. u8 disposition = (u8)buf.GetChar();
  841. if ( GS_UPLOAD_REQESTED != disposition )
  842. {
  843. // Server doesn't want a gamestats, oh well
  844. UpdateProgress( rGameStatsParameters, "Stats report accepted, data upload skipped." );
  845. return eGameStatsUploadSucceeded;
  846. }
  847. // Read in the game stats info parameters
  848. u32 harvester_ip = (u32)buf.GetInt();
  849. u16 harvester_port = (u16)buf.GetShort();
  850. u32 dumpcontext = (u32)buf.GetInt();
  851. sockaddr_in adr;
  852. adr.sin_family = AF_INET;
  853. adr.sin_port = htons( harvester_port );
  854. #ifdef _WIN32
  855. adr.sin_addr.S_un.S_addr = harvester_ip;
  856. #elif POSIX
  857. adr.sin_addr.s_addr = harvester_ip;
  858. #endif
  859. netadr_t GameStatsHarvesterFSMIPAddress;
  860. GameStatsHarvesterFSMIPAddress.SetFromSockadr( (struct sockaddr *)&adr );
  861. UpdateProgress( rGameStatsParameters, "Server requested game stats upload to %s.", GameStatsHarvesterFSMIPAddress.ToString() );
  862. // Keep using the same scratch buffer for messaging
  863. CWin32UploadGameStats uploader( GameStatsHarvesterFSMIPAddress, rGameStatsParameters, dumpcontext );
  864. status = uploader.Upload( buf );
  865. }
  866. }
  867. else
  868. {
  869. UpdateProgress( rGameStatsParameters, "No response from server." );
  870. }
  871. return status;
  872. #endif
  873. }
  874. //////////////////////////////////////////////////////////////////////////
  875. //
  876. // Implementation of async uploading
  877. //
  878. #ifdef IS_WINDOWS_PC
  879. class CAsyncUploaderThread
  880. {
  881. public:
  882. CAsyncUploaderThread()
  883. : m_hThread( NULL ) {}
  884. ~CAsyncUploaderThread()
  885. {
  886. if ( m_hThread )
  887. ReleaseThreadHandle( m_hThread );
  888. }
  889. protected:
  890. ThreadHandle_t m_hThread;
  891. CThreadFastMutex m_mtx;
  892. struct DataEntry
  893. {
  894. char const *szMapName;
  895. uint uiBlobVersion;
  896. uint uiBlobSize;
  897. void const *pvBlob;
  898. DataEntry *AllocCopy() const;
  899. void Free() { delete [] ( (char*)this ); }
  900. };
  901. CUtlVector< DataEntry * > m_queue;
  902. enum {
  903. SLEEP_QUEUE_EMPTY = 60 * 1000,
  904. SLEEP_RETRY_UPLOAD = 10 * 1000,
  905. SLEEP_ENTRY_UPLOADED = 10 * 1000,
  906. };
  907. public:
  908. static uintp CallbackThreadProc( void *pvParam ) { reinterpret_cast< CAsyncUploaderThread * >( pvParam )->ThreadProc(); return 0; }
  909. void ThreadProc();
  910. void QueueData( char const *szMapName, uint uiBlobVersion, uint uiBlobSize, const void *pvBlob );
  911. };
  912. static CAsyncUploaderThread g_AsyncUploader;
  913. CAsyncUploaderThread::DataEntry * CAsyncUploaderThread::DataEntry::AllocCopy() const
  914. {
  915. // Find out how much memory we would need
  916. uint lenMapName = ( szMapName ? strlen( szMapName ) : 0 );
  917. uint numBytes = sizeof( DataEntry ) + uiBlobSize + lenMapName + 1;
  918. char *pbData = new char[ numBytes ];
  919. DataEntry *pNew = ( DataEntry * )( pbData );
  920. if ( !pNew )
  921. return NULL;
  922. pNew->uiBlobVersion = uiBlobVersion;
  923. pNew->uiBlobSize = uiBlobSize;
  924. char *pbWriteMapName = ( char * )( pNew + 1 );
  925. pNew->szMapName = pbWriteMapName;
  926. memcpy( pbWriteMapName, szMapName, lenMapName );
  927. pbWriteMapName[ lenMapName ] = 0;
  928. char *pbWriteBlob = pbWriteMapName + lenMapName + 1;
  929. pNew->pvBlob = pbWriteBlob;
  930. memcpy( pbWriteBlob, pvBlob, uiBlobSize );
  931. return pNew;
  932. }
  933. void CAsyncUploaderThread::QueueData( char const *szMapName, uint uiBlobVersion, uint uiBlobSize, const void *pvBlob )
  934. {
  935. // DevMsg( 3, "AsyncUploaderThread: Queue [%.*s]\n", uiBlobSize, pvBlob );
  936. // Prepare for a DataEntry
  937. DataEntry de = { szMapName, uiBlobVersion, uiBlobSize, pvBlob };
  938. if ( DataEntry *pNew = de.AllocCopy() )
  939. {
  940. AUTO_LOCK( m_mtx );
  941. m_queue.AddToTail( pNew );
  942. if ( !m_hThread )
  943. {
  944. m_hThread = CreateSimpleThread( CallbackThreadProc, this );
  945. }
  946. }
  947. }
  948. void CAsyncUploaderThread::ThreadProc()
  949. {
  950. #ifdef _GAMECONSOLE
  951. Assert( !"This is illegal on console" );
  952. DebuggerBreak();
  953. #endif
  954. for ( ; ; )
  955. {
  956. // Fetch an item from queue
  957. DataEntry *pUpload = NULL;
  958. {
  959. AUTO_LOCK( m_mtx );
  960. if ( m_queue.Count() )
  961. {
  962. pUpload = m_queue[0];
  963. m_queue.Remove( 0 );
  964. }
  965. }
  966. // If queue is empty, then sleep
  967. if ( !pUpload )
  968. {
  969. ThreadSleep( SLEEP_QUEUE_EMPTY );
  970. continue;
  971. }
  972. // DevMsg( 3, "AsyncUploaderThread: Uploading [%.*s]\n", pUpload->uiBlobSize, pUpload->pvBlob );
  973. // Attempt to upload the data until successful
  974. bool bSuccess = g_pUploadGameStats->UploadGameStats( pUpload->szMapName, pUpload->uiBlobVersion, pUpload->uiBlobSize, pUpload->pvBlob );
  975. bSuccess;
  976. // After the data entry got uploaded, grab the next one
  977. // DevMsg( 3, "AsyncUploaderThread: Upload finished (status=%d) for data [%.*s]\n", bSuccess, pUpload->uiBlobSize, pUpload->pvBlob );
  978. ThreadSleep( SLEEP_ENTRY_UPLOADED );
  979. pUpload->Free();
  980. }
  981. }
  982. void AsyncUpload_QueueData( char const *szMapName, uint uiBlobVersion, uint uiBlobSize, const void *pvBlob )
  983. {
  984. g_AsyncUploader.QueueData( szMapName, uiBlobVersion, uiBlobSize, pvBlob );
  985. }
  986. #else
  987. void AsyncUpload_QueueData( char const *szMapName, uint uiBlobVersion, uint uiBlobSize, const void *pvBlob )
  988. {
  989. // -- nothing -- g_AsyncUploader.QueueData( szMapName, uiBlobVersion, uiBlobSize, pvBlob );
  990. }
  991. #endif