fteqw/engine/nqnet/net_dgrm.c
Spoike 9ae7e2621d SOFTWARE RENDERING IS BROKEN: DO NOT USE ASM VERSION.
Lots of changes.
CSQC should be functional, but is still tied to debug builds. It WILL have some bugs still, hopefully I'll be able to clean them up better if people test it a bit.
Precompiled headers are working properly now. Compile times are now much quicker in msvc. This takes most of the files this commit.
Restructured how client commands work. They're buffered outside the network message, some multithreaded code is in. It needs a bit of testing before it's active.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@885 fc73d0e0-1445-4013-8a0c-d673dee63da5
2005-02-28 07:16:19 +00:00

1717 lines
41 KiB
C

/*
Copyright (C) 1996-1997 Id Software, 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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// net_dgrm.c
#include "quakedef.h"
#ifdef NQPROT
#define NET_GAMENAME_NQ "QUAKE"
#define NET_GAMENAME_QW "QUAKEWORLD"
// This is enables a simple IP banning mechanism
//#define BAN_TEST
#ifdef BAN_TEST
#if defined(_WIN32)
#include <windows.h>
#elif defined (NeXT)
#include <sys/socket.h>
#include <arpa/inet.h>
#else
#define AF_INET 2 /* internet */
struct in_addr
{
union
{
struct { unsigned char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { unsigned short s_w1,s_w2; } S_un_w;
unsigned long S_addr;
} S_un;
};
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
struct sockaddr_in
{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
char *inet_ntoa(struct in_addr in);
unsigned long inet_addr(const char *cp);
#endif
#endif // BAN_TEST
#include "net_dgrm.h"
// these two macros are to make the code more readable
#define sfunc net_landrivers[sock->landriver]
#define dfunc net_landrivers[net_landriverlevel]
static int net_landriverlevel;
/* statistic counters */
int packetsSent = 0;
int packetsReSent = 0;
int packetsReceived = 0;
int receivedDuplicateCount = 0;
int shortPacketCount = 0;
int droppedDatagrams;
static int myDriverLevel;
struct
{
unsigned int length;
unsigned int sequence;
qbyte data[MAX_OVERALLDATAGRAM];
} packetBuffer;
#ifndef SERVERONLY
extern int m_return_state;
extern qboolean m_return_onerror;
extern char m_return_reason[32];
#endif
#ifdef DEBUG
char *StrAddr (struct qsockaddr *addr)
{
static char buf[34];
qbyte *p = (qbyte *)addr;
int n;
for (n = 0; n < 16; n++)
sprintf (buf + n * 2, "%02x", *p++);
return buf;
}
#endif
#ifdef BAN_TEST
unsigned long banAddr = 0x00000000;
unsigned long banMask = 0xffffffff;
void NET_Ban_f (void)
{
char addrStr [32];
char maskStr [32];
switch (Cmd_Argc ())
{
case 1:
if (((struct in_addr *)&banAddr)->s_addr)
{
Q_strcpy(addrStr, inet_ntoa(*(struct in_addr *)&banAddr));
Q_strcpy(maskStr, inet_ntoa(*(struct in_addr *)&banMask));
Con_Printf("Banning %s [%s]\n", addrStr, maskStr);
}
else
Con_Printf("Banning not active\n");
break;
case 2:
if (Q_strcasecmp(Cmd_Argv(1), "off") == 0)
banAddr = 0x00000000;
else
banAddr = inet_addr(Cmd_Argv(1));
banMask = 0xffffffff;
break;
case 3:
banAddr = inet_addr(Cmd_Argv(1));
banMask = inet_addr(Cmd_Argv(2));
break;
default:
Con_Printf("BAN ip_address [mask]\n");
break;
}
}
#endif
int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data)
{
unsigned int packetLen;
unsigned int dataLen;
unsigned int eom;
#ifdef DEBUG
if (data->cursize == 0)
Sys_Error("Datagram_SendMessage: zero length message\n");
if (data->cursize > NET_MAXMESSAGE)
Sys_Error("Datagram_SendMessage: message too big %u\n", data->cursize);
if (sock->canSend == false)
Sys_Error("SendMessage: called with canSend == false\n");
#endif
Q_memcpy(sock->sendMessage, data->data, data->cursize);
sock->sendMessageLength = data->cursize;
if (data->cursize <= MAX_NQDATAGRAM)
{
dataLen = data->cursize;
eom = NETFLAG_EOM;
}
else
{
dataLen = MAX_NQDATAGRAM;
eom = 0;
}
packetLen = NET_HEADERSIZE + dataLen;
packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
packetBuffer.sequence = BigLong(sock->sendSequence++);
Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen);
sock->canSend = false;
if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
sock->lastSendTime = net_time;
packetsSent++;
return 1;
}
int SendMessageNext (qsocket_t *sock)
{
unsigned int packetLen;
unsigned int dataLen;
unsigned int eom;
if (sock->sendMessageLength <= MAX_NQDATAGRAM)
{
dataLen = sock->sendMessageLength;
eom = NETFLAG_EOM;
}
else
{
dataLen = MAX_NQDATAGRAM;
eom = 0;
}
packetLen = NET_HEADERSIZE + dataLen;
packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
packetBuffer.sequence = BigLong(sock->sendSequence++);
Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen);
sock->sendNext = false;
if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
sock->lastSendTime = net_time;
packetsSent++;
return 1;
}
int ReSendMessage (qsocket_t *sock)
{
unsigned int packetLen;
unsigned int dataLen;
unsigned int eom;
if (sock->sendMessageLength <= MAX_NQDATAGRAM)
{
dataLen = sock->sendMessageLength;
eom = NETFLAG_EOM;
}
else
{
dataLen = MAX_NQDATAGRAM;
eom = 0;
}
packetLen = NET_HEADERSIZE + dataLen;
packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
packetBuffer.sequence = BigLong(sock->sendSequence - 1);
Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen);
sock->sendNext = false;
if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
sock->lastSendTime = net_time;
packetsReSent++;
return 1;
}
qboolean Datagram_CanSendMessage (qsocket_t *sock)
{
if (sock->sendNext)
SendMessageNext (sock);
return sock->canSend;
}
qboolean Datagram_CanSendUnreliableMessage (qsocket_t *sock)
{
return true;
}
int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
{
int packetLen;
#ifdef DEBUG
if (data->cursize == 0)
Sys_Error("Datagram_SendUnreliableMessage: zero length message\n");
if (data->cursize > MAX_NQDATAGRAM)
Sys_Error("Datagram_SendUnreliableMessage: message too big %u\n", data->cursize);
#endif
packetLen = NET_HEADERSIZE + data->cursize;
packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE);
packetBuffer.sequence = BigLong(sock->unreliableSendSequence++);
Q_memcpy (packetBuffer.data, data->data, data->cursize);
if (sfunc.Write (sock->socket, (qbyte *)&packetBuffer, packetLen, &sock->addr) == -1)
return -1;
packetsSent++;
return 1;
}
int Datagram_GetMessage (qsocket_t *sock)
{
unsigned int length;
unsigned int flags;
int ret = 0;
struct sockaddr_qstorage readaddr;
unsigned int sequence;
unsigned int count;
if (!sock->canSend)
if ((net_time - sock->lastSendTime) > 1.0)
ReSendMessage (sock);
for(;;)
{
length = sfunc.Read (sock->socket, (qbyte *)&packetBuffer, NET_DATAGRAMSIZE, &readaddr);
// if ((rand() & 255) > 220)
// continue;
if (length == 0)
break;
if (length == -1)
{
Con_Printf("Read error\n");
return -1;
}
if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0)
{
#ifdef DEBUG
Con_DPrintf("Forged packet received\n");
Con_DPrintf("Expected: %s\n", StrAddr (&sock->addr));
Con_DPrintf("Received: %s\n", StrAddr (&readaddr));
#endif
continue;
}
if (length < NET_HEADERSIZE)
{
shortPacketCount++;
continue;
}
length = BigLong(packetBuffer.length);
flags = length & (~NETFLAG_LENGTH_MASK);
length &= NETFLAG_LENGTH_MASK;
if (flags & NETFLAG_CTL)
continue;
sequence = BigLong(packetBuffer.sequence);
packetsReceived++;
if (flags & NETFLAG_UNRELIABLE)
{
if (sequence < sock->unreliableReceiveSequence)
{
Con_DPrintf("Got a stale datagram\n");
ret = 0;
break;
}
if (sequence != sock->unreliableReceiveSequence)
{
count = sequence - sock->unreliableReceiveSequence;
droppedDatagrams += count;
Con_DPrintf("Dropped %u datagram(s)\n", count);
}
sock->unreliableReceiveSequence = sequence + 1;
length -= NET_HEADERSIZE;
SZ_Clear (&net_message);
SZ_Write (&net_message, packetBuffer.data, length);
ret = 2;
break;
}
if (flags & NETFLAG_ACK)
{
if (sequence != (sock->sendSequence - 1))
{
Con_DPrintf("Stale ACK received\n");
continue;
}
if (sequence == sock->ackSequence)
{
sock->ackSequence++;
if (sock->ackSequence != sock->sendSequence)
Con_DPrintf("ack sequencing error\n");
}
else
{
Con_DPrintf("Duplicate ACK received\n");
continue;
}
sock->sendMessageLength -= MAX_NQDATAGRAM;
if (sock->sendMessageLength > 0)
{
Q_memcpy(sock->sendMessage, sock->sendMessage+MAX_NQDATAGRAM, sock->sendMessageLength);
sock->sendNext = true;
}
else
{
sock->sendMessageLength = 0;
sock->canSend = true;
}
continue;
}
if (flags & NETFLAG_DATA)
{
packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK);
packetBuffer.sequence = BigLong(sequence);
sfunc.Write (sock->socket, (qbyte *)&packetBuffer, NET_HEADERSIZE, &readaddr);
if (sequence != sock->receiveSequence)
{
receivedDuplicateCount++;
continue;
}
sock->receiveSequence++;
length -= NET_HEADERSIZE;
if (flags & NETFLAG_EOM)
{
SZ_Clear(&net_message);
SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength);
SZ_Write(&net_message, packetBuffer.data, length);
sock->receiveMessageLength = 0;
ret = 1;
break;
}
Q_memcpy(sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length);
sock->receiveMessageLength += length;
continue;
}
}
if (sock->sendNext)
SendMessageNext (sock);
return ret;
}
void PrintStats(qsocket_t *s)
{
Con_Printf("canSend = %4u \n", s->canSend);
Con_Printf("sendSeq = %4u ", s->sendSequence);
Con_Printf("recvSeq = %4u \n", s->receiveSequence);
Con_Printf("\n");
}
void NET_Stats_f (void)
{
qsocket_t *s;
if (Cmd_Argc () == 1)
{
Con_Printf("unreliable messages sent = %i\n", unreliableMessagesSent);
Con_Printf("unreliable messages recv = %i\n", unreliableMessagesReceived);
Con_Printf("reliable messages sent = %i\n", messagesSent);
Con_Printf("reliable messages received = %i\n", messagesReceived);
Con_Printf("packetsSent = %i\n", packetsSent);
Con_Printf("packetsReSent = %i\n", packetsReSent);
Con_Printf("packetsReceived = %i\n", packetsReceived);
Con_Printf("receivedDuplicateCount = %i\n", receivedDuplicateCount);
Con_Printf("shortPacketCount = %i\n", shortPacketCount);
Con_Printf("droppedDatagrams = %i\n", droppedDatagrams);
}
else if (Q_strcmp(Cmd_Argv(1), "*") == 0)
{
for (s = net_activeSockets; s; s = s->next)
PrintStats(s);
for (s = net_freeSockets; s; s = s->next)
PrintStats(s);
}
else
{
for (s = net_activeSockets; s; s = s->next)
if (Q_strcasecmp(Cmd_Argv(1), s->address) == 0)
break;
if (s == NULL)
for (s = net_freeSockets; s; s = s->next)
if (Q_strcasecmp(Cmd_Argv(1), s->address) == 0)
break;
if (s == NULL)
return;
PrintStats(s);
}
}
/*
static qboolean testInProgress = false;
static int testPollCount;
static int testDriver;
static int testSocket;
static void Test_Poll(void *arg);
PollProcedure testPollProcedure = {NULL, 0.0, Test_Poll};
static void Test_Poll(void *arg)
{
struct qsockaddr clientaddr;
int control;
int len;
char name[32];
char address[64];
int colors;
int frags;
int connectTime;
qbyte playerNumber;
net_landriverlevel = testDriver;
while (1)
{
len = dfunc.Read (testSocket, net_message.data, net_message.maxsize, &clientaddr);
if (len < sizeof(int))
break;
net_message.cursize = len;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
if (control == -1)
break;
if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
break;
if ((control & NETFLAG_LENGTH_MASK) != len)
break;
if (MSG_ReadByte() != CCREP_PLAYER_INFO)
Sys_Error("Unexpected repsonse to Player Info request\n");
playerNumber = MSG_ReadByte();
Q_strcpy(name, MSG_ReadString());
colors = MSG_ReadLong();
frags = MSG_ReadLong();
connectTime = MSG_ReadLong();
Q_strcpy(address, MSG_ReadString());
Con_Printf("%s\n frags:%3i colors:%u %u time:%u\n %s\n", name, frags, colors >> 4, colors & 0x0f, connectTime / 60, address);
}
testPollCount--;
if (testPollCount)
{
SchedulePollProcedure(&testPollProcedure, 0.1);
}
else
{
dfunc.CloseSocket(testSocket);
testInProgress = false;
}
}
static void Test_f (void)
{
char *host;
int n;
int max = MAX_SCOREBOARD;
struct qsockaddr sendaddr;
if (testInProgress)
return;
host = Cmd_Argv (1);
if (host && hostCacheCount)
{
for (n = 0; n < hostCacheCount; n++)
if (Q_strcasecmp (host, hostcache[n].name) == 0)
{
if (hostcache[n].driver != myDriverLevel)
continue;
net_landriverlevel = hostcache[n].ldriver;
max = hostcache[n].maxusers;
Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
break;
}
if (n < hostCacheCount)
goto JustDoIt;
}
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
{
if (!net_landrivers[net_landriverlevel].initialized)
continue;
// see if we can resolve the host name
if (dfunc.GetAddrFromName(host, &sendaddr) != -1)
break;
}
if (net_landriverlevel == net_numlandrivers)
return;
JustDoIt:
testSocket = dfunc.OpenSocket(0);
if (testSocket == -1)
return;
testInProgress = true;
testPollCount = 20;
testDriver = net_landriverlevel;
for (n = 0; n < max; n++)
{
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_PLAYER_INFO);
MSG_WriteByte(&net_message, n);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (testSocket, net_message.data, net_message.cursize, &sendaddr);
}
SZ_Clear(&net_message);
SchedulePollProcedure(&testPollProcedure, 0.1);
}
static qboolean test2InProgress = false;
static int test2Driver;
static int test2Socket;
static void Test2_Poll(void *arg);
PollProcedure test2PollProcedure = {NULL, 0.0, Test2_Poll};
static void Test2_Poll(void *arg)
{
struct qsockaddr clientaddr;
int control;
int len;
char name[256];
char value[256];
net_landriverlevel = test2Driver;
name[0] = 0;
len = dfunc.Read (test2Socket, net_message.data, net_message.maxsize, &clientaddr);
if (len < sizeof(int))
goto Reschedule;
net_message.cursize = len;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
if (control == -1)
goto Error;
if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
goto Error;
if ((control & NETFLAG_LENGTH_MASK) != len)
goto Error;
if (MSG_ReadByte() != CCREP_RULE_INFO)
goto Error;
Q_strcpy(name, MSG_ReadString());
if (name[0] == 0)
goto Done;
Q_strcpy(value, MSG_ReadString());
Con_Printf("%-16.16s %-16.16s\n", name, value);
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
MSG_WriteString(&net_message, name);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (test2Socket, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
Reschedule:
SchedulePollProcedure(&test2PollProcedure, 0.05);
return;
Error:
Con_Printf("Unexpected repsonse to Rule Info request\n");
Done:
dfunc.CloseSocket(test2Socket);
test2InProgress = false;
return;
}
static void Test2_f (void)
{
char *host;
int n;
struct qsockaddr sendaddr;
if (test2InProgress)
return;
host = Cmd_Argv (1);
if (host && hostCacheCount)
{
for (n = 0; n < hostCacheCount; n++)
if (Q_strcasecmp (host, hostcache[n].name) == 0)
{
if (hostcache[n].driver != myDriverLevel)
continue;
net_landriverlevel = hostcache[n].ldriver;
Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
break;
}
if (n < hostCacheCount)
goto JustDoIt;
}
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
{
if (!net_landrivers[net_landriverlevel].initialized)
continue;
// see if we can resolve the host name
if (dfunc.GetAddrFromName(host, &sendaddr) != -1)
break;
}
if (net_landriverlevel == net_numlandrivers)
return;
JustDoIt:
test2Socket = dfunc.OpenSocket(0);
if (test2Socket == -1)
return;
test2InProgress = true;
test2Driver = net_landriverlevel;
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
MSG_WriteString(&net_message, "");
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (test2Socket, net_message.data, net_message.cursize, &sendaddr);
SZ_Clear(&net_message);
SchedulePollProcedure(&test2PollProcedure, 0.05);
}
*/
int Datagram_Init (void)
{
int i;
int csock;
myDriverLevel = net_driverlevel;
Cmd_AddCommand ("net_stats", NET_Stats_f);
if (COM_CheckParm("-nolan"))
return -1;
for (i = 0; i < net_numlandrivers; i++)
{
csock = net_landrivers[i].Init ();
if (csock == -1)
continue;
net_landrivers[i].initialized = true;
net_landrivers[i].controlSock = csock;
}
#ifdef BAN_TEST
Cmd_AddCommand ("ban", NET_Ban_f);
#endif
// Cmd_AddCommand ("test", Test_f);
// Cmd_AddCommand ("test2", Test2_f);
return 0;
}
void Datagram_Shutdown (void)
{
int i;
//
// shutdown the lan drivers
//
for (i = 0; i < net_numlandrivers; i++)
{
if (net_landrivers[i].initialized)
{
net_landrivers[i].Shutdown ();
net_landrivers[i].initialized = false;
}
}
}
void Datagram_Close (qsocket_t *sock)
{
sfunc.CloseSocket(sock->socket);
}
void Datagram_Listen (qboolean state)
{
#ifndef CLIENTONLY
int i;
for (i = 0; i < net_numlandrivers; i++)
if (net_landrivers[i].initialized)
net_landrivers[i].Listen (state);
#endif
}
#ifndef CLIENTONLY
static qsocket_t *_Datagram_CheckNewConnections (void)
{
struct sockaddr_qstorage clientaddr;
struct sockaddr_qstorage newaddr;
int newsock;
int acceptsock;
qsocket_t *sock;
qsocket_t *s;
int len;
int command;
int control;
int ret;
#ifdef NET_GAMENAME_QW
char *gname;
#endif
acceptsock = dfunc.CheckNewConnections();
if (acceptsock == -1)
return NULL;
SZ_Clear(&net_message);
len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr);
if (len < sizeof(int))
return NULL;
net_message.cursize = len;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
if (control == -1)
return NULL;
if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
return NULL;
if ((control & NETFLAG_LENGTH_MASK) != len)
return NULL;
command = MSG_ReadByte();
if (command == CCREQ_SERVER_INFO)
{
int numcl;
#ifdef NET_GAMENAME_QW
gname = MSG_ReadString();
if (Q_strcmp(gname, NET_GAMENAME_QW))
if (Q_strcmp(gname, NET_GAMENAME_NQ) )
return NULL;
#else
if (Q_strcmp(MSG_ReadString(), NET_GAMENAME_NQ) != 0)
return NULL;
#endif
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_SERVER_INFO);
dfunc.GetSocketAddr(acceptsock, &newaddr);
MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
MSG_WriteString(&net_message, hostname.string);
MSG_WriteString(&net_message, sv.name);
numcl = 0;
for (len = 0; len < MAX_CLIENTS; len++)
{
if(svs.clients[len].state>cs_zombie || *svs.clients[len].name) //client or bot.
numcl++;
}
MSG_WriteByte(&net_message, numcl);
MSG_WriteByte(&net_message, sv.allocated_client_slots);
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
if (command == CCREQ_PLAYER_INFO)
{
int playerNumber;
int activeNumber;
int clientNumber;
client_t *client;
playerNumber = MSG_ReadByte();
activeNumber = -1;
for (clientNumber = 0, client = svs.clients; clientNumber < sv.allocated_client_slots; clientNumber++, client++)
{
if (client->state>cs_zombie || *svs.clients[len].name)
{
activeNumber++;
if (activeNumber == playerNumber)
break;
}
}
if (clientNumber == sv.allocated_client_slots)
return NULL;
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_PLAYER_INFO);
MSG_WriteByte(&net_message, playerNumber);
MSG_WriteString(&net_message, client->name);
MSG_WriteLong(&net_message, 0);
MSG_WriteLong(&net_message, (int)client->old_frags);
MSG_WriteLong(&net_message, (int)(net_time - client->connection_started));
MSG_WriteString(&net_message, "WITHHELD");
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
if (command == CCREQ_RULE_INFO)
{
extern cvar_group_t cvargroup_serverinfo;
char *prevCvarName;
cvar_t *var;
// find the search start location
prevCvarName = MSG_ReadString();
if (*prevCvarName)
{
var = Cvar_FindVar (prevCvarName);
if (!var)
return NULL;
var = var->next;
}
else
var = cvargroup_serverinfo.cvars;
// search for the next server cvar
while (var)
{
if (var->flags&CVAR_SERVERINFO)
break;
var = var->next;
}
// send the response
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_RULE_INFO);
if (var)
{
MSG_WriteString(&net_message, var->name);
MSG_WriteString(&net_message, var->string);
}
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
if (command != CCREQ_CONNECT)
return NULL;
#ifdef NET_GAMENAME_QW
gname = MSG_ReadString();
if (Q_strcmp(gname, NET_GAMENAME_QW))
if (Q_strcmp(gname, NET_GAMENAME_NQ) )
return NULL;
#else
if (Q_strcmp(MSG_ReadString(), NET_GAMENAME_NQ) != 0)
return NULL;
#endif
if (MSG_ReadByte() != NET_PROTOCOL_VERSION)
{
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_REJECT);
MSG_WriteString(&net_message, "Incompatible version.\n");
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
#ifdef BAN_TEST
// check for a ban
if (((struct sockaddr_in*)clientaddr).sa_family == AF_INET)
{
unsigned long testAddr;
testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr;
if ((testAddr & banMask) == banAddr)
{
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_REJECT);
MSG_WriteString(&net_message, "You have been banned.\n");
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
}
#endif
// see if this guy is already connected
for (s = net_activeSockets; s; s = s->next)
{
if (s->driver != net_driverlevel)
continue;
ret = dfunc.AddrCompare(&clientaddr, &s->addr);
if (ret >= 0)
{
// is this a duplicate connection reqeust?
if (ret == 0 && net_time - s->connecttime < 2.0)
{
// yes, so send a duplicate reply
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_ACCEPT);
dfunc.GetSocketAddr(s->socket, &newaddr);
MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
MSG_WriteString(&net_message, gname);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
// it's somebody coming back in from a crash/disconnect
// so close the old qsocket and let their retry get them back in
NET_Close(s);
return NULL;
}
}
// allocate a QSocket
sock = NET_NewQSocket ();
if (sock == NULL)
{
// no room; try to let him know
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_REJECT);
MSG_WriteString(&net_message, "Server is full.\n");
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return NULL;
}
if (!Q_strcmp(gname, NET_GAMENAME_QW))
sock->qwprotocol = true;
// allocate a network socket
newsock = dfunc.OpenSocket(0);
if (newsock == -1)
{
NET_FreeQSocket(sock);
return NULL;
}
// connect to the client
if (dfunc.Connect (newsock, &clientaddr) == -1)
{
dfunc.CloseSocket(newsock);
NET_FreeQSocket(sock);
return NULL;
}
// everything is allocated, just fill in the details
sock->socket = newsock;
sock->landriver = net_landriverlevel;
sock->addr = clientaddr;
Q_strcpy(sock->address, dfunc.AddrToString(&clientaddr));
// send him back the info about the server connection he has been allocated
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREP_ACCEPT);
dfunc.GetSocketAddr(newsock, &newaddr);
MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
MSG_WriteString(&net_message, gname);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
SZ_Clear(&net_message);
return sock;
}
qsocket_t *Datagram_CheckNewConnections (void)
{
qsocket_t *ret = NULL;
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
if (net_landrivers[net_landriverlevel].initialized)
if ((ret = _Datagram_CheckNewConnections ()) != NULL)
break;
return ret;
}
#else
qsocket_t *Datagram_CheckNewConnections (void)
{
return NULL; //client only can't.
}
#endif
static void _Datagram_SearchForHosts (qboolean xmit)
{
int ret;
int n;
int i;
struct sockaddr_qstorage readaddr;
struct sockaddr_qstorage myaddr;
int control;
dfunc.GetSocketAddr (dfunc.controlSock, &myaddr);
if (xmit)
{
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
MSG_WriteString(&net_message, NET_GAMENAME_QW);
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize);
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
MSG_WriteString(&net_message, NET_GAMENAME_NQ); //look for either sort of server
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize);
SZ_Clear(&net_message);
}
while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0)
{
if (ret < sizeof(int))
continue;
net_message.cursize = ret;
// don't answer our own query
// if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0)
// continue;
// is the cache full?
if (hostCacheCount == HOSTCACHESIZE)
continue;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
if (control == -1)
continue;
if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
continue;
if ((control & NETFLAG_LENGTH_MASK) != ret)
continue;
if (MSG_ReadByte() != CCREP_SERVER_INFO)
continue;
dfunc.GetAddrFromName(MSG_ReadString(), &readaddr);
// search the cache for this server
for (n = 0; n < hostCacheCount; n++)
if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0)
break;
// is it already there?
if (n < hostCacheCount)
continue;
// add it
hostCacheCount++;
Q_strncpyz(hostcache[n].name, MSG_ReadString(), sizeof(hostcache[n].name));
Q_strncpyz(hostcache[n].map, MSG_ReadString(), sizeof(hostcache[n].map));
hostcache[n].users = MSG_ReadByte();
hostcache[n].maxusers = MSG_ReadByte();
if (MSG_ReadByte() != NET_PROTOCOL_VERSION)
{
Q_strcpy(hostcache[n].cname, hostcache[n].name);
hostcache[n].cname[14] = 0;
Q_strcpy(hostcache[n].name, "*");
Q_strcat(hostcache[n].name, hostcache[n].cname);
}
Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct sockaddr_qstorage));
hostcache[n].driver = net_driverlevel;
hostcache[n].ldriver = net_landriverlevel;
Q_strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr));
// check for a name conflict
for (i = 0; i < hostCacheCount; i++)
{
if (i == n)
continue;
if (Q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0)
{
i = Q_strlen(hostcache[n].name);
if (i < 15 && hostcache[n].name[i-1] > '8')
{
hostcache[n].name[i] = '0';
hostcache[n].name[i+1] = 0;
}
else
hostcache[n].name[i-1]++;
i = -1;
}
}
}
}
void Datagram_SearchForHosts (qboolean xmit)
{
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
{
if (hostCacheCount == HOSTCACHESIZE)
break;
if (net_landrivers[net_landriverlevel].initialized)
_Datagram_SearchForHosts (xmit);
}
}
#ifndef SERVERONLY
qsocket_t *Datagram_ConnectToDarkPlacesServer(netadr_t *nadr)
{ //QuakeWorld connection hit upon a DP server.
struct sockaddr_qstorage sendaddr;
qsocket_t *sock;
int newsock;
net_driverlevel = 1;
net_landriverlevel = 0;
//hrm. the server uses out port as well as ip... we connected with the qw port... so we need to swap them over
newsock = cls.socketip;
cls.socketip = dfunc.OpenSocket(0);
if (newsock == -1)
{
cls.socketip = newsock;
Con_Printf("Failed to initialse NQ transports\n");
return NULL;
}
sock = NET_NewQSocket ();
if (sock == NULL)
{
Con_Printf("Failed to initialse NQ transports\n");
dfunc.CloseSocket(newsock);
return NULL;
}
sock->socket = newsock;
sock->landriver = net_landriverlevel;
NetadrToSockadr(nadr, &sendaddr);
if (dfunc.Connect(newsock, &sendaddr) == -1)
{
Con_Printf("Failed to initialse NQ transports\n");
NET_FreeQSocket(sock);
dfunc.CloseSocket(newsock);
return NULL;
}
Q_memcpy(&sock->addr, &sendaddr, sizeof(struct sockaddr_qstorage));
dfunc.GetNameFromAddr (&sendaddr, sock->address);
Con_Printf ("Connection accepted\n");
sock->lastMessageTime = SetNetTime();
return sock;
}
static qsocket_t *_Datagram_Connect (char *host)
{
struct sockaddr_qstorage sendaddr;
struct sockaddr_qstorage readaddr;
qsocket_t *sock;
int newsock;
int ret=0;
int reps;
double start_time;
int control;
char *reason;
// see if we can resolve the host name
if (dfunc.GetAddrFromName(host, &sendaddr) == -1)
return NULL;
newsock = dfunc.OpenSocket (0);
if (newsock == -1)
return NULL;
sock = NET_NewQSocket ();
if (sock == NULL)
goto ErrorReturn2;
sock->socket = newsock;
sock->landriver = net_landriverlevel;
// connect to the host
if (dfunc.Connect (newsock, &sendaddr) == -1)
goto ErrorReturn;
// send the connection request
Con_Printf("trying...\n"); SCR_UpdateScreen ();
start_time = net_time;
for (reps = 0; reps < 3; reps++)
{
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_CONNECT);
MSG_WriteString(&net_message, NET_GAMENAME_QW);
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr);
SZ_Clear(&net_message);
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_CONNECT);
MSG_WriteString(&net_message, NET_GAMENAME_NQ); //eitehr will do
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr);
SZ_Clear(&net_message);
/* {
int s = sock->landriver, d = net_landriverlevel;
SVNQ_CheckForNewClients();
net_landriverlevel = d;
sock->landriver = s;
}
*/
do
{
ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr);
// if we got something, validate it
if (ret > 0)
{
// is it from the right place?
if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0)
{
#ifdef DEBUG
Con_Printf("wrong reply address\n");
Con_Printf("Expected: %s\n", StrAddr (&sendaddr));
Con_Printf("Received: %s\n", StrAddr (&readaddr));
SCR_UpdateScreen ();
#endif
ret = 0;
continue;
}
if (ret < sizeof(int))
{
ret = 0;
continue;
}
net_message.cursize = ret;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
if (control == -1)
{
ret = 0;
continue;
}
if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
{
ret = 0;
continue;
}
if ((control & NETFLAG_LENGTH_MASK) != ret)
{
ret = 0;
continue;
}
}
}
while (ret == 0 && (SetNetTime() - start_time) < 2.5);
if (ret)
break;
Con_Printf("still trying...\n"); SCR_UpdateScreen ();
start_time = SetNetTime();
}
if (ret == 0)
{
reason = "No Response";
Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
goto ErrorReturn;
}
if (ret == -1)
{
reason = "Network Error";
Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
goto ErrorReturn;
}
ret = MSG_ReadByte();
if (ret == CCREP_REJECT)
{
reason = MSG_ReadString();
Con_Printf(reason);
Q_strncpyz(m_return_reason, reason, sizeof(m_return_reason));
goto ErrorReturn;
}
if (ret == CCREP_ACCEPT)
{
Q_memcpy(&sock->addr, &sendaddr, sizeof(struct sockaddr_qstorage));
dfunc.SetSocketPort (&sock->addr, MSG_ReadLong());
if (!Q_strcmp(NET_GAMENAME_QW, MSG_ReadString()))
sock->qwprotocol = true;
}
else
{
reason = "Bad Response";
Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
goto ErrorReturn;
}
dfunc.GetNameFromAddr (&sendaddr, sock->address);
Con_Printf ("Connection accepted\n");
sock->lastMessageTime = SetNetTime();
// switch the connection to the specified address
if (dfunc.Connect (newsock, &sock->addr) == -1)
{
reason = "Connect to Game failed";
Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
goto ErrorReturn;
}
m_return_onerror = false;
return sock;
ErrorReturn:
NET_FreeQSocket(sock);
ErrorReturn2:
dfunc.CloseSocket(newsock);
if (m_return_onerror)
{
key_dest = key_menu;
m_state = m_return_state;
m_return_onerror = false;
}
return NULL;
}
static int newsock;
static qsocket_t *sock;
static qsocket_t *_Datagram_FailConnect (char *host)
{
if (sock)
NET_Close(sock);
// NET_FreeQSocket(sock);
if (!newsock)
return NULL;
dfunc.CloseSocket(newsock);
if (m_return_onerror)
{
key_dest = key_menu;
m_state = m_return_state;
m_return_onerror = false;
}
sock = NULL;
return NULL;
}
static qsocket_t *_Datagram_ContinueConnect (char *host)
{
struct sockaddr_qstorage sendaddr;
struct sockaddr_qstorage readaddr;
int ret;
// int reps;
int control;
char *reason;
// see if we can resolve the host name
if (!sock || dfunc.GetAddrFromName(host, &sendaddr) == -1)
return NULL;
do
{
ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr);
// if we got something, validate it
if (ret > 0)
{
// is it from the right place?
if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0)
{
#ifdef DEBUG
Con_Printf("wrong reply address\n");
Con_Printf("Expected: %s\n", StrAddr (&sendaddr));
Con_Printf("Received: %s\n", StrAddr (&readaddr));
SCR_UpdateScreen ();
#endif
ret = 0;
continue;
}
if (ret < sizeof(int))
{
ret = 0;
continue;
}
net_message.cursize = ret;
MSG_BeginReading ();
control = BigLong(*((int *)net_message.data));
MSG_ReadLong();
if (control == -1)
{
ret = 0;
continue;
}
if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL)
{
ret = 0;
continue;
}
if ((control & NETFLAG_LENGTH_MASK) != ret)
{
ret = 0;
continue;
}
break;
}
}
while (ret > 0);
if (ret == 0)
{
reason = "No Response";
// Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
return NULL;
}
if (ret == -1)
{
reason = "Network Error";
// Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
return _Datagram_FailConnect(host);
}
ret = MSG_ReadByte();
if (ret == CCREP_REJECT)
{
reason = MSG_ReadString();
Con_Printf(reason);
Q_strncpyz(m_return_reason, reason, sizeof(m_return_reason));
return _Datagram_FailConnect(host);
}
if (ret == CCREP_ACCEPT)
{
Q_memcpy(&sock->addr, &sendaddr, sizeof(struct sockaddr_qstorage));
dfunc.SetSocketPort (&sock->addr, MSG_ReadLong());
if (!Q_strcmp(NET_GAMENAME_QW, MSG_ReadString()))
sock->qwprotocol = true;
}
else
{
reason = "Bad Response";
Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
return _Datagram_FailConnect(host);
}
dfunc.GetNameFromAddr (&sendaddr, sock->address);
Con_Printf ("Connection accepted\n");
sock->lastMessageTime = SetNetTime();
// switch the connection to the specified address
if (dfunc.Connect (newsock, &sock->addr) == -1)
{
reason = "Connect to Game failed";
Con_Printf("%s\n", reason);
Q_strcpy(m_return_reason, reason);
return _Datagram_FailConnect(host);
}
m_return_onerror = false;
return sock;
}
static qsocket_t *_Datagram_BeginConnect (char *host)
{
struct sockaddr_qstorage sendaddr;
double start_time;
// see if we can resolve the host name
if (dfunc.GetAddrFromName(host, &sendaddr) == -1)
return NULL;
sock = _Datagram_ContinueConnect(host);
if (sock)
return sock;
newsock = dfunc.OpenSocket (0);
if (newsock == -1)
return _Datagram_FailConnect(host);
sock = NET_NewQSocket ();
if (sock == NULL)
return _Datagram_FailConnect(host);
sock->socket = newsock;
sock->landriver = net_landriverlevel;
// connect to the host
if (dfunc.Connect (newsock, &sendaddr) == -1)
{
return _Datagram_FailConnect(host);
}
// send the connection request
Con_Printf("trying...\n");
start_time = net_time;
SZ_Clear(&net_message);
// save space for the header, filled in later
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_CONNECT);
MSG_WriteString(&net_message, NET_GAMENAME_QW);
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr);
SZ_Clear(&net_message);
MSG_WriteLong(&net_message, 0);
MSG_WriteByte(&net_message, CCREQ_CONNECT);
MSG_WriteString(&net_message, NET_GAMENAME_NQ); //either will do
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
*((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr);
SZ_Clear(&net_message);
return NULL;
}
#endif
qsocket_t *Datagram_BeginConnect (char *host)
{
qsocket_t *ret = NULL;
#ifndef SERVERONLY
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
if (net_landrivers[net_landriverlevel].initialized)
if ((ret = _Datagram_BeginConnect (host)) != NULL)
break;
#endif
return ret;
}
qsocket_t *Datagram_ContinueConnect (char *host)
{
qsocket_t *ret = NULL;
#ifndef SERVERONLY
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
if (net_landrivers[net_landriverlevel].initialized)
if ((ret = _Datagram_ContinueConnect (host)) != NULL)
break;
#endif
return ret;
}
qsocket_t *Datagram_Connect (char *host)
{
qsocket_t *ret = NULL;
#ifndef SERVERONLY
for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
if (net_landrivers[net_landriverlevel].initialized)
if ((ret = _Datagram_Connect (host)) != NULL)
break;
#endif
return ret;
}
#endif