#include "AdeptHeaders.hpp"
#include "GameSpy.h"
#define MAX_SERVERS_PER_MACHINE 100

#ifndef NEW_GAMESPY
/******
goautil.c
GameSpy Developer SDK

Copyright 1999 GameSpy Industries, Inc

Suite E-204
2900 Bristol Street
Costa Mesa, CA 92626
(714)549-7689
Fax(714)549-0757

******

 Please see the  GameSpy Developer SDK documentation for more
 information

 Updated 6/9/99 - DDW
  Added get_sockaddrin function, and use for resolving
  Made portable with additions from CEngine code
  Double check that we don't bind to localhost (instead use INADDR_ANY)

 Updated 9/1/99 - DDW
  Add the ability to pass in 0 as the queryport, to allocate it
  automatically.

 Updated 10/11/99 - DDW
  TCP Heartbeat support (#define TCP_HEARTBEATS)

 Updated 11/20/99 - BGW
  Split goa_process_queries into two functions: goa_check_queries() and
  goa_check_send_heartbeat().

 Updated 3/19/00 - DDW
  Added Dreamcast CE Support

******/

/********
INCLUDES
********/
#include "nonport.h"
#include "goautil.h"
#include <stdlib.h>
#include <stdio.h>


#include <string.h>
#ifndef UNDER_CE
#include <assert.h>
#else
#define assert(a)
#endif

