//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Lobby.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Description:
//
//     PC implementations of Asura_Lobby functions
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_LOBBY

// This file's header.
#include "Network/Online/Asura_Lobby.h"

// Asura headers
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_DebugText.h"
#include "Network/Asura_LanGameFinder.h"
#include "Network/Asura_Network_Session.h"
#include "Network/Asura_NetTransport_System.h"
#include "Network/Asura_ReliableSocket.h"
#include "Network/Asura_ReliableSocket_TCP.h"
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
#include "Network/Steamworks/Asura_PC_Steamworks.h"
#include "Network/Steamworks/Asura_PC_SteamworksClient.h"
#include "Network/Steamworks/Asura_PC_SteamworksConfig.h"
#include "Network/Steamworks/Asura_PC_SteamworksLeaderboards.h"
#include "Network/Steamworks/Asura_PC_SteamworksServer.h"
#include "Network/Steamworks/Asura_PC_SteamworksLobby.h"
#include "Network/Steamworks/Asura_PC_SteamworksReliableSocket.h"
#endif
#include "Network/Online/Asura_Merit.h"

//TODO: Remove
#include "Core/Asura_Multithread.h"
#include "Debugging/Asura_Logfile.h"

// Enums
// State machine for ending game update.
enum ASURA_PC_LOBBY_ENDGAMESTATE
{
	ASURA_PC_LOBBY_ENDGAMESTATE_INVALID,

	ASURA_PC_LOBBY_ENDGAMESTATE_STATS_START,			// Start writing stats
	ASURA_PC_LOBBY_ENDGAMESTATE_STATS_UPDATE,			// Update writing stats

//	ASURA_PC_LOBBY_ENDGAMESTATE_MERIT_START,			// Start writing merit
//	ASURA_PC_LOBBY_ENDGAMESTATE_MERIT_UPDATE,			// Update writing merit

	ASURA_PC_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START,	// Start writing game completion data
	ASURA_PC_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE,	// Update writing game completion data
};


// C O N S T A N T S /////////////////////////////////////////////////////////

static const float fASURA_PC_SESSION_SEARCH_TIME = 2.0f; // secs

#ifdef ASURA_USE_MERIT_SYSTEM
static const char* const szASURA_PC_MERIT_STORAGE_FILE = "logfiles\\asuramerit.sav";
static const char* const szASURA_PC_MERIT_STORAGE_FORMAT = "meritlevel=%u,meritpoints=%d\n";
#endif

#ifdef ASURA_PC_SUPPORT_STEAMWORKS
static const char* const szASURA_PC_LOBBY_MAXPLAYERSTRING = "MaxPlayers";
static const char* const szASURA_PC_LOBBY_NUMPLAYERSTRING = "NumPlayers";
#endif

// G L O B A L   V A R I A B L E S ///////////////////////////////////////////

static Asura_TickCount	g_xSessionSearchStartTickCount		= 0;

static ASURA_PC_LOBBY_ENDGAMESTATE g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_INVALID;

#ifdef ASURA_PC_SUPPORT_STEAMWORKS
static Asura_PC_SteamLobbyHandle g_xSteamLobby = Asura_PC_SteamInvalidLobbyHandle;
#endif

// G L O B A L  F U N C T I O N S ////////////////////////////////////////////

static Asura_Hash_ID GetComputerNameHash( void )
{
	char acBuffer[256];
	DWORD uBufferSize = sizeof(acBuffer);
	bool bResult = (GetComputerNameEx( ComputerNameNetBIOS, acBuffer, &uBufferSize ) != 0);
	Asura_Assert(bResult,"Failed to get computer name");
	return (bResult) ? Asura_GetHashID( acBuffer ) : ASURA_HASH_ID_UNSET;
}

static bool ConnectToNetworkSession( Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
	bool bDone = false;

	const Asura_Lobby* pxLobby = Asura_Lobby_System::GetLobby();
	Asura_Assert(pxLobby, "no lobby");
	const u_int uServerSessionID = pxLobby ? pxLobby->GetDataPtr()->m_uSessionID : 0; 
	if (!Asura_Network_Session_System::IsInitialised())
	{
		Asura_Network_Session_System::Initialise( true );
		Asura_NetTransport_System::SetServerLANReporting( false );
		Asura_LanGameFinder::SetRequestedServerSessionID(uServerSessionID);
	}

	Asura_Network_Session_System::Update();

	for (u_int u = 0; u < Asura_Network_Session_System::GetNumberOfSessions(); u++)
	{
		const Asura_Network_Session* pxSession = Asura_Network_Session_System::GetSession( u );
		if (pxSession->GetServerSessionID() == uServerSessionID)
		{
			xAddress = pxSession->GetAddress();
			xPort.m_usPort = pxSession->GetPort();
			bDone = true;

			// finished with the server session ID now
			Asura_LanGameFinder::SetRequestedServerSessionID(0);
		}
	}

	if (bDone)
	{
		if (Asura_Network_Session_System::IsInitialised())
		{
			Asura_Network_Session_System::Shutdown();
		}
	}

	return bDone;
}

// F U N C T I O N   D E F I N I T I O N S ///////////////////////////////////

bool Asura_Platform_Lobby_PlayerData::operator==( const Asura_Platform_Lobby_GameConnectData& xOther ) const
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		return (m_uSteamID == xOther.m_uSteamID);
	}
	else
#endif
	return ((m_dwProcessID == xOther.m_dwProcessID) && (m_uComputerName == xOther.m_uComputerName));
}

bool Asura_Platform_Lobby_PlayerData::operator==( const Asura_Platform_Lobby_PlayerData& xOther ) const
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		return (m_uSteamID == xOther.m_uSteamID);
	}
	else
#endif
		return ((m_dwProcessID == xOther.m_dwProcessID) && (m_uComputerName == xOther.m_uComputerName));
}

