source-engine/engine/xboxsystem.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

1082 lines
41 KiB
C++

//========= 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