//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Interface to Xbox 360 system functions. Helps deal with the async system and Live
//			functions by either providing a handle for the caller to check results or handling
//			automatic cleanup of the async data when the caller doesn't care about the results.
//
//=====================================================================================//

#include "host.h"
#include "tier3/tier3.h"
#include "vgui/ILocalize.h"
#include "ixboxsystem.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


static wchar_t g_szModSaveContainerDisplayName[XCONTENT_MAX_DISPLAYNAME_LENGTH] = L"";
static char g_szModSaveContainerName[XCONTENT_MAX_FILENAME_LENGTH] = "";

//-----------------------------------------------------------------------------
// Implementation of IXboxSystem interface
//-----------------------------------------------------------------------------
class CXboxSystem : public IXboxSystem
{
public:
	CXboxSystem( void );

	virtual	~CXboxSystem( void );

	virtual AsyncHandle_t	CreateAsyncHandle( void );
	virtual void			ReleaseAsyncHandle( AsyncHandle_t handle );
	virtual int				GetOverlappedResult( AsyncHandle_t handle, uint *pResultCode, bool bWait );
	virtual void			CancelOverlappedOperation( AsyncHandle_t handle );

	// Save/Load
	virtual void			GetModSaveContainerNames( const char *pchModName, const wchar_t **ppchDisplayName, const char **ppchName );
	virtual uint			GetContainerRemainingSpace( void );
	virtual bool			DeviceCapacityAdequate( DWORD nDeviceID, const char *pModName );
	virtual DWORD			DiscoverUserData( DWORD nUserID, const char *pModName );

	// XUI
	virtual bool			ShowDeviceSelector( bool bForce, uint *pStorageID, AsyncHandle_t *pHandle );
	virtual void			ShowSigninUI( uint nPanes, uint nFlags );

	// Rich Presence and Matchmaking
	virtual int				UserSetContext( uint nUserIdx, uint nContextID, uint nContextValue, bool bAsync, AsyncHandle_t *pHandle);
	virtual int				UserSetProperty( uint nUserIndex, uint nPropertyId, uint nBytes, const void *pvValue, bool bAsync, AsyncHandle_t *pHandle );