void Asura_Lobby_System::Platform_Initialise()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		// set user name
		Asura_PrintfToBfr( s_awcLocalPlayerName, L"%s", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, Asura_PC_SteamworksClient::GetSteamIDText() );
		// use the default port for the lobby TCP connection
		Asura_Lobby_System::SetTCPListenPort(ASURA_LOBBY_FIRST_PORT); 
	}
	else
	#endif
	{
		char acBuffer[256];
		DWORD uBufferSize = sizeof(acBuffer);
		// use win32 user name for now...
		if (GetUserName(acBuffer, &uBufferSize))
		{
			// if running multiple instances on this machine using different logfile directories,
			// use the number on the end to differentiate the player...
			int iIndex = -1;
#ifdef ASURA_LOGFILES_ENABLED
			extern const char* Asura_PC_FixupLogfileName(const char* szFilename, char* pcBuffer, u_int uBufferSize);
			char acFilename[MAX_PATH];
			Asura_PC_FixupLogfileName("logfiles\\z", acFilename, MAX_PATH);
			// get the number out of e.g. "logfiles999\\z"
			char* szNumber = &acFilename[strlen("logfiles")];
			if (*szNumber >= '0' && *szNumber <= '9')
			{
				char* szBackslash = strchr(szNumber, '\\');
				if (szBackslash)
				{
					*szBackslash = 0;
				}
				iIndex = atoi(szNumber);
			}

#endif
			if (iIndex == -1)
			{
				Asura_PrintfToBfr( s_awcLocalPlayerName, L"%S", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, acBuffer );
			}
			else
			{
				Asura_PrintfToBfr( s_awcLocalPlayerName, L"%S(%d)", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, acBuffer, iIndex );
			}
		}
		else
		{
			Asura_PrintfToBfr( s_awcLocalPlayerName, L"%S", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, "unknown" );
		}

		// use a dynamic port, since we have the means to communicate it and this allows multiple hosts on the same PC
		Asura_Lobby_System::SetTCPListenPort(0);
	}

	g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_INVALID;
}

#ifdef ASURA_USE_MERIT_SYSTEM
void Asura_Lobby_System::Platform_InitialiseMerit()
{
	// TODO: use some form of online storage for this via Steamworks etc.
	// This code is temporarily using the logfile dir for storage to allow 
	// multiple game instances running on the same PC to save independently.
#ifdef ASURA_LOGFILES_ENABLED
	extern const char* Asura_PC_FixupLogfileName(const char* szFilename, char* pcBuffer, u_int uBufferSize);
	char acFilename[MAX_PATH];
	const char* szFilename = Asura_PC_FixupLogfileName(szASURA_PC_MERIT_STORAGE_FILE, acFilename, MAX_PATH);
#else
	const char* szFilename = szASURA_PC_MERIT_STORAGE_FILE;
#endif

	u_int uMeritLevel = 0;
	int iMeritPoints = 0;
	FILE* pxFile = fopen(szFilename, "r");
	if (pxFile)
	{
		if(fscanf(pxFile, szASURA_PC_MERIT_STORAGE_FORMAT, &uMeritLevel, &iMeritPoints) != 2)
		{
			Asura_Assertf(false,("Error reading merit from %s.", szFilename));
		}
		fclose(pxFile);
	}

	s_xLocalPlayerMerit.SetMeritLevel(uMeritLevel);
	s_xLocalPlayerMerit.SetMeritPoints(iMeritPoints);
}
#endif

void Asura_Lobby_System::Platform_Terminate()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if( g_xSteamLobby != Asura_PC_SteamInvalidLobbyHandle )
		{
			Asura_PC_SteamworksLobby_System::LeaveLobby( g_xSteamLobby );
			g_xSteamLobby = Asura_PC_SteamInvalidLobbyHandle;
		}
	}
	else
	#endif
	{
		// clean up any outstanding sessions
		if (Asura_Network_Session_System::IsInitialised())
		{
			Asura_Network_Session_System::Shutdown();
		}

		if (Asura_NetTransport_System::IsInitialised())
		{
			Asura_NetTransport_System::SetServerLANReporting( false );
			Asura_NetTransport_System::Shutdown();
		}

		Asura_LanGameFinder::SetRequestedServerSessionID(0);
	}

	g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_INVALID;
}

void Asura_Lobby_System::Platform_Update()
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		// Update the list of voice comms peers

		if( s_pxLobby )
		{
			CSteamID	axVoiceSends[ ASURA_LOBBY_MAX_PLAYERS ];
			u_int		uNumVoiceSends = 0;

			u_char ucActiveChannelMask = 1 << Asura_Lobby_System::GetLocalPlayersActiveVoiceChannel(); 
			for ( u_int u = 0; u < s_pxLobby->GetNumPlayers(); ++u )
			{
				const Asura_Lobby_Player& xPlayerData = s_pxLobby->GetPlayerByIndex(u);
				if ( xPlayerData.GetPlatformData().m_uSteamID == Asura_PC_SteamworksClient::GetSteamID_Int64() )
				{
					// skip self
					continue;
				}

				if ( ( xPlayerData.GetVoiceChannels() & ucActiveChannelMask ) == 0 )
				{
					// skip players not connected to our active voice channel
					continue;
				}

				axVoiceSends[ uNumVoiceSends ] = CSteamID( xPlayerData.GetPlatformData().m_uSteamID );
				++uNumVoiceSends;
			}

			Asura_PC_SteamworksClient::SetVoiceSendList( &axVoiceSends[0], uNumVoiceSends );
		}
			
		if(		Asura_PC_SteamworksConfig::IsConfigured	( ) 
			&&	!Asura_PC_SteamworksConfig::IsServer	( ) )
		{
			// We are here because command line params were passed to us
			s_bInvitationPending = true;

			if( IsInitialised() )
			{
				Asura_Assert(s_pxSearchResults, "lobby system initialised but search results not allocated");
				// set search results so this is the only session we know about
				s_uNumSearchResults = 1;
				// init search results to 0.
				Asura_Mem::Set(&s_pxSearchResults[0], 0, sizeof(s_pxSearchResults[0]));

				s_pxSearchResults[0].m_uMaxPlayers = 0;
				s_pxSearchResults[0].m_uNumPlayers = 0;
				
				s_pxSearchResults[0].m_xLobbySteamID = Asura_PC_SteamworksConfig::GetAddress().m_xSteamID;

				Asura_PC_SteamworksConfig::ClearConfig();
			}
		}
	}
	else
