#include "AdeptHeaders.hpp"
#include "GameSpy.h"
#include "buildnum/buildnum.h"
#include <gameOS/GUNGameList.h>
#include <winsock.h>
#include <assert.h>
#include <mbstring.h>

#define GS_STRING_SIZE	80
#define GS_MAX_PLAYERS	32

#pragma comment(lib, "wsock32")

typedef struct tagGSPLAYER
{
	char	szName[GS_STRING_SIZE];
	char	szTeam[GS_STRING_SIZE];
	int		id;
} GSPLAYER;


#define E_GOA_WSOCKERROR	1
#define E_GOA_BINDERROR		2
#define E_GOA_DNSERROR		3
#define E_GOA_CONNERROR		4



#define MAX_SERVERS_PER_MACHINE 100

#include "nonport.h"
//#include <stdlib.h>
//#include <stdio.h>
#define HB_TIME 300000 /* 5 minutes */
#define MAX_DATA_SIZE 1400
#define INBUF_LEN 256
#define BUF_SIZE 4096

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

__forceinline int _mbscmp(const char *s1, const char *s2)
{
	return _mbscmp((const unsigned char *)s1, (const unsigned char *)s2);
}

__forceinline char * _mbscpy(char *s1, const char *s2)
{
	return (char *)_mbscpy((unsigned char *)s1, (const unsigned char *)s2);
}

__forceinline int _mbsicmp(const char *s1, const char *s2)
{
	return _mbsicmp((const unsigned char *)s1, (const unsigned char *)s2);
}

__forceinline size_t _mbslen(const char *s1)
{
	return _mbslen((const unsigned char *)s1);
}

__forceinline char * _mbsncat(char *s1, const char *s2, size_t t)
{
	return (char *)_mbsncat((unsigned char *)s1, (const unsigned char *)s2, t);
}

__forceinline char * _mbsncpy(char *s1, const char *s2, size_t t)
{
	return (char *)_mbsncpy((unsigned char *)s1, (const unsigned char *)s2, t);
}


//========================================================================
// GameSpyHost
//
// This is the class that implements the members necessary to advertise
// a GameSpy server.
//========================================================================
class _GameSpyHost : public GameSpyHost
{
	friend class GameSpyHost;

public:
	virtual bool __stdcall Release(void);
	virtual GSS __stdcall GetStatus(void);
	virtual bool __stdcall Synchronize(void);

public:
	virtual bool __stdcall BeginAdvertising(const char *szGameName, int MaxPlayers, const char *szPassword, const GUID &guidInstance, int flags);
	virtual bool __stdcall EndAdvertising(void);
	virtual bool __stdcall SetKey(const char *szKey, const char *szVal, DWORD dwPlayer);
	virtual bool __stdcall SetFlags(const int flags);
	virtual bool __stdcall AddPlayer(int id, const char *szName, const char *szTeam, int reserved);
	virtual bool __stdcall RemovePlayer(int id, const char *szName, const char *szTeam, int reserved);

private:
	char			m_szMasterServer[256];
	char			m_szSecretKey[GS_STRING_SIZE];
	GSS				m_status;

	GSPLAYER		m_player[GS_MAX_PLAYERS];
	char			m_szGameName[GS_STRING_SIZE];
	char			m_szPassworded[GS_STRING_SIZE];
	char			m_szMapName[GS_STRING_SIZE];
	char			m_szGameType[GS_STRING_SIZE];
	char			m_szHostName[GS_STRING_SIZE];
	char			m_szEngineName[GS_STRING_SIZE];
	bool			m_bInitialized;
					
	int				m_iNumPlayers;
	int				m_iMaxPlayers;
	int				m_iPort;
	GUID			m_guid;

	SOCKET			m_querysock;
	SOCKET			m_hbsock;


	unsigned long	m_lastheartbeat;
	int				m_queryid;
	int				m_packetnumber;
	int				m_qport;
	int				m_flags;

	void __stdcall basic_callback(char *szOutput, int maxlen, void *userdata);
	void __stdcall info_callback(char *szOutput, int maxlen, void *userdata);
	void __stdcall rules_callback(char *szOutput, int maxlen, void *userdata);
	void __stdcall players_callback(char *szOutput, int maxlen, void *userdata);



