//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Lobby.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Description:
//
//     Xbox 360 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 independent includes
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_DebugText.h"
#include "Debugging/Asura_Logfile.h"
#include "Network/Asura_Network.h"
#include "Network/Asura_NetTransport_System.h"
#include "Network/Asura_ReliableSocket.h"
#include "Network/Asura_ReliableSocket_TCP.h"

// Asura Xbox360 includes
#include "Core/Asura_Xbox360_OS.h"
#include "Core/Asura_Xbox360_Notifications.h"
#include "Network/Online/Asura_Xbox360_LiveContextManager.h"
#include "Network/Online/Asura_Xbox360_LiveSession.h"
#include "Network/Online/Asura_Xbox360_MatchFinder.h"
#include "Network/Online/Asura_Xbox360_Stats.h"
#include "Network/Online/Asura_Xbox360_Storage.h"
#include "Network/Online/Asura_Xbox360_Voice.h"


// Enums
enum ASURA_LOBBY_PLATFORM_MESSAGE_ID
{
	ASURA_PLATFORM_MSG_LOBBY_SESSIONINFO = ASURA_MSG_LOBBY_FIRST_PLATFORM_SPECIFIC,
};


// State machine for ending game update.
enum ASURA_XBOX360_LOBBY_ENDGAMESTATE
{
	ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID,

	ASURA_XBOX360_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START,	// Start writing game completion data
	ASURA_XBOX360_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE,	// Update writing game completion data

	ASURA_XBOX360_LOBBY_ENDGAMESTATE_LIVE_UPDATE,			// Update ending Xbox Live session
};


// Game completion data buffer class
class Asura_Xbox360_GameCompletionData
{
public:
	// Ranked
	u_int m_uNumGamesHosted0;
	u_int m_uNumGamesStarted0;
	u_int m_uNumGamesCompleted0;

	// Friendly
	u_int m_uNumGamesHosted1;
	u_int m_uNumGamesStarted1;
	u_int m_uNumGamesCompleted1;
};


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

// Filename of the stored file on the Xbox LIVE server.
static const WCHAR* g_wszGameCompletionFilename = L"GameCompletion.dat";


// G L O B A L S /////////////////////////////////////////////////////////////
static Asura_Xbox360_LiveSession* g_pxLiveSession;
static Asura_Xbox360_MatchFinder* g_pxMatchFinder;
static XINVITE_INFO g_xInviteDetails;

static Asura_Xbox360_StorageTransactionManager g_xGameCompletionManager;
static Asura_Xbox360_GameCompletionData g_xGameCompletion;

static ASURA_XBOX360_LOBBY_ENDGAMESTATE g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID;

// S T A T I C    F U N C T I O N S //////////////////////////////////////////
static bool GetPeerConnection( IN_ADDR& xIPAddr, const XNADDR* pxAddr = NULL )
{
	const XSESSION_INFO& xSessionInfo = g_pxLiveSession->GetSessionInfo();

	int iResult = XNetXnAddrToInAddr( (pxAddr) ? pxAddr : &xSessionInfo.hostAddress, &xSessionInfo.sessionID, &xIPAddr);
	Asura_Assertf(iResult == 0, ("XNetXnAddrToInAddr returned error, %d", WSAGetLastError()));
	return (iResult == 0);
}

// C L A S S   I M P L E M E N T A T I O N ///////////////////////////////////
void Asura_Lobby_System::Platform_Initialise()
{
	const u_int uPlayerIndex = Asura_Xbox360_OS::GetActivePlayerIndex();
	char acBuffer[ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE];
	HRESULT hResult = XUserGetName(uPlayerIndex, acBuffer, ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE);
	Asura_Assert(hResult == ERROR_SUCCESS, "XUserGetName failed");
	if (hResult == ERROR_SUCCESS)
	{
		Asura_PrintfToBfr( s_awcLocalPlayerName, L"%S", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, acBuffer );
	}

	Asura_Xbox360_Voice::Initialise(ASURA_LOBBY_MAX_PLAYERS - 1);

	g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID;
}

void Asura_Lobby_System::Platform_Shutdown()
{
	Asura_Xbox360_Voice::Shutdown();

	delete g_pxMatchFinder;
	g_pxMatchFinder = NULL;
}

void Asura_Lobby_System::Platform_Update()
{
	if (Asura_XBox360_Notifications::IsGameInvitePending())
	{
		s_bInvitationPending = true;

		if (s_bInitialised)
		{
			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_xSessionInfo = Asura_XBox360_Notifications::GetGameInviteDetails().hostInfo;
		}
	}

	if (!g_pxLiveSession || !s_pxLobby)
	{
		return;
	}
	
	g_pxLiveSession->Update();

	//We use ANTS to transmit voice data
	if (Asura_NetTransport_System::IsInitialised())
	{
		Asura_NetTransport_System::Update();

		if (Asura_Xbox360_Voice::Update())
		{
			XUID xLocalXuid;
			XUserGetXUID(Asura_Xbox360_OS::GetActivePlayerIndex(), &xLocalXuid);

			// send voice data to all players.
			// TODO: filter which players based on whether they have a headset, and whether they should be 
			// receiving voice from this player (e.g. not same team, muted etc).
			// JG - 16 January 2009 - Implemented voice channels on 360, it allows some filtering, see comment above
			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_xXUID == xLocalXuid)
				{
					// skip self
					continue;
				}
				
				if ((xPlayerData.GetVoiceChannels() & ucActiveChannelMask) == 0)
				{
					// skip players not connected to our active voice channel
					continue;
				}

				IN_ADDR xAddress;
				XNetXnAddrToInAddr( &s_pxLobby->GetPlayerByIndex(u).GetPlatformData().m_xXNAddr, &g_pxLiveSession->GetSessionInfo().sessionID, &xAddress );
				// use unencrypted mode for VDP
				if(!Asura_NetTransport_System::SendMessageToAddress(Asura_Xbox360_Voice::GetVoiceMsgData(), 
					Asura_Xbox360_Voice::GetVoiceMsgDataSize(), xAddress.S_un.S_addr, false))
				{
					Asura_Assert(false, "SendMessageToAddress failed");
				}
			}
			Asura_Xbox360_Voice::VoiceMsgSent();
		}
	}
}

