/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***                            Confidential - Westwood Studios                              ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Commando/GameSpy_QnR.cpp                     $*
 *                                                                                             *
 *                      $Author:: Greg_h                                                      $*
 *                                                                                             *
 *                     $Modtime:: 7/08/02 5:55p                                               $*
 *                                                                                             *
 *                    $Revision:: 17                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include <Gamespy\gs_patch_usage.h>
#include <Gamespy\gcdkeyserver.h>
#include "specialbuilds.h"
#include "dlgcncteaminfo.h"
#include "resource.h"
#include "listctrl.h"
#include "imagectrl.h"
#include "playertype.h"
#include "combat.h"
#include "teammanager.h"
#include "playermanager.h"
#include "player.h"
#include "soldier.h"
#include "gameinitmgr.h"
#include "gamemode.h"
#include "gamedata.h"
#include "input.h"
#include "healthbarctrl.h"
#include "basecontroller.h"
#include "building.h"
#include "damage.h"
#include "vehicle.h"
#include "assets.h"
#include "translatedb.h"
#include "WOLGMode.h"
#include <WWOnline\WOLUser.h>
#include "string_ids.h"
#include "mousemgr.h"
#include "directinput.h"
#include "GameSpy_QnR.h"
#include "verchk.h"
#include "buildnum.h"
#include "serversettings.h"
#include "consolemode.h"
#include "useroptions.h"
#include "gdcnc.h"
#include "rawfile.h"
#include "shellapi.h"
#include "netutil.h"
#include "gamespybanlist.h"

CGameSpyQnR GameSpyQnR;

#if defined(MULTIPLAYERDEMO)
	const char *CGameSpyQnR::gamename = "ccrenegadedemo";
	const char *CGameSpyQnR::bname = "Demo";
	const int CGameSpyQnR::prodid = 10063;
	const int CGameSpyQnR::cdkey_id = 0;
#elif defined(FREEDEDICATEDSERVER)
	const char *CGameSpyQnR::bname = "FDS";
	const char *CGameSpyQnR::gamename = "ccrenegade";
	const int CGameSpyQnR::prodid = 10064;
	const int CGameSpyQnR::cdkey_id = 577;
#elif defined(BETACLIENT)
	const char *CGameSpyQnR::bname = "Beta";
	const char *CGameSpyQnR::gamename = "ccrenegade";
	const int CGameSpyQnR::prodid = 10064;
	const int CGameSpyQnR::cdkey_id = 0;
#else
	const char *CGameSpyQnR::bname = "Retail";
	const char *CGameSpyQnR::gamename = "ccrenegade";
	const int CGameSpyQnR::prodid = 10064;
	const int CGameSpyQnR::cdkey_id = 577;
#endif

const char *CGameSpyQnR::default_heartbeat_list = "master.gamespy.com:27900, master.udpsoft.com:27900";



/*********
These c-style callback stubs are used by the SDK. The current game object
(passed in qr_init) is given in userdata, so we know what object to reference
**********/

void c_players_callback(char *outbuf, int maxlen, void *userdata)
{
	((CGameSpyQnR *)userdata)->players_callback(outbuf, maxlen);
}

void c_rules_callback(char *outbuf, int maxlen, void *userdata)
{
	((CGameSpyQnR *)userdata)->rules_callback(outbuf, maxlen);
}

void c_info_callback(char *outbuf, int maxlen, void *userdata)
{
	((CGameSpyQnR *)userdata)->info_callback(outbuf, maxlen);
}

void c_basic_callback(char *outbuf, int maxlen, void *userdata)
{
	((CGameSpyQnR *)userdata)->basic_callback(outbuf, maxlen);
}

