You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1703 lines
42 KiB
1703 lines
42 KiB
//===== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "basefilesystem.h"
|
|
#include "filesystem/ixboxinstaller.h"
|
|
#include "tier1/utlbuffer.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "tier2/tier2.h"
|
|
#include "characterset.h"
|
|
#include "xbox/xbox_launch.h"
|
|
#include "fmtstr.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// Uncomment to allow system to operate (Image must be built with expected segmented zips)
|
|
#if defined( _X360 )
|
|
#define SUPPORTS_INSTALL_TO_XBOX_HDD
|
|
#endif
|
|
|
|
#define INSTALL_MANAGER_PROCESSOR 3
|
|
#define INSTALL_READ_PROCESSOR 3
|
|
#define INSTALL_WRITE_PROCESSOR 4
|
|
|
|
#if !defined( _CERT )
|
|
ConVar xbox_install_fake_readerror( "xbox_install_fake_readerror", "0", 0 );
|
|
ConVar xbox_install_fake_writeerror( "xbox_install_fake_writeerror", "0", 0 );
|
|
#endif
|
|
|
|
// artifical condition to disallow a qualified installation
|
|
// this allows an install to be re-enabled
|
|
ConVar xbox_install_allowed( "xbox_install_allowed", "1", 0 );
|
|
|
|
struct installEntry_t
|
|
{
|
|
const char *pSource;
|
|
const char *pTarget;
|
|
};
|
|
installEntry_t g_InstallScript[] =
|
|
{
|
|
{"D:\\csgo\\zip0.360.zip", "csgo\\zip0.360.zip"},
|
|
// no localiztion install
|
|
#if 0
|
|
{"D:\\csgo_%\\zip0.360.zip", "csgo_%\\zip0.360.zip"},
|
|
#endif
|
|
// put down last as final complete marker
|
|
{"D:\\version.xtx", "version.txt"},
|
|
};
|
|
|
|
bool ReadFileTest( const char *pFilename, DWORD nMaxReadSize, DWORD nRandomOffset )
|
|
{
|
|
OVERLAPPED Overlapped = { 0 };
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
void *pBuffer = NULL;
|
|
bool bSuccess = false;
|
|
char fixedFilename[MAX_PATH];
|
|
|
|
V_strncpy( fixedFilename, pFilename, sizeof( fixedFilename ) );
|
|
V_FixSlashes( fixedFilename );
|
|
pFilename = fixedFilename;
|
|
|
|
// validate the source file
|
|
hFile = CreateFile( pFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING, NULL );
|
|
if ( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
|
|
DWORD nFileSize = GetFileSize( hFile, NULL );
|
|
if ( nFileSize == (DWORD)-1 )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
|
|
DWORD nBufferSize = AlignValue( nFileSize, SOURCE_SECTOR_SIZE );
|
|
nBufferSize = min( nBufferSize, nMaxReadSize );
|
|
pBuffer = malloc( nBufferSize );
|
|
if ( !pBuffer )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
|
|
if ( nRandomOffset + nBufferSize > nFileSize )
|
|
{
|
|
nRandomOffset = 0;
|
|
}
|
|
Overlapped.Offset = nRandomOffset;
|
|
|
|
float startTime = Plat_FloatTime();
|
|
|
|
// read file
|
|
BOOL bResult = ReadFile( hFile, pBuffer, nBufferSize, NULL, &Overlapped );
|
|
DWORD dwError = GetLastError();
|
|
if ( !bResult && dwError != ERROR_IO_PENDING )
|
|
{
|
|
if ( dwError != ERROR_HANDLE_EOF )
|
|
{
|
|
goto cleanUp;
|
|
}
|
|
}
|
|
|
|
DWORD dwBytesRead = 0;
|
|
bResult = GetOverlappedResult( hFile, &Overlapped, &dwBytesRead, TRUE );
|
|
dwError = GetLastError();
|
|
if ( dwBytesRead != nBufferSize )
|
|
{
|
|
goto cleanUp;
|
|
}
|
|
|
|
float elapsed, totalSizeMB;
|
|
elapsed = Plat_FloatTime() - startTime;
|
|
totalSizeMB = (float)nBufferSize/(1024.0f*1024.0f);
|
|
Msg( "Read: %s, %.2f MB in %.2f secs, %.2f MB/sec\n", pFilename, totalSizeMB, elapsed, totalSizeMB/elapsed );
|
|
|
|
bSuccess = true;
|
|
|
|
cleanUp:
|
|
if ( hFile != INVALID_HANDLE_VALUE )
|
|
{
|
|
CloseHandle( hFile );
|
|
}
|
|
|
|
if ( pBuffer )
|
|
{
|
|
free( pBuffer );
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
struct FileDetails_t
|
|
{
|
|
FileDetails_t()
|
|
{
|
|
m_nRealFileSize = 0;
|
|
m_bFileIsValid = false;
|
|
}
|
|
|
|
CUtlString m_SourceName;
|
|
CUtlString m_TargetName;
|
|
|
|
// this is the size we want the target file to be at completion
|
|
DWORD m_nRealFileSize;
|
|
// this is the reserved size of the target file
|
|
bool m_bFileIsValid;
|
|
};
|
|
|
|
struct InstallData_t
|
|
{
|
|
void Reset()
|
|
{
|
|
m_Files.Purge();
|
|
m_nTotalSize = 0;
|
|
m_nVersion = 0;
|
|
m_bValid = false;
|
|
m_bCompleted = false;
|
|
m_bFailed = false;
|
|
}
|
|
|
|
CUtlVector< FileDetails_t > m_Files;
|
|
DWORD m_nTotalSize;
|
|
DWORD m_nVersion;
|
|
bool m_bValid;
|
|
bool m_bCompleted;
|
|
bool m_bFailed;
|
|
};
|
|
|
|
// copying onto HDD
|
|
#define INSTALL_BUFFER_SIZE (512*1024)
|
|
#define INSTALL_NUM_BUFFERS 4
|
|
|
|
struct CopyFile_t
|
|
{
|
|
// source file
|
|
HANDLE m_hSrcFile;
|
|
DWORD m_srcFileSize;
|
|
int m_readBufferSize;
|
|
|
|
// target file
|
|
HANDLE m_hDstFile;
|
|
DWORD m_dstCurrentFileSize;
|
|
|
|
CopyStats_t *m_pCopyStats;
|
|
};
|
|
|
|
struct Buffer_t
|
|
{
|
|
unsigned char *pData;
|
|
DWORD dwSize;
|
|
Buffer_t* pNext;
|
|
int id;
|
|
};
|
|
|
|
class CXboxInstaller : public CTier2AppSystem< IXboxInstaller >
|
|
{
|
|
typedef CTier2AppSystem< IXboxInstaller > BaseClass;
|
|
|
|
public:
|
|
CXboxInstaller();
|
|
virtual ~CXboxInstaller();
|
|
|
|
// Inherited from IAppSystem
|
|
virtual InitReturnVal_t Init();
|
|
virtual void Shutdown();
|
|
|
|
virtual bool Setup( bool bForceInstall );
|
|
virtual void ResetSetup();
|
|
|
|
virtual bool Start();
|
|
virtual void Stop();
|
|
virtual bool IsStopped( bool bForceStop );
|
|
|
|
virtual DWORD GetTotalSize();
|
|
virtual DWORD GetVersion();
|
|
virtual const CopyStats_t *GetCopyStats();
|
|
virtual bool IsInstallEnabled();
|
|
virtual bool IsFullyInstalled();
|
|
|
|
virtual bool ShouldRestart();
|
|
virtual bool ForceCachePaths();
|
|
virtual void SpewStatus();
|
|
|
|
DWORD ReadFileThread( CopyFile_t *pCopyFile );
|
|
DWORD WriteFileThread( CopyFile_t *pCopyFile );
|
|
DWORD InstallThreadFunc();
|
|
|
|
private:
|
|
bool BuildInstallScript( bool bForceFullInstall );
|
|
bool IsTargetFileValid( const char *pFilename, DWORD dwSrcFileSize );
|
|
bool DoesFileExist( const char *pFilename, DWORD *pSize );
|
|
bool CopyFileOverlapped( FileDetails_t *pFileDetails, CopyStats_t *pCopyStats );
|
|
bool CreateFilePath( const char *inPath );
|
|
bool FixupNamespaceFilename( const char *pLanguage, const char *pFilename, char *pOutFilename, int outFilenameSize );
|
|
Buffer_t *LockBufferForRead();
|
|
Buffer_t *LockBufferForWrite();
|
|
void AddBufferForRead( Buffer_t *pBuffer );
|
|
void AddBufferForWrite( Buffer_t *pBuffer );
|
|
bool PrepareCachePartitionForInstall();
|
|
|
|
CRITICAL_SECTION m_CriticalSection;
|
|
|
|
HANDLE m_hReadEvent;
|
|
HANDLE m_hWriteEvent;
|
|
HANDLE m_hInstallThread;
|
|
|
|
Buffer_t *m_pReadBuffers;
|
|
Buffer_t *m_pWriteBuffers;
|
|
DWORD *m_pNumReadBuffers;
|
|
DWORD *m_pNumWriteBuffers;
|
|
|
|
InstallData_t m_InstallData;
|
|
CopyStats_t m_CopyStats;
|
|
|
|
unsigned char *m_pCopyBuffers[INSTALL_NUM_BUFFERS];
|
|
|
|
bool m_bHasHDD;
|
|
bool m_bInit;
|
|
bool m_bAppSystemInit;
|
|
bool m_bForcedCachePaths;
|
|
|
|
// used to stop the process under NON-ERROR conditions
|
|
CInterlockedInt m_bStopping;
|
|
// stops the process, read errors are fatal
|
|
CInterlockedInt m_bReadError;
|
|
// stops the process, write errors just permanently halt the install
|
|
CInterlockedInt m_bWriteError;
|
|
};
|
|
|
|
static CXboxInstaller g_XboxInstaller;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CXboxInstaller, IXboxInstaller, XBOXINSTALLER_INTERFACE_VERSION, g_XboxInstaller );
|
|
|
|
CON_COMMAND( xbox_install_testcache, "" )
|
|
{
|
|
if ( !g_XboxInstaller.IsFullyInstalled() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DWORD nRandomOffset = 0;
|
|
DWORD nInitialSize = 16*1024;
|
|
for ( int i = 0; i < 8; i++ )
|
|
{
|
|
Msg( "\nRead Test at %.2f MB\n", (float)nInitialSize/(1024.0f*1024.0f) );
|
|
|
|
nRandomOffset += 4*1024*1024;
|
|
ReadFileTest( CFmtStr( "%s/csgo/zip0.360.zip", CACHE_PATH_CSTIKRE15 ), nInitialSize, nRandomOffset );
|
|
ReadFileTest( "d:/csgo/zip0.360.zip", nInitialSize, nRandomOffset );
|
|
|
|
nInitialSize <<= 1;
|
|
}
|
|
}
|
|
|
|
// for thrash testing only
|
|
CON_COMMAND( xbox_install_teststop, "" )
|
|
{
|
|
g_XboxInstaller.Stop();
|
|
}
|
|
|
|
// for thrash testing only
|
|
CON_COMMAND( xbox_install_teststart, "" )
|
|
{
|
|
g_XboxInstaller.Start();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Isolated all mounting/unmounting here.
|
|
//-----------------------------------------------------------------------------
|
|
static bool MountCachePartition( bool bFormat )
|
|
{
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
return false;
|
|
#endif
|
|
|
|
static bool s_bMounted = false;
|
|
if ( s_bMounted )
|
|
{
|
|
if ( bFormat )
|
|
{
|
|
// must unmount before format
|
|
XUnmountUtilityDrive();
|
|
s_bMounted = false;
|
|
}
|
|
else
|
|
{
|
|
// already mounted
|
|
return true;
|
|
}
|
|
}
|
|
|
|
COM_TimestampedLog( "XMountUtilityDrive( %s )", ( bFormat ? "format" : "" ) );
|
|
|
|
// small cluster size not useful, as we are block installing large files
|
|
DWORD dwResult = XMountUtilityDrive( bFormat, 64*1024, 256*1024 );
|
|
if ( dwResult == ERROR_SUCCESS )
|
|
{
|
|
s_bMounted = true;
|
|
}
|
|
|
|
COM_TimestampedLog( "XMountUtilityDrive() - Finish" );
|
|
|
|
return s_bMounted;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// USED BY THE FILESYSTEM ONLY!!!! AT IT'S INIT/BOOT TIME
|
|
// This is here as a back door, it has to solve the query during the filesystem's
|
|
// init method, without using the filesystem.
|
|
//-----------------------------------------------------------------------------
|
|
bool IsAlreadyInstalledToXboxHDDCache()
|
|
{
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
return false;
|
|
#endif
|
|
|
|
// TCR best practices allows a backdoor (LS+RS) to reforce HDD cache installers.
|
|
// Tech support may tell users with flaky HDD to do this.
|
|
// Our inputsystem wrapper is not available yet due to init chain, so go directly to hardware.
|
|
bool bForceInstall = false;
|
|
DWORD dwResult;
|
|
for ( DWORD i=0; i < XUSER_MAX_COUNT; i++ )
|
|
{
|
|
XINPUT_STATE state;
|
|
ZeroMemory( &state, sizeof( XINPUT_STATE ) );
|
|
|
|
dwResult = XInputGetState( i, &state );
|
|
if ( dwResult != ERROR_SUCCESS )
|
|
continue;
|
|
|
|
// it must be (LS+RS) exactlty only on any controller, and not a mashing of buttons
|
|
if ( state.Gamepad.wButtons == ( XINPUT_GAMEPAD_LEFT_SHOULDER|XINPUT_GAMEPAD_RIGHT_SHOULDER ) &&
|
|
state.Gamepad.bLeftTrigger == 0 &&
|
|
state.Gamepad.bRightTrigger == 0 )
|
|
{
|
|
bForceInstall = true;
|
|
}
|
|
else if ( state.Gamepad.wButtons || state.Gamepad.bLeftTrigger || state.Gamepad.bRightTrigger )
|
|
{
|
|
// other buttons are being held, sorry
|
|
bForceInstall = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( CommandLine()->FindParm( "-forceinstall" ) != 0 )
|
|
{
|
|
bForceInstall = true;
|
|
}
|
|
|
|
if ( !xbox_install_allowed.GetBool() )
|
|
{
|
|
// ignore any prior install by clearing any existing install
|
|
bForceInstall = true;
|
|
}
|
|
|
|
if ( g_XboxInstaller.Setup( bForceInstall ) )
|
|
{
|
|
return g_XboxInstaller.IsFullyInstalled();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CXboxInstaller::CXboxInstaller()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Destructor
|
|
//-----------------------------------------------------------------------------
|
|
CXboxInstaller::~CXboxInstaller()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Initialization
|
|
//-----------------------------------------------------------------------------
|
|
InitReturnVal_t CXboxInstaller::Init()
|
|
{
|
|
InitReturnVal_t nRetVal = BaseClass::Init();
|
|
if ( nRetVal != INIT_OK )
|
|
{
|
|
return nRetVal;
|
|
}
|
|
|
|
// NOTHING CAN GO HERE!!!!
|
|
// This subverts normal startup procedures and performs a few operations
|
|
// for the filesystem's init() before it gets inited.
|
|
m_bAppSystemInit = true;
|
|
|
|
return INIT_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Shutdown
|
|
//-----------------------------------------------------------------------------
|
|
void CXboxInstaller::Shutdown()
|
|
{
|
|
BaseClass::Shutdown();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CreateFilePath
|
|
//
|
|
// Create full path to specified file.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::CreateFilePath( const char *inPath )
|
|
{
|
|
char* ptr;
|
|
char dirPath[MAX_PATH];
|
|
BOOL bSuccess;
|
|
|
|
// prime and skip to first seperator after the drive path
|
|
strcpy( dirPath, inPath );
|
|
ptr = strchr( dirPath, '\\' );
|
|
while ( ptr )
|
|
{
|
|
ptr = strchr( ptr+1, '\\' );
|
|
if ( ptr )
|
|
{
|
|
*ptr = '\0';
|
|
bSuccess = ::CreateDirectory( dirPath, NULL );
|
|
*ptr = '\\';
|
|
}
|
|
}
|
|
|
|
// ensure read-only is cleared
|
|
SetFileAttributes( inPath, FILE_ATTRIBUTE_NORMAL );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// FixupNamespaceFilename
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::FixupNamespaceFilename( const char *pLanguage, const char *pFilename, char *pOutFilename, int outFilenameSize )
|
|
{
|
|
char newFilename[MAX_PATH];
|
|
|
|
bool bFixup = false;
|
|
int dstLen = 0;
|
|
int srcLen = strlen( pFilename );
|
|
for ( int i=0; i<srcLen+1; i++ )
|
|
{
|
|
// replace every occurrence of % with language
|
|
if ( pFilename[i] == '%' )
|
|
{
|
|
int len = strlen( pLanguage );
|
|
memcpy( newFilename + dstLen, pLanguage, len );
|
|
dstLen += len;
|
|
bFixup = true;
|
|
}
|
|
else
|
|
{
|
|
newFilename[dstLen] = pFilename[i];
|
|
dstLen++;
|
|
}
|
|
}
|
|
|
|
V_strncpy( pOutFilename, newFilename, outFilenameSize );
|
|
return bFixup;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LockBufferForRead
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
Buffer_t *CXboxInstaller::LockBufferForRead()
|
|
{
|
|
EnterCriticalSection( &m_CriticalSection );
|
|
|
|
if ( m_pReadBuffers )
|
|
{
|
|
ResetEvent( m_hReadEvent );
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
// prevent any possible block
|
|
if ( ( m_bStopping || m_bReadError || m_bWriteError ) )
|
|
{
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
return NULL;
|
|
}
|
|
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
|
|
// out of data, wait for it
|
|
WaitForSingleObject( m_hReadEvent, INFINITE );
|
|
|
|
EnterCriticalSection( &m_CriticalSection );
|
|
}
|
|
while ( !m_pReadBuffers );
|
|
}
|
|
|
|
Buffer_t *pBuffer = m_pReadBuffers;
|
|
m_pReadBuffers = pBuffer->pNext;
|
|
|
|
(*m_pNumReadBuffers)--;
|
|
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
|
|
return pBuffer;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LockBufferForWrite
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
Buffer_t *CXboxInstaller::LockBufferForWrite()
|
|
{
|
|
EnterCriticalSection( &m_CriticalSection );
|
|
|
|
if ( m_pWriteBuffers )
|
|
{
|
|
ResetEvent( m_hWriteEvent );
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
// prevent any possible block
|
|
if ( ( m_bStopping || m_bReadError || m_bWriteError ) )
|
|
{
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
return NULL;
|
|
}
|
|
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
|
|
// out of data, wait for more
|
|
WaitForSingleObject( m_hWriteEvent, INFINITE );
|
|
|
|
EnterCriticalSection( &m_CriticalSection );
|
|
}
|
|
while ( !m_pWriteBuffers );
|
|
}
|
|
|
|
Buffer_t *pBuffer = m_pWriteBuffers;
|
|
m_pWriteBuffers = pBuffer->pNext;
|
|
|
|
(*m_pNumWriteBuffers)--;
|
|
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
|
|
return pBuffer;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// AddBufferForRead
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CXboxInstaller::AddBufferForRead( Buffer_t *pBuffer )
|
|
{
|
|
EnterCriticalSection( &m_CriticalSection );
|
|
|
|
// add to end of list
|
|
Buffer_t *pCurrent = m_pReadBuffers;
|
|
while ( pCurrent && pCurrent->pNext )
|
|
{
|
|
pCurrent = pCurrent->pNext;
|
|
}
|
|
if ( pCurrent )
|
|
{
|
|
pBuffer->pNext = pCurrent->pNext;
|
|
pCurrent->pNext = pBuffer;
|
|
}
|
|
else
|
|
{
|
|
pBuffer->pNext = NULL;
|
|
m_pReadBuffers = pBuffer;
|
|
}
|
|
|
|
(*m_pNumReadBuffers)++;
|
|
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
|
|
SetEvent( m_hReadEvent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// AddBufferForWrite
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CXboxInstaller::AddBufferForWrite( Buffer_t *pBuffer )
|
|
{
|
|
EnterCriticalSection( &m_CriticalSection );
|
|
|
|
// add to end of list
|
|
Buffer_t* pCurrent = m_pWriteBuffers;
|
|
while ( pCurrent && pCurrent->pNext )
|
|
{
|
|
pCurrent = pCurrent->pNext;
|
|
}
|
|
if ( pCurrent )
|
|
{
|
|
pBuffer->pNext = pCurrent->pNext;
|
|
pCurrent->pNext = pBuffer;
|
|
}
|
|
else
|
|
{
|
|
pBuffer->pNext = NULL;
|
|
m_pWriteBuffers = pBuffer;
|
|
}
|
|
|
|
(*m_pNumWriteBuffers)++;
|
|
|
|
LeaveCriticalSection( &m_CriticalSection );
|
|
|
|
SetEvent( m_hWriteEvent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ReadFileThread
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD CXboxInstaller::ReadFileThread( CopyFile_t *pCopyFile )
|
|
{
|
|
OVERLAPPED overlappedRead = {0};
|
|
DWORD startTime;
|
|
DWORD dwBytesRead;
|
|
DWORD dwError;
|
|
BOOL bResult;
|
|
Buffer_t *pBuffer = NULL;
|
|
|
|
// start reading from resume point
|
|
overlappedRead.Offset = pCopyFile->m_dstCurrentFileSize;
|
|
|
|
// Copy from the buffer to the Hard Drive
|
|
while ( overlappedRead.Offset < pCopyFile->m_srcFileSize )
|
|
{
|
|
pBuffer = LockBufferForRead();
|
|
if ( !pBuffer )
|
|
{
|
|
break;
|
|
}
|
|
if ( m_bStopping || m_bReadError || m_bWriteError )
|
|
{
|
|
// stopping or
|
|
// errors occuring, cease all reading
|
|
break;
|
|
}
|
|
|
|
startTime = GetTickCount();
|
|
dwBytesRead = 0;
|
|
|
|
int numAttempts = 0;
|
|
retry:
|
|
// read file from DVD
|
|
bResult = ReadFile( pCopyFile->m_hSrcFile, pBuffer->pData, pCopyFile->m_readBufferSize, NULL, &overlappedRead );
|
|
dwError = GetLastError();
|
|
if ( !bResult && dwError != ERROR_IO_PENDING )
|
|
{
|
|
if ( dwError == ERROR_HANDLE_EOF )
|
|
{
|
|
// nothing more to read
|
|
break;
|
|
}
|
|
|
|
numAttempts++;
|
|
if ( numAttempts == 3 )
|
|
{
|
|
// error
|
|
m_bReadError = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
goto retry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Wait for the operation to finish
|
|
GetOverlappedResult( pCopyFile->m_hSrcFile, &overlappedRead, &dwBytesRead, TRUE );
|
|
}
|
|
|
|
#if !defined( _CERT )
|
|
if ( xbox_install_fake_readerror.GetBool() )
|
|
{
|
|
dwBytesRead = 0;
|
|
}
|
|
#endif
|
|
|
|
if ( !dwBytesRead )
|
|
{
|
|
m_bReadError = true;
|
|
break;
|
|
}
|
|
|
|
overlappedRead.Offset += dwBytesRead;
|
|
pCopyFile->m_pCopyStats->m_BufferReadSize = dwBytesRead;
|
|
pCopyFile->m_pCopyStats->m_BufferReadTime = GetTickCount() - startTime;
|
|
pCopyFile->m_pCopyStats->m_TotalReadSize += pCopyFile->m_pCopyStats->m_BufferReadSize;
|
|
pCopyFile->m_pCopyStats->m_TotalReadTime += pCopyFile->m_pCopyStats->m_BufferReadTime;
|
|
|
|
pBuffer->dwSize = dwBytesRead;
|
|
AddBufferForWrite( pBuffer );
|
|
}
|
|
|
|
if ( ( m_bStopping || m_bReadError || m_bWriteError ) && pBuffer )
|
|
{
|
|
// the aborted buffer must be returned to the pool
|
|
// this unblocks the write thread who will detect the error
|
|
AddBufferForWrite( pBuffer );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DWORD WINAPI ReadFileThread( LPVOID lParam )
|
|
{
|
|
return g_XboxInstaller.ReadFileThread( (CopyFile_t*)lParam );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// WriteFileThread
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD CXboxInstaller::WriteFileThread( CopyFile_t *pCopyFile )
|
|
{
|
|
OVERLAPPED overlappedWrite = {0};
|
|
DWORD startTime;
|
|
DWORD dwBytesWrite;
|
|
DWORD dwWriteSize;
|
|
DWORD dwError;
|
|
BOOL bResult;
|
|
Buffer_t *pBuffer = NULL;
|
|
unsigned char *pWriteBuffer;
|
|
|
|
// start writing from the resume point
|
|
overlappedWrite.Offset = pCopyFile->m_dstCurrentFileSize;
|
|
|
|
pCopyFile->m_pCopyStats->m_BytesCopied += pCopyFile->m_dstCurrentFileSize;
|
|
pCopyFile->m_pCopyStats->m_WriteSize += pCopyFile->m_dstCurrentFileSize;
|
|
|
|
while ( overlappedWrite.Offset < pCopyFile->m_srcFileSize )
|
|
{
|
|
// wait for wake-up event
|
|
pBuffer = LockBufferForWrite();
|
|
if ( !pBuffer )
|
|
{
|
|
break;
|
|
}
|
|
if ( m_bStopping || m_bReadError || m_bWriteError )
|
|
{
|
|
// stopping or
|
|
// errors occuring, cease all writing
|
|
break;
|
|
}
|
|
|
|
dwWriteSize = pBuffer->dwSize;
|
|
pWriteBuffer = pBuffer->pData;
|
|
|
|
if ( overlappedWrite.Offset + dwWriteSize >= pCopyFile->m_srcFileSize )
|
|
{
|
|
// last buffer, ensure all data is written
|
|
dwWriteSize = ALIGN_VALUE( dwWriteSize, TARGET_SECTOR_SIZE );
|
|
}
|
|
|
|
startTime = GetTickCount();
|
|
dwBytesWrite = 0;
|
|
|
|
int numAttempts = 0;
|
|
retry:
|
|
// write file to HDD
|
|
bResult = WriteFile( pCopyFile->m_hDstFile, pWriteBuffer, (dwWriteSize/TARGET_SECTOR_SIZE) * TARGET_SECTOR_SIZE, NULL, &overlappedWrite );
|
|
dwError = GetLastError();
|
|
if ( !bResult && dwError != ERROR_IO_PENDING )
|
|
{
|
|
numAttempts++;
|
|
if ( numAttempts == 3 )
|
|
{
|
|
// error
|
|
m_bWriteError = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
goto retry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Wait for the operation to finish
|
|
bResult = GetOverlappedResult( pCopyFile->m_hDstFile, &overlappedWrite, &dwBytesWrite, TRUE );
|
|
dwError = GetLastError();
|
|
}
|
|
|
|
#if !defined( _CERT )
|
|
if ( xbox_install_fake_writeerror.GetBool() )
|
|
{
|
|
dwBytesWrite = 0;
|
|
}
|
|
#endif
|
|
|
|
if ( !dwBytesWrite )
|
|
{
|
|
m_bWriteError = true;
|
|
break;
|
|
}
|
|
|
|
// track expected size
|
|
overlappedWrite.Offset += dwBytesWrite;
|
|
pCopyFile->m_pCopyStats->m_BytesCopied += dwBytesWrite;
|
|
pCopyFile->m_pCopyStats->m_WriteSize += dwBytesWrite;
|
|
pCopyFile->m_pCopyStats->m_BufferWriteSize = dwBytesWrite;
|
|
pCopyFile->m_pCopyStats->m_BufferWriteTime = GetTickCount() - startTime;
|
|
pCopyFile->m_pCopyStats->m_TotalWriteSize += pCopyFile->m_pCopyStats->m_BufferWriteSize;
|
|
pCopyFile->m_pCopyStats->m_TotalWriteTime += pCopyFile->m_pCopyStats->m_BufferWriteTime;
|
|
|
|
// done with buffer
|
|
AddBufferForRead( pBuffer );
|
|
}
|
|
|
|
if ( ( m_bStopping || m_bReadError || m_bWriteError ) && pBuffer )
|
|
{
|
|
// the aborted buffer must be returned to the pool
|
|
// this unblocks the read thread who will detect the error
|
|
AddBufferForRead( pBuffer );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DWORD WINAPI WriteFileThread( LPVOID lParam )
|
|
{
|
|
return g_XboxInstaller.WriteFileThread( (CopyFile_t*)lParam );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CopyFileOverlapped
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::CopyFileOverlapped( FileDetails_t *pFileDetails, CopyStats_t *pCopyStats )
|
|
{
|
|
CopyFile_t copyFile = {0};
|
|
Buffer_t buffers[INSTALL_NUM_BUFFERS] = {0};
|
|
HANDLE hReadThread = NULL;
|
|
HANDLE hWriteThread = NULL;
|
|
bool bSuccess = false;
|
|
DWORD startCopyTime;
|
|
DWORD dwResult;
|
|
|
|
const char *pSrcFilename = pFileDetails->m_SourceName.String();
|
|
const char *pDstFilename = pFileDetails->m_TargetName.String();
|
|
|
|
startCopyTime = GetTickCount();
|
|
|
|
m_pReadBuffers = NULL;
|
|
m_pWriteBuffers = NULL;
|
|
|
|
pCopyStats->m_NumReadBuffers = 0;
|
|
pCopyStats->m_NumWriteBuffers = 0;
|
|
|
|
m_pNumReadBuffers = &pCopyStats->m_NumReadBuffers;
|
|
m_pNumWriteBuffers = &pCopyStats->m_NumWriteBuffers;
|
|
|
|
strcpy( pCopyStats->m_srcFilename, pSrcFilename );
|
|
strcpy( pCopyStats->m_dstFilename, pDstFilename );
|
|
|
|
copyFile.m_hSrcFile = INVALID_HANDLE_VALUE;
|
|
copyFile.m_hDstFile = INVALID_HANDLE_VALUE;
|
|
|
|
copyFile.m_pCopyStats = pCopyStats;
|
|
|
|
if ( !m_hReadEvent )
|
|
{
|
|
m_hReadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
if ( !m_hReadEvent )
|
|
{
|
|
goto cleanUp;
|
|
}
|
|
}
|
|
|
|
if ( !m_hWriteEvent )
|
|
{
|
|
m_hWriteEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
if ( !m_hWriteEvent )
|
|
{
|
|
goto cleanUp;
|
|
}
|
|
}
|
|
|
|
// expected startup state
|
|
ResetEvent( m_hReadEvent );
|
|
ResetEvent( m_hWriteEvent );
|
|
|
|
// validate the source file
|
|
copyFile.m_hSrcFile = CreateFile( pSrcFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING, NULL );
|
|
if ( copyFile.m_hSrcFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
|
|
// ensure the target file path exists
|
|
CreateFilePath( pDstFilename );
|
|
|
|
// validate the target file
|
|
copyFile.m_hDstFile = CreateFile( pDstFilename, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING, NULL );
|
|
if ( copyFile.m_hDstFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
|
|
pCopyStats->m_ReadSize = pFileDetails->m_nRealFileSize;
|
|
pCopyStats->m_WriteSize = 0;
|
|
|
|
// setup for copy
|
|
copyFile.m_readBufferSize = INSTALL_BUFFER_SIZE;
|
|
copyFile.m_srcFileSize = pFileDetails->m_nRealFileSize;
|
|
// not supporting resume, always copy entire file
|
|
copyFile.m_dstCurrentFileSize = 0;
|
|
|
|
// setup read buffers
|
|
for ( int i = 0; i < INSTALL_NUM_BUFFERS; i++ )
|
|
{
|
|
buffers[i].pData = m_pCopyBuffers[i];
|
|
buffers[i].dwSize = 0;
|
|
buffers[i].pNext = NULL;
|
|
AddBufferForRead( &buffers[i] );
|
|
}
|
|
|
|
// pre-size the target file in aligned buffers
|
|
DWORD dwAligned = ALIGN_VALUE( copyFile.m_srcFileSize, TARGET_SECTOR_SIZE );
|
|
dwResult = SetFilePointer( copyFile.m_hDstFile, dwAligned, NULL, FILE_BEGIN );
|
|
if ( dwResult == INVALID_SET_FILE_POINTER )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
SetEndOfFile( copyFile.m_hDstFile );
|
|
|
|
// start the read thread
|
|
hReadThread = CreateThread( 0, 0, &::ReadFileThread, ©File, CREATE_SUSPENDED, 0 );
|
|
if ( !hReadThread )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
XSetThreadProcessor( hReadThread, INSTALL_READ_PROCESSOR );
|
|
ResumeThread( hReadThread );
|
|
|
|
// start the write thread
|
|
hWriteThread = CreateThread( 0, 0, &::WriteFileThread, ©File, CREATE_SUSPENDED, 0 );
|
|
if ( !hWriteThread )
|
|
{
|
|
// failure
|
|
goto cleanUp;
|
|
}
|
|
XSetThreadProcessor( hWriteThread, INSTALL_WRITE_PROCESSOR );
|
|
ResumeThread( hWriteThread );
|
|
|
|
// wait for threads to finish
|
|
WaitForSingleObject( hWriteThread, INFINITE );
|
|
WaitForSingleObject( hReadThread, INFINITE );
|
|
|
|
CloseHandle( copyFile.m_hDstFile );
|
|
copyFile.m_hDstFile = INVALID_HANDLE_VALUE;
|
|
|
|
if ( m_bReadError || m_bWriteError )
|
|
{
|
|
goto cleanUp;
|
|
}
|
|
|
|
// only change the file to its true size if file copying has completed
|
|
if ( pCopyStats->m_WriteSize >= copyFile.m_srcFileSize )
|
|
{
|
|
// file has completed copying, fixup to the correct final file size
|
|
// no need to change the file size if sector aligned
|
|
if ( copyFile.m_srcFileSize % TARGET_SECTOR_SIZE )
|
|
{
|
|
// re-open file as non-buffered to adjust to correct file size
|
|
HANDLE hFile = CreateFile( pDstFilename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
|
|
if ( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
goto cleanUp;
|
|
}
|
|
SetFilePointer( hFile, copyFile.m_srcFileSize, NULL, FILE_BEGIN );
|
|
SetEndOfFile( hFile );
|
|
CloseHandle( hFile );
|
|
}
|
|
|
|
pCopyStats->m_WriteSize = copyFile.m_srcFileSize;
|
|
|
|
// copy is complete
|
|
pFileDetails->m_bFileIsValid = true;
|
|
}
|
|
|
|
// finished
|
|
bSuccess = true;
|
|
|
|
cleanUp:
|
|
if ( copyFile.m_hSrcFile != INVALID_HANDLE_VALUE )
|
|
{
|
|
CloseHandle( copyFile.m_hSrcFile );
|
|
}
|
|
|
|
if ( copyFile.m_hDstFile != INVALID_HANDLE_VALUE )
|
|
{
|
|
CloseHandle( copyFile.m_hDstFile );
|
|
}
|
|
|
|
if ( hReadThread )
|
|
{
|
|
CloseHandle( hReadThread );
|
|
}
|
|
|
|
if ( hWriteThread )
|
|
{
|
|
CloseHandle( hWriteThread );
|
|
}
|
|
|
|
if ( !bSuccess )
|
|
{
|
|
pCopyStats->m_CopyErrors++;
|
|
}
|
|
|
|
pCopyStats->m_CopyTime = GetTickCount() - startCopyTime;
|
|
|
|
if ( m_bReadError )
|
|
{
|
|
FSDirtyDiskReportFunc_t func = g_pFullFileSystem->GetDirtyDiskReportFunc();
|
|
if ( func )
|
|
{
|
|
func();
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// DoesFileExist
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::DoesFileExist( const char *pFilename, DWORD *pSize )
|
|
{
|
|
if ( pSize )
|
|
{
|
|
*pSize = 0;
|
|
}
|
|
|
|
HANDLE hFile = CreateFile( pFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
|
|
if ( hFile != INVALID_HANDLE_VALUE )
|
|
{
|
|
// must be able to get file size
|
|
DWORD dwFileSize = GetFileSize( hFile, NULL );
|
|
CloseHandle( hFile );
|
|
if ( dwFileSize != (DWORD)-1 )
|
|
{
|
|
// file exists and can get file size
|
|
if ( pSize )
|
|
{
|
|
*pSize = dwFileSize;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// not present
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// IsTargetFileValid
|
|
//
|
|
// Optional non-zero expected source file size must match.
|
|
// Passing in 0 for srcFileSize is the simpler existence check.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::IsTargetFileValid( const char *pFilename, DWORD dwSrcFileSize )
|
|
{
|
|
DWORD dwTargetFileSize = 0;
|
|
|
|
// all valid target files are non-zero, CANNOT allow 0 sized files
|
|
// the target file size could possibly be larger if a copy was aborted
|
|
if ( !DoesFileExist( pFilename, &dwTargetFileSize ) || !dwTargetFileSize )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// valid means the target file's contents are finalized and can be trusted
|
|
// all the sizes must be in agreement
|
|
if ( dwSrcFileSize && dwSrcFileSize != dwTargetFileSize )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// valid (or if source size not supplied, then present)
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Ensure the cache partition has enough space for the install. Possibly
|
|
// unmounts and reformats.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::PrepareCachePartitionForInstall()
|
|
{
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
return false;
|
|
#endif
|
|
|
|
if ( m_InstallData.m_bValid )
|
|
{
|
|
// already installed, nothing to do
|
|
return true;
|
|
}
|
|
|
|
// Always reformat - the cache partition will fragment easily
|
|
// a reformat erases any existing data
|
|
// the full install will be needed
|
|
DWORD nBytesNeeded = m_InstallData.m_nTotalSize + 16*1024*1024;
|
|
|
|
// force a re-format
|
|
// expect ~2GB free, according to docs
|
|
if ( !MountCachePartition( true ) )
|
|
{
|
|
// can't remount
|
|
return false;
|
|
}
|
|
|
|
ULARGE_INTEGER FreeBytesAvailable = { 0 };
|
|
ULARGE_INTEGER TotalNumberOfBytes = { 0 };
|
|
ULARGE_INTEGER TotalNumberOfFreeBytes = { 0 };
|
|
|
|
// check the partition again
|
|
if ( !GetDiskFreeSpaceEx(
|
|
"cache:\\",
|
|
&FreeBytesAvailable,
|
|
&TotalNumberOfBytes,
|
|
&TotalNumberOfFreeBytes ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( nBytesNeeded > FreeBytesAvailable.LowPart )
|
|
{
|
|
// huh? could not get enough free bytes after a re-format!
|
|
// very bad
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// BuildInstallScript
|
|
//
|
|
// Parse filenames to be copied. Builds install script.
|
|
// Returns false if install is not possible (i.e. no HDD).
|
|
// Returns true with script. Script may be empty as install is valid.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::BuildInstallScript( bool bForceFullInstall )
|
|
{
|
|
char srcFile[MAX_PATH];
|
|
char dstFile[MAX_PATH];
|
|
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
return false;
|
|
#endif
|
|
|
|
if ( m_bAppSystemInit )
|
|
{
|
|
if ( g_pFullFileSystem->IsInstalledToXboxHDDCache() )
|
|
{
|
|
// nothing to do, already validated, cache is in use
|
|
return true;
|
|
}
|
|
|
|
if ( !g_pFullFileSystem->IsInstallAllowed() )
|
|
{
|
|
// the filesystem has trumped, we cannot run
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// clear out any prior results
|
|
// setup default state
|
|
ResetSetup();
|
|
|
|
// mount cache partition as-is
|
|
// need to do this to validate possible existing install (i.e. at powerup)
|
|
if ( !MountCachePartition( false ) )
|
|
{
|
|
// no HDD
|
|
return false;
|
|
}
|
|
|
|
// get the version, expected to be at root of disk
|
|
CUtlBuffer sourceVersionBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
if ( !g_pFullFileSystem->ReadFile( "d:/version.xtx", NULL, sourceVersionBuffer ) )
|
|
{
|
|
// missing critical install file
|
|
return false;
|
|
}
|
|
m_InstallData.m_nVersion = atoi( (const char *)sourceVersionBuffer.Base() );
|
|
|
|
// only interested in installing a localized audio component
|
|
// not all supported languages have a localized audio component
|
|
const char *pLanguageString = "";
|
|
if ( XBX_IsAudioLocalized() )
|
|
{
|
|
pLanguageString = XBX_GetLanguageString();
|
|
// paranoid check
|
|
if ( !V_stricmp( pLanguageString, "english" ) )
|
|
{
|
|
// bad, system is confused
|
|
pLanguageString = "";
|
|
}
|
|
}
|
|
|
|
bool bForce = bForceFullInstall;
|
|
|
|
characterset_t breakSet;
|
|
CharacterSetBuild( &breakSet, "" );
|
|
|
|
// scan install script
|
|
// ensure every expected file exists properly at the target, otherwise an install is necessary
|
|
for ( int i = 0; i < ARRAYSIZE( g_InstallScript ); i++ )
|
|
{
|
|
V_strncpy( srcFile, g_InstallScript[i].pSource, sizeof( srcFile ) );
|
|
V_ComposeFileName( CACHE_PATH_CSTIKRE15, g_InstallScript[i].pTarget, dstFile, sizeof( dstFile ) );
|
|
|
|
// name may be optionally decorated
|
|
// replace with language token
|
|
bool bIsLocalizedFile = FixupNamespaceFilename( pLanguageString, srcFile, srcFile, sizeof( srcFile ) );
|
|
if ( bIsLocalizedFile )
|
|
{
|
|
// this is a localized file
|
|
if ( !pLanguageString[0] )
|
|
{
|
|
// ignore installing localized files when not configured for that language
|
|
continue;
|
|
}
|
|
FixupNamespaceFilename( pLanguageString, dstFile, dstFile, sizeof( dstFile ) );
|
|
}
|
|
|
|
// explicitly attempting to open file at source, same as overlapped copy to ensure i/o will succeed later
|
|
DWORD dwSrcSize;
|
|
if ( !DoesFileExist( srcFile, &dwSrcSize ) )
|
|
{
|
|
// can't validate source
|
|
return false;
|
|
}
|
|
|
|
// target file may exist, but aborted
|
|
bool bIsValid = IsTargetFileValid( dstFile, dwSrcSize );
|
|
if ( !bIsValid )
|
|
{
|
|
bForce = true;
|
|
}
|
|
|
|
if ( !bForce )
|
|
{
|
|
// the version must be present and match, otherwise full re-install
|
|
const char *pVersion = V_stristr( dstFile, "version" );
|
|
if ( pVersion )
|
|
{
|
|
// compare contents of version file
|
|
CUtlBuffer targetVersionBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
|
|
if ( !g_pFullFileSystem->ReadFile( dstFile, NULL, targetVersionBuffer ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( V_strcmp( (const char*)sourceVersionBuffer.Base(), (const char *)targetVersionBuffer.Base() ) )
|
|
{
|
|
// differing contents, full re-install
|
|
bForce = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
int iIndex = m_InstallData.m_Files.AddToTail();
|
|
m_InstallData.m_Files[iIndex].m_SourceName = srcFile;
|
|
m_InstallData.m_Files[iIndex].m_TargetName = dstFile;
|
|
m_InstallData.m_Files[iIndex].m_nRealFileSize = dwSrcSize;
|
|
m_InstallData.m_Files[iIndex].m_bFileIsValid = bIsValid;
|
|
|
|
m_InstallData.m_nTotalSize += AlignValue( dwSrcSize, TARGET_SECTOR_SIZE );
|
|
}
|
|
|
|
// cannot support indeterminate installs, either all of its valid, or it gets wiped
|
|
// base on above logic, any invalid file would have set force
|
|
bool bValid = true;
|
|
if ( bForce )
|
|
{
|
|
// force discard all
|
|
// any invalid file will trigger a reformat
|
|
for ( int i = 0; i < m_InstallData.m_Files.Count(); i++ )
|
|
{
|
|
m_InstallData.m_Files[i].m_bFileIsValid = false;
|
|
}
|
|
bValid = false;
|
|
}
|
|
|
|
m_InstallData.m_bValid = bValid;
|
|
m_InstallData.m_bCompleted = bValid;
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Copies all install files to the hard drive
|
|
//-----------------------------------------------------------------------------
|
|
DWORD CXboxInstaller::InstallThreadFunc()
|
|
{
|
|
bool bSuccess;
|
|
|
|
m_CopyStats.m_InstallStartTime = GetTickCount();
|
|
|
|
// allocate buffers
|
|
for ( int i=0; i < INSTALL_NUM_BUFFERS; i++ )
|
|
{
|
|
m_pCopyBuffers[i] = new unsigned char[INSTALL_BUFFER_SIZE];
|
|
}
|
|
|
|
for ( int i = 0; i < m_InstallData.m_Files.Count(); ++i )
|
|
{
|
|
if ( m_bStopping )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( m_InstallData.m_Files[i].m_bFileIsValid )
|
|
{
|
|
// this file is valid
|
|
m_CopyStats.m_BytesCopied += AlignValue( m_InstallData.m_Files[i].m_nRealFileSize, TARGET_SECTOR_SIZE );
|
|
continue;
|
|
}
|
|
|
|
bSuccess = CopyFileOverlapped( &m_InstallData.m_Files[i], &m_CopyStats );
|
|
if ( !bSuccess )
|
|
{
|
|
// quit
|
|
break;
|
|
}
|
|
}
|
|
|
|
// all files must be valid
|
|
bool bValid = true;
|
|
for ( int i = 0; i < m_InstallData.m_Files.Count(); ++i )
|
|
{
|
|
if ( !m_InstallData.m_Files[i].m_bFileIsValid )
|
|
{
|
|
bValid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bValid )
|
|
{
|
|
// Despite what the docs say, which do NOT have our best interest,
|
|
// this will cause fragmentation if it is called frequently.
|
|
// We only call it ONCE here at the end of the successful install operation.
|
|
// The resume pattern must re-format and restart.
|
|
XFlushUtilityDrive();
|
|
}
|
|
|
|
// release buffers
|
|
for ( int i=0; i < INSTALL_NUM_BUFFERS; i++ )
|
|
{
|
|
delete [] m_pCopyBuffers[i];
|
|
}
|
|
|
|
m_CopyStats.m_InstallStopTime = GetTickCount();
|
|
|
|
// set when install operation is complete, regardless of error
|
|
m_InstallData.m_bValid = bValid;
|
|
m_InstallData.m_bCompleted = true;
|
|
m_InstallData.m_bFailed = (m_CopyStats.m_CopyErrors != 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD WINAPI InstallThreadFunc( LPVOID lpParam )
|
|
{
|
|
return g_XboxInstaller.InstallThreadFunc();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Starts installation to disk.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::Start()
|
|
{
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
return false;
|
|
#endif
|
|
|
|
if ( !m_bHasHDD || !m_InstallData.m_nTotalSize || m_InstallData.m_bValid || m_InstallData.m_bFailed )
|
|
{
|
|
// nothing to do, cannot be started or
|
|
// either completed or failed
|
|
return false;
|
|
}
|
|
|
|
if ( !xbox_install_allowed.GetBool() )
|
|
{
|
|
// artifical condition to disallow a qualified installation
|
|
return false;
|
|
}
|
|
|
|
if ( m_bStopping )
|
|
{
|
|
// we must be completely stopped before restarting
|
|
// force the stop process to complete
|
|
// this will cause a hitch if the time between Stop() and Start() is very short
|
|
IsStopped( true );
|
|
}
|
|
else if ( m_hInstallThread )
|
|
{
|
|
// already started
|
|
return true;
|
|
}
|
|
|
|
if ( !m_bInit )
|
|
{
|
|
m_bInit = true;
|
|
InitializeCriticalSection( &m_CriticalSection );
|
|
}
|
|
|
|
Msg( "Xbox Install: Starting...\n" );
|
|
|
|
// reset expected state
|
|
V_memset( &m_CopyStats, 0, sizeof( CopyStats_t ) );
|
|
m_InstallData.m_bCompleted = false;
|
|
|
|
if ( m_bStopping )
|
|
{
|
|
// Cannot support resuming
|
|
// reset any prior results
|
|
for ( int i = 0; i < m_InstallData.m_Files.Count(); i++ )
|
|
{
|
|
m_InstallData.m_Files[i].m_bFileIsValid = false;
|
|
}
|
|
// reformat prior to restart
|
|
// prevents ANY fragmentation
|
|
if ( !MountCachePartition( true ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_bStopping = false;
|
|
m_bReadError = false;
|
|
m_bWriteError = false;
|
|
|
|
// Start the install thread
|
|
m_hInstallThread = CreateThread( NULL, 0, &::InstallThreadFunc, NULL, CREATE_SUSPENDED, 0 );
|
|
if ( !m_hInstallThread )
|
|
{
|
|
// failed, install operation is not running
|
|
m_InstallData.m_bCompleted = true;
|
|
m_InstallData.m_bFailed = true;
|
|
return false;
|
|
}
|
|
XSetThreadProcessor( m_hInstallThread, INSTALL_MANAGER_PROCESSOR );
|
|
ResumeThread( m_hInstallThread );
|
|
|
|
// started
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Poll for install stoppage. Optionally synchronously force the stop.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::IsStopped( bool bForceStop )
|
|
{
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
// installer is not running and will never run
|
|
return true;
|
|
#endif
|
|
|
|
if ( !m_bHasHDD || !m_hInstallThread )
|
|
{
|
|
// not running or already stopped
|
|
return true;
|
|
}
|
|
|
|
if ( bForceStop )
|
|
{
|
|
// caller may not have invoked
|
|
// start the stopping procedure
|
|
Stop();
|
|
}
|
|
|
|
// poll or wait for the install thread to terminate
|
|
if ( m_hInstallThread )
|
|
{
|
|
DWORD dwValue = WaitForSingleObject( m_hInstallThread, bForceStop ? INFINITE : 0 );
|
|
if ( dwValue != WAIT_OBJECT_0 )
|
|
{
|
|
return false;
|
|
}
|
|
CloseHandle( m_hInstallThread );
|
|
m_hInstallThread = NULL;
|
|
}
|
|
|
|
Msg( "Xbox Install: Stopped.\n" );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Signal to stop the install. Use IsStopped() to poll or synchronously stop.
|
|
//-----------------------------------------------------------------------------
|
|
void CXboxInstaller::Stop()
|
|
{
|
|
#if defined( _DEMO ) || !defined( SUPPORTS_INSTALL_TO_XBOX_HDD )
|
|
// under demo conditions cannot allow any HDD access
|
|
return;
|
|
#endif
|
|
|
|
if ( !m_bHasHDD )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( m_bStopping )
|
|
{
|
|
// already stopping
|
|
return;
|
|
}
|
|
|
|
if ( !m_hInstallThread || m_InstallData.m_bCompleted || m_InstallData.m_bFailed )
|
|
{
|
|
// already stopped
|
|
return;
|
|
}
|
|
|
|
Msg( "Xbox Install: Stopping...\n" );
|
|
m_bStopping = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Discards the setup.
|
|
//-----------------------------------------------------------------------------
|
|
void CXboxInstaller::ResetSetup()
|
|
{
|
|
V_memset( &m_CopyStats, 0, sizeof( CopyStats_t ) );
|
|
m_InstallData.Reset();
|
|
|
|
m_bHasHDD = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Setup for install.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::Setup( bool bForceInstall )
|
|
{
|
|
if ( BuildInstallScript( bForceInstall ) )
|
|
{
|
|
m_bHasHDD = PrepareCachePartitionForInstall();
|
|
}
|
|
|
|
return m_bHasHDD;
|
|
}
|
|
|
|
bool CXboxInstaller::IsFullyInstalled()
|
|
{
|
|
return m_InstallData.m_bValid;
|
|
}
|
|
|
|
DWORD CXboxInstaller::GetTotalSize()
|
|
{
|
|
return m_InstallData.m_nTotalSize;
|
|
}
|
|
|
|
DWORD CXboxInstaller::GetVersion()
|
|
{
|
|
return m_InstallData.m_nVersion;
|
|
}
|
|
|
|
const CopyStats_t *CXboxInstaller::GetCopyStats()
|
|
{
|
|
return &m_CopyStats;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Identifies that we may or can be installing.
|
|
// Cannot go TRUE unless the filesystem allows the install.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::IsInstallEnabled()
|
|
{
|
|
return m_bHasHDD;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns TRUE if the install has completed and a restart is desireable
|
|
// to achieve a remount.
|
|
//-----------------------------------------------------------------------------
|
|
bool CXboxInstaller::ShouldRestart()
|
|
{
|
|
// strong exact checking for paranoia reasons
|
|
if ( IsFullyInstalled() &&
|
|
!g_pFullFileSystem->IsInstalledToXboxHDDCache() &&
|
|
g_pFullFileSystem->IsDVDHosted() )
|
|
{
|
|
// Install is valid and..
|
|
// the game did not startup with the install already intact and...
|
|
// the game is running with its primary paths set to the DVD and..
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CXboxInstaller::ForceCachePaths()
|
|
{
|
|
bool bDidFixup = false;
|
|
|
|
// restarting is valid also indicates we can patch the search paths
|
|
// when the image is already installed, but the search paths haven't been patched, do it now
|
|
if ( !m_bForcedCachePaths && ( ShouldRestart() || g_pFullFileSystem->IsInstalledToXboxHDDCache() ) )
|
|
{
|
|
// regardless of outcome, we only do it once
|
|
// if it didn't occur, too bad, we don't do it ever again
|
|
m_bForcedCachePaths = true;
|
|
bDidFixup = g_pFullFileSystem->FixupSearchPathsAfterInstall();
|
|
}
|
|
|
|
return bDidFixup;
|
|
}
|
|
|
|
void CXboxInstaller::SpewStatus()
|
|
{
|
|
Msg( "Install Status:\n" );
|
|
|
|
Msg( "Version: %d (%s) (Xbox)\n", GetVersion(), XBX_GetLanguageString() );
|
|
Msg( "DVD Hosted: %s\n", g_pFullFileSystem->IsDVDHosted() ? "Enabled" : "Disabled" );
|
|
|
|
if ( g_pFullFileSystem->IsInstalledToXboxHDDCache() )
|
|
{
|
|
Msg( "Existing Image Found.\n" );
|
|
}
|
|
|
|
if ( !IsInstallEnabled() || !xbox_install_allowed.GetBool() )
|
|
{
|
|
Msg( "Install Disabled.\n" );
|
|
}
|
|
|
|
if ( IsFullyInstalled() )
|
|
{
|
|
Msg( "Fully Installed.\n" );
|
|
}
|
|
|
|
Msg( "Progress: %d/%d MB\n", GetCopyStats()->m_BytesCopied/(1024*1024), GetTotalSize()/(1024*1024) );
|
|
}
|