source-engine/utils/xbox/xbox_loader/xbox_fileCopy.cpp

596 lines
14 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= 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, &copyFile, 0, 0 );
if ( !hReadThread )
{
// failure
goto cleanUp;
}
// wait for buffers to populate
// start the write thread
hWriteThread = CreateThread( 0, 0, &WriteFileThread, &copyFile, 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;
}