#endif // #ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if (Asura_NetTransport_System::IsInitialised())
	{
		Asura_NetTransport_System::Update();
	}
}

void Asura_Lobby_System::Platform_Shutdown()
{
}

void Asura_Lobby_System::Platform_CreateLobby(bool bHost)
{
	Asura_Assert(s_pxLobby, "platform independent lobby must have been created first");

	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if( !bHost )
		{
			return;
		}

		// Fill in a list of meta data and create our lobby

		Asura_List<Asura_PC_Steamworks_KeyValuePair> xMetaData;

		Asura_PC_Steamworks_KeyValuePair* pxMetaData = new Asura_PC_Steamworks_KeyValuePair[ ASURA_LOBBY_NUM_INT_ATTRIBUTES + 2 ];

		Asura_Lobby_Attributes xAttributes = s_pxLobby->GetAttributes();

		for( u_int uAttrib = 0; uAttrib < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++uAttrib )
		{
			Asura_PrintfToBfr( pxMetaData[uAttrib].m_szKey, "Attribute %u", ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true, uAttrib );
			Asura_PrintfToBfr( pxMetaData[uAttrib].m_szValue, "%d", ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true, xAttributes.GetIntAttribute( uAttrib ) );
			
			xMetaData.AddToList( &pxMetaData[uAttrib] );
		}

		u_int uNextMeta = ASURA_LOBBY_NUM_INT_ATTRIBUTES;

		Asura_PrintfToBfr( pxMetaData[uNextMeta].m_szKey, szASURA_PC_LOBBY_MAXPLAYERSTRING, ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true );
		Asura_PrintfToBfr( pxMetaData[uNextMeta].m_szValue, "%u", ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true, s_pxLobby->GetMaxPlayers() );
		xMetaData.AddToList( &pxMetaData[uNextMeta] );

		++uNextMeta;

		Asura_PrintfToBfr( pxMetaData[uNextMeta].m_szKey, szASURA_PC_LOBBY_NUMPLAYERSTRING, ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true );
		Asura_PrintfToBfr( pxMetaData[uNextMeta].m_szValue, "%u", ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true, s_pxLobby->GetNumPlayers() );
		xMetaData.AddToList( &pxMetaData[uNextMeta] );

		if( g_xSteamLobby != Asura_PC_SteamInvalidLobbyHandle )
		{ 
			Asura_PC_SteamworksLobby_System::LeaveLobby( g_xSteamLobby ); 
			g_xSteamLobby = Asura_PC_SteamInvalidLobbyHandle;
		}

		g_xSteamLobby = Asura_PC_SteamworksLobby_System::CreateLobby ( &xMetaData, false );

		// clean up

		delete []pxMetaData;
	}
	else
	#endif // ASURA_PC_SUPPORT_STEAMWORKS
	if (bHost)
	{
		//For enum responses provided by LANGameFinder.
		if (!Asura_NetTransport_System::IsInitialised())
		{
			Asura_NetTransport_System::Initialise();
			Asura_NetTransport_System::SetServerLANReporting( true );
		}

		//Start hosting a session (LAN only) so other players can find this lobby
		Asura_NetTransport_System::StartServerAndHostSession( false );
		s_pxLobby->Platform_FillLobbyData();
	}
}

bool Asura_Lobby_System::Platform_UpdateLobbyCreation()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS

	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if( IsHost() )
		{
			const Asura_PC_SteamworksLobby* pxSteamLobby = Asura_PC_SteamworksLobby_System::GetLobby( g_xSteamLobby );

			if( pxSteamLobby && pxSteamLobby->IsActive() )
			{
				return true;
			}

			return false;
		}

		return true;
	}

	// Create a server for this lobby

	/*Asura_PC_Steamworks_ServerConfig xConfig;

	Asura_PrintfToBfr( xConfig.m_szServerName	, "%s", 64, true, "Server Name" );	// TODO: fill in server name
	Asura_PrintfToBfr( xConfig.m_szDescription	, "%s", 64, true, "Description" );	// TODO: fill in description
	Asura_PrintfToBfr( xConfig.m_szMapName		, "%s", 64, true, "Map Name"	);	// TODO: fill in map name
	xConfig.m_usMaxPlayers = static_cast<unsigned short>( s_pxLobby->GetMaxPlayers() );
	xConfig.m_eAuthLevel = ASURA_PC_STEAMWORKS_AUTHENTICATE_AND_VAC;

	Asura_PC_SteamworksServer::Create( &xConfig );

	// Set our attributes on the server
	char szKey   [64];
	char szValue [64];

	Asura_Lobby_Attributes xAttributes = s_pxLobby->GetAttributes();

	for( u_int uAttrib = 0; uAttrib < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++uAttrib )
	{
		Asura_PrintfToBfr( szKey, "Attribute %u", 64, true, uAttrib );
		Asura_PrintfToBfr( szValue, "%d", 64, true, xAttributes.GetIntAttribute( uAttrib ) );

		Asura_PC_SteamworksServer::AddServerProperty( szKey, szValue );
	}*/

	
	#endif

	return true;
}