	// Matchmaking
	virtual int				CreateSession( uint nFlags, uint nUserIdx, uint nMaxPublicSlots, uint nMaxPrivateSlots, uint64 *pNonce, void *pSessionInfo, XboxHandle_t *pSessionHandle, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual uint			DeleteSession( XboxHandle_t hSession, bool bAsync, AsyncHandle_t *pAsyncHandle = NULL );
	virtual uint			SessionSearch( uint nProcedureIndex, uint nUserIndex, uint nNumResults, uint nNumUsers, uint nNumProperties, uint nNumContexts, XUSER_PROPERTY *pSearchProperties, XUSER_CONTEXT *pSearchContexts, uint *pcbResultsBuffer, XSESSION_SEARCHRESULT_HEADER *pSearchResults, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual uint			SessionStart( XboxHandle_t hSession, uint nFlags, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual uint			SessionEnd( XboxHandle_t hSession, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual int				SessionJoinLocal( XboxHandle_t hSession, uint nUserCount, const uint *pUserIndexes, const bool *pPrivateSlots, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual int				SessionJoinRemote( XboxHandle_t hSession, uint nUserCount, const XUID *pXuids, const bool *pPrivateSlot, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual int				SessionLeaveLocal( XboxHandle_t hSession, uint nUserCount, const uint *pUserIndexes, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual int				SessionLeaveRemote( XboxHandle_t hSession, uint nUserCount, const XUID *pXuids, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual int				SessionMigrate( XboxHandle_t hSession, uint nUserIndex, void *pSessionInfo, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual int				SessionArbitrationRegister( XboxHandle_t hSession, uint nFlags, uint64 nonce, uint *pBytes, void *pBuffer, bool bAsync, AsyncHandle_t *pAsyncHandle );

	// Stats
	virtual int				WriteStats( XboxHandle_t hSession, XUID xuid, uint nViews, void* pViews, bool bAsync, AsyncHandle_t *pAsyncHandle );

	// Achievements
	virtual int				EnumerateAchievements( uint nUserIdx, uint64 xuid, uint nStartingIdx, uint nCount, void *pBuffer, uint nBufferBytes, bool bAsync, AsyncHandle_t *pAsyncHandle );
	virtual void			AwardAchievement( uint nUserIdx, uint nAchievementId );
	
	virtual void			FinishContainerWrites( void );
	virtual uint			GetContainerOpenResult( void );
	virtual uint			OpenContainers( void );
	virtual void			CloseContainers( void );

private:
	virtual uint			CreateSavegameContainer( uint nCreationFlags );
	virtual uint			CreateUserSettingsContainer( uint nCreationFlags );

	uint					m_OpenContainerResult;
};

static CXboxSystem s_XboxSystem;
IXboxSystem *g_pXboxSystem = &s_XboxSystem;

EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CXboxSystem, IXboxSystem, XBOXSYSTEM_INTERFACE_VERSION, s_XboxSystem );

#define ASYNC_RESULT(ph) 	((AsyncResult_t*)*ph);

#if defined( _X360 )
//-----------------------------------------------------------------------------
// Holds the overlapped object and any persistent data for async system calls
//-----------------------------------------------------------------------------
typedef struct AsyncResult_s
{
	XOVERLAPPED		overlapped;
	bool			bAutoRelease;
	void			*pInputData;
	AsyncResult_s	*pNext;
} AsyncResult_t;

static AsyncResult_t * g_pAsyncResultHead = NULL;

//-----------------------------------------------------------------------------
// Purpose: Remove an AsyncResult_t from the list
//-----------------------------------------------------------------------------
static void ReleaseAsyncResult( AsyncResult_t *pAsyncResult )
{
	if ( pAsyncResult == g_pAsyncResultHead )
	{
		g_pAsyncResultHead = pAsyncResult->pNext;
		free( pAsyncResult->pInputData );
		delete pAsyncResult;
		return;
	}

	AsyncResult_t *pNode = g_pAsyncResultHead;
	while ( pNode->pNext )
	{
		if ( pNode->pNext == pAsyncResult )
		{
			pNode->pNext = pAsyncResult->pNext;
			free( pAsyncResult->pInputData );
			delete pAsyncResult;
			return;
		}
		pNode = pNode->pNext;
	}
	Warning( "AsyncResult_t not found in ReleaseAsyncResult.\n" );
}

//-----------------------------------------------------------------------------
// Purpose: Remove an AsyncResult_t from the list
//-----------------------------------------------------------------------------
static void ReleaseAsyncResult( XOVERLAPPED *pOverlapped )
{
	AsyncResult_t *pResult = g_pAsyncResultHead;
	while ( pResult )
	{
		if ( &pResult->overlapped == pOverlapped )
		{
			ReleaseAsyncResult( pResult );
			return;
		}
	}
	Warning( "XOVERLAPPED couldn't be found in ReleaseAsyncResult.\n" );
}

//-----------------------------------------------------------------------------
// Purpose: Release async results that were marked for auto-release.
//-----------------------------------------------------------------------------
static void CleanupFinishedAsyncResults()
{
	AsyncResult_t *pResult = g_pAsyncResultHead;
	AsyncResult_t *pNext;
	while( pResult )
	{
		pNext = pResult->pNext;
		if ( pResult->bAutoRelease )
		{
			if ( XHasOverlappedIoCompleted( &pResult->overlapped ) )
			{
				ReleaseAsyncResult( pResult );
			}
		}
		pResult = pNext;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Add a new AsyncResult_t object to the list
//-----------------------------------------------------------------------------
static AsyncResult_t *CreateAsyncResult( bool bAutoRelease )
{
	// Take this opportunity to clean up finished operations
	CleanupFinishedAsyncResults();

	AsyncResult_t *pAsyncResult = new AsyncResult_t;
	memset( pAsyncResult, 0, sizeof( AsyncResult_t ) );

	pAsyncResult->pNext = g_pAsyncResultHead;
	g_pAsyncResultHead = pAsyncResult;

	if ( bAutoRelease )
	{
		pAsyncResult->bAutoRelease = true;
	}

	return pAsyncResult;
}

//-----------------------------------------------------------------------------
// Purpose: Return an AsyncResult_t object to the pool
//-----------------------------------------------------------------------------
static void InitializeAsyncHandle( AsyncHandle_t *pHandle )
{
	XOVERLAPPED *pOverlapped = &((AsyncResult_t *)*pHandle)->overlapped;
	memset( pOverlapped, 0, sizeof( XOVERLAPPED ) );
}

//-----------------------------------------------------------------------------
// Purpose: Initialize or create and async handle
//-----------------------------------------------------------------------------
static AsyncResult_t *InitializeAsyncResult( AsyncHandle_t **ppAsyncHandle )
{
	AsyncResult_t *pResult = NULL;
	if ( *ppAsyncHandle )
	{
		InitializeAsyncHandle( *ppAsyncHandle );
		pResult = ASYNC_RESULT( *ppAsyncHandle );
	}
	else
	{
		// No handle provided, create one
		pResult = CreateAsyncResult( true );
	}
	return pResult;
}

CXboxSystem::CXboxSystem( void ) : m_OpenContainerResult( ERROR_SUCCESS )
{
}

//-----------------------------------------------------------------------------
// Purpose: Force overlapped operations to finish and clean up
//-----------------------------------------------------------------------------
CXboxSystem::~CXboxSystem()
{
	// Force async operations to finish.
	AsyncResult_t *pResult = g_pAsyncResultHead;
	while ( pResult )
	{
		AsyncResult_t *pNext = pResult->pNext;
		GetOverlappedResult( (AsyncHandle_t)pResult, NULL, true );
		pResult = pNext;
	}

	// Release any remaining handles - should have been released by the client that created them.
	int ct = 0;
	while ( g_pAsyncResultHead )
	{
		ReleaseAsyncResult( g_pAsyncResultHead );
		++ct;
	}

	if ( ct )
	{
		Warning( "Released %d async handles\n", ct );
	}
}

//-----------------------------------------------------------------------------
//	Purpose: Check on the result of an overlapped operation
//-----------------------------------------------------------------------------
int CXboxSystem::GetOverlappedResult( AsyncHandle_t handle, uint *pResultCode, bool bWait )
{
	if ( !handle )
		return ERROR_INVALID_HANDLE;

	return XGetOverlappedResult( &((AsyncResult_t*)handle)->overlapped, (DWORD*)pResultCode, bWait );
}

//-----------------------------------------------------------------------------
//	Purpose: Cancel an overlapped operation
//-----------------------------------------------------------------------------
void CXboxSystem::CancelOverlappedOperation( AsyncHandle_t handle )
{
	XCancelOverlapped( &((AsyncResult_t*)handle)->overlapped );
}

//-----------------------------------------------------------------------------
// Purpose: Create a new AsyncHandle_t
//-----------------------------------------------------------------------------
AsyncHandle_t CXboxSystem::CreateAsyncHandle( void )
{
	return (AsyncHandle_t)CreateAsyncResult( false );
}

//-----------------------------------------------------------------------------
// Purpose: Delete an AsyncHandle_t
//-----------------------------------------------------------------------------
void CXboxSystem::ReleaseAsyncHandle( AsyncHandle_t handle )
{
	ReleaseAsyncResult( (AsyncResult_t*)handle );
}

//-----------------------------------------------------------------------------
// Purpose: Close the open containers
//-----------------------------------------------------------------------------
void CXboxSystem::CloseContainers( void )
{
	XContentClose( GetCurrentMod(), NULL );
	XContentClose( XBX_USER_SETTINGS_CONTAINER_DRIVE, NULL );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
uint CXboxSystem::OpenContainers( void )
{
	// Close the containers (force dismount)
	CloseContainers();

	m_OpenContainerResult = ERROR_SUCCESS;

	// Open the save games
	if ( ( m_OpenContainerResult = CreateUserSettingsContainer( XCONTENTFLAG_OPENALWAYS ) ) != ERROR_SUCCESS )
		return m_OpenContainerResult;

	// If we're TF, we don't care about save game space
	if ( !Q_stricmp( GetCurrentMod(), "tf" ) )
		return m_OpenContainerResult;

	// Open the user settings
	if ( ( m_OpenContainerResult = CreateSavegameContainer( XCONTENTFLAG_OPENALWAYS ) ) != ERROR_SUCCESS )
	{
		CloseContainers();
		return m_OpenContainerResult;
	}
	
	return m_OpenContainerResult;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the results from the last container opening
//-----------------------------------------------------------------------------
uint CXboxSystem::GetContainerOpenResult( void )
{
	return m_OpenContainerResult;
}

//-----------------------------------------------------------------------------
//	Purpose: Open the save game container for the current mod
//-----------------------------------------------------------------------------
uint CXboxSystem::CreateSavegameContainer( uint nCreationFlags )
{
	if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
		return ERROR_INVALID_HANDLE;

	// Don't allow any of our saves or user data to be transferred to another user
	nCreationFlags |= XCONTENTFLAG_NOPROFILE_TRANSFER;

	const wchar_t *pchContainerDisplayName;
	const char *pchContainerName;
	g_pXboxSystem->GetModSaveContainerNames( GetCurrentMod(), &pchContainerDisplayName, &pchContainerName );

	XCONTENT_DATA contentData;
	contentData.DeviceID = XBX_GetStorageDeviceId();
	contentData.dwContentType = XCONTENTTYPE_SAVEDGAME;
	Q_wcsncpy( contentData.szDisplayName, pchContainerDisplayName, sizeof ( contentData.szDisplayName ) );
	Q_snprintf( contentData.szFileName, sizeof( contentData.szFileName ), pchContainerName );

	SIZE_T dwFileCacheSize = 0; // Use the smallest size (default)
	ULARGE_INTEGER ulSize;
	ulSize.QuadPart = XBX_PERSISTENT_BYTES_NEEDED;

	int nRet = XContentCreateEx( XBX_GetPrimaryUserId(), GetCurrentMod(), &contentData, nCreationFlags, NULL, NULL, dwFileCacheSize, ulSize, NULL );
	if ( nRet == ERROR_SUCCESS )
	{
		BOOL bUserIsCreator = false;
		XContentGetCreator( XBX_GetPrimaryUserId(), &contentData, &bUserIsCreator, NULL, NULL );
		if( bUserIsCreator == false )
		{
			XContentClose( GetCurrentMod(), NULL );
			return ERROR_ACCESS_DENIED;
		}
	}

	return nRet;
}

//-----------------------------------------------------------------------------
//	Purpose: Open the user settings container for the current mod
//-----------------------------------------------------------------------------
uint CXboxSystem::CreateUserSettingsContainer( uint nCreationFlags )
{
	if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
		return ERROR_INVALID_HANDLE;

	// Don't allow any of our saves or user data to be transferred to another user
	nCreationFlags |= XCONTENTFLAG_NOPROFILE_TRANSFER;

	XCONTENT_DATA contentData;
	contentData.DeviceID = XBX_GetStorageDeviceId();
	contentData.dwContentType = XCONTENTTYPE_SAVEDGAME;
	Q_wcsncpy( contentData.szDisplayName, g_pVGuiLocalize->Find( "#GameUI_Console_UserSettings" ), sizeof( contentData.szDisplayName ) );
	Q_snprintf( contentData.szFileName, sizeof( contentData.szFileName ), "UserSettings" );

	SIZE_T dwFileCacheSize = 0; // Use the smallest size (default)
	ULARGE_INTEGER ulSize;
	ulSize.QuadPart = XBX_USER_SETTINGS_BYTES;

	int nRet = XContentCreateEx( XBX_GetPrimaryUserId(), XBX_USER_SETTINGS_CONTAINER_DRIVE, &contentData, nCreationFlags, NULL, NULL, dwFileCacheSize, ulSize, NULL );
	if ( nRet == ERROR_SUCCESS )
	{
		BOOL bUserIsCreator = false;
		XContentGetCreator( XBX_GetPrimaryUserId(), &contentData, &bUserIsCreator, NULL, NULL );
		if( bUserIsCreator == false )
		{
			XContentClose( XBX_USER_SETTINGS_CONTAINER_DRIVE, NULL );
			return ERROR_ACCESS_DENIED;
		}
	}

	return nRet;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CXboxSystem::FinishContainerWrites( void )
{
	// Finish all writes
	XContentFlush( GetCurrentMod(), NULL );
	XContentFlush( XBX_USER_SETTINGS_CONTAINER_DRIVE, NULL );
}

//-----------------------------------------------------------------------------
// Purpose: Retrieve the names used for our save game container
// Input  : *pchModName - Name of the mod we're running (tf, hl2, etc)
//			**ppchDisplayName - Display name that will be presented to users by the console
//			**ppchName - Filename of the container
//-----------------------------------------------------------------------------
void CXboxSystem::GetModSaveContainerNames( const char *pchModName, const wchar_t **ppchDisplayName, const char **ppchName )
{
	// If the strings haven't been setup
	if ( g_szModSaveContainerDisplayName[ 0 ] == '\0' )
	{
		if ( Q_stricmp( pchModName, "episodic" ) == 0 )
		{
			Q_wcsncpy( g_szModSaveContainerDisplayName, g_pVGuiLocalize->Find( "#GameUI_Console_Ep1_Saves" ), sizeof( g_szModSaveContainerDisplayName ) );
		}
		else if ( Q_stricmp( pchModName, "ep2" ) == 0 )
		{
			Q_wcsncpy( g_szModSaveContainerDisplayName, g_pVGuiLocalize->Find( "#GameUI_Console_Ep2_Saves" ), sizeof( g_szModSaveContainerDisplayName ) );
		}
		else if ( Q_stricmp( pchModName, "portal" ) == 0 )
		{
			Q_wcsncpy( g_szModSaveContainerDisplayName, g_pVGuiLocalize->Find( "#GameUI_Console_Portal_Saves" ), sizeof( g_szModSaveContainerDisplayName ) );
		}
		else if ( Q_stricmp( pchModName, "tf" ) == 0 )
		{
			Q_wcsncpy( g_szModSaveContainerDisplayName, g_pVGuiLocalize->Find( "#GameUI_Console_TF2_Saves" ), sizeof( g_szModSaveContainerDisplayName ) );
		}
		else
		{
			Q_wcsncpy( g_szModSaveContainerDisplayName, g_pVGuiLocalize->Find( "#GameUI_Console_HL2_Saves" ), sizeof( g_szModSaveContainerDisplayName ) );
		}

		// Create a filename with the format "mod_saves"
		Q_snprintf( g_szModSaveContainerName, sizeof( g_szModSaveContainerName ), "%s_saves", pchModName );
	}

	// Return pointers to these internally kept strings
	*ppchDisplayName = g_szModSaveContainerDisplayName;
	*ppchName = g_szModSaveContainerName;
}

//-----------------------------------------------------------------------------
// Purpose: Search the device and find out if we have adequate space to start a game
// Input  : nStorageID - Device to check
//			*pModName - Name of the mod we want to check for
//-----------------------------------------------------------------------------
bool CXboxSystem::DeviceCapacityAdequate( DWORD nStorageID, const char *pModName )
{
	// If we don't have a valid user id, we can't poll the device
	if ( XBX_GetPrimaryUserId() == XBX_INVALID_USER_ID ) 
		return false;

	// Must be a valid storage device to poll
	if ( nStorageID == XBX_INVALID_STORAGE_ID  )
		return false;

	// Get the actual amount on the drive
	XDEVICE_DATA deviceData;
	if ( XContentGetDeviceData( nStorageID, &deviceData ) != ERROR_SUCCESS )
		return false;

	const ULONGLONG nSaveGameSize = XContentCalculateSize( XBX_PERSISTENT_BYTES_NEEDED, 1 );
	const ULONGLONG nUserSettingsSize = XContentCalculateSize( XBX_USER_SETTINGS_BYTES, 1 );
	bool bIsTF2 = ( !Q_stricmp( pModName, "tf" ) );
	ULONGLONG nTotalSpaceNeeded = ( bIsTF2 ) ? nUserSettingsSize : ( nSaveGameSize + nUserSettingsSize );
	ULONGLONG nAvailableSpace = deviceData.ulDeviceFreeBytes; // Take the first device's free space to compare this against
	
	// If they've already got enough space, early out
	if ( nAvailableSpace >= nTotalSpaceNeeded )
		return true;

	const int nNumItemsToRetrieve = 1;
	const int fContentFlags = XCONTENTFLAG_ENUM_EXCLUDECOMMON;

	// Save for queries against the storage devices
	const wchar_t *pchContainerDisplayName;
	const char *pchContainerName;
	GetModSaveContainerNames( pModName, &pchContainerDisplayName, &pchContainerName );

	// Look for a user settings block for all products
	DWORD nBufferSize;
	HANDLE hEnumerator;
	if ( XContentCreateEnumerator(	XBX_GetPrimaryUserId(), 
									nStorageID, 
									XCONTENTTYPE_SAVEDGAME, 
									fContentFlags, 
									nNumItemsToRetrieve, 
									&nBufferSize, 
									&hEnumerator ) == ERROR_SUCCESS )
	{
		// Allocate a buffer of the correct size
		BYTE *pBuffer = new BYTE[nBufferSize];
		if ( pBuffer == NULL )
			return XBX_INVALID_STORAGE_ID;

		char szFilename[XCONTENT_MAX_FILENAME_LENGTH+1];
		szFilename[XCONTENT_MAX_FILENAME_LENGTH] = 0;
		XCONTENT_DATA *pData = NULL;

		// Step through all items, looking for ones we care about
		DWORD nNumItems;
		while ( XEnumerate( hEnumerator, pBuffer, nBufferSize, &nNumItems, NULL ) == ERROR_SUCCESS )
		{
			// Grab the item in question
			pData = (XCONTENT_DATA *) pBuffer;

			// Safely store this away (null-termination is not guaranteed by the API!)
			memcpy( szFilename, pData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );

			// See if this is our user settings file
			if ( !Q_stricmp( szFilename, "UserSettings" ) )
			{
				nTotalSpaceNeeded -= nUserSettingsSize;
			}
			else if ( bIsTF2 == false && !Q_stricmp( szFilename, pchContainerName ) )
			{
				nTotalSpaceNeeded -= nSaveGameSize;
			}
		}

		// Clean up
		delete[] pBuffer;
		CloseHandle( hEnumerator );
	}

	// Finally, check its complete size
	if ( nTotalSpaceNeeded <= nAvailableSpace )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Enumerate all devices and search for game data already present.  If only one device has it, we return it
// Input  : nUserID - User whose data we're searching for
//			*pModName - Name of the mod we're searching for
// Output : Device ID which contains our data (-1 if no data was found, or data resided on multiple devices)
//-----------------------------------------------------------------------------
DWORD CXboxSystem::DiscoverUserData( DWORD nUserID, const char *pModName )
{
	// If we're entering this function without a storage device, then we must pop the UI anyway to choose it!
	Assert( nUserID != XBX_INVALID_USER_ID );
	if ( nUserID == XBX_INVALID_USER_ID )
		return XBX_INVALID_STORAGE_ID;

	const int nNumItemsToRetrieve = 1;
	const int fContentFlags = XCONTENTFLAG_ENUM_EXCLUDECOMMON;
	DWORD nFoundDevice = XBX_INVALID_STORAGE_ID;

	// Save for queries against the storage devices
	const wchar_t *pchContainerDisplayName;
	const char *pchContainerName;
	GetModSaveContainerNames( pModName, &pchContainerDisplayName, &pchContainerName );

	const ULONGLONG nSaveGameSize = XContentCalculateSize( XBX_PERSISTENT_BYTES_NEEDED, 1 );
	const ULONGLONG nUserSettingsSize = XContentCalculateSize( XBX_USER_SETTINGS_BYTES, 1 );
	ULONGLONG nTotalSpaceNeeded = ( nSaveGameSize + nUserSettingsSize );
	ULONGLONG nAvailableSpace = 0; // Take the first device's free space to compare this against

	// Look for a user settings block for all products
	DWORD nBufferSize;
	HANDLE hEnumerator;
	if ( XContentCreateEnumerator(	nUserID, 
									XCONTENTDEVICE_ANY, // All devices we know about
									XCONTENTTYPE_SAVEDGAME, 
									fContentFlags, 
									nNumItemsToRetrieve, 
									&nBufferSize, 
									&hEnumerator ) == ERROR_SUCCESS )
	{
		// Allocate a buffer of the correct size
		BYTE *pBuffer = new BYTE[nBufferSize];
		if ( pBuffer == NULL )
			return XBX_INVALID_STORAGE_ID;

		char szFilename[XCONTENT_MAX_FILENAME_LENGTH+1];
		szFilename[XCONTENT_MAX_FILENAME_LENGTH] = 0;
		XCONTENT_DATA *pData = NULL;

		// Step through all items, looking for ones we care about
		DWORD nNumItems;
		while ( XEnumerate( hEnumerator, pBuffer, nBufferSize, &nNumItems, NULL ) == ERROR_SUCCESS )
		{
			// Grab the item in question
			pData = (XCONTENT_DATA *) pBuffer;

			// If they have multiple devices installed, then we must ask
			if ( nFoundDevice != XBX_INVALID_STORAGE_ID && nFoundDevice != pData->DeviceID )
			{
				// Clean up
				delete[] pBuffer;
				CloseHandle( hEnumerator );

				return XBX_INVALID_STORAGE_ID;
			}

			// Hold on to this device ID
			if ( nFoundDevice != pData->DeviceID )
			{
				nFoundDevice = pData->DeviceID;

				XDEVICE_DATA deviceData;
				if ( XContentGetDeviceData( nFoundDevice, &deviceData ) != ERROR_SUCCESS )
					continue;

				nAvailableSpace = deviceData.ulDeviceFreeBytes;
			}

			// Safely store this away (null-termination is not guaranteed by the API!)
			memcpy( szFilename, pData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );

			// See if this is our user settings file
			if ( !Q_stricmp( szFilename, "UserSettings" ) )
			{
				nTotalSpaceNeeded -= nUserSettingsSize;
			}
			else if ( !Q_stricmp( szFilename, pchContainerName ) )
			{
				nTotalSpaceNeeded -= nSaveGameSize;
			}
		}

		// Clean up
		delete[] pBuffer;
		CloseHandle( hEnumerator );
	}

	// If we found nothing, then give up
	if ( nFoundDevice == XBX_INVALID_STORAGE_ID )
		return nFoundDevice;

	// Finally, check its complete size
	if ( nTotalSpaceNeeded <= nAvailableSpace )
		return nFoundDevice;

	return XBX_INVALID_STORAGE_ID;
}

//-----------------------------------------------------------------------------
// Purpose: Space free on the current device
//-----------------------------------------------------------------------------
uint CXboxSystem::GetContainerRemainingSpace( void )
{
	XDEVICE_DATA deviceData;
	if ( XContentGetDeviceData( XBX_GetStorageDeviceId(), &deviceData ) != ERROR_SUCCESS )
		return 0;

	return deviceData.ulDeviceFreeBytes;
}

//-----------------------------------------------------------------------------
//	Purpose: Show the storage device selector
//-----------------------------------------------------------------------------
bool CXboxSystem::ShowDeviceSelector( bool bForce, uint *pStorageID, AsyncHandle_t *pAsyncHandle  )
{
	AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );

	// We validate the size outside of this because we want to look inside our packages to see what's really free
	ULARGE_INTEGER bytes;
	bytes.QuadPart = XContentCalculateSize( XBX_PERSISTENT_BYTES_NEEDED + XBX_USER_SETTINGS_BYTES, 1 );

	DWORD showFlags = bForce ? XCONTENTFLAG_FORCE_SHOW_UI : 0;
	showFlags |= XCONTENTFLAG_MANAGESTORAGE;

	DWORD ret = XShowDeviceSelectorUI(	XBX_GetPrimaryUserId(), 
										XCONTENTTYPE_SAVEDGAME, 
										showFlags, 
										bytes, 
										(DWORD*) pStorageID, 
										&pResult->overlapped 
										);

	if ( ret != ERROR_IO_PENDING )
	{
		Msg( "Error showing device Selector UI\n" );
		return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Show the user sign in screen
//-----------------------------------------------------------------------------
void CXboxSystem::ShowSigninUI( uint nPanes, uint nFlags )
{
	XShowSigninUI( nPanes, nFlags );
}

//-----------------------------------------------------------------------------
//	Purpose: Set a user context
//-----------------------------------------------------------------------------
int CXboxSystem::UserSetContext( uint nUserIdx, uint nContextID, uint nContextValue, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;
	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XUserSetContextEx( nUserIdx, nContextID, nContextValue, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Set a user property
//-----------------------------------------------------------------------------
int CXboxSystem::UserSetProperty( uint nUserIndex, uint nPropertyId, uint nBytes, const void *pvValue, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;
	const void *pData = pvValue;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );

		if ( nBytes && pvValue )
		{
			pResult->pInputData = malloc( nBytes );
			memcpy( pResult->pInputData, pvValue, nBytes );
		}
		else
		{
			nBytes = 0;
		}

		pOverlapped = &pResult->overlapped;
		pData = pResult->pInputData;
	}

	return XUserSetPropertyEx( nUserIndex, nPropertyId, nBytes, pData, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Create a matchmaking session
//-----------------------------------------------------------------------------
int CXboxSystem::CreateSession( uint nFlags, 
							    uint nUserIdx, 
								uint nMaxPublicSlots, 
								uint nMaxPrivateSlots, 
								uint64 *pNonce,  
								void *pSessionInfo,
								XboxHandle_t *pSessionHandle,
								bool bAsync,
								AsyncHandle_t *pAsyncHandle 
								)
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	// Create the session
	return XSessionCreate( nFlags, nUserIdx, nMaxPublicSlots, nMaxPrivateSlots, pNonce, (XSESSION_INFO*)pSessionInfo, pOverlapped, pSessionHandle );
}

//-----------------------------------------------------------------------------
//	Purpose: Destroy a matchmaking session
//-----------------------------------------------------------------------------
uint CXboxSystem::DeleteSession( XboxHandle_t hSession, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	// Delete the session
	uint ret = XSessionDelete( hSession, pOverlapped );
	CloseHandle( hSession );

	return ret;
}

//-----------------------------------------------------------------------------
//	Purpose: Create a matchmaking session
//-----------------------------------------------------------------------------
uint CXboxSystem::SessionSearch( uint nProcedureIndex,
								 uint nUserIndex,
								 uint nNumResults,
								 uint nNumUsers,
								 uint nNumProperties,
								 uint nNumContexts,
								 XUSER_PROPERTY *pSearchProperties,
								 XUSER_CONTEXT *pSearchContexts,
								 uint *pcbResultsBuffer,
								 XSESSION_SEARCHRESULT_HEADER *pSearchResults,
								 bool	bAsync,
								 AsyncHandle_t *pAsyncHandle
								 )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	// Search for the session
	return XSessionSearchEx( nProcedureIndex, nUserIndex, nNumResults, nNumUsers, nNumProperties, nNumContexts, pSearchProperties, pSearchContexts, (DWORD*)pcbResultsBuffer, pSearchResults, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Starting a multiplayer game
//-----------------------------------------------------------------------------
uint CXboxSystem::SessionStart( XboxHandle_t hSession, uint nFlags, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionStart( hSession, nFlags, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Finished a multiplayer game
//-----------------------------------------------------------------------------
uint CXboxSystem::SessionEnd( XboxHandle_t hSession, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionEnd( hSession, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Join local users to a session
//-----------------------------------------------------------------------------
int	CXboxSystem::SessionJoinLocal( XboxHandle_t hSession, uint nUserCount, const uint *pUserIndexes, const bool *pPrivateSlots, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionJoinLocal( hSession, nUserCount, (DWORD*)pUserIndexes, (BOOL*)pPrivateSlots, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Join remote users to a session
//-----------------------------------------------------------------------------
int	CXboxSystem::SessionJoinRemote( XboxHandle_t hSession, uint nUserCount, const XUID *pXuids, const bool *pPrivateSlots, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionJoinRemote( hSession, nUserCount, pXuids, (BOOL*)pPrivateSlots, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Remove local users from a session
//-----------------------------------------------------------------------------
int	CXboxSystem::SessionLeaveLocal( XboxHandle_t hSession, uint nUserCount, const uint *pUserIndexes, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionLeaveLocal( hSession, nUserCount, (DWORD*)pUserIndexes, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Remove remote users from a session
//-----------------------------------------------------------------------------
int	CXboxSystem::SessionLeaveRemote( XboxHandle_t hSession, uint nUserCount, const XUID *pXuids, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionLeaveRemote( hSession, nUserCount, pXuids, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Migrate a session to a new host
//-----------------------------------------------------------------------------
int	CXboxSystem::SessionMigrate( XboxHandle_t hSession, uint nUserIndex, void *pSessionInfo, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionMigrateHost( hSession, nUserIndex, (XSESSION_INFO*)pSessionInfo, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Register for arbitration
//-----------------------------------------------------------------------------
int	CXboxSystem::SessionArbitrationRegister( XboxHandle_t hSession, uint nFlags, uint64 nonce, uint *pBytes, void *pBuffer, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionArbitrationRegister( hSession, nFlags, nonce, (DWORD*)pBytes, (XSESSION_REGISTRATION_RESULTS*)pBuffer, pOverlapped );
}

//-----------------------------------------------------------------------------
//	Purpose: Upload player stats to Xbox Live
//-----------------------------------------------------------------------------
int	CXboxSystem::WriteStats( XboxHandle_t hSession, XUID xuid, uint nViews, void* pViews, bool bAsync, AsyncHandle_t *pAsyncHandle )
{
	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	return XSessionWriteStats( hSession, xuid, nViews, (XSESSION_VIEW_PROPERTIES*)pViews, pOverlapped );
}


//-----------------------------------------------------------------------------
//	Purpose: Enumerate a player's achievements
//-----------------------------------------------------------------------------
int CXboxSystem::EnumerateAchievements( uint nUserIdx, 
									    uint64 xuid, 
										uint nStartingIdx, 
										uint nCount, 
										void *pBuffer, 
										uint nBufferBytes,
										bool bAsync,
										AsyncHandle_t *pAsyncHandle 
										)
{
	HANDLE hEnumerator = INVALID_HANDLE_VALUE;
	DWORD ret = XUserCreateAchievementEnumerator( 0, nUserIdx, xuid, XACHIEVEMENT_DETAILS_ALL, nStartingIdx, nCount, (DWORD*)pBuffer, &hEnumerator );
	
	// Just looking for the buffer size needed
	if ( ret != ERROR_SUCCESS || nBufferBytes == 0 )
	{
		CloseHandle( hEnumerator );
		return ret;
	}

	if ( nBufferBytes < *(uint*)pBuffer )
	{
		Warning( "EnumerateAchievements: Buffer provided not large enough to hold results" );
		return ERROR_NOT_ENOUGH_MEMORY;
	}

	XOVERLAPPED *pOverlapped = NULL;

	if ( bAsync )
	{
		AsyncResult_t *pResult = InitializeAsyncResult( &pAsyncHandle );
		pOverlapped = &pResult->overlapped;
	}

	DWORD items;
	ret = XEnumerate( hEnumerator, pBuffer, nBufferBytes, &items, pOverlapped );
	if ( ret != ERROR_SUCCESS )
	{
		Warning( "XEnumerate failed in EnumerateAchievements.\n" );
	}
	CloseHandle( hEnumerator );

	return items;
}

//-----------------------------------------------------------------------------
//	Purpose: Award an achievement to the current user
//-----------------------------------------------------------------------------
void CXboxSystem::AwardAchievement( uint nUserIdx, uint nAchievementId )
{
	AsyncResult_t *pResult = CreateAsyncResult( true );

	XUSER_ACHIEVEMENT ach;
	ach.dwUserIndex = nUserIdx;
	ach.dwAchievementId = nAchievementId;

	pResult->pInputData = malloc( sizeof( ach ) );
	Q_memcpy( pResult->pInputData, &ach, sizeof( ach ) );

	DWORD ret = XUserWriteAchievements( 1, (XUSER_ACHIEVEMENT*)pResult->pInputData, &pResult->overlapped );
	if ( ret != ERROR_IO_PENDING )
	{
		Warning( "XUserWriteAchievments failed.\n" );
	}
}

#else

// Stubbed interface for win32
CXboxSystem::~CXboxSystem( void ) {}
CXboxSystem::CXboxSystem( void ) {}
AsyncHandle_t	CXboxSystem::CreateAsyncHandle( void ) { return NULL; }
void			CXboxSystem::ReleaseAsyncHandle( AsyncHandle_t handle ) {}
int				CXboxSystem::GetOverlappedResult( AsyncHandle_t handle, uint *pResultCode, bool bWait ) { return 0; }
void			CXboxSystem::CancelOverlappedOperation( AsyncHandle_t handle ) {};
void			CXboxSystem::GetModSaveContainerNames( const char *pchModName, const wchar_t **ppchDisplayName, const char **ppchName ) 
{
	*ppchDisplayName = g_szModSaveContainerDisplayName;
	*ppchName = g_szModSaveContainerName;
}
DWORD			CXboxSystem::DiscoverUserData( DWORD nUserID, const char *pModName ) { return ((DWORD)-1); }
bool			CXboxSystem::DeviceCapacityAdequate( DWORD nDeviceID, const char *pModName ) { return true; }
uint			CXboxSystem::GetContainerRemainingSpace( void ) { return 0; }
bool			CXboxSystem::ShowDeviceSelector( bool bForce, uint *pStorageID, AsyncHandle_t *pHandle  ) { return false; }
void			CXboxSystem::ShowSigninUI( uint nPanes, uint nFlags ) {}
int				CXboxSystem::UserSetContext( uint nUserIdx, uint nContextID, uint nContextValue, bool bAsync, AsyncHandle_t *pHandle) { return 0; }
int				CXboxSystem::UserSetProperty( uint nUserIndex, uint nPropertyId, uint nBytes, const void *pvValue, bool bAsync, AsyncHandle_t *pHandle ) { return 0; }
int				CXboxSystem::CreateSession( uint nFlags, uint nUserIdx, uint nMaxPublicSlots, uint nMaxPrivateSlots, uint64 *pNonce, void *pSessionInfo, XboxHandle_t *pSessionHandle, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
uint			CXboxSystem::DeleteSession( XboxHandle_t hSession, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
uint			CXboxSystem::SessionSearch( uint nProcedureIndex, uint nUserIndex, uint nNumResults, uint nNumUsers, uint nNumProperties, uint nNumContexts, XUSER_PROPERTY *pSearchProperties, XUSER_CONTEXT *pSearchContexts, uint *pcbResultsBuffer, XSESSION_SEARCHRESULT_HEADER *pSearchResults, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
uint			CXboxSystem::SessionStart( XboxHandle_t hSession, uint nFlags, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; };
uint			CXboxSystem::SessionEnd( XboxHandle_t hSession, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; };
int				CXboxSystem::SessionJoinLocal( XboxHandle_t hSession, uint nUserCount, const uint *pUserIndexes, const bool *pPrivateSlots, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::SessionJoinRemote( XboxHandle_t hSession, uint nUserCount, const XUID *pXuids, const bool *pPrivateSlot, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::SessionLeaveLocal( XboxHandle_t hSession, uint nUserCount, const uint *pUserIndexes, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::SessionLeaveRemote( XboxHandle_t hSession, uint nUserCount, const XUID *pXuids, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::SessionMigrate( XboxHandle_t hSession, uint nUserIndex, void *pSessionInfo, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::SessionArbitrationRegister( XboxHandle_t hSession, uint nFlags, uint64 nonce, uint *pBytes, void *pBuffer, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::WriteStats( XboxHandle_t hSession, XUID xuid, uint nViews, void* pViews, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
int				CXboxSystem::EnumerateAchievements( uint nUserIdx, uint64 xuid, uint nStartingIdx, uint nCount, void *pBuffer, uint nBufferBytes, bool bAsync, AsyncHandle_t *pAsyncHandle ) { return 0; }
void			CXboxSystem::AwardAchievement( uint nUserIdx, uint nAchievementId ) {}
void			CXboxSystem::FinishContainerWrites( void ) {}
uint			CXboxSystem::GetContainerOpenResult( void ) { return 0; }
uint			CXboxSystem::OpenContainers( void ) { return 0; }
void			CXboxSystem::CloseContainers( void ) {}
uint			CXboxSystem::CreateSavegameContainer( uint nCreationFlags ) { return 0; }
uint			CXboxSystem::CreateUserSettingsContainer( uint nCreationFlags ) { return 0; }

#endif // defined _X360