//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Lobby.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Description:
//
//     PS3 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 "Debugging/Asura_DebugText.h"
#include "Interface/Asura_CommandConsole.h"
#include "Network/Asura_Network.h"
#include "Network/Asura_NetTransport_System.h"
#include "Network/Asura_ReliableSocket.h"
#include "Network/Asura_ReliableSocket_TCP.h"
#include "Network/Online/Asura_Merit.h"

// Asura PS3 includes
#include "Network/Online/Asura_PS3_AVChat2.h"
#include "Network/Online/Asura_PS3_NP.h"
#include "Network/Online/Asura_PS3_NP_TUS.h"
#include "Network/Online/Asura_PS3_NP_Basic.h"
#include "Network/Online/Asura_PS3_NP_Room.h"
#include "Network/Online/Asura_PS3_NP_RoomFinder.h"
#include "Network/Online/Asura_PS3_MeritHelper.h"
#include "Network/Online/Asura_PS3_Stats.h"

// library includes
#include <np.h>
#include <wchar.h>

// Enums
// State machine for ending game update.
enum ASURA_PS3_LOBBY_ENDGAMESTATE
{
	ASURA_PS3_LOBBY_ENDGAMESTATE_INVALID,

//	ASURA_PS3_LOBBY_ENDGAMESTATE_STATS_START,			// Start writing stats
	ASURA_PS3_LOBBY_ENDGAMESTATE_STATS_UPDATE,			// Update writing stats

	ASURA_PS3_LOBBY_ENDGAMESTATE_MERIT_START,			// Start writing merit
	ASURA_PS3_LOBBY_ENDGAMESTATE_MERIT_UPDATE,			// Update writing merit

	ASURA_PS3_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START,	// Start writing game completion data
	ASURA_PS3_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE,	// Update writing game completion data
};


// G L O B A L S /////////////////////////////////////////////////////////////
static Asura_PS3_NP_Room* g_pxRoom;
static Asura_PS3_NP_RoomFinder* g_pxRoomFinder;
#ifdef ASURA_SUPPORT_LEADERBOARDS
static Asura_PS3_StatsWriter* g_pxStatsWriter;
#endif
#ifdef ASURA_USE_MERIT_SYSTEM
static Asura_PS3_MeritWriter* g_pxMeritWriter;
#endif

static Asura_PS3_TUSTransactionManagerData g_xGameCompletionTUSManager;
static Asura_PS3_TUS_GameCompletionData g_xGameCompletion;

static ASURA_PS3_LOBBY_ENDGAMESTATE g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_INVALID;

static u_short g_usNewHostRoomMemberID = 0;
static bool g_bCanGrantCorrectHostOwnership = false;

// S T A T I C    F U N C T I O N S //////////////////////////////////////////
static bool GetPeerConnection( SceNpMatching2RoomMemberId usPeer, in_addr* xAddr, in_port_t* xPort )
{
	// sceNpMatching2SignalingGetConnectionStatus will give us the IP/port of the peer
	if (g_pxRoom)
	{
		int iConnStatus;
		int iRet = sceNpMatching2SignalingGetConnectionStatus(Asura_PS3_NP::GetContextID(), g_pxRoom->GetRoomId(), 
			usPeer, &iConnStatus, xAddr, xPort);
		if (iRet == CELL_OK)
		{
			//Asura_Assert(iConnStatus != SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, "should be pending or active");
			//Status will be inactive after we've changed host, until we receive SCE_NP_MATCHING2_SIGNALING_EVENT_Established. PJC.
			if (iConnStatus == SCE_NP_SIGNALING_CONN_STATUS_ACTIVE)
			{
				return true;
			}
		}
	}

	return false;
}

static void CreateHostPriorityList( SceNpMatching2RoomMemberId* pusOwners, u_int uNumOwners )
{
	const Asura_Lobby* pxLobby = Asura_Lobby_System::GetLobby();
	Asura_Assert(pxLobby && pusOwners,"Invalid call to CreateHostPriorityList");
	if (pxLobby && pusOwners)
	{
		//Fill array in order of score (lowest first)
		for (int iSlot = static_cast<int>(uNumOwners - 1); iSlot >= 0; iSlot--)
		{					
 			const Asura_Lobby_Player* pxPassBestHost = NULL;
 			for (u_int uOwner = 0; uOwner < uNumOwners; uOwner++)
 			{
 				const Asura_Lobby_Player& xPlayer = pxLobby->GetPlayerByIndex(uOwner);
				//if (xPlayer.GetID() == pxLobby->GetHostID()) continue;
				if (!pxPassBestHost || xPlayer.IsBetterHost( pxPassBestHost ))
				{
					bool bAlreadyAdded = false;
					for (int iExisting = static_cast<int>(uNumOwners - 1); iExisting > iSlot; iExisting--)
					{
						//If already added this player
						if (pusOwners[iExisting] == xPlayer.GetPlatformData().m_xRoomMemberID) 
						{
							bAlreadyAdded = true;
							break;
						}
					}

					if (!bAlreadyAdded)
					{
						pxPassBestHost = &xPlayer;
					}
				}
			}

			///Asura_OutputToDebugger(("Slot: %d, Connection: %d, Roomid: %d\n, Hostscore: %d", iSlot, pxPassBestHost->GetID().AsInt(), pxPassBestHost->GetPlatformData().m_xRoomMemberID, pxPassBestHost->DebugGetHostScore() ));
			pusOwners[iSlot] = pxPassBestHost->GetPlatformData().m_xRoomMemberID; //uPassHighestPlayer;
		}

#ifdef ASURA_ASSERT
		const Asura_Lobby_Player& xBestHost = pxLobby->FindUniqueBestHost();
		Asura_Assert((xBestHost.GetPlatformData().m_xRoomMemberID == pusOwners[uNumOwners - 1]), "Highest priority host is not the player with the best host score");
#endif
	}
};