/***********
A simple game object. Consists of some data and a main loop function.
***********/
CGameSpyQnR::CGameSpyQnR(void) : m_GSInit(FALSE), m_GSEnabled(FALSE)
{
	// Secret keys removed per Security review requirements. LFeenanEA - 27th January 2025
	
	//set the secret key, in a semi-obfuscated manner
	// tY1S8q = FULL , LsEwS3 = DEMO

#ifdef MULTIPLAYERDEMO
	secret_key[3] = 'R';  if (secret_key[3])
	secret_key[1] = 'E';  if (secret_key[1])
	secret_key[0] = 'M';  if (secret_key[0])
	secret_key[4] = 'O';  if (secret_key[4])
	secret_key[5] = 'V';  if (secret_key[5])
	secret_key[3] = 'E';  if (secret_key[3])
	secret_key[2] = 'D';  if (secret_key[2])
	secret_key[6] = '1'; if (secret_key[2])
	secret_key[5] = '2';  if (secret_key[5])
	secret_key[4] = '3';
#else 
	secret_key[5] = 'R';  if (secret_key[5])
	secret_key[4] = 'E';  if (secret_key[4])
	secret_key[6] = 'M'; if (secret_key[4])
	secret_key[1] = 'O';  if (secret_key[1])
	secret_key[5] = 'V';  if (secret_key[5])
	secret_key[2] = 'E';  if (secret_key[2])
	secret_key[3] = 'D';  if (secret_key[3])
	secret_key[6] = '1'; if (secret_key[3])
	secret_key[0] = '2';  if (secret_key[0])
	secret_key[3] = '3';
#endif

}

CGameSpyQnR::~CGameSpyQnR()
{
	Shutdown();
}

void CGameSpyQnR::LaunchArcade(void) {
	char *akey = "Software\\GameSpy\\GameSpy Arcade";
	BOOL launched = FALSE;
	HKEY key = NULL;
	int result = 0;

	result = RegOpenKeyEx(HKEY_CURRENT_USER, akey, 0, KEY_READ, &key);
	if (result == ERROR_SUCCESS) {
		StringClass value(true);
		//
		//	Get the size of the entry
		//
		DWORD data_size = 0;
		DWORD type = 0;
		result = ::RegQueryValueEx ((HKEY)key, "InstDir", NULL, &type, NULL, &data_size);
		if (result == ERROR_SUCCESS && type == REG_SZ) {

			//
			//	Read the entry from the registry
			//
			::RegQueryValueEx ((HKEY)key, "InstDir", NULL, &type,
				(LPBYTE)value.Get_Buffer(data_size), &data_size);
		}
		if (!value.Is_Empty()) {
			if (value[value.Get_Length()-1] == '\\') {
				value += "Aphex.exe";
			} else {
				value += "\\Aphex.exe";
			}

			RawFileClass file(value);
			if (file.Is_Available()) {
				StringClass params("+svc ");
				params += gamename;
				if (((int)ShellExecute (NULL, "open", value, params, NULL, SW_SHOW)) > 32) {
					launched = TRUE;
				}
			}
		}
	}

	if (!launched) {
		char url[1000] = "";

#ifdef MULTIPLAYERDEMO
		::strcpy(url, "http://www.gamespyarcade.com/features/launch.asp?svcname=ccrenegadedemo&distID=432");
#else
		::strcpy(url, "http://www.gamespyarcade.com/features/launch.asp?svcname=ccrenegade&distID=391");
#endif

		ShellExecute (NULL, "open", url, NULL, NULL, SW_SHOW);
	}
}
void CGameSpyQnR::Shutdown(void) {

#ifndef BETACLIENT
	if (m_GSInit) {
		/*
		We don't really need to set the mode to exiting here, since we immediately
		send the statechanged heartbeat and kill off the query sockets 
		gamemode = "exiting";*/
		ConsoleBox.Print("Shutting down GameSpy Q&R\n");
		qr_send_exiting(query_reporting_rec);
		qr_shutdown(query_reporting_rec);
		m_GSEnabled = m_GSInit = false;
	}
#endif
}

