Files
GTASource/game/system/companion_x64.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

383 lines
9.4 KiB
C++

#if RSG_PC
//--------------------------------------------------------------------------------------
// companion_x64.cpp
//--------------------------------------------------------------------------------------
#include "companion_x64.h"
#if COMPANION_APP
#include "file/tcpip.h"
#include "control/replay/file/FileStorePC.h"
#define LIMIT_DEVICE_PAIRING_TO_ROCKSTARID 1
PARAM(CompanionNoSCNeeded, "Companion won't do filtering based on SocialClub ID");
#define LE_CHR(a,b,c,d) ( ((a)<<24) | ((b)<<16) | ((c)<<8) | (d) )
const int kMessagePayloadSize = 60;
const int kConnectionTimeOutDuration = 5; // this is in seconds
static unsigned char unique_key_ifruit[32] = {
0x45, 0x3e, 0xea, 0x4a, 0x51, 0x53, 0x20, 0x4b,
0xa5, 0x18, 0xc0, 0xb4, 0x49, 0xea, 0x06, 0x00,
0xb7, 0x07, 0xfa, 0x8f, 0xf5, 0xf4, 0x70, 0x72,
0xf4, 0x06, 0xcf, 0x1d, 0x9b, 0xde, 0xf3, 0xc3
};
const unsigned int kMessageHeader = LE_CHR('s', 's', 'p', 'c');
const unsigned int kMessageBroadcastDiscoveryTag = LE_CHR('b', 'c', 'd', 'g');
const unsigned int kMessageBroadcastReplyTag = LE_CHR('b', 'c', 'r', 't');
const unsigned int kMessageDeviceConnectTag = LE_CHR('d', 'c', 't', 'g');
const unsigned int kMessageDeviceDisconnectTag = LE_CHR('d', 'd', 't', 'g');
const unsigned int kMessageDevicePingTag = LE_CHR('d', 'p', 'g', 't');
const unsigned int kMessageGameConnectionAccepted = LE_CHR('g', 'c', 'a', 'c');
const unsigned int kMessageGameConnectionBusy = LE_CHR('g', 'c', 'b', 's');
struct sComMessage
{
u32 m_Header;
u32 m_PayloadSize;
u32 m_MessageTag;
char m_data[kMessagePayloadSize];
};
//////////////////////////////////////////////////////////////////////////
// CPairedDevice
//////////////////////////////////////////////////////////////////////////
CPairedDevice::CPairedDevice(const netSocketAddress& from) : deviceAddress(from)
{
connectionTimeOut = time(NULL);
}
CPairedDevice::~CPairedDevice()
{
}
CPairedDevice& CPairedDevice::operator =(const CPairedDevice& other)
{
deviceAddress = other.deviceAddress;
connectionTimeOut = other.connectionTimeOut;
return *this;
}
//////////////////////////////////////////////////////////////////////////
// CPairingManager
//////////////////////////////////////////////////////////////////////////
bool CPairingManager::init(CCompanionDataPC* comp)
{
// initialize the listening socket - is there to catch any broadcasts for SecondScreen
if (!netHardware::CreateSocket(&listener, kPairingProtocolPort, NET_PROTO_UDP, NET_SOCKET_NONBLOCKING))
{
return false;
}
// initialize our crypto lib
codex = rage_new AES(unique_key_ifruit);
companion = comp;
return true;
}
void CPairingManager::update()
{
if (!listener.CanSendReceive())
{
return;
}
time_t timeNow = time(NULL);
if (listener.IsReceivePending())
{
netSocketAddress from;
netSocketError error;
char buffer[kPairingMaxBufferLen];
int len = listener.Receive(&from, &buffer, kPairingMaxBufferLen, &error);
if (len > 0)
{
buffer[len] = 0;
u32* msgPtr = (u32*)buffer;
// There is nothing to process if this is not for us, the protocol header makes sure
if (*msgPtr++ == kMessageHeader)
{
processIncomingMessages(len - 4, (char*)msgPtr, from);
}
}
}
atArray<CPairedDevice>::iterator it = devices.begin();
while(it != devices.end())
{
if (timeNow - (it)->connectionTimeOut > kConnectionTimeOutDuration)
{
it = devices.erase(it);
if (devices.GetCount() == 0)
{
companion->OnClientDisconnected();
companion->GetHttpServer().shutdown();
}
}
else
{
++it;
}
}
}
void CPairingManager::processIncomingMessages(int len, char* buffer, const netSocketAddress& from)
{
u32* msgPtr = (u32*)buffer;
u32 payloadSize = *msgPtr++;
CPairedDevice* device = getDeviceWithAddress(from);
if (device)
{
u32 msgTag = *msgPtr++;
switch (msgTag)
{
case kMessageDevicePingTag:
{
sComMessage message;
message.m_Header = kMessageHeader;
message.m_PayloadSize = 4;
message.m_MessageTag = kMessageDevicePingTag;
netSocketError error;
listener.Send(from, &message, 12, &error);
device->connectionTimeOut = time(NULL);
}
break;
case kMessageDeviceDisconnectTag:
{
CompanionDisplayf("Received device disconnect msg, from: %s",from.ToString());
// iterate through the connected devices and force the device to be removed on next update
atArray<CPairedDevice>::iterator it = devices.begin();
while(it != devices.end())
{
if ((it)->deviceAddress == from)
{
it = devices.erase(it);
}
else
{
++it;
}
}
// If we have no more devices, disconnect
if (devices.GetCount() == 0)
{
companion->OnClientDisconnected();
companion->GetHttpServer().shutdown();
}
}
break;
default:
break;
}
}
else
{
// we don't have a device in our list with that address,
// all messages not address to a device in a list, are assumed encoded
codex->Decrypt(msgPtr, payloadSize);
u32 msgTag = *msgPtr++;
switch(msgTag)
{
case kMessageBroadcastDiscoveryTag:
{
CompanionDisplayf("Received broadcast discoverability msg, from: %s",from.ToString());
if (payloadSize != 8 && (signed int)payloadSize == (len - 4))
{
// we expectg a payload of 8, anything else and this is not what we agreed!
return;
}
#if LIMIT_DEVICE_PAIRING_TO_ROCKSTARID
char rockstarIdString[20];
sprintf_s(rockstarIdString, 20, "%llu", companion->GetRockstarId());
u32 rIdHash = atStringHash(rockstarIdString,0);
u32 remoteUserIdHash = *msgPtr;
#if !__FINAL
const bool requireSClubId = PARAM_CompanionNoSCNeeded.Get() == false;
if (requireSClubId == true && rIdHash != remoteUserIdHash)
#else
if (rIdHash != remoteUserIdHash)
#endif // !__FINAL
{
return;
}
#endif
sComMessage message;
message.m_Header = kMessageHeader;
message.m_PayloadSize = kMessagePayloadSize;
message.m_MessageTag = kMessageBroadcastReplyTag;
u32 nameLen = kMessagePayloadSize;
GetComputerName(message.m_data, (LPDWORD)&nameLen);
codex->Encrypt(&message.m_MessageTag, kMessagePayloadSize + 4);
netSocketError error;
listener.Send(from, &message, sizeof(message), &error);
}
break;
case kMessageDeviceConnectTag:
{
CompanionDisplayf("Received device connect msg, from: %s",from.ToString());
#if LIMIT_DEVICE_PAIRING_TO_ROCKSTARID
char rockstarIdString[20];
sprintf_s(rockstarIdString, 20, "%llu", companion->GetRockstarId());
u32 rIdHash = atStringHash(rockstarIdString,0);
u32 remoteUserIdHash = *msgPtr;
#if !__FINAL
const bool requireSClubId = PARAM_CompanionNoSCNeeded.Get() == false;
if (requireSClubId == true && rIdHash != remoteUserIdHash)
#else
if (rIdHash != remoteUserIdHash)
#endif // !__FINAL
{
return;
}
#endif
sComMessage message;
message.m_Header = kMessageHeader;
message.m_PayloadSize = 4;
if (devices.GetCount() == 0)
{
companion->GetHttpServer().startListening(from.GetIp());
companion->OnClientConnected();
devices.PushAndGrow(CPairedDevice(from));
message.m_MessageTag = kMessageGameConnectionAccepted;
}
else
{
message.m_MessageTag = kMessageGameConnectionBusy;
}
netSocketError error;
listener.Send(from, &message, 12, &error);
}
break;
default:
CompanionDisplayf("Unrecognized tag was sent over the pairing protocol = %d",msgTag);
break;
}
}
}
CPairedDevice* CPairingManager::getDeviceWithAddress(const netSocketAddress& address)
{
atArray<CPairedDevice>::iterator it = devices.begin();
while(it != devices.end())
{
CPairedDevice* device = &(*it);
if (device->deviceAddress == address)
return device;
++it;
}
return NULL;
}
//--------------------------------------------------------------------------------------
// CCompanionData class
//--------------------------------------------------------------------------------------
CCompanionData* CCompanionData::GetInstance()
{
if (!sm_instance)
{
sm_instance = rage_new CCompanionDataPC();
}
return sm_instance;
}
//--------------------------------------------------------------------------------------
// CCompanionDataPC class
//--------------------------------------------------------------------------------------
CCompanionDataPC::CCompanionDataPC() : CCompanionData(), m_httpServer(kCOMPANION_HTTP_PROTOCOL_PORT), m_rockstarId(0)
{
}
CCompanionDataPC::~CCompanionDataPC()
{
// Call down to base class
CCompanionData::~CCompanionData();
}
void CCompanionDataPC::Initialise()
{
// start our pairing manager
m_pairingMgr.init(this);
m_SCMgr = &CLiveManager::GetSocialClubMgr();
}
void CCompanionDataPC::Update()
{
// Update the base class first
CCompanionData::Update();
if (m_rockstarId == 0 && m_SCMgr->IsConnectedToSocialClub())
{
const rlScAccountInfo* accountInfo = m_SCMgr->GetLocalGamerAccountInfo();
m_rockstarId = accountInfo->m_RockstarId;
}
m_pairingMgr.update();
}
void CCompanionDataPC::OnClientConnected()
{
CompanionDisplayf("CCompanionDataPC::OnClientConnected");
// Call down to base class
CCompanionData::OnClientConnected();
}
void CCompanionDataPC::OnClientDisconnected()
{
CompanionDisplayf("CCompanionDataPC::OnClientDisconnected");
// Call down to base class
CCompanionData::OnClientDisconnected();
}
RockstarId CCompanionDataPC::GetRockstarId()
{
return m_rockstarId;
}
CHttpServer& CCompanionDataPC::GetHttpServer()
{
return m_httpServer;
}
#endif // COMPANION_APP
#endif // RSG_PC