CnC_Remastered_Collection/REDALERT/TCPIP.CPP

907 lines
35 KiB
C++

//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
/***************************************************************************
** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S **
***************************************************************************
* *
* Project Name : Command & Conquer - Red Alert *
* *
* File Name : TCPIP.CPP *
* *
* Programmer : Steve Tall *
* *
* Start Date : March 11th, 1996 *
* *
* Last Update : March 20th, 1996 [ST] *
* *
*-------------------------------------------------------------------------*
* Overview: *
* *
* Member functions of the TcpipManagerClass which provides the Winsock *
* interface for C&C *
* *
* *
*-------------------------------------------------------------------------*
* Functions: *
* *
* TMC::TcpipManagerClass -- constructor for the TcpipManagerClass *
* TMC::~TcpipManagerClass -- destructor for the TcpipManagerClass *
* TMC::Close -- restores any currently in use Winsock resources *
* TMC::Init -- Initialised Winsock for use. *
* TMC::Start_Server -- Initialise connection and start listening. *
* TMC::Read -- read any pending input from the stream socket *
* TMC::Write -- Send data via the Winsock streaming socket *
* TMC::Add_Client -- A client has requested to connect. *
* TMC::Message_Handler -- Message handler for Winsock. *
* TMC::Set_Host_Address -- Set the address of the host *
* TMC::Start_Client -- Start trying to connect to a game host *
* TMC::Close_Socket -- Close an opened Winsock socket. *
* TMC::Copy_To_In_Buffer -- copy data from our winsock buffer *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef WIN32
#include "function.h"
#include "tcpip.h"
/*
** Nasty globals
*/
#ifndef WOLAPI_INTEGRATION
bool Server; //Is this player acting as client or server
#endif
TcpipManagerClass Winsock; //The object for interfacing with Winsock
/***********************************************************************************************
* TMC::TcpipManagerClass -- constructor for the TcpipManagerClass *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 2:51PM ST : Created *
*=============================================================================================*/
TcpipManagerClass::TcpipManagerClass(void)
{
WinsockInitialised = FALSE;
Connected = FALSE;
UseUDP = TRUE;
SocketReceiveBuffer = 4096;
SocketSendBuffer = 4096;
}
/***********************************************************************************************
* TMC::~TcpipManagerClass -- destructor for the TcpipManagerClass *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 2:52PM ST : Created *
*=============================================================================================*/
TcpipManagerClass::~TcpipManagerClass(void)
{
Close();
}
/***********************************************************************************************
* TMC::Close -- restores any currently in use Winsock resources *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 2:52PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Close(void)
{
/*
** If we never initialised the class in the first place then just return
*/
if (!WinsockInitialised) return;
/*
** Cancel any outstaning asyncronous events
*/
if (Async){
WSACancelAsyncRequest(Async);
}
/*
** Close any open sockets
*/
if (ConnectSocket != INVALID_SOCKET){
Close_Socket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
}
if (ListenSocket != INVALID_SOCKET){
Close_Socket(ListenSocket);
ListenSocket = INVALID_SOCKET;
}
if (UDPSocket != INVALID_SOCKET){
Close_Socket(ListenSocket);
UDPSocket = INVALID_SOCKET;
}
/*
** Call the Winsock cleanup function to say we are finished using Winsock
*/
WSACleanup();
WinsockInitialised = FALSE;
Connected = FALSE;
}
/***********************************************************************************************
* TMC::Init -- Initialised Winsock for use. *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: TRUE if Winsock is available and was initialised *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 2:54PM ST : Created *
*=============================================================================================*/
BOOL TcpipManagerClass::Init(void)
{
short version;
int rc;
/*
** Just return true if we are already set up
*/
if (WinsockInitialised) return (TRUE);
/*
** Initialise sockets to null
*/
ListenSocket = INVALID_SOCKET;
ConnectSocket =INVALID_SOCKET;
UDPSocket = INVALID_SOCKET;
/*
** Start WinSock, and fill in our WinSockData
*/
version = (WINSOCK_MINOR_VER << 8) | WINSOCK_MAJOR_VER;
rc = WSAStartup(version, &WinsockInfo);
if (rc != 0) {
return (FALSE);
}
/*
** Check the Winsock version number
*/
if ((WinsockInfo.wVersion & 0x00ff) != (version & 0x00ff) ||
(WinsockInfo.wVersion >> 8) != (version >> 8)) {
return (FALSE);
}
/*
** Everything is OK so return success
*/
WinsockInitialised = TRUE;
return (TRUE);
}
/***********************************************************************************************
* TMC::Start_Server -- initialise out connection as the server. Start listening for clients. *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 2:56PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Start_Server(void)
{
int i;
//struct sockaddr_in addr;
Start_Client();
/*
** Set up the incoming and outgoing data buffers head and tail pointers
*/
TXBufferHead = 0;
TXBufferTail = 0;
RXBufferHead = 0;
RXBufferTail = 0;
for (i=0 ; i<WS_NUM_TX_BUFFERS ; i++){
TransmitBuffers[i].InUse = false;
}
for (i=0 ; i<WS_NUM_RX_BUFFERS ; i++){
ReceiveBuffers[i].InUse = false;
}
/*
** Flag that we are the server side not the client
*/
IsServer = TRUE;
UseUDP = TRUE;
ConnectStatus = CONNECTING;
}
/***********************************************************************************************
* TMC::Read -- read any pending input from the stream socket *
* *
* *
* *
* INPUT: ptr to buffer to receive input *
* length of buffer *
* *
* OUTPUT: number of bytes transfered to buffer *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 2:58PM ST : Created *
*=============================================================================================*/
int TcpipManagerClass::Read(void *buffer, int buffer_len)
{
int bytes_copied = 0;
char *dest_buf = (char*) buffer;
/*
** Make sure the message loop gets called because all the Winsock notifications
** are done via messages.
*/
Keyboard->Check();
/*
** Copy any outstanding incoming data to the buffer provided
*/
if (ReceiveBuffers[RXBufferTail].InUse){
memcpy (buffer, ReceiveBuffers[RXBufferTail].Buffer,
MIN(ReceiveBuffers[RXBufferTail].DataLength, buffer_len));
ReceiveBuffers[RXBufferTail].InUse = false;
bytes_copied = MIN(ReceiveBuffers[RXBufferTail++].DataLength, buffer_len);
RXBufferTail &= WS_NUM_RX_BUFFERS-1;
}
return (bytes_copied);
}
/***********************************************************************************************
* TMC::Write -- Send data via the Winsock UDP socket *
* *
* *
* *
* INPUT: ptr to buffer containing data to send *
* length of data to send *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:00PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Write(void *buffer, int buffer_len)
{
char *source_buf = (char*) buffer;
/*
** Copy the data to one of the classes internal buffers
*/
if (!TransmitBuffers[TXBufferHead].InUse){
memcpy (TransmitBuffers[TXBufferHead].Buffer,
buffer,
MIN (buffer_len, WS_INTERNET_BUFFER_LEN));
TransmitBuffers[TXBufferHead].InUse = true;
TransmitBuffers[TXBufferHead++].DataLength = MIN(buffer_len, WS_INTERNET_BUFFER_LEN);
TXBufferHead &= WS_NUM_TX_BUFFERS-1;
}
/*
** Send a message to ourselves to start off the event
*/
if (UseUDP){
SendMessage(MainWindow, WM_UDPASYNCEVENT, 0, (LONG)FD_WRITE);
}else{
SendMessage(MainWindow, WM_ASYNCEVENT, 0, (LONG)FD_WRITE);
}
/*
** Make sure the message loop gets called because all the Winsock notifications
** are done via messages.
*/
Keyboard->Check();
}
/***********************************************************************************************
* TMC::Add_Client -- a client has requested to connect. Make the connection *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: TRUE if client was successfully connected *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:02PM ST : Created *
*=============================================================================================*/
BOOL TcpipManagerClass::Add_Client(void)
{
struct sockaddr_in addr;
int addrsize;
bool delay = TRUE;
/*
** Accept the connection. If there is an error then dont do anything else
*/
addrsize = sizeof(addr);
ConnectSocket = accept (ListenSocket, (LPSOCKADDR)&addr, &addrsize);
if (ConnectSocket == INVALID_SOCKET) {
//Show_Error("accept", WSAGetLastError());
return(FALSE);
}
/*
** Set options for this socket
*/
setsockopt (ConnectSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&delay, 4);
setsockopt (ConnectSocket, SOL_SOCKET, SO_RCVBUF, (char*)&SocketReceiveBuffer, 4);
setsockopt (ConnectSocket, SOL_SOCKET, SO_SNDBUF, (char*)&SocketSendBuffer, 4);
/*
** Save the clients address
*/
memcpy(&ClientIPAddress, &addr.sin_addr.s_addr,4);
memcpy(&UDPIPAddress, &addr.sin_addr.s_addr,4);
/*
** Initiate an asynchronous host lookup by address. Our window will receive notification
** when this is complete or when it times out.
*/
Async = WSAAsyncGetHostByAddr (MainWindow, WM_HOSTBYADDRESS,
(char const *)&addr.sin_addr, 4, PF_INET, &HostBuff[0],
MAXGETHOSTSTRUCT);
/*
** Enable asynchronous events on this socket
*/
if (WSAAsyncSelect (ConnectSocket, MainWindow, WM_ASYNCEVENT,
FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR){
WSACancelAsyncRequest(Async);
Close_Socket (ConnectSocket);
return(FALSE);
}
/*
** Create our UDP socket
*/
UDPSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (UDPSocket == INVALID_SOCKET) {
return (FALSE);
}
/*
** Bind our UDP socket to our UDP port number
*/
addr.sin_family = AF_INET;
addr.sin_port = htons(PlanetWestwoodPortNumber);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(UDPSocket, (LPSOCKADDR)&addr, sizeof(addr)) ==
SOCKET_ERROR) {
Close_Socket(UDPSocket);
ConnectStatus = NOT_CONNECTING;
return(FALSE);
}
/*
** Set options for the UDP socket
*/
setsockopt (UDPSocket, SOL_SOCKET, SO_RCVBUF, (char*)&SocketReceiveBuffer, 4);
setsockopt (UDPSocket, SOL_SOCKET, SO_SNDBUF, (char*)&SocketSendBuffer, 4);
/*
** Enable asynchronous events on this socket
*/
if (WSAAsyncSelect (UDPSocket, MainWindow, WM_UDPASYNCEVENT,
FD_READ | FD_WRITE) == SOCKET_ERROR){
WSACancelAsyncRequest(Async);
Close_Socket (UDPSocket);
Close_Socket (ConnectSocket);
return(FALSE);
}
return (TRUE);
}
/***********************************************************************************************
* TMC::Message_Handler -- Message handler function for Winsock related messages *
* *
* *
* *
* INPUT: Windows message handler stuff *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:05PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Message_Handler(HWND, UINT message, UINT , LONG lParam)
{
struct hostent *hentry;
struct sockaddr_in addr;
int event;
int rc;
int addr_len;
switch (message){
/*
** Handle the GetHostByAddress result
*/
case WM_HOSTBYADDRESS:
if (IsServer){
/*
** We are the server
*/
ConnectStatus = CONNECTING;
if (WSAGETASYNCERROR(lParam)==0) {
hentry = (struct hostent *)&HostBuff[0];
strcpy (&ClientName[0], hentry->h_name);
}
Async = 0;
return;
}else{
/*
** We are the client
*/
ConnectStatus = CONTACTING_SERVER;
if (WSAGETASYNCERROR(lParam)==0) {
hentry = (struct hostent *)&HostBuff[0];
strcpy (Server.Name, hentry->h_name);
}
else {
Server.Name[0] = 0;
}
Async = 0;
return;
}
/*
** Retrieve host by name: Start connecting now that we have the
** address.
*/
case WM_HOSTBYNAME:
if (WSAGETASYNCERROR(lParam)==0) {
hentry = (struct hostent *)&HostBuff[0];
memcpy (&(Server.Addr.s_addr), hentry->h_addr, 4);
memcpy(&UDPIPAddress, hentry->h_addr, 4);
strcpy (Server.DotAddr, inet_ntoa(Server.Addr));
ConnectStatus = CONNECTED_OK;
Connected = TRUE;
}
else {
Server.Name[0] = 0;
strcpy (Server.DotAddr, "????");
ConnectStatus = SERVER_ADDRESS_LOOKUP_FAILED;
}
Async = 0;
return;
/*
** Connection is ready - accept the client
*/
case WM_ACCEPT:
rc = WSAGETSELECTERROR(lParam);
if (rc != 0) {
ConnectStatus = UNABLE_TO_ACCEPT_CLIENT;
return;
}
if (Add_Client()) {
ConnectStatus = CONNECTED_OK;
Connected = TRUE;
}
else {
ConnectStatus = UNABLE_TO_ACCEPT_CLIENT;
}
return;
/*
** Handle UDP packet events
*/
case WM_UDPASYNCEVENT:
event = WSAGETSELECTEVENT(lParam);
switch (event) {
case FD_READ:
rc = WSAGETSELECTERROR(lParam);
if (rc != 0) {
Clear_Socket_Error(UDPSocket);
return;
}
addr_len = sizeof(addr);
rc = recvfrom(UDPSocket, ReceiveBuffer, WS_RECEIVE_BUFFER_LEN, 0,
(LPSOCKADDR)&addr, &addr_len);
if (rc == SOCKET_ERROR) {
Clear_Socket_Error(UDPSocket);
return;
}
memcpy(&UDPIPAddress, &addr.sin_addr.s_addr, 4);
Copy_To_In_Buffer(rc);
return;
case FD_WRITE:
if (UseUDP){
rc = WSAGETSELECTERROR(lParam);
if (rc != 0) {
Clear_Socket_Error(UDPSocket);
return;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PlanetWestwoodPortNumber);
memcpy (&addr.sin_addr.s_addr, &UDPIPAddress, 4);
/*
** Send as many bytes as there are in the buffer; if there's
** an error, just bail out. If we get a WOULDBLOCK error,
** WinSock will send us another message when the socket is
** available for another write.
*/
while (TransmitBuffers[TXBufferTail].InUse){
rc = sendto(UDPSocket,
TransmitBuffers[TXBufferTail].Buffer,
TransmitBuffers[TXBufferTail].DataLength,
0,
(LPSOCKADDR)&addr,
sizeof (addr));
if (rc == SOCKET_ERROR){
if (WSAGetLastError() != WSAEWOULDBLOCK) {
Clear_Socket_Error(UDPSocket);
}
break;
}
TransmitBuffers[TXBufferTail++].InUse = false;
TXBufferTail &= WS_NUM_TX_BUFFERS-1;
}
return;
}
}
/*
** Handle the asynchronous event callbacks
*/
case WM_ASYNCEVENT:
event = WSAGETSELECTEVENT(lParam);
switch (event) {
/*
** FD_CLOSE: the client has gone away. Remove the client from our system.
*/
case FD_CLOSE:
rc = WSAGETSELECTERROR(lParam);
if (rc != 0 && rc != WSAECONNRESET) {
ConnectStatus = CONNECTION_LOST;
return;
}
if (Async != 0) {
WSACancelAsyncRequest(Async);
}
WSAAsyncSelect (ConnectSocket, MainWindow, WM_ASYNCEVENT, 0);
Close_Socket (ConnectSocket);
ConnectSocket = INVALID_SOCKET;
//Connected = FALSE;
ConnectStatus = CONNECTION_LOST;
break;
/*
** FD_CONNECT: A connection was made, or an error occurred.
*/
case FD_CONNECT:
rc = WSAGETSELECTERROR(lParam);
if (rc != 0) {
ConnectStatus = UNABLE_TO_CONNECT;
return;
}
ConnectStatus = CONNECTED_OK;
Connected = TRUE;
return;
}
}
}
/***********************************************************************************************
* TMC::Copy_To_In_Buffer -- copy data from our winsock buffer to our internal buffer *
* *
* *
* *
* INPUT: bytes to copy *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:17PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Copy_To_In_Buffer(int bytes)
{
if (!ReceiveBuffers[RXBufferHead].InUse){
memcpy (ReceiveBuffers[RXBufferHead].Buffer, ReceiveBuffer, MIN(bytes, WS_INTERNET_BUFFER_LEN));
ReceiveBuffers[RXBufferHead].InUse = true;
ReceiveBuffers[RXBufferHead++].DataLength = MIN(bytes, WS_INTERNET_BUFFER_LEN);
RXBufferHead &= WS_NUM_RX_BUFFERS-1;
}
}
/***********************************************************************************************
* TMC::Set_Host_Address -- Set the address of the host game we want to connect to *
* *
* *
* *
* INPUT: ptr to address string *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:19PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Set_Host_Address(char *address)
{
strcpy(HostAddress, address);
}
/***********************************************************************************************
* TMC::Start_Client -- Start trying to connect to a game host *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:19PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Start_Client(void)
{
struct sockaddr_in addr;
bool delay = true;
int i;
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*
** Set up the incoming and outgoing data buffers head and tail pointers
*/
TXBufferHead = 0;
TXBufferTail = 0;
RXBufferHead = 0;
RXBufferTail = 0;
for (i=0 ; i<WS_NUM_TX_BUFFERS ; i++){
TransmitBuffers[i].InUse = false;
}
for (i=0 ; i<WS_NUM_RX_BUFFERS ; i++){
ReceiveBuffers[i].InUse = false;
}
Connected = FALSE;
/*
** Flag that we are the client side not the server
*/
IsServer = FALSE;
UseUDP = TRUE;
/*
** Create our UDP socket
*/
UDPSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (UDPSocket == INVALID_SOCKET) {
Close_Socket(ConnectSocket);
ConnectStatus = NOT_CONNECTING;
return;
}
/*
** Bind our UDP socket to our UDP port number
*/
addr.sin_family = AF_INET;
addr.sin_port = htons(PlanetWestwoodPortNumber);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(UDPSocket, (LPSOCKADDR)&addr, sizeof(addr)) ==
SOCKET_ERROR) {
Close_Socket(UDPSocket);
Close_Socket(ConnectSocket);
ConnectStatus = NOT_CONNECTING;
return;
}
/*
** Set options for the UDP socket
*/
setsockopt (UDPSocket, SOL_SOCKET, SO_RCVBUF, (char*)&SocketReceiveBuffer, 4);
setsockopt (UDPSocket, SOL_SOCKET, SO_SNDBUF, (char*)&SocketSendBuffer, 4);
/*
** Enable asynchronous events on the UDP socket
*/
if (WSAAsyncSelect (UDPSocket, MainWindow, WM_UDPASYNCEVENT,
FD_READ | FD_WRITE) == SOCKET_ERROR){
WSACancelAsyncRequest(Async);
Close_Socket (UDPSocket);
Close_Socket (ConnectSocket);
ConnectStatus = NOT_CONNECTING;
return;
}
/*
** If the name is not a dot-decimal ip address then do a nameserver lookup
*/
Server.Addr.s_addr = inet_addr(PlanetWestwoodIPAddress);
memcpy(&UDPIPAddress, &Server.Addr.s_addr, 4);
if (Server.Addr.s_addr == INADDR_NONE){
strcpy (Server.Name, PlanetWestwoodIPAddress);
Async = WSAAsyncGetHostByName(MainWindow, WM_HOSTBYNAME,
Server.Name, HostBuff, MAXGETHOSTSTRUCT);
ConnectStatus = RESOLVING_HOST_ADDRESS;
}else{
ConnectStatus = CONNECTED_OK;
Connected = TRUE;
}
}
/***********************************************************************************************
* TMC::Close_Socket -- Close an opened Winsock socket. *
* *
* *
* *
* INPUT: Socket to close *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/20/96 3:24PM ST : Created *
*=============================================================================================*/
void TcpipManagerClass::Close_Socket(SOCKET s)
{
LINGER ling;
ling.l_onoff = 0; // linger off
ling.l_linger = 0; // timeout in seconds (ie close now)
setsockopt(s, SOL_SOCKET, SO_LINGER, (LPSTR)&ling, sizeof(ling));
closesocket (s);
}
void TcpipManagerClass::Set_Protocol_UDP(BOOL state)
{
UseUDP = state;
}
void TcpipManagerClass::Clear_Socket_Error(SOCKET socket)
{
unsigned long error_code;
int length = 4;
getsockopt (socket, SOL_SOCKET, SO_ERROR, (char*)&error_code, &length);
error_code = 0;
setsockopt (socket, SOL_SOCKET, SO_ERROR, (char*)&error_code, length);
}
#endif //WIN32