aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2013-12-19 11:23:48 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2013-12-19 11:23:48 +0100
commitebd502e0c220f7c2d3b9ce65bd83ed569d65f314 (patch)
treecb3c0d82de0432448db4a09e9e0ef6d4e71beacb
parent55f159cf69d8b4816876653d25239abf6c3ef5a4 (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.cpp1096
-rw-r--r--src/FileStream.h70
-rw-r--r--src/SBaseCommon.cpp2
-rw-r--r--src/SBaseFileTable.cpp108
-rw-r--r--src/SFileGetFileInfo.cpp49
-rw-r--r--src/SFileOpenArchive.cpp58
-rw-r--r--src/StormCommon.h3
-rw-r--r--src/StormLib.h61
-rw-r--r--src/StormPort.h2
-rw-r--r--test/Test.cpp267
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");