/* ** Command & Conquer Generals(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