// 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()
{
	ASURA_STATIC_ASSERT(ASURA_LOBBY_PLAYER_NAME_MAX_CHARS >= SCE_NET_NP_ONLINEID_MAX_LENGTH);
	ASURA_STATIC_ASSERT(sizeof(Asura_PS3_TUS_GameCompletionData) == 24);

	SceNpOnlineId xName;
	int iRet = sceNpManagerGetOnlineId(&xName);
	Asura_Assertf( iRet == CELL_OK, ("sceNpManagerGetOnlineId failed (0x%x)", iRet) );
	if (iRet == CELL_OK)
	{
		Asura_PrintfToBfr( s_awcLocalPlayerName, L"%S", ASURA_LOBBY_PLAYER_NAME_BUFFER_SIZE, true, xName.data );
	}

	Asura_PS3_AVChat2::Initialise(ASURA_LOBBY_MAX_PLAYERS);

	g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_INVALID;
}

#ifdef ASURA_USE_MERIT_SYSTEM
void Asura_Lobby_System::Platform_InitialiseMerit()
{
	// this is now set once by Asura_PS3_NP and then we maintain it ourselves.
	// So there's nothing to do on PS3 anymore.
}
#endif


void Asura_Lobby_System::Platform_Terminate()
{
	// clean up any outstanding sessions
	delete g_pxRoom;
	g_pxRoom = NULL;

#ifdef ASURA_SUPPORT_LEADERBOARDS
	delete g_pxStatsWriter;
	g_pxStatsWriter = NULL;

	Asura_PS3_StatsPreGameReader::Shutdown();
#endif

	g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_INVALID;
}

