diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-19 11:23:48 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-19 11:23:48 +0100 |
commit | ebd502e0c220f7c2d3b9ce65bd83ed569d65f314 (patch) | |
tree | cb3c0d82de0432448db4a09e9e0ef6d4e71beacb | |
parent | 55f159cf69d8b4816876653d25239abf6c3ef5a4 (diff) |
+ Bitmap support was moved from archive functions to FileStream functions, where are more appropriate
+ Added support for master-mirror streams (like Blizzard games do)
+ SFileGetArchiveBitmap was moved into SFileGetFileInfo
+ Fixed bug in SFileCompactArchive
+ Removed classes SFileMpqBitmapXXX from SFileGetFileInfo
-rw-r--r-- | src/FileStream.cpp | 1096 | ||||
-rw-r--r-- | src/FileStream.h | 70 | ||||
-rw-r--r-- | src/SBaseCommon.cpp | 2 | ||||
-rw-r--r-- | src/SBaseFileTable.cpp | 108 | ||||
-rw-r--r-- | src/SFileGetFileInfo.cpp | 49 | ||||
-rw-r--r-- | src/SFileOpenArchive.cpp | 58 | ||||
-rw-r--r-- | src/StormCommon.h | 3 | ||||
-rw-r--r-- | src/StormLib.h | 61 | ||||
-rw-r--r-- | src/StormPort.h | 2 | ||||
-rw-r--r-- | test/Test.cpp | 267 |
10 files changed, 1009 insertions, 707 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp index eaf85de..403d1d4 100644 --- a/src/FileStream.cpp +++ b/src/FileStream.cpp @@ -47,77 +47,89 @@ void SetLastError(int nError) } #endif -#ifndef PLATFORM_LITTLE_ENDIAN -void ConvertPartHeader(void * partHeader) +//----------------------------------------------------------------------------- +// Preparing file bitmap for a complete file of a given size + +static bool FileBitmap_CheckRange( + TFileBitmap * pBitmap, + ULONGLONG ByteOffset, + ULONGLONG EndOffset) { - PPART_FILE_HEADER theHeader = (PPART_FILE_HEADER)partHeader; + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); + DWORD BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize); + DWORD ByteIndex = (BlockIndex / 0x08); + BYTE BitMask = (BYTE)(0x01 << (BlockIndex & 0x07)); - theHeader->PartialVersion = SwapUInt32(theHeader->PartialVersion); - theHeader->Flags = SwapUInt32(theHeader->Flags); - theHeader->FileSizeLo = SwapUInt32(theHeader->FileSizeLo); - theHeader->FileSizeHi = SwapUInt32(theHeader->FileSizeHi); - theHeader->BlockSize = SwapUInt32(theHeader->BlockSize); -} -#endif + // Mask the bye offset down to the begin of the block + ByteOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1); -//----------------------------------------------------------------------------- -// Preparing file bitmap for a complete file of a given size + // Check each block + while(ByteOffset < EndOffset) + { + // Check availability of that block + if((pbBitmap[ByteIndex] & BitMask) == 0) + return false; -#define DEFAULT_BLOCK_SIZE 0x4000 + // Move to the next block + ByteOffset += pBitmap->BlockSize; + ByteIndex += (BitMask >> 0x07); + BitMask = (BitMask >> 0x07) | (BitMask << 0x01); + } -static bool Dummy_GetBitmap( - TFileStream * pStream, + // All blocks are present + return true; +} + +static void FileBitmap_SetRange( TFileBitmap * pBitmap, - DWORD Length, - LPDWORD LengthNeeded) + ULONGLONG ByteOffset, + ULONGLONG EndOffset) { - ULONGLONG FileSize = 0; - DWORD TotalLength; - DWORD BlockCount; - DWORD BitmapSize; - DWORD LastByte; - bool bResult = false; - - // Get file size and calculate bitmap length - FileStream_GetSize(pStream, &FileSize); - BlockCount = (DWORD)(((FileSize - 1) / DEFAULT_BLOCK_SIZE) + 1); - BitmapSize = (DWORD)(((BlockCount - 1) / 8) + 1); + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); + DWORD BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize); + DWORD ByteIndex = (BlockIndex / 0x08); + BYTE BitMask = (BYTE)(0x01 << (BlockIndex & 0x07)); - // Calculate and give the total length - TotalLength = sizeof(TFileBitmap) + BitmapSize; - if(LengthNeeded != NULL) - *LengthNeeded = TotalLength; + // Mask the bye offset down to the begin of the block + ByteOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1); - // Has the caller given enough space for storing the structure? - if(Length >= sizeof(TFileBitmap)) + // Check each block + while(ByteOffset < EndOffset) { - memset(pBitmap, 0, sizeof(TFileBitmap)); - pBitmap->EndOffset = FileSize; - pBitmap->IsComplete = 1; - pBitmap->BitmapSize = BitmapSize; - pBitmap->BlockSize = DEFAULT_BLOCK_SIZE; - bResult = true; + // Set that bit + pbBitmap[ByteIndex] |= BitMask; + + // Move to the next block + ByteOffset += pBitmap->BlockSize; + ByteIndex += (BitMask >> 0x07); + BitMask = (BitMask >> 0x07) | (BitMask << 0x01); } +} - // Do we have enough space to fill the bitmap as well? - if(Length >= TotalLength) - { - LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); +static DWORD FileBitmap_CheckFile(TFileBitmap * pBitmap) +{ + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); + DWORD WholeByteCount = (pBitmap->BlockCount / 8); + DWORD ExtraBitsCount = (pBitmap->BlockCount & 7); + BYTE ExpectedValue; - // Fill the full blocks - memset(pbBitmap, 0xFF, (BlockCount / 8)); - pbBitmap += (BlockCount / 8); - bResult = true; + // Verify the whole bytes - their value must be 0xFF + for(DWORD i = 0; i < WholeByteCount; i++) + { + if(pbBitmap[i] != 0xFF) + return 0; + } - // Supply the last block - if(BlockCount & 7) - { - LastByte = (1 << (BlockCount & 7)) - 1; - pbBitmap[0] = (BYTE)LastByte; - } + // If there are extra bits, calculate the mask + if(ExtraBitsCount != 0) + { + ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); + if(pbBitmap[WholeByteCount] != ExpectedValue) + return 0; } - return bResult; + // Yes, the file is complete + return 1; } //----------------------------------------------------------------------------- @@ -129,7 +141,7 @@ static bool BaseFile_Read( 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.File.FilePos; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; DWORD dwBytesRead = 0; // Must be set by platform-specific code #ifdef PLATFORM_WINDOWS @@ -140,7 +152,7 @@ static bool BaseFile_Read( // one system call to SetFilePointer // Update the byte offset - pStream->Base.File.FilePos = ByteOffset; + pStream->FilePos = ByteOffset; // Read the data if(dwBytesToRead != 0) @@ -153,24 +165,6 @@ static bool BaseFile_Read( if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, &Overlapped)) return false; } -/* - // If the byte offset is different from the current file position, - // we have to update the file position - if(ByteOffset != pStream->Base.File.FilePos) - { - LONG ByteOffsetHi = (LONG)(ByteOffset >> 32); - - SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN); - pStream->Base.File.FilePos = ByteOffset; - } - - // Read the data - if(dwBytesToRead != 0) - { - if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL)) - return false; - } -*/ } #endif @@ -180,10 +174,10 @@ static bool BaseFile_Read( // If the byte offset is different from the current file position, // we have to update the file position - if(ByteOffset != pStream->Base.File.FilePos) + if(ByteOffset != pStream->FilePos) { lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET); - pStream->Base.File.FilePos = ByteOffset; + pStream->FilePos = ByteOffset; } // Perform the read operation @@ -203,7 +197,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->Base.File.FilePos = ByteOffset + dwBytesRead; + pStream->FilePos = ByteOffset + dwBytesRead; if(dwBytesRead != dwBytesToRead) SetLastError(ERROR_HANDLE_EOF); return (dwBytesRead == dwBytesToRead); @@ -218,7 +212,7 @@ static bool BaseFile_Read( static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) { - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; DWORD dwBytesWritten = 0; // Must be set by platform-specific code #ifdef PLATFORM_WINDOWS @@ -229,7 +223,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const // one system call to SetFilePointer // Update the byte offset - pStream->Base.File.FilePos = ByteOffset; + pStream->FilePos = ByteOffset; // Read the data if(dwBytesToWrite != 0) @@ -242,24 +236,6 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, &Overlapped)) return false; } -/* - // If the byte offset is different from the current file position, - // we have to update the file position - if(ByteOffset != pStream->Base.File.FilePos) - { - LONG ByteOffsetHi = (LONG)(ByteOffset >> 32); - - SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN); - pStream->Base.File.FilePos = ByteOffset; - } - - // Read the data - if(dwBytesToWrite != 0) - { - if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL)) - return false; - } -*/ } #endif @@ -288,33 +264,17 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const #endif // Increment the current file position by number of bytes read - pStream->Base.File.FilePos = ByteOffset + dwBytesWritten; + pStream->FilePos = ByteOffset + dwBytesWritten; // Also modify the file size, if needed - if(pStream->Base.File.FilePos > pStream->Base.File.FileSize) - pStream->Base.File.FileSize = pStream->Base.File.FilePos; + if(pStream->FilePos > pStream->FileSize) + pStream->FileSize = pStream->FilePos; if(dwBytesWritten != dwBytesToWrite) SetLastError(ERROR_DISK_FULL); return (dwBytesWritten == dwBytesToWrite); } -static bool BaseFile_GetPos( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset) // Pointer to file byte offset -{ - *pByteOffset = pStream->Base.File.FilePos; - return true; -} - -static bool BaseFile_GetSize( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pFileSize) // Pointer where to store file size -{ - *pFileSize = pStream->Base.File.FileSize; - return true; -} - /** * \a pStream Pointer to an open stream * \a NewFileSize New size of the file @@ -337,8 +297,8 @@ static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) bResult = (bool)SetEndOfFile(pStream->Base.File.hFile); // Restore the file position - FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32); - FileSizeLo = (LONG)(pStream->Base.File.FilePos); + FileSizeHi = (LONG)(pStream->FilePos >> 32); + FileSizeLo = (LONG)(pStream->FilePos); SetFilePointer(pStream->Base.File.hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN); return bResult; } @@ -357,12 +317,6 @@ static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) #endif } -static bool BaseFile_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) -{ - *pFileTime = pStream->Base.File.FileTime; - return true; -} - // Renames the file pointed by pStream so that it contains data from pNewStream static bool BaseFile_Switch(TFileStream * pStream, TFileStream * pNewStream) { @@ -404,16 +358,13 @@ static void BaseFile_Close(TFileStream * pStream) pStream->Base.File.hFile = INVALID_HANDLE_VALUE; } -static bool BaseFile_Create( - TFileStream * pStream, - const TCHAR * szFileName, - DWORD dwStreamFlags) +static bool BaseFile_Create(TFileStream * pStream) { #ifdef PLATFORM_WINDOWS { - DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; + DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; - pStream->Base.File.hFile = CreateFile(szFileName, + pStream->Base.File.hFile = CreateFile(pStream->szFileName, GENERIC_READ | GENERIC_WRITE, dwWriteShare | FILE_SHARE_READ, NULL, @@ -429,7 +380,7 @@ static bool BaseFile_Create( { intptr_t handle; - handle = open(szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + 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; @@ -443,33 +394,26 @@ static bool BaseFile_Create( // Fill-in the entry points pStream->BaseRead = BaseFile_Read; pStream->BaseWrite = BaseFile_Write; - pStream->BaseGetPos = BaseFile_GetPos; - pStream->BaseGetSize = BaseFile_GetSize; pStream->BaseSetSize = BaseFile_SetSize; - pStream->BaseGetTime = BaseFile_GetTime; pStream->BaseClose = BaseFile_Close; // Reset the file position - pStream->Base.File.FileSize = 0; - pStream->Base.File.FilePos = 0; - pStream->dwFlags = dwStreamFlags; + pStream->FileSize = 0; + pStream->FilePos = 0; return true; } -static bool BaseFile_Open( - TFileStream * pStream, - const TCHAR * szFileName, - DWORD dwStreamFlags) +static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags) { #ifdef PLATFORM_WINDOWS { ULARGE_INTEGER FileSize; - DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : GENERIC_READ | GENERIC_WRITE; + 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(szFileName, - FILE_READ_DATA | dwWriteAccess, + pStream->Base.File.hFile = CreateFile(pStream->szFileName, + FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess, FILE_SHARE_READ | dwWriteShare, NULL, OPEN_EXISTING, @@ -480,10 +424,10 @@ static bool BaseFile_Open( // Query the file size FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart); - pStream->Base.File.FileSize = FileSize.QuadPart; + pStream->FileSize = FileSize.QuadPart; // Query last write time - GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime); + GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->FileTime); } #endif @@ -520,15 +464,11 @@ static bool BaseFile_Open( // Fill-in the entry points pStream->BaseRead = BaseFile_Read; pStream->BaseWrite = BaseFile_Write; - pStream->BaseGetPos = BaseFile_GetPos; - pStream->BaseGetSize = BaseFile_GetSize; pStream->BaseSetSize = BaseFile_SetSize; - pStream->BaseGetTime = BaseFile_GetTime; pStream->BaseClose = BaseFile_Close; // Reset the file position - pStream->Base.File.FilePos = 0; - pStream->dwFlags = dwStreamFlags; + pStream->FilePos = 0; return true; } @@ -541,13 +481,13 @@ static bool BaseMap_Read( 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; + 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->Base.Map.FileSize) + if((ByteOffset + dwBytesToRead) > pStream->FileSize) return false; // Copy the required data @@ -555,29 +495,7 @@ static bool BaseMap_Read( } // Move the current file position - pStream->Base.Map.FilePos += dwBytesToRead; - return true; -} - -static bool BaseMap_GetPos( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset) // Pointer to file byte offset -{ - *pByteOffset = pStream->Base.Map.FilePos; - return true; -} - -static bool BaseMap_GetSize( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pFileSize) // Pointer where to store file size -{ - *pFileSize = pStream->Base.Map.FileSize; - return true; -} - -static bool BaseMap_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) -{ - *pFileTime = pStream->Base.Map.FileTime; + pStream->FilePos += dwBytesToRead; return true; } @@ -590,16 +508,13 @@ static void BaseMap_Close(TFileStream * pStream) #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) if(pStream->Base.Map.pbFile != NULL) - munmap(pStream->Base.Map.pbFile, (size_t )pStream->Base.Map.FileSize); + munmap(pStream->Base.Map.pbFile, (size_t )pStream->FileSize); #endif pStream->Base.Map.pbFile = NULL; } -static bool BaseMap_Open( - TFileStream * pStream, - const TCHAR * szFileName, - DWORD dwStreamFlags) +static bool BaseMap_Open(TFileStream * pStream) { #ifdef PLATFORM_WINDOWS @@ -609,7 +524,7 @@ static bool BaseMap_Open( bool bResult = false; // Open the file for read access - hFile = CreateFile(szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + hFile = CreateFile(pStream->szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(hFile != NULL) { // Retrieve file size. Don't allow mapping file of a zero size. @@ -617,7 +532,7 @@ static bool BaseMap_Open( if(FileSize.QuadPart != 0) { // Retrieve file time - GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime); + GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->FileTime); // Now create mapping object hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); @@ -629,8 +544,8 @@ static bool BaseMap_Open( pStream->Base.Map.pbFile = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if(pStream->Base.Map.pbFile != NULL) { - pStream->Base.Map.FileSize = FileSize.QuadPart; - pStream->Base.Map.FilePos = 0; + pStream->FileSize = FileSize.QuadPart; + pStream->FilePos = 0; bResult = true; } @@ -667,9 +582,9 @@ static bool BaseMap_Open( // 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.Map.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); - pStream->Base.Map.FileSize = (ULONGLONG)fileinfo.st_size; - pStream->Base.Map.FilePos = 0; + pStream->FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); + pStream->FileSize = (ULONGLONG)fileinfo.st_size; + pStream->FilePos = 0; bResult = true; } } @@ -686,11 +601,7 @@ static bool BaseMap_Open( // Fill-in entry points pStream->BaseRead = BaseMap_Read; - pStream->BaseGetPos = BaseMap_GetPos; - pStream->BaseGetSize = BaseMap_GetSize; - pStream->BaseGetTime = BaseMap_GetTime; pStream->BaseClose = BaseMap_Close; - pStream->dwFlags = dwStreamFlags; return true; } @@ -727,7 +638,7 @@ static bool BaseHttp_Read( DWORD dwBytesToRead) // Number of bytes to read from the file { #ifdef PLATFORM_WINDOWS - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; DWORD dwTotalBytesRead = 0; // Do we have to read anything at all? @@ -777,7 +688,7 @@ static bool BaseHttp_Read( } // Increment the current file position by number of bytes read - pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead; + pStream->FilePos = ByteOffset + dwTotalBytesRead; // If the number of bytes read doesn't match the required amount, return false if(dwTotalBytesRead != dwBytesToRead) @@ -797,28 +708,6 @@ static bool BaseHttp_Read( #endif } -static bool BaseHttp_GetPos( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset) // Pointer to file byte offset -{ - *pByteOffset = pStream->Base.Http.FilePos; - return true; -} - -static bool BaseHttp_GetSize( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pFileSize) // Pointer where to store file size -{ - *pFileSize = pStream->Base.Http.FileSize; - return true; -} - -static bool BaseHttp_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) -{ - *pFileTime = pStream->Base.Http.FileTime; - return true; -} - static void BaseHttp_Close(TFileStream * pStream) { #ifdef PLATFORM_WINDOWS @@ -834,13 +723,11 @@ static void BaseHttp_Close(TFileStream * pStream) #endif } -static bool BaseHttp_Open( - TFileStream * pStream, - const TCHAR * szFileName, - DWORD dwStreamFlags) +static bool BaseHttp_Open(TFileStream * pStream) { #ifdef PLATFORM_WINDOWS + const TCHAR * szFileName; HINTERNET hRequest; DWORD dwTemp = 0; bool bFileAvailable = false; @@ -869,7 +756,7 @@ static bool BaseHttp_Open( DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; // Initiate connection with the server - szFileName = BaseHttp_ExtractServerName(szFileName, szServerName); + szFileName = BaseHttp_ExtractServerName(pStream->szFileName, szServerName); pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, szServerName, INTERNET_DEFAULT_HTTP_PORT, @@ -899,7 +786,7 @@ static bool BaseHttp_Open( // 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; + pStream->FileTime = FileTime; // Verify if the server supports random access dwDataSize = sizeof(DWORD); @@ -907,8 +794,8 @@ static bool BaseHttp_Open( { if(dwFileSize != 0) { - pStream->Base.Http.FileSize = dwFileSize; - pStream->Base.Http.FilePos = 0; + pStream->FileSize = dwFileSize; + pStream->FilePos = 0; bFileAvailable = true; } } @@ -927,126 +814,412 @@ static bool BaseHttp_Open( // Fill-in entry points pStream->BaseRead = BaseHttp_Read; - pStream->BaseGetPos = BaseHttp_GetPos; - pStream->BaseGetSize = BaseHttp_GetSize; - pStream->BaseGetTime = BaseHttp_GetTime; pStream->BaseClose = BaseHttp_Close; - pStream->dwFlags = dwStreamFlags; return true; #else // Not supported - pStream = pStream; - szFileName = szFileName; SetLastError(ERROR_NOT_SUPPORTED); + pStream = pStream; return false; #endif } //----------------------------------------------------------------------------- -// Local functions - linear stream support +// Local functions - bitmap for a complete file -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 +#define DEFAULT_BLOCK_SIZE 0x4000 + +static bool CompleteFile_GetBmp( + TFileStream * pStream, + void * pvBitmap, + DWORD Length, + LPDWORD LengthNeeded) { - ULONGLONG ByteOffset; - ULONGLONG EndOffset; - LPBYTE pbBitmap; - DWORD BlockIndex; - DWORD ByteIndex; - DWORD BitMask; + 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; - // At this point, we must have a bitmap set - assert(pStream->pBitmap != NULL); + // Calculate and give the total length + TotalLength = sizeof(TFileBitmap) + BitmapSize; + if(LengthNeeded != NULL) + *LengthNeeded = TotalLength; - // If we have data map, we must check if the data block is present in the MPQ - if(dwBytesToRead != 0) + // Has the caller given enough space for storing the structure? + if(Length >= sizeof(TFileBitmap)) { - DWORD BlockSize = pStream->pBitmap->BlockSize; - - // Get the offset where we read it from - if(pByteOffset == NULL) - pStream->BaseGetPos(pStream, &ByteOffset); - else - ByteOffset = *pByteOffset; - EndOffset = ByteOffset + dwBytesToRead; + pBitmap->StartOffset = 0; + pBitmap->EndOffset = FileSize; + pBitmap->BitmapSize = BitmapSize; + pBitmap->BlockSize = DEFAULT_BLOCK_SIZE; + pBitmap->BlockCount = BlockCount; + pBitmap->IsComplete = 1; - // If the start of the area is within the region - // protected by data map, check each block - if(ByteOffset < pStream->pBitmap->EndOffset) + // Do we have enough space to fill the bitmap as well? + if(Length >= TotalLength) { - // Cut the end of the stream protected by the data map - EndOffset = STORMLIB_MIN(EndOffset, pStream->pBitmap->EndOffset); + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); - // Calculate the initial block index - BlockIndex = (DWORD)(ByteOffset / BlockSize); - pbBitmap = (LPBYTE)(pStream->pBitmap + 1); + // Fill the full blocks + memset(pbBitmap, 0xFF, (BlockCount / 8)); + pbBitmap += (BlockCount / 8); - // Parse each block - while(ByteOffset < EndOffset) + // Supply the last block + if(BlockCount & 7) { - // Prepare byte index and bit mask - ByteIndex = BlockIndex / 8; - BitMask = 1 << (BlockIndex & 0x07); + LastByte = (1 << (BlockCount & 7)) - 1; + pbBitmap[0] = (BYTE)LastByte; + } + } + + return true; + } + else + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return false; + } +} - // If that bit is not set, it means that the block is not present - if((pbBitmap[ByteIndex] & BitMask) == 0) +//----------------------------------------------------------------------------- +// Local functions - linear stream support + +typedef struct _DATA_BLOCK_INFO +{ + 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 + +} DATA_BLOCK_INFO, *PDATA_BLOCK_INFO; + +static bool LinearStream_LoadBitmap( + TLinearStream * pStream) // Pointer to an open stream +{ + FILE_BITMAP_FOOTER Footer; + TFileBitmap * pBitmap; + ULONGLONG ByteOffset; + DWORD BlockCount; + DWORD BitmapSize; + + // Only if the size is greater than sizeof bitmap footer + if(pStream->FileSize > sizeof(FILE_BITMAP_FOOTER)) + { + // Load the bitmap footer + ByteOffset = pStream->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) + { + // 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); + BitmapSize = ((BlockCount + 7) / 8); + + // Check if the sizes match + if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->FileSize) { - SetLastError(ERROR_FILE_CORRUPT); - return false; + // Allocate space for the linear bitmap + pBitmap = (TFileBitmap *)STORM_ALLOC(BYTE, sizeof(TFileBitmap) + BitmapSize); + if(pBitmap != 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)) + { + STORM_FREE(pBitmap); + 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; + return true; + } } + } + } + } + + return false; +} - // Move to tne next block - ByteOffset += BlockSize; - BlockIndex++; +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); } - // Now if all tests passed, we can call the base read function - return pStream->BaseRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); + return bResult; } -static bool LinearStream_Switch(TLinearStream * pStream, TLinearStream * pNewStream) +static bool LinearStream_ReadBlocks( + TLinearStream * pStream, // Pointer to an open stream + DATA_BLOCK_INFO & bi, + bool bBlocksAreAvailable) { - // 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); + 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); - // Close the new stream - pNewStream->BaseClose(pNewStream); + // 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; - // Close the source stream - pStream->BaseClose(pStream); + // Allocate buffer and read the complete blocks + BlockToRead = (DWORD)(bi.BlockOffset - bi.BlockOffset0); + pbBlockBuffer = STORM_ALLOC(BYTE, BlockToRead); + if(pbBlockBuffer == NULL) + return false; - // Rename the new data source file to the existing file - if(!BaseFile_Switch(pStream, pNewStream)) + // 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 +{ + DATA_BLOCK_INFO bi; + TFileBitmap * pBitmap = pStream->pBitmap; + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; + LPBYTE 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; + } - // Now we have to open the "pStream" again - if(!BaseFile_Open(pStream, pStream->szFileName, pNewStream->dwFlags)) + // If we have no data bitmap, we assume that the file is complete. + if(pBitmap == NULL) + return pStream->BaseRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); + + // 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; + } + + // 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)) + { + SetLastError(ERROR_CAN_NOT_COMPLETE); return false; + } - // We need to cleanup the new data stream - FileStream_Close(pNewStream); + // 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); + } + + // Increment the position + pStream->FilePos = ByteOffset + dwBytesToRead; + return true; +} + +static bool LinearStream_Write(TLinearStream *, ULONGLONG *, const void *, DWORD) +{ + // Writing to linear stream with bitmap is not allowed + SetLastError(ERROR_ACCESS_DENIED); + return false; +} + +static bool LinearStream_GetSize(TLinearStream * pStream, ULONGLONG * pFileSize) +{ + *pFileSize = pStream->FileSize; return true; } -static bool LinearStream_GetBitmap( +static bool LinearStream_GetTime(TLinearStream * pStream, ULONGLONG * pFileTime) +{ + *pFileTime = pStream->FileTime; + return true; +} + +static bool LinearStream_GetPos(TLinearStream * pStream, ULONGLONG * pByteOffset) +{ + *pByteOffset = pStream->FilePos; + return true; +} + +static bool LinearStream_GetBmp( TLinearStream * pStream, - TFileBitmap * pBitmap, + void * pvBitmap, DWORD Length, LPDWORD LengthNeeded) { DWORD TotalLength; - bool bResult = false; + DWORD CopyLength = sizeof(TFileBitmap); // Assumed that we have bitmap now assert(pStream->pBitmap != NULL); @@ -1059,45 +1232,106 @@ static bool LinearStream_GetBitmap( // Do we have enough space to fill at least the bitmap structure? if(Length >= sizeof(TFileBitmap)) { - // Enough space for complete bitmap? + // Enough space for the complete bitmap? if(Length >= TotalLength) - { - memcpy(pBitmap, pStream->pBitmap, TotalLength); - bResult = true; - } - else - { - memcpy(pBitmap, pStream->pBitmap, sizeof(TFileBitmap)); - bResult = true; - } + CopyLength = TotalLength; + memcpy(pvBitmap, pStream->pBitmap, CopyLength); + return true; + } + else + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return false; } +} - return bResult; +static bool LinearStream_Switch(TLinearStream * pStream, TLinearStream * pNewStream) +{ + // 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)) + return false; + + // We need to cleanup the new data stream + FileStream_Close(pNewStream); + return true; } static void LinearStream_Close(TLinearStream * pStream) { + // Free the souce stream, if any + if(pStream->pMaster != NULL) + FileStream_Close(pStream->pMaster); + pStream->pMaster = NULL; + // Free the data map, if any if(pStream->pBitmap != NULL) STORM_FREE(pStream->pBitmap); pStream->pBitmap = NULL; - + // Call the base class for closing the stream - return pStream->BaseClose(pStream); + pStream->BaseClose(pStream); } static bool LinearStream_Open(TLinearStream * pStream) { - // No extra work here really; just set entry points + // Set the entry points pStream->StreamRead = pStream->BaseRead; pStream->StreamWrite = pStream->BaseWrite; - pStream->StreamGetPos = pStream->BaseGetPos; - pStream->StreamGetSize = pStream->BaseGetSize; pStream->StreamSetSize = pStream->BaseSetSize; - pStream->StreamGetTime = pStream->BaseGetTime; - pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap; + 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) + { + // Attempt to load the file bitmap + LinearStream_LoadBitmap(pStream); + + // 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 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; + } + } + } + return true; } @@ -1260,11 +1494,11 @@ static bool PartialStream_GetBitmap( { // Fill the bitmap header pBitmap->StartOffset = 0; - pBitmap->EndOffset = pStream->VirtualSize; - pBitmap->IsComplete = 1; - pBitmap->BitmapSize = BitmapSize; - pBitmap->BlockSize = pStream->BlockSize; - pBitmap->Reserved = 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++) @@ -1330,7 +1564,7 @@ static bool PartialStream_Open(TPartialStream * pStream) if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER))) { // We need to swap PART file header on big-endian platforms - BSWAP_PART_HEADER(&PartHdr); + BSWAP_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER)); // Verify the PART file header if(IsPartHeader(&PartHdr)) @@ -1360,8 +1594,7 @@ static bool PartialStream_Open(TPartialStream * pStream) pStream->StreamRead = (STREAM_READ)PartialStream_Read; pStream->StreamGetPos = (STREAM_GETPOS)PartialStream_GetPos; pStream->StreamGetSize = (STREAM_GETSIZE)PartialStream_GetSize; - pStream->StreamGetTime = pStream->BaseGetTime; - pStream->StreamGetTime = pStream->BaseGetTime; + pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime; pStream->StreamGetBmp = (STREAM_GETBMP)PartialStream_GetBitmap; pStream->StreamClose = (STREAM_CLOSE)PartialStream_Close; return true; @@ -1615,10 +1848,7 @@ static bool EncryptedStream_Read( bool bResult = false; // Get the byte offset - if(pByteOffset == NULL) - pStream->BaseGetPos(pStream, &ByteOffset); - else - ByteOffset = *pByteOffset; + ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos; // Cut it down to MPQE chunk size StartOffset = ByteOffset; @@ -1675,10 +1905,10 @@ static bool EncryptedStream_Open(TEncryptedStream * pStream) { // Assign functions pStream->StreamRead = (STREAM_READ)EncryptedStream_Read; - pStream->StreamGetPos = pStream->BaseGetPos; - pStream->StreamGetSize = pStream->BaseGetSize; - pStream->StreamGetTime = pStream->BaseGetTime; - pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap; + 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 @@ -1693,6 +1923,101 @@ static bool EncryptedStream_Open(TEncryptedStream * pStream) } //----------------------------------------------------------------------------- +// 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) +{ + 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; + + // 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; + } + + // Re-create the stream flags + dwStreamFlags = (dwStreamFlags & STREAM_FLAG_MASK) | dwStreamProvider | dwBaseProvider; + + // Allocate file stream for each stream provider + switch(dwStreamFlags & STREAM_PROVIDER_MASK) + { + 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; + + case STREAM_PROVIDER_ENCRYPTED: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + StreamSize = sizeof(TEncryptedStream); + break; + + default: + return NULL; + } + + // 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; +} + +//----------------------------------------------------------------------------- // Public functions /** @@ -1726,24 +2051,20 @@ TFileStream * FileStream_CreateFile( } // Allocate file stream structure for linear stream - pStream = STORM_ALLOC(TFileStream, 1); + pStream = AllocateFileStream(szFileName, dwStreamFlags); if(pStream != NULL) { - // Reset entire structure to zero - memset(pStream, 0, sizeof(TFileStream)); - _tcscpy(pStream->szFileName, szFileName); - // Attempt to create the disk file - if(BaseFile_Create(pStream, szFileName, dwStreamFlags)) + if(BaseFile_Create(pStream)) { // Fill the stream provider functions pStream->StreamRead = pStream->BaseRead; pStream->StreamWrite = pStream->BaseWrite; - pStream->StreamGetPos = pStream->BaseGetPos; - pStream->StreamGetSize = pStream->BaseGetSize; pStream->StreamSetSize = pStream->BaseSetSize; - pStream->StreamGetTime = pStream->BaseGetTime; - pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap;; + 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 = pStream->BaseClose; return pStream; @@ -1782,55 +2103,46 @@ TFileStream * FileStream_OpenFile( DWORD dwStreamFlags) { TFileStream * pStream = NULL; - size_t StreamSize = 0; + DWORD dwClearMask = STREAM_OPTIONS_MASK; bool bStreamResult = false; bool bBaseResult = false; - // Allocate file stream for each stream provider - switch(dwStreamFlags & STREAM_PROVIDER_MASK) - { - 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; - - case STREAM_PROVIDER_ENCRYPTED: - dwStreamFlags |= STREAM_FLAG_READ_ONLY; - StreamSize = sizeof(TEncryptedStream); - break; - - default: - return NULL; - } - - // Allocate the stream for each type - pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize); + // Allocate the stream of the given type + pStream = AllocateFileStream(szFileName, dwStreamFlags); if(pStream == NULL) return NULL; - // Fill the stream structure with zeros - memset(pStream, 0, StreamSize); - _tcscpy(pStream->szFileName, szFileName); + // Few special checks when we want the stream to be cached from another source, + if(pStream->szSourceName != NULL) + { + // 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; + } // Now initialize the respective base provider - switch(dwStreamFlags & BASE_PROVIDER_MASK) + switch(pStream->dwFlags & BASE_PROVIDER_MASK) { case BASE_PROVIDER_FILE: - bBaseResult = BaseFile_Open(pStream, szFileName, dwStreamFlags); + bBaseResult = BaseFile_Open(pStream, pStream->dwFlags & dwClearMask); break; case BASE_PROVIDER_MAP: - dwStreamFlags |= STREAM_FLAG_READ_ONLY; - bBaseResult = BaseMap_Open(pStream, szFileName, dwStreamFlags); + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; + bBaseResult = BaseMap_Open(pStream); break; case BASE_PROVIDER_HTTP: - dwStreamFlags |= STREAM_FLAG_READ_ONLY; - bBaseResult = BaseHttp_Open(pStream, szFileName, dwStreamFlags); + pStream->dwFlags |= STREAM_FLAG_READ_ONLY; + bBaseResult = BaseHttp_Open(pStream); break; } @@ -1842,7 +2154,7 @@ TFileStream * FileStream_OpenFile( } // Now initialize the stream provider - switch(dwStreamFlags & STREAM_PROVIDER_MASK) + switch(pStream->dwFlags & STREAM_PROVIDER_MASK) { case STREAM_PROVIDER_LINEAR: bStreamResult = LinearStream_Open((TLinearStream *)pStream); @@ -2019,38 +2331,6 @@ bool FileStream_IsReadOnly(TFileStream * pStream) } /** - * This function enabled a linear stream to include data bitmap. - * Used by MPQs v 4.0 from WoW. Each file block is represented by - * a bit in the bitmap. 1 means the block is present, 0 means it's not. - * - * \a pStream Pointer to an open stream - * \a pBitmap Pointer to file bitmap - */ - -bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap) -{ - TLinearStream * pLinearStream; - - // It must be a linear stream. - if((pStream->dwFlags & STREAM_PROVIDER_MASK) != STREAM_PROVIDER_LINEAR) - return false; - pLinearStream = (TLinearStream *)pStream; - - // Two bitmaps are not allowed - if(pLinearStream->pBitmap != NULL) - return false; - - // We need to change some entry points - pLinearStream->StreamRead = (STREAM_READ)LinearStream_Read; - pLinearStream->StreamGetBmp = (STREAM_GETBMP)LinearStream_GetBitmap; - - // Using data bitmap renders the stream to be read only. - pLinearStream->dwFlags |= STREAM_FLAG_READ_ONLY; - pLinearStream->pBitmap = pBitmap; - return true; -} - -/** * 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 @@ -2061,10 +2341,10 @@ bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap) * \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, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded) +bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD Length, LPDWORD LengthNeeded) { assert(pStream->StreamGetBmp != NULL); - return pStream->StreamGetBmp(pStream, pBitmap, Length, LengthNeeded); + return pStream->StreamGetBmp(pStream, pvBitmap, Length, LengthNeeded); } /** diff --git a/src/FileStream.h b/src/FileStream.h index d00c82e..a44b48e 100644 --- a/src/FileStream.h +++ b/src/FileStream.h @@ -28,9 +28,9 @@ typedef bool (*STREAM_WRITE)( DWORD dwBytesToWrite // Number of bytes to read from the file ); -typedef bool (*STREAM_GETPOS)( +typedef bool (*STREAM_SETSIZE)( struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset // Pointer to store current file position + ULONGLONG FileSize // New size for the file, in bytes ); typedef bool (*STREAM_GETSIZE)( @@ -38,34 +38,36 @@ typedef bool (*STREAM_GETSIZE)( ULONGLONG * pFileSize // Receives the file size, in bytes ); -typedef bool (*STREAM_SETSIZE)( - struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG FileSize // New size for the file, in bytes - ); - typedef bool (*STREAM_GETTIME)( struct TFileStream * pStream, ULONGLONG * pFT ); -typedef bool (*STREAM_SWITCH)( - struct TFileStream * pStream, - struct TFileStream * pNewStream +typedef bool (*STREAM_GETPOS)( + struct TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset // Pointer to store current file position ); typedef bool (*STREAM_GETBMP)( TFileStream * pStream, - TFileBitmap * pBitmap, + void * pvBitmap, DWORD Length, LPDWORD LengthNeeded ); +typedef bool (*STREAM_SWITCH)( + struct TFileStream * pStream, + struct TFileStream * pNewStream + ); + typedef void (*STREAM_CLOSE)( struct TFileStream * pStream ); //----------------------------------------------------------------------------- -// Local structures - part file structure +// Local structures - partial file structure and bitmap footer + +#define ID_FILE_BITMAP_FOOTER 0x33767470 // Signature of the file bitmap footer ('ptv3') typedef struct _PART_FILE_HEADER { @@ -89,6 +91,17 @@ typedef struct _PART_FILE_MAP_ENTRY } PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY; +typedef struct _FILE_BITMAP_FOOTER +{ + DWORD dwSignature; // 'ptv3' (MPQ_DATA_BITMAP_SIGNATURE) + DWORD dwAlways3; // Unknown, seems to always have value of 3 + DWORD dwBuildNumber; // Game build number for that MPQ + DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map + DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map + DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) + +} FILE_BITMAP_FOOTER, *PFILE_BITMAP_FOOTER; + //----------------------------------------------------------------------------- // Local structures @@ -96,25 +109,16 @@ union TBaseData { struct { - ULONGLONG FileSize; // Size of the file - ULONGLONG FilePos; // Current file position - ULONGLONG FileTime; // Date/time of last modification of the file HANDLE hFile; // File handle } File; struct { - ULONGLONG FileSize; // Mapped file size - ULONGLONG FilePos; // Current stream position - ULONGLONG FileTime; // Date/time of last modification of the file LPBYTE pbFile; // Pointer to mapped view } Map; struct { - ULONGLONG FileSize; // Size of the internet file - ULONGLONG FilePos; // Current position in the file - ULONGLONG FileTime; // Date/time of last modification of the file HANDLE hInternet; // Internet handle HANDLE hConnect; // Connection to the internet server } Http; @@ -128,39 +132,39 @@ struct TFileStream // Stream provider functions STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly. STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly. - STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position - STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size STREAM_SETSIZE StreamSetSize; // Pointer to function changing file size + STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size STREAM_GETTIME StreamGetTime; // Pointer to function retrieving the file time + STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position STREAM_GETBMP StreamGetBmp; // Pointer to function that retrieves the file bitmap STREAM_SWITCH StreamSwitch; // Pointer to function changing the stream to another file STREAM_CLOSE StreamClose; // Pointer to function closing the stream - // Stream provider data members - TCHAR szFileName[MAX_PATH]; // File name - DWORD dwFlags; // Stream flags - // Base provider functions STREAM_READ BaseRead; STREAM_WRITE BaseWrite; - STREAM_GETPOS BaseGetPos; // Pointer to function that returns current file position - STREAM_GETSIZE BaseGetSize; // Pointer to function returning file size STREAM_SETSIZE BaseSetSize; // Pointer to function changing file size - STREAM_GETTIME BaseGetTime; // Pointer to function retrieving the file time STREAM_CLOSE BaseClose; // Pointer to function closing the stream - // Base provider data members + ULONGLONG FileSize; // Size of the file + ULONGLONG FilePos; // Current file position + ULONGLONG FileTime; // Date/time of last modification of the file + TCHAR * szSourceName; // Name of the source file (might be HTTP file server or local file) + TCHAR * szFileName; // File name (self-relative pointer) + DWORD dwFlags; // Stream flags + TBaseData Base; // Base provider data // Followed by stream provider data, with variable length }; //----------------------------------------------------------------------------- -// Structure for linear stream +// Structures for linear stream struct TLinearStream : public TFileStream { - TFileBitmap * pBitmap; // Pointer to the stream bitmap + TFileStream * pMaster; // Master file for loading missing data blocks + TFileBitmap * pBitmap; // Pointer to the linear bitmap }; //----------------------------------------------------------------------------- diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index bb580b5..ef753bd 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -1410,8 +1410,6 @@ void FreeMPQArchive(TMPQArchive *& ha) STORM_FREE(ha->pFileTable); } - if(ha->pBitmap != NULL) - STORM_FREE(ha->pBitmap); if(ha->pHashTable != NULL) STORM_FREE(ha->pHashTable); if(ha->pHetTable != NULL) diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index d88c5f9..fe02931 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -252,12 +252,22 @@ static DWORD GetMaxFileOffset32(TMPQArchive * ha) static ULONGLONG DetermineEndOfArchive_V1_V2( TMPQArchive * ha, + TMPQHeader * pHeader, ULONGLONG MpqOffset, ULONGLONG FileSize) { ULONGLONG ByteOffset; ULONGLONG EndOfMpq = FileSize; DWORD SignatureHeader = 0; + DWORD dwArchiveSize32; + + // Check if we can rely on the archive size in the header + if((FileSize >> 0x20) == 0) + { + dwArchiveSize32 = (DWORD)(FileSize - MpqOffset); + if(pHeader->dwBlockTablePos < pHeader->dwArchiveSize && pHeader->dwArchiveSize <= dwArchiveSize32) + return pHeader->dwArchiveSize; + } // Check if there is a signature header if((EndOfMpq - MpqOffset) > (MPQ_STRONG_SIGNATURE_SIZE + 4)) @@ -375,7 +385,7 @@ int ConvertMpqHeaderToFormat4( // Determine the archive size on malformed MPQs if(ha->dwFlags & MPQ_FLAG_MALFORMED) { - pHeader->ArchiveSize64 = DetermineEndOfArchive_V1_V2(ha, MpqOffset, FileSize); + pHeader->ArchiveSize64 = DetermineEndOfArchive_V1_V2(ha, pHeader, MpqOffset, FileSize); pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64; } break; @@ -418,7 +428,7 @@ int ConvertMpqHeaderToFormat4( assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock))); // Determine the size of the hi-block table - pHeader->HiBlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, MpqOffset, FileSize) - pHeader->HiBlockTablePos64; + pHeader->HiBlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, pHeader, MpqOffset, FileSize) - pHeader->HiBlockTablePos64; assert(pHeader->HiBlockTableSize64 == (pHeader->dwBlockTableSize * sizeof(USHORT))); // Recalculate the archive size @@ -427,7 +437,7 @@ int ConvertMpqHeaderToFormat4( else { // Block table size is the end of the archive minus the block table position - pHeader->BlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, MpqOffset, FileSize) - BlockTablePos64; + pHeader->BlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, pHeader, MpqOffset, FileSize) - BlockTablePos64; assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock))); // dwArchiveSize in the header v 2.0 is 32-bit only @@ -1989,96 +1999,6 @@ void InvalidateInternalFiles(TMPQArchive * ha) } //----------------------------------------------------------------------------- -// Functions that loads and verify MPQ data bitmap - -int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComplete) -{ - TMPQBitmap * pBitmap = NULL; - TMPQBitmap DataBitmap; - ULONGLONG BitmapOffset; - ULONGLONG EndOfMpq; - DWORD DataBlockCount = 0; - DWORD BitmapByteSize = 0; - DWORD WholeByteCount; - DWORD ExtraBitsCount; - - // Is there enough space for a MPQ bitmap? - // Note: Do not rely on file size when looking for the bitmap. - // Battle.net.MPQ from SC2:HOTS (build 22342) has some data appended after the bitmap - EndOfMpq = ha->MpqPos + ha->pHeader->ArchiveSize64; - if(FileSize > EndOfMpq && ha->pHeader->dwRawChunkSize != 0) - { - // Calculate the number of extra bytes for data bitmap - DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / ha->pHeader->dwRawChunkSize) + 1); - BitmapByteSize = ((DataBlockCount + 7) / 8); - BitmapOffset = EndOfMpq + BitmapByteSize; - - // Try to load the data bitmap from the end of the file - if(FileStream_Read(ha->pStream, &BitmapOffset, &DataBitmap, sizeof(TMPQBitmap))) - { - // Is it a valid data bitmap? - BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&DataBitmap), sizeof(TMPQBitmap)); - if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE) - { - // Several sanity checks to ensure integrity of the bitmap - assert(ha->MpqPos + MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); - assert(ha->pHeader->dwRawChunkSize == DataBitmap.dwBlockSize); - - // Allocate space for the data bitmap - pBitmap = (TMPQBitmap *)STORM_ALLOC(BYTE, sizeof(TMPQBitmap) + BitmapByteSize); - if(pBitmap != NULL) - { - // Copy the bitmap header - memcpy(pBitmap, &DataBitmap, sizeof(TMPQBitmap)); - - // Read the remaining part - if(!FileStream_Read(ha->pStream, &EndOfMpq, (pBitmap + 1), BitmapByteSize)) - { - STORM_FREE(pBitmap); - pBitmap = NULL; - } - } - } - } - } - - // If the caller asks for file completeness, check it - if(pBitmap != NULL && pbFileIsComplete != NULL) - { - LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); - DWORD i; - bool bFileIsComplete = true; - - // Calculate the number of whole bytes and extra bits of the bitmap - WholeByteCount = (DataBlockCount / 8); - ExtraBitsCount = (DataBlockCount & 7); - - // Verify the whole bytes - their value must be 0xFF - for(i = 0; i < WholeByteCount; i++) - { - if(pbBitmap[i] != 0xFF) - bFileIsComplete = false; - } - - // If there are extra bits, calculate the mask - if(ExtraBitsCount != 0) - { - BYTE ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); - - if(pbBitmap[i] != ExpectedValue) - bFileIsComplete = false; - } - - // Give the result to the caller - *pbFileIsComplete = bFileIsComplete; - } - - ha->dwBitmapSize = sizeof(TMPQBitmap) + BitmapByteSize; - ha->pBitmap = pBitmap; - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- // Support for file tables - hash table, block table, hi-block table int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) @@ -2726,7 +2646,6 @@ int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxF // Set the new tables to the MPQ archive ha->pFileTable = pFileTable; ha->pHashTable = pHashTable; - pFileTable = NULL; // Set the new limits to the MPQ archive ha->pHeader->dwHashTableSize = dwNewHashTableSize; @@ -2757,6 +2676,7 @@ int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxF // Update the file table size ha->dwFileTableSize = (DWORD)(pFileEntry - pFileTable); ha->dwFlags |= MPQ_FLAG_CHANGED; + pFileTable = NULL; } // Now free the remaining entries diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp index da47fc9..31ee594 100644 --- a/src/SFileGetFileInfo.cpp +++ b/src/SFileGetFileInfo.cpp @@ -163,6 +163,12 @@ bool WINAPI SFileGetFileInfo( } break; + case SFileMpqFileBitmap: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + return FileStream_GetBitmap(ha->pStream, pvFileInfo, cbFileInfo, pcbLengthNeeded); + break; + case SFileMpqUserDataOffset: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) @@ -510,49 +516,6 @@ bool WINAPI SFileGetFileInfo( } break; - case SFileMpqBitmapOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pBitmap != NULL) - { - Int64Value = MAKE_OFFSET64(ha->pBitmap->dwMapOffsetHi, ha->pBitmap->dwMapOffsetLo); - pvSrcFileInfo = &Int64Value; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqBitmapSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pBitmap != NULL) - { - pvSrcFileInfo = &ha->dwBitmapSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqBitmap: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pBitmap != NULL) - { - pvSrcFileInfo = ha->pBitmap; - cbSrcFileInfo = ha->dwBitmapSize; - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - case SFileMpqArchiveSize64: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index 417b43d..e5dcf03 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -59,34 +59,6 @@ static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSiz return NULL; } -static TFileBitmap * CreateFileBitmap(TMPQArchive * ha, TMPQBitmap * pMpqBitmap, bool bFileIsComplete) -{ - TFileBitmap * pBitmap; - size_t nLength; - - // Calculate the length of the bitmap in blocks and in bytes - nLength = (size_t)(((ha->pHeader->ArchiveSize64 - 1) / pMpqBitmap->dwBlockSize) + 1); - nLength = (size_t)(((nLength - 1) / 8) + 1); - - // Allocate the file bitmap - pBitmap = (TFileBitmap *)STORM_ALLOC(BYTE, sizeof(TFileBitmap) + nLength); - if(pBitmap != NULL) - { - // Fill the structure - pBitmap->StartOffset = ha->MpqPos; - pBitmap->EndOffset = ha->MpqPos + ha->pHeader->ArchiveSize64; - pBitmap->IsComplete = bFileIsComplete ? 1 : 0; - pBitmap->BitmapSize = (DWORD)nLength; - pBitmap->BlockSize = pMpqBitmap->dwBlockSize; - pBitmap->Reserved = 0; - - // Copy the file bitmap - memcpy((pBitmap + 1), (pMpqBitmap + 1), nLength); - } - - return pBitmap; -} - // This function gets the right positions of the hash table and the block table. static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize) { @@ -190,7 +162,7 @@ bool WINAPI SFileOpenArchive( if(nError == ERROR_SUCCESS) { // Initialize the stream - pStream = FileStream_OpenFile(szMpqName, (dwFlags & STREAM_OPTIONS_MASK)); + pStream = FileStream_OpenFile(szMpqName, (dwFlags & STREAM_OPTIONS_MASK) | STREAM_FLAG_USE_BITMAP); if(pStream == NULL) nError = GetLastError(); } @@ -344,24 +316,6 @@ bool WINAPI SFileOpenArchive( nError = VerifyMpqTablePositions(ha, FileSize); } - // Check if the MPQ has data bitmap. If yes, we can verify if the MPQ is complete - if(nError == ERROR_SUCCESS && ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4) - { - TFileBitmap * pBitmap; - bool bFileIsComplete = true; - - LoadMpqDataBitmap(ha, FileSize, &bFileIsComplete); - if(ha->pBitmap != NULL && bFileIsComplete == false) - { - // Convert the MPQ bitmap to the file bitmap - pBitmap = CreateFileBitmap(ha, ha->pBitmap, bFileIsComplete); - - // Set the data bitmap into the file stream for additional checks - FileStream_SetBitmap(ha->pStream, pBitmap); - ha->dwFlags |= MPQ_FLAG_READ_ONLY; - } - } - // Read the hash table. Ignore the result, as hash table is no longer required // Read HET table. Ignore the result, as HET table is no longer required if(nError == ERROR_SUCCESS) @@ -450,16 +404,6 @@ bool WINAPI SFileOpenArchive( } //----------------------------------------------------------------------------- -// SFileGetArchiveBitmap - -bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - return FileStream_GetBitmap(ha->pStream, pBitmap, Length, LengthNeeded); -} - -//----------------------------------------------------------------------------- // bool SFileFlushArchive(HANDLE hMpq) // // Saves all dirty data into MPQ archive. diff --git a/src/StormCommon.h b/src/StormCommon.h index aeedbca..2901c9f 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -185,9 +185,6 @@ TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool ULONGLONG FindFreeMpqSpace(TMPQArchive * ha); -// Functions that loads and verifies MPQ data bitmap -int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComplete); - // Functions that load the HET and BET tables int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize); int LoadAnyHashTable(TMPQArchive * ha); diff --git a/src/StormLib.h b/src/StormLib.h index 9cdb28b..fcc1925 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -239,9 +239,6 @@ extern "C" { #define MPQ_KEY_HASH_TABLE 0xC3AF3770 // Obtained by HashString("(hash table)", MPQ_HASH_FILE_KEY) #define MPQ_KEY_BLOCK_TABLE 0xEC83B3A3 // Obtained by HashString("(block table)", MPQ_HASH_FILE_KEY) -// Block map defines -#define MPQ_DATA_BITMAP_SIGNATURE 0x33767470 // Signature of the MPQ data bitmap ('ptv3') - #define LISTFILE_NAME "(listfile)" // Name of internal listfile #define SIGNATURE_NAME "(signature)" // Name of internal signature #define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file @@ -274,6 +271,7 @@ extern "C" { #define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only #define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write +#define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it #define STREAM_FLAG_MASK 0x0000FF00 // Mask for stream flags #define STREAM_OPTIONS_MASK 0x0000FFFF // Mask for all stream options @@ -363,6 +361,7 @@ typedef enum _SFileInfoClass { // Info classes for archives SFileMpqFileName, // Name of the archive file (TCHAR []) + SFileMpqFileBitmap, // Bitmap of the archive (TFileBitmap + BYTE[]) SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG) SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData) SFileMpqUserData, // MPQ USer data, without the header (BYTE []) @@ -392,9 +391,6 @@ typedef enum _SFileInfoClass SFileMpqStrongSignatureOffset, // Byte offset of the strong signature, relative to begin of the file (ULONGLONG) SFileMpqStrongSignatureSize, // Size of the strong signature (DWORD) SFileMpqStrongSignature, // The strong signature (BYTE []) - SFileMpqBitmapOffset, // Byte offset of the MPQ bitmap, relative to begin of the file (ULONGLONG) - SFileMpqBitmapSize, // Size of the MPQ bitmap (DWORD) - SFileMpqBitmap, // The MPQ Bitmap (BYTE []) SFileMpqArchiveSize64, // Archive size from the header (ULONGLONG) SFileMpqArchiveSize, // Archive size from the header (DWORD) SFileMpqMaxFileCount, // Max number of files in the archive (DWORD) @@ -484,19 +480,6 @@ typedef struct _TBitArray void GetBits(TBitArray * array, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize); void SetBits(TBitArray * array, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize); -// Structure for file bitmap. Used by SFileGetArchiveBitmap -typedef struct _TFileBitmap -{ - ULONGLONG StartOffset; // Starting offset of the file, covered by bitmap - ULONGLONG EndOffset; // Ending offset of the file, covered by bitmap - DWORD IsComplete; // If nonzero, no blocks are missing - DWORD BitmapSize; // Size of the file bitmap (in bytes) - DWORD BlockSize; // Size of one block, in bytes - DWORD Reserved; // Alignment - - // Followed by file bitmap (variable length), array of BYTEs) -} TFileBitmap; - //----------------------------------------------------------------------------- // Structures related to MPQ format // @@ -712,6 +695,19 @@ typedef struct _TPatchHeader #define SIZE_OF_XFRM_HEADER 0x0C +// Structure for file bitmap. Used by SFileGetFileInfo(SFileMpqFileBitmap) +typedef struct _TFileBitmap +{ + ULONGLONG StartOffset; // Starting offset of the file, covered by bitmap + ULONGLONG EndOffset; // Ending offset of the file, covered by bitmap + DWORD BitmapSize; // Size of the file bitmap (in bytes) + DWORD BlockSize; // Size of one block, in bytes + DWORD BlockCount; // Number of data blocks in the file + DWORD IsComplete; // If nonzero, no blocks are missing + + // Followed by file bitmap (variable length), array of BYTEs) +} TFileBitmap; + // This is the combined file entry for maintaining file list in the MPQ. // This structure is combined from block table, hi-block table, // (attributes) file and from (listfile). @@ -786,22 +782,6 @@ typedef struct _TMPQBetHeader } TMPQBetHeader; -// -// MPQ data bitmap, can be found at (FileSize - sizeof(TMPQBlockMap)) -// -// There is bit map of the entire MPQ before TMPQBitmap. Each 0x4000-byte -// block is represented by one bit (including the last, eventually incomplete block). -// -typedef struct _TMPQBitmap -{ - DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE) - DWORD dwAlways3; // Unknown, seems to always have value of 3 - DWORD dwBuildNumber; // Game build number for that MPQ - DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map - DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map - DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) -} TMPQBitmap; - // Structure for parsed HET table typedef struct _TMPQHetTable { @@ -858,7 +838,6 @@ typedef struct _TMPQArchive TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file) TMPQHeader * pHeader; // MPQ file header - TMPQBitmap * pBitmap; // MPQ bitmap TMPQHash * pHashTable; // Hash table TMPQHetTable * pHetTable; // HET table TFileEntry * pFileTable; // File table @@ -869,7 +848,6 @@ typedef struct _TMPQArchive DWORD dwHETBlockSize; DWORD dwBETBlockSize; - DWORD dwBitmapSize; // sizeof(TMPQBitmap) + size of the bit array DWORD dwMaxFileCount; // Maximum number of files in the MPQ. Also total size of the file table. DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one DWORD dwReservedFiles; // Number of entries reserved for internal MPQ files (listfile, attributes) @@ -968,11 +946,6 @@ TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlag TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags); const TCHAR * FileStream_GetFileName(TFileStream * pStream); -//#ifdef _UNICODE -//TFileStream * FileStream_CreateFile(const char * szFileName, DWORD dwStreamFlags); -//TFileStream * FileStream_OpenFile(const char * szFileName, DWORD dwStreamFlags); -//#endif - bool FileStream_IsReadOnly(TFileStream * pStream); bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead); bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite); @@ -983,8 +956,7 @@ bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize); bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFT); bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags); bool FileStream_Switch(TFileStream * pStream, TFileStream * pTempStream); -bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap); -bool FileStream_GetBitmap(TFileStream * pStream, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded); +bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD Length, LPDWORD LengthNeeded); void FileStream_Close(TFileStream * pStream); //----------------------------------------------------------------------------- @@ -1013,7 +985,6 @@ bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq); bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq); -bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded); bool WINAPI SFileFlushArchive(HANDLE hMpq); bool WINAPI SFileCloseArchive(HANDLE hMpq); diff --git a/src/StormPort.h b/src/StormPort.h index 38726ab..0d98bcb 100644 --- a/src/StormPort.h +++ b/src/StormPort.h @@ -240,7 +240,6 @@ void ConvertUInt16Buffer(void * ptr, size_t length); void ConvertUInt32Buffer(void * ptr, size_t length); void ConvertUInt64Buffer(void * ptr, size_t length); - void ConvertPartHeader(void * partHeader); void ConvertTMPQUserData(void *userData); void ConvertTMPQHeader(void *header, uint16_t wPart); void ConvertTMPKHeader(void *header); @@ -256,7 +255,6 @@ #define BSWAP_ARRAY16_UNSIGNED(a,b) ConvertUInt16Buffer((a),(b)) #define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b)) #define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b)) - #define BSWAP_PART_HEADER(a) ConvertPartHeader(a) #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b)) #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a)) #endif diff --git a/test/Test.cpp b/test/Test.cpp index a9cc5e6..e3e78f0 100644 --- a/test/Test.cpp +++ b/test/Test.cpp @@ -196,13 +196,27 @@ static bool IsMpqExtension(const char * szFileName) return true; if(!_stricmp(szExtension, ".SC2Map")) return true; - if(!_stricmp(szExtension, ".link")) - return true; +// if(!_stricmp(szExtension, ".link")) +// return true; } return false; } +static bool CompareBlocks(LPBYTE pbBlock1, LPBYTE pbBlock2, DWORD dwLength, DWORD * pdwDifference) +{ + for(DWORD i = 0; i < dwLength; i++) + { + if(pbBlock1[i] != pbBlock2[i]) + { + pdwDifference[0] = i; + return false; + } + } + + return true; +} + static size_t ConvertSha1ToText(const unsigned char * sha1_digest, char * szSha1Text) { const char * szTable = "0123456789abcdef"; @@ -609,11 +623,6 @@ static int InitializeMpqDirectory(char * argv[], int argc) const char * szDirName; char szFullPath[MAX_PATH]; -#ifdef _MSC_VER - // Mix the random number generator - srand(GetTickCount()); -#endif - // Retrieve the name of the MPQ directory if(argc > 1 && argv[1] != NULL) { @@ -847,9 +856,6 @@ static int CopyFileData( { while(ByteOffset < EndOffset) { - // Notify the user - pLogger->PrintProgress("Copying %I64u of %I64u ...", BytesCopied, ByteCount); - // Read source BytesToRead = ((EndOffset - ByteOffset) > BlockLength) ? BlockLength : (DWORD)(EndOffset - ByteOffset); if(!FileStream_Read(pStream1, &ByteOffset, pbCopyBuffer, BytesToRead)) @@ -865,8 +871,12 @@ static int CopyFileData( break; } + // Increment the byte counts BytesCopied += BytesToRead; ByteOffset += BytesToRead; + + // Notify the user + pLogger->PrintProgress("Copying (%I64u of %I64u complete) ...", BytesCopied, ByteCount); } STORM_FREE(pbCopyBuffer); @@ -876,7 +886,7 @@ static int CopyFileData( } // Support function for copying file -static int CreateMpqCopy( +static int CreateFileCopy( TLogHelper * pLogger, const char * szPlainName, const char * szFileCopy, @@ -953,6 +963,30 @@ static int CreateMpqCopy( return nError; } +static int CreateMasterAndMirrorPaths( + TLogHelper * pLogger, + char * szMirrorPath, + char * szMasterPath, + const char * szMirrorName, + const char * szMasterName) +{ + char szCopyPath[MAX_PATH]; + int nError; + + // Copy the mirrored file from the source to the work directory + nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, szCopyPath); + if(nError == ERROR_SUCCESS) + { + // Create the full path name of the master file + CreateFullPathName(szMasterPath, szMpqSubDir, szMasterName); + + // Create the full path name of the mirror file + sprintf(szMirrorPath, "%s*%s", szCopyPath, szMasterPath); + } + + return nError; +} + static void WINAPI AddFileCallback(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall) { TLogHelper * pLogger = (TLogHelper *)pvUserData; @@ -1086,6 +1120,81 @@ static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName, return pFileData; } +static int CompareTwoLocalFilesRR( + TLogHelper * pLogger, + TFileStream * pStream1, // Master file + TFileStream * pStream2) // Mirror file +{ + ULONGLONG RandomNumber = 0x12345678; // We need pseudo-random number that will repeat each run of the program + ULONGLONG RandomSeed; + ULONGLONG ByteOffset; + ULONGLONG FileSize1 = 1; + ULONGLONG FileSize2 = 2; + DWORD BytesToRead; + DWORD Difference; + LPBYTE pbBuffer1; + LPBYTE pbBuffer2; + DWORD cbBuffer = 0x100000; + int nError = ERROR_SUCCESS; + + // Compare file sizes + FileStream_GetSize(pStream1, &FileSize1); + FileStream_GetSize(pStream2, &FileSize2); + if(FileSize1 != FileSize2) + { + pLogger->PrintMessage("The files have different size"); + return ERROR_CAN_NOT_COMPLETE; + } + + // Allocate both buffers + pbBuffer1 = STORM_ALLOC(BYTE, cbBuffer); + pbBuffer2 = STORM_ALLOC(BYTE, cbBuffer); + if(pbBuffer1 && pbBuffer2) + { + // Perform many random reads + for(int i = 0; i < 0x10000; i++) + { + // Generate psudo-random offsrt and data size + ByteOffset = (RandomNumber % FileSize1); + BytesToRead = (DWORD)(RandomNumber % cbBuffer); + + // Show the progress message + pLogger->PrintProgress("Comparing file: Offset: " I64u_a ", Length: %u", ByteOffset, BytesToRead); + + // Only perform read if the byte offset is below + if(ByteOffset < FileSize1) + { + if((ByteOffset + BytesToRead) > FileSize1) + BytesToRead = (DWORD)(FileSize1 - ByteOffset); + + memset(pbBuffer1, 0xEE, cbBuffer); + memset(pbBuffer2, 0xAA, cbBuffer); + + FileStream_Read(pStream1, &ByteOffset, pbBuffer1, BytesToRead); + FileStream_Read(pStream2, &ByteOffset, pbBuffer2, BytesToRead); + + if(!CompareBlocks(pbBuffer1, pbBuffer2, BytesToRead, &Difference)) + { + pLogger->PrintMessage("Difference at %u (Offset " I64u_a ", Length %u)", Difference, ByteOffset, BytesToRead); + nError = ERROR_FILE_CORRUPT; + break; + } + + // Shuffle the random number + memcpy(&RandomSeed, pbBuffer1, sizeof(RandomSeed)); + RandomNumber = ((RandomNumber >> 0x11) | (RandomNumber << 0x29)) ^ (RandomNumber + RandomSeed); + } + } + } + + // Free both buffers + if(pbBuffer2 != NULL) + STORM_FREE(pbBuffer2); + if(pbBuffer1 != NULL) + STORM_FREE(pbBuffer1); + return nError; +} + static TFileData * LoadMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName) { TFileData * pFileData = NULL; @@ -1390,7 +1499,7 @@ static int OpenExistingArchiveWithCopy(TLogHelper * pLogger, const char * szFile // If both names entered, create a copy if(szFileName != NULL && szCopyName != NULL) { - nError = CreateMpqCopy(pLogger, szFileName, szCopyName, szFullPath); + nError = CreateFileCopy(pLogger, szFileName, szCopyName, szFullPath); if(nError != ERROR_SUCCESS) return nError; } @@ -1654,8 +1763,37 @@ static int TestSearchListFile(const char * szPlainName) return ERROR_SUCCESS; } +// Open a file stream with mirroring a master file +static int TestReadFile_MasterMirror(const char * szMirrorName, const char * szMasterName) +{ + TFileStream * pStream1; // Master file + TFileStream * pStream2; // Mirror file + TLogHelper Logger("OpenMirrorFile", szMasterName); + char szMirrorPath[MAX_PATH + MAX_PATH]; + char szMasterPath[MAX_PATH]; + int nError; + + // Create copy of the file to serve as mirror, keep master there + nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName); + if(nError == ERROR_SUCCESS) + { + // Open both master and mirror file + pStream1 = OpenLocalFile(szMasterPath, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP); + pStream2 = OpenLocalFile(szMirrorPath, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP); + if(pStream1 && pStream2) + nError = CompareTwoLocalFilesRR(&Logger, pStream1, pStream2); + + if(pStream2 != NULL) + FileStream_Close(pStream2); + if(pStream1 != NULL) + FileStream_Close(pStream1); + } + + return nError; +} + // -static int TestPartFileRead(const char * szPlainName) +static int TestReadFile_Partial(const char * szPlainName) { TLogHelper Logger("PartFileRead", szPlainName); TMPQHeader Header; @@ -1810,6 +1948,27 @@ static int TestOpenArchive(const char * szPlainName, const char * szListFile = N return nError; } +static int TestOpenArchive_Corrupt(const char * szPlainName) +{ + TLogHelper Logger("OpenCorruptMpqTest", szPlainName); + HANDLE hMpq = NULL; + TCHAR szFullPathT[MAX_PATH]; + char szFullPath[MAX_PATH]; + + // Copy the archive so we won't fuck up the original one + CreateFullPathName(szFullPath, szMpqSubDir, szPlainName); + CopyFileName(szFullPathT, szFullPath, strlen(szFullPath)); + if(SFileOpenArchive(szFullPathT, 0, STREAM_FLAG_READ_ONLY, &hMpq)) + { + SFileCloseArchive(hMpq); + Logger.PrintMessage("Opening archive %s succeeded, but it shouldn't", szFullPath); + return ERROR_CAN_NOT_COMPLETE; + } + + return ERROR_SUCCESS; +} + + // Opens a patched MPQ archive static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatchedFile = NULL, int nExpectedPatchCount = 0) { @@ -1850,7 +2009,7 @@ static int TestOpenArchive_ReadOnly(const char * szPlainName, bool bReadOnly) // Copy the fiel so we wont screw up something szCopyName = bReadOnly ? "StormLibTest_ReadOnly.mpq" : "StormLibTest_ReadWrite.mpq"; - nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szFullPathName); + nError = CreateFileCopy(&Logger, szPlainName, szCopyName, szFullPathName); // Now open the archive for read-only access if(nError == ERROR_SUCCESS) @@ -1966,6 +2125,62 @@ static int TestOpenArchive_GetFileInfo(const char * szPlainName1, const char * s return ERROR_SUCCESS; } +static int TestOpenArchive_MasterMirror(const char * szMirrorName, const char * szMasterName, const char * szFileToExtract) +{ + TFileData * pFileData; + TLogHelper Logger("OpenServerMirror", szMirrorName); + HANDLE hFile = NULL; + HANDLE hMpq = NULL; + DWORD dwVerifyResult; + char szMirrorPath[MAX_PATH + MAX_PATH]; // Combined name + char szMasterPath[MAX_PATH]; // Original (server) name + int nError; + + // Create both paths + nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName); + + // Now open both archives as local-server pair + if(nError == ERROR_SUCCESS) + { + nError = OpenExistingArchive(&Logger, szMirrorPath, 0, &hMpq); + } + + // The MPQ must be read-only. Writing to mirrored MPQ is not allowed + if(nError == ERROR_SUCCESS) + { + if(SFileCreateFile(hMpq, "AddedFile.bin", 0, 0x10, 0, MPQ_FILE_COMPRESS, &hFile)) + { + SFileCloseFile(hFile); + Logger.PrintMessage("The archive is writable, although it should not be"); + nError = ERROR_FILE_CORRUPT; + } + } + + // Verify the file + if(nError == ERROR_SUCCESS && szFileToExtract) + { + dwVerifyResult = SFileVerifyFile(hMpq, szFileToExtract, SFILE_VERIFY_ALL); + if(dwVerifyResult & VERIFY_FILE_ERROR_MASK) + { + Logger.PrintMessage("File verification failed"); + nError = ERROR_FILE_CORRUPT; + } + } + + // Load the file to memory + if(nError == ERROR_SUCCESS && szFileToExtract) + { + pFileData = LoadMpqFile(&Logger, hMpq, szFileToExtract); + if(pFileData != NULL) + STORM_FREE(pFileData); + } + + if(hMpq != NULL) + SFileCloseArchive(hMpq); + return nError; +} + + static int TestOpenArchive_VerifySignature(const char * szPlainName, const char * szOriginalName) { TLogHelper Logger("VerifySignatureTest", szPlainName); @@ -2018,7 +2233,7 @@ static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char int nError; // Create copy of the archive, with interleaving some user data - nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szFullPath, 0x400, 0x531); + nError = CreateFileCopy(&Logger, szPlainName, szCopyName, szFullPath, 0x400, 0x531); // Open the archive and load some files if(nError == ERROR_SUCCESS) @@ -2892,18 +3107,22 @@ int main(int argc, char * argv[]) // Search all testing archives and verify their SHA1 hash if(nError == ERROR_SUCCESS) nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqDirectory); - - // Test opening local file with SFileOpenFileEx - if(nError == ERROR_SUCCESS) - nError = TestOpenLocalFile("ListFile_Blizzard.txt"); // Search in listfile if(nError == ERROR_SUCCESS) nError = TestSearchListFile("ListFile_Blizzard.txt"); + // Search in listfile + if(nError == ERROR_SUCCESS) + nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "MPQ_2013_v4_alternate-original.MPQ"); + // Test reading partial file if(nError == ERROR_SUCCESS) - nError = TestPartFileRead("MPQ_2009_v2_WoW_patch.MPQ.part"); + nError = TestReadFile_Partial("MPQ_2009_v2_WoW_patch.MPQ.part"); + + // Test opening local file with SFileOpenFileEx + if(nError == ERROR_SUCCESS) + nError = TestOpenLocalFile("ListFile_Blizzard.txt"); // Test working with an archive that has no listfile if(nError == ERROR_SUCCESS) @@ -2969,6 +3188,10 @@ int main(int argc, char * argv[]) if(nError == ERROR_SUCCESS) nError = FindFiles(ForEachFile_OpenArchive, szMpqDirectory); + // Test on an archive that has been invalidated by extending an old valid MPQ + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_Corrupt("MPQ_2013_vX_Battle.net.MPQ"); + // Open a patched archive if(nError == ERROR_SUCCESS) nError = TestOpenArchive_Patched(PatchList_WoW_OldWorld13286, "OldWorld\\World\\Model.blob", 2); @@ -2993,6 +3216,10 @@ int main(int argc, char * argv[]) if(nError == ERROR_SUCCESS) nError = TestOpenArchive_GetFileInfo("MPQ_2002_v1_StrongSignature.w3m", "MPQ_2013_v4_SC2_EmptyMap.SC2Map"); + // Downloadable MPQ archive + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "MPQ_2013_v4_alternate-original.MPQ", "alternate\\DUNGEONS\\TEXTURES\\ICECROWN\\GATE\\jlo_IceC_Floor_Thrown.blp"); + // Check archive signature if(nError == ERROR_SUCCESS) nError = TestOpenArchive_VerifySignature("MPQ_1999_v1_WeakSignature.exe", "War2Patch_202.exe"); |