	int goa_init(int queryport, char *gamename, char *ip, void *userdata);
	void goa_process_queries(void);
	void goa_check_queries(void);
	void goa_check_send_heartbeat(void);
	void goa_send_statechanged(void);
	void goa_shutdown(void);

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

	void packet_send(struct sockaddr *addr, char *buffer);
	void buffer_send(struct sockaddr *sender, char *buffer, char *newdata);


	void send_basic(struct sockaddr *sender, char *outbuf);
	void send_info(struct sockaddr *sender, char *outbuf);
	void send_rules(struct sockaddr *sender, char *outbuf);
	void send_players(struct sockaddr *sender, char *outbuf);
	void send_echo(struct sockaddr *sender, char *outbuf,char *echostr);
	void send_final(struct sockaddr *sender, char *outbuf,char *validation);

};

//========================================================================
//========================================================================
GameSpyHost * __stdcall GameSpyHost::Create(char *szMasterServer)
{
	_GameSpyHost *p = new _GameSpyHost;
	if (p)
	{
		if (szMasterServer)
			strcpy(p->m_szMasterServer, szMasterServer);
		else
			strcpy(p->m_szMasterServer, GetMasterAddress());

		p->m_status			= GSS_DISCONNECTED;
		p->m_lastheartbeat	= 0;
		p->m_queryid		= 1;
		p->m_packetnumber	= 0;
		p->m_qport			= 0;
		p->m_querysock		= INVALID_SOCKET;
		p->m_hbsock			= INVALID_SOCKET;
		p->m_szGameName[0]	= 0;
		p->m_szPassworded[0]= 0;
		p->m_szMapName[0]	= 0;
		p->m_szGameType[0]	= 0;
		p->m_szHostName[0]	= 0;
		p->m_szSecretKey[0]	= 0;
		p->m_iNumPlayers	= 0;
		p->m_iMaxPlayers	= GS_MAX_PLAYERS;
		p->m_guid			= GUID_NULL;
		p->m_bInitialized	= false;
		p->m_flags			= 0;
		p->m_iPort			= 0;
	}

	return p;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::Release(void)
{
	EndAdvertising();
	delete this;
	return true;
}

//========================================================================
//========================================================================
GameSpy::GSS __stdcall _GameSpyHost::GetStatus(void)
{
	return m_status;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::Synchronize(void)
{
	goa_process_queries();
	return true;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::BeginAdvertising(const char *szGameName, int MaxPlayers, const char *szPassword, const GUID &guidInstance, int flags)
{
	EndAdvertising();

	strncpy(m_szSecretKey, GetSecretKey(), sizeof(m_szSecretKey));
	strncpy(m_szGameName, szGameName, sizeof(m_szGameName));
	if (szPassword==NULL || *szPassword==0)
		strncpy(m_szPassworded, "No", sizeof(m_szPassworded));
	else
		strncpy(m_szPassworded, "Yes", sizeof(m_szPassworded));
	m_iMaxPlayers = MaxPlayers;
	m_iNumPlayers = 0;
	m_guid = guidInstance;
	m_flags = flags;

	if (!goa_init(GetQueryPort(), GAMESPY_MASTER_ENGINENAME, "localhost", NULL))
	{
		// success
		m_bInitialized = true;
		return true;
	}

	return false;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::EndAdvertising(void)
{
	if (m_bInitialized)
	{
		//goa_send_statechanged();
		goa_shutdown();
		m_bInitialized = false;
	}

	return true;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::SetKey(const char *szKey, const char *szValue, DWORD dwPlayer)
{
	if (!strcmp(szKey, GAMELIST_KEY_MAP_TYPE))
	{
		strcpy(m_szMapName, szValue);
	}
	else if (!strcmp(szKey, GAMELIST_KEY_GAME_TYPE))
	{
		//
		// find the index in the list
		//
		int index;
		char szGameType[256];

		for (index=0 ; true ; index++)
		{
			Environment.GetSpecialGameData(0, index, sizeof(szGameType), szGameType);
			if (!_mbscmp(szGameType, szValue))
				break;
			if (szGameType[0]==0)
				break;
		}
		if (szGameType[0]==0)
		{
			for (index=0 ; true ; index++)
			{
				Environment.GetSpecialGameData(0, index, sizeof(szGameType), szGameType);
				if (!_mbsicmp(szGameType, szValue))
					break;
				if (szGameType[0]==0)
					break;
			}
			if (szGameType[0]==0)
				index--;
		}

		sprintf(m_szGameType, "%u", index);
	}
	else if (!strcmp(szKey, GAMELIST_KEY_SERVER_PORT))
	{
		m_iPort = (int)szValue;
	}
	else if (!strcmp(szKey, PLAYERLIST_KEY_CLAN_NAME))
	{
		//
		// find the player
		//
		int index=0;
		for (index=0 ; index<m_iNumPlayers ; index++)
		{
			if (dwPlayer == (int)m_player[index].id)
				break;
		}
		if (index<m_iNumPlayers)
		{
			// we found the player
			strncpy(m_player[index].szTeam, szValue, sizeof(m_player[index].szTeam));
			m_player[index].szTeam[sizeof(m_player[index].szTeam)-1]=0;
		}
	}

	goa_send_statechanged();

	return true;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::SetFlags(const int flags)
{
	m_flags = flags;
	goa_send_statechanged();
	return true;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::AddPlayer(int id, const char *szName, const char *szTeam, int reserved)
{
	if (m_iNumPlayers == m_iMaxPlayers)
		return false;

	if (szName)
	{
		strncpy(m_player[m_iNumPlayers].szName, szName, sizeof(m_player[m_iNumPlayers].szName));
		m_player[m_iNumPlayers].szName[sizeof(m_player[m_iNumPlayers].szName)-1]=0;
	}
	else
		m_player[m_iNumPlayers].szName[0]=0;

	if (szTeam)
	{
		strncpy(m_player[m_iNumPlayers].szTeam, szTeam, sizeof(m_player[m_iNumPlayers].szTeam));
		m_player[m_iNumPlayers].szTeam[sizeof(m_player[m_iNumPlayers].szTeam)-1]=0;
	}
	else
		m_player[m_iNumPlayers].szTeam[0]=0;

	m_player[m_iNumPlayers].id = id;
	m_iNumPlayers++;

	if (m_bInitialized)
		goa_send_statechanged();

	return true;
}

//========================================================================
//========================================================================
bool __stdcall _GameSpyHost::RemovePlayer(int id, const char *szName, const char *szTeam, int reserved)
{
	int i;
	for (i=0 ; i<m_iNumPlayers ; i++)
	{
		if (m_player[i].id == id)
			break;
	}

	if (i==m_iNumPlayers)
		return false;

	m_iNumPlayers--;
	strcpy(m_player[i].szName, m_player[m_iNumPlayers].szName);
	strcpy(m_player[i].szTeam, m_player[m_iNumPlayers].szTeam);
	m_player[i].id = m_player[m_iNumPlayers].id;

	if (m_bInitialized)
		goa_send_statechanged();

	return true;
}















//========================================================================
//========================================================================
const char * __stdcall GameSpy::GetSecretKey(void)
{
	static const unsigned char szKey1[] = { 0x5C,0x2B,0xCA,0x29,0x6E,0x2E,0xA4, };
	static const unsigned char szKey2[] = { 0x29,0x65,0xA8,0x71,0x0B,0x48,0xA4, };
	static char szKey[10];
	char *p = &szKey[-1];
	unsigned char const *p1 = szKey1;
	unsigned char const *p2 = szKey2;

	do
	{
		*++p = (unsigned char)(*p2++^*p1++);
	} while (*p);

	return szKey;
}

//========================================================================
//========================================================================
const char * __stdcall GameSpy::GetMasterAddress(void)
{
	static char szGameSpyAddress[256];

	int s=sizeof(szGameSpyAddress);
	gos_LoadDataFromRegistry(GAMESPY_MASTER_SERVER_KEY, szGameSpyAddress, (DWORD *)&s);
	if (s == 0)
	{
		gos_SaveStringToRegistry(GAMESPY_MASTER_SERVER_KEY, GAMESPY_MASTER_ADDR, sizeof(GAMESPY_MASTER_ADDR));
		strncpy(szGameSpyAddress, GAMESPY_MASTER_ADDR, sizeof(szGameSpyAddress));
	}

	return szGameSpyAddress;
}

//========================================================================
//========================================================================
int __stdcall GameSpy::GetHeartbeatPort(void)
{
	int port;
	int size = sizeof(port);

	gos_LoadDataFromRegistry(GAMESPY_MASTER_HEARTBEAT_PORT_KEY, &port, (DWORD *)&size);
	if (size == 0)
	{
		port = GAMESPY_MASTER_HEARTBEAT_PORT;
		gos_SaveDataToRegistry(GAMESPY_MASTER_HEARTBEAT_PORT_KEY, &port, sizeof(port));
	}

	return port;
}

//========================================================================
//========================================================================
int __stdcall GameSpy::GetServerListPort(void)
{
	int port;
	int size = sizeof(port);

	gos_LoadDataFromRegistry(GAMESPY_MASTER_SERVERLIST_PORT_KEY, &port, (DWORD *)&size);
	if (size == 0)
	{
		port = GAMESPY_MASTER_SERVERLIST_PORT;
		gos_SaveDataToRegistry(GAMESPY_MASTER_SERVERLIST_PORT_KEY, &port, sizeof(port));
	}

	return port;
}

//========================================================================
//========================================================================
int __stdcall GameSpy::GetQueryPort(void)
{
	int port;
	int size = sizeof(port);

	gos_LoadDataFromRegistry(GAMESPY_MASTER_QUERY_PORT_KEY, &port, (DWORD *)&size);
	if (size == 0)
	{
		port = GAMESPY_MASTER_QUERY_PORT;
		gos_SaveDataToRegistry(GAMESPY_MASTER_QUERY_PORT_KEY, &port, sizeof(port));
	}

	return port;
}




























































int _GameSpyHost::goa_init(int queryport, char *gamename, char *ip, void *userdata)
{
	int lasterror;
	struct sockaddr_in saddr;

	//save off query port,gamename and userdata
	m_qport = queryport;
	strcpy(m_szEngineName, gamename);

	//create our sockets
	SocketStartUp();

	do
	{
		m_querysock = socket(PF_INET, SOCK_DGRAM, 0);
		m_hbsock = socket(PF_INET, SOCK_DGRAM, 0);
		if (INVALID_SOCKET == m_querysock || INVALID_SOCKET == m_hbsock)
		{
			return E_GOA_WSOCKERROR;
		}

		get_sockaddrin(ip,m_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(m_querysock, (struct sockaddr *)&saddr, sizeof(saddr));
		if (lasterror == 0)
			break;

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

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

	if (lasterror)
		return E_GOA_BINDERROR;


	int nGameSpyPort;
	nGameSpyPort = GetHeartbeatPort();
	
	return do_connect(m_hbsock, m_szMasterServer, nGameSpyPort);

}

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

/* check_queries: Processes any waiting queries */
void _GameSpyHost::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 == m_querysock)
		return; //no sockets to work with!

	FD_ZERO ( &set );
	FD_SET ( m_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(m_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 _GameSpyHost::goa_check_send_heartbeat(void)
{
	unsigned int tc = current_time();

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

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

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

/* goa_shutdown: Cleans up the sockets and shuts down */
void _GameSpyHost::goa_shutdown(void)
{
	if (INVALID_SOCKET != m_querysock)
	{
		closesocket(m_querysock);
		m_querysock = INVALID_SOCKET;
	}
		
	if (INVALID_SOCKET != m_hbsock)
	{
		closesocket(m_hbsock);
		m_hbsock = INVALID_SOCKET;
	}
	m_lastheartbeat = 0;
	SocketShutDown();
}

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

int _GameSpyHost::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 */
int _GameSpyHost::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! */
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;
}

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

static 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 */
void _GameSpyHost::packet_send(struct sockaddr *addr, char *buffer)
{
	char keyvalue[80];
	
	if (strlen(buffer) == 0)
		return; //dont need to send an empty one!
	m_packetnumber++; //packet numbers start at 1
	sprintf(keyvalue,"\\queryid\\%d.%d",m_queryid, m_packetnumber);
	strcat(buffer,keyvalue);
#ifdef _DEBUG
	OutputDebugString("\n");
	OutputDebugString(buffer);
	OutputDebugString("\n");
#endif
	sendto(m_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 */
void _GameSpyHost::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 */
void _GameSpyHost::send_basic(struct sockaddr *sender, char *outbuf)
{
	char keyvalue[BUF_SIZE] = "";

	basic_callback(keyvalue, sizeof(keyvalue), NULL);

	buffer_send(sender, outbuf, keyvalue);


}

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

	info_callback(keyvalue, sizeof(keyvalue), NULL);
	buffer_send(sender, outbuf, keyvalue);

}

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

	rules_callback(keyvalue, sizeof(keyvalue), NULL);
	buffer_send(sender, outbuf, keyvalue);

}

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

	players_callback(keyvalue, sizeof(keyvalue), NULL);
	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*/
void _GameSpyHost::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. */
void _GameSpyHost::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 *)m_szSecretKey, strlen(m_szSecretKey), (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 */
void _GameSpyHost::parse_query(char *query, struct sockaddr *sender)
{
	query_t querytype;
	char buffer[MAX_DATA_SIZE]="";
	char *value;
	char validation[256] = "";

	m_queryid++;
	m_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 */
void _GameSpyHost::send_heartbeat(int statechanged)
{
	char buf[256];
	int ret;
	
	sprintf(buf,"\\heartbeat\\%d\\gamename\\%s",m_qport, m_szEngineName);
	if (statechanged)
		strcat(buf,"\\statechanged\\");
#ifdef _DEBUG
	OutputDebugString("send: ");
	OutputDebugString(buf);
	OutputDebugString("\n");
#endif
	ret = send(m_hbsock, buf, strlen(buf), 0);
	if (ret < 0) /* try to reconnect (tcp only) */
	{
		closesocket(m_hbsock);
		m_hbsock = socket(PF_INET, SOCK_STREAM, 0);

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

	m_lastheartbeat = current_time();
}






void _GameSpyHost::basic_callback(char *outbuf, int maxlen, void *userdata)
{
	sprintf(outbuf, "\\%s\\%s\\%s\\%s",
		GAMESPY_KEY_GAME_NAME, m_szEngineName,
		GAMESPY_KEY_PRODUCT_VERSION, CURRENT_BUILD_FULL_STRING
	);
}

void _GameSpyHost::info_callback(char *outbuf, int maxlen, void *userdata)
{
	char keyvalue[200];

	sprintf(keyvalue,"\\%s\\%s", GAMESPY_KEY_HOSTNAME, m_szGameName);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%d", GAMESPY_KEY_HOSTPORT, m_iPort);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%s", GAMESPY_KEY_MAP_TYPE, m_szMapName);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%s", GAMESPY_KEY_GAME_TYPE, m_szGameType);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%d", GAMESPY_KEY_NUM_PLAYERS, m_iNumPlayers);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%d", GAMESPY_KEY_MAX_PLAYERS, m_iMaxPlayers);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%s", GAMESPY_KEY_GAMEMODE, "");strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%d", GAMESPY_KEY_FLAGS, "");strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%s", GAMESPY_KEY_PASSWORD_PROTECTED, m_szPassworded);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\%u", GAMESPY_KEY_FLAGS, m_flags);strncat(outbuf, keyvalue, maxlen);
	sprintf(keyvalue,"\\%s\\{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", GAMESPY_KEY_INSTANCE,
		m_guid.Data1,
		m_guid.Data2,
		m_guid.Data3,
		m_guid.Data4[0],
		m_guid.Data4[1],
		m_guid.Data4[2],
		m_guid.Data4[3],
		m_guid.Data4[4],
		m_guid.Data4[5],
		m_guid.Data4[6],
		m_guid.Data4[7]);strncat(outbuf, keyvalue, maxlen);
}

void _GameSpyHost::rules_callback(char *outbuf, int maxlen, void *userdata)
{
}

void _GameSpyHost::players_callback(char *outbuf, int maxlen, void *userdata)
{
	char keyvalue[160];
	int i;

	for (i = 0; i < m_iNumPlayers ; i++)
	{
		sprintf(keyvalue,
			"\\%s_%d\\%s\\%s_%d\\%s",
			GAMESPY_PLAYER_KEY_PLAYER, i, m_player[i].szName,
			GAMESPY_PLAYER_KEY_CLAN, i, m_player[i].szTeam);

		strncat(outbuf, keyvalue, maxlen);
	}
}