void Asura_Lobby_System::Platform_Update()
{
	if (Asura_PS3_NP_Basic::IsInvitePending())
	{
		s_bInvitationPending = true;

		if (s_bInitialised)
		{
			const Asura_PS3_InvitationInfo& xInviteInfo = Asura_PS3_NP_Basic::GetInviteInfo();

			if (g_pxRoom && g_pxRoom->GetRoomId() == xInviteInfo.m_xRoomId)
			{
				// skip invitation to a room we're already in
				s_bInvitationPending = false;
			}
			else
			{
				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_xRoomID = xInviteInfo.m_xRoomId;
			}
		}
	}

	if (s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME || s_eLobbyState == ASURA_LOBBY_STATE_PLAYING_GAME)
	{
		// check for the room having been killed off (e.g. host lost connection to PSN)q.
		if (g_pxRoom && g_pxRoom->GetState() == ASURA_PS3_NP_ROOM_STATE_DESTROYED)
		{
			Terminate(ASURA_NDR_CONNECTION_DROPPED);
		}
	}

	// JG - 16 January 2009 - Implemented voice channels on PS3
	if (s_pxLobby)
	{
		// voice channels configuration
		CellSysutilAvc2StreamingTarget xStreamingTarget; 
		CellSysutilAvc2RoomMemberList xRoomMemberList;
		Asura_Mem::Set(&xRoomMemberList, 0, sizeof(xRoomMemberList));
		SceNpMatching2RoomMemberId ausRoomMemberIDs[ASURA_LOBBY_MAX_PLAYERS]; 
		xStreamingTarget.target_mode = CELL_SYSUTIL_AVC2_CHAT_TARGET_MODE_PRIVATE; 

		u_char ucActiveChannelMask = 1 << Asura_Lobby_System::GetLocalPlayersActiveVoiceChannel(); 
		u_int uPlayers = 0; 
		for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); ++u)
		{
			const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(u);
			// skip players not connected to our active voice channel
			if ((xPlayer.GetVoiceChannels() & ucActiveChannelMask) == 0)
			{
				continue;
			}

			// skip players on the block list
			if (Asura_PS3_NP_Basic::IsPlayerOnBlockList(xPlayer.GetPlatformData().m_xNpID))
			{
				continue;
			}

			ausRoomMemberIDs[uPlayers] = xPlayer.GetPlatformData().m_xRoomMemberID;

			++uPlayers; 
		}

		xRoomMemberList.member_id = ausRoomMemberIDs;
		xRoomMemberList.member_num = uPlayers; 
		xStreamingTarget.room_member_list = xRoomMemberList; 

		cellSysutilAvc2SetStreamingTarget(xStreamingTarget);


		//If we notice that we're the room owner but not the lobby host, grant the host we're attempting
		//to connect to room ownership.
		if (g_bCanGrantCorrectHostOwnership && !IsHost() && (g_pxRoom->GetLocalMemberID() == g_pxRoom->GetHostMemberId()))
		{
			Asura_Assertf(false,("Local player is room host, but not lobby host. Granting room ownership to lobby host (room id %d)", g_usNewHostRoomMemberID));
			g_pxRoom->GrantOwnership( g_usNewHostRoomMemberID );
			g_bCanGrantCorrectHostOwnership = false;
	}

		//If players have joined, the host needs to update the owner priority list
		if (IsHost())
		{
			if ((s_eLobbyState == ASURA_LOBBY_STATE_PRE_GAME) && s_bPlayerJoinedSinceLastUpdate)
			{
				u_int uNumPotentialOwners = (s_pxLobby->GetNumPlayers() - 1); //Don't include host
				if (uNumPotentialOwners >= 1)
				{
					SceNpMatching2RoomMemberId ausOwners[uNumPotentialOwners];
					Asura_Mem::Set(ausOwners, 0, sizeof(ausOwners));

					CreateHostPriorityList( ausOwners, uNumPotentialOwners );

#if _DEBUG
					Asura_OutputToDebugger(("New host priority order (by RoomID): "));
					for (u_int u = 0; u < uNumPotentialOwners; u++)
					{
						Asura_OutputToDebugger(("%d ", ausOwners[u]));
					}
					Asura_OutputToDebugger(("\n"));
#endif

					g_pxRoom->SetOwnerAutoGrant( ausOwners, uNumPotentialOwners );
					//SceNpMatching2RoomMemberId usBestHost = s_pxLobby->FindUniqueBestHost( false ).GetPlatformData().m_xRoomMemberID;
					//Asura_OutputToDebugger_Retail(( "Autogrant: %d", usBestHost ));
					//g_pxRoom->SetOwnerAutoGrant( &usBestHost, 1 );
				}
			}
		}
	}
}

void Asura_Lobby_System::Platform_Shutdown()
{
	Asura_PS3_AVChat2::Shutdown();
	delete g_pxRoomFinder;
	g_pxRoomFinder = NULL;
	
#ifdef ASURA_USE_MERIT_SYSTEM	
	delete g_pxMeritWriter;
	g_pxMeritWriter = NULL;
#endif
}

void Asura_Lobby_System::Platform_CreateLobby(bool bHost)
{
	// on PS3 there's nothing to do if not host.
	if (bHost)
	{
		Asura_Assert(s_pxLobby, "platform independent lobby must have been created first");

		u_int uNumPrivateSlots = Asura_Lobby_System::s_xHostingSettings.m_uPrivateSlots;
		// can also get Asura_Lobby_System::s_xHostingSettings.m_bIsRanked;
		Asura_Assert(s_pxLobby->GetMaxPlayers() >= uNumPrivateSlots, "can't have more private slots than total slots");
		u_int uNumPublicSlots = s_pxLobby->GetMaxPlayers() - uNumPrivateSlots;

		g_pxRoom = new Asura_PS3_NP_Room(s_pxLobby->GetMaxPlayers());
		// create int attributes on the stack
		SceNpMatching2IntAttr axSearchAttributes[ASURA_LOBBY_NUM_INT_ATTRIBUTES];
		for (u_int uAttribute = 0; uAttribute < ASURA_LOBBY_NUM_INT_ATTRIBUTES; ++uAttribute)
		{
			axSearchAttributes[uAttribute].id = SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID + uAttribute;
			axSearchAttributes[uAttribute].num = s_pxLobby->GetAttributes().GetIntAttribute(uAttribute);
		}
		g_pxRoom->HostRoom(uNumPublicSlots, uNumPrivateSlots, axSearchAttributes, ASURA_LOBBY_NUM_INT_ATTRIBUTES);
	}
}