void CGameSpyQnR::TrackUsage(void) {

#ifndef WWDEBUG
	char filename[MAX_PATH];
	GetModuleFileName(NULL, filename, sizeof(filename));
	VS_FIXEDFILEINFO version;
	GetVersionInfo(filename, &version);
	int ver = version.dwFileVersionMS;

	StringClass b(true);
	b.Format("%s %s V%d.%3.3d(%s-%d)", "Win-X86", bname, (ver&0xffff0000)>>16, ver&0xffff, 
		BuildInfoClass::Get_Builder_Initials(), BuildInfoClass::Get_Build_Number());

	// Send off usage Tracking info to GameSpy
	ptTrackUsage(0, prodid, b.Peek_Buffer(), (cUserOptions::Sku.Get()&0xff)+438, false); 
#endif // WWDEBUG
}

void CGameSpyQnR::Init(void) {

#ifndef BETACLIENT

	if (m_GSEnabled && !m_GSInit && The_Game() && The_Game()->Get_Game_Type() == cGameData::GAME_TYPE_CNC) {
	
		ConsoleBox.Print("Initializing GameSpy Q&R\n");

		BOOL test = FALSE;
		// Init the GameSpy QnR engine
		extern ULONG g_ip_override;
		char ipstr[32];
		char *ip = ipstr;

		if (g_ip_override == INADDR_NONE) {
			if (cUserOptions::PreferredGameSpyNic.Get()) {
				strcpy(ip, cNetUtil::Address_To_String(cUserOptions::PreferredGameSpyNic.Get()));
			} else {
				ip = NULL;
			}
		} else {
			strcpy(ip, cNetUtil::Address_To_String(g_ip_override));
		}

		if (!get_master_count()) {
			GameSpyQnR.Parse_HeartBeat_List(Get_Default_HeartBeat_List());
		}
		test = qr_init(&query_reporting_rec, ip, cUserOptions::GameSpyQueryPort.Get(),
			gamename, secret_key, c_basic_callback, c_info_callback, c_rules_callback, 
			c_players_callback, this);
		WWASSERT(!test);
		gcd_init_qr(query_reporting_rec, cdkey_id);

		StartTime = time(NULL);
		m_GSInit = TRUE;
	}
#endif
}

/*******
 DoGameStuff
Simulate whatever else a game server does 
********/
void CGameSpyQnR::DoGameStuff(void)
{
//	Sleep(100);
}

#define BANLIST_RELOAD_TIME (1*60*1000)

/*******************
CGameSpyQnR::run
Simulates a main game loop
*****************/
void CGameSpyQnR::Think()
{
	static DWORD stime = (DWORD)(0 - BANLIST_RELOAD_TIME);
	static DWORD ttime = 0;

	if (TIMEGETTIME() - stime > BANLIST_RELOAD_TIME) {
		GameSpyBanList.LoadBans();
		stime = TIMEGETTIME();
	}
	// check twice a second
	if (TIMEGETTIME() - ttime > 500) {
		GameSpyBanList.Think();
		ttime = TIMEGETTIME();
	}
	
#ifndef BETACLIENT
//	DoGameStuff();
	if (m_GSInit && m_GSEnabled && GameInitMgrClass::Is_LAN_Initialized() &&
			!CombatManager::Is_Loading_Level()) {
		qr_process_queries(query_reporting_rec);
		gcd_think();
	}
#endif
}