void Asura_Lobby_System::Platform_Terminate()
{
	//Will only be active outside of game
	if (Asura_NetTransport_System::IsInitialised())
	{
		Asura_NetTransport_System::Shutdown();
	}

	// clean up any outstanding sessions
	delete g_pxLiveSession;
	g_pxLiveSession = NULL;

	delete g_pxMatchFinder;
	g_pxMatchFinder = NULL;

	Asura_Xbox360_Stats_PreGameReader::Shutdown();

	g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID;
}

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

		const u_int uUserIndex = Asura_Xbox360_OS::GetActivePlayerIndex();

		// must set GAME_TYPE context before creating session
		XUserSetContext( uUserIndex, X_CONTEXT_GAME_TYPE, s_bUseRankedLobbies ? X_CONTEXT_GAME_TYPE_RANKED : X_CONTEXT_GAME_TYPE_STANDARD );
		// game mode is not deathmatch vs team deathmatch but rather types of play with completely different skillsets.
		// e.g. if you could play online chess, backgammon and draughts in the same title they would be different.
		// Maybe Star Wars Battlefront would have a different game mode for normal games compared to 1v1 Galactic Conquest.
		XUserSetContext( uUserIndex, X_CONTEXT_GAME_MODE, 0 ); 

		// setup contexts from lobby attributes
		for (u_int u = 0; u < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++u)
		{
			bool bIsProperty;
			u_int uContext = Asura_Xbox360_LiveContextManager::GetMapping(u, bIsProperty);
			int iAttributeValue = s_pxLobby->GetAttributes().GetIntAttribute(u);
			if (bIsProperty)
			{
				XUserSetProperty( uUserIndex, uContext, sizeof(iAttributeValue), &iAttributeValue );
			}
			else
			{
				XUserSetContext( uUserIndex, uContext, iAttributeValue );
			}
		}

		Asura_Assert(!g_pxLiveSession, "Live! session already exists - memory leak");
		const u_int uNumPrivateSlots = s_xHostingSettings.m_uPrivateSlots;
		Asura_Assert(s_pxLobby->GetMaxPlayers() >= uNumPrivateSlots, "can't have more private slots than total slots");
		const u_int uNumPublicSlots = s_pxLobby->GetMaxPlayers() - uNumPrivateSlots;
		const bool bAllowJoinInProgress = s_bAllowJoinInProgress && (!s_bUseRankedLobbies);

		g_pxLiveSession = new Asura_Xbox360_LiveSession( uUserIndex, bHost, s_bUseRankedLobbies, 
			bAllowJoinInProgress, uNumPublicSlots, uNumPrivateSlots );
		g_pxLiveSession->SwitchToState( ASURA_XBOX360_LIVESESSION_STATE_CREATING );
	}

	//For VDP client to client voice comms
	Asura_Assert(!Asura_NetTransport_System::IsInitialised(),"NetTransport shouldn't be initialised at this point");
	if (!Asura_NetTransport_System::IsInitialised())
	{
		Asura_NetTransport_System::Initialise();
	}
	Asura_NetTransport_System::SetServerLANReporting( false );
}

bool Asura_Lobby_System::Platform_UpdateLobbyCreation()
{
	if (!IsHost())
	{
		// we now join the room before connecting
		return true;
	}
	Asura_Assert(s_pxLobby, "platform independent lobby must have been created first");

	Asura_Assert(g_pxLiveSession, "no Live! session was created");
	if (!g_pxLiveSession)
	{
		Asura_Lobby_System::Terminate(ASURA_NDR_FAILED_TO_LIST_SERVER);
		return true;
	}

	switch (g_pxLiveSession->GetSessionState())
	{
		case ASURA_XBOX360_LIVESESSION_STATE_CREATING:
			break;
		case ASURA_XBOX360_LIVESESSION_STATE_IDLE:
			s_pxLobby->Platform_FillLobbyData();
			return true;
		case ASURA_XBOX360_LIVESESSION_STATE_ERROR:
			Asura_Lobby_System::Terminate(IsHost() ? ASURA_NDR_FAILED_TO_LIST_SERVER : ASURA_NDR_UNKNOWN );
			break;
		default:
			Asura_Assert(false, "unhandled state");
			break;
	}
	return false;
}

