/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRC.h"
#include "GameNetwork/Transport.h"
#include "GameNetwork/NetworkInterface.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//--------------------------------------------------------------------------
// Packet-level encryption is an XOR operation, for speed reasons. To get
// the max throughput, we only XOR whole 4-byte words, so the last bytes
// can be non-XOR'd.
// This assumes the buf is a multiple of 4 bytes. Extra is not encrypted.
static inline void encryptBuf( unsigned char *buf, Int len )
{
UnsignedInt mask = 0x0000Fade;
UnsignedInt *uintPtr = (UnsignedInt *) (buf);
for (int i=0 ; iBind(ip, port);
}
if (retval != 0) {
DEBUG_CRASH(("Could not bind to 0x%8.8X:%d\n", ip, port));
DEBUG_LOG(("Transport::init - Failure to bind socket with error code %x\n", retval));
delete m_udpsock;
m_udpsock = NULL;
return false;
}
// ------- Clear buffers --------
for (int i=0; im_latencyAverage > 0 || TheGlobalData->m_latencyNoise)
m_useLatency = true;
if (TheGlobalData->m_packetLoss)
m_usePacketLoss = true;
#endif
return true;
}
void Transport::reset( void )
{
if (m_udpsock)
{
delete m_udpsock;
m_udpsock = NULL;
}
if (m_winsockInit)
{
WSACleanup();
m_winsockInit = false;
}
}
Bool Transport::update( void )
{
Bool retval = TRUE;
if (doRecv() == FALSE && m_udpsock && m_udpsock->GetStatus() == UDP::ADDRNOTAVAIL)
{
retval = FALSE;
}
DEBUG_ASSERTLOG(retval, ("WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
if (doSend() == FALSE && m_udpsock && m_udpsock->GetStatus() == UDP::ADDRNOTAVAIL)
{
retval = FALSE;
}
DEBUG_ASSERTLOG(retval, ("WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
return retval;
}
Bool Transport::doSend() {
if (!m_udpsock)
{
DEBUG_LOG(("Transport::doSend() - m_udpSock is NULL!\n"));
return FALSE;
}
Bool retval = TRUE;
// Statistics gathering
UnsignedInt now = timeGetTime();
if (m_lastSecond + 1000 < now)
{
m_lastSecond = now;
m_statisticsSlot = (m_statisticsSlot + 1) % MAX_TRANSPORT_STATISTICS_SECONDS;
m_outgoingPackets[m_statisticsSlot] = 0;
m_outgoingBytes[m_statisticsSlot] = 0;
m_incomingPackets[m_statisticsSlot] = 0;
m_incomingBytes[m_statisticsSlot] = 0;
m_unknownPackets[m_statisticsSlot] = 0;
m_unknownBytes[m_statisticsSlot] = 0;
}
// Send all messages
int i;
for (i=0; iWrite((unsigned char *)(&m_outBuffer[i]), m_outBuffer[i].length + sizeof(TransportMessageHeader), m_outBuffer[i].addr, m_outBuffer[i].port)) > 0)
{
//DEBUG_LOG(("Sending %d bytes to %d:%d\n", m_outBuffer[i].length + sizeof(TransportMessageHeader), m_outBuffer[i].addr, m_outBuffer[i].port));
m_outgoingPackets[m_statisticsSlot]++;
m_outgoingBytes[m_statisticsSlot] += m_outBuffer[i].length + sizeof(TransportMessageHeader);
m_outBuffer[i].length = 0; // Remove from queue
// DEBUG_LOG(("Transport::doSend - sent %d butes to %d.%d.%d.%d:%d\n", bytesSent,
// (m_outBuffer[i].addr >> 24) & 0xff,
// (m_outBuffer[i].addr >> 16) & 0xff,
// (m_outBuffer[i].addr >> 8) & 0xff,
// m_outBuffer[i].addr & 0xff,
// m_outBuffer[i].port));
}
else
{
//DEBUG_LOG(("Could not write to socket!!! Not discarding message!\n"));
retval = FALSE;
//DEBUG_LOG(("Transport::doSend returning FALSE\n"));
}
}
} // for (i=0; iRead(buf, MAX_MESSAGE_LEN, &from)) > 0 )
{
#if defined(_DEBUG) || defined(_INTERNAL)
// Packet loss simulation
if (m_usePacketLoss)
{
if ( TheGlobalData->m_packetLoss >= GameClientRandomValue(0, 100) )
{
continue;
}
}
#endif
// DEBUG_LOG(("Transport::doRecv - Got something! len = %d\n", len));
// Decrypt the packet
// DEBUG_LOG(("buffer = "));
// for (Int munkee = 0; munkee < len; ++munkee) {
// DEBUG_LOG(("%02x", *(buf + munkee)));
// }
// DEBUG_LOG(("\n"));
decryptBuf(buf, len);
incomingMessage.length = len - sizeof(TransportMessageHeader);
if (len <= sizeof(TransportMessageHeader) || !isGeneralsPacket( &incomingMessage ))
{
m_unknownPackets[m_statisticsSlot]++;
m_unknownBytes[m_statisticsSlot] += len;
continue;
}
// Something there; stick it somewhere
// DEBUG_LOG(("Saw %d bytes from %d:%d\n", len, ntohl(from.sin_addr.S_un.S_addr), ntohs(from.sin_port)));
m_incomingPackets[m_statisticsSlot]++;
m_incomingBytes[m_statisticsSlot] += len;
for (int i=0; im_latencyAverage +
(Int)(TheGlobalData->m_latencyAmplitude * sin(now * TheGlobalData->m_latencyPeriod)) +
GameClientRandomValue(-TheGlobalData->m_latencyNoise, TheGlobalData->m_latencyNoise);
m_delayedInBuffer[i].message.length = incomingMessage.length;
m_delayedInBuffer[i].message.addr = ntohl(from.sin_addr.S_un.S_addr);
m_delayedInBuffer[i].message.port = ntohs(from.sin_port);
memcpy(&m_delayedInBuffer[i].message, buf, len);
break;
}
}
else
{
#endif
if (m_inBuffer[i].length == 0)
{
// Empty slot; use it
m_inBuffer[i].length = incomingMessage.length;
m_inBuffer[i].addr = ntohl(from.sin_addr.S_un.S_addr);
m_inBuffer[i].port = ntohs(from.sin_port);
memcpy(&m_inBuffer[i], buf, len);
break;
}
#if defined(_DEBUG) || defined(_INTERNAL)
}
#endif
}
//DEBUG_ASSERTCRASH(i MAX_PACKET_SIZE)
{
return false;
}
for (i=0; ilength < 0 || msg->length > MAX_MESSAGE_LEN)
return false;
CRC crc;
// crc.computeCRC( (unsigned char *)msg->data, msg->length );
crc.computeCRC( (unsigned char *)(&(msg->header.magic)), msg->length + sizeof(TransportMessageHeader) - sizeof(UnsignedInt) );
if (crc.get() != msg->header.crc)
return false;
if (msg->header.magic != GENERALS_MAGIC_NUMBER)
return false;
return true;
}
// Statistics ---------------------------------------------------
Real Transport::getIncomingBytesPerSecond( void )
{
Real val = 0.0;
for (int i=0; i