/*
 * "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 "mmulti_unstable.h"

//STL
//#include <vector>
//#include "buildqueue.h"

#include "network.h"

#include "fixedPoint_math.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])

void Error (int errorType, char *error, ...);

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];
int tmpmax[8]; //addfaz variable addition (you could probs think of something better)
int itmp = 0; //addfaz router fix STUN

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];

extern unsigned short g_bStun;

static short bakpacketptr[MAXPLAYERS][256], bakpacketlen[MAXPLAYERS][256];
static char bakpacketbuf[BAKSIZ];
static long bakpacketplc = 0;

short myconnectindex = 0, numplayers = 1;
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;

/*
//
// Packet queue data structs
//
typedef struct packet_wrapper
{
	unsigned long packetnumber;
	gcomtype packet;
} PACKETWRAPPER;

typedef PACKETWRAPPER packet_buffer;

unsigned long currentpacketnumber[MAXPLAYERS];

//
// Packet Buffer
//
packet_buffer pBuff[256];
*/


/*
typedef struct 
{
	unsigned char buffer[MAXPACKETSIZE];
}PACKET;
*/

//typedef std::vector<PACKET> PacketQueue;

enum ECommitCMDs
{
	COMMIT_CMD_SEND				= 1,
	COMMIT_CMD_GET              = 2,
	COMMIT_CMD_SENDTOALL        = 3,
	COMMIT_CMD_SENDTOALLOTHERS  = 4,
	COMMIT_CMD_SCORE            = 5,
};


// Queue of out going packets.
//PacketQueue outgoingPacketQueue;
//outgoingPacketQueue.reserve(128);

gcomtype *init_network_transport(char **ARGV, int argpos);
void deinit_network_transport(gcomtype *gcom);
void unstable_callcommit(void);
void dosendpackets(long other);


void unstable_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 unstable_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 unstable_initmultiplayers(char damultioption, char dacomrateoption, char dapriority)
{
	long i;

	unstable_initcrc();
	for(i=0;i<MAXPLAYERS;i++)
	{
		incnt[i] = 0L;
		outcntplc[i] = 0L;
		outcntend[i] = 0L;
		bakpacketlen[i][255] = -1;
	}

	// clear out the packet ordering
//	memset(&currentpacketnumber, 0, sizeof(unsigned long) * MAXPLAYERS);

	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)
	{
        Error(EXIT_SUCCESS, "Network transport initialization failed!\n"
							"Be sure you have closed *all* the previous instances of\n"
							"the xDuke and that no other application is using the\n"
							"same port# as Duke. If you can't solve this problem, try\n"
							"changing the port# or reboot.\n");
	}

	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;
}


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,(short)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 = COMMIT_CMD_SEND; 
			unstable_callcommit(); 
		}
}


void unstable_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 unstable_setpackettimeout(long datimeoutcount, long daresendagaincount)
{
	// Don't do this it keeps '/f4' from working
	// Though /f4 feels weird on my mouse.... slugish is the word...
	/*
	long i;

	timeoutcount = datimeoutcount;
	resendagaincount = daresendagaincount;
	for(i=0;i<numplayers;i++) lastsendtime[i] = totalclock;
	*/
}


void unstable_uninitmultiplayers(void)
{
    deinit_network_transport(gcom);
    gcom = NULL;
}

void unstable_sendlogon(void)
{
}

void unstable_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 unstable_getoutputcirclesize(void)
{
	return(0);
}

void unstable_setsocket(short newsocket)
{
}