// Platform_UpdateSessionRegistration() - returns true when registration is finished, sets s_bRegistered if successful.
bool Asura_Lobby_System::Platform_UpdateSessionRegistration()
{
	Asura_Assert(g_pxLiveSession, "no Live! session was created");
	Asura_Assert(s_pxLobby, "no lobby was created");
	if (!g_pxLiveSession || !s_pxLobby)
	{
		return true;
	}

	if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_ERROR)
	{
		return true;
	}

	if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_REGISTERED)
	{
		s_bRegistered = true;
		if (IsHost())
		{
			// get registration results and remove any players who weren't registered.
			// This information will be communicated to clients via the final player list message.
			const XSESSION_REGISTRATION_RESULTS* pxResults = g_pxLiveSession->GetRegistrationResults();
			Asura_Assert(pxResults, "no registration result");
			if (pxResults)
			{
				const u_int uFinalNumPlayers = pxResults->wNumRegistrants;
				Asura_Guid auPlayersToRemove[ASURA_LOBBY_MAX_PLAYERS];
				u_int uNumPlayersToRemove = 0;

				Asura_Assert(s_pxLobby->GetNumPlayers() <= ASURA_LOBBY_MAX_PLAYERS, "too many players in lobby" );
				// for each player in lobby, check there is one in the registration result with a matching XUID
				for (u_int uPlayerInOurList = 0; uPlayerInOurList < s_pxLobby->GetNumPlayers(); ++uPlayerInOurList)
				{
					const XUID& xPlayerXuid = s_pxLobby->GetPlayerByIndex(uPlayerInOurList).GetPlatformData().m_xXUID;

					bool bFound = false;
					// search list of registered players
					for (u_int u = 0; u < uFinalNumPlayers; ++u)
					{
						// check all players on the console, in case multiple players are signed in.
						for (u_int uPlayerOnConsole = 0; uPlayerOnConsole < pxResults->rgRegistrants[u].bNumUsers; ++uPlayerOnConsole)
						{
							if (pxResults->rgRegistrants[u].rgUsers[uPlayerOnConsole] == xPlayerXuid)
							{
								bFound = true;
								break;
							}
						}
						if (bFound)
						{
							break;
						}
					}

					if (!bFound)
					{
						// this player wasn't in the registered list
						auPlayersToRemove[uNumPlayersToRemove++] = s_pxLobby->GetPlayerByIndex(uPlayerInOurList).GetClientGuid();
					}
				}

				// if any players need to be removed, do that now.
				for (u_int u = 0; u < uNumPlayersToRemove; ++u)
				{
					Asura_Network::RemovePlayer(auPlayersToRemove[u], ASURA_NDR_SERVERKICK);
				}
			}
		}
		return true;
	}

	if (g_pxLiveSession->GetSessionState() != ASURA_XBOX360_LIVESESSION_STATE_REGISTERING)
	{
		if (!IsHost())
		{
			g_pxLiveSession->SetSessionNonce(s_pxLobby->GetDataPtr()->m_ulSessionNonce);
		}
		g_pxLiveSession->SwitchToState(ASURA_XBOX360_LIVESESSION_STATE_REGISTERING);
	}
	return false;
}

// Platform_UpdateStartingSession() - do processing to start the session and return true when all is done.
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;
	}

	if (g_pxLiveSession)
	{
		if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_IDLE || 
			g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_REGISTERED)
		{
			g_pxLiveSession->SwitchToState(ASURA_XBOX360_LIVESESSION_STATE_STARTING);
		}
		else
		{
			Asura_Assert(g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_IN_GAME || 
				g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_STARTING,
				"unexpected session state" );
		}
		g_pxLiveSession->Update();

		if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_STARTING)
		{
			return false;
		}

		// kick off fetch of pre-game stats
		if (!Asura_Xbox360_Stats_PreGameReader::IsInitialised())
		{
			// create array of XUIDs for this session
			XUID axXuids[ASURA_LOBBY_MAX_PLAYERS];
			const u_int uNumPrivateSlots = g_pxLiveSession->GetNumPrivateSlotsFilled();
			const u_int uNumPublicSlots = g_pxLiveSession->GetNumPublicSlotsFilled();
			for (u_int u = 0; u < uNumPrivateSlots; ++u)
			{
				axXuids[u] = g_pxLiveSession->GetXUIDForPrivateSlot(u);
			}
			for (u_int u = 0; u < uNumPublicSlots; ++u)
			{
				axXuids[uNumPrivateSlots + u] = g_pxLiveSession->GetXUIDForPublicSlot(u);
			}

			Asura_Xbox360_Stats_PreGameReader::Initialise(axXuids, uNumPrivateSlots+uNumPublicSlots);
		}
		// don't continue until stats have arrived.
		if (!Asura_Xbox360_Stats_PreGameReader::Update())
		{
			return false;
		}
	}
	return true;
}


// Platform_UpdateEndingSession() - do processing to end the session and return true when all is done.
bool Asura_Lobby_System::Platform_UpdateEndingSession()
{
	bool bFinishedState = true;

	switch (g_eLobbyEndGameState)
	{
	case ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID:
		{
			g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START;
			break;
		}

	case ASURA_XBOX360_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START:
		{
			s_xGameCompletionHistory.Platform_WriteData_Start();
			g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE;
			break;
		}

	case ASURA_XBOX360_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE:
		{
			// Wait for the data to be written.
			if (s_xGameCompletionHistory.Platform_WriteData_Update())
			{
				g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_LIVE_UPDATE;
			}
			break;
		}

	case ASURA_XBOX360_LOBBY_ENDGAMESTATE_LIVE_UPDATE:
		{
			if (g_pxLiveSession)
			{
				if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_IN_GAME)
				{
					// set the session to end (calls XSessionEnd())
					g_pxLiveSession->SwitchToState(ASURA_XBOX360_LIVESESSION_STATE_ENDING);
				}
				
				g_pxLiveSession->Update();

				if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_ENDING)
				{
					bFinishedState = false; // Need to wait.
				}
			}

			if (bFinishedState)
			{
				g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID;
				return true;
			}
			break;
		}

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

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

