duke3d/buildengine/mmulti.c

1546 lines
38 KiB
C
Raw Normal View History

/*
* "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
* Ken Silverman's official web site: "http://www.advsys.net/ken"
* See the included license file "BUILDLIC.TXT" for license info.
* This file has been modified from Ken Silverman's original release
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include "platform.h"
#include "pragmas.h"
#define MAXPLAYERS 16
#define BAKSIZ 16384
#define SIMULATEERRORS 0
#define SHOWSENDPACKETS 0
#define SHOWGETPACKETS 0
#define PRINTERRORS 0
#define updatecrc16(crc,dat) crc = (((crc<<8)&65535)^crctable[((((unsigned short)crc)>>8)&65535)^dat])
static long incnt[MAXPLAYERS], outcntplc[MAXPLAYERS], outcntend[MAXPLAYERS];
static char errorgotnum[MAXPLAYERS];
static char errorfixnum[MAXPLAYERS];
static char errorresendnum[MAXPLAYERS];
#if (PRINTERRORS)
static char lasterrorgotnum[MAXPLAYERS];
#endif
long crctable[256];
static char lastpacket[576], inlastpacket = 0;
static short lastpacketfrom, lastpacketleng;
extern long totalclock; /* MUST EXTERN 1 ANNOYING VARIABLE FROM GAME */
static long timeoutcount = 60, resendagaincount = 4, lastsendtime[MAXPLAYERS];
static short bakpacketptr[MAXPLAYERS][256], bakpacketlen[MAXPLAYERS][256];
static char bakpacketbuf[BAKSIZ];
static long bakpacketplc = 0;
short myconnectindex, numplayers;
short connecthead, connectpoint2[MAXPLAYERS];
char syncstate = 0;
extern int _argc;
extern char **_argv;
#define MAXPACKETSIZE 2048
typedef struct
{
short intnum; /* communication between Game and the driver */
short command; /* 1-send, 2-get */
short other; /* dest for send, set by get (-1 = no packet) */
short numbytes;
short myconnectindex;
short numplayers;
short gametype; /* gametype: 1-serial,2-modem,3-net */
short filler;
char buffer[MAXPACKETSIZE];
long longcalladdress;
} gcomtype;
static gcomtype *gcom;
#define COMMIT_CMD_SEND 1
#define COMMIT_CMD_GET 2
#define COMMIT_CMD_SENDTOALL 3
#define COMMIT_CMD_SENDTOALLOTHERS 4
#define COMMIT_CMD_SCORE 5
gcomtype *init_network_transport(char **ARGV, int argpos);
void deinit_network_transport(gcomtype *gcom);
void callcommit(void);
void initcrc(void)
{
long i, j, k, a;
for(j=0;j<256;j++) /* Calculate CRC table */
{
k = (j<<8); a = 0;
for(i=7;i>=0;i--)
{
if (((k^a)&0x8000) > 0)
a = ((a<<1)&65535) ^ 0x1021; /* 0x1021 = genpoly */
else
a = ((a<<1)&65535);
k = ((k<<1)&65535);
}
crctable[j] = (a&65535);
}
}
long getcrc(char *buffer, short bufleng)
{
long i, j;
j = 0;
for(i=bufleng-1;i>=0;i--) updatecrc16(j,buffer[i]);
return(j&65535);
}
void initmultiplayers(char damultioption, char dacomrateoption, char dapriority)
{
long i;
initcrc();
for(i=0;i<MAXPLAYERS;i++)
{
incnt[i] = 0L;
outcntplc[i] = 0L;
outcntend[i] = 0L;
bakpacketlen[i][255] = -1;
}
for (i = _argc - 1; i > 0; i--)
{
const char *arg = _argv[i];
char ch = *arg;
if ((ch == '-') || (ch == '/'))
{
if (stricmp(arg + 1, "net") == 0)
break;
}
}
if ((i == 0) || (i+1 == _argc))
{
numplayers = 1; myconnectindex = 0;
connecthead = 0; connectpoint2[0] = -1;
return;
}
gcom = init_network_transport(_argv, i+1);
if (gcom == NULL)
{
printf("Network transport initialization failed. Aborting...\n");
exit(1);
}
numplayers = gcom->numplayers;
myconnectindex = gcom->myconnectindex-1;
#if (SIMULATEERRORS != 0)
srand(myconnectindex*24572457+345356);
#endif
connecthead = 0;
for(i=0;i<numplayers-1;i++) connectpoint2[i] = i+1;
connectpoint2[numplayers-1] = -1;
for(i=0;i<numplayers;i++) lastsendtime[i] = totalclock;
}
static void dosendpackets(long other)
{
long i, j, k, messleng;
unsigned short dacrc;
if (outcntplc[other] == outcntend[other]) return;
#if (PRINTERRORS)
if (errorgotnum[other] > lasterrorgotnum[other])
{
lasterrorgotnum[other]++;
printf(" MeWant %ld",incnt[other]&255);
}
#endif
if (outcntplc[other]+1 == outcntend[other])
{ /* Send 1 sub-packet */
k = 0;
gcom->buffer[k++] = (outcntplc[other]&255);
gcom->buffer[k++] = (errorgotnum[other]&7)+((errorresendnum[other]&7)<<3);
gcom->buffer[k++] = (incnt[other]&255);
j = bakpacketptr[other][outcntplc[other]&255];
messleng = bakpacketlen[other][outcntplc[other]&255];
for(i=0;i<messleng;i++)
gcom->buffer[k++] = bakpacketbuf[(i+j)&(BAKSIZ-1)];
outcntplc[other]++;
}
else
{ /* Send 2 sub-packets */
k = 0;
gcom->buffer[k++] = (outcntplc[other]&255);
gcom->buffer[k++] = (errorgotnum[other]&7)+((errorresendnum[other]&7)<<3)+128;
gcom->buffer[k++] = (incnt[other]&255);
/* First half-packet */
j = bakpacketptr[other][outcntplc[other]&255];
messleng = bakpacketlen[other][outcntplc[other]&255];
gcom->buffer[k++] = (char)(messleng&255);
gcom->buffer[k++] = (char)(messleng>>8);
for(i=0;i<messleng;i++)
gcom->buffer[k++] = bakpacketbuf[(i+j)&(BAKSIZ-1)];
outcntplc[other]++;
/* Second half-packet */
j = bakpacketptr[other][outcntplc[other]&255];
messleng = bakpacketlen[other][outcntplc[other]&255];
for(i=0;i<messleng;i++)
gcom->buffer[k++] = bakpacketbuf[(i+j)&(BAKSIZ-1)];
outcntplc[other]++;
}
dacrc = getcrc(gcom->buffer,k);
gcom->buffer[k++] = (dacrc&255);
gcom->buffer[k++] = (dacrc>>8);
gcom->other = other+1;
gcom->numbytes = k;
#if (SHOWSENDPACKETS)
printf("Send(%ld): ",gcom->other);
for(i=0;i<gcom->numbytes;i++) printf("%2x ",gcom->buffer[i]);
printf("\n");
#endif
#if (SIMULATEERRORS != 0)
if (!(rand()&SIMULATEERRORS)) gcom->buffer[rand()%gcom->numbytes] = (rand()&255);
if (rand()&SIMULATEERRORS)
#endif
{ gcom->command = 1; callcommit(); }
}
void sendpacket(long other, char *bufptr, long messleng)
{
long i = 0;
long j = 0;
if (numplayers < 2) return;
i = 0;
if (bakpacketlen[other][(outcntend[other]-1)&255] == messleng)
{
j = bakpacketptr[other][(outcntend[other]-1)&255];
for(i=messleng-1;i>=0;i--)
if (bakpacketbuf[(i+j)&(BAKSIZ-1)] != bufptr[i]) break;
}
bakpacketlen[other][outcntend[other]&255] = messleng;
if (i < 0) /* Point to last packet to save space on bakpacketbuf */
bakpacketptr[other][outcntend[other]&255] = j;
else
{
bakpacketptr[other][outcntend[other]&255] = bakpacketplc;
for(i=0;i<messleng;i++)
bakpacketbuf[(bakpacketplc+i)&(BAKSIZ-1)] = bufptr[i];
bakpacketplc = ((bakpacketplc+messleng)&(BAKSIZ-1));
}
outcntend[other]++;
lastsendtime[other] = totalclock;
dosendpackets(other);
}
void setpackettimeout(long datimeoutcount, long daresendagaincount)
{
long i;
timeoutcount = datimeoutcount;
resendagaincount = daresendagaincount;
for(i=0;i<numplayers;i++) lastsendtime[i] = totalclock;
}
void uninitmultiplayers(void)
{
deinit_network_transport(gcom);
gcom = NULL;
}
void sendlogon(void)
{
}
void sendlogoff(void)
{
long i;
char tempbuf[2];
tempbuf[0] = 255;
tempbuf[1] = myconnectindex;
for(i=connecthead;i>=0;i=connectpoint2[i])
if (i != myconnectindex)
sendpacket(i,tempbuf,2L);
}
int getoutputcirclesize(void)
{
return(0);
}
void setsocket(short newsocket)
{
}
short getpacket (short *other, char *bufptr)
{
long i, messleng;
unsigned short dacrc;
if (numplayers < 2) return(0);
for(i=connecthead;i>=0;i=connectpoint2[i])
if (i != myconnectindex)
{
if (totalclock < lastsendtime[i]) lastsendtime[i] = totalclock;
if (totalclock > lastsendtime[i]+timeoutcount)
{
#if (PRINTERRORS)
printf(" TimeOut!");
#endif
errorgotnum[i] = errorfixnum[i]+1;
if ((outcntplc[i] == outcntend[i]) && (outcntplc[i] > 0))
{ outcntplc[i]--; lastsendtime[i] = totalclock; }
else
lastsendtime[i] += resendagaincount;
dosendpackets(i);
/* } */
}
}
if (inlastpacket != 0)
{
/* 2ND half of good double-packet */
inlastpacket = 0;
*other = lastpacketfrom;
memcpy(bufptr,lastpacket,lastpacketleng);
return(lastpacketleng);
}
gcom->command = 2;
callcommit();
#if (SHOWGETPACKETS)
if (gcom->other != -1)
{
printf(" Get(%ld): ",gcom->other);
for(i=0;i<gcom->numbytes;i++) printf("%2x ",gcom->buffer[i]);
printf("\n");
}
#endif
if (gcom->other < 0) return(0);
*other = gcom->other-1;
messleng = gcom->numbytes;
dacrc = ((unsigned short)gcom->buffer[messleng-2]);
dacrc += (((unsigned short)gcom->buffer[messleng-1])<<8);
if (dacrc != getcrc(gcom->buffer,messleng-2)) /* CRC check */
{
#if (PRINTERRORS)
printf("\n%ld CRC",gcom->buffer[0]);
#endif
errorgotnum[*other] = errorfixnum[*other]+1;
return(0);
}
while ((errorfixnum[*other]&7) != ((gcom->buffer[1]>>3)&7))
errorfixnum[*other]++;
if ((gcom->buffer[1]&7) != (errorresendnum[*other]&7))
{
errorresendnum[*other]++;
outcntplc[*other] = (outcntend[*other]&0xffffff00)+gcom->buffer[2];
if (outcntplc[*other] > outcntend[*other]) outcntplc[*other] -= 256;
}
if (gcom->buffer[0] != (incnt[*other]&255)) /* CNT check */
{
if (((incnt[*other]-gcom->buffer[0])&255) > 32)
{
errorgotnum[*other] = errorfixnum[*other]+1;
#if (PRINTERRORS)
printf("\n%ld CNT",gcom->buffer[0]);
#endif
}
#if (PRINTERRORS)
else
{
if (!(gcom->buffer[1]&128)) /* single else double packet */
printf("\n%ld cnt",gcom->buffer[0]);
else
{
if (((gcom->buffer[0]+1)&255) == (incnt[*other]&255))
{
/* GOOD! Take second half of double packet */
#if (PRINTERRORS)
printf("\n%ld-%ld .<2E> ",gcom->buffer[0],(gcom->buffer[0]+1)&255);
#endif
messleng = ((long)gcom->buffer[3]) + (((long)gcom->buffer[4])<<8);
lastpacketleng = gcom->numbytes-7-messleng;
memcpy(bufptr,&gcom->buffer[messleng+5],lastpacketleng);
incnt[*other]++;
return(lastpacketleng);
}
else
printf("\n%ld-%ld cnt ",gcom->buffer[0],(gcom->buffer[0]+1)&255);
}
}
#endif
return(0);
}
/* PACKET WAS GOOD! */
if ((gcom->buffer[1]&128) == 0) /* Single packet */
{
#if (PRINTERRORS)
printf("\n%ld <20> ",gcom->buffer[0]);
#endif
messleng = gcom->numbytes-5;
memcpy(bufptr,&gcom->buffer[3],messleng);
incnt[*other]++;
return(messleng);
}
/* Double packet */
#if (PRINTERRORS)
printf("\n%ld-%ld <20><> ",gcom->buffer[0],(gcom->buffer[0]+1)&255);
#endif
messleng = ((long)gcom->buffer[3]) + (((long)gcom->buffer[4])<<8);
lastpacketleng = gcom->numbytes-7-messleng;
inlastpacket = 1; lastpacketfrom = *other;
memcpy(bufptr,&gcom->buffer[5],messleng);
memcpy(lastpacket,&gcom->buffer[messleng+5],lastpacketleng);
incnt[*other] += 2;
return(messleng);
}
void flushpackets()
{
#if 0
long i;
if (numplayers < 2) return;
do
{
gcom->command = 2;
callcommit();
} while (gcom->other >= 0);
for(i=connecthead;i>=0;i=connectpoint2[i])
{
incnt[i] = 0L;
outcntplc[i] = 0L;
outcntend[i] = 0L;
errorgotnum[i] = 0;
errorfixnum[i] = 0;
errorresendnum[i] = 0;
lastsendtime[i] = totalclock;
}
#endif
}
void genericmultifunction(long other, char *bufptr, long messleng, long command)
{
if (numplayers < 2) return;
gcom->command = command;
gcom->numbytes = min(messleng,MAXPACKETSIZE);
copybuf(bufptr,gcom->buffer,(gcom->numbytes+3)>>2);
gcom->other = other+1;
callcommit();
}
#if STUB_NETWORKING
gcomtype *init_network_transport(char **ARGV, int argpos)
{
printf("No networking support built in.\n");
return NULL;
} /* init_network_transport */
void deinit_network_transport(gcomtype *gcom)
{
}
void callcommit(void)
{
}
#elif (defined PLATFORM_DOS)
gcomtype *init_network_transport(char **ARGV, int argpos)
{
/*
* How to talk to COMMIT is passed as a pointer to a block of memory
* that COMMIT.EXE configures...
*/
return((gcomtype *)atol(ARGV[argpos])); /* UGH! --ryan. */
} /* init_network_transport */
static union REGS regs;
#pragma aux longcall =\
"call eax",\
parm [eax]
void callcommit(void)
{
if (gcom->intnum&0xff00)
longcall(gcom->longcalladdress);
else
int386(gcom->intnum,&regs,&regs);
}
void deinit_network_transport(gcomtype *gcom)
{
/* no-op, apparently. */
}
#elif UDP_NETWORKING
#if PLATFORM_WIN32
# include <winsock.h>
# define EAGAIN WSAEWOULDBLOCK
# define EWOULDBLOCK WSAEWOULDBLOCK
# define ECONNREFUSED WSAECONNRESET
# define socklen_t size_t
# define netstrerror() win32netstrerror()
# define neterrno() WSAGetLastError()
# define sockettype SOCKET
# define socketclose(x) closesocket(x)
#else
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
# include <sys/uio.h>
# include <sys/ioctl.h>
# include <sys/time.h>
# include <errno.h>
# include <fcntl.h>
# include <time.h>
# define netstrerror() strerror(errno)
# define neterrno() errno
# define sockettype int
# define socketclose(x) close(x)
# ifndef MSG_ERRQUEUE /* legacy glibc header workaround... */
# define MSG_ERRQUEUE 0x2000
# endif
# if PLATFORM_MACOSX
# define socklen_t int
# endif
#endif
#define SOCKET_SHUTDOWN_BOTH 2
#include <signal.h>
#include "cache1d.h" /* kopen4load for cfg file. */
#include "display.h" /* getticks */
#define IPSEG1(ip) ((((unsigned int) ip) & 0xFF000000) >> 24)
#define IPSEG2(ip) ((((unsigned int) ip) & 0x00FF0000) >> 16)
#define IPSEG3(ip) ((((unsigned int) ip) & 0x0000FF00) >> 8)
#define IPSEG4(ip) ((((unsigned int) ip) & 0x000000FF) )
#define MAX_PLAYERS 16
#define BUILD_DEFAULT_UDP_PORT 1635 /* eh...why not? */
#define CLIENT_POLL_DELAY 3000 /* ms between pings at peer-to-peer startup. */
#define HEADER_PEER_GREETING 245
static sockettype udpsocket = -1;
static short udpport = BUILD_DEFAULT_UDP_PORT;
static struct {
int host;
short port;
} allowed_addresses[MAX_PLAYERS]; /* only respond to these IPs. */
volatile int ctrlc_pressed = 0;
static void siginthandler(int sigint)
{
ctrlc_pressed = 1;
}
#if PLATFORM_WIN32
/*
* Figure out what the last failing Win32 API call was, and
* generate a human-readable string for the error message.
*
* The return value is a static buffer that is overwritten with
* each call to this function.
*
* Code lifted from PhysicsFS: http://icculus.org/physfs/
*/
static const char *win32netstrerror(void)
{
static TCHAR msgbuf[255];
TCHAR *ptr = msgbuf;
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
WSAGetLastError(), /*GetLastError(),*/
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
msgbuf,
sizeof (msgbuf) / sizeof (TCHAR),
NULL
);
/* chop off newlines. */
for (ptr = msgbuf; *ptr; ptr++)
{
if ((*ptr == '\n') || (*ptr == '\r'))
{
*ptr = ' ';
break;
} /* if */
} /* for */
return((const char *) msgbuf);
} /* win32strerror */
#endif
typedef enum
{
udpmode_peer,
udpmode_server,
udpmode_client
} udpmodes;
static udpmodes udpmode = udpmode_peer;
static char *static_ipstring(int ip)
{
static char s[16];
sprintf(s, "%u.%u.%u.%u", IPSEG1(ip), IPSEG2(ip), IPSEG3(ip), IPSEG4(ip));
return(s);
}
static int send_udp_packet(int ip, short port, void *pkt, size_t pktsize)
{
/* !!! FIXME: See if this would ever block. */
/* !!! FIXME: See if this would send a partial packet. */
struct sockaddr_in addr;
int rc;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(ip);
addr.sin_port = htons(port);
rc = sendto(udpsocket, pkt, pktsize, 0,
(struct sockaddr *) &addr, sizeof (addr));
if (rc != (int) pktsize)
{
printf("sendto err rc==%d when sending %d to %s:%d [%s].\n",
rc, (int) pktsize, static_ipstring(ip), (int) port,
netstrerror());
return(0);
}
/*printf("Sent %d byte packet to %s:%d\n", (int) pktsize, static_ipstring(ip), (int) port);*/
return(1);
}
static void process_udp_send_queue(void)
{
/* Fill this in if the non-blocking socket becomes an issue. */
}
static int get_udp_packet(int *ip, short *_port, void *pkt, size_t pktsize)
{
int err = 0;
struct sockaddr_in addr;
short port;
socklen_t fromlen = sizeof (addr);
int valid = 0;
int i;
/* FIXME: Will this ever receive a partial packet? */
int rc = recvfrom(udpsocket, pkt, pktsize, 0,
(struct sockaddr *) &addr, &fromlen);
if (rc == -1)
err = neterrno();
#if !PLATFORM_WIN32
/* !!! FIXME: Linux specific? */
if (rc == -1) /* fill in the addr structure on error... */
{
socklen_t l = sizeof (addr);
recvfrom(udpsocket, NULL, 0, MSG_ERRQUEUE,
(struct sockaddr *) &addr, &l);
}
#endif
*ip = ntohl(addr.sin_addr.s_addr);
port = ntohs(addr.sin_port);
if (_port)
*_port = port;
/*
* Reject packets from unallowed IPs. Prevents (ha) DoS attacks and
* other confusion...
*/
if (gcom == NULL)
valid = 1;
else
{
for (i = 1; i <= gcom->numplayers; i++)
{
if ((allowed_addresses[i].host == *ip) &&
(allowed_addresses[i].port == port))
{
valid = i;
break;
}
}
}
if (!valid)
{
/*
static int unallowed_ip_spam = 0;
if (unallowed_ip_spam <= 100)
{
printf("Packet received from unallowed IP %s:%d\n",
static_ipstring(*ip), (int) port);
if (unallowed_ip_spam == 100)
printf("(Disabling further unallowed IP spam.)\n");
unallowed_ip_spam++;
}
*/
return(0);
}
if (rc == -1)
{
if ((err == EAGAIN) || (err == EWOULDBLOCK))
rc = 0;
else if (err == ECONNREFUSED) /* "connection reset by peer" in winsock */
{
/*
* This means that we sent a packet to an unopened port, and
* it responded by telling us to piss off. Take them out of the
* allowed list. We check gcom so that we don't worry about this
* during detection when game might still be loading elsewhere.
*/
if (gcom != NULL)
{
allowed_addresses[valid].host = 0;
printf("%s:%d refused packets. Removed from game.\n",
static_ipstring(*ip), (int) port);
}
/* !!! FIXME: Actually boot player, too. */
}
else
{
printf("recvfrom err rc==%d when getting %d from %s:%d [%s].\n",
rc, (int) pktsize, static_ipstring(*ip), (int) port,
netstrerror());
}
}
/*else printf("Got %d byte packet from %s:%d\n", (int) rc, static_ipstring(*ip), (int) port);*/
return(rc);
}
static char *read_whole_file(const char *cfgfile)
{
char *buf;
long len, rc;
long handle;
if (cfgfile == NULL)
return(NULL);
handle = kopen4load(cfgfile, 0);
if (handle == -1)
{
printf("ERROR: Failed to open config file [%s].\n", cfgfile);
return(NULL);
}
len = kfilelength(handle);
buf = (char *) malloc(len + 2);
if (!buf)
{
kclose(handle);
return(NULL);
}
rc = kread(handle, buf, len);
kclose(handle);
if (rc != len)
{
free(buf);
return(NULL);
}
buf[len] = '\0';
buf[len+1] = '\0';
return(buf);
}
static char *get_token(char **ptr)
{
char *retval;
char *p = *ptr;
if (*p == '\0')
return(NULL);
while ((*p != '\0') && (isspace(*p)))
p++;
if (*p == '\0') /* nothing but whitespace. */
return(NULL);
retval = p;
while ((*p != '\0') && (!isspace(*p)))
p++;
*p = '\0';
*ptr = p + 1;
/*printf("Got token [%s].\n", retval);*/
return(retval);
}
static int set_socket_blockmode(int onOrOff)
{
int flags;
int rc = 0;
/* set socket to be (non-)blocking. */
#if PLATFORM_WIN32
flags = (onOrOff) ? 0 : 1;
rc = (ioctlsocket(udpsocket, FIONBIO, &flags) == 0);
#else
flags = fcntl(udpsocket, F_GETFL, 0);
if (flags != -1)
{
if (onOrOff)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
rc = (fcntl(udpsocket, F_SETFL, flags) == 0);
}
#endif
if (!rc)
{
printf("set socket %sblocking failed: %s\n",
((onOrOff) ? "" : "non-"), netstrerror());
}
return(rc);
}
static int set_socket_broadcast(int onOrOff)
{
int f = (onOrOff) ? 1 : 0;
int rc;
/* give socket clearance to broadcast. */
rc = setsockopt(udpsocket, SOL_SOCKET, SO_BROADCAST, (const char *) &f, sizeof (f)) == 0;
if (!rc)
{
printf("%sset SO_BROADCAST failed: %s\n",
((onOrOff) ? "" : "un"), netstrerror());
}
return(rc);
}
static int open_udp_socket(int ip, int port)
{
struct sockaddr_in addr;
printf("Setting up UDP interface %s:%d...\n", static_ipstring(ip), port);
udpsocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (udpsocket == -1)
{
printf("socket creation failed: %s\n", netstrerror());
return(0);
}
if (!set_socket_blockmode(0))
return(0);
#if !PLATFORM_WIN32 && !PLATFORM_MACOSX && !PLATFORM_SOLARIS && !PLATFORM_FREEBSD
{
/* Linux-specific. */
int flags = 1;
setsockopt(udpsocket, SOL_IP, IP_RECVERR, &flags, sizeof (flags));
}
#endif
memset(&addr, '\0', sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(ip);
addr.sin_port = htons(port);
if (bind(udpsocket, (struct sockaddr *) &addr, sizeof (addr)) == -1)
{
printf("socket binding failed: %s\n", netstrerror());
return(0);
}
return(1);
}
/* server init. */
static int wait_for_other_players(gcomtype *gcom, int myip)
{
printf("Server code NOT implemented!\n");
return(0);
}
/* client init. */
static int connect_to_server(gcomtype *gcom, int myip)
{
printf("Client code NOT implemented!\n");
return(0);
}
typedef struct
{
unsigned char dummy1; /* so these don't confuse game after load. */
unsigned char dummy2; /* so these don't confuse game after load. */
unsigned char dummy3; /* so these don't confuse game after load. */
unsigned char header; /* always HEADER_PEER_GREETING (245). */
unsigned short id;
} PacketPeerGreeting;
static void send_peer_greeting(int ip, short port, short myid)
{
PacketPeerGreeting packet;
memset(&packet, '\0', sizeof (packet));
packet.header = HEADER_PEER_GREETING;
packet.id = BUILDSWAP_INTEL16(myid);
send_udp_packet(ip, port, &packet, sizeof (packet));
}
/* peer to peer init. */
static int connect_to_everyone(gcomtype *gcom, int myip, int bcast)
{
PacketPeerGreeting packet;
unsigned short my_id = 0;
int i;
int rc;
int ip;
short port;
int first_send = 1;
unsigned short heard_from[MAX_PLAYERS];
unsigned long resendat;
int max;
int remaining;
printf("peer-to-peer init. CTRL-C to abort...\n");
if (bcast)
{
if (gcom->numplayers > 1)
{
printf("ERROR: Can't do both 'broadcast' and 'allow'.\n");
return(0);
}
if (!set_socket_broadcast(1))
return(0);
gcom->numplayers = bcast + 1;
}
memset(heard_from, '\0', sizeof (heard_from));
while (my_id == 0) /* player number is based on id, low to high. */
my_id = (unsigned short) rand();
printf("(This client's ID for this round is 0x%X.)\n\n", my_id);
resendat = getticks();
remaining = max = gcom->numplayers - 1;
printf("Waiting for %d player%s...\n", remaining, remaining==1 ? "":"s");
if (remaining == 0)
{
printf("Hmmm...don't have time to play with myself.\n");
return(0);
}
while ((remaining) && (!ctrlc_pressed))
{
if (resendat <= getticks())
{
if (bcast)
{
printf("%sroadcasting greeting...\n", first_send ? "B":"Reb");
/* !!! FIXME: This is...probably not right. */
send_peer_greeting(0xFFFFFFFF, udpport, my_id);
}
else
{
for (i = 0; i < max; i++)
{
if (!heard_from[i])
{
printf("%sending greeting to %s:%d...\n",
first_send ? "S" : "Res",
static_ipstring(allowed_addresses[i].host),
allowed_addresses[i].port);
send_peer_greeting(allowed_addresses[i].host,
allowed_addresses[i].port,
my_id);
}
}
}
first_send = 0;
resendat += CLIENT_POLL_DELAY;
}
_idle();
process_udp_send_queue();
rc = get_udp_packet(&ip, &port, &packet, sizeof (packet));
if ( (rc > 0) && (ip) && ((ip != myip) || (port != udpport)) )
{
char *ipstr = static_ipstring(ip);
for (i = 0; i < max; i++)
{
if ((ip == allowed_addresses[i].host) &&
(port == allowed_addresses[i].port))
{
break;
}
if ((bcast) && (allowed_addresses[i].host == 0))
break; /* select this slot. */
}
if (i == max)
printf("%s is not an allowed player.\n", ipstr);
else if (rc != sizeof (packet))
printf("Missized packet/packet fragment from %s\n", ipstr);
else if (packet.header != HEADER_PEER_GREETING)
printf("Unexpected packet type from %s\n", ipstr);
else if (heard_from[i] == 0)
{
packet.id = BUILDSWAP_INTEL16(packet.id);
heard_from[i] = packet.id;
allowed_addresses[i].host = ip; /* bcast needs this. */
allowed_addresses[i].port = port;
remaining--;
printf("Heard from %s (id 0x%X). %d player%s to go.\n",
ipstr, (int) packet.id,
remaining, remaining == 1 ? "" : "s");
/* make sure they've heard from us at all... */
/* !!! FIXME: Could be fatal if packet is dropped... */
send_peer_greeting(allowed_addresses[i].host,
allowed_addresses[i].port,
my_id);
}
}
}
if (ctrlc_pressed)
{
printf("Connection attempt aborted.\n");
return(0);
}
/* ok, now everyone is talking to you. Sort them into player numbers... */
heard_from[max] = my_id; /* so we sort, too... */
allowed_addresses[max].host = myip;
allowed_addresses[max].port = udpport;
do
{
remaining = 0;
for (i = 0; i < max; i++)
{
if (heard_from[i] == heard_from[i+1]) /* blah. */
{
printf("ERROR: Two players have the same random ID!\n");
printf("ERROR: Please restart the game to generate new IDs.\n");
return(0);
}
else if (heard_from[i] > heard_from[i+1])
{
int tmpi;
short tmps;
tmps = heard_from[i];
heard_from[i] = heard_from[i+1];
heard_from[i+1] = tmps;
tmpi = allowed_addresses[i].host;
allowed_addresses[i].host = allowed_addresses[i+1].host;
allowed_addresses[i+1].host = tmpi;
tmps = allowed_addresses[i].port;
allowed_addresses[i].port = allowed_addresses[i+1].port;
allowed_addresses[i+1].port = tmps;
remaining = 1; /* yay for bubble sorting! */
}
}
} while (remaining);
/*
* Now we're sorted. But, the local player is referred to by both his
* player number and player index ZERO, so bump everyone up one to
* their actual index and fill in local player as item zero.
*/
memmove(&allowed_addresses[1], &allowed_addresses[0],
sizeof (allowed_addresses) - sizeof (allowed_addresses[0]));
allowed_addresses[0].host = myip;
gcom->myconnectindex = 0;
for (i = 1; i <= gcom->numplayers; i++)
{
ip = allowed_addresses[i].host;
if (ip == myip)
gcom->myconnectindex = i;
printf("%s is player #%d.\n", static_ipstring(ip), i);
}
assert(gcom->myconnectindex);
printf("Everyone ready! We are player #%d\n", (int) gcom->myconnectindex);
/*
* Ok, we should have specific IPs and ports for all players, and
* therefore shouldn't broadcast anymore. Disable permission to do so,
* just in case, so we aren't flooding the LAN with broadcasted packets.
*/
set_socket_broadcast(0);
return(1);
}
static int parse_ip(const char *str, int *ip)
{
int ip1, ip2, ip3, ip4;
if (stricmp(str, "any") == 0)
{
*ip = 0;
return(1);
}
if (sscanf(str, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) != 4)
{
printf("\"%s\" is not a valid IP address.\n", str);
return(0);
}
/* we _should_ check that 0 <= ip? <= 255, but it'll fail later anyhow. */
*ip = ( ((ip1 & 0xFF) << 24) |
((ip2 & 0xFF) << 16) |
((ip3 & 0xFF) << 8) |
((ip4 & 0xFF) ) );
return(1);
}
static int parse_interface(char *str, int *ip, short *udpport)
{
char *ptr = strchr(str, ':');
if (ptr) /* portnum specified? */
*ptr = '\0';
if (!parse_ip(str, ip))
return(0);
*udpport = BUILD_DEFAULT_UDP_PORT;
if (ptr != NULL) /* port specified? */
{
ptr++;
if (stricmp(ptr, "any") == 0)
*udpport = 0;
else
*udpport = (short) atoi(ptr);
}
return(1);
}
static int initialize_sockets(void)
{
#if PLATFORM_WIN32
int rc;
WSADATA data;
printf("initializing WinSock...\n");
rc = WSAStartup(0x0101, &data);
if (rc != 0)
{
printf("WinSock failed to initialize! [err==%d].\n", rc);
return(0);
}
else
{
printf("WinSock initialized.\n");
printf(" - Caller uses version %d.%d, highest supported is %d.%d.\n",
data.wVersion >> 8, data.wVersion & 0xFF,
data.wHighVersion >> 8, data.wHighVersion & 0xFF);
printf(" - Implementation description: [%s].\n", data.szDescription);
printf(" - System status: [%s].\n", data.szSystemStatus);
printf(" - Max sockets: %d.\n", data.iMaxSockets);
printf(" - Max UDP datagram size: %d.\n", data.iMaxUdpDg);
}
#endif
return(1);
}
static void deinitialize_sockets(void)
{
#if PLATFORM_WIN32
WSACleanup();
#endif
}
static int parse_udp_config(const char *cfgfile, gcomtype *gcom)
{
char *buf;
char *tok;
char *ptr;
int ip = 0; /* interface */
int bcast = 0;
buf = read_whole_file(cfgfile); /* we must free this. */
if (buf == NULL)
return(0);
ptr = buf;
while ((tok = get_token(&ptr)) != NULL)
{
int bogus = 1;
if (stricmp(tok, "interface") == 0)
{
if ( (tok = get_token(&ptr)) &&
(parse_interface(tok, &ip, &udpport)) )
{
bogus = 0;
}
printf("Interface %s:%d chosen.\n",
static_ipstring(ip), (int) udpport);
}
else if (stricmp(tok, "mode") == 0)
{
if ((tok = get_token(&ptr)) != NULL)
{
bogus = 0;
if (stricmp(tok, "server") == 0)
udpmode = udpmode_server;
else if (stricmp(tok, "client") == 0)
udpmode = udpmode_client;
else if (stricmp(tok, "peer") == 0)
udpmode = udpmode_peer;
else
bogus = 1;
if (!bogus)
printf("You want to be in [%s] mode\n", tok);
}
}
else if (stricmp(tok, "broadcast") == 0)
{
if ((tok = get_token(&ptr)) != NULL)
{
bcast = atoi(tok);
if (bcast > MAX_PLAYERS - 1)
{
printf("WARNING: Too many broadcast players.\n");
bcast = MAX_PLAYERS - 1;
}
bogus = 0;
}
}
else if (stricmp(tok, "allow") == 0)
{
int host;
short port=BUILD_DEFAULT_UDP_PORT;
if ((tok = get_token(&ptr)) != NULL)
{
if (gcom->numplayers >= MAX_PLAYERS - 1)
printf("WARNING: Too many allowed IP addresses.\n");
else if (parse_interface(tok, &host, &port))
{
allowed_addresses[gcom->numplayers].host = host;
allowed_addresses[gcom->numplayers].port = port;
gcom->numplayers++;
bogus = 0;
}
}
}
if (bogus)
printf("bogus token! [%s]\n", tok);
}
free(buf);
if (open_udp_socket(ip, udpport))
{
gcom->numplayers++; /* that's you. */
if (udpmode == udpmode_server)
return(wait_for_other_players(gcom, ip));
else if (udpmode == udpmode_client)
return(connect_to_server(gcom, ip));
else if (udpmode == udpmode_peer)
return(connect_to_everyone(gcom, ip, bcast));
printf("wtf?!"); /* Should be handled by a udpmode above... */
assert(0);
}
return(0);
}
gcomtype *init_network_transport(char **ARGV, int argpos)
{
gcomtype *retval;
printf("\n\nUDP NETWORK TRANSPORT INITIALIZING...\n");
ctrlc_pressed = 0;
if (!initialize_sockets())
return(NULL);
srand(time(NULL));
retval = (gcomtype *)malloc(sizeof (gcomtype));
if (retval != NULL)
{
int rc;
char *cfgfile = ARGV[argpos];
void (*oldsigint)(int);
memset(retval, '\0', sizeof (gcomtype));
memset(allowed_addresses, '\0', sizeof (allowed_addresses));
udpsocket = -1;
udpport = BUILD_DEFAULT_UDP_PORT;
udpmode = udpmode_peer;
oldsigint = signal(SIGINT, siginthandler);
rc = parse_udp_config(cfgfile, retval);
signal(SIGINT, oldsigint);
if (!rc)
{
free(retval);
deinit_network_transport(NULL);
return(NULL);
}
retval->gametype = 3; /* gametype: 1-serial,2-modem,3-net */
}
return(retval);
}
void deinit_network_transport(gcomtype *gcom)
{
printf("\n\nUDP NETWORK TRANSPORT DEINITIALIZING...\n");
if (gcom != NULL)
{
printf(" ...freeing gcom structure...\n");
free(gcom);
}
if (udpsocket != -1)
{
printf(" ...closing socket...\n");
set_socket_blockmode(1); /* block while socket drains. */
shutdown(udpsocket, SOCKET_SHUTDOWN_BOTH);
socketclose(udpsocket);
udpsocket = -1;
}
deinitialize_sockets();
printf("UDP net deinitialized successfully.\n");
}
void callcommit(void)
{
int ip, i, rc;
short port;
if (udpsocket == -1)
return;
process_udp_send_queue();
switch (gcom->command)
{
case COMMIT_CMD_GET:
rc = get_udp_packet(&ip, &port, gcom->buffer, sizeof(gcom->buffer));
if (rc > 0)
{
gcom->numbytes = rc; /* size of new packet. */
for (i = 1; i <= gcom->numplayers; i++)
{
if ( (allowed_addresses[i].host == ip) &&
(allowed_addresses[i].port == port) )
{
gcom->other = i;
return;
}
}
/* if you manage to hit this, it'll report no packet avail. */
}
gcom->numbytes = 0;
gcom->other = -1; /* no packet available. */
break;
case COMMIT_CMD_SEND:
if ((gcom->other < 0) || (gcom->other > gcom->numplayers))
{
printf("NET TRANSPORT ERROR: send to player out of range\n");
return;
}
ip = allowed_addresses[gcom->other].host;
if (ip == 0) /* dropped player? */
return;
port = allowed_addresses[gcom->other].port;
if (!send_udp_packet(ip, port, gcom->buffer, gcom->numbytes))
{
printf("NET TRANSPORT ERROR: send failed to %s:%d\n",
static_ipstring(ip), (int) port);
}
break;
case COMMIT_CMD_SENDTOALL:
/* skip player zero, 'cause that's a duplicate of local IP. */
for (i = 1; i <= gcom->numplayers; i++)
{
ip = allowed_addresses[i].host;
if (ip == 0) /* dropped player? */
continue;
port = allowed_addresses[i].port;
if (!send_udp_packet(ip, port, gcom->buffer, gcom->numbytes))
{
printf("NET TRANSPORT ERROR: send failed to %s:%d\n",
static_ipstring(ip), (int) port);
}
}
break;
case COMMIT_CMD_SENDTOALLOTHERS:
/* skip player zero, 'cause that's a duplicate of local IP. */
for (i = 1; i <= gcom->numplayers; i++)
{
if (i == gcom->myconnectindex) /* local player. */
continue;
ip = allowed_addresses[i].host;
if (ip == 0) /* dropped player? */
continue;
port = allowed_addresses[i].port;
if (!send_udp_packet(ip, port, gcom->buffer, gcom->numbytes))
{
printf("NET TRANSPORT ERROR: send failed to %s:%d\n",
static_ipstring(ip), (int) port);
}
}
break;
/* Gets used, but doesn't seem to do anything in Duke3D... */
case COMMIT_CMD_SCORE:
break;
default:
printf("NET TRANSPORT ERROR: Unknown command %d\n", gcom->command);
gcom->other = -1; /* oh well. */
break;
}
}
#else
#error Please define a network transport for your platform.
#endif
/* end of mmulti.c ... */