Team Fortress 2 Source Code as on 22/4/2020
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.

1253 lines
34 KiB

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