void Asura_Lobby_System::Platform_EndGame()
{
	Asura_Assert(s_pxLobby, "lobby destroyed already?");

	g_eLobbyEndGameState = ASURA_XBOX360_LOBBY_ENDGAMESTATE_INVALID;

	// only write stats for ranked games
	if (s_pxLobby && (IsHost() || s_bUseRankedLobbies))
	{
		// TODO: make stats writing async? If we did this code might not work since the scores might
		// be destroyed immediately after this function returns. Depends how long this function takes...
#ifndef ASURA_NO_DEBUG_OUTPUT
		Asura_OutputToDebugger_Retail(("writing stats for %u players...\n", s_pxLobby->GetNumPlayers()));
		const u_int uStartTime = Asura_Timers::GetMSecsSinceStart_Continuous();
#endif

		const bool bLeftEarly = false;
		Asura_Xbox360_StatsWriter xStatsWriter(g_pxLiveSession->GetSessionHandle(), s_bUseRankedLobbies);
		for (u_int uPlayer = 0; uPlayer < s_pxLobby->GetNumPlayers(); ++uPlayer)
		{
			const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(uPlayer);
			xStatsWriter.WriteStatsForPlayer(bLeftEarly, xPlayer.GetClientGuid(), xPlayer.GetPlatformData().m_xXUID);
		}

#ifndef ASURA_NO_DEBUG_OUTPUT
		const u_int uEndTime = Asura_Timers::GetMSecsSinceStart_Continuous();
		Asura_OutputToDebugger_Retail(("stats write done, time taken: %u msecs (%.01f msecs per player)\n", 
			uEndTime - uStartTime, static_cast<float>(uEndTime - uStartTime) / static_cast<float>(s_pxLobby->GetNumPlayers())) );
#endif
	}
}

void Asura_Lobby_System::Platform_BeginSearch()
{
	Asura_Assert(!g_pxMatchFinder, "Match finder already exists - memory leak");
	g_pxMatchFinder = new Asura_Xbox360_MatchFinder(ASURA_LOBBY_MAX_SEARCH_RESULTS, ASURA_LOBBY_MAX_SEARCH_CRITERIA, s_bUseRankedLobbies);
	for (u_int u = 0; u < s_uNumSearchCriteria; ++u)
	{
		bool bIsProperty;
		u_int uContext = Asura_Xbox360_LiveContextManager::GetMapping(s_pxSearchCriteria[u].GetAttributeID(), bIsProperty);
		if (uContext != uASURA_XBOX360_INVALID_CONTEXT_MAPPING)
		{
			if (bIsProperty)
			{
				g_pxMatchFinder->AddSearchProperty(uContext, s_pxSearchCriteria->GetValue());
			}
			else
			{
				Asura_Assert(s_pxSearchCriteria->GetValue() >= 0.0f, "context values must be unsigned");
				g_pxMatchFinder->AddSearchContext(uContext, static_cast<u_int>(s_pxSearchCriteria->GetValue()));
			}
		}
	}
	if (!g_pxMatchFinder->BeginSearch())
	{
		delete g_pxMatchFinder;
		g_pxMatchFinder = NULL;
	}
}

void Asura_Lobby_System::Platform_CancelSearch()
{
	delete g_pxMatchFinder;
	g_pxMatchFinder = NULL;
}