bool Asura_Lobby_System::Platform_UpdateLobbyCreation()
{
	if (!IsHost())
	{
		// on PS3 we join the room before connecting
		return true;
	}

	Asura_Assert(g_pxRoom, "no room was created");
	if (!g_pxRoom)
	{
		Asura_Lobby_System::Terminate(ASURA_NDR_FAILED_TO_LIST_SERVER);
		return true;
	}

	g_pxRoom->Update();
	switch (g_pxRoom->GetState())
	{
		case ASURA_PS3_NP_ROOM_STATE_CREATING:
			break;
		case ASURA_PS3_NP_ROOM_STATE_IDLE:
			return true;
		case ASURA_PS3_NP_ROOM_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;
}

bool Asura_Lobby_System::Platform_UpdateSessionRegistration()
{
	// returns true when registration is finished, sets s_bRegistered if successful.
	s_bRegistered = true;
	return true;
}

bool Asura_Lobby_System::Platform_UpdateStartingSession()
{
	if (g_pxRoom && IsHost())
	{
		const bool bAllowJoinInProgress = s_bAllowJoinInProgress && (!s_bUseRankedLobbies);
		g_pxRoom->StartGame(bAllowJoinInProgress);
	}

	// 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_SUPPORT_LEADERBOARDS
	// kick off fetch of pre-game stats
	if (!Asura_PS3_StatsPreGameReader::IsInitialised())
	{
		Asura_PS3_StatsPreGameReader::Initialise();
	}
	// don't continue until stats have arrived.
	if (!Asura_PS3_StatsPreGameReader::Update())
	{
		return false;
	}
#endif // #ifdef ASURA_SUPPORT_LEADERBOARDS

	return true;
}

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

	switch (g_eLobbyEndGameState)
	{
	case ASURA_PS3_LOBBY_ENDGAMESTATE_INVALID:
		{
			g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_STATS_UPDATE;
			break;
		}

	case ASURA_PS3_LOBBY_ENDGAMESTATE_STATS_UPDATE:
		{
			#ifdef ASURA_SUPPORT_LEADERBOARDS
			if (g_pxStatsWriter)
			{
				bFinishedState = g_pxStatsWriter->Update();
				if (bFinishedState)
				{
					delete g_pxStatsWriter;
					g_pxStatsWriter = NULL;
				}
			}
			#endif //ASURA_SUPPORT_LEADERBOARDS

			if (bFinishedState)
			{
				g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_MERIT_START;
			}
			break;
		}

	case ASURA_PS3_LOBBY_ENDGAMESTATE_MERIT_START:
		{
			#ifdef ASURA_USE_MERIT_SYSTEM
			if (Asura_Merit_System::HasComputedStats())
			{
				// update local merit store
				u_int uMeritLevel = 0;
				int iMeritPoints = 0;
				int iMeritLevelChange = 0;
				int iMeritPointsChange = 0;
				bool bGotMyMerit = Asura_Merit_System::GetPlayerMerit(Asura_Network::GetLastClientID(), 
					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);
				}

				Asura_Assert(!g_pxMeritWriter, "memory leak?");
				
				// write the stats for everyone if we're the host
				if (IsHost())
				{
					g_pxMeritWriter = new Asura_PS3_MeritWriter();
				}
				else
				{
					// ensure it's not set (in case the assert fired)
					g_pxMeritWriter = NULL;
				}
			}
			#endif //ASURA_USE_MERIT_SYSTEM
			g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_MERIT_UPDATE;
			break;
		}

	case ASURA_PS3_LOBBY_ENDGAMESTATE_MERIT_UPDATE:
		{
			#ifdef ASURA_USE_MERIT_SYSTEM	
			if (g_pxMeritWriter)
			{
				bFinishedState = g_pxMeritWriter->Update();
				if (bFinishedState)
				{
					delete g_pxMeritWriter;
					g_pxMeritWriter = NULL;
				}
			}
			#endif //ASURA_USE_MERIT_SYSTEM

			if (bFinishedState)
			{
				g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START;
			}
			break;
		}

	case ASURA_PS3_LOBBY_ENDGAMESTATE_GAMECOMPLETION_START:
		{
			s_xGameCompletionHistory.Platform_WriteData_Start();
			g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE;
			break;
		}

	case ASURA_PS3_LOBBY_ENDGAMESTATE_GAMECOMPLETION_UPDATE:
		{
			bFinishedState = s_xGameCompletionHistory.Platform_WriteData_Update();
			if (bFinishedState)
			{
				g_eLobbyEndGameState = ASURA_PS3_LOBBY_ENDGAMESTATE_INVALID;
				return true; // Done everything.
			}
			break;
		}

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

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

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

	// NOTE: this has to be done early before the entities which contain the stats are deleted.

#ifdef ASURA_SUPPORT_LEADERBOARDS
	// only write stats in ranked games.
	// If there was an error retrieving pre game stats, additive stats will not
	// work so ignore stats completely in that case.
	if (s_bUseRankedLobbies && !Asura_PS3_StatsPreGameReader::HasErrorOccurred())
	{
		Asura_Assert(!g_pxStatsWriter, "memory leak");
		g_pxStatsWriter = new Asura_PS3_StatsWriter();
		g_pxStatsWriter->WriteStats(false, Asura_Network::GetClientID());
	}
#endif //ASURA_SUPPORT_LEADERBOARDS
}

