mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
528 lines
13 KiB
C
528 lines
13 KiB
C
/*
|
|
GameSpy GT2 SDK
|
|
Dan "Mr. Pants" Schoenblum
|
|
dan@gamespy.com
|
|
|
|
Copyright 2002 GameSpy Industries, Inc
|
|
|
|
devsupport@gamespy.com
|
|
*/
|
|
|
|
#include "gt2Socket.h"
|
|
#include "gt2Buffer.h"
|
|
#include "gt2Message.h"
|
|
#include "gt2Connection.h"
|
|
#include "gt2Utility.h"
|
|
#include "gt2Callback.h"
|
|
#include <stdlib.h>
|
|
|
|
#ifdef GSI_ADHOC
|
|
// External functions defined at the platform specific level
|
|
// to be moved to nonport or socket.h
|
|
extern int _NetworkAdHocSocketCreate(gsi_u16 port);
|
|
extern void _NetworkAdHocSocketDestroy ( int adhoc_socket);
|
|
extern int _NetworkAdHocSocketSendTo ( int adhoc_socket,
|
|
const void *data,
|
|
int len,
|
|
int flags,
|
|
const char *dest_addr,
|
|
gsi_u16 dest_port
|
|
);
|
|
extern void NetAdhocMacGet(char *mac);
|
|
#endif
|
|
|
|
#define GTI2_DEFAULT_INCOMING_BUFFER_SIZE (64 * 1024)
|
|
#define GTI2_DEFAULT_OUTGOING_BUFFER_SIZE (64 * 1024)
|
|
|
|
#define STARTING_SERIAL_NUMBER 0
|
|
|
|
static int gti2ConnectionHash(const void * elem, int numBuckets)
|
|
{
|
|
GT2Connection connection = *(GT2Connection *)elem;
|
|
|
|
return (int)(((connection->ip * connection->port)) % numBuckets);
|
|
}
|
|
|
|
static int GS_STATIC_CALLBACK gti2ConnectionCompare(const void * elem1, const void * elem2)
|
|
{
|
|
GT2Connection connection1 = *(GT2Connection *)elem1;
|
|
GT2Connection connection2 = *(GT2Connection *)elem2;
|
|
|
|
if(connection1->ip != connection2->ip)
|
|
return (int)(connection1->ip - connection2->ip);
|
|
|
|
return (int)(short)(connection1->port - connection2->port);
|
|
}
|
|
|
|
static void gti2ConnectionFree(void * elem)
|
|
{
|
|
gti2ConnectionCleanup(*(GT2Connection *)elem);
|
|
}
|
|
|
|
GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, unsigned short port)
|
|
{
|
|
GTI2Connection connection;
|
|
GT2Connection connectionPtr;
|
|
GT2Connection * connectionPtrPtr;
|
|
|
|
connection.ip = ip;
|
|
connection.port = port;
|
|
|
|
connectionPtr = &connection;
|
|
connectionPtrPtr = (GT2Connection *)TableLookup(socket->connections, &connectionPtr);
|
|
if(connectionPtrPtr)
|
|
return *connectionPtrPtr;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GT2Result gti2CreateSocket
|
|
(
|
|
GT2Socket * sock,
|
|
const char * localAddress,
|
|
int outgoingBufferSize,
|
|
int incomingBufferSize,
|
|
gt2SocketErrorCallback callback,
|
|
GTI2ProtocolType type
|
|
)
|
|
{
|
|
SOCKADDR_IN address;
|
|
GT2Socket socketTemp;
|
|
int rcode;
|
|
unsigned int ip;
|
|
unsigned short port;
|
|
int len;
|
|
|
|
// startup the sockets engine if needed
|
|
SocketStartUp();
|
|
|
|
// check for using defaults
|
|
if(!incomingBufferSize)
|
|
incomingBufferSize = GTI2_DEFAULT_INCOMING_BUFFER_SIZE;
|
|
if(!outgoingBufferSize)
|
|
outgoingBufferSize = GTI2_DEFAULT_OUTGOING_BUFFER_SIZE;
|
|
|
|
// convert the address to an IP and port
|
|
if(!gt2StringToAddress(localAddress, &ip, &port))
|
|
return GT2AddressError;
|
|
|
|
// allocate the object
|
|
socketTemp = (GT2Socket)gsimalloc(sizeof(GTI2Socket));
|
|
if(!socketTemp)
|
|
return GT2OutOfMemory;
|
|
|
|
// set initial values
|
|
memset(socketTemp, 0, sizeof(GTI2Socket));
|
|
socketTemp->socket = INVALID_SOCKET;
|
|
socketTemp->incomingBufferSize = incomingBufferSize;
|
|
socketTemp->outgoingBufferSize = outgoingBufferSize;
|
|
socketTemp->socketErrorCallback = callback;
|
|
|
|
// create the connections table
|
|
socketTemp->connections = TableNew2(sizeof(GT2Connection), 32, 2, gti2ConnectionHash, gti2ConnectionCompare, NULL);
|
|
if(!socketTemp->connections)
|
|
{
|
|
gsifree(socketTemp);
|
|
return GT2OutOfMemory;
|
|
}
|
|
|
|
// create the closed connections list
|
|
socketTemp->closedConnections = ArrayNew(sizeof(GT2Connection), 4, gti2ConnectionFree);
|
|
if(!socketTemp->closedConnections)
|
|
{
|
|
TableFree(socketTemp->connections);
|
|
gsifree(socketTemp);
|
|
return GT2OutOfMemory;
|
|
}
|
|
|
|
// create the socket
|
|
|
|
#ifdef _XBOX
|
|
if (type == GTI2VdpProtocol)
|
|
|
|
socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_VDP);
|
|
else
|
|
#endif
|
|
#ifdef GSI_ADHOC
|
|
if (type == GTI2AdHocProtocol)
|
|
{
|
|
socketTemp->socket = _NetworkAdHocSocketCreate( port);
|
|
}
|
|
else
|
|
#endif
|
|
socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
socketTemp->protocolType = type;
|
|
|
|
if (type == GTI2AdHocProtocol)
|
|
{
|
|
socketTemp->protocolOffset = 0;
|
|
}
|
|
else
|
|
socketTemp->protocolOffset = type;
|
|
|
|
#ifdef _XBOX
|
|
if (type == GTI2UdpProtocol)
|
|
{
|
|
SetSockBroadcast(socketTemp->socket);
|
|
socketTemp->protocolOffset = type;
|
|
}
|
|
#endif
|
|
|
|
if(socketTemp->socket == INVALID_SOCKET)
|
|
{
|
|
TableFree(socketTemp->connections);
|
|
ArrayFree(socketTemp->closedConnections);
|
|
gsifree(socketTemp);
|
|
return GT2NetworkError;
|
|
}
|
|
|
|
// bind it
|
|
memset(&address, 0, sizeof(SOCKADDR_IN));
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = ip;
|
|
address.sin_port = htons(port);
|
|
if (type != GTI2AdHocProtocol)
|
|
{
|
|
rcode = bind(socketTemp->socket, (SOCKADDR *)&address, sizeof(SOCKADDR_IN));
|
|
if (gsiSocketIsError(rcode))
|
|
{
|
|
closesocket(socketTemp->socket);
|
|
TableFree(socketTemp->connections);
|
|
ArrayFree(socketTemp->closedConnections);
|
|
gsifree(socketTemp);
|
|
return GT2NetworkError;
|
|
}
|
|
}
|
|
|
|
// get the ip and port we're bound to
|
|
#ifdef GSI_ADHOC
|
|
if (type == GTI2AdHocProtocol)
|
|
{
|
|
char mac[6];
|
|
NetAdhocMacGet(mac);
|
|
|
|
socketTemp->ip = gt2MacToIp(mac);
|
|
socketTemp->port = ntohs(address.sin_port);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
len = sizeof(SOCKADDR_IN);
|
|
getsockname(socketTemp->socket, (SOCKADDR *)&address, &len);
|
|
socketTemp->ip = address.sin_addr.s_addr;
|
|
socketTemp->port = ntohs(address.sin_port);
|
|
}
|
|
|
|
*sock = socketTemp;
|
|
|
|
return GT2Success;
|
|
}
|
|
|
|
void gti2CloseSocket(GT2Socket socket)
|
|
{
|
|
|
|
if(socket->callbackLevel)
|
|
{
|
|
socket->close = GT2True;
|
|
return;
|
|
}
|
|
|
|
#ifdef GSI_ADHOC
|
|
if (socket->protocolType == GTI2AdHocProtocol)
|
|
{
|
|
_NetworkAdHocSocketDestroy(socket->socket);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
closesocket(socket->socket);
|
|
}
|
|
|
|
TableFree(socket->connections);
|
|
ArrayFree(socket->closedConnections);
|
|
gsifree(socket);
|
|
|
|
SocketShutDown();
|
|
}
|
|
|
|
void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback)
|
|
{
|
|
socket->connectAttemptCallback = callback;
|
|
}
|
|
|
|
static GT2Connection gti2CreateConnectionObject(void)
|
|
{
|
|
// mj todo: give options of allocating this from a static pool for games with known number of connections.
|
|
return (GT2Connection)gsimalloc(sizeof(GTI2Connection));
|
|
}
|
|
|
|
GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port)
|
|
{
|
|
GT2Connection connectionPtr = NULL;
|
|
|
|
// check if this ip and port already exists
|
|
if(gti2SocketFindConnection(socket, ip, port))
|
|
return GT2DuplicateAddress;
|
|
|
|
// allocate a connection object
|
|
connectionPtr = gti2CreateConnectionObject();
|
|
if(!connectionPtr)
|
|
goto out_of_memory;
|
|
|
|
// set some basics
|
|
memset(connectionPtr, 0, sizeof(GTI2Connection));
|
|
connectionPtr->ip = ip;
|
|
connectionPtr->port = port;
|
|
connectionPtr->socket = socket;
|
|
connectionPtr->startTime = current_time();
|
|
connectionPtr->lastSend = connectionPtr->startTime;
|
|
connectionPtr->serialNumber = STARTING_SERIAL_NUMBER;
|
|
connectionPtr->expectedSerialNumber = STARTING_SERIAL_NUMBER;
|
|
|
|
// allocate the buffers
|
|
if(!gti2AllocateBuffer(&connectionPtr->incomingBuffer, socket->incomingBufferSize))
|
|
goto out_of_memory;
|
|
if(!gti2AllocateBuffer(&connectionPtr->outgoingBuffer, socket->outgoingBufferSize))
|
|
goto out_of_memory;
|
|
|
|
// allocate the message arrays
|
|
connectionPtr->incomingBufferMessages = ArrayNew(sizeof(GTI2IncomingBufferMessage), 64, NULL);
|
|
if(!connectionPtr->incomingBufferMessages)
|
|
goto out_of_memory;
|
|
connectionPtr->outgoingBufferMessages = ArrayNew(sizeof(GTI2OutgoingBufferMessage), 64, NULL);
|
|
if(!connectionPtr->outgoingBufferMessages)
|
|
goto out_of_memory;
|
|
|
|
// allocate the filter arrays
|
|
connectionPtr->sendFilters = ArrayNew(sizeof(gt2SendFilterCallback), 2, NULL);
|
|
if(!connectionPtr->sendFilters)
|
|
goto out_of_memory;
|
|
connectionPtr->receiveFilters = ArrayNew(sizeof(gt2ReceiveFilterCallback), 2, NULL);
|
|
if(!connectionPtr->receiveFilters)
|
|
goto out_of_memory;
|
|
|
|
// add it to the table
|
|
TableEnter(socket->connections, &connectionPtr);
|
|
|
|
// check that it's in the table (and get the address)
|
|
*connection = gti2SocketFindConnection(socket, ip, port);
|
|
if(!*connection)
|
|
goto out_of_memory;
|
|
|
|
return GT2Success;
|
|
|
|
out_of_memory:
|
|
|
|
// there wasn't enough memory, free everything and return the error
|
|
if(connectionPtr)
|
|
{
|
|
gsifree(connectionPtr->incomingBuffer.buffer);
|
|
gsifree(connectionPtr->outgoingBuffer.buffer);
|
|
if(connectionPtr->incomingBufferMessages)
|
|
ArrayFree(connectionPtr->incomingBufferMessages);
|
|
if(connectionPtr->outgoingBufferMessages)
|
|
ArrayFree(connectionPtr->outgoingBufferMessages);
|
|
if(connectionPtr->sendFilters)
|
|
ArrayFree(connectionPtr->sendFilters);
|
|
if(connectionPtr->receiveFilters)
|
|
ArrayFree(connectionPtr->receiveFilters);
|
|
gsifree(connectionPtr);
|
|
}
|
|
|
|
return GT2OutOfMemory;
|
|
}
|
|
|
|
void gti2FreeSocketConnection(GT2Connection connection)
|
|
{
|
|
// check if we can actually free it
|
|
if(connection->freeAtAcceptReject || connection->callbackLevel)
|
|
return;
|
|
|
|
// remove it from the correct list depending on the connect state
|
|
if(connection->state == GTI2Closed)
|
|
{
|
|
int len;
|
|
int i;
|
|
|
|
len = ArrayLength(connection->socket->closedConnections);
|
|
for(i = 0 ; i < len ; i++)
|
|
{
|
|
if(connection == *(GT2Connection *)ArrayNth(connection->socket->closedConnections, i))
|
|
{
|
|
ArrayDeleteAt(connection->socket->closedConnections, i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TableRemove(connection->socket->connections, &connection);
|
|
}
|
|
}
|
|
|
|
GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, const GT2Byte * message, int len)
|
|
{
|
|
SOCKADDR_IN address;
|
|
int rcode;
|
|
|
|
#ifdef GTI2_DROP_SEND_RATE
|
|
// drop some percentage of packets and see what happens
|
|
if((rand() % 100) < GTI2_DROP_SEND_RATE)
|
|
return GT2True;
|
|
#endif
|
|
|
|
// check the message and len
|
|
gti2MessageCheck(&message, &len);
|
|
|
|
if (socket->protocolType != GTI2AdHocProtocol)
|
|
{
|
|
#ifndef INSOCK // insock never sets write flag for UDP sockets
|
|
// check if we can't send
|
|
if(!CanSendOnSocket(socket->socket))
|
|
return GT2True;
|
|
#endif
|
|
}
|
|
|
|
// do the send
|
|
#ifdef GSI_ADHOC
|
|
if (socket->protocolType == GTI2AdHocProtocol)
|
|
{
|
|
char mac[6];
|
|
// convert to mac
|
|
gt2IpToMac(ip,mac);
|
|
// change IP address to mac ethernet
|
|
rcode = _NetworkAdHocSocketSendTo(socket->socket, (const char *)message, len, 0, mac, port);
|
|
if(rcode <0)
|
|
{
|
|
gti2SocketError(socket);
|
|
return gsi_false;
|
|
}
|
|
|
|
// let the dump handle this
|
|
if(socket->sendDumpCallback)
|
|
{
|
|
if(!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), ip, port, GT2False, message, len, GT2True))
|
|
return GT2False;
|
|
}
|
|
return gsi_true;
|
|
}
|
|
|
|
#endif
|
|
|
|
// fill the address structure
|
|
memset(&address, 0, sizeof(SOCKADDR_IN));
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = ip;
|
|
address.sin_port = htons(port);
|
|
rcode = sendto(socket->socket, (const char *)message, len, 0, (SOCKADDR *)&address, sizeof(SOCKADDR_IN));
|
|
if (gsiSocketIsError(rcode))
|
|
{
|
|
rcode = GOAGetLastError(socket->socket);
|
|
if(rcode == WSAECONNRESET)
|
|
{
|
|
// handle the reset
|
|
if(!gti2HandleConnectionReset(socket, ip, port))
|
|
return GT2False;
|
|
}
|
|
#ifndef SN_SYSTEMS
|
|
else if (rcode == WSAEHOSTUNREACH)
|
|
{
|
|
if (!gti2HandleHostUnreachable(socket, ip, port, GT2True))
|
|
return GT2False;
|
|
}
|
|
#endif
|
|
// some systems might return these errors
|
|
else if((rcode == WSAENOBUFS) || (rcode == WSAEWOULDBLOCK))
|
|
{
|
|
return GT2True;
|
|
}
|
|
#if defined(SN_SYSTEMS) || defined(_NITRO) || defined(_REVOLUTION)
|
|
// for systems that don't support WSAEHOSTDOWN (EHOSTDOWN)
|
|
else if((rcode != WSAEMSGSIZE))
|
|
#else
|
|
else if((rcode != WSAEMSGSIZE) && (rcode != WSAEHOSTDOWN))
|
|
#endif
|
|
{
|
|
// fatal socket error
|
|
gti2SocketError(socket);
|
|
return GT2False;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// let the dump handle this
|
|
if(socket->sendDumpCallback)
|
|
{
|
|
if(!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), ip, port, GT2False, message, len, GT2True))
|
|
return GT2False;
|
|
}
|
|
}
|
|
|
|
return GT2True;
|
|
}
|
|
|
|
static int gti2SocketConnectionsThinkMap(void * elem, void * clientData)
|
|
{
|
|
GT2Connection connection = *(GT2Connection *)elem;
|
|
gsi_time now = *(gsi_time *)clientData;
|
|
|
|
// only think if we're not closed
|
|
if(connection->state != GTI2Closed)
|
|
{
|
|
// think
|
|
if(!gti2ConnectionThink(connection, now))
|
|
return 0;
|
|
}
|
|
|
|
// check for a closed connection
|
|
if((connection->state == GTI2Closed) && !connection->freeAtAcceptReject && !connection->callbackLevel)
|
|
gti2FreeSocketConnection(connection);
|
|
|
|
return 1;
|
|
}
|
|
|
|
GT2Bool gti2SocketConnectionsThink(GT2Socket socket)
|
|
{
|
|
gsi_time now;
|
|
|
|
// get the current time
|
|
now = current_time();
|
|
|
|
// go through the list of connections and let them think
|
|
if(TableMapSafe2(socket->connections, gti2SocketConnectionsThinkMap, &now))
|
|
return GT2False;
|
|
|
|
return GT2True;
|
|
}
|
|
|
|
void gti2FreeClosedConnections(GT2Socket socket)
|
|
{
|
|
int i;
|
|
int len;
|
|
|
|
// loop through the closed connections, attempting to free them all
|
|
len = ArrayLength(socket->closedConnections);
|
|
for(i = (len - 1) ; i >= 0 ; i--)
|
|
gti2FreeSocketConnection(*(GT2Connection *)ArrayNth(socket->closedConnections, i));
|
|
}
|
|
|
|
void gti2SocketError(GT2Socket socket)
|
|
{
|
|
// if there was already an error, don't go through this again
|
|
if(socket->error)
|
|
return;
|
|
|
|
// flag the error
|
|
socket->error = GT2True;
|
|
|
|
// first close all the socket's connections
|
|
gt2CloseAllConnectionsHard(socket);
|
|
|
|
// call the error callback
|
|
if(!gti2SocketErrorCallback(socket))
|
|
return;
|
|
|
|
// close the socket
|
|
gti2CloseSocket(socket);
|
|
}
|