void Asura_Lobby_System::Platform_BeginSearch()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		// Convert the search criteria into filters for use in steam
		Asura_PC_Steamworks_LobbyFilter axFilters[16];

		for( u_int uFilter = 0; uFilter < s_uNumSearchCriteria; ++uFilter)
		{
			// ask the backend to filter out games which don't match the attributes we need an exact match for.
			Asura_PrintfToBfr( axFilters[ uFilter ].m_szKey, "Attribute %u", ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true, s_pxSearchCriteria[uFilter].GetAttributeID() );
			axFilters[ uFilter ].m_iValue = s_pxSearchCriteria[uFilter].GetValue();
			switch( s_pxSearchCriteria[uFilter].GetSearchOperator() )
			{
				case ASURA_LOBBY_SEARCH_OPERATOR_EQ: axFilters[ uFilter ].m_eOperator = ASURA_PC_STEAMWORKS_FILTER_OP_EQUAL;		break;
				case ASURA_LOBBY_SEARCH_OPERATOR_GE: axFilters[ uFilter ].m_eOperator = ASURA_PC_STEAMWORKS_FILTER_OP_GREAT_EQUAL;	break;
				case ASURA_LOBBY_SEARCH_OPERATOR_LE: axFilters[ uFilter ].m_eOperator = ASURA_PC_STEAMWORKS_FILTER_OP_LESS_EQUAL;	break;
			}
		}

		// Fill in the list of strings representing the metadata we need from the lobby
		Asura_List< Asura_String > xMetaDataStrings;

		Asura_String* pxStrings = new Asura_String[ ASURA_LOBBY_NUM_INT_ATTRIBUTES + 2 ];

		for( u_int uAttrib = 0; uAttrib < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++uAttrib )
		{
			char szBuffer[ ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS ];

			Asura_PrintfToBfr( szBuffer, "Attribute %u", ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS, true, uAttrib );
			pxStrings[uAttrib] = szBuffer;
			xMetaDataStrings.AddToList( &pxStrings[uAttrib] );
		}

		u_int uNextMetaString = ASURA_LOBBY_NUM_INT_ATTRIBUTES;

		pxStrings[uNextMetaString] = szASURA_PC_LOBBY_MAXPLAYERSTRING;
		xMetaDataStrings.AddToList( &pxStrings[uNextMetaString] );

		++uNextMetaString;

		pxStrings[uNextMetaString] = szASURA_PC_LOBBY_NUMPLAYERSTRING;
		xMetaDataStrings.AddToList( &pxStrings[uNextMetaString] );

		Asura_PC_Steamworks::BeginLobbySearch( &axFilters[0], s_uNumSearchCriteria, &xMetaDataStrings );

		// clean up

		delete []pxStrings;
	}
	else
	#endif
	{
		Asura_Network_Session_System::Initialise();

		g_xSessionSearchStartTickCount = Asura_Timers::GetCurTicks();
	}
}

void Asura_Lobby_System::Platform_CancelSearch()
{
	if (Asura_Network_Session_System::IsInitialised())
	{
		Asura_Network_Session_System::Shutdown();
	}
}

