|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Copies a file using overlapped async IO.
//
// Stub executeable
//=====================================================================================//
#include "xbox_loader.h"
#define BUFFER_SIZE (1*1024*1024)
#define NUM_BUFFERS 4
#define ALIGN(x,y) (((x)+(y)-1) & ~((y)-1))
struct CopyFile_t { // source file
HANDLE m_hSrcFile; DWORD m_srcFileSize; int m_readBufferSize; unsigned int m_numReadCycles;
// target file
HANDLE m_hDstFile; DWORD m_dstFileSize;
// source file gets decompressed
bool m_bInflate; unsigned char *m_pInflateBuffer; int m_inflateBufferSize;
bool m_bCopyError; CopyStats_t *m_pCopyStats; };
struct Buffer_t { unsigned char *pData; DWORD dwSize; Buffer_t* pNext; int id; };
Buffer_t *g_pReadBuffers = NULL; Buffer_t *g_pWriteBuffers = NULL;
CRITICAL_SECTION g_criticalSection; HANDLE g_hReadEvent; HANDLE g_hWriteEvent; DWORD *g_pNumReadBuffers; DWORD *g_pNumWriteBuffers;
//-----------------------------------------------------------------------------
// CreateFilePath
//
// Create full path to specified file.
//-----------------------------------------------------------------------------
bool 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; }
//-----------------------------------------------------------------------------
// LockBufferForRead
//
//-----------------------------------------------------------------------------
Buffer_t *LockBufferForRead() { if ( !g_pReadBuffers ) { // out of data, wait for it
WaitForSingleObject( g_hReadEvent, INFINITE ); } else { ResetEvent( g_hReadEvent ); }
EnterCriticalSection( &g_criticalSection );
Buffer_t *pBuffer = g_pReadBuffers; g_pReadBuffers = pBuffer->pNext;
(*g_pNumReadBuffers)--;
LeaveCriticalSection( &g_criticalSection );
return pBuffer; }
//-----------------------------------------------------------------------------
// LockBufferForWrite
//
//-----------------------------------------------------------------------------
Buffer_t* LockBufferForWrite() { if ( !g_pWriteBuffers ) { // out of data, wait for more
WaitForSingleObject( g_hWriteEvent, INFINITE ); } else { ResetEvent( g_hWriteEvent ); }
EnterCriticalSection( &g_criticalSection );
Buffer_t *pBuffer = g_pWriteBuffers; g_pWriteBuffers = pBuffer->pNext;
(*g_pNumWriteBuffers)--;
LeaveCriticalSection( &g_criticalSection );
return pBuffer; }
//-----------------------------------------------------------------------------
// AddBufferForRead
//
//-----------------------------------------------------------------------------
void AddBufferForRead( Buffer_t *pBuffer ) { EnterCriticalSection( &g_criticalSection );
// add to end of list
Buffer_t *pCurrent = g_pReadBuffers; while ( pCurrent && pCurrent->pNext ) { pCurrent = pCurrent->pNext; } if ( pCurrent ) { pBuffer->pNext = pCurrent->pNext; pCurrent->pNext = pBuffer; } else { pBuffer->pNext = NULL; g_pReadBuffers = pBuffer; } (*g_pNumReadBuffers)++;
LeaveCriticalSection( &g_criticalSection );
SetEvent( g_hReadEvent ); }
//-----------------------------------------------------------------------------
// AddBufferForWrite
//
//-----------------------------------------------------------------------------
void AddBufferForWrite( Buffer_t *pBuffer ) { EnterCriticalSection( &g_criticalSection );
// add to end of list
Buffer_t* pCurrent = g_pWriteBuffers; while ( pCurrent && pCurrent->pNext ) { pCurrent = pCurrent->pNext; } if ( pCurrent ) { pBuffer->pNext = pCurrent->pNext; pCurrent->pNext = pBuffer; } else { pBuffer->pNext = NULL; g_pWriteBuffers = pBuffer; }
(*g_pNumWriteBuffers)++;
LeaveCriticalSection( &g_criticalSection );
SetEvent( g_hWriteEvent ); }
//-----------------------------------------------------------------------------
// ReadFileThread
//
//-----------------------------------------------------------------------------
DWORD WINAPI ReadFileThread( LPVOID lParam ) { CopyFile_t *pCopyFile; OVERLAPPED overlappedRead = {0}; DWORD startTime; DWORD dwBytesRead; DWORD dwError; BOOL bResult; Buffer_t *pBuffer;
pCopyFile = (CopyFile_t*)lParam;
// Copy from the buffer to the Hard Drive
for ( unsigned int readCycle = 0; readCycle < pCopyFile->m_numReadCycles; ++readCycle ) { pBuffer = LockBufferForRead();
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
pCopyFile->m_bCopyError = true; break; } else { goto retry; } } else { // Wait for the operation to finish
GetOverlappedResult( pCopyFile->m_hSrcFile, &overlappedRead, &dwBytesRead, TRUE ); overlappedRead.Offset += dwBytesRead; }
if ( !dwBytesRead ) { pCopyFile->m_bCopyError = true; break; }
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 ); }
return 0; }
//-----------------------------------------------------------------------------
// WriteFileThread
//
//-----------------------------------------------------------------------------
DWORD WINAPI WriteFileThread( LPVOID lParam ) { CopyFile_t *pCopyFile; OVERLAPPED overlappedWrite = {0}; DWORD startTime; DWORD dwBytesWrite; DWORD dwWriteSize; DWORD dwError; BOOL bResult; Buffer_t *pBuffer; unsigned char *pWriteBuffer;
pCopyFile = (CopyFile_t*)lParam;
while ( overlappedWrite.Offset < pCopyFile->m_dstFileSize ) { // wait for wake-up event
pBuffer = LockBufferForWrite();
if ( pCopyFile->m_bInflate ) { startTime = GetTickCount();
DWORD dwSkip = overlappedWrite.Offset ? 0 : sizeof( xCompressHeader ); dwWriteSize = JCALG1_Decompress_Formatted_Buffer( pBuffer->dwSize - dwSkip, pBuffer->pData + dwSkip, pCopyFile->m_inflateBufferSize, pCopyFile->m_pInflateBuffer ); if ( dwWriteSize == (DWORD)-1 ) { pCopyFile->m_bCopyError = true; break; }
pCopyFile->m_pCopyStats->m_inflateSize = dwWriteSize; pCopyFile->m_pCopyStats->m_inflateTime = GetTickCount() - startTime;
pWriteBuffer = pCopyFile->m_pInflateBuffer; } else { // straight copy
dwWriteSize = pBuffer->dwSize; pWriteBuffer = pBuffer->pData; }
if ( overlappedWrite.Offset + dwWriteSize >= pCopyFile->m_dstFileSize ) { // last buffer, ensure all data is written
dwWriteSize = ALIGN( dwWriteSize, 512 ); }
startTime = GetTickCount(); dwBytesWrite = 0; int numAttempts = 0; retry: // write file to HDD
bResult = WriteFile( pCopyFile->m_hDstFile, pWriteBuffer, (dwWriteSize/512) * 512, NULL, &overlappedWrite ); dwError = GetLastError(); if ( !bResult && dwError != ERROR_IO_PENDING ) { numAttempts++; if ( numAttempts == 3 ) { // error
pCopyFile->m_bCopyError = true; break; } else { goto retry; } } else { // Wait for the operation to finish
GetOverlappedResult( pCopyFile->m_hDstFile, &overlappedWrite, &dwBytesWrite, TRUE ); overlappedWrite.Offset += dwBytesWrite; }
if ( dwBytesWrite ) { // track expected size
pCopyFile->m_pCopyStats->m_bytesCopied += dwBytesWrite; pCopyFile->m_pCopyStats->m_writeSize += dwBytesWrite; } else { pCopyFile->m_bCopyError = true; break; }
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;
AddBufferForRead( pBuffer ); }
return 0; }
//-----------------------------------------------------------------------------
// CopyFileInit
//
//-----------------------------------------------------------------------------
void CopyFileInit() { static bool init = false; if ( !init ) { InitializeCriticalSection( &g_criticalSection ); g_hReadEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); g_hWriteEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); init = true; } else { // expected startup state
ResetEvent( g_hReadEvent ); ResetEvent( g_hWriteEvent );
g_pReadBuffers = NULL; g_pWriteBuffers = NULL; } }
//-----------------------------------------------------------------------------
// CopyFileOverlapped
//
//-----------------------------------------------------------------------------
bool CopyFileOverlapped( const char *pSrcFilename, const char *pDstFilename, xCompressHeader *pxcHeader, CopyStats_t *pCopyStats ) { CopyFile_t copyFile = {0}; Buffer_t buffers[NUM_BUFFERS] = {0}; HANDLE hReadThread = NULL; HANDLE hWriteThread = NULL; bool bSuccess = false; DWORD startCopyTime; DWORD dwResult; int i;
startCopyTime = GetTickCount();
CopyFileInit();
g_pNumReadBuffers = &pCopyStats->m_numReadBuffers; g_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; copyFile.m_bCopyError = false;
// validate the source file
copyFile.m_hSrcFile = CreateFile( pSrcFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING, NULL ); if ( copyFile.m_hSrcFile == INVALID_HANDLE_VALUE ) { // failure
goto cleanUp; }
copyFile.m_srcFileSize = GetFileSize( copyFile.m_hSrcFile, NULL ); if ( copyFile.m_srcFileSize == (DWORD)-1 ) { // 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 = copyFile.m_srcFileSize; pCopyStats->m_writeSize = 0;
if ( pxcHeader ) { // read in chunks of compressed blocks
copyFile.m_readBufferSize = pxcHeader->nReadBlockSize; copyFile.m_dstFileSize = pxcHeader->nUncompressedFileSize; } else { // setup for copy
copyFile.m_readBufferSize = BUFFER_SIZE; copyFile.m_dstFileSize = copyFile.m_srcFileSize; }
// setup read buffers
for ( i=0; i<NUM_BUFFERS; i++) { buffers[i].pData = new unsigned char[copyFile.m_readBufferSize]; buffers[i].dwSize = 0; buffers[i].pNext = NULL; AddBufferForRead( &buffers[i] ); } copyFile.m_numReadCycles = (copyFile.m_srcFileSize + copyFile.m_readBufferSize - 1)/copyFile.m_readBufferSize;
// setup write buffer
if ( pxcHeader ) { copyFile.m_pInflateBuffer = new unsigned char[pxcHeader->nDecompressionBufferSize]; copyFile.m_inflateBufferSize = pxcHeader->nDecompressionBufferSize; copyFile.m_bInflate = true; } else { copyFile.m_bInflate = false; }
// pre-size the target file in aligned buffers
DWORD dwAligned = ALIGN( copyFile.m_dstFileSize, 512 ); 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, 0, 0 ); if ( !hReadThread ) { // failure
goto cleanUp; }
// wait for buffers to populate
// start the write thread
hWriteThread = CreateThread( 0, 0, &WriteFileThread, ©File, 0, 0 ); if ( !hWriteThread ) { // failure
goto cleanUp; }
// wait for write thread to finish
WaitForSingleObject( hWriteThread, INFINITE ); WaitForSingleObject( hReadThread, INFINITE );
if ( copyFile.m_bCopyError ) { goto cleanUp; }
// Fixup the file size
CloseHandle( copyFile.m_hDstFile ); copyFile.m_hDstFile = INVALID_HANDLE_VALUE;
if ( copyFile.m_dstFileSize % 512 ) { // 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 ); SetFilePointer( hFile, copyFile.m_dstFileSize, NULL, FILE_BEGIN ); SetEndOfFile( hFile ); CloseHandle( hFile ); }
// 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 ); }
for ( i=0; i<NUM_BUFFERS; i++ ) { if ( buffers[i].pData ) { delete [] buffers[i].pData; } }
if ( copyFile.m_pInflateBuffer ) { delete [] copyFile.m_pInflateBuffer; }
if ( !bSuccess ) { pCopyStats->m_copyErrors++; }
pCopyStats->m_copyTime = GetTickCount() - startCopyTime;
return bSuccess; }
|