bool Asura_Lobby_System::Platform_SearchHasFinished()
{
	Asura_Assert(g_pxMatchFinder, "no Live! session was created");
	if (!g_pxMatchFinder)
	{
		return true;
	}

	bool bSearchComplete = g_pxMatchFinder->IsSearchComplete();
	if (bSearchComplete)
	{
		// copy the search results into the platform-independent data array
		u_int uNumSearchResults = g_pxMatchFinder->GetNumSearchResults();
		Asura_Assert(uNumSearchResults < ASURA_LOBBY_MAX_SEARCH_RESULTS, "too many search results");
		uNumSearchResults = Asura_Maths::Min(uNumSearchResults, u_int(ASURA_LOBBY_MAX_SEARCH_RESULTS));
		u_int uNumSearchResultsUsed = 0;

		// init the search results to 0.
		if (uNumSearchResults)
		{
			Asura_Mem::Set(s_pxSearchResults, 0, uNumSearchResults * sizeof(s_pxSearchResults[0]));
		}

		for (u_int uResult = 0; uResult < uNumSearchResults; ++uResult)
		{
			const XSESSION_SEARCHRESULT& xResult = g_pxMatchFinder->GetResult(uResult);

			// see if this is the session we're already in
			if (g_pxLiveSession && g_pxLiveSession->IsSameSession(xResult.info))
			{
				continue;
			}

			s_pxSearchResults[uNumSearchResultsUsed].m_xSessionInfo = xResult.info;
			s_pxSearchResults[uNumSearchResultsUsed].m_uNumPublicSlots = xResult.dwFilledPublicSlots + xResult.dwOpenPublicSlots;
			s_pxSearchResults[uNumSearchResultsUsed].m_uNumPrivateSlots = xResult.dwFilledPrivateSlots + xResult.dwOpenPrivateSlots;
			s_pxSearchResults[uNumSearchResultsUsed].m_uNumPlayers = xResult.dwFilledPrivateSlots + xResult.dwFilledPublicSlots;
			s_pxSearchResults[uNumSearchResultsUsed].m_uMaxPlayers = xResult.dwOpenPrivateSlots + xResult.dwOpenPublicSlots + s_pxSearchResults[uNumSearchResultsUsed].m_uNumPlayers;

			// look at the contexts
			bool bFoundGameType = false;
			for (u_int uContext = 0; uContext < xResult.cContexts; ++uContext)
			{
				// copy to aligned buffer since the docs imply the data might be unaligned.
				XUSER_CONTEXT xContextData;
				Asura_Mem::Copy(&xContextData, &xResult.pContexts[uContext], sizeof(xContextData));
				if (xContextData.dwContextId == X_CONTEXT_GAME_TYPE)
				{
					bFoundGameType = true;
					s_pxSearchResults[uNumSearchResultsUsed].m_bIsRanked = (xContextData.dwValue == X_CONTEXT_GAME_TYPE_RANKED); 
				}
				else
				{
					// see if this context is one of our registered lobby attributes
					for (u_int uAttribute = 0; uAttribute < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++uAttribute)
					{
						bool bIsProperty;
						if (Asura_Xbox360_LiveContextManager::GetMapping(uAttribute, bIsProperty) == xContextData.dwContextId)
						{
							Asura_Assert(!bIsProperty, "should only be dealing with contexts here");
							s_pxSearchResults[uNumSearchResultsUsed].m_xAttributes.SetIntAttribute(uAttribute, static_cast<int>(xContextData.dwValue));
							break;
						}
					}
				}
			}
			Asura_Assert(bFoundGameType, "didn't find game type context; don't know if session is ranked or not");

			// reset hostname
			s_pxSearchResults[uNumSearchResultsUsed].m_awcDescription[0] = 0;

			// now look at the properties
			for (u_int uProperty = 0; uProperty < xResult.cProperties; ++uProperty)
			{
				// copy to aligned buffer since the docs imply the data might be unaligned.
				XUSER_PROPERTY xPropertyData;
				Asura_Mem::Copy(&xPropertyData, &xResult.pProperties[uProperty], sizeof(xPropertyData));
				if (xPropertyData.dwPropertyId == X_PROPERTY_GAMER_HOSTNAME)
				{
					Asura_Assert(xPropertyData.value.type == XUSER_DATA_TYPE_UNICODE,"");
					Asura_PrintfToBfr( s_pxSearchResults[uNumSearchResultsUsed].m_awcDescription, xPropertyData.value.string.pwszData, ASURA_LOBBY_DESCRIPTION_LENGTH, true );
				}
				else if (xPropertyData.value.type == XUSER_DATA_TYPE_INT32)
				{
					// see if this property is one of our registered lobby attributes
					for (u_int uAttribute = 0; uAttribute < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++uAttribute)
					{
						bool bIsProperty;
						if (Asura_Xbox360_LiveContextManager::GetMapping(uAttribute, bIsProperty) == xPropertyData.dwPropertyId)
						{
							Asura_Assert(bIsProperty, "should only be dealing with properties here");
							s_pxSearchResults[uNumSearchResultsUsed].m_xAttributes.SetIntAttribute(uAttribute, static_cast<int>(xPropertyData.value.nData));
							break;
						}
					}
				}
			}

			++uNumSearchResultsUsed;
		}
		s_uNumSearchResults = uNumSearchResultsUsed;

		// now we're done with the matchfinder
		delete g_pxMatchFinder;
		g_pxMatchFinder = NULL;
	}
	return bSearchComplete;
}

bool Asura_Lobby_System::Platform_PrepareToJoinLobby( u_int uIndex, Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
	// first time, create the session
	if (!g_pxLiveSession)
	{
		Asura_Assert(uIndex < s_uNumSearchResults && s_pxSearchResults, "expecting search results to be valid");

		const XSESSION_INFO& xSessionInfo = s_pxSearchResults[uIndex].m_xSessionInfo;

		const u_int uUserIndex = Asura_Xbox360_OS::GetActivePlayerIndex();
		const bool bRanked = s_pxSearchResults[uIndex].m_bIsRanked;
		const bool bHost = false;

		// must set GAME_TYPE context before creating session
		XUserSetContext( uUserIndex, X_CONTEXT_GAME_TYPE, bRanked ? X_CONTEXT_GAME_TYPE_RANKED : X_CONTEXT_GAME_TYPE_STANDARD );
		XUserSetContext( uUserIndex, X_CONTEXT_GAME_MODE, 0 ); // TODO: more game modes?

		Asura_Assert(!g_pxLiveSession, "Live! session already exists - memory leak");
		u_int uNumPrivateSlots = s_pxSearchResults[uIndex].m_uNumPrivateSlots;
		u_int uNumPublicSlots = s_pxSearchResults[uIndex].m_uNumPublicSlots;
		if (!uNumPrivateSlots && !uNumPublicSlots)
		{
			// I don't know how important it is to get this right. For now let clients pretend all slots are public.
			uNumPublicSlots = ASURA_LOBBY_MAX_PLAYERS;
		}

		const bool bAllowJoinInProgress = false;// irrelevant
		g_pxLiveSession = new Asura_Xbox360_LiveSession( uUserIndex, bHost, bRanked, bAllowJoinInProgress, uNumPublicSlots, uNumPrivateSlots );
		g_pxLiveSession->SetSessionInfo(xSessionInfo);
		g_pxLiveSession->SwitchToState( ASURA_XBOX360_LIVESESSION_STATE_CREATING );
	}

	// see if session creation has finished
	g_pxLiveSession->Update();
	switch (g_pxLiveSession->GetSessionState())
	{
	case ASURA_XBOX360_LIVESESSION_STATE_CREATING:
		// still working
		return false;
	case ASURA_XBOX360_LIVESESSION_STATE_IDLE:
		// finished - do processing below
		break;
	case ASURA_XBOX360_LIVESESSION_STATE_ERROR:
		Asura_Lobby_System::Terminate( ASURA_NDR_UNKNOWN );
		return false;
	default:
		Asura_Assert(false, "unhandled state");
		return false;
	}

	{
		//int iResult = XNetRegisterKey(&xSessionInfo.sessionID, &xSessionInfo.keyExchangeKey);
		//Asura_Assertf(iResult == 0, ("XNetRegisterKey failed error, 0x%x", iResult));
		IN_ADDR xIPAddress;
		if (GetPeerConnection( xIPAddress ))
		{
			// TODO: XNetRegisterKey should be done again by the XSessionCreate call.
			// So we should not need to register/unregister it here...
			//const int iResult2 = XNetUnregisterKey(&xSessionInfo.sessionID);
			//Asura_Assertf(iResult2 == 0, ("XNetUnregisterKey failed error, 0x%x", iResult));
			xAddress = xIPAddress.S_un.S_addr;
			xPort.m_usPort = ASURA_LOBBY_FIRST_PORT;
			return true;
		}
		else
		{
			Asura_Lobby_System::Terminate( ASURA_NDR_UNKNOWN );
		}

		return false;
	}
}