#ifdef __cplusplus
extern "C" {
#endif

/********
DEFINES
********/
#define HB_TIME 300000 /* 5 minutes */
#define MAX_DATA_SIZE 1400
#define INBUF_LEN 256
#define BUF_SIZE 1400

/*****
TCP_HEARTBEATS
Define this to use reliable heartbeats. Only needed for certain
classes of peer-to-peer games, please contact us if you are unsure
whether you need this or not.
******/
//#define TCP_HEARTBEATS

/********
GLOBAL VARS
********/
goa_querycallback_t goa_basic_callback = NULL;
goa_querycallback_t goa_info_callback = NULL;
goa_querycallback_t goa_rules_callback = NULL;
goa_querycallback_t goa_players_callback = NULL;
char goa_secret_key[256];

/********
TYPEDEFS
********/
typedef unsigned char uchar;

typedef enum {qtunknown, qtbasic, qtinfo, qtrules, qtplayers, qtstatus, qtpackets, qtecho, qtsecure} query_t;


/********
VARS
********/
static const char *queries[]={"","basic","info","rules","players","status","packets", "echo", "secure"};

static unsigned long lastheartbeat = 0;
static int queryid = 1;
static int packetnumber;
static int qport;
static void *udata = NULL;
static char gname[24];

static SOCKET querysock = INVALID_SOCKET;
static SOCKET hbsock = INVALID_SOCKET;


/********
PROTOTYPES
********/
static void send_heartbeat(int statechanged);
static void parse_query(char *query, struct sockaddr *sender);
static int get_sockaddrin(char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent);
static int do_connect(SOCKET sock, char *addr, int port);


/****************************************************************************/
/* PUBLIC FUNCTIONS */
/****************************************************************************/

/* goa_init: Initializes the sockets, etc. Returns an error value
if an error occured, or 0 otherwise */
int goa_init(int queryport, char *gamename, char *ip, void *userdata)
{
	int lasterror;
	struct sockaddr_in saddr;
	int saddrlen;

	//save off query port,gamename and userdata
	qport = queryport;
	strncpy(gname, gamename, 23);
	udata = userdata;

	//create our sockets
	SocketStartUp();

	do
	{
		querysock = socket(PF_INET, SOCK_DGRAM, 0);
#ifdef TCP_HEARTBEATS
		hbsock = socket(PF_INET, SOCK_STREAM, 0);
#else
		hbsock = socket(PF_INET, SOCK_DGRAM, 0);
#endif
		if (INVALID_SOCKET == querysock || INVALID_SOCKET == hbsock)
		{
			return E_GOA_WSOCKERROR;
		}

		get_sockaddrin(ip,qport,&saddr,NULL);
		if (saddr.sin_addr.s_addr == htonl(0x7F000001)) //localhost -- we don't want that!
			saddr.sin_addr.s_addr = INADDR_ANY;

		lasterror = bind(querysock, (struct sockaddr *)&saddr, sizeof(saddr));
		if (lasterror == 0)
			break;

		//shutdown(querysock, SO_BOTH);
		//shutdown(hbsock, SO_BOTH);
		closesocket(querysock);
		closesocket(hbsock);

		qport++;
	} while (qport < (queryport + MAX_SERVERS_PER_MACHINE));

	if (lasterror)
		return E_GOA_BINDERROR;


	if (qport == 0) //we need to allocate it ourselves
	{
		saddrlen = sizeof(saddr);
		lasterror = getsockname(querysock,(struct sockaddr *)&saddr, &saddrlen);
		if (lasterror)
			return E_GOA_BINDERROR;
		qport = ntohs(saddr.sin_port);
	}

	int nGameSpyPort;
	GameSpyOld::GetGameSpyAddress(GameSpyOld::szGameSpyAddress, 256);
	nGameSpyPort = GameSpyOld::GetHeartbeatPort();
	
	return do_connect(hbsock, GameSpyOld::szGameSpyAddress, nGameSpyPort);

}

/* goa_process_queries: Processes any waiting queries, and sends a
heartbeat if 5 minutes have elapsed */
void goa_process_queries(void)
{
	goa_check_send_heartbeat();
	goa_check_queries();
}

/* check_queries: Processes any waiting queries */
void goa_check_queries(void)
{
	static char indata[INBUF_LEN]; //256 byte input buffer
	struct sockaddr saddr;
	int saddrlen = sizeof(struct sockaddr);
	fd_set set;
	struct timeval timeout = {0,0};
	int error;

	if (INVALID_SOCKET == querysock)
		return; //no sockets to work with!

	FD_ZERO ( &set );
	FD_SET ( querysock, &set );

	while (true)
	{
		error = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
		if (SOCKET_ERROR == error || 0 == error)
			return;
		//else we have data
		error = recvfrom(querysock, indata, INBUF_LEN - 1, 0, &saddr, &saddrlen);
		if (error != SOCKET_ERROR)
		{
			indata[error] = '\0';
			parse_query(indata, &saddr);
		}
	}
}

/* check_send_heartbeat: Perform any scheduled outgoing
heartbeats, (every 5 minutes) */
void goa_check_send_heartbeat(void)
{
	unsigned int tc = current_time();

	if (INVALID_SOCKET == hbsock)
		return; //no sockets to work with!

	//check if we need to send a heartbet
	if (tc - lastheartbeat > HB_TIME)
		send_heartbeat(0);	
}

/* goa_send_statechanged: Sends a statechanged heartbeat, call when
your gamemode changes */
void goa_send_statechanged(void)
{
	send_heartbeat(1);
}

/* goa_shutdown: Cleans up the sockets and shuts down */
void goa_shutdown(void)
{
	if (INVALID_SOCKET != querysock)
	{
		closesocket(querysock);
		querysock = INVALID_SOCKET;
	}
		
	if (INVALID_SOCKET != hbsock)
	{
		closesocket(hbsock);
		hbsock = INVALID_SOCKET;
	}
	lastheartbeat = 0;
	SocketShutDown();
}

/****************************************************************************/

static int do_connect(SOCKET sock, char *addr, int port)
{
	struct sockaddr_in saddr;

	if (!get_sockaddrin(addr, port,&saddr,NULL))
		return E_GOA_CONNERROR;

	if (connect (sock, (struct sockaddr *) &saddr, sizeof(saddr)) == SOCKET_ERROR)
	{
		return E_GOA_CONNERROR;
	}
	return 0;
}

/* Return a sockaddrin for the given host (numeric or DNS) and port)
Returns the hostent in savehent if it is not NULL */
static int get_sockaddrin(char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent)
{
	struct hostent *hent=NULL;

	saddr->sin_family = AF_INET;
	saddr->sin_port = htons((unsigned short)port);
	if (host == NULL)
		saddr->sin_addr.s_addr = INADDR_ANY;
	else
		saddr->sin_addr.s_addr = inet_addr(host);
	
	if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host,"255.255.255.255") != 0)
	{
		hent = gethostbyname(host);
		if (!hent)
			return 0;
		saddr->sin_addr.s_addr = *(u_long *)hent->h_addr_list[0];
	}
	if (savehent != NULL)
		*savehent = hent;
	return 1;

}



