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.
445 lines
10 KiB
445 lines
10 KiB
//========= 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;
|
|
|