void Asura_Lobby_System::Platform_BeginSearch()
{
	Asura_Assert(!g_pxRoomFinder, "Match finder already exists - memory leak");
	g_pxRoomFinder = new Asura_PS3_NP_RoomFinder(ASURA_LOBBY_MAX_SEARCH_RESULTS);

	// create int search filters for the matching 2 SDK.

	// check the SDK defines these as consecutive
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_2_ID);
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_2_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_3_ID);
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_3_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_4_ID);
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_4_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_5_ID);
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_5_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_6_ID);
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_6_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_7_ID);
	ASURA_STATIC_ASSERT(SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_7_ID + 1 == SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_8_ID);
	ASURA_STATIC_ASSERT(ASURA_LOBBY_NUM_INT_ATTRIBUTES <= 8);

	SceNpMatching2IntSearchFilter axFilters[ASURA_LOBBY_MAX_SEARCH_CRITERIA];
	Asura_Assert(s_uNumSearchCriteria <= ASURA_LOBBY_MAX_SEARCH_CRITERIA, "too many filters");
	Asura_Mem::Set(axFilters, 0, sizeof(axFilters));
	for (u_int u = 0; u < s_uNumSearchCriteria; ++u)
	{
		Asura_Assert(s_pxSearchCriteria[u].GetAttributeID() < ASURA_LOBBY_NUM_INT_ATTRIBUTES, "unexpected attribute");
		axFilters[u].attr.id = SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID + s_pxSearchCriteria[u].GetAttributeID();
		bool bForceNonNegative = true; // >= and <= checks can only be done on non-negative int values.
		switch (s_pxSearchCriteria[u].GetSearchOperator())
		{
			case ASURA_LOBBY_SEARCH_OPERATOR_EQ:
			{
				axFilters[u].searchOperator = SCE_NP_MATCHING2_OPERATOR_EQ;
				bForceNonNegative = false; // With equality we allow the full range, allowing hash ids to be used for some search criteria
				break;
			}
			case ASURA_LOBBY_SEARCH_OPERATOR_GE:
			{
				axFilters[u].searchOperator = SCE_NP_MATCHING2_OPERATOR_GE;
				break;
			}
			case ASURA_LOBBY_SEARCH_OPERATOR_LE:
			{
				axFilters[u].searchOperator = SCE_NP_MATCHING2_OPERATOR_LE;
				break;
			}
			default:
			{
				Asura_Assert(false, "unhandled search operator");
				break;
			}
		}
		if (bForceNonNegative)
		{
			Asura_Assert(s_pxSearchCriteria[u].GetValue() >= 0, "PS3 requires lobby searchable ints to be unsigned");
			axFilters[u].attr.num = Asura_Maths::ClampToRange(s_pxSearchCriteria[u].GetValue(), 0, INT_MAX);
		}
		else
		{
			axFilters[u].attr.num = s_pxSearchCriteria[u].GetValue();
		}
	}

	if (!g_pxRoomFinder->BeginSearch(axFilters, s_uNumSearchCriteria, ASURA_LOBBY_NUM_INT_ATTRIBUTES))
	{
		delete g_pxRoomFinder;
		g_pxRoomFinder = NULL;
	}
}

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