bool Asura_Lobby_System::Platform_UpdateInitialisingGame( void )
{
	//Should be active first time through
	if (Asura_NetTransport_System::IsInitialised())
	{
		Asura_NetTransport_System::Shutdown();
	}

	if (IsHost())
	{
		//Nothing to do
		return true;
	}
	else
	{
		if ((s_bUseRankedLobbies && (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_REGISTERED)) || 
			(g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_IDLE))
		{
			IN_ADDR xIPAddress;
			if (GetPeerConnection( xIPAddress ))
			{
				u_int uAddress = xIPAddress.S_un.S_addr;
				u_short usPort = ASURA_NETSOCKET_DEFAULTPORT;
				Asura_PrintfToBfr( s_acGameAddress, Asura_NetTransport_System::GetStringFromAddress( uAddress, usPort ), ASURA_LOBBY_ADDRESS_LENGTH, true );
				return true;
			}
			else
			{
				Asura_Lobby_System::Terminate( ASURA_NDR_UNKNOWN );
			}

			return false;
		}
	}

	return false;
}

void Asura_Lobby_System::Platform_ServerStartMigration( bool bNewServer )
{
	if (bNewServer)
	{
		Asura_Assert(g_pxLiveSession, "no session");
		if (g_pxLiveSession)
		{
			g_pxLiveSession->MigrateSession();
		}
	}
}

bool Asura_Lobby_System::Platform_ServerEndMigration()
{
	Asura_Assert(g_pxLiveSession && s_pxHostSocket, "");
	if (!g_pxLiveSession || (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_ERROR))
	{
		Terminate( ASURA_NDR_UNKNOWN );
	}
	else if (s_pxHostSocket && (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_IDLE))
	{
		s_pxHostSocket->SendMessageToAll( ASURA_PLATFORM_MSG_LOBBY_SESSIONINFO, sizeof(XSESSION_INFO), &g_pxLiveSession->GetSessionInfo() );
		return true;
	}

	return false;
}

bool Asura_Lobby_System::Platform_ClientEndMigration()
{
	// Wait for the platform migration to complete

	if (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_ERROR)
	{
		Terminate( ASURA_NDR_UNKNOWN ); //TODO: Reason
	}

	return (g_pxLiveSession->GetSessionState() == ASURA_XBOX360_LIVESESSION_STATE_IDLE);
}

bool Asura_Lobby_System::Platform_GetMigrationAddress( Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
	Asura_Assert(g_pxLiveSession, "no session");
	Asura_Assert(s_pxLobby, "no lobby");
	if (g_pxLiveSession && s_pxLobby)
	{
		IN_ADDR xAddr;
		if (GetPeerConnection( xAddr, &s_pxLobby->FindUniqueBestHost().GetPlatformData().m_xXNAddr ))
		{
			xPort.m_usPort = ASURA_LOBBY_FIRST_PORT;
			xAddress = xAddr.S_un.S_addr;
			Asura_OutputToDebugger(("Got migration address: %s\n", Asura_NetTransport_System::GetStringFromAddress( xAddress, xPort.m_usPort )));
			return true;
		}
	}
	
	return false;
}

bool Asura_Lobby_System::Platform_ClientMessageHandler( u_short usMsgID, u_short usMsgBodySize, const char* pcMsgBody )
{
	if (!IsHost())
	{
		switch (usMsgID)
		{
			case ASURA_PLATFORM_MSG_LOBBY_SESSIONINFO:
			{
				const XSESSION_INFO* pxNewSessionInfo = Asura_ReliableSocket_CheckAndExtractData<XSESSION_INFO>( pcMsgBody, usMsgBodySize );
				Asura_Assert(g_pxLiveSession,"");
				if (pxNewSessionInfo && g_pxLiveSession)
				{
					g_pxLiveSession->MigrateSession( pxNewSessionInfo );
				}
				return true;
			}
			default: break;
		}
	}

	return false;
}

void Asura_Lobby_System::Platform_UpdateDebugOutput( u_int& uX, u_int& uY )
{
#if ASURA_USE_DEBUG_TEXT
	if (g_pxLiveSession)
	{
		const u_int* pKey = (const u_int*)(g_pxLiveSession->GetSessionInfo().keyExchangeKey.ab); //16 bytes
		const u_int* pSession = (const u_int*)(g_pxLiveSession->GetSessionInfo().sessionID.ab); //8 bytes

		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Live session", g_pxLiveSession->GetSessionState() );
		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "State: %d", g_pxLiveSession->GetSessionState() );
		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Key: 0x%08x%08x%08x%08x", pKey[3], pKey[2], pKey[1], pKey[0] );
		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Session: 0x%08x%08x", pSession[1], pSession[0] );
		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFFFFFF, 0, "Slots filled: %d", g_pxLiveSession->GetNumPublicSlotsFilled() );
	}
#endif
}