/*************
basic_callback
sends a (sample) response to the basic query 
includes the following keys:
\gamename\
\gamever\
\location\
*************/
void CGameSpyQnR::basic_callback(char *outbuf, int maxlen)
{

	WWDEBUG_SAY(("-->GS_QnR -- Basic callback\n"));
	WWASSERT(!CombatManager::Is_Loading_Level());

	if (!maxlen || !outbuf) return;

	outbuf[0] = 0;

	StringClass b(true);
	b.Format("%d", BuildInfoClass::Get_Build_Number());
	sprintf(outbuf, "\\gamename\\%s\\gamever\\%s", gamename, b.Peek_Buffer());
//	sprintf(outbuf, "\\gamename\\%s\\gamever\\%s\\location\\%d", gamename, b.Peek_Buffer(), 1);

#ifdef WWDEBUG
	StringClass tstr(true);
	tstr.Format("GS_QnR -- Basic callback, sent: %s\n",outbuf);
	OutputDebugString(tstr.Peek_Buffer());
#endif
	WWDEBUG_SAY(("<--GS_QnR -- Basic callback\n"));

//	printf("Basic callback, sent: %s\n\n",outbuf);
}

/************
info_callback
Sends a (sample) response to the info query 
including the following keys:
\hostname\
\hostport\
\mapname\
\gametype\
\numplayers\
\maxplayers\
\gamemode\
************/
void CGameSpyQnR::info_callback(char *outbuf, int maxlen)
{

	WWDEBUG_SAY(("-->GS_QnR -- Info callback\n"));
	WWASSERT(!CombatManager::Is_Loading_Level());

	if (!maxlen || !outbuf) return;

	outbuf[0] = 0;

	while(1) {

		StringClass value(true);

		WideStringClass(The_Game()->Get_Game_Title()).Convert_To(value);
		if (value.Get_Length() > 25) {
			value[25] = 0;
		}
		if (!Append_InfoKey_Pair(outbuf, maxlen, "hostname", value)) break;

		value.Format("%d", The_Game()->Get_Port());
		if (!Append_InfoKey_Pair(outbuf, maxlen, "hostport", value)) break;

		value = The_Game()->Get_Map_Name();
		char *s = value.Peek_Buffer();
		char *t = strrchr(s, '.');
		if (t) value.Erase(t-s, s+strlen(s)-t);
		if (!Append_InfoKey_Pair(outbuf, maxlen, "mapname", value)) break;

		value = The_Game()->Get_Mod_Name();
		if (!value.Is_Empty()) {
			s = value.Peek_Buffer();
			t = strrchr(s, '.');
			if (t) value.Erase(t-s, s+strlen(s)-t);
			if (!Append_InfoKey_Pair(outbuf, maxlen, "gametype", value)) break;
		} else {
			if (!Append_InfoKey_Pair(outbuf, maxlen, "gametype", "C&C")) break;
		}

		int pcount = 0;
		for (SLNode<cPlayer> *player_node = cPlayerManager::Get_Player_Object_List ()->Head ()
				; player_node != NULL; player_node = player_node->Next ()) {

			cPlayer *player = player_node->Data ();
			WWASSERT (player != NULL);

			if (player->Get_Is_Active().Is_False()) {
//			if (player->Get_Is_Active().Is_False() || !player->Is_Human()) {
				continue;
			}
			pcount++;
		}

		value.Format("%d", pcount);
		if (!Append_InfoKey_Pair(outbuf, maxlen, "numplayers", value)) break;
		value.Format("%d", The_Game()->Get_Max_Players());
		if (!Append_InfoKey_Pair(outbuf, maxlen, "maxplayers", value)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "gamemode", "openplaying")) break;
		break;
	}

#ifdef WWDEBUG
	StringClass tstr(true);
	tstr.Format("GS_QnR -- Info callback, sent: %s\n",outbuf);
	OutputDebugString(tstr.Peek_Buffer());
#endif
	WWDEBUG_SAY(("<--GS_QnR -- Info callback\n"));

}