bool Asura_Lobby_System::Platform_SearchHasFinished()
{
	Asura_Assert(g_pxRoomFinder, "no room finder was created");
	if (!g_pxRoomFinder)
	{
		return true;
	}

	bool bSearchComplete = g_pxRoomFinder->IsSearchComplete();
	if (bSearchComplete)
	{
		// copy the search results into the platform-independent data array
		u_int uNumSearchResults = g_pxRoomFinder->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 uNumValidSearchResults = 0;

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

			// don't count the room we're in (in case we're in a room but searching for another one)
			if (g_pxRoom && xResult.roomId == g_pxRoom->GetRoomId())
			{
				continue;
			}

			// check player counts are valid.
			const u_int uNumPlayers = xResult.curMemberNum;
			const u_int uMaxPlayers = xResult.maxSlot;

			Asura_Assertf(uNumPlayers <= ASURA_LOBBY_MAX_PLAYERS && uMaxPlayers <= ASURA_LOBBY_MAX_PLAYERS, 
				("invalid num/max (%u/%u) players, skipping result %u", uNumPlayers, uMaxPlayers, uResult));
			if (uNumPlayers > ASURA_LOBBY_MAX_PLAYERS || uMaxPlayers > ASURA_LOBBY_MAX_PLAYERS)
			{
				continue;
			}

			s_pxSearchResults[uNumValidSearchResults].m_xRoomID = xResult.roomId;
			s_pxSearchResults[uNumValidSearchResults].m_uNumPlayers = uNumPlayers;
			s_pxSearchResults[uNumValidSearchResults].m_uMaxPlayers = uMaxPlayers;

			if (xResult.owner && xResult.owner->onlineName)
			{
				Asura_PrintfToBfr( s_pxSearchResults[uNumValidSearchResults].m_awcDescription, L"%S", ASURA_LOBBY_DESCRIPTION_LENGTH, true, xResult.owner->onlineName->data );
			}
			else
			{
				s_pxSearchResults[uNumValidSearchResults].m_awcDescription[0] = 0;
			}
			

			Asura_Assertf(xResult.roomSearchableIntAttrExternalNum == ASURA_LOBBY_NUM_INT_ATTRIBUTES, 
				("not all room attributes were available, ignoring result %u", uResult));
			if (xResult.roomSearchableIntAttrExternalNum != ASURA_LOBBY_NUM_INT_ATTRIBUTES)
			{
				continue;
			}
			bool bOk = true;
			for (u_int uAttribute = 0; uAttribute < xResult.roomSearchableIntAttrExternalNum; ++uAttribute)
			{
				Asura_Assertf(xResult.roomSearchableIntAttrExternal[uAttribute].id == 
					SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID + uAttribute, 
					("unexpected attribute ID, skipping result %u", uResult));
				if (xResult.roomSearchableIntAttrExternal[uAttribute].id != 
					SCE_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID + uAttribute)
				{
					bOk = false;
					break;
				}
				s_pxSearchResults[uNumValidSearchResults].m_xAttributes.SetIntAttribute(uAttribute, xResult.roomSearchableIntAttrExternal[uAttribute].num);
			}
			// only make this result valid if all attributes were obtained.
			if (bOk)
			{
				++uNumValidSearchResults;
			}
		}

		s_uNumSearchResults = uNumValidSearchResults;

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

bool Asura_Lobby_System::Platform_PrepareToJoinLobby( u_int uIndex, Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
	// on PS3 we need to join the lobby first to enable P2P communications
	if (!g_pxRoom)
	{
		Asura_Assert(uIndex < s_uNumSearchResults, "invalid search result index");
		if (uIndex >= s_uNumSearchResults)
		{
			return false;
		}

		// when joining a lobby by invitation, max players is not known in advance.
		u_int uMaxPlayers = s_pxSearchResults[uIndex].m_uMaxPlayers;
		if (!uMaxPlayers)
		{
			uMaxPlayers = ASURA_LOBBY_MAX_PLAYERS;
		}

		g_pxRoom = new Asura_PS3_NP_Room(uMaxPlayers);
		g_pxRoom->JoinRoom(s_pxSearchResults[uIndex].m_xRoomID);
	}

	g_pxRoom->Update();

	switch (g_pxRoom->GetState())
	{
		case ASURA_PS3_NP_ROOM_STATE_IDLE:
		{
			struct in_addr xPeerAddr;
			in_port_t usPeerPort;
			if (GetPeerConnection( g_pxRoom->GetHostMemberId(), &xPeerAddr, &usPeerPort))
				{
					xAddress = xPeerAddr.s_addr;
					xPort.m_usPort = ASURA_LOBBY_FIRST_PORT;
					xPort.m_usVPort = usPeerPort;
					return true;
				}
			break;
		}
		case ASURA_PS3_NP_ROOM_STATE_ERROR:
		{
			Terminate(g_pxRoom->GetErrorCode());
			break;
		}
		default:
			break;
	}

	return false;
}

bool Asura_Lobby_System::Platform_UpdateInitialisingGame()
{
	if (!g_pxRoom)
	{
		Asura_Assert(false,"Should have a room at this point");
		return false;
	}

	g_pxRoom->Update();

	switch (g_pxRoom->GetState())
	{
		case ASURA_PS3_NP_ROOM_STATE_IDLE:
		{
			if (IsHost())
			{
				//Nothing to do
				return true;
			}
			else
			{
				if (s_bMigrateOnGameStart || s_bMigrateOnDeadHost)
				{
					// TODO: this probably doesn't quite do what we want...
					Asura_Assert(s_pxLobby,"");
					if (s_pxLobby)
					{
						struct in_addr xPeerAddr;
						in_port_t usPeerPort;
						//We don't use the room's host to connect to, as we might have not received the updated host after a migration
						const Asura_Lobby_Player& xNewHost = s_pxLobby->FindUniqueBestHost();	
						if (GetPeerConnection( xNewHost.GetPlatformData().m_xRoomMemberID, &xPeerAddr, &usPeerPort ))
						{
							Asura_PrintfToBfr( s_acGameAddress, "%s", ASURA_LOBBY_ADDRESS_LENGTH, true,
								Asura_NetTransport_System::GetStringFromAddress(xPeerAddr.s_addr, usPeerPort));
							return true;
						}
					}
				}
				else
				{
					struct in_addr xPeerAddr;
					in_port_t usPeerPort;
					if (GetPeerConnection( g_pxRoom->GetHostMemberId(), &xPeerAddr, &usPeerPort))
					{
						Asura_PrintfToBfr( s_acGameAddress, "%s", ASURA_LOBBY_ADDRESS_LENGTH, true,
							Asura_NetTransport_System::GetStringFromAddress(xPeerAddr.s_addr, usPeerPort));
						return true;
					}
				}
			}
			break;
		}
		
		case ASURA_PS3_NP_ROOM_STATE_ERROR:
		{
			Terminate(g_pxRoom->GetErrorCode());
			break;
		}
	
		default:
			break;
	}

	return false;
}

void Asura_Lobby_System::Platform_ServerStartMigration( bool bNewServer )
{
	if (!bNewServer)
	{
		Asura_Assert(g_pxRoom && s_pxLobby,"");
		if (g_pxRoom && s_pxLobby)
		{
			u_short usNewOwner = s_pxLobby->FindUniqueBestHost().GetPlatformData().m_xRoomMemberID;
			g_pxRoom->SetOwnerAutoGrant( NULL, 0 ); //TODO: is this necessary?
			g_pxRoom->GrantOwnership( usNewOwner );
		}
	}

	g_bCanGrantCorrectHostOwnership = false;
}

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

bool Asura_Lobby_System::Platform_ClientEndMigration()
{
	// Nothing to do
	// Possibly check that our lobby's definition of host matches that held by the room
	return true;
}

bool Asura_Lobby_System::Platform_GetMigrationAddress( Asura_NetAddress& xAddress, Asura_NetPort& xPort )
{
	if (s_pxLobby)
	{
		const Asura_Lobby_Player& xNewHost = s_pxLobby->FindUniqueBestHost();

		in_addr xAddr;
		in_port_t usPort;
		if (GetPeerConnection( xNewHost.GetPlatformData().m_xRoomMemberID, &xAddr, &usPort ))
		{
			g_usNewHostRoomMemberID = xNewHost.GetPlatformData().m_xRoomMemberID;
			g_bCanGrantCorrectHostOwnership = (s_eLobbyState == ASURA_LOBBY_STATE_MIGRATING_DEADHOST);

			xAddress = xAddr.s_addr;
			xPort.m_usPort = ASURA_LOBBY_FIRST_PORT;
			xPort.m_usVPort = usPort;	
			return true;
		}
	}
	return false;
}

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

void Asura_Lobby_System::Platform_UpdateDebugOutput( u_int& uX, u_int& uY )
{
#ifdef ASURA_USE_DEBUG_TEXT
	uY++;
	SceNpMatching2RoomMemberId usPlatformHostID = g_pxRoom->GetHostMemberId();
	bool bFound = false;
	for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
	{
		Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(u);
		if (xPlayer.GetPlatformData().m_xRoomMemberID == usPlatformHostID)
		{
			bool bMatches = (xPlayer.GetID() == s_pxLobby->GetHostID());
			if (bMatches)
			{
				Asura_DebugText::AddTextBlock( uX, uY++, 0xFF00FF00, 0, "Platform Lobby Host : %d (Matches)", xPlayer.GetID().AsInt() );
			}
			else
			{
				Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFF0000, 0, "Platform Lobby Host : %d (No-match)", xPlayer.GetID().AsInt() );
			}
			bFound = true;
			break;
		}
	}
	if (!bFound) Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFF0000, 0, "Platform Lobby Host : (Unknown)" );

	for (u_int u = 0; u < s_pxLobby->GetNumPlayers(); u++)
	{
		const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(u);
		Asura_DebugText::AddTextBlock( uX, uY++, 0xFFFF0000, 0, "Connection %d = Room Member %d", xPlayer.GetID().AsInt(), xPlayer.GetPlatformData().m_xRoomMemberID );
	}