/* value_for_key: this returns a value for a certain key in s, where s is a string
containing key\value pairs. If the key does not exist, it returns  NULL.
Note: the value is stored in a common buffer. If you want to keep it, make a copy! */
static char *value_for_key(const char *s, const char *key)
{
	static int valueindex;
	char *pos,*pos2;
	char keyspec[256]="\\";
	static char value[2][256];

	valueindex ^= 1;
	strcat(keyspec,key);
	strcat(keyspec,"\\");
	pos = strstr(s,keyspec);
	if (!pos)
		return NULL;
	pos += strlen(keyspec);
	pos2 = value[valueindex];
	while (*pos && *pos != '\\')
		*pos2++ = *pos++;
	*pos2 = '\0';
	return value[valueindex];
}

/*****************************************************************************/
/* Various encryption / encoding routines */

#ifndef _GUTIL
#define _GUTIL
static void swap_byte ( uchar *a, uchar *b )
{
	uchar swapByte;
	
	swapByte = *a;
	*a = *b;
	*b = swapByte;
}

static uchar encode_ct ( uchar c )
{
	if (c <  26) return (uchar)('A'+c);
	if (c <  52) return (uchar)('a'+c-26);
	if (c <  62) return (uchar)('0'+c-52);
	if (c == 62) return (uchar)('+');
	if (c == 63) return (uchar)('/');
	
	return 0;
}

void gs_encode ( uchar *ins, int size, uchar *result )
{
	int    i,pos;
	uchar  trip[3];
	uchar  kwart[4];
	
	i=0;
	while (i < size)
	{
		for (pos=0 ; pos <= 2 ; pos++, i++)
			if (i < size) trip[pos] = *ins++;
			else trip[pos] = '\0';
			kwart[0] = (uchar)(  (trip[0])       >> 2);
			kwart[1] = (uchar)((((trip[0]) &  3) << 4) + ((trip[1]) >> 4));
			kwart[2] = (uchar)((((trip[1]) & 15) << 2) + ((trip[2]) >> 6));
			kwart[3] = (uchar)(  (trip[2]) & 63);
			for (pos=0; pos <= 3; pos++) *result++ = encode_ct(kwart[pos]);
	}
	*result='\0';
}

void gs_encrypt ( uchar *key, int key_len, uchar *buffer_ptr, int buffer_len )
{
	short counter;
	uchar x, y, xorIndex;
	uchar state[256];
	
	for ( counter = 0; counter < 256; counter++) state[counter] = (uchar) counter;
	
	x = 0; y = 0;
	for ( counter = 0; counter < 256; counter++)
	{
		y = (uchar)((key[x] + state[counter] + y) % 256);
		x = (uchar)((x + 1) % key_len);
		swap_byte ( &state[counter], &state[y] );
	}
	
	x = 0; y = 0;
	for ( counter = 0; counter < buffer_len; counter ++)
	{
		x = (uchar)((x + buffer_ptr[counter] + 1) % 256);
		y = (uchar)((state[x] + y) % 256);
		swap_byte ( &state[x], &state[y] );
		xorIndex = (uchar)((state[x] + state[y]) % 256);
		buffer_ptr[counter] ^= state[xorIndex];
	}
}
/*****************************************************************************/
#endif