/***************
rules_callback
Sends a response to the rules query. You may
need to add custom fields for your game in here. Some are provided
as an example 
The following rules are included:
\timelimit\
\fraglimit\
\teamplay\
\rankedserver\
****************/
void CGameSpyQnR::rules_callback(char *outbuf, int maxlen)
{
	static StringClass b;
	WWDEBUG_SAY(("-->GS_QnR -- Rules callback\n"));
	WWASSERT(!CombatManager::Is_Loading_Level());

	if (!maxlen || !outbuf) return;

	outbuf[0] = 0;

	while(1) {

		StringClass value(true);
		const char *zero = "0"; const char *one = "1";

//		WideStringClass timelimit;
//		The_Game()->Get_Time_Limit_Text(timelimit);
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "timeleft", timelimit)) break;

//		value.Format("%d", The_Game()->Get_Time_Limit_Minutes());
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "timelimit", value)) break;

//		if (b.Is_Empty()) {
//			char filename[MAX_PATH];
//			GetModuleFileName(NULL, filename, sizeof(filename));
//			VS_FIXEDFILEINFO version;
//			GetVersionInfo(filename, &version);
//			int ver = version.dwFileVersionMS;
//
//			b.Format("%s %s V%d.%3.3d(%s-%d)", "Win-X86", bname, (ver&0xffff0000)>>16, ver&0xffff, 
//				BuildInfoClass::Get_Builder_Initials(), BuildInfoClass::Get_Build_Number());
//		}
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "Version", b)) break;

//		if (!Append_InfoKey_Pair(outbuf, maxlen, "ServerMOTD", WideStringClass(The_Game()->Get_Motd()))) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "OwnerName", The_Game()->Get_Owner())) break;
		value.Format("%d", (int)cUserOptions::BandwidthBps.Get());
		if (!Append_InfoKey_Pair(outbuf, maxlen, "BW", value)) break;
//		int utime = time(NULL) - StartTime;
//		value.Format("%d:%d:%d:%d", utime/60/60/24, (utime/60/60)%24, (utime/60)%60, utime%60);
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "Uptime", value)) break;
		if (!Append_InfoKey_Pair(outbuf, maxlen, "CSVR", ConsoleBox.Is_Exclusive() ? one : zero)) break;
		if (!Append_InfoKey_Pair(outbuf, maxlen, "DED", The_Game()->IsDedicated.Get() ? one : zero)) break;
		if (!Append_InfoKey_Pair(outbuf, maxlen, "DG", The_Game()->DriverIsAlwaysGunner.Get() ? one : zero)) break;
		if (!Append_InfoKey_Pair(outbuf, maxlen, "password", The_Game()->IsPassworded.Get() ? one : zero)) break;
		if (!Append_InfoKey_Pair(outbuf, maxlen, "TC", The_Game()->IsTeamChangingAllowed.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "WeaponSpawning", The_Game()->SpawnWeapons.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "TeamRemix", The_Game()->RemixTeams.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "BuildingRepair", The_Game()->CanRepairBuildings.Get() ? one : zero)) break;
		if (!Append_InfoKey_Pair(outbuf, maxlen, "FF", The_Game()->IsFriendlyFirePermitted.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "BaseDestructionEndsGame", The_Game()->As_Cnc()->BaseDestructionEndsGame.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "BeaconPlacementEndsGame", The_Game()->As_Cnc()->BeaconPlacementEndsGame.Get() ? one : zero)) break;
//		value.Format("%d", (int)The_Game()->Get_Radar_Mode());
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "RadarMode", value)) break;
		value.Format("%d", The_Game()->As_Cnc()->Get_Starting_Credits());
		if (!Append_InfoKey_Pair(outbuf, maxlen, "SC", value)) break;
		