bool Asura_Lobby_System::Platform_IsPlayerTalking( const Asura_Lobby_Player& xPlayer )
{
	return Asura_Xbox360_Voice::IsPlayerTalking(xPlayer.GetPlatformData().m_xXUID);
}

bool Asura_Lobby_System::Platform_IsMicrophoneAttached()
{
	return Asura_Xbox360_Voice::IsHeadsetPresent();
}

void Asura_Lobby_System::Platform_DoPlayerListAction( u_int uIndex )
{
	// bring up Gamercard.
#ifdef ASURA_ASSERT
	DWORD uRet = 
#endif
	XShowGamerCardUI(Asura_Xbox360_OS::GetActivePlayerIndex(), s_pxLobby->GetPlayerByIndex(uIndex).GetPlatformData().m_xXUID);
	Asura_Assert(uRet == ERROR_SUCCESS, "XShowGamerCardUI failed");
}

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

void Asura_Lobby::Platform_AddPlayer( const Asura_Lobby_Player& xPlayer )
{
	Asura_Assert(g_pxLiveSession, "no session");
	if (g_pxLiveSession)
	{
		XUID xLocalXuid;
		XUserGetXUID(Asura_Xbox360_OS::GetActivePlayerIndex(), &xLocalXuid);

		// TODO: the host should know whether this player was invited.
		const bool bInvited = false;

		if (xPlayer.GetPlatformData().m_xXUID == xLocalXuid)
		{
			g_pxLiveSession->AddLocalPlayer(bInvited);
		}
		else
		{
			XUID axPlayersToAdd[1] = { xPlayer.GetPlatformData().m_xXUID };
			BOOL abPlayersToAddInvited[1] = { bInvited ? TRUE : FALSE };

			g_pxLiveSession->AddRemotePlayers(1, axPlayersToAdd, abPlayersToAddInvited);
			
			// setup voice comms. TODO: only do this for players with headset connected?
			Asura_Xbox360_Voice::RegisterRemotePlayer(xPlayer.GetPlatformData().m_xXUID);
		}
	}
}

void Asura_Lobby::Platform_RemovePlayer( const Asura_Lobby_Player& xPlayer )
{
	Asura_Assert(g_pxLiveSession, "no session");
	if (g_pxLiveSession)
	{
		XUID xLocalXuid;
		XUserGetXUID(Asura_Xbox360_OS::GetActivePlayerIndex(), &xLocalXuid);

		if (xPlayer.GetPlatformData().m_xXUID == xLocalXuid)
		{
			g_pxLiveSession->RemoveLocalPlayer();
		}
		else
		{
			// for a ranked match, if the player left during gameplay, write stats now.
			const bool bRankedMatch = Asura_Lobby_System::IsUsingRankedLobbies();
			if (bRankedMatch && Asura_Lobby_System::GetState() == ASURA_LOBBY_STATE_PLAYING_GAME)
			{
				const bool bLeftEarly = true;
				Asura_Xbox360_StatsWriter xStatsWriter(g_pxLiveSession->GetSessionHandle(), 
					Asura_Lobby_System::IsUsingRankedLobbies());
				xStatsWriter.WriteStatsForPlayer(bLeftEarly, xPlayer.GetClientGuid(), xPlayer.GetPlatformData().m_xXUID);
			}

			XUID axPlayersToRemove[1] = { xPlayer.GetPlatformData().m_xXUID };
			g_pxLiveSession->RemoveRemotePlayers(1, axPlayersToRemove);

			// remove from voice comms system
			Asura_Xbox360_Voice::UnregisterRemotePlayer(xPlayer.GetPlatformData().m_xXUID);
		}
	}
}

void Asura_Lobby::Platform_FillLobbyData()
{
	Asura_Assert(Asura_Lobby_System::IsHost(), "only expected on host");
	Asura_Assert(g_pxLiveSession, "session must have been created first");
	if (g_pxLiveSession)
	{
		m_xData.m_ulSessionNonce = g_pxLiveSession->GetSessionNonce();
	}
}

void Asura_Lobby_GameConnectData::Platform_Fill()
{
#ifdef ASURA_ASSERT
	DWORD uRet = 
#endif
		XUserGetXUID(Asura_Xbox360_OS::GetActivePlayerIndex(), &m_xXUID);
	Asura_Assert(uRet == ERROR_SUCCESS, "XUserGetXUID failed");
}

void Asura_Lobby_GameConnectData::Platform_ChangeByteOrder()
{
}

void Asura_Lobby_ConnectData::Platform_Fill()
{
#ifdef ASURA_ASSERT
	DWORD uRet = 
#endif
		XUserGetXUID(Asura_Xbox360_OS::GetActivePlayerIndex(), &m_xXUID);
	Asura_Assert(uRet == ERROR_SUCCESS, "XUserGetXUID failed");

	// get our XNADDR. This could be done by the server when it receives the connection
	// message with XNetInAddrToXnAddr but doing it here it simpler.
#ifdef ASURA_ASSERT
	uRet = 
#endif
		XNetGetTitleXnAddr(&m_xXNAddr);
	Asura_Assert((uRet & XNET_GET_XNADDR_PENDING) == FALSE, "XNetGetTitleXnAddr failed");
}

