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.
596 lines
14 KiB
596 lines
14 KiB
5 years ago
|
//========= 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;
|
||
|
}
|