//========= 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; inDecompressionBufferSize]; 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; im_copyErrors++; } pCopyStats->m_copyTime = GetTickCount() - startCopyTime; return bSuccess; }