diff options
Diffstat (limited to 'src/FileStream.cpp')
-rw-r--r-- | src/FileStream.cpp | 2163 |
1 files changed, 985 insertions, 1178 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp index 403d1d4..58543e3 100644 --- a/src/FileStream.cpp +++ b/src/FileStream.cpp @@ -48,100 +48,125 @@ void SetLastError(int nError) #endif //----------------------------------------------------------------------------- -// Preparing file bitmap for a complete file of a given size +// Dummy init function -static bool FileBitmap_CheckRange( - TFileBitmap * pBitmap, - ULONGLONG ByteOffset, - ULONGLONG EndOffset) +static void BaseNone_Init(TFileStream *) { - LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); - DWORD BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize); - DWORD ByteIndex = (BlockIndex / 0x08); - BYTE BitMask = (BYTE)(0x01 << (BlockIndex & 0x07)); + // Nothing here +} - // Mask the bye offset down to the begin of the block - ByteOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1); +//----------------------------------------------------------------------------- +// Local functions - base file support - // Check each block - while(ByteOffset < EndOffset) +static bool BaseFile_Create(TFileStream * pStream) +{ +#ifdef PLATFORM_WINDOWS { - // Check availability of that block - if((pbBitmap[ByteIndex] & BitMask) == 0) + DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; + + pStream->Base.File.hFile = CreateFile(pStream->szFileName, + GENERIC_READ | GENERIC_WRITE, + dwWriteShare | FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + 0, + NULL); + if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) return false; + } +#endif - // Move to the next block - ByteOffset += pBitmap->BlockSize; - ByteIndex += (BitMask >> 0x07); - BitMask = (BitMask >> 0x07) | (BitMask << 0x01); +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + { + intptr_t handle; + + handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if(handle == -1) + { + nLastError = errno; + return false; + } + + pStream->Base.File.hFile = (HANDLE)handle; } +#endif - // All blocks are present + // Reset the file size and position + pStream->Base.File.FileSize = 0; + pStream->Base.File.FilePos = 0; return true; } -static void FileBitmap_SetRange( - TFileBitmap * pBitmap, - ULONGLONG ByteOffset, - ULONGLONG EndOffset) +static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags) { - LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); - DWORD BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize); - DWORD ByteIndex = (BlockIndex / 0x08); - BYTE BitMask = (BYTE)(0x01 << (BlockIndex & 0x07)); - - // Mask the bye offset down to the begin of the block - ByteOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1); - - // Check each block - while(ByteOffset < EndOffset) +#ifdef PLATFORM_WINDOWS { - // Set that bit - pbBitmap[ByteIndex] |= BitMask; + ULARGE_INTEGER FileSize; + DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES; + DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; - // Move to the next block - ByteOffset += pBitmap->BlockSize; - ByteIndex += (BitMask >> 0x07); - BitMask = (BitMask >> 0x07) | (BitMask << 0x01); - } -} + // Open the file + pStream->Base.File.hFile = CreateFile(pStream->szFileName, + FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess, + FILE_SHARE_READ | dwWriteShare, + NULL, + OPEN_EXISTING, + 0, + NULL); + if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) + return false; -static DWORD FileBitmap_CheckFile(TFileBitmap * pBitmap) -{ - LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); - DWORD WholeByteCount = (pBitmap->BlockCount / 8); - DWORD ExtraBitsCount = (pBitmap->BlockCount & 7); - BYTE ExpectedValue; + // Query the file size + FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart); + pStream->Base.File.FileSize = FileSize.QuadPart; - // Verify the whole bytes - their value must be 0xFF - for(DWORD i = 0; i < WholeByteCount; i++) - { - if(pbBitmap[i] != 0xFF) - return 0; + // Query last write time + GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime); } +#endif - // If there are extra bits, calculate the mask - if(ExtraBitsCount != 0) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) { - ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); - if(pbBitmap[WholeByteCount] != ExpectedValue) - return 0; + struct stat64 fileinfo; + int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR; + intptr_t handle; + + // Open the file + handle = open(szFileName, oflag | O_LARGEFILE); + if(handle == -1) + { + nLastError = errno; + return false; + } + + // Get the file size + if(fstat64(handle, &fileinfo) == -1) + { + nLastError = errno; + return false; + } + + // time_t is number of seconds since 1.1.1970, UTC. + // 1 second = 10000000 (decimal) in FILETIME + // Set the start to 1.1.1970 00:00:00 + pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); + pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size; + pStream->Base.File.hFile = (HANDLE)handle; } +#endif - // Yes, the file is complete - return 1; + // Reset the file position + pStream->Base.File.FilePos = 0; + return true; } -//----------------------------------------------------------------------------- -// Local functions - base file support - static bool BaseFile_Read( TFileStream * pStream, // Pointer to an open stream ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position void * pvBuffer, // Pointer to data to be read DWORD dwBytesToRead) // Number of bytes to read from the file { - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; DWORD dwBytesRead = 0; // Must be set by platform-specific code #ifdef PLATFORM_WINDOWS @@ -152,7 +177,7 @@ static bool BaseFile_Read( // one system call to SetFilePointer // Update the byte offset - pStream->FilePos = ByteOffset; + pStream->Base.File.FilePos = ByteOffset; // Read the data if(dwBytesToRead != 0) @@ -197,7 +222,7 @@ static bool BaseFile_Read( // Increment the current file position by number of bytes read // If the number of bytes read doesn't match to required amount, return false - pStream->FilePos = ByteOffset + dwBytesRead; + pStream->Base.File.FilePos = ByteOffset + dwBytesRead; if(dwBytesRead != dwBytesToRead) SetLastError(ERROR_HANDLE_EOF); return (dwBytesRead == dwBytesToRead); @@ -212,7 +237,7 @@ static bool BaseFile_Read( static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) { - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; DWORD dwBytesWritten = 0; // Must be set by platform-specific code #ifdef PLATFORM_WINDOWS @@ -223,7 +248,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const // one system call to SetFilePointer // Update the byte offset - pStream->FilePos = ByteOffset; + pStream->Base.File.FilePos = ByteOffset; // Read the data if(dwBytesToWrite != 0) @@ -264,11 +289,11 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const #endif // Increment the current file position by number of bytes read - pStream->FilePos = ByteOffset + dwBytesWritten; + pStream->Base.File.FilePos = ByteOffset + dwBytesWritten; // Also modify the file size, if needed - if(pStream->FilePos > pStream->FileSize) - pStream->FileSize = pStream->FilePos; + if(pStream->Base.File.FilePos > pStream->Base.File.FileSize) + pStream->Base.File.FileSize = pStream->Base.File.FilePos; if(dwBytesWritten != dwBytesToWrite) SetLastError(ERROR_DISK_FULL); @@ -279,7 +304,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const * \a pStream Pointer to an open stream * \a NewFileSize New size of the file */ -static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) +static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize) { #ifdef PLATFORM_WINDOWS { @@ -297,8 +322,8 @@ static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) bResult = (bool)SetEndOfFile(pStream->Base.File.hFile); // Restore the file position - FileSizeHi = (LONG)(pStream->FilePos >> 32); - FileSizeLo = (LONG)(pStream->FilePos); + FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32); + FileSizeLo = (LONG)(pStream->Base.File.FilePos); SetFilePointer(pStream->Base.File.hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN); return bResult; } @@ -317,8 +342,26 @@ static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) #endif } +// Gives the current file size +static bool BaseFile_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) +{ + // Note: Used by all thre base providers. + // Requires the TBaseData union to have the same layout for all three base providers + *pFileSize = pStream->Base.File.FileSize; + return true; +} + +// Gives the current file position +static bool BaseFile_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) +{ + // Note: Used by all thre base providers. + // Requires the TBaseData union to have the same layout for all three base providers + *pByteOffset = pStream->Base.File.FilePos; + return true; +} + // Renames the file pointed by pStream so that it contains data from pNewStream -static bool BaseFile_Switch(TFileStream * pStream, TFileStream * pNewStream) +static bool BaseFile_Replace(TFileStream * pStream, TFileStream * pNewStream) { #ifdef PLATFORM_WINDOWS // Delete the original stream file. Don't check the result value, @@ -358,163 +401,23 @@ static void BaseFile_Close(TFileStream * pStream) pStream->Base.File.hFile = INVALID_HANDLE_VALUE; } -static bool BaseFile_Create(TFileStream * pStream) +// Initializes base functions for the disk file +static void BaseFile_Init(TFileStream * pStream) { -#ifdef PLATFORM_WINDOWS - { - DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; - - pStream->Base.File.hFile = CreateFile(pStream->szFileName, - GENERIC_READ | GENERIC_WRITE, - dwWriteShare | FILE_SHARE_READ, - NULL, - CREATE_ALWAYS, - 0, - NULL); - if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) - return false; - } -#endif - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - intptr_t handle; - - handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if(handle == -1) - { - nLastError = errno; - return false; - } - - pStream->Base.File.hFile = (HANDLE)handle; - } -#endif - - // Fill-in the entry points - pStream->BaseRead = BaseFile_Read; - pStream->BaseWrite = BaseFile_Write; - pStream->BaseSetSize = BaseFile_SetSize; - pStream->BaseClose = BaseFile_Close; - - // Reset the file position - pStream->FileSize = 0; - pStream->FilePos = 0; - return true; -} - -static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags) -{ -#ifdef PLATFORM_WINDOWS - { - ULARGE_INTEGER FileSize; - DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES; - DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; - - // Open the file - pStream->Base.File.hFile = CreateFile(pStream->szFileName, - FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess, - FILE_SHARE_READ | dwWriteShare, - NULL, - OPEN_EXISTING, - 0, - NULL); - if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) - return false; - - // Query the file size - FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart); - pStream->FileSize = FileSize.QuadPart; - - // Query last write time - GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->FileTime); - } -#endif - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - struct stat64 fileinfo; - int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR; - intptr_t handle; - - // Open the file - handle = open(szFileName, oflag | O_LARGEFILE); - if(handle == -1) - { - nLastError = errno; - return false; - } - - // Get the file size - if(fstat64(handle, &fileinfo) == -1) - { - nLastError = errno; - return false; - } - - // time_t is number of seconds since 1.1.1970, UTC. - // 1 second = 10000000 (decimal) in FILETIME - // Set the start to 1.1.1970 00:00:00 - pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); - pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size; - pStream->Base.File.hFile = (HANDLE)handle; - } -#endif - - // Fill-in the entry points + pStream->BaseCreate = BaseFile_Create; + pStream->BaseOpen = BaseFile_Open; pStream->BaseRead = BaseFile_Read; pStream->BaseWrite = BaseFile_Write; - pStream->BaseSetSize = BaseFile_SetSize; + pStream->BaseResize = BaseFile_Resize; + pStream->BaseGetSize = BaseFile_GetSize; + pStream->BaseGetPos = BaseFile_GetPos; pStream->BaseClose = BaseFile_Close; - - // Reset the file position - pStream->FilePos = 0; - return true; } //----------------------------------------------------------------------------- // Local functions - base memory-mapped file support -static bool BaseMap_Read( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position - void * pvBuffer, // Pointer to data to be read - DWORD dwBytesToRead) // Number of bytes to read from the file -{ - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; - - // Do we have to read anything at all? - if(dwBytesToRead != 0) - { - // Don't allow reading past file size - if((ByteOffset + dwBytesToRead) > pStream->FileSize) - return false; - - // Copy the required data - memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead); - } - - // Move the current file position - pStream->FilePos += dwBytesToRead; - return true; -} - -static void BaseMap_Close(TFileStream * pStream) -{ -#ifdef PLATFORM_WINDOWS - if(pStream->Base.Map.pbFile != NULL) - UnmapViewOfFile(pStream->Base.Map.pbFile); -#endif - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - if(pStream->Base.Map.pbFile != NULL) - munmap(pStream->Base.Map.pbFile, (size_t )pStream->FileSize); -#endif - - pStream->Base.Map.pbFile = NULL; -} - -static bool BaseMap_Open(TFileStream * pStream) +static bool BaseMap_Open(TFileStream * pStream, DWORD dwStreamFlags) { #ifdef PLATFORM_WINDOWS @@ -523,6 +426,9 @@ static bool BaseMap_Open(TFileStream * pStream) HANDLE hMap; bool bResult = false; + // Keep compiler happy + dwStreamFlags = dwStreamFlags; + // Open the file for read access hFile = CreateFile(pStream->szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(hFile != NULL) @@ -531,9 +437,6 @@ static bool BaseMap_Open(TFileStream * pStream) FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart); if(FileSize.QuadPart != 0) { - // Retrieve file time - GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->FileTime); - // Now create mapping object hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if(hMap != NULL) @@ -544,8 +447,12 @@ static bool BaseMap_Open(TFileStream * pStream) pStream->Base.Map.pbFile = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if(pStream->Base.Map.pbFile != NULL) { - pStream->FileSize = FileSize.QuadPart; - pStream->FilePos = 0; + // Retrieve file time + GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime); + + // Retrieve file size and position + pStream->Base.Map.FileSize = FileSize.QuadPart; + pStream->Base.Map.FilePos = 0; bResult = true; } @@ -599,10 +506,60 @@ static bool BaseMap_Open(TFileStream * pStream) } #endif - // Fill-in entry points + return true; +} + +static bool BaseMap_Read( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file +{ + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos; + + // Do we have to read anything at all? + if(dwBytesToRead != 0) + { + // Don't allow reading past file size + if((ByteOffset + dwBytesToRead) > pStream->Base.Map.FileSize) + return false; + + // Copy the required data + memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead); + } + + // Move the current file position + pStream->Base.Map.FilePos += dwBytesToRead; + return true; +} + +static void BaseMap_Close(TFileStream * pStream) +{ +#ifdef PLATFORM_WINDOWS + if(pStream->Base.Map.pbFile != NULL) + UnmapViewOfFile(pStream->Base.Map.pbFile); +#endif + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + if(pStream->Base.Map.pbFile != NULL) + munmap(pStream->Base.Map.pbFile, (size_t )pStream->FileSize); +#endif + + pStream->Base.Map.pbFile = NULL; +} + +// Initializes base functions for the mapped file +static void BaseMap_Init(TFileStream * pStream) +{ + // Supply the file stream functions + pStream->BaseOpen = BaseMap_Open; pStream->BaseRead = BaseMap_Read; + pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function + pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function pStream->BaseClose = BaseMap_Close; - return true; + + // Mapped files are read-only + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; } //----------------------------------------------------------------------------- @@ -631,6 +588,110 @@ static const TCHAR * BaseHttp_ExtractServerName(const TCHAR * szFileName, TCHAR return szFileName; } +static bool BaseHttp_Open(TFileStream * pStream, DWORD dwStreamFlags) +{ +#ifdef PLATFORM_WINDOWS + + const TCHAR * szFileName; + HINTERNET hRequest; + DWORD dwTemp = 0; + bool bFileAvailable = false; + int nError = ERROR_SUCCESS; + + // Keep compiler happy + dwStreamFlags = dwStreamFlags; + + // Don't connect to the internet + if(!InternetGetConnectedState(&dwTemp, 0)) + nError = GetLastError(); + + // Initiate the connection to the internet + if(nError == ERROR_SUCCESS) + { + pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0); + if(pStream->Base.Http.hInternet == NULL) + nError = GetLastError(); + } + + // Connect to the server + if(nError == ERROR_SUCCESS) + { + TCHAR szServerName[MAX_PATH]; + DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; + + // Initiate connection with the server + szFileName = BaseHttp_ExtractServerName(pStream->szFileName, szServerName); + pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, + szServerName, + INTERNET_DEFAULT_HTTP_PORT, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + dwFlags, + 0); + if(pStream->Base.Http.hConnect == NULL) + nError = GetLastError(); + } + + // Now try to query the file size + if(nError == ERROR_SUCCESS) + { + // Open HTTP request to the file + hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); + if(hRequest != NULL) + { + if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) + { + ULONGLONG FileTime = 0; + DWORD dwFileSize = 0; + DWORD dwDataSize; + DWORD dwIndex = 0; + + // Check if the MPQ has Last Modified field + dwDataSize = sizeof(ULONGLONG); + if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) + pStream->Base.Http.FileTime = FileTime; + + // Verify if the server supports random access + dwDataSize = sizeof(DWORD); + if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) + { + if(dwFileSize != 0) + { + pStream->Base.Http.FileSize = dwFileSize; + pStream->Base.Http.FilePos = 0; + bFileAvailable = true; + } + } + } + InternetCloseHandle(hRequest); + } + } + + // If the file is not there and is not available for random access, + // report error + if(bFileAvailable == false) + { + pStream->BaseClose(pStream); + return false; + } + + return true; + +#else + + // Not supported + SetLastError(ERROR_NOT_SUPPORTED); + pStream = pStream; + return false; + +#endif +} + static bool BaseHttp_Read( TFileStream * pStream, // Pointer to an open stream ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position @@ -638,7 +699,7 @@ static bool BaseHttp_Read( DWORD dwBytesToRead) // Number of bytes to read from the file { #ifdef PLATFORM_WINDOWS - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos; DWORD dwTotalBytesRead = 0; // Do we have to read anything at all? @@ -688,7 +749,7 @@ static bool BaseHttp_Read( } // Increment the current file position by number of bytes read - pStream->FilePos = ByteOffset + dwTotalBytesRead; + pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead; // If the number of bytes read doesn't match the required amount, return false if(dwTotalBytesRead != dwBytesToRead) @@ -723,242 +784,318 @@ static void BaseHttp_Close(TFileStream * pStream) #endif } -static bool BaseHttp_Open(TFileStream * pStream) +// Initializes base functions for the mapped file +static void BaseHttp_Init(TFileStream * pStream) { -#ifdef PLATFORM_WINDOWS + // Supply the stream functions + pStream->BaseOpen = BaseHttp_Open; + pStream->BaseRead = BaseHttp_Read; + pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function + pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function + pStream->BaseClose = BaseHttp_Close; - const TCHAR * szFileName; - HINTERNET hRequest; - DWORD dwTemp = 0; - bool bFileAvailable = false; - int nError = ERROR_SUCCESS; + // HTTP files are read-only + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; +} - // Don't connect to the internet - if(!InternetGetConnectedState(&dwTemp, 0)) - nError = GetLastError(); +//----------------------------------------------------------------------------- +// Local functions - base block-based support - // Initiate the connection to the internet - if(nError == ERROR_SUCCESS) +// Generic function that loads blocks from the file +// The function groups the block with the same availability, +// so the called BlockRead can finish the request in a single system call +static bool BlockStream_Read( + TBlockStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file +{ + ULONGLONG BlockOffset0; + ULONGLONG BlockOffset; + ULONGLONG ByteOffset; + ULONGLONG EndOffset; + LPBYTE TransferBuffer; + LPBYTE BlockBuffer; + DWORD BlockBufferOffset; // Offset of the desired data in the block buffer + DWORD BytesNeeded; // Number of bytes that really need to be read + DWORD BlockSize = pStream->BlockSize; + DWORD BlockCount; + bool bPrevBlockAvailable; + bool bBlockAvailable; + bool bResult = true; + + // The base block read function must be present + assert(pStream->BlockCheck != NULL); + assert(pStream->BlockRead != NULL); + + // NOP reading of zero bytes + if(dwBytesToRead == 0) + return true; + + // Get the current position in the stream + ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos; + EndOffset = ByteOffset + dwBytesToRead; + if(EndOffset > pStream->StreamSize) { - pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0); - if(pStream->Base.Http.hInternet == NULL) - nError = GetLastError(); + SetLastError(ERROR_CAN_NOT_COMPLETE); + return false; } - // Connect to the server - if(nError == ERROR_SUCCESS) - { - TCHAR szServerName[MAX_PATH]; - DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; + // Calculate the block parameters + BlockOffset0 = BlockOffset = ByteOffset & ~((ULONGLONG)BlockSize - 1); + BlockCount = (DWORD)(((EndOffset - BlockOffset) + (BlockSize - 1)) / BlockSize); + BytesNeeded = (DWORD)(EndOffset - BlockOffset); - // Initiate connection with the server - szFileName = BaseHttp_ExtractServerName(pStream->szFileName, szServerName); - pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, - szServerName, - INTERNET_DEFAULT_HTTP_PORT, - NULL, - NULL, - INTERNET_SERVICE_HTTP, - dwFlags, - 0); - if(pStream->Base.Http.hConnect == NULL) - nError = GetLastError(); + // Remember where we have our data + assert((BlockSize & (BlockSize - 1)) == 0); + BlockBufferOffset = (DWORD)(ByteOffset & (BlockSize - 1)); + + // Allocate buffer for reading blocks + TransferBuffer = BlockBuffer = STORM_ALLOC(BYTE, (BlockCount * BlockSize)); + if(TransferBuffer == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return false; } - // Now try to query the file size - if(nError == ERROR_SUCCESS) + // If all blocks are available, just read all blocks at once + if(pStream->IsComplete == 0) { - // Open HTTP request to the file - hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); - if(hRequest != NULL) + // Now parse the blocks and send the block read request + // to all blocks with the same availability + bPrevBlockAvailable = pStream->BlockCheck(pStream, BlockOffset); + + // Loop as long as we have something to read + while(BlockOffset < EndOffset) { - if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) + // Determine availability of the next block + bBlockAvailable = pStream->BlockCheck(pStream, BlockOffset); + + // If the availability has changed, read all blocks up to this one + if(bBlockAvailable != bPrevBlockAvailable) { - ULONGLONG FileTime = 0; - DWORD dwFileSize = 0; - DWORD dwDataSize; - DWORD dwIndex = 0; + // Load the continuous blocks with the same availability + assert(BlockOffset > BlockOffset0); + bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable); + if(!bResult) + break; + + // Move the block offset + BlockBuffer += (DWORD)(BlockOffset - BlockOffset0); + BytesNeeded -= (DWORD)(BlockOffset - BlockOffset0); + bPrevBlockAvailable = bBlockAvailable; + BlockOffset0 = BlockOffset; + } - // Check if the MPQ has Last Modified field - dwDataSize = sizeof(ULONGLONG); - if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) - pStream->FileTime = FileTime; + // Move to the block offset in the stream + BlockOffset += BlockSize; + } - // Verify if the server supports random access - dwDataSize = sizeof(DWORD); - if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) - { - if(dwFileSize != 0) - { - pStream->FileSize = dwFileSize; - pStream->FilePos = 0; - bFileAvailable = true; - } - } - } - InternetCloseHandle(hRequest); + // If there is a block(s) remaining to be read, do it + if(BlockOffset > BlockOffset0) + { + // Read the complete blocks from the file + if(BlockOffset > pStream->StreamSize) + BlockOffset = pStream->StreamSize; + bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable); } } + else + { + // Read the complete blocks from the file + if(EndOffset > pStream->StreamSize) + EndOffset = pStream->StreamSize; + bResult = pStream->BlockRead(pStream, BlockOffset, EndOffset, BlockBuffer, BytesNeeded, true); + } - // If the file is not there and is not available for random access, - // report error - if(bFileAvailable == false) + // Now copy the data to the user buffer + if(bResult) { - BaseHttp_Close(pStream); - return false; + memcpy(pvBuffer, TransferBuffer + BlockBufferOffset, dwBytesToRead); + pStream->StreamPos = ByteOffset + dwBytesToRead; } - // Fill-in entry points - pStream->BaseRead = BaseHttp_Read; - pStream->BaseClose = BaseHttp_Close; + // Free the block buffer and return + STORM_FREE(TransferBuffer); + return bResult; +} + +static bool BlockStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) +{ + *pFileSize = pStream->StreamSize; return true; +} -#else +static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) +{ + *pByteOffset = pStream->StreamPos; + return true; +} - // Not supported - SetLastError(ERROR_NOT_SUPPORTED); - pStream = pStream; - return false; +static void BlockStream_Close(TBlockStream * pStream) +{ + // Free the data map, if any + if(pStream->FileBitmap != NULL) + STORM_FREE(pStream->FileBitmap); + pStream->FileBitmap = NULL; -#endif + // Call the base class for closing the stream + pStream->BaseClose(pStream); } //----------------------------------------------------------------------------- -// Local functions - bitmap for a complete file - -#define DEFAULT_BLOCK_SIZE 0x4000 - -static bool CompleteFile_GetBmp( - TFileStream * pStream, - void * pvBitmap, - DWORD Length, - LPDWORD LengthNeeded) -{ - TFileBitmap * pBitmap = (TFileBitmap *)pvBitmap; - ULONGLONG FileSize = pStream->FileSize; - DWORD TotalLength; - DWORD BlockCount = (DWORD)(((FileSize - 1) / DEFAULT_BLOCK_SIZE) + 1); - DWORD BitmapSize = (DWORD)(((BlockCount - 1) / 8) + 1); - DWORD LastByte; - - // Calculate and give the total length - TotalLength = sizeof(TFileBitmap) + BitmapSize; - if(LengthNeeded != NULL) - *LengthNeeded = TotalLength; - - // Has the caller given enough space for storing the structure? - if(Length >= sizeof(TFileBitmap)) - { - pBitmap->StartOffset = 0; - pBitmap->EndOffset = FileSize; - pBitmap->BitmapSize = BitmapSize; - pBitmap->BlockSize = DEFAULT_BLOCK_SIZE; - pBitmap->BlockCount = BlockCount; - pBitmap->IsComplete = 1; - - // Do we have enough space to fill the bitmap as well? - if(Length >= TotalLength) - { - LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); +// File stream allocation function - // Fill the full blocks - memset(pbBitmap, 0xFF, (BlockCount / 8)); - pbBitmap += (BlockCount / 8); +static STREAM_INIT StreamBaseInit[4] = +{ + BaseFile_Init, + BaseMap_Init, + BaseHttp_Init, + BaseNone_Init +}; - // Supply the last block - if(BlockCount & 7) - { - LastByte = (1 << (BlockCount & 7)) - 1; - pbBitmap[0] = (BYTE)LastByte; - } - } +// This function allocates an empty structure for the file stream +// The stream structure is created as variable length, linear block of data +// The file name is placed after the end of the stream structure data +static TFileStream * AllocateFileStream( + const TCHAR * szFileName, + size_t StreamSize, + DWORD dwStreamFlags) +{ + TFileStream * pMaster = NULL; + TFileStream * pStream; + const TCHAR * szNextFile = szFileName; + size_t FileNameSize; - return true; + // Sanity check + assert(StreamSize != 0); + + // The caller can specify chain of files in the following form: + // C:\archive.MPQ*http://www.server.com/MPQs/archive-server.MPQ + // In that case, we use the part after "*" as master file name + while(szNextFile[0] != 0 && szNextFile[0] != _T('*')) + szNextFile++; + FileNameSize = (size_t)((szNextFile - szFileName) * sizeof(TCHAR)); + + // If we have a next file, we need to open it as master stream + // Note that we don't care if the master stream exists or not, + // If it doesn't, later attempts to read missing file block will fail + if(szNextFile[0] == _T('*')) + { + // Don't allow another master file in the string + if(_tcschr(szNextFile + 1, _T('*')) != NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; + } + + // Open the master file + pMaster = FileStream_OpenFile(szNextFile + 1, STREAM_FLAG_READ_ONLY); } - else + + // Allocate the stream structure for the given stream type + pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize + sizeof(TCHAR)); + if(pStream != NULL) { - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return false; + // Zero the entire structure + memset(pStream, 0, StreamSize); + pStream->pMaster = pMaster; + pStream->dwFlags = dwStreamFlags; + + // Initialize the file name + pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize); + memcpy(pStream->szFileName, szFileName, FileNameSize); + pStream->szFileName[FileNameSize / sizeof(TCHAR)] = 0; + + // Initialize the stream functions + StreamBaseInit[dwStreamFlags & 0x03](pStream); } + + return pStream; } //----------------------------------------------------------------------------- // Local functions - linear stream support -typedef struct _DATA_BLOCK_INFO +static DWORD LinearStream_CheckFile(TBlockStream * pStream) { - ULONGLONG BlockOffset0; // Offset of the first block in the continuous array - ULONGLONG BlockOffset; // Offset of the current block - ULONGLONG ByteOffset; // Offset of the loaded data - ULONGLONG EndOffset; // Offset of the end of the block - LPBYTE ReadBuffer; // Pointer to the buffer where to read the data - DWORD BlockSize; // Length of one block, in bytes - DWORD ByteIndex0; // Byte index of the first block in the continuous array - DWORD ByteIndex; // Index of the byte in the file bitmap - BYTE MirrorUpdated; // If set to nonzero, the mirror stream has been updated - BYTE BitMask0; // Bit mask of the first block in the continuous array - BYTE BitMask; // Bit mask of the current bit in file bitmap + LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap; + DWORD WholeByteCount = (pStream->BlockCount / 8); + DWORD ExtraBitsCount = (pStream->BlockCount & 7); + BYTE ExpectedValue; + + // Verify the whole bytes - their value must be 0xFF + for(DWORD i = 0; i < WholeByteCount; i++) + { + if(FileBitmap[i] != 0xFF) + return 0; + } -} DATA_BLOCK_INFO, *PDATA_BLOCK_INFO; + // If there are extra bits, calculate the mask + if(ExtraBitsCount != 0) + { + ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); + if(FileBitmap[WholeByteCount] != ExpectedValue) + return 0; + } -static bool LinearStream_LoadBitmap( - TLinearStream * pStream) // Pointer to an open stream + // Yes, the file is complete + return 1; +} + +static bool LinearStream_LoadBitmap(TBlockStream * pStream) { FILE_BITMAP_FOOTER Footer; - TFileBitmap * pBitmap; ULONGLONG ByteOffset; + LPBYTE FileBitmap; DWORD BlockCount; DWORD BitmapSize; - // Only if the size is greater than sizeof bitmap footer - if(pStream->FileSize > sizeof(FILE_BITMAP_FOOTER)) + // Do not load the bitmap if we should not have to + if(!(pStream->dwFlags & STREAM_FLAG_USE_BITMAP)) + return false; + + // Only if the size is greater than size of bitmap footer + if(pStream->Base.File.FileSize > sizeof(FILE_BITMAP_FOOTER)) { // Load the bitmap footer - ByteOffset = pStream->FileSize - sizeof(FILE_BITMAP_FOOTER); + ByteOffset = pStream->Base.File.FileSize - sizeof(FILE_BITMAP_FOOTER); if(pStream->StreamRead(pStream, &ByteOffset, &Footer, sizeof(FILE_BITMAP_FOOTER))) { // Make sure that the array is properly BSWAP-ed BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&Footer), sizeof(FILE_BITMAP_FOOTER)); // Verify if there is actually a footer - if(Footer.dwSignature == ID_FILE_BITMAP_FOOTER && Footer.dwAlways3 == 0x03) + if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03) { - // Get offset of the bitmap, size of the bitmap and check for match - ByteOffset = MAKE_OFFSET64(Footer.dwMapOffsetHi, Footer.dwMapOffsetLo); - BlockCount = (DWORD)(((ByteOffset - 1) / Footer.dwBlockSize) + 1); + // Get the offset of the bitmap, number of blocks and size of the bitmap + ByteOffset = MAKE_OFFSET64(Footer.MapOffsetHi, Footer.MapOffsetLo); + BlockCount = (DWORD)(((ByteOffset - 1) / Footer.BlockSize) + 1); BitmapSize = ((BlockCount + 7) / 8); // Check if the sizes match - if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->FileSize) + if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize) { // Allocate space for the linear bitmap - pBitmap = (TFileBitmap *)STORM_ALLOC(BYTE, sizeof(TFileBitmap) + BitmapSize); - if(pBitmap != NULL) + FileBitmap = STORM_ALLOC(BYTE, BitmapSize); + if(FileBitmap != NULL) { - // Fill the bitmap header - pBitmap->StartOffset = 0; - pBitmap->EndOffset = ByteOffset; - pBitmap->BitmapSize = BitmapSize; - pBitmap->BlockSize = Footer.dwBlockSize; - pBitmap->BlockCount = BlockCount; - // Load the bitmap bits - if(!pStream->BaseRead(pStream, &ByteOffset, (pBitmap + 1), BitmapSize)) + if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize)) { - STORM_FREE(pBitmap); + STORM_FREE(FileBitmap); return false; } - // Verify if the file is complete or not - pBitmap->IsComplete = FileBitmap_CheckFile(pBitmap); - - // Set the file bitmap into the file stream - pStream->FileSize = ByteOffset; - pStream->pBitmap = pBitmap; + // Update the stream size + pStream->StreamSize = ByteOffset; + + // Fill the bitmap information + pStream->FileBitmap = FileBitmap; + pStream->BitmapSize = BitmapSize; + pStream->BlockSize = Footer.BlockSize; + pStream->BlockCount = BlockCount; + pStream->IsComplete = LinearStream_CheckFile(pStream); return true; } } @@ -969,370 +1106,171 @@ static bool LinearStream_LoadBitmap( return false; } -static bool LinearStream_LoadMissingBlocks( - TLinearStream * pStream, - ULONGLONG ByteOffset, - ULONGLONG EndOffset, - void * pvBuffer) -{ - TFileBitmap * pBitmap = pStream->pBitmap; - ULONGLONG BlockSizeMask = pStream->pBitmap->BlockSize; - ULONGLONG BlockOffset = ByteOffset & ~(BlockSizeMask - 1); - ULONGLONG BlockEnd = (EndOffset + (pStream->pBitmap->BlockSize - 1)) & ~(BlockSizeMask - 1); - LPBYTE pbDataBlock; - DWORD cbBytesToCopy = (DWORD)(EndOffset - ByteOffset); - DWORD cbBlockSize = (DWORD)(BlockEnd - BlockOffset); - bool bResult = false; - - // Sanity check - assert(pStream->pBitmap != NULL); - - // Cannot load missing blocks if no master file - if(pStream->pMaster == NULL) - return false; - - // Allocate space for the file block - pbDataBlock = STORM_ALLOC(BYTE, cbBlockSize); - if(pbDataBlock != NULL) - { - // Load the entire missing block from the master MPQ - if(FileStream_Read(pStream->pMaster, &BlockOffset, pbDataBlock, cbBlockSize)) - { - // We can satisfy the read from the loaded data - assert(cbBytesToCopy <= cbBlockSize); - memcpy(pvBuffer, pbDataBlock + (DWORD)(ByteOffset - BlockOffset), cbBytesToCopy); - bResult = true; - - // Write the file block to the cached archive - if(pStream->BaseWrite(pStream, &BlockOffset, pbDataBlock, cbBlockSize)) - { - // Update the file bitmap - FileBitmap_SetRange(pStream->pBitmap, BlockOffset, BlockEnd); - - // If this fails, the data blocks will be re-downloaded next time, - // but the file is not corrupt - ByteOffset = pBitmap->EndOffset; - if(!pStream->BaseWrite(pStream, &ByteOffset, pBitmap + 1, pBitmap->BitmapSize)) - bResult = false; - } - } - - // Free the file block - STORM_FREE(pbDataBlock); - } - - return bResult; -} - -static bool LinearStream_ReadBlocks( - TLinearStream * pStream, // Pointer to an open stream - DATA_BLOCK_INFO & bi, - bool bBlocksAreAvailable) -{ - ULONGLONG EndOffset; - LPBYTE pbBlockBuffer; - DWORD BlockToRead = 0; - DWORD BytesToRead = 0; - DWORD ReadOffset; - bool bResult = true; - - // Only do something if there is at least one block to be read - if(bi.BlockOffset > bi.BlockOffset0) - { - // Get the read range - EndOffset = STORMLIB_MIN(bi.BlockOffset, bi.EndOffset); - BytesToRead = (DWORD)(EndOffset - bi.ByteOffset); - - // If the block is not available, we need to load them from the master and store to the mirror - if(bBlocksAreAvailable == false) - { - // If we have no master, we cannot satisfy read request - if(pStream->pMaster == NULL) - return false; - - // Allocate buffer and read the complete blocks - BlockToRead = (DWORD)(bi.BlockOffset - bi.BlockOffset0); - pbBlockBuffer = STORM_ALLOC(BYTE, BlockToRead); - if(pbBlockBuffer == NULL) - return false; - - // Load the block buffer from the master stream - if(FileStream_Read(pStream->pMaster, &bi.BlockOffset0, pbBlockBuffer, BlockToRead)) - { - // We can now satisfy the request - ReadOffset = (DWORD)(bi.ByteOffset - bi.BlockOffset0); - memcpy(bi.ReadBuffer, pbBlockBuffer + ReadOffset, BytesToRead); - - // Store the loaded blocks to the mirror file - if(pStream->BaseWrite(pStream, &bi.BlockOffset0, pbBlockBuffer, BlockToRead)) - bi.MirrorUpdated = 1; - } - - // Free the transfer buffer - STORM_FREE(pbBlockBuffer); - } - - // If the blocks are available, we just read them from the mirror - else - { - // Perform the file read - bResult = pStream->BaseRead(pStream, &bi.ByteOffset, bi.ReadBuffer, BytesToRead); - } - - // Move the offsets and bit masks - bi.BlockOffset0 = bi.BlockOffset; - bi.ByteOffset += BytesToRead; - bi.ReadBuffer += BytesToRead; - } - return bResult; -} - -static bool LinearStream_Read( - TLinearStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position - void * pvBuffer, // Pointer to data to be read - DWORD dwBytesToRead) // Number of bytes to read from the file +static void LinearStream_UpdateBitmap( + TBlockStream * pStream, // Pointer to an open stream + ULONGLONG StartOffset, + ULONGLONG EndOffset) { - DATA_BLOCK_INFO bi; - TFileBitmap * pBitmap = pStream->pBitmap; - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; - LPBYTE FileBitmap; + LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap; DWORD BlockIndex; - bool bPrevBlockAvailable; - bool bBlockAvailable; - - // NOP reading zero bytes - if(dwBytesToRead == 0) - return true; - - // Cannot read past the end of the file - if((ByteOffset + dwBytesToRead) > pStream->FileSize) - { - SetLastError(ERROR_HANDLE_EOF); - return false; - } + DWORD BlockSize = pStream->BlockSize; + DWORD ByteIndex; + BYTE BitMask; - // If we have no data bitmap, we assume that the file is complete. - if(pBitmap == NULL) - return pStream->BaseRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); + // Sanity checks + assert((StartOffset & (BlockSize - 1)) == 0); + assert(FileBitmap != NULL); // Calculate the index of the block - FileBitmap = (LPBYTE)(pBitmap + 1); - BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize); - - // Fill the data block info - bi.BlockOffset0 = - bi.BlockOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1); - bi.ByteOffset = ByteOffset; - bi.EndOffset = ByteOffset + dwBytesToRead; - bi.ReadBuffer = (LPBYTE)pvBuffer; - bi.BlockSize = pBitmap->BlockSize; - bi.ByteIndex0 = - bi.ByteIndex = (BlockIndex / 0x08); - bi.BitMask0 = - bi.BitMask = (BYTE)(0x01 << (BlockIndex & 0x07)); - - // Check if the current block is available - bPrevBlockAvailable = (FileBitmap[bi.ByteIndex] & bi.BitMask) ? true : false; - - // Loop as long as we have something to read - while(bi.BlockOffset < bi.EndOffset) - { - // Determine if that block is available in the mirror file - bBlockAvailable = (FileBitmap[bi.ByteIndex] & bi.BitMask) ? true : false; - - // If the availability has changed, - // reload all the previous blocks with the same availability - if(bBlockAvailable != bPrevBlockAvailable) - { - if(!LinearStream_ReadBlocks(pStream, bi, bPrevBlockAvailable)) - { - SetLastError(ERROR_CAN_NOT_COMPLETE); - return false; - } - - bPrevBlockAvailable = bBlockAvailable; - } + BlockIndex = (DWORD)(StartOffset / BlockSize); + ByteIndex = (BlockIndex / 0x08); + BitMask = (BYTE)(1 << (BlockIndex & 0x07)); - // Move to the next block in the stream - bi.BlockOffset += bi.BlockSize; - bi.ByteIndex += (bi.BitMask >> 0x07); - bi.BitMask = (bi.BitMask >> 0x07) | (bi.BitMask << 0x01); - } - - // We now need to read the last blocks that weren't loaded in the loop - if(!LinearStream_ReadBlocks(pStream, bi, bPrevBlockAvailable)) + // Set all bits for the specified range + while(StartOffset < EndOffset) { - SetLastError(ERROR_CAN_NOT_COMPLETE); - return false; - } + // Set the bit + FileBitmap[ByteIndex] |= BitMask; - // We also need to update the file bitmap in the file - if(bi.MirrorUpdated) - { - // Update all bits in the bitmap - while(bi.ByteIndex0 != bi.ByteIndex || bi.BitMask0 != bi.BitMask) - { - FileBitmap[bi.ByteIndex0] |= bi.BitMask0; - bi.ByteIndex0 += (bi.BitMask0 >> 0x07); - bi.BitMask0 = (bi.BitMask0 >> 0x07) | (bi.BitMask0 << 0x01); - } - - // Write the updated bitmap to the mirror file - pStream->BaseWrite(pStream, &pBitmap->EndOffset, FileBitmap, pBitmap->BitmapSize); + // Move all + StartOffset += BlockSize; + ByteIndex += (BitMask >> 0x07); + BitMask = (BitMask >> 0x07) | (BitMask << 0x01); } - - // Increment the position - pStream->FilePos = ByteOffset + dwBytesToRead; - return true; } -static bool LinearStream_Write(TLinearStream *, ULONGLONG *, const void *, DWORD) +static bool LinearStream_BlockCheck( + TBlockStream * pStream, // Pointer to an open stream + ULONGLONG BlockOffset) { - // Writing to linear stream with bitmap is not allowed - SetLastError(ERROR_ACCESS_DENIED); - return false; -} + LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap; + DWORD BlockIndex; + BYTE BitMask; -static bool LinearStream_GetSize(TLinearStream * pStream, ULONGLONG * pFileSize) -{ - *pFileSize = pStream->FileSize; - return true; -} + // Sanity checks + assert((BlockOffset & (pStream->BlockSize - 1)) == 0); + assert(FileBitmap != NULL); + + // Calculate the index of the block + BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize); + BitMask = (BYTE)(1 << (BlockIndex & 0x07)); -static bool LinearStream_GetTime(TLinearStream * pStream, ULONGLONG * pFileTime) -{ - *pFileTime = pStream->FileTime; - return true; + // Check if the bit is present + return (FileBitmap[BlockIndex / 0x08] & BitMask) ? true : false; } -static bool LinearStream_GetPos(TLinearStream * pStream, ULONGLONG * pByteOffset) +static bool LinearStream_BlockRead( + TBlockStream * pStream, // Pointer to an open stream + ULONGLONG StartOffset, + ULONGLONG EndOffset, + LPBYTE BlockBuffer, + DWORD BytesNeeded, + bool bAvailable) { - *pByteOffset = pStream->FilePos; - return true; -} + DWORD BytesToRead = (DWORD)(EndOffset - StartOffset); -static bool LinearStream_GetBmp( - TLinearStream * pStream, - void * pvBitmap, - DWORD Length, - LPDWORD LengthNeeded) -{ - DWORD TotalLength; - DWORD CopyLength = sizeof(TFileBitmap); + // The starting offset must be aligned to size of the block + assert(pStream->FileBitmap != NULL && pStream->IsComplete == 0); + assert((StartOffset & (pStream->BlockSize - 1)) == 0); + assert(StartOffset < EndOffset); + + // If the blocks are not available, we need to load them from the master + // and then save to the mirror + if(bAvailable == false) + { + // If we have no master, we cannot satisfy read request + if(pStream->pMaster == NULL) + return false; - // Assumed that we have bitmap now - assert(pStream->pBitmap != NULL); + // Load the blocks from the master stream + // Note that we always have to read complete blocks + // so they get properly stored to the mirror stream + if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead)) + return false; - // Give the bitmap length - TotalLength = sizeof(TFileBitmap) + pStream->pBitmap->BitmapSize; - if(LengthNeeded != NULL) - *LengthNeeded = TotalLength; + // Store the loaded blocks to the mirror file. + // Note that this operation is not required to succeed + if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead) && bAvailable == false) + LinearStream_UpdateBitmap(pStream, StartOffset, EndOffset); - // Do we have enough space to fill at least the bitmap structure? - if(Length >= sizeof(TFileBitmap)) - { - // Enough space for the complete bitmap? - if(Length >= TotalLength) - CopyLength = TotalLength; - memcpy(pvBitmap, pStream->pBitmap, CopyLength); return true; } else { - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return false; + if(BytesToRead > BytesNeeded) + BytesToRead = BytesNeeded; + return pStream->BaseRead(pStream, &StartOffset, BlockBuffer, BytesToRead); } } -static bool LinearStream_Switch(TLinearStream * pStream, TLinearStream * pNewStream) +static bool LinearStream_CreateMirror(TBlockStream * pStream) { - // Sanity checks - assert((pNewStream->dwFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_LINEAR); - assert((pNewStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_FILE); - assert((pStream->dwFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_LINEAR); - assert((pStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_FILE); - - // Close the new stream - pNewStream->BaseClose(pNewStream); - - // Close the source stream - pStream->BaseClose(pStream); - - // Rename the new data source file to the existing file - if(!BaseFile_Switch(pStream, pNewStream)) - return false; - - // Now we have to open the "pStream" again - if(!BaseFile_Open(pStream, pStream->dwFlags)) + // Do we have master function and base creation function? + if(pStream->pMaster == NULL || pStream->BaseCreate == NULL) return false; - // We need to cleanup the new data stream - FileStream_Close(pNewStream); - return true; + return false; } -static void LinearStream_Close(TLinearStream * pStream) +static TFileStream * LinearStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { - // Free the souce stream, if any - if(pStream->pMaster != NULL) - FileStream_Close(pStream->pMaster); - pStream->pMaster = NULL; + TBlockStream * pStream; - // Free the data map, if any - if(pStream->pBitmap != NULL) - STORM_FREE(pStream->pBitmap); - pStream->pBitmap = NULL; + // Create new empty stream + pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); + if(pStream == NULL) + return NULL; - // Call the base class for closing the stream - pStream->BaseClose(pStream); -} + // Attempt to open the base stream. If this fails + // and we have a master stream, we can create new mirror + assert(pStream->BaseOpen != NULL); + if(!pStream->BaseOpen(pStream, dwStreamFlags)) + { + // Do we have base create function and master stream? +// if(!LinearStream_CreateMirror(pStream)) + { + FileStream_Close(pStream); + SetLastError(ERROR_FILE_NOT_FOUND); + return NULL; + } + } -static bool LinearStream_Open(TLinearStream * pStream) -{ - // Set the entry points + // Set the stream function as if this was linear file without bitmap pStream->StreamRead = pStream->BaseRead; pStream->StreamWrite = pStream->BaseWrite; - pStream->StreamSetSize = pStream->BaseSetSize; - pStream->StreamGetSize = (STREAM_GETSIZE)LinearStream_GetSize; - pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime; - pStream->StreamGetPos = (STREAM_GETPOS)LinearStream_GetPos; - pStream->StreamGetBmp = (STREAM_GETBMP)CompleteFile_GetBmp; - pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch; - pStream->StreamClose = (STREAM_CLOSE)LinearStream_Close; - - // If the caller wanted us to load stream bitmap, do it - if(pStream->dwFlags & STREAM_FLAG_USE_BITMAP) + pStream->StreamResize = pStream->BaseResize; + pStream->StreamGetSize = pStream->BaseGetSize; + pStream->StreamGetPos = pStream->BaseGetPos; + pStream->StreamClose = pStream->BaseClose; + + // Setup the stream size + pStream->StreamSize = pStream->Base.File.FileSize; + + // If we load the bitmap and find out that it's an incomplete file, + // We set the reading functions which check presence of each file block + if(LinearStream_LoadBitmap(pStream)) { - // Attempt to load the file bitmap - LinearStream_LoadBitmap(pStream); + // Set the stream position to zero. Stream size is already set + assert(pStream->StreamSize != 0); + pStream->StreamPos = 0; + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - // Reset the position to zero after manipulating with file pointer - pStream->FilePos = 0; - - // If there is a file bitmap and the file is not complete, - // we need to set the reading function so that it verifies each block - if(pStream->pBitmap != NULL) + // Set the reading function + if(pStream->IsComplete == 0) { - // Set different function for retrieving file bitmap - pStream->StreamGetBmp = (STREAM_GETBMP)LinearStream_GetBmp; - - // If the file is not complete, we also need to set different function for file read+write - if(pStream->pBitmap->IsComplete == 0) - { - // If we also have source file, open that one - if(pStream->szSourceName != NULL) - pStream->pMaster = FileStream_OpenFile(pStream->szSourceName, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP); - - // Change functions for read+write - pStream->StreamWrite = (STREAM_WRITE)LinearStream_Write; - pStream->StreamRead = (STREAM_READ)LinearStream_Read; - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - } + // Supply the reading function + pStream->StreamRead = (STREAM_READ)BlockStream_Read; + pStream->StreamGetSize = BlockStream_GetSize; + pStream->StreamGetPos = BlockStream_GetPos; + pStream->StreamClose = (STREAM_CLOSE)BlockStream_Close; + + // Supply the block functions + pStream->BlockCheck = (BLOCK_CHECK)LinearStream_BlockCheck; + pStream->BlockRead = (BLOCK_READ)LinearStream_BlockRead; } } - return true; + return pStream; } //----------------------------------------------------------------------------- @@ -1355,260 +1293,188 @@ static bool IsPartHeader(PPART_FILE_HEADER pPartHdr) return false; } -static bool PartialStream_Read( - TPartialStream * pStream, - ULONGLONG * pByteOffset, - void * pvBuffer, - DWORD dwBytesToRead) +static bool PartialStream_LoadBitmap(TBlockStream * pStream) { - ULONGLONG RawByteOffset; - LPBYTE pbBuffer = (LPBYTE)pvBuffer; - DWORD dwBytesRemaining = dwBytesToRead; - DWORD dwPartOffset; - DWORD dwPartIndex; - DWORD dwBytesRead = 0; - DWORD dwBlockSize = pStream->BlockSize; - int nFailReason = ERROR_HANDLE_EOF; // Why it failed if not enough bytes was read - - // If the byte offset is not entered, use the current position - if(pByteOffset == NULL) - pByteOffset = &pStream->VirtualPos; - - // Check if the file position is not at or beyond end of the file - if(*pByteOffset >= pStream->VirtualSize) - { - SetLastError(ERROR_HANDLE_EOF); - return false; - } - - // Get the part index where the read offset is - // Note that the part index should now be within the range, - // as read requests beyond-EOF are handled by the previous test - dwPartIndex = (DWORD)(*pByteOffset / pStream->BlockSize); - assert(dwPartIndex < pStream->BlockCount); - - // If the number of bytes remaining goes past - // the end of the file, cut them - if((*pByteOffset + dwBytesRemaining) > pStream->VirtualSize) - dwBytesRemaining = (DWORD)(pStream->VirtualSize - *pByteOffset); - - // Calculate the offset in the current part - dwPartOffset = (DWORD)(*pByteOffset) & (pStream->BlockSize - 1); + PPART_FILE_MAP_ENTRY FileBitmap; + PART_FILE_HEADER PartHdr; + ULONGLONG ByteOffset = 0; + ULONGLONG StreamSize = 0; + DWORD BlockCount; + DWORD BitmapSize; - // Read all data, one part at a time - while(dwBytesRemaining != 0) + // Only if the size is greater than size of the bitmap header + if(pStream->Base.File.FileSize > sizeof(PART_FILE_HEADER)) { - PPART_FILE_MAP_ENTRY PartMap = pStream->PartMap + dwPartIndex; - DWORD dwBytesInPart; - - // If the part is not present in the file, we fail the read - if((PartMap->Flags & 3) == 0) + // Attempt to read PART file header + if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER))) { - nFailReason = ERROR_FILE_CORRUPT; - break; - } - - // If we are in the last part, we have to cut the number of bytes in the last part - if(dwPartIndex == pStream->BlockCount - 1) - dwBlockSize = (DWORD)pStream->VirtualSize & (pStream->BlockSize - 1); + // We need to swap PART file header on big-endian platforms + BSWAP_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER)); - // Get the number of bytes reamining in the current part - dwBytesInPart = dwBlockSize - dwPartOffset; + // Verify the PART file header + if(IsPartHeader(&PartHdr)) + { + // Get the number of blocks and size of one block + StreamSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo); + ByteOffset = sizeof(PART_FILE_HEADER); + BlockCount = (DWORD)((StreamSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize); + BitmapSize = BlockCount * sizeof(PART_FILE_MAP_ENTRY); + + // Check if sizes match + if((ByteOffset + BitmapSize) < pStream->Base.File.FileSize) + { + // Allocate space for the array of PART_FILE_MAP_ENTRY + FileBitmap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount); + if(FileBitmap != NULL) + { + // Load the block map + if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize)) + { + STORM_FREE(FileBitmap); + return false; + } - // Compute the raw file offset of the file part - RawByteOffset = MAKE_OFFSET64(PartMap->BlockOffsHi, PartMap->BlockOffsLo); - if(RawByteOffset == 0) - { - nFailReason = ERROR_FILE_CORRUPT; - break; - } + // Make sure that the byte order is correct + BSWAP_ARRAY32_UNSIGNED(FileBitmap, BitmapSize); - // If the number of bytes in part is too big, cut it - if(dwBytesInPart > dwBytesRemaining) - dwBytesInPart = dwBytesRemaining; + // Update the stream size + pStream->StreamSize = StreamSize; - // Append the offset within the part - RawByteOffset += dwPartOffset; - if(!pStream->BaseRead(pStream, &RawByteOffset, pbBuffer, dwBytesInPart)) - { - nFailReason = ERROR_FILE_CORRUPT; - break; + // Fill the bitmap information + pStream->FileBitmap = FileBitmap; + pStream->BitmapSize = BitmapSize; + pStream->BlockSize = PartHdr.BlockSize; + pStream->BlockCount = BlockCount; + pStream->IsComplete = 0; + return true; + } + } + } } - - // Increment the file position - dwBytesRemaining -= dwBytesInPart; - dwBytesRead += dwBytesInPart; - pbBuffer += dwBytesInPart; - - // Move to the next file part - dwPartOffset = 0; - dwPartIndex++; } - // Move the file position by the number of bytes read - pStream->VirtualPos = *pByteOffset + dwBytesRead; - if(dwBytesRead != dwBytesToRead) - SetLastError(nFailReason); - return (dwBytesRead == dwBytesToRead); + return false; } -static bool PartialStream_GetPos( - TPartialStream * pStream, - ULONGLONG & ByteOffset) +static bool PartialStream_BlockCheck( + TBlockStream * pStream, // Pointer to an open stream + ULONGLONG BlockOffset) { - ByteOffset = pStream->VirtualPos; - return true; -} + PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap; + DWORD BlockIndex; -static bool PartialStream_GetSize( - TPartialStream * pStream, // Pointer to an open stream - ULONGLONG & FileSize) // Pointer where to store file size -{ - FileSize = pStream->VirtualSize; - return true; + // Sanity checks + assert((BlockOffset & (pStream->BlockSize - 1)) == 0); + assert(FileBitmap != NULL); + + // Calculate the index of the block + BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize); + + // Check if the flags are present + return (FileBitmap[BlockIndex].Flags & 0x03) ? true : false; } -static bool PartialStream_GetBitmap( - TPartialStream * pStream, - TFileBitmap * pBitmap, - DWORD Length, - LPDWORD LengthNeeded) +static bool PartialStream_BlockRead( + TBlockStream * pStream, + ULONGLONG StartOffset, + ULONGLONG EndOffset, + LPBYTE BlockBuffer, + DWORD BytesNeeded, + bool bAvailable) { - LPBYTE pbBitmap; - DWORD TotalLength; - DWORD BitmapSize = 0; - DWORD ByteOffset; - DWORD BitMask; - bool bResult = false; + PPART_FILE_MAP_ENTRY FileBitmap; + ULONGLONG ByteOffset; + DWORD BytesToRead; + DWORD BlockIndex = (DWORD)(StartOffset / pStream->BlockSize); - // Do we have stream bitmap? - BitmapSize = ((pStream->BlockCount - 1) / 8) + 1; + // The starting offset must be aligned to size of the block + assert(pStream->FileBitmap != NULL); + assert((StartOffset & (pStream->BlockSize - 1)) == 0); + assert(StartOffset < EndOffset); - // Give the bitmap length - TotalLength = sizeof(TFileBitmap) + BitmapSize; - if(LengthNeeded != NULL) - *LengthNeeded = TotalLength; + // Get the file map entry + FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex; - // Do we have enough to fill at least the header? - if(Length >= sizeof(TFileBitmap)) + // If the blocks are not available, we need to load them from the master + // and then save to the mirror + if(bAvailable == false) { - // Fill the bitmap header - pBitmap->StartOffset = 0; - pBitmap->EndOffset = pStream->VirtualSize; - pBitmap->BitmapSize = BitmapSize; - pBitmap->BlockSize = pStream->BlockSize; - pBitmap->BlockCount = pStream->BlockCount; - pBitmap->IsComplete = 1; - - // Is there at least one incomplete block? - for(DWORD i = 0; i < pStream->BlockCount; i++) - { - if(pStream->PartMap[i].Flags != 3) - { - pBitmap->IsComplete = 0; - break; - } - } - - bResult = true; + // TODO: Support for downloading blocks + return false; } - - // Do we have enough space for supplying the bitmap? - if(Length >= TotalLength) + else { - // Fill the file bitmap - pbBitmap = (LPBYTE)(pBitmap + 1); - for(DWORD i = 0; i < pStream->BlockCount; i++) + while(StartOffset < EndOffset) { - // Is the block there? - if(pStream->PartMap[i].Flags == 3) - { - ByteOffset = i / 8; - BitMask = 1 << (i & 7); - pbBitmap[ByteOffset] |= BitMask; - } + // Get the number of bytes to be read + BytesToRead = (DWORD)(EndOffset - StartOffset); + if(BytesToRead > pStream->BlockSize) + BytesToRead = pStream->BlockSize; + if(BytesToRead > BytesNeeded) + BytesToRead = BytesNeeded; + + // Read the block + ByteOffset = MAKE_OFFSET64(FileBitmap->BlockOffsHi, FileBitmap->BlockOffsLo); + if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead)) + return false; + + // Move the pointers + StartOffset += pStream->BlockSize; + BlockBuffer += pStream->BlockSize; + BytesNeeded -= pStream->BlockSize; } - bResult = true; } - return bResult; + return true; } -static void PartialStream_Close(TPartialStream * pStream) +static TFileStream * PartialStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { - // Free the part map - if(pStream->PartMap != NULL) - STORM_FREE(pStream->PartMap); - pStream->PartMap = NULL; - - // Clear variables - pStream->VirtualSize = 0; - pStream->VirtualPos = 0; + TBlockStream * pStream; - // Close the base stream - assert(pStream->BaseClose != NULL); - pStream->BaseClose(pStream); -} - -static bool PartialStream_Open(TPartialStream * pStream) -{ - PART_FILE_HEADER PartHdr; - ULONGLONG VirtualSize; // Size of the file stored in part file - ULONGLONG ByteOffset = {0}; - DWORD BlockCount; + // Create new empty stream + pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); + if(pStream == NULL) + return NULL; - // Sanity check - assert(pStream->BaseRead != NULL); + // Attempt to open the base stream. If this fails + // and we have a master stream, we can create new mirror + assert(pStream->BaseOpen != NULL); + if(!pStream->BaseOpen(pStream, dwStreamFlags)) + { + // Do we have base create function and master stream? +// if(!PartialStream_CreateMirror(pStream)) + { + FileStream_Close(pStream); + SetLastError(ERROR_FILE_NOT_FOUND); + return NULL; + } + } - // Attempt to read PART file header - if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER))) + // Load the stream bitmap + if(PartialStream_LoadBitmap(pStream)) { - // We need to swap PART file header on big-endian platforms - BSWAP_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER)); + // Set the stream position to zero. Stream size is already set + assert(pStream->StreamSize != 0); + pStream->StreamPos = 0; + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - // Verify the PART file header - if(IsPartHeader(&PartHdr)) - { - // Calculate the number of parts in the file - VirtualSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo); - assert(VirtualSize != 0); - BlockCount = (DWORD)((VirtualSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize); - - // Allocate the map entry array - pStream->PartMap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount); - if(pStream->PartMap != NULL) - { - // Load the block map - if(pStream->BaseRead(pStream, NULL, pStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY))) - { - // Swap the array of file map entries - BSWAP_ARRAY32_UNSIGNED(pStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY)); - - // Fill the members of PART file stream - pStream->VirtualSize = ((ULONGLONG)PartHdr.FileSizeHi) + PartHdr.FileSizeLo; - pStream->VirtualPos = 0; - pStream->BlockCount = BlockCount; - pStream->BlockSize = PartHdr.BlockSize; - - // Set new function pointers - pStream->StreamRead = (STREAM_READ)PartialStream_Read; - pStream->StreamGetPos = (STREAM_GETPOS)PartialStream_GetPos; - pStream->StreamGetSize = (STREAM_GETSIZE)PartialStream_GetSize; - pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime; - pStream->StreamGetBmp = (STREAM_GETBMP)PartialStream_GetBitmap; - pStream->StreamClose = (STREAM_CLOSE)PartialStream_Close; - return true; - } + // Set new function pointers + pStream->StreamRead = (STREAM_READ)BlockStream_Read; + pStream->StreamGetPos = BlockStream_GetPos; + pStream->StreamGetSize = BlockStream_GetSize; + pStream->StreamClose = (STREAM_CLOSE)BlockStream_Close; - // Free the part map - STORM_FREE(pStream->PartMap); - pStream->PartMap = NULL; - } - } + // Supply the block functions + pStream->BlockCheck = (BLOCK_CHECK)PartialStream_BlockCheck; + pStream->BlockRead = (BLOCK_READ)PartialStream_BlockRead; + return pStream; } + // Cleanup the stream and return + FileStream_Close(pStream); SetLastError(ERROR_BAD_FORMAT); - return false; + return NULL; } //----------------------------------------------------------------------------- @@ -1806,32 +1672,79 @@ static void DecryptFileChunk( } } -static bool DetectFileKey(LPBYTE pbKeyBuffer, LPBYTE pbEncryptedHeader) +static bool EncryptedStream_DetectFileKey(TEncryptedStream * pStream) { ULONGLONG ByteOffset = 0; + BYTE EncryptedHeader[MPQE_CHUNK_SIZE]; BYTE FileHeader[MPQE_CHUNK_SIZE]; - // We just try all known keys one by one - for(int i = 0; AuthCodeArray[i] != NULL; i++) + // Read the first file chunk + if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader))) { - // Prepare they decryption key from game serial number - CreateKeyFromAuthCode(pbKeyBuffer, AuthCodeArray[i]); - - // Try to decrypt with the given key - memcpy(FileHeader, pbEncryptedHeader, MPQE_CHUNK_SIZE); - DecryptFileChunk((LPDWORD)FileHeader, pbKeyBuffer, ByteOffset, MPQE_CHUNK_SIZE); - - // We check the decrypted data - // All known encrypted MPQs have header at the begin of the file, - // so we check for MPQ signature there. - if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q') - return true; + // We just try all known keys one by one + for(int i = 0; AuthCodeArray[i] != NULL; i++) + { + // Prepare they decryption key from game serial number + CreateKeyFromAuthCode(pStream->Key, AuthCodeArray[i]); + + // Try to decrypt with the given key + memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE); + DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE); + + // We check the decrypted data + // All known encrypted MPQs have header at the begin of the file, + // so we check for MPQ signature there. + if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q') + { + // Update the stream size + pStream->StreamSize = pStream->Base.File.FileSize; + + // Fill the block information + pStream->BlockSize = MPQE_CHUNK_SIZE; + pStream->BlockCount = (DWORD)(pStream->Base.File.FileSize + MPQE_CHUNK_SIZE - 1) / MPQE_CHUNK_SIZE; + pStream->IsComplete = 1; + return true; + } + } } // Key not found, sorry return false; } +static bool EncryptedStream_BlockCheck(TEncryptedStream *, ULONGLONG) +{ + return true; +} + +static bool EncryptedStream_BlockRead( + TEncryptedStream * pStream, + ULONGLONG StartOffset, + ULONGLONG EndOffset, + LPBYTE BlockBuffer, + DWORD BytesNeeded, + bool bAvailable) +{ + DWORD dwBytesToRead; + + assert((StartOffset & (pStream->BlockSize - 1)) == 0); + assert(StartOffset < EndOffset); + assert(bAvailable != false); + BytesNeeded = BytesNeeded; + + // Read the file from the stream as-is + // Limit the reading to number of blocks really needed + dwBytesToRead = (DWORD)(EndOffset - StartOffset); + if(!pStream->BaseRead(pStream, &StartOffset, BlockBuffer, dwBytesToRead)) + return false; + + // Decrypt the data + dwBytesToRead = (dwBytesToRead + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1); + DecryptFileChunk((LPDWORD)BlockBuffer, pStream->Key, StartOffset, dwBytesToRead); + return true; +} + +/* static bool EncryptedStream_Read( TEncryptedStream * pStream, // Pointer to an open stream ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position @@ -1848,7 +1761,7 @@ static bool EncryptedStream_Read( bool bResult = false; // Get the byte offset - ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; + ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->StreamPos; // Cut it down to MPQE chunk size StartOffset = ByteOffset; @@ -1857,7 +1770,7 @@ static bool EncryptedStream_Read( // Calculate number of bytes to decrypt dwBytesToDecrypt = (DWORD)(EndOffset - StartOffset); - dwBytesToAllocate = (dwBytesToDecrypt + (MPQE_CHUNK_SIZE - 1)) & ~(MPQE_CHUNK_SIZE - 1); + dwBytesToAllocate = (dwBytesToDecrypt + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1); // Allocate buffers for encrypted and decrypted data pbMpqData = STORM_ALLOC(BYTE, dwBytesToAllocate); @@ -1885,136 +1798,50 @@ static bool EncryptedStream_Read( STORM_FREE(pbMpqData); } - // Free buffers and exit + // Update stream position + pStream->StreamPos = ByteOffset + dwBytesToRead; return bResult; } +*/ -static bool EncryptedStream_Open(TEncryptedStream * pStream) -{ - ULONGLONG ByteOffset = 0; - BYTE EncryptedHeader[MPQE_CHUNK_SIZE]; - - // Sanity check - assert(pStream->BaseRead != NULL); - - // Load one MPQE chunk and try to detect the file key - if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader))) - { - // Attempt to decrypt the MPQ header with all known keys - if(DetectFileKey(pStream->Key, EncryptedHeader)) - { - // Assign functions - pStream->StreamRead = (STREAM_READ)EncryptedStream_Read; - pStream->StreamGetSize = (STREAM_GETSIZE)LinearStream_GetSize; - pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime; - pStream->StreamGetPos = (STREAM_GETPOS)LinearStream_GetPos; - pStream->StreamGetBmp = (STREAM_GETBMP)CompleteFile_GetBmp; - pStream->StreamClose = pStream->BaseClose; - - // We need to reset the position back to the begin of the file - pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, 0); - return true; - } - - // An unknown key - SetLastError(ERROR_UNKNOWN_FILE_KEY); - } - return false; -} - -//----------------------------------------------------------------------------- -// File stream allocation function - -/** - * This function allocates an empty structure for the file stream - * The stream structure is created as variable length, linear block of data - * The file name is placed after the end of the stream structure data - * - * \a szFileName Name of the file - * \a dwStreamFlags Stream flags telling what kind of stream structure to create - */ - -static TFileStream * AllocateFileStream( - const TCHAR * szFileName, - DWORD dwStreamFlags) +static TFileStream * EncryptedStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { - TFileStream * pStream; - TCHAR * szSourceName; - size_t FileNameSize = (_tcslen(szFileName) + 1) * sizeof(TCHAR); - size_t StreamSize = 0; - DWORD dwStreamProvider = dwStreamFlags & STREAM_PROVIDER_MASK; - DWORD dwBaseProvider = dwStreamFlags & BASE_PROVIDER_MASK; + TEncryptedStream * pStream; - // The "file:" prefix forces the BASE_PROVIDER_FILE - if(!_tcsicmp(szFileName, _T("file:"))) - { - dwBaseProvider = BASE_PROVIDER_FILE; - szFileName += 5; - } - - // The "map:" prefix forces the BASE_PROVIDER_MAP - if(!_tcsicmp(szFileName, _T("map:"))) - { - dwBaseProvider = BASE_PROVIDER_MAP; - szFileName += 4; - } - - // The "http:" prefix forces the BASE_PROVIDER_HTTP - if(!_tcsicmp(szFileName, _T("http:"))) - { - dwBaseProvider = BASE_PROVIDER_HTTP; - szFileName += 5; - } + // Create new empty stream + pStream = (TEncryptedStream *)AllocateFileStream(szFileName, sizeof(TEncryptedStream), dwStreamFlags); + if(pStream == NULL) + return NULL; - // Re-create the stream flags - dwStreamFlags = (dwStreamFlags & STREAM_FLAG_MASK) | dwStreamProvider | dwBaseProvider; + // Attempt to open the base stream + assert(pStream->BaseOpen != NULL); + if(!pStream->BaseOpen(pStream, dwStreamFlags)) + return NULL; - // Allocate file stream for each stream provider - switch(dwStreamFlags & STREAM_PROVIDER_MASK) + // Determine the encryption key for the MPQ + if(EncryptedStream_DetectFileKey(pStream)) { - case STREAM_PROVIDER_LINEAR: // Allocate structure for linear stream - StreamSize = sizeof(TLinearStream); - break; - - case STREAM_PROVIDER_PARTIAL: - dwStreamFlags |= STREAM_FLAG_READ_ONLY; - StreamSize = sizeof(TPartialStream); - break; + // Set the stream position and size + assert(pStream->StreamSize != 0); + pStream->StreamPos = 0; + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - case STREAM_PROVIDER_ENCRYPTED: - dwStreamFlags |= STREAM_FLAG_READ_ONLY; - StreamSize = sizeof(TEncryptedStream); - break; + // Set new function pointers + pStream->StreamRead = (STREAM_READ)BlockStream_Read; + pStream->StreamGetPos = BlockStream_GetPos; + pStream->StreamGetSize = BlockStream_GetSize; + pStream->StreamClose = pStream->BaseClose; - default: - return NULL; + // Supply the block functions + pStream->BlockCheck = (BLOCK_CHECK)EncryptedStream_BlockCheck; + pStream->BlockRead = (BLOCK_READ)EncryptedStream_BlockRead; + return pStream; } - // Allocate the stream structure for the given stream type - pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize); - if(pStream == NULL) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return NULL; - } - - // Fill the stream structure with zeros - memset(pStream, 0, StreamSize); - - // Remember the file name - pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize); - pStream->dwFlags = dwStreamFlags; - memcpy(pStream->szFileName, szFileName, FileNameSize); - - // If we have source file name, setup it as well - szSourceName = _tcschr(pStream->szFileName, _T('*')); - if(szSourceName != NULL) - { - // Remember the source name and cut these two file names - pStream->szSourceName = szSourceName + 1; - szSourceName[0] = 0; - } - return pStream; + // Cleanup the stream and return + FileStream_Close(pStream); + SetLastError(ERROR_UNKNOWN_FILE_KEY); + return NULL; } //----------------------------------------------------------------------------- @@ -2044,14 +1871,14 @@ TFileStream * FileStream_CreateFile( TFileStream * pStream; // We only support creation of linear, local file - if((dwStreamFlags & (STREAM_PROVIDER_MASK | BASE_PROVIDER_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE)) + if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE)) { SetLastError(ERROR_NOT_SUPPORTED); return NULL; } // Allocate file stream structure for linear stream - pStream = AllocateFileStream(szFileName, dwStreamFlags); + pStream = AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); if(pStream != NULL) { // Attempt to create the disk file @@ -2060,12 +1887,9 @@ TFileStream * FileStream_CreateFile( // Fill the stream provider functions pStream->StreamRead = pStream->BaseRead; pStream->StreamWrite = pStream->BaseWrite; - pStream->StreamSetSize = pStream->BaseSetSize; - pStream->StreamGetSize = (STREAM_GETSIZE)LinearStream_GetSize; - pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime; - pStream->StreamGetPos = (STREAM_GETPOS)LinearStream_GetPos; - pStream->StreamGetBmp = (STREAM_GETBMP)CompleteFile_GetBmp; - pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch; + pStream->StreamResize = pStream->BaseResize; + pStream->StreamGetSize = pStream->BaseGetSize; + pStream->StreamGetPos = pStream->BaseGetPos; pStream->StreamClose = pStream->BaseClose; return pStream; } @@ -2087,8 +1911,6 @@ TFileStream * FileStream_CreateFile( * - If the file does not exist, the function must return NULL * - If the file exists but cannot be open, then function must return NULL * - The parameters of the function must be validate by the caller - * - The function must check if the file is a PART file, - * and create TPartialStream object if so. * - The function must initialize all stream function pointers in TFileStream * - If the function fails from any reason, it must close all handles * and free all memory that has been allocated in the process of stream creation, @@ -2102,83 +1924,46 @@ TFileStream * FileStream_OpenFile( const TCHAR * szFileName, DWORD dwStreamFlags) { - TFileStream * pStream = NULL; - DWORD dwClearMask = STREAM_OPTIONS_MASK; - bool bStreamResult = false; - bool bBaseResult = false; - - // Allocate the stream of the given type - pStream = AllocateFileStream(szFileName, dwStreamFlags); - if(pStream == NULL) - return NULL; + DWORD dwBaseProvider = dwStreamFlags & BASE_PROVIDER_MASK; - // Few special checks when we want the stream to be cached from another source, - if(pStream->szSourceName != NULL) + // The "file:" prefix forces the BASE_PROVIDER_FILE + if(!_tcsicmp(szFileName, _T("file:"))) { - // We don't allow other base types than BASE_PROVIDER_FILE - if((pStream->dwFlags & BASE_PROVIDER_MASK) != BASE_PROVIDER_FILE) - { - SetLastError(ERROR_INVALID_PARAMETER); - STORM_FREE(pStream); - return NULL; - } - - // Clear the STREAM_FLAG_READ_ONLY flag for the base file when being open - // as local cache of a remote file - dwClearMask &= ~STREAM_FLAG_READ_ONLY; + dwBaseProvider = BASE_PROVIDER_FILE; + szFileName += 5; } - - // Now initialize the respective base provider - switch(pStream->dwFlags & BASE_PROVIDER_MASK) + + // The "map:" prefix forces the BASE_PROVIDER_MAP + if(!_tcsicmp(szFileName, _T("map:"))) { - case BASE_PROVIDER_FILE: - bBaseResult = BaseFile_Open(pStream, pStream->dwFlags & dwClearMask); - break; - - case BASE_PROVIDER_MAP: - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - bBaseResult = BaseMap_Open(pStream); - break; - - case BASE_PROVIDER_HTTP: - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - bBaseResult = BaseHttp_Open(pStream); - break; + dwBaseProvider = BASE_PROVIDER_MAP; + szFileName += 4; } - - // If we failed to open the base storage, fail the operation - if(bBaseResult == false) + + // The "http:" prefix forces the BASE_PROVIDER_HTTP + if(!_tcsicmp(szFileName, _T("http:"))) { - STORM_FREE(pStream); - return NULL; + dwBaseProvider = BASE_PROVIDER_HTTP; + szFileName += 5; } - // Now initialize the stream provider - switch(pStream->dwFlags & STREAM_PROVIDER_MASK) + // Re-assemble the stream flags + dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | (dwStreamFlags & STREAM_PROVIDER_MASK) | dwBaseProvider; + switch(dwStreamFlags & STREAM_PROVIDER_MASK) { case STREAM_PROVIDER_LINEAR: - bStreamResult = LinearStream_Open((TLinearStream *)pStream); - break; + return LinearStream_Open(szFileName, dwStreamFlags); case STREAM_PROVIDER_PARTIAL: - bStreamResult = PartialStream_Open((TPartialStream *)pStream); - break; + return PartialStream_Open(szFileName, dwStreamFlags); case STREAM_PROVIDER_ENCRYPTED: - bStreamResult = EncryptedStream_Open((TEncryptedStream *)pStream); - break; - } + return EncryptedStream_Open(szFileName, dwStreamFlags); - // If the operation failed, free the stream and set it to NULL - if(bStreamResult == false) - { - // Only close the base stream - pStream->BaseClose(pStream); - STORM_FREE(pStream); - pStream = NULL; + default: + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; } - - return pStream; } /** @@ -2221,24 +2006,16 @@ bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBu bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) { if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) + { + SetLastError(ERROR_ACCESS_DENIED); return false; + } assert(pStream->StreamWrite != NULL); return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite); } /** - * This function returns the current file position - * \a pStream - * \a ByteOffset - */ -bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) -{ - assert(pStream->StreamGetPos != NULL); - return pStream->StreamGetPos(pStream, pByteOffset); -} - -/** * Returns the size of a file * * \a pStream Pointer to an open stream @@ -2259,10 +2036,24 @@ bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) { if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) + { + SetLastError(ERROR_ACCESS_DENIED); return false; + } + + assert(pStream->StreamResize != NULL); + return pStream->StreamResize(pStream, NewFileSize); +} - assert(pStream->StreamSetSize != NULL); - return pStream->StreamSetSize(pStream, NewFileSize); +/** + * This function returns the current file position + * \a pStream + * \a pByteOffset + */ +bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) +{ + assert(pStream->StreamGetPos != NULL); + return pStream->StreamGetPos(pStream, pByteOffset); } /** @@ -2273,8 +2064,9 @@ bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) */ bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) { - assert(pStream->StreamGetTime != NULL); - return pStream->StreamGetTime(pStream, pFileTime); + // Just use the saved filetime value + *pFileTime = pStream->Base.File.FileTime; + return true; } /** @@ -2298,15 +2090,39 @@ bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags) * 3) Opens the MPQ stores the handle and stream position to the new stream structure * * \a pStream Pointer to an open stream - * \a pTempStream Temporary ("working") stream (created during archive compacting) + * \a pNewStream Temporary ("working") stream (created during archive compacting) */ -bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream) +bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream) { + // Only supported on linear files + if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE)) + { + SetLastError(ERROR_NOT_SUPPORTED); + return false; + } + + // Not supported on read-only streams if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) + { + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + + // Close both stream's base providers + pNewStream->BaseClose(pNewStream); + pStream->BaseClose(pStream); + + // Now we have to delete the (now closed) old file and rename the new file + if(!BaseFile_Replace(pStream, pNewStream)) + return false; + + // Now open the base file again + if(BaseFile_Open(pStream, pStream->dwFlags)) return false; - assert(pStream->StreamSwitch != NULL); - return pStream->StreamSwitch(pStream, pNewStream); + // Cleanup the new stream + FileStream_Close(pNewStream); + return true; } /** @@ -2331,23 +2147,6 @@ bool FileStream_IsReadOnly(TFileStream * pStream) } /** - * This function retrieves the file bitmap. A file bitmap is an array - * of bits, each bit representing one file block. A value of 1 means - * that the block is present in the file, a value of 0 means that the - * block is not present. - * - * \a pStream Pointer to an open stream - * \a pBitmap Pointer to buffer where to store the file bitmap - * \a Length Size of buffer pointed by pBitmap, in bytes - * \a LengthNeeded If non-NULL, the function supplies the necessary byte size of the buffer - */ -bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD Length, LPDWORD LengthNeeded) -{ - assert(pStream->StreamGetBmp != NULL); - return pStream->StreamGetBmp(pStream, pvBitmap, Length, LengthNeeded); -} - -/** * This function closes an archive file and frees any data buffers * that have been allocated for stream management. The function must also * support partially allocated structure, i.e. one or more buffers @@ -2360,10 +2159,18 @@ void FileStream_Close(TFileStream * pStream) // Check if the stream structure is allocated at all if(pStream != NULL) { + // Free the master stream, if any + if(pStream->pMaster != NULL) + FileStream_Close(pStream->pMaster); + pStream->pMaster = NULL; + // Close the stream provider. - // This will also close the base stream - assert(pStream->StreamClose != NULL); - pStream->StreamClose(pStream); + if(pStream->StreamClose != NULL) + pStream->StreamClose(pStream); + + // Also close base stream, if any + else if(pStream->BaseClose != NULL) + pStream->BaseClose(pStream); // Free the stream itself STORM_FREE(pStream); |