short unstable_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 = COMMIT_CMD_GET;
	unstable_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,(short)(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 .\xFB ",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 \xFB  ",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 \xFB\xFB ",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 unstable_flushpackets()
{
#if 0
	long i;

	if (numplayers < 2) return;

	do
	{
		gcom->command = COMMIT_CMD_GET;
		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 unstable_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 unstable_callcommit(void){}

#elif UDP_NETWORKING

#  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

#define SOCKET_SHUTDOWN_BOTH 2

#include <signal.h>
#include "filesystem.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;
}

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, (int *)&fromlen);
	
	if (rc == -1)
        err = neterrno();

	/* !!! 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);
    }

    *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)
	{
		if(g_bStun) //if stun is enabled
		{
			if ((allowed_addresses[itmp].host == *ip)
			&& (allowed_addresses[itmp].port == port))
			{
				valid = 1; //only accept packets from the current player (itmp) and no one else
			}
			else
			{
				return 0; //reject packets from other players
			}
		}
		else
        {
			valid = 1;
		}
	}
    else
    {
        for (i = 1; i <= gcom->numplayers; i++)
        {
            if ((allowed_addresses[i].host == *ip)
				&& (allowed_addresses[i].port == port))

            {
				valid = i;

				//printf ("Packet received from %s:%d\n",static_ipstring(*ip), (int) port);
				
                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. He either crashed or quited abruptly. Please restart.\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);
	//printf( "IP from client %d", *ip);
    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)
{
    unsigned long flags;
    int rc = 0;

    /* set socket to be (non-)blocking. */

    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);
    }

    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, (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);
	printf("Stun is currently %s\n", (g_bStun) ? "Enabled":"Disabled");

    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);

    {
        /* !!! FIXME: Might be Linux (not Unix, not BSD, not WinSock) specific. */
        int flags = 1;
        setsockopt(udpsocket, SOL_IP, IP_RECVERR, &flags, sizeof (flags));
    }

    memset(&addr, '\0', sizeof (addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(ip);
    addr.sin_port = htons((unsigned short)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) || g_bStun ; i++)
                {
				
					//only checking one player at a time works
					//this is where special formatting of allow lines comes in    
					if(g_bStun)
					{
						i = itmp; //addfaz router fix
					}
	
					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);
                    }

					// If this is stun-enabled then don't loop.
					if(g_bStun)
					{
						break;
					}
                }
            }
            first_send = 0;
            resendat += CLIENT_POLL_DELAY;
        }

        _idle();
        process_udp_send_queue();

        rc = get_udp_packet(&ip, &port, &packet, sizeof (packet));

		//this is so we don't get unexpected packet errors from players already heard from
		
		if(g_bStun)	
		{
			//addfaz router/stun addition *Start*
			//this is so we are not dealing with players already heard from.
			for (i = 0; i < max; i++)
			{
				if (ip == allowed_addresses[i].host &&
				port == allowed_addresses[i].port)
				{
					if (heard_from[i] != 0) //if we've heard from player already.
					{
						rc = 0;
					}	     
				}
			}
			//addfaz router/stun addition *End*
		}


        if ( (rc > 0) && (ip) && ((ip != myip) || (port != udpport)) )
        {
            char *ipstr = static_ipstring(ip);

            for (i = 0; i < max; i++)
            {

                ////addfaz NAT addition *START*////
				if(!g_bStun)
				{
					if(tmpmax[i] != 1)
					{					
						if (allowed_addresses[i].host == ip)
						{
							if(allowed_addresses[i].port != port)
							{	
								printf("Different player Port Number detected. %s:%i\n",ipstr,
								allowed_addresses[i].port);
								printf("Changed to %s:%i, player may be behind a firewall.\n", ipstr, port); //addfaz NAT addition
								allowed_addresses[i].port = port;
							}					
						}
					}
				}
				////addfaz NAT addition *END*////

				if ((ip == allowed_addresses[i].host) &&
                    (port == allowed_addresses[i].port))  //addfaz NAT line addition
                {
                    break;
                }

                if ((bcast) && (allowed_addresses[i].host == 0))
                    break;  /* select this slot. */
				
            }

            if (i == max)
                printf("%s:%d is not an allowed player.\n", ipstr, port);

            else if (rc != sizeof (packet))
                printf("Missized packet/packet fragment from %s:%i\n", ipstr, port);

            else if (packet.header != HEADER_PEER_GREETING)
                printf("Unexpected packet type from %s:%i\n", ipstr, port);
			
            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:%i (id 0x%X). %d player%s to go.\n",
                        ipstr, port ,(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(g_bStun)
				{
					itmp++; //addfaz router/stun addition (goto next player)
				}
				else
				{
					tmpmax[i] = 1; //addfaz line addition
				}
            }
        }
    }
	
    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)
		{
            if (udpport == allowed_addresses[i].port)
				gcom->myconnectindex = i;
		}

        printf("%s:%i is player #%i.\n", static_ipstring(ip),allowed_addresses[i].port,i);
    }
    assert(gcom->myconnectindex);

    printf("Everyone ready! We are player #%i\n", 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)
{
    return(1);
}

static void deinitialize_sockets(void)
{
}

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("UDP NETWORK TRANSPORT INITIALIZING...\n");

    ctrlc_pressed = 0;

    if (!initialize_sockets())
        return(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("UDP 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 unstable_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;


        case COMMIT_CMD_SCORE:
			// FIX_00008: minor protocol error after frags (NET TRANSPORT ERROR: Unknown command 5)

			// Leave it on, since the sendscore() function will trigger that case.
			// This will avoid to fall in the default case on each frag and flood
			// the DOS console with NET TRANSPORT ERROR: Unknown command error messages.
			// sendscore() seems to be an obsolete function, and was probably used
			// in an old netcode architecture that 3drealms forgot to remove.
			// We leave sendscore() in the code anyway since it doesnt seem to break 
			// anything. It's not needed to fix what's not broken...
			gcom->other = -1;
            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 ... */