bool Asura_Lobby_System::Platform_SearchHasFinished()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if( Asura_PC_Steamworks::IsSearchFinished() )
		{
			// Copy the search results into a format the lobby system can use
			
			s_uNumSearchResults = Asura_PC_Steamworks::GetNumSearchResults();
			s_uNumSearchResults = Asura_Maths::Min( s_uNumSearchResults, (u_int) ASURA_LOBBY_MAX_SEARCH_RESULTS );
			
			for( u_int uResult = 0; uResult < s_uNumSearchResults; ++uResult )
			{
				const Asura_PC_SteamworksLobby_Data* pxSteamResult = Asura_PC_Steamworks::GetLobbySearchResult( uResult );
				
				if( pxSteamResult )
				{
					// Convert all of the metadata into something useful for the search results
					s_pxSearchResults[uResult].m_xLobbySteamID = pxSteamResult->m_xSteamID;
					
					if( pxSteamResult->m_bMetaDataReceived )
					{
						Asura_List_ConstIt< Asura_PC_Steamworks_KeyValuePair > xIter( pxSteamResult->m_xMetaData );

						for( ; !xIter.Done(); xIter.Next() )
						{
							const Asura_PC_Steamworks_KeyValuePair* pxCurrentMeta = xIter.GetCurrent();
							if( strncmp( pxCurrentMeta->m_szKey, szASURA_PC_LOBBY_MAXPLAYERSTRING, ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS ) == 0 )
							{
								s_pxSearchResults[uResult].m_uMaxPlayers = pxCurrentMeta->GetValueAsInteger();
							}
							else if( strncmp( pxCurrentMeta->m_szKey, szASURA_PC_LOBBY_NUMPLAYERSTRING, ASURA_PC_STEAMWORKS_FILTER_MAX_CHARS ) == 0 )
							{
								s_pxSearchResults[uResult].m_uNumPlayers = pxCurrentMeta->GetValueAsInteger();
							}
							else if( strncmp( pxCurrentMeta->m_szKey, "Attribute", 9 ) == 0 )
							{
								// Copy the digit from this string

								char acNum[8];
								int iPos = 0;
								const int iOffset = 10;
								while( pxCurrentMeta->m_szKey[ iPos + iOffset ] != 0 )
								{
									acNum[ iPos ] = pxCurrentMeta->m_szKey[  iPos + iOffset ];
									++iPos;

									if( iPos == 7 )
									{
										break;
									}
								}

								acNum[ iPos ] = 0;

								int iAttribIndex = 0;
								if( Asura_StringUtil::GetIntValue( &acNum[0], &iAttribIndex ) && ( iAttribIndex < ASURA_LOBBY_NUM_INT_ATTRIBUTES ) )
								{
									s_pxSearchResults[uResult].m_xAttributes.SetIntAttribute( iAttribIndex, pxCurrentMeta->GetValueAsInteger() );
								}
							}
						}
					
						//TODO: Steamworks server name
						Asura_PrintfToBfr( s_pxSearchResults[uResult].m_awcDescription, L"Unknown Host", ASURA_LOBBY_DESCRIPTION_LENGTH, true );
					}
				}
			}

			Asura_PC_Steamworks::ClearSearch();
			return true;
		}
		return false;
	}
	else
	#endif // ASURA_PC_SUPPORT_STEAMWORKS
	{
		Asura_Network_Session_System::Update();
		const u_int uNumSessions = Asura_Network_Session_System::GetNumberOfSessions();

		const Asura_TickCount	xElapsedTicks	= Asura_Timers::GetElapsedTicks(g_xSessionSearchStartTickCount);
		const float				fElapsedSeconds	= Asura_Timers::TicksToSeconds(xElapsedTicks);

		bool bSearchComplete = (fElapsedSeconds > fASURA_PC_SESSION_SEARCH_TIME);

		if (bSearchComplete)
		{
			// copy the search results into the platform-independent data array.
			u_int uNumFilteredSessions = 0;

			for (u_int u = 0; u < uNumSessions; ++u)
			{
				// Get lobby attirbutes from platform data and filter results as we go.
				bool bReject = false;
				const Asura_Network_Session& xResult = *Asura_Network_Session_System::GetSession(u);
				Asura_Assert(xResult.GetPlatformSessionDataSize() == sizeof(Asura_Lobby_Attributes), "incorrect size for platform data");
				if (xResult.GetPlatformSessionDataSize() == sizeof(Asura_Lobby_Attributes))
				{
					const Asura_Lobby_Attributes* pxAttributes = reinterpret_cast<const Asura_Lobby_Attributes*>(xResult.GetPlatformSessionData());
					for (u_int uFilter = 0; uFilter < s_uNumSearchCriteria; ++uFilter)
					{
						// reject results that don't match the search criterion
						const int iValue = s_pxSearchCriteria[uFilter].GetValue();
						switch (s_pxSearchCriteria[uFilter].GetSearchOperator())
						{
							case ASURA_LOBBY_SEARCH_OPERATOR_EQ:
							{
								if (pxAttributes->GetIntAttribute(s_pxSearchCriteria[uFilter].GetAttributeID()) != iValue)
								{
									bReject = true;
								}
								break;
							}
							case ASURA_LOBBY_SEARCH_OPERATOR_GE:
							{
								if (pxAttributes->GetIntAttribute(s_pxSearchCriteria[uFilter].GetAttributeID()) < iValue)
								{
									bReject = true;
								}
								break;
							}
							case ASURA_LOBBY_SEARCH_OPERATOR_LE:
							{
								if (pxAttributes->GetIntAttribute(s_pxSearchCriteria[uFilter].GetAttributeID()) > iValue)
								{
									bReject = true;
								}
								break;
							}
							default:
							{
								Asura_Assert(false, "unhandled search operator");
								break;
							}
						}
						// don't bother checking for further reject conditions if we've found one.
						if (bReject)
						{
							break;
						}
					}
					if (!bReject)
					{
						if (uNumFilteredSessions >= ASURA_LOBBY_MAX_SEARCH_RESULTS)
						{
							Asura_Assert(false, "too many search results");
							break;
						}
						s_pxSearchResults[uNumFilteredSessions].m_xAddress = xResult.GetAddress();
						s_pxSearchResults[uNumFilteredSessions].m_usPort = xResult.GetPort();
						s_pxSearchResults[uNumFilteredSessions].m_uNumPlayers = xResult.GetCurrentNumberOfPlayers();
						s_pxSearchResults[uNumFilteredSessions].m_uMaxPlayers = xResult.GetMaxNumberOfPlayers();
						s_pxSearchResults[uNumFilteredSessions].m_xAttributes = *pxAttributes;

						Asura_PrintfToBfr( s_pxSearchResults[uNumFilteredSessions].m_awcDescription, L"%s (%S)", ASURA_LOBBY_DESCRIPTION_LENGTH, true, xResult.GetSessionName(), 
							Asura_NetTransport_System::GetStringFromAddress( xResult.GetAddress(), xResult.GetPort() ) );
						
						++uNumFilteredSessions;
					}
				}
			}

			s_uNumSearchResults = uNumFilteredSessions;

			Asura_Network_Session_System::Shutdown();
		}

		return bSearchComplete;
	}
}

bool Asura_Lobby_System::Platform_UpdateInitialisingGame()
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		if ( !IsHost() )
		{
			const Asura_PC_SteamworksLobby* pxSteamLobby = Asura_PC_SteamworksLobby_System::GetLobby( g_xSteamLobby );

			if( pxSteamLobby && pxSteamLobby->IsActive() )
			{
				// I guess we will use the netsocket default port here?

				Asura_NetAddress xAddress;

				xAddress.m_xSteamID = pxSteamLobby->GetOwner();

				const char* szAddress = Asura_NetTransport_System::GetStringFromAddress( xAddress, ASURA_PC_STEAMWORKS_DEFAULT_VPORT );
				Asura_PrintfToBfr(s_acGameAddress, "%s", ASURA_LOBBY_ADDRESS_LENGTH, true, szAddress);
			}
		}
		return true;
	}
	else
#endif
	{
		if (IsHost())
		{
			if (Asura_NetTransport_System::IsInitialised())
			{
				//Need to restart this with game settings
				u_int uServerSessionID = Asura_NetTransport_System::GetServerSessionID();
				Asura_NetTransport_System::Shutdown();
				Asura_NetTransport_System::SetServerSessionID(uServerSessionID);
			}

			return true;
		}
		else
		{
			Asura_NetAddress xAddress;
			Asura_NetPort xPort;
			if (ConnectToNetworkSession( xAddress, xPort ))
			{
				const char* szAddress = Asura_NetTransport_System::GetStringFromAddress( xAddress, xPort.m_usPort );
					Asura_PrintfToBfr(s_acGameAddress, "%s", ASURA_LOBBY_ADDRESS_LENGTH, true, szAddress);
				return true;
				}
			}
			}

	return false;
}