//		cTeam * p_team;
//		for (SLNode<cTeam> * objnode = cTeamManager::Get_Team_Object_List()->Head()
//				; objnode != NULL; objnode = objnode->Next()) {
//
//			p_team = objnode->Data();
//			WWASSERT(p_team != NULL);
//
//			if (p_team->Get_Id() == PLAYERTYPE_GDI) {
//				value.Format("%.0f", p_team->Get_Score());
//				if (!Append_InfoKey_Pair(outbuf, maxlen, "GDI_Score", value)) break;
//			} else if (p_team->Get_Id() == PLAYERTYPE_NOD) {
//				value.Format("%.0f", p_team->Get_Score());
//				if (!Append_InfoKey_Pair(outbuf, maxlen, "NOD_Score", value)) break;
//			}
//		}

		// These don't apply to LAN/GameSpy Games
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "ClanMatch", The_Game()->IsClanGame.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "Laddered", The_Game()->IsLaddered.Get() ? one : zero)) break;
//		if (!Append_InfoKey_Pair(outbuf, maxlen, "QuickMatch", The_Game()->Is_QuickMatch_Server() ? one : zero)) break;


		break;

	}

#ifdef WWDEBUG
	StringClass tstr(true);
	tstr.Format("GS_QnR -- Rules callback, sent: %s\n",outbuf);
	OutputDebugString(tstr.Peek_Buffer());
#endif
	WWDEBUG_SAY(("<--GS_QnR -- Rules callback\n"));

}

BOOL CGameSpyQnR::Parse_HeartBeat_List(const char *list) {

	BOOL master_added = false;

	char *str = new char[strlen(list)+1];
	strcpy(str, list);

	char *t = str;

	clear_master_list();

	while (t) {
		WORD port = 27900;
		struct sockaddr_in taddr;
		memset(&taddr, 0, sizeof(taddr));
		taddr.sin_family = AF_INET;
		char *q = strchr(t, ',');
		if (q) *q++ = 0;
		char *s = strchr(t, ':');
		if (s) {
			// Parse off the port value
			*s++ = 0;
			char *p = s;
			if (atoi(p)) port = atoi(p);
		}
		// skip white space
		while (*t == ' ' || *t == '\t') t++;
		// process the address
		if (*t && get_sockaddrin(t, port, &taddr, NULL)) {
			add_master(&taddr);
			master_added = true;
		}
		t = q;
	}

	delete [] str;

	if (!master_added) {
		ConsoleBox.Print("Error processing HeartBeat List: <%s>\n", list);
		ConsoleBox.Print("Assigning default HeartBeat List\n");
		return false;
	}

	return true;
}

BOOL CGameSpyQnR::Append_InfoKey_Pair(char *outbuf, int maxlen, const char *key, const char *value) {

	WWASSERT(value);
	WWASSERT(outbuf);
	WWASSERT(key);

	int clen = strlen(outbuf);

	if (clen + strlen(key) + strlen(value) + 3 > (unsigned int)maxlen) return FALSE;

	char *s = new char[strlen(value)+1];
	strcpy(s, value);
	char *t = s;
	while (*t) {
		if (*t == '\\') *t = '/';
		t++;
	}
	t = strtrim(s);
	sprintf(&outbuf[clen], "\\%s\\%s", key, t);
	delete [] s;

	return TRUE;
}

BOOL CGameSpyQnR::Append_InfoKey_Pair(char *outbuf, int maxlen, const char *key, const WideStringClass &value) {
	static StringClass text;

	value.Convert_To(text);
	return Append_InfoKey_Pair(outbuf, maxlen, key, text.Peek_Buffer());
}

BOOL CGameSpyQnR::Append_InfoKey_Pair(char *outbuf, int maxlen, const char *key, const StringClass &value) {

	return Append_InfoKey_Pair(outbuf, maxlen, key, value.Peek_Buffer());
}