/* packet_send: sends a key\value packet. Appends the queryid
key\value pair. Clears the buffer */
static void packet_send(struct sockaddr *addr, char *buffer)
{
	char keyvalue[80];
	
	if (strlen(buffer) == 0)
		return; //dont need to send an empty one!
	packetnumber++; //packet numbers start at 1
	sprintf(keyvalue,"\\queryid\\%d.%d",queryid, packetnumber);
	strcat(buffer,keyvalue);
#ifdef _DEBUG
	OutputDebugString("\n");
	OutputDebugString(buffer);
	OutputDebugString("\n");
#endif
	sendto(querysock, buffer, strlen(buffer), 0, addr, sizeof(struct sockaddr));
	buffer[0]='\0';
}

/* buffer_send: appends buffer with newdata. If the combined
size would be too large, it flushes buffer first. Space is reserved
on the total size to allow for the queryid key\value */
static void buffer_send(struct sockaddr *sender, char *buffer, char *newdata)
{
	char *pos, *lastkey;
	int bcount = 0;

	if (strlen(buffer) + strlen(newdata) < MAX_DATA_SIZE - 50)
	{
		strcat(buffer, newdata);
	} else
	{//test this!
		if (strlen(newdata) > MAX_DATA_SIZE - 50) //incoming data is too big already!
		{
			lastkey = pos = newdata;
			while (pos - newdata < MAX_DATA_SIZE-50)
			{
				if ('\\' == *pos)
				{
					if (bcount % 2 == 0)
						lastkey = pos;
					bcount++;
				}
				pos++;
			}
			if (lastkey == newdata)
				return; //endless loop - single key was too big!
			*lastkey = '\0';
			buffer_send(sender, buffer, newdata);
			*lastkey = '\\';
			buffer_send(sender, buffer, lastkey); //send the rest!
		} else
		{
			packet_send(sender, buffer);
			strcpy(buffer,newdata);
		}
	}

}

/* send_basic: sends a response to the basic query */
static void send_basic(struct sockaddr *sender, char *outbuf)
{
	char keyvalue[BUF_SIZE] = "";

	assert(goa_basic_callback);
	goa_basic_callback(keyvalue, sizeof(keyvalue), udata);

	buffer_send(sender, outbuf, keyvalue);


}

/* send_info: sends a response to the info query */
static void send_info(struct sockaddr *sender, char *outbuf)
{
	char keyvalue[BUF_SIZE] = "";

	assert(goa_info_callback);
	goa_info_callback(keyvalue, sizeof(keyvalue), udata);
	buffer_send(sender, outbuf, keyvalue);

}

/* send_rules: sends a response to the rules query. */
static void send_rules(struct sockaddr *sender, char *outbuf)
{
	char keyvalue[BUF_SIZE] = "";

	assert(goa_rules_callback);
	goa_rules_callback(keyvalue, sizeof(keyvalue), udata);
	buffer_send(sender, outbuf, keyvalue);

}

/* send_players: sends the players and their information.*/
static void send_players(struct sockaddr *sender, char *outbuf)
{
	char keyvalue[BUF_SIZE] = "";

	assert(goa_players_callback);
	goa_players_callback(keyvalue, sizeof(keyvalue), udata);
	buffer_send(sender, outbuf, keyvalue);

}

/* send_echo: bounces the echostr back to sender
Note: you should always make sure that your echostr doesn't exceed the MAX_DATA_SIZE*/
static void send_echo(struct sockaddr *sender, char *outbuf,char *echostr)
{
	char keyvalue[MAX_DATA_SIZE];

	if (strlen(echostr) > MAX_DATA_SIZE - 50)
		return;
	sprintf(keyvalue,"\\echo\\%s",echostr);
	buffer_send(sender, outbuf, keyvalue);

}

