446 lines
10 KiB
C++
446 lines
10 KiB
C++
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
//=============================================================================//
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
#if !defined( _X360 )
|
||
|
#include <windows.h>
|
||
|
#endif
|
||
|
#elif defined(POSIX)
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <pwd.h>
|
||
|
#include <sys/types.h>
|
||
|
#else
|
||
|
#error
|
||
|
#endif
|
||
|
|
||
|
#include "host.h"
|
||
|
#include "quakedef.h"
|
||
|
#include "net.h"
|
||
|
#include "bitbuf.h"
|
||
|
#include "tier0/icommandline.h"
|
||
|
#include "cserserverprotocol_engine.h"
|
||
|
#include "host_phonehome.h"
|
||
|
#include "mathlib/IceKey.H"
|
||
|
#include "tier0/vcrmode.h"
|
||
|
#include "blockingudpsocket.h"
|
||
|
|
||
|
#if defined( _X360 )
|
||
|
#include "xbox/xbox_win32stubs.h"
|
||
|
#endif
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
#define PHONE_HOME_TIMEOUT 1.5f
|
||
|
#define PHONE_HOME_RETRIES 3
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: returns a pointer to a function, given a module
|
||
|
// Input : pModuleName - module name
|
||
|
// *pName - proc name
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static char const *g_pszExitMsg = "Renderer: Out of memory, message code %i";
|
||
|
|
||
|
class CPhoneHome : public IPhoneHome
|
||
|
{
|
||
|
public:
|
||
|
CPhoneHome() :
|
||
|
m_bPhoneHome( false ),
|
||
|
m_uSessionID( 0 ),
|
||
|
m_pSocket( 0 )
|
||
|
{
|
||
|
Q_memset( &m_cserIP, 0, sizeof( m_cserIP ) );
|
||
|
Q_memset( m_szBuildIdentifier, 0, sizeof( m_szBuildIdentifier ) );
|
||
|
}
|
||
|
|
||
|
virtual void Shutdown()
|
||
|
{
|
||
|
delete m_pSocket;
|
||
|
m_pSocket = NULL;
|
||
|
}
|
||
|
|
||
|
virtual void Init()
|
||
|
{
|
||
|
char build_identifier[ 32 ];
|
||
|
|
||
|
Q_strncpy( build_identifier, "VLV_INTERNAL ", sizeof( build_identifier ) );
|
||
|
int iBI = CommandLine()->FindParm("-bi");
|
||
|
if ( iBI > 0 )
|
||
|
{
|
||
|
if ( (iBI+1) < CommandLine()->ParmCount() )
|
||
|
{
|
||
|
char const *pBuildParam = CommandLine()->GetParm( iBI + 1 );
|
||
|
Q_memset( build_identifier, 0, sizeof( build_identifier ) );
|
||
|
Q_strncpy( build_identifier, pBuildParam, sizeof( build_identifier ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
build_identifier[ 0 ] = '!';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( Q_strlen( build_identifier ) >= 1 &&
|
||
|
Q_strnicmp( build_identifier, "VLV_INTERNAL", Q_strlen( "VLV_INTERNAL" ) ) )
|
||
|
{
|
||
|
// Strip trailing spaces from identifer
|
||
|
char *identifer = &build_identifier[ Q_strlen( build_identifier ) - 1 ];
|
||
|
while ( identifer > build_identifier && *identifer == ' ' )
|
||
|
{
|
||
|
*identifer-- = 0;
|
||
|
}
|
||
|
|
||
|
// FIXME: Don't hardcode CSER ip, get from Steam!!!
|
||
|
if ( NET_StringToAdr( "207.173.177.12:27013", &m_cserIP ) )
|
||
|
{
|
||
|
m_bPhoneHome = true;
|
||
|
|
||
|
Q_strncpy( m_szBuildIdentifier, build_identifier, sizeof( m_szBuildIdentifier ) );
|
||
|
|
||
|
m_pSocket = new CBlockingUDPSocket();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
virtual void Message( byte msgtype, char const *mapname )
|
||
|
{
|
||
|
if ( !m_bPhoneHome )
|
||
|
return;
|
||
|
|
||
|
if ( !m_pSocket )
|
||
|
return;
|
||
|
|
||
|
switch ( msgtype )
|
||
|
{
|
||
|
default:
|
||
|
break;
|
||
|
case PHONE_MSG_ENGINESTART:
|
||
|
if ( !RequestSessionId( m_uSessionID ) )
|
||
|
{
|
||
|
ExitApp();
|
||
|
}
|
||
|
// Note we always return here!!!
|
||
|
return;
|
||
|
case PHONE_MSG_ENGINEEND:
|
||
|
break;
|
||
|
case PHONE_MSG_MAPSTART:
|
||
|
{
|
||
|
if ( m_bLevelStarted )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_bLevelStarted = true;
|
||
|
|
||
|
// Tracker 22394: Don't send map start/finish when building reslists...
|
||
|
if ( CommandLine()->FindParm( "-makereslists" ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case PHONE_MSG_MAPEND:
|
||
|
{
|
||
|
if ( !m_bLevelStarted )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_bLevelStarted = false;
|
||
|
|
||
|
// Tracker 22394: Don't send map start/finish when building reslists...
|
||
|
if ( CommandLine()->FindParm( "-makereslists" ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
SendSessionMessage( msgtype, mapname );
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
|
||
|
void ExitApp()
|
||
|
{
|
||
|
byte msgtype = 212;
|
||
|
Error( g_pszExitMsg, msgtype );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: encrypts an 8-byte sequence
|
||
|
//-----------------------------------------------------------------------------
|
||
|
inline void Encrypt8ByteSequence( IceKey& cipher, const unsigned char *plainText, unsigned char *cipherText)
|
||
|
{
|
||
|
cipher.encrypt(plainText, cipherText);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void EncryptBuffer( IceKey& cipher, unsigned char *bufData, uint bufferSize)
|
||
|
{
|
||
|
unsigned char *cipherText = bufData;
|
||
|
unsigned char *plainText = bufData;
|
||
|
uint bytesEncrypted = 0;
|
||
|
|
||
|
while (bytesEncrypted < bufferSize)
|
||
|
{
|
||
|
// encrypt 8 byte section
|
||
|
Encrypt8ByteSequence( cipher, plainText, cipherText);
|
||
|
bytesEncrypted += 8;
|
||
|
cipherText += 8;
|
||
|
plainText += 8;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void BuildMessage( bf_write& buf, byte msgtype, char const *mapname, unsigned int uSessionID )
|
||
|
{
|
||
|
|
||
|
bf_write encrypted;
|
||
|
ALIGN4 byte encrypted_data[ 2048 ] ALIGN4_POST;
|
||
|
|
||
|
buf.WriteByte( C2M_PHONEHOME );
|
||
|
buf.WriteByte( '\n' );
|
||
|
buf.WriteByte( C2M_PHONEHOME_PROTOCOL_VERSION );
|
||
|
buf.WriteLong( uSessionID ); // sessionid (request new id by sending 0)
|
||
|
|
||
|
// encryption object
|
||
|
IceKey cipher(1); /* medium encryption level */
|
||
|
unsigned char ucEncryptionKey[8] = { 191, 1, 0, 222, 85, 39, 154, 1 };
|
||
|
cipher.set( ucEncryptionKey );
|
||
|
|
||
|
encrypted.StartWriting( encrypted_data, sizeof( encrypted_data ) );
|
||
|
|
||
|
byte corruption_identifier = 0x01;
|
||
|
|
||
|
encrypted.WriteByte( corruption_identifier );
|
||
|
|
||
|
// Data version protocol
|
||
|
encrypted.WriteByte( 1 );
|
||
|
|
||
|
// Write the "build identifier" -- unique to each person we give a build to.
|
||
|
encrypted.WriteString( m_szBuildIdentifier );
|
||
|
{
|
||
|
char computername[ 64 ];
|
||
|
Q_memset( computername, 0, sizeof( computername ) );
|
||
|
#if defined ( _WIN32 )
|
||
|
DWORD length = sizeof( computername ) - 1;
|
||
|
if ( !GetComputerName( computername, &length ) )
|
||
|
{
|
||
|
Q_strncpy( computername, "???", sizeof( computername ) );
|
||
|
}
|
||
|
#else
|
||
|
if ( gethostname( computername, sizeof(computername) ) == -1 )
|
||
|
{
|
||
|
Q_strncpy( computername, "Linux????", sizeof( computername ) );
|
||
|
}
|
||
|
computername[sizeof(computername)-1] = '\0';
|
||
|
#endif
|
||
|
encrypted.WriteString( computername );
|
||
|
}
|
||
|
|
||
|
{
|
||
|
char username[ 64 ];
|
||
|
Q_memset( username, 0, sizeof( username ) );
|
||
|
#if defined ( _WIN32 )
|
||
|
DWORD length = sizeof( username ) - 1;
|
||
|
if ( !GetUserName( username, &length ) )
|
||
|
{
|
||
|
Q_strncpy( username, "???", sizeof( username ) );
|
||
|
}
|
||
|
#else
|
||
|
struct passwd *pass = getpwuid( getuid() );
|
||
|
if ( pass )
|
||
|
{
|
||
|
Q_strncpy( username, pass->pw_name, sizeof( username ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpy( username, "LinuxUser??", sizeof( username ) );
|
||
|
}
|
||
|
username[sizeof(username)-1] = '\0';
|
||
|
#endif
|
||
|
encrypted.WriteString( username );
|
||
|
}
|
||
|
|
||
|
char gamedir[ 64 ];
|
||
|
Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
|
||
|
encrypted.WriteString( gamedir );
|
||
|
|
||
|
unsigned int uBuildNumber = build_number();
|
||
|
|
||
|
encrypted.WriteLong( (int)uBuildNumber );
|
||
|
|
||
|
// WRite timestamp of engine
|
||
|
encrypted.WriteFloat( (float)realtime );
|
||
|
|
||
|
encrypted.WriteByte( msgtype );
|
||
|
if ( mapname != NULL )
|
||
|
{
|
||
|
encrypted.WriteString( mapname );
|
||
|
}
|
||
|
|
||
|
int isDebugUser = ( Sys_IsDebuggerPresent() || CommandLine()->FindParm( "-allowdebug" ) ) ? 1 : 0;
|
||
|
|
||
|
encrypted.WriteByte( isDebugUser );
|
||
|
|
||
|
while ( encrypted.GetNumBytesWritten() % 8 )
|
||
|
{
|
||
|
encrypted.WriteByte( 0 );
|
||
|
}
|
||
|
|
||
|
EncryptBuffer( cipher, (unsigned char *)encrypted.GetData(), encrypted.GetNumBytesWritten() );
|
||
|
|
||
|
buf.WriteShort( (int)encrypted.GetNumBytesWritten() );
|
||
|
buf.WriteBytes( (unsigned char *)encrypted.GetData(), encrypted.GetNumBytesWritten() );
|
||
|
}
|
||
|
|
||
|
void SendSessionMessage( byte msgtype, char const *mapname )
|
||
|
{
|
||
|
if ( m_uSessionID == 0 )
|
||
|
return;
|
||
|
|
||
|
bf_write buf;
|
||
|
ALIGN4 byte data[ 2048 ] ALIGN4_POST;
|
||
|
|
||
|
buf.StartWriting( data, sizeof( data ) );
|
||
|
|
||
|
BuildMessage( buf, msgtype, mapname, m_uSessionID );
|
||
|
|
||
|
struct sockaddr_in sa;
|
||
|
|
||
|
m_cserIP.ToSockadr( (struct sockaddr *)&sa );
|
||
|
|
||
|
m_pSocket->SendSocketMessage( sa, (const byte *)buf.GetData(), buf.GetNumBytesWritten() );
|
||
|
|
||
|
// If we already have a sessionid, don't wait for the server to give us back a new one...
|
||
|
if ( m_uSessionID != 0 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_pSocket->WaitForMessage( PHONE_HOME_TIMEOUT ) )
|
||
|
{
|
||
|
ALIGN4 byte readbuf[ 128 ] ALIGN4_POST;
|
||
|
|
||
|
bf_read replybuf( readbuf, sizeof( readbuf ) );
|
||
|
|
||
|
struct sockaddr_in replyaddress;
|
||
|
uint bytesReceived = m_pSocket->ReceiveSocketMessage( &replyaddress, (byte *)readbuf, sizeof( readbuf ) );
|
||
|
if ( bytesReceived > 0 )
|
||
|
{
|
||
|
// Fixup actual size
|
||
|
replybuf.StartReading( readbuf, bytesReceived );
|
||
|
|
||
|
// Parse out data
|
||
|
byte responseType = (byte)replybuf.ReadByte();
|
||
|
if ( M2C_ACKPHONEHOME == responseType )
|
||
|
{
|
||
|
bool allowPlay = replybuf.ReadByte() == 1 ? true : false;
|
||
|
if ( allowPlay )
|
||
|
{
|
||
|
m_uSessionID = replybuf.ReadLong();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool RequestSessionId( unsigned int& id )
|
||
|
{
|
||
|
id = 0u;
|
||
|
|
||
|
bf_write buf;
|
||
|
ALIGN4 byte data[ 2048 ] ALIGN4_POST;
|
||
|
|
||
|
buf.StartWriting( data, sizeof( data ) );
|
||
|
|
||
|
BuildMessage( buf, PHONE_MSG_ENGINESTART, NULL, id );
|
||
|
|
||
|
struct sockaddr_in sa;
|
||
|
|
||
|
m_cserIP.ToSockadr( (struct sockaddr *)&sa );
|
||
|
|
||
|
for ( int retries = 0; retries < PHONE_HOME_RETRIES; ++retries )
|
||
|
{
|
||
|
m_pSocket->SendSocketMessage( sa, (const byte *)buf.GetData(), buf.GetNumBytesWritten() ); //lint !e534
|
||
|
if ( m_pSocket->WaitForMessage( PHONE_HOME_TIMEOUT ) )
|
||
|
{
|
||
|
ALIGN4 byte readbuf[ 128 ] ALIGN4_POST;
|
||
|
|
||
|
bf_read replybuf( readbuf, sizeof( readbuf ) );
|
||
|
|
||
|
struct sockaddr_in replyaddress;
|
||
|
uint bytesReceived = m_pSocket->ReceiveSocketMessage( &replyaddress, (byte *)readbuf, sizeof( readbuf ) );
|
||
|
if ( bytesReceived > 0 )
|
||
|
{
|
||
|
// Fixup actual size
|
||
|
replybuf.StartReading( readbuf, bytesReceived );
|
||
|
|
||
|
// Parse out data
|
||
|
byte responseType = (byte)replybuf.ReadByte();
|
||
|
if ( M2C_ACKPHONEHOME == responseType )
|
||
|
{
|
||
|
bool allowPlay = replybuf.ReadByte() == 1 ? true : false;
|
||
|
if ( allowPlay )
|
||
|
{
|
||
|
id = replybuf.ReadLong();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// FIXME, this is BS
|
||
|
bool IsExternalBuild()
|
||
|
{
|
||
|
if ( CommandLine()->FindParm( "-publicbuild" ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !CommandLine()->FindParm( "-internalbuild" ) &&
|
||
|
!CommandLine()->CheckParm("-dev") )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// It's an external build...
|
||
|
if ( m_bPhoneHome )
|
||
|
{
|
||
|
if ( !Q_stricmp( m_szBuildIdentifier, "beta-playtest" ) )
|
||
|
{
|
||
|
// Still internal
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool m_bPhoneHome;
|
||
|
netadr_t m_cserIP;
|
||
|
char m_szBuildIdentifier[ 32 ];
|
||
|
bool m_bLevelStarted;
|
||
|
unsigned int m_uSessionID;
|
||
|
|
||
|
CBlockingUDPSocket *m_pSocket;
|
||
|
};
|
||
|
|
||
|
CPhoneHome g_PhoneHome;
|
||
|
|
||
|
IPhoneHome *phonehome = &g_PhoneHome;
|