bool Asura_Lobby_System::Platform_PrepareToJoinLobby( u_int uIndex, Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		if( g_xSteamLobby == Asura_PC_SteamInvalidLobbyHandle )
		{
			// No connection to the lobby, create one

			g_xSteamLobby = Asura_PC_SteamworksLobby_System::JoinLobby( s_pxSearchResults[uIndex].m_xLobbySteamID );

			if( g_xSteamLobby == Asura_PC_SteamInvalidLobbyHandle )
			{
				Asura_Assert( false, "Failed to join lobby, should do error handling here somehow?" );
				xAddress = Asura_NetAddress();
				return true;
			}
		}
		else
		{
			const Asura_PC_SteamworksLobby* pxSteamLobby = Asura_PC_SteamworksLobby_System::GetLobby( g_xSteamLobby );

			if( !pxSteamLobby )
			{
				// Something messed up since we have a handle to an invalid lobby, try to join again

				g_xSteamLobby = Asura_PC_SteamInvalidLobbyHandle;
				return false;
			}

			if( pxSteamLobby->GetState() == ASURA_STEAM_LOBBY_STATE_ERROR )
			{
				// Failed to join, what do we do for error handling here?
				Asura_Assert( false, "Failed to join lobby, should do error handling here somehow?" );
				xAddress = Asura_NetAddress();
				return true;
			}
			else if( pxSteamLobby->IsActive() )
			{
				// Connection exists and is connected, grab the steam ID of the owner to connect to

				xAddress.m_xSteamID = pxSteamLobby->GetOwner();
				return true;
			}
		}

		return false;
	}
	else
	#endif
	{
		if (uIndex < s_uNumSearchResults)
		{
			xAddress.m_uAddress = s_pxSearchResults[uIndex].m_xAddress.m_uAddress;
			// this only worked when using a fixed port.
			//xPort.m_usPort = ASURA_LOBBY_FIRST_PORT;
			xPort.m_usPort = s_pxSearchResults[uIndex].m_usPort;
		}
		return true;
	}
}


bool Asura_Lobby_System::Platform_UpdateStartingSession()
{
	// This needs to be done first as we've already started the transaction.
	if (!s_xGameCompletionHistory.Platform_WriteData_Update())
	{
		// Wait for the data to be written.
		return false;
	}

	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		return true;
	}
	else
	#endif
	{
		return true;
	}
}

bool Asura_Lobby_System::Platform_UpdateEndingSession()
{
	bool bFinishedState = true;

	switch (g_eLobbyEndGameState)
	{
		case ASURA_PC_LOBBY_ENDGAMESTATE_INVALID:
		{
			g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_STATS_START;
			break;
		}

		case ASURA_PC_LOBBY_ENDGAMESTATE_STATS_START:
		{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
			Asura_PC_SteamworksLeaderboards::StartWrite(false, Asura_Network::GetClientID());
#endif
			g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_STATS_UPDATE;
			break;
		}

		case ASURA_PC_LOBBY_ENDGAMESTATE_STATS_UPDATE:
		{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
			bFinishedState = Asura_PC_SteamworksLeaderboards::UpdateWrite();
#endif
			if (bFinishedState)
			{
				g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START;
			}
			break;
		}

		case ASURA_PC_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START:
		{
			s_xGameCompletionHistory.Platform_WriteData_Start();
			g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE;
			break;
		}

		case ASURA_PC_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE:
		{
			// Wait for the data to be written.
			bFinishedState = s_xGameCompletionHistory.Platform_WriteData_Update();
			if (bFinishedState)
			{
				g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_INVALID;
				return true; // Done everything.
			}
			break;
		}

		default:
		{
			Asura_Assertf(0, ("Unexpected ASURA_PC_LOBBY_ENDGAMESTATE value (%u)", static_cast<u_int>(g_eLobbyEndGameState)));
			g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_INVALID;
			return true; // Give up.
		}
	}

	// Haven't finished yet.
	return false;
}

void Asura_Lobby_System::Platform_EndGame()
{
	g_eLobbyEndGameState = ASURA_PC_LOBBY_ENDGAMESTATE_INVALID;

#ifdef ASURA_USE_MERIT_SYSTEM
	//Added s_bUseRankedLobbies check to prevent asserts on end of a non-ranked PC game
	if (s_bUseRankedLobbies)
	{
		// write out the new player merit so it can be loaded in next time
		// should be able to get the new computed merit now using our player guid.
		u_int uMeritLevel = 0;
		int iMeritPoints = 0;
		int iMeritLevelChange = 0;
		int iMeritPointsChange = 0;
		bool bGotMyMerit = Asura_Merit_System::GetPlayerMerit(Asura_Network::GetClientID(), 
			uMeritLevel, iMeritPoints, iMeritLevelChange, iMeritPointsChange);
		Asura_Assert(bGotMyMerit, "failed to get player merit");
		if (bGotMyMerit)
		{
			// update the lobby system version
			s_xLocalPlayerMerit.SetMeritLevel(uMeritLevel);
			s_xLocalPlayerMerit.SetMeritPoints(iMeritPoints);

			// TODO: use some form of online storage for this via Steamworks etc.
			// This code is temporarily using the logfile dir for storage to allow 
			// multiple game instances running on the same PC to save independently.
#ifdef ASURA_LOGFILES_ENABLED
			extern const char* Asura_PC_FixupLogfileName(const char* szFilename, char* pcBuffer, u_int uBufferSize);
			char acFilename[MAX_PATH];
			const char* szFilename = Asura_PC_FixupLogfileName(szASURA_PC_MERIT_STORAGE_FILE, acFilename, MAX_PATH);
#else
			const char* szFilename = szASURA_PC_MERIT_STORAGE_FILE;
#endif
			FILE* pxFile = fopen(szFilename, "w");
			if (pxFile)
			{
				if(!fprintf(pxFile, szASURA_PC_MERIT_STORAGE_FORMAT, uMeritLevel, iMeritPoints))
				{
					Asura_Assertf(false,("Error writing merit to %s.", szFilename));
				}
				fclose(pxFile);
			}
		}
	}
#endif// #ifdef ASURA_USE_MERIT_SYSTEM
}