#endif
}

bool Asura_Lobby_System::Platform_IsPlayerTalking( const Asura_Lobby_Player& xPlayer )
{
	return Asura_PS3_AVChat2::IsPlayerSpeaking(xPlayer.GetPlatformData().m_xRoomMemberID);
}

bool Asura_Lobby_System::Platform_IsMicrophoneAttached()
{
	return Asura_PS3_AVChat2::IsMicrophoneAttached();
}

void Asura_Lobby_System::Platform_DoPlayerListAction( u_int uIndex )
{
	const Asura_Lobby_Player& xPlayer = s_pxLobby->GetPlayerByIndex(uIndex);
	Asura_Assert(s_pxLobby && (uIndex < s_pxLobby->GetNumPlayers()), "should be checked by calling code");
#ifdef ASURA_ASSERT
		int iRet = 
#endif
		sceNpBasicAddFriend( &xPlayer.GetPlatformData().m_xNpID, NULL, SYS_MEMORY_CONTAINER_ID_INVALID);
		Asura_Assertf(iRet == CELL_OK, ("sceNpBasicAddFriend() failed (0x%x)", iRet));
}

bool Asura_Lobby_System::Platform_ConsoleCmd_LobbyInviteFriends( bool bHelp )
{
	if (!bHelp)
	{
		Asura_Assert(g_pxRoom, "only expect InviteFriends command to be used when in a lobby");
		Asura_Assert(!s_bUseRankedLobbies, "can't invite friends to a ranked game");
		if (g_pxRoom && !s_bUseRankedLobbies)
		{
			const Asura_UnicodeChar* wszSubject = NULL;
			const Asura_UnicodeChar* wszBody = NULL;
			Project_GetInviteText(wszSubject, wszBody);
			g_pxRoom->InviteFriendToRoom(wszSubject, wszBody);
			return true;
		}
	}
	return false;
}