/* send_final: sends the remaining data in outbuf. Appends the final
key\value to the end. Also adds validation if required. */
static void send_final(struct sockaddr *sender, char *outbuf,char *validation)
{
	char keyvalue[256];
	char encrypted_val[128]; //don't need to null terminate
	char encoded_val[200];
	int keylen;
	
	if (validation[0])
	{
		keylen = strlen(validation);
		if (keylen > 128) return;
		strcpy(encrypted_val, validation);
		gs_encrypt((uchar *)goa_secret_key, strlen(goa_secret_key), (uchar *)encrypted_val, keylen);
		gs_encode((uchar *)encrypted_val,keylen, (uchar *)encoded_val);
		sprintf(keyvalue,"\\validate\\%s",encoded_val);
		buffer_send(sender, outbuf, keyvalue);
	}
	
	sprintf(keyvalue,"\\final\\");
	buffer_send(sender, outbuf, keyvalue);
	packet_send(sender, outbuf);
}


/* parse_query: parse an incoming query (which may contain 1 or more
individual queries) and reply to each query */
static void parse_query(char *query, struct sockaddr *sender)
{
	query_t querytype;
	char buffer[MAX_DATA_SIZE]="";
	char *value;
	char validation[256] = "";

	queryid++;
	packetnumber = 0;

	for (querytype = qtbasic; querytype <= qtsecure ; (*(int *)(&querytype))++)
	{
		if ((value = value_for_key(query, queries[querytype])) != 0)
			switch (querytype)
			{
				case qtbasic:
					send_basic(sender,buffer);
					break;
				case qtinfo:
					send_info(sender,buffer);
					break;
				case qtrules:
					send_rules(sender,buffer);
					break;
				case qtplayers:
					send_players(sender,buffer);
					break;
				case qtstatus:
					send_basic(sender,buffer);
					send_info(sender,buffer);
					send_rules(sender,buffer);
					send_players(sender,buffer);
					break;
				case qtpackets:
				/*note: "packets" is NOT a real query type. It is simply here to illustrate
					how a large query would look if broken into packets */
					send_basic(sender,buffer); packet_send(sender, buffer);
					send_info(sender,buffer); packet_send(sender, buffer);
					send_rules(sender,buffer); packet_send(sender, buffer);
					send_players(sender,buffer);
					break;
				case qtecho:
					//note: \echo\value is the syntax here
					send_echo(sender,buffer,value);
					break;
				case qtsecure:
					strcpy(validation, value);
					break;
				case qtunknown:
					break;

			}
	}
	send_final(sender,buffer,validation);
}

/* send_heartbeat: Sends a heartbeat to the gamemaster,
adds \statechanged\ if statechanged != 0 */
static void send_heartbeat(int statechanged)
{
	char buf[256];
	int ret;
	
	sprintf(buf,"\\heartbeat\\%d\\gamename\\%s",qport, gname);
	if (statechanged)
		strcat(buf,"\\statechanged\\");
#ifdef _DEBUG
	OutputDebugString("send: ");
	OutputDebugString(buf);
	OutputDebugString("\n");
#endif
	ret = send(hbsock, buf, strlen(buf), 0);
	if (ret < 0) /* try to reconnect (tcp only) */
	{
		closesocket(hbsock);
		hbsock = socket(PF_INET, SOCK_STREAM, 0);

		int nGameSpyPort;
		GameSpyOld::GetGameSpyAddress(GameSpyOld::szGameSpyAddress, 256);
		nGameSpyPort = GameSpyOld::GetHeartbeatPort();
		
		if (do_connect(hbsock, GameSpyOld::szGameSpyAddress, nGameSpyPort) == 0)
		{
#ifdef _DEBUG
			OutputDebugString("send: ");
			OutputDebugString(buf);
			OutputDebugString("\n");
#endif
			send(hbsock, buf, strlen(buf), 0); /* try again */
		}
	}

	lastheartbeat = current_time();
}

#ifdef __cplusplus
}
#endif
#endif