bool Asura_Lobby_System::Platform_UpdateSessionRegistration()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		s_bRegistered = true;
		return true;
	}
	else
	#endif
	{
		s_bRegistered = true;
		return true;
	}
}

void Asura_Lobby_System::Platform_ServerStartMigration( bool bNewServer )
{
#ifdef ASURA_SUPPORT_STEAMWORKS
	Asura_Assert(!Asura_PC_Steamworks::IsSteamworksEnabled(),"Untested on steamworks");
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		Asura_Assert(false,"Steamworks support pending");
	}
	else
#endif
	{
		//The clients will find the server using the same session ID as before
		Asura_NetTransport_System::SetServerSessionID( s_pxLobby->GetDataPtr()->m_uSessionID );
	
		if (bNewServer)
		{
			Platform_CreateLobby( true );
		}
		else
		{
			//Finished listing, shutdown ANTS.
			if (Asura_NetTransport_System::IsInitialised())
			{
				Asura_NetTransport_System::Shutdown();
			}
		}
	}
}

bool Asura_Lobby_System::Platform_ServerEndMigration()
{
	// Nothing
	return true;
}

bool Asura_Lobby_System::Platform_ClientEndMigration()
{
#ifdef ASURA_SUPPORT_STEAMWORKS
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		Asura_Assert(false,"Steamworks support pending");
	}
	else
#endif
	{
		if (!IsHost())
		{
			//Finished searching for game, shutdown ANTS.
			if (Asura_NetTransport_System::IsInitialised())
			{
				Asura_NetTransport_System::Shutdown();
			}
		}
	}

	return true;
}

bool Asura_Lobby_System::Platform_GetMigrationAddress( Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
#ifdef ASURA_SUPPORT_STEAMWORKS
	if (Asura_PC_Steamworks::IsSteamworksEnabled())
	{
		Asura_Assert(false,"Steamworks support pending");
		return false;
	}
	else
#endif
	{
		//On PC-NS we connect to the same session ID so if we are encountering players attempting to 
		//re-connect to the old host (which will refuse them) we might need to put in a delay here.
		if (s_eHostMigrationState != ASURA_LOBBY_HOST_MIGRATIONSTATE_STOPPING)
		{
			return ConnectToNetworkSession( xAddress, xPort );
		}
		else
		{
			return false;
		}
	}
}

bool Asura_Lobby_System::Platform_ClientMessageHandler( u_short usMsgID, u_short usMsgBodySize, const char* pcMsgBody )
{
	// Nothing
	return false;
}

void Asura_Lobby_System::Platform_UpdateDebugOutput( u_int& uX, u_int& uY )
{
#ifdef ASURA_USE_DEBUG_TEXT
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if (g_xSteamLobby != Asura_PC_SteamInvalidLobbyHandle)
		{
			Asura_PC_SteamworksLobby* pxLobby = Asura_PC_SteamworksLobby_System::GetLobby( g_xSteamLobby );
			if (pxLobby && pxLobby->IsActive())
			{
				uint64 uLobbyID = pxLobby->GetSteamID().ConvertToUint64();
				u_int* pauLobbyID = reinterpret_cast<u_int*>(&uLobbyID);
				Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Lobby ID: 0x%08x%08x", pauLobbyID[1], pauLobbyID[0] );
				Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Lobby State: %d", pxLobby->GetState() );
				uint64 uLobbyOwnerID = pxLobby->GetOwner().ConvertToUint64();
				u_int* pauLobbyOwnerID = reinterpret_cast<u_int*>(&uLobbyOwnerID);
				Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Lobby Owner ID: 0x%08x%08x", pauLobbyOwnerID[1], pauLobbyOwnerID[0] );
				//Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Asura Owner ID: 0x%08x", pxLobby->GetSteamID().ConvertToUint64() );
			}
		}
	}
	else
#endif
	{
		if (IsHost() && Asura_NetTransport_System::IsInitialised())
		{
			Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Server Session: 0x%08x", Asura_NetTransport_System::GetServerSessionID() );
		}
	}
#endif
}

bool Asura_Lobby_System::Platform_IsPlayerTalking( const Asura_Lobby_Player& xPlayer )
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if( Asura_Network::IsDedicatedServer() )
		{
			// If we are a dedicated server we wont receive voice packets from any clients so we
			// cannot tell who is speaking
			Asura_Assert( false, "Cannot determine speaking players from dedicated server" );
			return false;
		}
		return Asura_PC_SteamworksClient::IsPeerVoicePlaying( CSteamID( xPlayer.GetPlatformData().m_uSteamID ) );
	}
	else
	#endif
	return false;
}

bool Asura_Lobby_System::Platform_IsMicrophoneAttached()
{
	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		return Asura_PC_SteamworksClient::IsMicrophoneAttached();
	}
	#endif //ASURA_PC_SUPPORT_STEAMWORKS

	return false;
}

void Asura_Lobby_System::Platform_DoPlayerListAction( u_int uIndex )
{
	Asura_Assert(s_pxLobby && uIndex < s_pxLobby->GetNumPlayers(), "should be checked by calling code");

	// TODO
}

bool Asura_Lobby_System::Platform_ConsoleCmd_LobbyInviteFriends(bool bHelp)
{
	if (!bHelp)
	{
		// nothing to do
		Asura_Assert(false, "not expected on PC");
	}
	return true;
}

void Asura_Lobby::Platform_AddPlayer( const Asura_Lobby_Player& xPlayer )
{
	// nothing to do
}

void Asura_Lobby::Platform_RemovePlayer( const Asura_Lobby_Player& xPlayer )
{
	// nothing to do
}