void Asura_Lobby::Platform_AddPlayer( const Asura_Lobby_Player& xPlayer )
{
	Asura_Assert(g_pxRoom, "no room created");
	if (g_pxRoom)
	{
		g_pxRoom->AddPlayer(xPlayer.GetPlatformData().m_xNpID);
	}
	Asura_PS3_NP_Basic::AddPlayerToRecentPlayersList(xPlayer.GetPlatformData().m_xNpID);
}

void Asura_Lobby::Platform_RemovePlayer( const Asura_Lobby_Player& xPlayer )
{
	if (Asura_PS3_NP::IsInErrorState())
	{
		// calling NP functions when it's not initialised will crash so do nothing.
		return;
	}

	Asura_Assert(g_pxRoom, "no room created");
	if (g_pxRoom)
	{
		g_pxRoom->RemovePlayer(xPlayer.GetPlatformData().m_xNpID);
	}
}

void Asura_Lobby_GameConnectData::Platform_Fill()
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
		sceNpManagerGetNpId(&m_xNpID);
	Asura_Assertf(iRet == CELL_OK, ("sceNpManagerGetOnlineId() failed (0x%x)", iRet));
}

void Asura_Lobby_GameConnectData::Platform_ChangeByteOrder()
{
}

void Asura_Lobby_ConnectData::Platform_Fill()
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
		sceNpManagerGetNpId(&m_xNpID);
	Asura_Assertf(iRet == CELL_OK, ("sceNpManagerGetOnlineId() failed (0x%x)", iRet));

	Asura_Assert(!Asura_Lobby_System::IsInitialised() || g_pxRoom, "room must have been created before connecting");
	if (Asura_Lobby_System::IsInitialised() && g_pxRoom)
	{
		m_xRoomMemberID = g_pxRoom->GetLocalMemberID();
	}
	else
	{
		m_xRoomMemberID = 0;
	}
}

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

	// MGH 23/2/2009: Reread the data even if it has already been initialised.
	// This is to cover the case in which a client pulls out their network cable during
	// a game, which is incorrectly counted as a completion. We don't want them to be
	// able to write this bogus stat back to the server, so reread the data when
	// they reconnect and reinit the netlink manager.
	//if (m_bDataInitialised) return; // If we've already initialised the data then there is no need to re-read it.
	if (m_bGetting) return;
	m_bGetting = true;
	m_bDataChanged = false;

	// Request data from stats server.
	Asura_PS3_NP_TUS::Initialise();
	const int iRet = g_xGameCompletionTUSManager.GetDataAsync(Asura_PS3_NP::GetNpID(), ASURA_PS3_TUS_DATA_SLOT_GAMECOMPLETION, &g_xGameCompletion, sizeof(g_xGameCompletion));
	const bool bOk = (iRet == CELL_OK);
	if (!bOk)
	{
		 // An error occurred. Give up.
		m_bGetting = false;
		Asura_PS3_NP_TUS::Shutdown();
	}
}

// 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_xGameCompletionTUSManager.Update())
	{
		// Still waiting for the data to arrive.
		return false;
	}

	// Check the error status
	const int iResult = g_xGameCompletionTUSManager.GetOperationResult();

	m_bGetting = false;
	Asura_PS3_NP_TUS::Shutdown();
	
	if (iResult == static_cast<int>(SCE_NP_COMMUNITY_SERVER_ERROR_USER_STORAGE_DATA_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;
	}

	const bool bError = g_xGameCompletionTUSManager.HasErrorOccurred();
	m_bDataInitialised = !bError;

	Asura_Assertf(!bError, ("error getting game completion from TUS: 0x%x", iResult));

	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.
	Asura_PS3_NP_TUS::Initialise();

	const int iRet = g_xGameCompletionTUSManager.SetDataAsync(Asura_PS3_NP::GetNpID(), ASURA_PS3_TUS_DATA_SLOT_GAMECOMPLETION, &g_xGameCompletion, sizeof(g_xGameCompletion));

	const bool bOk = (iRet == CELL_OK);
	if (!bOk)
	{
		 // An error occurred. Give up.
		m_bWriting = false;
		Asura_PS3_NP_TUS::Shutdown();
	}
}

// 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_xGameCompletionTUSManager.Update())
	{
		// Still waiting for the data to arrive.
		return false;
	}

	// Check the error status
	Asura_Assertf(!g_xGameCompletionTUSManager.HasErrorOccurred(), ("error getting game completion from TUS: 0x%x", g_xGameCompletionTUSManager.GetOperationResult()));

	m_bWriting = false;
	Asura_PS3_NP_TUS::Shutdown();

	return true;
}



#endif // #ifdef ASURA_SUPPORT_LOBBY