/***************
players_callback
sends the players and their information. 
Note that \ characters are not stripped out of player names. If
your game allows players or team names with the \ character, you will need
to strip or change it here. 
The following keys are included for each player:
\player_N\
\frags_N\
\deaths_N\
\skill_N\
\ping_N\
\team_N\
***************/
void CGameSpyQnR::players_callback(char *outbuf, int maxlen)
{

	// Send the minimum for now to reduce Bandwidth usage.
	outbuf[0] = 0;
	return;



	int pindex = 0;

	WWDEBUG_SAY(("-->GS_QnR -- Players callback\n"));
	WWASSERT(!CombatManager::Is_Loading_Level());

	if (!maxlen || !outbuf) return;

	outbuf[0] = 0;

	for (SLNode<cPlayer> *player_node = cPlayerManager::Get_Player_Object_List ()->Head ();
			player_node != NULL; 
			player_node = player_node->Next ()) {

		cPlayer *player = player_node->Data ();
		WWASSERT (player != NULL);

		if (player->Get_Is_Active().Is_False()) {
//		if (player->Get_Is_Active().Is_False() || !player->Is_Human()) {
			continue;
		}

		StringClass keyval(true);
		StringClass value(true);

		// Set the Player's Name [Team]
		value = player->Get_Name();
		if (player->Get_Player_Type() == PLAYERTYPE_NOD) {
			value += " [NOD]";
		} else if (player->Get_Player_Type() == PLAYERTYPE_GDI) {
			value += " [GDI]";
		}

		keyval.Format("player_%d", pindex);
		if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), value)) break;

		// Set the Player's Score
		keyval.Format("frags_%d", pindex);
		value.Format("%.0f", player->Get_Score());
		if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), value)) break;

		// Set the Player's Credits
//		keyval.Format("credits_%d", pindex);
//		value.Format("%.0f", player->Get_Money());
//		if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), value)) break;

		// Set the Player's Ping to Server
		keyval.Format("ping_%d", pindex);
		value.Format("%d", player->Get_Ping());
		if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), value)) break;

		// Set the Player's Kills
//		keyval.Format("kills_%d", pindex);
//		value.Format("%d", player->Get_Kills());
//		if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), value)) break;

		// Set the Player's Deaths
//		keyval.Format("deaths_%d", pindex);
//		value.Format("%d", player->Get_Deaths());
//		if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), value)) break;

/*		SmartGameObj *game_obj = player->Get_GameObj ();
		if (game_obj != NULL && game_obj->As_SoldierGameObj () != NULL) {

			// Set the Player's Class (ie: Technician,Sakura,Havok)
			keyval.Format("class_%d", pindex);
			if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), 
				WideStringClass(TRANSLATE(game_obj->Get_Translated_Name_ID())) )) break;
			
			SoldierGameObj *soldier = game_obj->As_SoldierGameObj();
			VehicleGameObj *vehicle = soldier->Get_Vehicle ();

			// If they're in a vehicle set the vehicle name
			keyval.Format("vehicle_%d", pindex);
			if (vehicle != NULL) {
				if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), 
					WideStringClass(TRANSLATE(vehicle->Get_Translated_Name_ID())) )) break;
			} else {
				if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), "None")) break;
			}
		} else {
			// If there's no gameobj then set vehicle/class to unknown
			keyval.Format("class_%d", pindex);
			if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), "Unknown")) break;
			keyval.Format("vehicle_%d", pindex);
			if (!Append_InfoKey_Pair(outbuf, maxlen, keyval.Peek_Buffer(), "Unknown")) break;
		} */

		pindex++;

	}

#ifdef WWDEBUG
	StringClass tstr(true);
	tstr.Format("GS_QnR -- Players callback, sent: %s\n",outbuf);
	OutputDebugString(tstr.Peek_Buffer());
#endif

	WWDEBUG_SAY(("<--GS_QnR -- Players callback\n"));
	return;
}
/************
We'll actually start up two completely seperate "game servers"
Each one initializes the Query & Reporting SDK and calls processs on it during
their main loop
************/
/*
int main(int argc, char* argv[])
{
	CGameSpyQnR mygame1("Test Game Server 1"), mygame2("Test Game Server 2");

	srand( GetTickCount() );
	
	printf("Press any key to quit\n");
	while (!_kbhit())
	{
		mygame1.run();
		mygame2.run();
	}
	return 0;

}
*/