void Asura_Lobby::Platform_FillLobbyData()
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if (!Asura_PC_Steamworks::IsSteamworksEnabled())
#endif
	{
		m_xData.m_uSessionID = Asura_NetTransport_System::GetServerSessionID();
	}
}


void Asura_Lobby_ConnectData::Platform_Fill()
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	m_uSteamID = 0;

	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		m_uSteamID = Asura_PC_SteamworksClient::GetSteamID_Int64();
		m_dwProcessID = 0;
		m_uComputerName = 0;
	}
	else
#endif
	{
		m_uSteamID = 0;
		m_dwProcessID = GetCurrentProcessId();
		m_uComputerName = GetComputerNameHash(); 
	}
}

void Asura_Lobby_GameConnectData::Platform_Fill()
{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		m_uSteamID = Asura_PC_SteamworksClient::GetSteamID_Int64();
		m_dwProcessID = 0;
		m_uComputerName = 0;
	}
	else
#endif
	{
		m_uSteamID = 0;
		m_dwProcessID = GetCurrentProcessId();
		m_uComputerName = GetComputerNameHash();
	}
}

void Asura_Lobby_GameConnectData::Platform_ChangeByteOrder()
{
}


///////////////////////////////////////////////////////////////////////////////
// Asura_GameCompletionHistory
///////////////////////////////////////////////////////////////////////////////

static const char* const	szASURA_PC_GAME_COMPLETION_STORAGE_FILE		= "gamecomp.cld";
static const uint32			uASURA_PC_GAME_COMPLETION_DATA_VERSION		= 1;
static const u_int			uASURA_PC_GAME_COMPLETION_BUFFER_SIZE		= 128;

void Asura_GameCompletionHistory::Platform_GetData_Start()
{
	// If these static asserts fail, bump the data version number and update the code accordingly
	ASURA_STATIC_ASSERT( sizeof( Asura_GameCompletionHistory::GameCompletionRecord ) == 12 );
	ASURA_STATIC_ASSERT( ASURA_NUM_GAME_COMPLETION_RECORDS == 2 );


	Asura_Assert( !m_bGetting && !m_bWriting, "Unexpected flags");
	if ( m_bGetting )
	{
		return;
	}

	m_bGetting		= true;
	m_bDataChanged	= false;
}

// Returns true when finished.
bool Asura_GameCompletionHistory::Platform_GetData_Update()
{
	if (!m_bGetting) 
	{
		return true;
	}

	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		if( !Asura_PC_SteamworksClient::RemoteStorageFileExists( szASURA_PC_GAME_COMPLETION_STORAGE_FILE ) )
		{
			m_bGetting = false;
			return true;
		}

		// Read the data from the file

		char* pcDataBuffer[ uASURA_PC_GAME_COMPLETION_BUFFER_SIZE ];

		Asura_Mem::Set( pcDataBuffer, 0, uASURA_PC_GAME_COMPLETION_BUFFER_SIZE );

		const u_int uTotalRead = sizeof( GameCompletionRecord ) * ASURA_NUM_GAME_COMPLETION_RECORDS + sizeof( uint32 );

		if( Asura_PC_SteamworksClient::ReadFromRemoteStorage( pcDataBuffer, uTotalRead, szASURA_PC_GAME_COMPLETION_STORAGE_FILE ) )
		{
			// Check the version any copy into our data structure

			if( *reinterpret_cast< uint32* >( pcDataBuffer ) == uASURA_PC_GAME_COMPLETION_DATA_VERSION )
			{
				Asura_Mem::Copy( m_axRecord, &pcDataBuffer[ sizeof( uint32 ) ], sizeof( sizeof( GameCompletionRecord ) * ASURA_NUM_GAME_COMPLETION_RECORDS ) );
			}
			#ifdef ASURA_ASSERT
			else
			{
				Asura_Assert( false, "Wrong version number in cloud data" );
			}
			#endif
		}
	}
	#endif

	m_bGetting = false;
	
	return true;
}

void Asura_GameCompletionHistory::Platform_WriteData_Start()
{
	Asura_Assert(!m_bGetting && !m_bWriting, "Unexpected flags");

	if (m_bWriting) 
	{
		return;
	}
	if (!m_bDataChanged) 
	{
		return;
	}

	m_bDataChanged	= false;
	m_bWriting		= true;
}

// Returns true when finished.
bool Asura_GameCompletionHistory::Platform_WriteData_Update()
{
	if( !m_bWriting )
	{
		return true;
	}

	#ifdef ASURA_PC_SUPPORT_STEAMWORKS
	if( Asura_PC_Steamworks::IsSteamworksEnabled() )
	{
		char* pcDataBuffer[ uASURA_PC_GAME_COMPLETION_BUFFER_SIZE ];

		Asura_Mem::Set( pcDataBuffer, 0, uASURA_PC_GAME_COMPLETION_BUFFER_SIZE );

		// Write out a version number in case we want to change the data format or contents

		*reinterpret_cast< uint32* >( pcDataBuffer ) = uASURA_PC_GAME_COMPLETION_DATA_VERSION;

		// Write out our game completion records

		Asura_Mem::Copy( &pcDataBuffer[ sizeof( uint32 ) ], &m_axRecord, sizeof( GameCompletionRecord ) * ASURA_NUM_GAME_COMPLETION_RECORDS );

		const u_int uTotalWrite = sizeof( GameCompletionRecord ) * ASURA_NUM_GAME_COMPLETION_RECORDS + sizeof( uint32 );

		#ifdef ASURA_ASSERT
		bool bOK =
		#endif
		Asura_PC_SteamworksClient::WriteToRemoteStorage( pcDataBuffer, uTotalWrite, szASURA_PC_GAME_COMPLETION_STORAGE_FILE );
		Asura_Assert( bOK, "Failed to write to remote storage cache" );
	}
	#endif

	m_bWriting = false;

	return true;
}

#endif // #ifdef ASURA_SUPPORT_LOBBY