///////////////////////////////////////////////////////////////////////////////
// Asura_GameCompletionHistory
///////////////////////////////////////////////////////////////////////////////
void Asura_GameCompletionHistory::Platform_GetData_Start()
{
	Asura_Assert(!m_bGetting && !m_bWriting, "Unexpected flags");
	if (m_bGetting) return;
	m_bGetting = true;
	m_bDataChanged = false;

	// Request data from stats server.	
	g_xGameCompletionManager.GetDataAsync(g_wszGameCompletionFilename, &g_xGameCompletion, sizeof(g_xGameCompletion));
	if (g_xGameCompletionManager.HasErrorOccurred())
	{
		// An error occurred. Give up.
		m_bGetting = false;
	}
}

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

	// Check whether the data has arrived from stats server.
	if (!g_xGameCompletionManager.Update())
	{
		// Still waiting for the data to arrive.
		return false;
	}

	// Check the error status
	const DWORD dwResult = g_xGameCompletionManager.GetOperationResult();

	m_bGetting = false;
	
	if (dwResult == static_cast<DWORD>(XONLINE_E_STORAGE_FILE_NOT_FOUND))
	{
		// Haven't saved any data yet. Use the initial default zero values.
		Asura_OutputToDebugger_Retail(("no game completion data found on server. Initialising to default values.\n"));
		m_bDataInitialised = true;
		return true;
	}
	
	bool bError = g_xGameCompletionManager.HasErrorOccurred();
	
	if (g_xGameCompletionManager.GetNumBytesDownloaded() != sizeof(g_xGameCompletion))
	{
		Asura_Assertf(0, ("Wrong number of bytes (%u) downloaded for game completion data.", g_xGameCompletionManager.GetNumBytesDownloaded()));
		bError = true;
	}

	m_bDataInitialised = !bError;

	Asura_Assertf(!bError, ("error getting game completion from xStorage: 0x%08x", static_cast<u_int>(dwResult)));

	if (!bError)
	{
		// Copy data from g_xGameCompletion.

		// Ranked
		m_axRecord[ASURA_GAME_COMPLETION_RECORD_RANKED].m_uNumGamesHosted		= g_xGameCompletion.m_uNumGamesHosted0;
		m_axRecord[ASURA_GAME_COMPLETION_RECORD_RANKED].m_uNumGamesStarted		= g_xGameCompletion.m_uNumGamesStarted0;
		m_axRecord[ASURA_GAME_COMPLETION_RECORD_RANKED].m_uNumGamesCompleted	= g_xGameCompletion.m_uNumGamesCompleted0;

		// Friendly
		m_axRecord[ASURA_GAME_COMPLETION_RECORD_FRIENDLY].m_uNumGamesHosted		= g_xGameCompletion.m_uNumGamesHosted1;
		m_axRecord[ASURA_GAME_COMPLETION_RECORD_FRIENDLY].m_uNumGamesStarted	= g_xGameCompletion.m_uNumGamesStarted1;
		m_axRecord[ASURA_GAME_COMPLETION_RECORD_FRIENDLY].m_uNumGamesCompleted	= g_xGameCompletion.m_uNumGamesCompleted1;


		Asura_OutputToDebugger_Retail(("retrieved game completion: ranked: hosted=%u, started=%u, completed=%u,   "
									   "friendly: hosted=%u, started=%u, completed=%u\n",
			g_xGameCompletion.m_uNumGamesHosted0, g_xGameCompletion.m_uNumGamesStarted0, g_xGameCompletion.m_uNumGamesCompleted0,
			g_xGameCompletion.m_uNumGamesHosted1, g_xGameCompletion.m_uNumGamesStarted1, g_xGameCompletion.m_uNumGamesCompleted1));
	}

	return true;
}

void Asura_GameCompletionHistory::Platform_WriteData_Start()
{
	Asura_Assert(m_bDataInitialised, "Game completion data not initialised, can't write to server.");
	if (!m_bDataInitialised) return;

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

	m_bDataChanged = false;
	m_bWriting = true;

	// Copy data to g_xGameCompletion
	
	// Ranked
	g_xGameCompletion.m_uNumGamesHosted0	= m_axRecord[ASURA_GAME_COMPLETION_RECORD_RANKED].m_uNumGamesHosted;
	g_xGameCompletion.m_uNumGamesStarted0	= m_axRecord[ASURA_GAME_COMPLETION_RECORD_RANKED].m_uNumGamesStarted;
	g_xGameCompletion.m_uNumGamesCompleted0 = m_axRecord[ASURA_GAME_COMPLETION_RECORD_RANKED].m_uNumGamesCompleted;

	// Friendly
	g_xGameCompletion.m_uNumGamesHosted1	= m_axRecord[ASURA_GAME_COMPLETION_RECORD_FRIENDLY].m_uNumGamesHosted;
	g_xGameCompletion.m_uNumGamesStarted1	= m_axRecord[ASURA_GAME_COMPLETION_RECORD_FRIENDLY].m_uNumGamesStarted;
	g_xGameCompletion.m_uNumGamesCompleted1	= m_axRecord[ASURA_GAME_COMPLETION_RECORD_FRIENDLY].m_uNumGamesCompleted;

	// Send data to stats server.
	g_xGameCompletionManager.SetDataAsync(g_wszGameCompletionFilename, &g_xGameCompletion, sizeof(g_xGameCompletion));
	
	if (g_xGameCompletionManager.HasErrorOccurred())
	{
		// An error occurred. Give up.
		m_bWriting = false;
	}
}

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

	Asura_Assert(m_bDataInitialised, "Game completion data not initialised, can't write to server.");
	if (!m_bDataInitialised) return true;

	// Check whether we have finished writing the data.
	if (!g_xGameCompletionManager.Update())
	{
		// Still waiting for the data to arrive.
		return false;
	}

	// Check the error status
	Asura_Assertf(!g_xGameCompletionManager.HasErrorOccurred(), ("error getting game completion from XStorage: 0x%08x", static_cast<u_int>(g_xGameCompletionManager.GetOperationResult())));

	m_bWriting = false;
	return true;
}


#endif // #ifdef ASURA_SUPPORT_LOBBY
