diff options
Diffstat (limited to 'engine/xboxsystem.cpp')
| -rw-r--r-- | engine/xboxsystem.cpp | 1081 |
1 files changed, 1081 insertions, 0 deletions
diff --git a/engine/xboxsystem.cpp b/engine/xboxsystem.cpp new file mode 100644 index 0000000..1770aca --- /dev/null +++ b/engine/xboxsystem.cpp @@ -0,0 +1,1081 @@ +//========= 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 |