aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/FileStream.cpp801
-rw-r--r--src/FileStream.h18
-rw-r--r--src/SBaseFileTable.cpp95
-rw-r--r--src/SFileAddFile.cpp2
-rw-r--r--src/SFileAttributes.cpp45
-rw-r--r--src/SFileCompactArchive.cpp2
-rw-r--r--src/SFileCreateArchive.cpp6
-rw-r--r--src/SFileExtractFile.cpp2
-rw-r--r--src/SFileGetFileInfo.cpp18
-rw-r--r--src/SFileOpenArchive.cpp31
-rw-r--r--src/SFileOpenFileEx.cpp2
-rw-r--r--src/SFilePatchArchives.cpp2
-rw-r--r--src/StormCommon.h2
-rw-r--r--src/StormLib.h38
-rw-r--r--test/TLogHelper.cpp6
-rw-r--r--test/Test.cpp370
16 files changed, 1051 insertions, 389 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp
index 58543e3..c196f11 100644
--- a/src/FileStream.cpp
+++ b/src/FileStream.cpp
@@ -97,7 +97,7 @@ static bool BaseFile_Create(TFileStream * pStream)
return true;
}
-static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags)
+static bool BaseFile_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
{
#ifdef PLATFORM_WINDOWS
{
@@ -106,7 +106,7 @@ static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags)
DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
// Open the file
- pStream->Base.File.hFile = CreateFile(pStream->szFileName,
+ pStream->Base.File.hFile = CreateFile(szFileName,
FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess,
FILE_SHARE_READ | dwWriteShare,
NULL,
@@ -417,7 +417,7 @@ static void BaseFile_Init(TFileStream * pStream)
//-----------------------------------------------------------------------------
// Local functions - base memory-mapped file support
-static bool BaseMap_Open(TFileStream * pStream, DWORD dwStreamFlags)
+static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
{
#ifdef PLATFORM_WINDOWS
@@ -430,7 +430,7 @@ static bool BaseMap_Open(TFileStream * pStream, DWORD dwStreamFlags)
dwStreamFlags = dwStreamFlags;
// Open the file for read access
- hFile = CreateFile(pStream->szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ hFile = CreateFile(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.
@@ -588,11 +588,10 @@ static const TCHAR * BaseHttp_ExtractServerName(const TCHAR * szFileName, TCHAR
return szFileName;
}
-static bool BaseHttp_Open(TFileStream * pStream, DWORD dwStreamFlags)
+static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
{
#ifdef PLATFORM_WINDOWS
- const TCHAR * szFileName;
HINTERNET hRequest;
DWORD dwTemp = 0;
bool bFileAvailable = false;
@@ -624,7 +623,7 @@ static bool BaseHttp_Open(TFileStream * pStream, DWORD dwStreamFlags)
DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE;
// Initiate connection with the server
- szFileName = BaseHttp_ExtractServerName(pStream->szFileName, szServerName);
+ szFileName = BaseHttp_ExtractServerName(szFileName, szServerName);
pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet,
szServerName,
INTERNET_DEFAULT_HTTP_PORT,
@@ -821,11 +820,11 @@ static bool BlockStream_Read(
DWORD BlockSize = pStream->BlockSize;
DWORD BlockCount;
bool bPrevBlockAvailable;
+ bool bCallbackCalled = false;
bool bBlockAvailable;
bool bResult = true;
// The base block read function must be present
- assert(pStream->BlockCheck != NULL);
assert(pStream->BlockRead != NULL);
// NOP reading of zero bytes
@@ -837,7 +836,7 @@ static bool BlockStream_Read(
EndOffset = ByteOffset + dwBytesToRead;
if(EndOffset > pStream->StreamSize)
{
- SetLastError(ERROR_CAN_NOT_COMPLETE);
+ SetLastError(ERROR_HANDLE_EOF);
return false;
}
@@ -863,6 +862,7 @@ static bool BlockStream_Read(
{
// Now parse the blocks and send the block read request
// to all blocks with the same availability
+ assert(pStream->BlockCheck != NULL);
bPrevBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
// Loop as long as we have something to read
@@ -874,6 +874,13 @@ static bool BlockStream_Read(
// If the availability has changed, read all blocks up to this one
if(bBlockAvailable != bPrevBlockAvailable)
{
+ // Call the file stream callback, if the block is not available
+ if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false)
+ {
+ pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0));
+ bCallbackCalled = true;
+ }
+
// Load the continuous blocks with the same availability
assert(BlockOffset > BlockOffset0);
bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
@@ -894,6 +901,13 @@ static bool BlockStream_Read(
// If there is a block(s) remaining to be read, do it
if(BlockOffset > BlockOffset0)
{
+ // Call the file stream callback, if the block is not available
+ if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false)
+ {
+ pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0));
+ bCallbackCalled = true;
+ }
+
// Read the complete blocks from the file
if(BlockOffset > pStream->StreamSize)
BlockOffset = pStream->StreamSize;
@@ -914,6 +928,15 @@ static bool BlockStream_Read(
memcpy(pvBuffer, TransferBuffer + BlockBufferOffset, dwBytesToRead);
pStream->StreamPos = ByteOffset + dwBytesToRead;
}
+ else
+ {
+ // If the block read failed, set the last error
+ SetLastError(ERROR_FILE_INCOMPLETE);
+ }
+
+ // Call the callback to indicate we are done
+ if(bCallbackCalled)
+ pStream->pfnCallback(pStream->UserData, 0, 0);
// Free the block buffer and return
STORM_FREE(TransferBuffer);
@@ -955,7 +978,7 @@ static STREAM_INIT StreamBaseInit[4] =
};
// This function allocates an empty structure for the file stream
-// The stream structure is created as variable length, linear block of data
+// The stream structure is created as flat block, variable length
// The file name is placed after the end of the stream structure data
static TFileStream * AllocateFileStream(
const TCHAR * szFileName,
@@ -1015,9 +1038,9 @@ static TFileStream * AllocateFileStream(
}
//-----------------------------------------------------------------------------
-// Local functions - linear stream support
+// Local functions - flat stream support
-static DWORD LinearStream_CheckFile(TBlockStream * pStream)
+static DWORD FlatStream_CheckFile(TBlockStream * pStream)
{
LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
DWORD WholeByteCount = (pStream->BlockCount / 8);
@@ -1043,7 +1066,7 @@ static DWORD LinearStream_CheckFile(TBlockStream * pStream)
return 1;
}
-static bool LinearStream_LoadBitmap(TBlockStream * pStream)
+static bool FlatStream_LoadBitmap(TBlockStream * pStream)
{
FILE_BITMAP_FOOTER Footer;
ULONGLONG ByteOffset;
@@ -1060,7 +1083,7 @@ static bool LinearStream_LoadBitmap(TBlockStream * pStream)
{
// Load the bitmap footer
ByteOffset = pStream->Base.File.FileSize - sizeof(FILE_BITMAP_FOOTER);
- if(pStream->StreamRead(pStream, &ByteOffset, &Footer, sizeof(FILE_BITMAP_FOOTER)))
+ if(pStream->BaseRead(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));
@@ -1076,7 +1099,7 @@ static bool LinearStream_LoadBitmap(TBlockStream * pStream)
// Check if the sizes match
if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize)
{
- // Allocate space for the linear bitmap
+ // Allocate space for the bitmap
FileBitmap = STORM_ALLOC(BYTE, BitmapSize);
if(FileBitmap != NULL)
{
@@ -1095,7 +1118,7 @@ static bool LinearStream_LoadBitmap(TBlockStream * pStream)
pStream->BitmapSize = BitmapSize;
pStream->BlockSize = Footer.BlockSize;
pStream->BlockCount = BlockCount;
- pStream->IsComplete = LinearStream_CheckFile(pStream);
+ pStream->IsComplete = FlatStream_CheckFile(pStream);
return true;
}
}
@@ -1106,7 +1129,7 @@ static bool LinearStream_LoadBitmap(TBlockStream * pStream)
return false;
}
-static void LinearStream_UpdateBitmap(
+static void FlatStream_UpdateBitmap(
TBlockStream * pStream, // Pointer to an open stream
ULONGLONG StartOffset,
ULONGLONG EndOffset)
@@ -1137,9 +1160,12 @@ static void LinearStream_UpdateBitmap(
ByteIndex += (BitMask >> 0x07);
BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
}
+
+ // Increment the bitmap update count
+ pStream->IsModified = 1;
}
-static bool LinearStream_BlockCheck(
+static bool FlatStream_BlockCheck(
TBlockStream * pStream, // Pointer to an open stream
ULONGLONG BlockOffset)
{
@@ -1159,7 +1185,7 @@ static bool LinearStream_BlockCheck(
return (FileBitmap[BlockIndex / 0x08] & BitMask) ? true : false;
}
-static bool LinearStream_BlockRead(
+static bool FlatStream_BlockRead(
TBlockStream * pStream, // Pointer to an open stream
ULONGLONG StartOffset,
ULONGLONG EndOffset,
@@ -1170,7 +1196,7 @@ static bool LinearStream_BlockRead(
DWORD BytesToRead = (DWORD)(EndOffset - StartOffset);
// The starting offset must be aligned to size of the block
- assert(pStream->FileBitmap != NULL && pStream->IsComplete == 0);
+ assert(pStream->FileBitmap != NULL);
assert((StartOffset & (pStream->BlockSize - 1)) == 0);
assert(StartOffset < EndOffset);
@@ -1190,8 +1216,8 @@ static bool LinearStream_BlockRead(
// Store the loaded blocks to the mirror file.
// Note that this operation is not required to succeed
- if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead) && bAvailable == false)
- LinearStream_UpdateBitmap(pStream, StartOffset, EndOffset);
+ if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead))
+ FlatStream_UpdateBitmap(pStream, StartOffset, EndOffset);
return true;
}
@@ -1203,16 +1229,107 @@ static bool LinearStream_BlockRead(
}
}
-static bool LinearStream_CreateMirror(TBlockStream * pStream)
+static void FlatStream_Close(TBlockStream * pStream)
+{
+ FILE_BITMAP_FOOTER Footer;
+
+ if(pStream->FileBitmap && pStream->IsModified)
+ {
+ // Write the file bitmap
+ pStream->BaseWrite(pStream, &pStream->StreamSize, pStream->FileBitmap, pStream->BitmapSize);
+
+ // Prepare and write the file footer
+ Footer.Signature = ID_FILE_BITMAP_FOOTER;
+ Footer.Version = 3;
+ Footer.BuildNumber = 10958; // BUGBUG: What build number shall we set???
+ Footer.MapOffsetLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF);
+ Footer.MapOffsetHi = (DWORD)(pStream->StreamSize >> 0x20);
+ Footer.BlockSize = pStream->BlockSize;
+ BSWAP_ARRAY32_UNSIGNED(&Footer, sizeof(FILE_BITMAP_FOOTER));
+ pStream->BaseWrite(pStream, NULL, &Footer, sizeof(FILE_BITMAP_FOOTER));
+ }
+
+ // Close the base class
+ BlockStream_Close(pStream);
+}
+
+static bool FlatStream_CreateMirror(TBlockStream * pStream)
{
+ ULONGLONG MasterSize = 0;
+ ULONGLONG MirrorSize = 0;
+ LPBYTE FileBitmap = NULL;
+ DWORD dwBitmapSize;
+ DWORD dwBlockCount;
+ bool bNeedCreateMirrorStream = true;
+ bool bNeedResizeMirrorStream = true;
+
// Do we have master function and base creation function?
if(pStream->pMaster == NULL || pStream->BaseCreate == NULL)
return false;
- return false;
+ // Retrieve the master file size, block count and bitmap size
+ FileStream_GetSize(pStream->pMaster, &MasterSize);
+ dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
+ dwBitmapSize = (DWORD)((dwBlockCount + 7) / 8);
+
+ // Setup stream size and position
+ pStream->StreamSize = MasterSize;
+ pStream->StreamPos = 0;
+
+ // Open the base stream for write access
+ if(pStream->BaseOpen(pStream, pStream->szFileName, 0))
+ {
+ // If the file open succeeded, check if the file size matches required size
+ pStream->BaseGetSize(pStream, &MirrorSize);
+ if(MirrorSize == MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER))
+ {
+ // Attempt to load an existing file bitmap
+ if(FlatStream_LoadBitmap(pStream))
+ return true;
+
+ // We need to create new file bitmap
+ bNeedResizeMirrorStream = false;
+ }
+
+ // We need to create mirror stream
+ bNeedCreateMirrorStream = false;
+ }
+
+ // Create a new stream, if needed
+ if(bNeedCreateMirrorStream)
+ {
+ if(!pStream->BaseCreate(pStream))
+ return false;
+ }
+
+ // If we need to, then resize the mirror stream
+ if(bNeedResizeMirrorStream)
+ {
+ if(!pStream->BaseResize(pStream, MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER)))
+ return false;
+ }
+
+ // Allocate the bitmap array
+ FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize);
+ if(FileBitmap == NULL)
+ return false;
+
+ // Initialize the bitmap
+ memset(FileBitmap, 0, dwBitmapSize);
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = dwBitmapSize;
+ pStream->BlockSize = DEFAULT_BLOCK_SIZE;
+ pStream->BlockCount = dwBlockCount;
+ pStream->IsComplete = 0;
+ pStream->IsModified = 1;
+
+ // Note: Don't write the stream bitmap right away.
+ // Doing so would cause sparse file resize on NTFS,
+ // which would take long time on larger files.
+ return true;
}
-static TFileStream * LinearStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
{
TBlockStream * pStream;
@@ -1221,53 +1338,63 @@ static TFileStream * LinearStream_Open(const TCHAR * szFileName, DWORD dwStreamF
if(pStream == NULL)
return NULL;
- // Attempt to open the base stream. If this fails
- // and we have a master stream, we can create new mirror
- assert(pStream->BaseOpen != NULL);
- if(!pStream->BaseOpen(pStream, dwStreamFlags))
+ // Do we have a master stream?
+ if(pStream->pMaster != NULL)
{
- // Do we have base create function and master stream?
-// if(!LinearStream_CreateMirror(pStream))
+ if(!FlatStream_CreateMirror(pStream))
{
FileStream_Close(pStream);
SetLastError(ERROR_FILE_NOT_FOUND);
return NULL;
}
}
+ else
+ {
+ // Attempt to open the base stream
+ if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
+ return false;
- // Set the stream function as if this was linear file without bitmap
- pStream->StreamRead = pStream->BaseRead;
- pStream->StreamWrite = pStream->BaseWrite;
- pStream->StreamResize = pStream->BaseResize;
- pStream->StreamGetSize = pStream->BaseGetSize;
- pStream->StreamGetPos = pStream->BaseGetPos;
- pStream->StreamClose = pStream->BaseClose;
+ // Load the bitmap, if required to
+ if(dwStreamFlags & STREAM_FLAG_USE_BITMAP)
+ FlatStream_LoadBitmap(pStream);
- // Setup the stream size
- pStream->StreamSize = pStream->Base.File.FileSize;
+ // Setup the stream size
+ if(pStream->FileBitmap == NULL)
+ pStream->StreamSize = pStream->Base.File.FileSize;
+ pStream->StreamPos = 0;
+ }
- // If we load the bitmap and find out that it's an incomplete file,
- // We set the reading functions which check presence of each file block
- if(LinearStream_LoadBitmap(pStream))
+ // If we have a stream bitmap, set the reading functions
+ // which check presence of each file block
+ if(pStream->FileBitmap != NULL)
{
// Set the stream position to zero. Stream size is already set
assert(pStream->StreamSize != 0);
pStream->StreamPos = 0;
pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- // Set the reading function
- if(pStream->IsComplete == 0)
- {
- // Supply the reading function
- pStream->StreamRead = (STREAM_READ)BlockStream_Read;
- pStream->StreamGetSize = BlockStream_GetSize;
- pStream->StreamGetPos = BlockStream_GetPos;
- pStream->StreamClose = (STREAM_CLOSE)BlockStream_Close;
-
- // Supply the block functions
- pStream->BlockCheck = (BLOCK_CHECK)LinearStream_BlockCheck;
- pStream->BlockRead = (BLOCK_READ)LinearStream_BlockRead;
- }
+ // Supply the stream functions
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamClose = (STREAM_CLOSE)FlatStream_Close;
+
+ // Supply the block functions
+ pStream->BlockCheck = (BLOCK_CHECK)FlatStream_BlockCheck;
+ pStream->BlockRead = (BLOCK_READ)FlatStream_BlockRead;
+ }
+ else
+ {
+ // Reset the file position to zero
+ pStream->Base.File.FilePos = 0;
+
+ // Set the base functions
+ pStream->StreamRead = pStream->BaseRead;
+ pStream->StreamWrite = pStream->BaseWrite;
+ pStream->StreamResize = pStream->BaseResize;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamClose = pStream->BaseClose;
}
return pStream;
@@ -1440,7 +1567,7 @@ static TFileStream * PartialStream_Open(const TCHAR * szFileName, DWORD dwStream
// Attempt to open the base stream. If this fails
// and we have a master stream, we can create new mirror
assert(pStream->BaseOpen != NULL);
- if(!pStream->BaseOpen(pStream, dwStreamFlags))
+ if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
{
// Do we have base create function and master stream?
// if(!PartialStream_CreateMirror(pStream))
@@ -1478,7 +1605,7 @@ static TFileStream * PartialStream_Open(const TCHAR * szFileName, DWORD dwStream
}
//-----------------------------------------------------------------------------
-// Local functions - encrypted stream support
+// Local functions - MPQE stream support
static const char * szKeyTemplate = "expand 32-byte k000000000000000000000000000000000000000000000000";
@@ -1672,7 +1799,7 @@ static void DecryptFileChunk(
}
}
-static bool EncryptedStream_DetectFileKey(TEncryptedStream * pStream)
+static bool MpqeStream_DetectFileKey(TEncryptedStream * pStream)
{
ULONGLONG ByteOffset = 0;
BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
@@ -1712,12 +1839,7 @@ static bool EncryptedStream_DetectFileKey(TEncryptedStream * pStream)
return false;
}
-static bool EncryptedStream_BlockCheck(TEncryptedStream *, ULONGLONG)
-{
- return true;
-}
-
-static bool EncryptedStream_BlockRead(
+static bool MpqeStream_BlockRead(
TEncryptedStream * pStream,
ULONGLONG StartOffset,
ULONGLONG EndOffset,
@@ -1731,6 +1853,7 @@ static bool EncryptedStream_BlockRead(
assert(StartOffset < EndOffset);
assert(bAvailable != false);
BytesNeeded = BytesNeeded;
+ bAvailable = bAvailable;
// Read the file from the stream as-is
// Limit the reading to number of blocks really needed
@@ -1744,67 +1867,7 @@ static bool EncryptedStream_BlockRead(
return true;
}
-/*
-static bool EncryptedStream_Read(
- TEncryptedStream * pStream, // Pointer to an open stream
- ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
- void * pvBuffer, // Pointer to data to be read
- DWORD dwBytesToRead) // Number of bytes to read from the file
-{
- ULONGLONG StartOffset; // Offset of the first byte to be read from the file
- ULONGLONG ByteOffset; // Offset that the caller wants
- ULONGLONG EndOffset; // End offset that is to be read from the file
- DWORD dwBytesToAllocate;
- DWORD dwBytesToDecrypt;
- DWORD dwOffsetInCache;
- LPBYTE pbMpqData = NULL;
- bool bResult = false;
-
- // Get the byte offset
- ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->StreamPos;
-
- // Cut it down to MPQE chunk size
- StartOffset = ByteOffset;
- StartOffset = StartOffset & ~(MPQE_CHUNK_SIZE - 1);
- EndOffset = ByteOffset + dwBytesToRead;
-
- // Calculate number of bytes to decrypt
- dwBytesToDecrypt = (DWORD)(EndOffset - StartOffset);
- dwBytesToAllocate = (dwBytesToDecrypt + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1);
-
- // Allocate buffers for encrypted and decrypted data
- pbMpqData = STORM_ALLOC(BYTE, dwBytesToAllocate);
- if(pbMpqData)
- {
- // Get the offset of the desired data in the cache
- dwOffsetInCache = (DWORD)(ByteOffset - StartOffset);
-
- // Read the file from the stream as-is
- if(pStream->BaseRead(pStream, &StartOffset, pbMpqData, dwBytesToDecrypt))
- {
- // Decrypt the data
- DecryptFileChunk((LPDWORD)pbMpqData, pStream->Key, StartOffset, dwBytesToAllocate);
-
- // Copy the decrypted data
- memcpy(pvBuffer, pbMpqData + dwOffsetInCache, dwBytesToRead);
- bResult = true;
- }
- else
- {
- assert(false);
- }
-
- // Free decryption buffer
- STORM_FREE(pbMpqData);
- }
-
- // Update stream position
- pStream->StreamPos = ByteOffset + dwBytesToRead;
- return bResult;
-}
-*/
-
-static TFileStream * EncryptedStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+static TFileStream * MpqeStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
{
TEncryptedStream * pStream;
@@ -1815,11 +1878,11 @@ static TFileStream * EncryptedStream_Open(const TCHAR * szFileName, DWORD dwStre
// Attempt to open the base stream
assert(pStream->BaseOpen != NULL);
- if(!pStream->BaseOpen(pStream, dwStreamFlags))
+ if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
return NULL;
// Determine the encryption key for the MPQ
- if(EncryptedStream_DetectFileKey(pStream))
+ if(MpqeStream_DetectFileKey(pStream))
{
// Set the stream position and size
assert(pStream->StreamSize != 0);
@@ -1833,8 +1896,7 @@ static TFileStream * EncryptedStream_Open(const TCHAR * szFileName, DWORD dwStre
pStream->StreamClose = pStream->BaseClose;
// Supply the block functions
- pStream->BlockCheck = (BLOCK_CHECK)EncryptedStream_BlockCheck;
- pStream->BlockRead = (BLOCK_READ)EncryptedStream_BlockRead;
+ pStream->BlockRead = (BLOCK_READ)MpqeStream_BlockRead;
return pStream;
}
@@ -1845,6 +1907,203 @@ static TFileStream * EncryptedStream_Open(const TCHAR * szFileName, DWORD dwStre
}
//-----------------------------------------------------------------------------
+// Local functions - Block4 stream support
+
+#define BLOCK4_BLOCK_SIZE 0x4000 // Size of one block
+#define BLOCK4_HASH_SIZE 0x20 // Size of MD5 hash that is after each block
+#define BLOCK4_MAX_BLOCKS 0x00002000 // Maximum amount of blocks per file
+#define BLOCK4_MAX_FSIZE 0x08040000 // Max size of one file
+
+static bool Block4Stream_BlockRead(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
+{
+ TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap;
+ ULONGLONG ByteOffset;
+ DWORD BytesToRead;
+ DWORD StreamIndex;
+ DWORD BlockIndex;
+
+ // The starting offset must be aligned to size of the block
+ assert(pStream->FileBitmap != NULL);
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+ assert(bAvailable == true);
+
+ while(BytesNeeded != 0)
+ {
+ // Calculate the block index and the file index
+ StreamIndex = (DWORD)((StartOffset / pStream->BlockSize) / BLOCK4_MAX_BLOCKS);
+ BlockIndex = (DWORD)((StartOffset / pStream->BlockSize) % BLOCK4_MAX_BLOCKS);
+ if(StreamIndex > pStream->BitmapSize)
+ return false;
+
+ // Prepare the appropriate stream to the base variables
+ memcpy(&pStream->Base, BaseArray + StreamIndex, sizeof(TBaseProviderData));
+
+ // Calculate the block offset
+ ByteOffset = ((ULONGLONG)BlockIndex * (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE));
+ BytesToRead = min(BytesNeeded, BLOCK4_BLOCK_SIZE);
+
+ // Read from the base stream
+ if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead))
+ return false;
+
+ // Move pointers
+ StartOffset += BytesToRead;
+ BlockBuffer += BytesToRead;
+ BytesNeeded -= BytesToRead;
+ }
+
+ return true;
+}
+
+
+static void Block4Stream_Close(TBlockStream * pStream)
+{
+ TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap;
+
+ // If we have a non-zero count of base streams,
+ // we have to close them all
+ if(BaseArray != NULL)
+ {
+ // Close all base streams
+ for(DWORD i = 0; i < pStream->BitmapSize; i++)
+ {
+ memcpy(&pStream->Base, BaseArray + i, sizeof(TBaseProviderData));
+ pStream->BaseClose(pStream);
+ }
+ }
+
+ // Free the data map, if any
+ if(pStream->FileBitmap != NULL)
+ STORM_FREE(pStream->FileBitmap);
+ pStream->FileBitmap = NULL;
+
+ // Do not call the BaseClose function,
+ // we closed all handles already
+ return;
+}
+
+static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+ TBaseProviderData * NewBaseArray = NULL;
+ ULONGLONG RemainderBlock;
+ ULONGLONG BlockCount;
+ ULONGLONG FileSize;
+ TBlockStream * pStream;
+ TCHAR * szNameBuff;
+ size_t nNameLength;
+ DWORD dwBaseFiles = 0;
+ DWORD dwBaseFlags;
+
+ // Create new empty stream
+ pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
+
+ // Sanity check
+ assert(pStream->BaseOpen != NULL);
+
+ // Get the length of the file name without numeric suffix
+ nNameLength = _tcslen(pStream->szFileName);
+ if(pStream->szFileName[nNameLength - 2] == '.' && pStream->szFileName[nNameLength - 1] == '0')
+ nNameLength -= 2;
+ pStream->szFileName[nNameLength] = 0;
+
+ // Supply the stream functions
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamClose = (STREAM_CLOSE)Block4Stream_Close;
+ pStream->BlockRead = (BLOCK_READ)Block4Stream_BlockRead;
+
+ // Allocate work space for numeric names
+ szNameBuff = STORM_ALLOC(TCHAR, nNameLength + 4);
+ if(szNameBuff != NULL)
+ {
+ // Set the base flags
+ dwBaseFlags = (dwStreamFlags & STREAM_PROVIDERS_MASK) | STREAM_FLAG_READ_ONLY;
+
+ // Go all suffixes from 0 to 30
+ for(int nSuffix = 0; nSuffix < 30; nSuffix++)
+ {
+ // Open the n-th file
+ _stprintf(szNameBuff, _T("%s.%u"), pStream->szFileName, nSuffix);
+ if(!pStream->BaseOpen(pStream, szNameBuff, dwBaseFlags))
+ break;
+
+ // If the open succeeded, we re-allocate the base provider array
+ NewBaseArray = STORM_ALLOC(TBaseProviderData, dwBaseFiles + 1);
+ if(NewBaseArray == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return false;
+ }
+
+ // Copy the old base data array to the new base data array
+ if(pStream->FileBitmap != NULL)
+ {
+ memcpy(NewBaseArray, pStream->FileBitmap, sizeof(TBaseProviderData) * dwBaseFiles);
+ STORM_FREE(pStream->FileBitmap);
+ }
+
+ // Also copy the opened base array
+ memcpy(NewBaseArray + dwBaseFiles, &pStream->Base, sizeof(TBaseProviderData));
+ pStream->FileBitmap = NewBaseArray;
+ dwBaseFiles++;
+
+ // Get the size of the base stream
+ pStream->BaseGetSize(pStream, &FileSize);
+ assert(FileSize <= BLOCK4_MAX_FSIZE);
+ RemainderBlock = FileSize % (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE);
+ BlockCount = FileSize / (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE);
+
+ // Increment the stream size and number of blocks
+ pStream->StreamSize += (BlockCount * BLOCK4_BLOCK_SIZE);
+ pStream->BlockCount += (DWORD)BlockCount;
+
+ // Is this the last file?
+ if(FileSize < BLOCK4_MAX_FSIZE)
+ {
+ if(RemainderBlock)
+ {
+ pStream->StreamSize += (RemainderBlock - BLOCK4_HASH_SIZE);
+ pStream->BlockCount++;
+ }
+ break;
+ }
+ }
+
+ // Fill the remainining block stream variables
+ pStream->BitmapSize = dwBaseFiles;
+ pStream->BlockSize = BLOCK4_BLOCK_SIZE;
+ pStream->IsComplete = 1;
+ pStream->IsModified = 0;
+
+ // Fill the remaining stream variables
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+
+ STORM_FREE(szNameBuff);
+ }
+
+ // If we opened something, return success
+ if(dwBaseFiles == 0)
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ pStream = NULL;
+ }
+
+ return pStream;
+}
+
+//-----------------------------------------------------------------------------
// Public functions
/**
@@ -1870,14 +2129,14 @@ TFileStream * FileStream_CreateFile(
{
TFileStream * pStream;
- // We only support creation of linear, local file
- if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
+ // We only support creation of flat, local file
+ if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
{
SetLastError(ERROR_NOT_SUPPORTED);
return NULL;
}
- // Allocate file stream structure for linear stream
+ // Allocate file stream structure for flat stream
pStream = AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
if(pStream != NULL)
{
@@ -1924,41 +2183,27 @@ TFileStream * FileStream_OpenFile(
const TCHAR * szFileName,
DWORD dwStreamFlags)
{
- 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;
- }
+ DWORD dwProvider = dwStreamFlags & STREAM_PROVIDERS_MASK;
+ size_t nPrefixLength = FileStream_Prefix(szFileName, &dwProvider);
// Re-assemble the stream flags
- dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | (dwStreamFlags & STREAM_PROVIDER_MASK) | dwBaseProvider;
+ dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | dwProvider;
+ szFileName += nPrefixLength;
+
+ // Perform provider-specific open
switch(dwStreamFlags & STREAM_PROVIDER_MASK)
{
- case STREAM_PROVIDER_LINEAR:
- return LinearStream_Open(szFileName, dwStreamFlags);
+ case STREAM_PROVIDER_FLAT:
+ return FlatStream_Open(szFileName, dwStreamFlags);
case STREAM_PROVIDER_PARTIAL:
return PartialStream_Open(szFileName, dwStreamFlags);
- case STREAM_PROVIDER_ENCRYPTED:
- return EncryptedStream_Open(szFileName, dwStreamFlags);
+ case STREAM_PROVIDER_MPQE:
+ return MpqeStream_Open(szFileName, dwStreamFlags);
+
+ case STREAM_PROVIDER_BLOCK4:
+ return Block4Stream_Open(szFileName, dwStreamFlags);
default:
SetLastError(ERROR_INVALID_PARAMETER);
@@ -1967,6 +2212,215 @@ TFileStream * FileStream_OpenFile(
}
/**
+ * Returns the file name of the stream
+ *
+ * \a pStream Pointer to an open stream
+ */
+const TCHAR * FileStream_GetFileName(TFileStream * pStream)
+{
+ assert(pStream != NULL);
+ return pStream->szFileName;
+}
+
+/**
+ * Returns the length of the provider prefix. Returns zero if no prefix
+ *
+ * \a szFileName Pointer to a stream name (file, mapped file, URL)
+ * \a pdwStreamProvider Pointer to a DWORD variable that receives stream provider (STREAM_PROVIDER_XXX)
+ */
+
+size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider)
+{
+ size_t nPrefixLength1 = 0;
+ size_t nPrefixLength2 = 0;
+ DWORD dwProvider = 0;
+
+ if(szFileName != NULL)
+ {
+ //
+ // Determine the stream provider
+ //
+
+ if(!_tcsnicmp(szFileName, _T("flat-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_FLAT;
+ nPrefixLength1 = 5;
+ }
+
+ if(!_tcsnicmp(szFileName, _T("part-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_PARTIAL;
+ nPrefixLength1 = 5;
+ }
+
+ if(!_tcsnicmp(szFileName, _T("mpqe-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_MPQE;
+ nPrefixLength1 = 5;
+ }
+
+ if(!_tcsnicmp(szFileName, _T("blk4-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_BLOCK4;
+ nPrefixLength1 = 5;
+ }
+
+ //
+ // Determine the base provider
+ //
+
+ if(!_tcsnicmp(szFileName+nPrefixLength1, _T("file:"), 5))
+ {
+ dwProvider |= BASE_PROVIDER_FILE;
+ nPrefixLength2 = 5;
+ }
+
+ if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4))
+ {
+ dwProvider |= BASE_PROVIDER_MAP;
+ nPrefixLength2 = 4;
+ }
+
+ if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5))
+ {
+ dwProvider |= BASE_PROVIDER_HTTP;
+ nPrefixLength2 = 5;
+ }
+
+ // Only accept stream provider if we recognized the base provider
+ if(nPrefixLength2 != 0)
+ {
+ // It is also allowed to put "//" after the base provider, e.g. "file://", "http://"
+ if(szFileName[nPrefixLength1+nPrefixLength2] == '/' && szFileName[nPrefixLength1+nPrefixLength2+1] == '/')
+ nPrefixLength2 += 2;
+
+ if(pdwProvider != NULL)
+ *pdwProvider = dwProvider;
+ return nPrefixLength1 + nPrefixLength2;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Sets a download callback. Whenever the stream needs to download one or more blocks
+ * from the server, the callback is called
+ *
+ * \a pStream Pointer to an open stream
+ * \a pfnCallback Pointer to callback function
+ * \a pvUserData Arbitrary user pointer passed to the download callback
+ */
+
+bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData)
+{
+ TBlockStream * pBlockStream = (TBlockStream *)pStream;
+
+ if(pStream->BlockRead == NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+ }
+
+ pBlockStream->pfnCallback = pfnCallback;
+ pBlockStream->UserData = pvUserData;
+ return true;
+}
+
+/**
+ * This function gives the block map. The 'pvBitmap' pointer must point to a buffer
+ * of at least sizeof(STREAM_BLOCK_MAP) size. It can also have size of the complete
+ * block map (i.e. sizeof(STREAM_BLOCK_MAP) + BitmapSize). In that case, the function
+ * also copies the bit-based block map.
+ *
+ * \a pStream Pointer to an open stream
+ * \a pvBitmap Pointer to buffer where the block map will be stored
+ * \a cbBitmap Length of the buffer, of the block map
+ * \a cbLengthNeeded Length of the bitmap, in bytes
+ */
+
+bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, PDWORD pcbLengthNeeded)
+{
+ TStreamBitmap * pBitmap = (TStreamBitmap *)pvBitmap;
+ TBlockStream * pBlockStream = (TBlockStream *)pStream;
+ ULONGLONG BlockOffset;
+ LPBYTE Bitmap = (LPBYTE)(pBitmap + 1);
+ DWORD BitmapSize;
+ DWORD BlockCount;
+ DWORD BlockSize;
+ bool bResult = false;
+
+ // Retrieve the size of one block
+ if(pStream->BlockCheck != NULL)
+ {
+ BlockCount = pBlockStream->BlockCount;
+ BlockSize = pBlockStream->BlockSize;
+ }
+ else
+ {
+ BlockCount = (DWORD)((pStream->StreamSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
+ BlockSize = DEFAULT_BLOCK_SIZE;
+ }
+
+ // Fill-in the variables
+ BitmapSize = (BlockCount + 7) / 8;
+
+ // Give the number of blocks
+ if(pcbLengthNeeded != NULL)
+ pcbLengthNeeded[0] = sizeof(TStreamBitmap) + BitmapSize;
+
+ // If the length of the buffer is not enough
+ if(pvBitmap != NULL && cbBitmap != 0)
+ {
+ // Give the STREAM_BLOCK_MAP structure
+ if(cbBitmap >= sizeof(TStreamBitmap))
+ {
+ pBitmap->StreamSize = pStream->StreamSize;
+ pBitmap->BitmapSize = BitmapSize;
+ pBitmap->BlockCount = BlockCount;
+ pBitmap->BlockSize = BlockSize;
+ pBitmap->IsComplete = (pStream->BlockCheck != NULL) ? pBlockStream->IsComplete : 1;
+ bResult = true;
+ }
+
+ // Give the block bitmap, if enough space
+ if(cbBitmap >= sizeof(TStreamBitmap) + BitmapSize)
+ {
+ // Version with bitmap present
+ if(pStream->BlockCheck != NULL)
+ {
+ DWORD ByteIndex = 0;
+ BYTE BitMask = 0x01;
+
+ // Initialize the map with zeros
+ memset(Bitmap, 0, BitmapSize);
+
+ // Fill the map
+ for(BlockOffset = 0; BlockOffset < pStream->StreamSize; BlockOffset += BlockSize)
+ {
+ // Set the bit if the block is present
+ if(pBlockStream->BlockCheck(pStream, BlockOffset))
+ Bitmap[ByteIndex] |= BitMask;
+
+ // Move bit position
+ ByteIndex += (BitMask >> 0x07);
+ BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
+ }
+ }
+ else
+ {
+ memset(Bitmap, 0xFF, BitmapSize);
+ }
+ }
+ }
+
+ // Set last error value and return
+ if(bResult == false)
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return bResult;
+}
+
+/**
* Reads data from the stream
*
* - Returns true if the read operation succeeded and all bytes have been read
@@ -2094,8 +2548,8 @@ bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags)
*/
bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream)
{
- // Only supported on linear files
- if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
+ // Only supported on flat files
+ if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
{
SetLastError(ERROR_NOT_SUPPORTED);
return false;
@@ -2117,7 +2571,7 @@ bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream)
return false;
// Now open the base file again
- if(BaseFile_Open(pStream, pStream->dwFlags))
+ if(BaseFile_Open(pStream, pStream->szFileName, pStream->dwFlags))
return false;
// Cleanup the new stream
@@ -2126,27 +2580,6 @@ bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream)
}
/**
- * Returns the file name of the stream
- *
- * \a pStream Pointer to an open stream
- */
-const TCHAR * FileStream_GetFileName(TFileStream * pStream)
-{
- assert(pStream != NULL);
- return pStream->szFileName;
-}
-
-/**
- * Returns true if the stream is read-only
- *
- * \a pStream Pointer to an open stream
- */
-bool FileStream_IsReadOnly(TFileStream * pStream)
-{
- return (pStream->dwFlags & STREAM_FLAG_READ_ONLY) ? true : false;
-}
-
-/**
* This function closes an archive file and frees any data buffers
* that have been allocated for stream management. The function must also
* support partially allocated structure, i.e. one or more buffers
diff --git a/src/FileStream.h b/src/FileStream.h
index f660a87..60f41d3 100644
--- a/src/FileStream.h
+++ b/src/FileStream.h
@@ -24,6 +24,7 @@ typedef bool (*STREAM_CREATE)(
typedef bool (*STREAM_OPEN)(
struct TFileStream * pStream, // Pointer to an unopened stream
+ const TCHAR * szFileName, // Pointer to file name to be open
DWORD dwStreamFlags // Stream flags
);
@@ -57,11 +58,11 @@ typedef bool (*STREAM_GETPOS)(
);
typedef void (*STREAM_CLOSE)(
- struct TFileStream * pStream
+ struct TFileStream * pStream // Pointer to an open stream
);
typedef bool (*BLOCK_READ)(
- struct TFileStream * pStream, // Pointer to an opened block stream
+ struct TFileStream * pStream, // Pointer to a block-oriented stream
ULONGLONG StartOffset, // Byte offset of start of the block array
ULONGLONG EndOffset, // End offset (either end of the block or end of the file)
LPBYTE BlockBuffer, // Pointer to block-aligned buffer
@@ -70,8 +71,12 @@ typedef bool (*BLOCK_READ)(
);
typedef DWORD (*BLOCK_CHECK)(
- struct TFileStream * pStream,
- ULONGLONG BlockOffset
+ struct TFileStream * pStream, // Pointer to a block-oriented stream
+ ULONGLONG BlockOffset // Offset of the file to check
+ );
+
+typedef void (*BLOCK_SAVEMAP)(
+ struct TFileStream * pStream // Pointer to a block-oriented stream
);
//-----------------------------------------------------------------------------
@@ -187,12 +192,15 @@ struct TFileStream
struct TBlockStream : public TFileStream
{
+ SFILE_DOWNLOAD_CALLBACK pfnCallback; // Callback for downloading
void * FileBitmap; // Array of bits for file blocks
+ void * UserData; // User data to be passed to the download callback
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
-};
+ DWORD IsModified; // nonzero if the bitmap has been modified
+};
//-----------------------------------------------------------------------------
// Structure for encrypted stream
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index fe02931..322211c 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -250,7 +250,7 @@ static DWORD GetMaxFileOffset32(TMPQArchive * ha)
return dwMaxFileOffset;
}
-static ULONGLONG DetermineEndOfArchive_V1_V2(
+static ULONGLONG DetermineArchiveSize_V1_V2(
TMPQArchive * ha,
TMPQHeader * pHeader,
ULONGLONG MpqOffset,
@@ -264,9 +264,17 @@ static ULONGLONG DetermineEndOfArchive_V1_V2(
// 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;
+ if(pHeader->dwBlockTablePos < pHeader->dwArchiveSize)
+ {
+ // If the block table end matches the archive size, we trust the archive size
+ if(pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)) == pHeader->dwArchiveSize)
+ return pHeader->dwArchiveSize;
+
+ // If the archive size in the header is less than real file size
+ dwArchiveSize32 = (DWORD)(FileSize - MpqOffset);
+ if(pHeader->dwArchiveSize <= dwArchiveSize32)
+ return pHeader->dwArchiveSize;
+ }
}
// Check if there is a signature header
@@ -385,7 +393,7 @@ int ConvertMpqHeaderToFormat4(
// Determine the archive size on malformed MPQs
if(ha->dwFlags & MPQ_FLAG_MALFORMED)
{
- pHeader->ArchiveSize64 = DetermineEndOfArchive_V1_V2(ha, pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V1_V2(ha, pHeader, MpqOffset, FileSize);
pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
}
break;
@@ -423,25 +431,25 @@ int ConvertMpqHeaderToFormat4(
if(pHeader->HiBlockTablePos64 != 0)
{
// BlockTableSize64 may be less than TblSize * sizeof(TMPQBlock).
- // That means that the hash table is compressed.
+ // That means that the hi-block table is compressed.
pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - BlockTablePos64;
assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
- // Determine the size of the hi-block table
- pHeader->HiBlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, pHeader, MpqOffset, FileSize) - pHeader->HiBlockTablePos64;
- assert(pHeader->HiBlockTableSize64 == (pHeader->dwBlockTableSize * sizeof(USHORT)));
+ // Determine real archive size
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V1_V2(ha, pHeader, MpqOffset, FileSize);
- // Recalculate the archive size
- pHeader->ArchiveSize64 = pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64;
+ // Calculate the size of the hi-block table
+ pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64;
+ assert(pHeader->HiBlockTableSize64 == (pHeader->dwBlockTableSize * sizeof(USHORT)));
}
else
{
- // Block table size is the end of the archive minus the block table position
- pHeader->BlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, pHeader, MpqOffset, FileSize) - BlockTablePos64;
- assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
+ // Determine real archive size
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V1_V2(ha, pHeader, MpqOffset, FileSize);
- // dwArchiveSize in the header v 2.0 is 32-bit only
- pHeader->ArchiveSize64 = BlockTablePos64 + pHeader->BlockTableSize64;
+ // Calculate size of the block table
+ pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - BlockTablePos64;
+ assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
}
}
else
@@ -1041,7 +1049,7 @@ static void CreateHetHeader(
pHetHeader->dwIndexTableSize;
}
-TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData)
+TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData)
{
TMPQHetTable * pHetTable;
@@ -1058,33 +1066,37 @@ TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwNameHashBitSize, LPBYTE
pHetTable->AndMask64 = ((dwNameHashBitSize != 0x40) ? ((ULONGLONG)1 << dwNameHashBitSize) : 0) - 1;
pHetTable->OrMask64 = (ULONGLONG)1 << (dwNameHashBitSize - 1);
+ // If the total count is not entered, use default
+ if(dwTotalCount == 0)
+ dwTotalCount = (dwEntryCount * 4) / 3;
+
// Store the HET table parameters
- pHetTable->dwEntryCount = dwFileCount;
- pHetTable->dwTotalCount = (dwFileCount * 4) / 3;
+ pHetTable->dwEntryCount = dwEntryCount;
+ pHetTable->dwTotalCount = dwTotalCount;
pHetTable->dwNameHashBitSize = dwNameHashBitSize;
- pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwFileCount);
+ pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwEntryCount);
pHetTable->dwIndexSizeExtra = 0;
pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
// Allocate array of hashes
- pHetTable->pNameHashes = STORM_ALLOC(BYTE, pHetTable->dwTotalCount);
+ pHetTable->pNameHashes = STORM_ALLOC(BYTE, dwTotalCount);
if(pHetTable->pNameHashes != NULL)
{
// Make sure the data are initialized
- memset(pHetTable->pNameHashes, 0, pHetTable->dwTotalCount);
+ memset(pHetTable->pNameHashes, 0, dwTotalCount);
// Allocate the bit array for file indexes
- pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF);
+ pHetTable->pBetIndexes = CreateBitArray(dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF);
if(pHetTable->pBetIndexes != NULL)
{
// Initialize the HET table from the source data (if given)
if(pbSrcData != NULL)
{
// Copy the name hashes
- memcpy(pHetTable->pNameHashes, pbSrcData, pHetTable->dwTotalCount);
+ memcpy(pHetTable->pNameHashes, pbSrcData, dwTotalCount);
// Copy the file indexes
- memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + pHetTable->dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes);
+ memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes);
}
// Return the result HET table
@@ -1152,7 +1164,7 @@ static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader)
assert(pHetHeader->ExtHdr.dwVersion == 1);
// Verify size of the HET table
- if(pHetHeader->ExtHdr.dwDataSize >= sizeof(TMPQHetHeader))
+ if(pHetHeader->ExtHdr.dwDataSize >= (sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader)))
{
// Verify the size of the table in the header
if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize)
@@ -1161,7 +1173,8 @@ static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader)
assert((sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize) == pHetHeader->dwTableSize);
// So far, all MPQs with HET Table have had total number of entries equal to 4/3 of file count
- assert(((pHetHeader->dwEntryCount * 4) / 3) == pHetHeader->dwTotalCount);
+ // Exception: "2010 - Starcraft II\!maps\Tya's Zerg Defense (unprotected).SC2Map"
+// assert(((pHetHeader->dwEntryCount * 4) / 3) == pHetHeader->dwTotalCount);
// The size of one index is predictable as well
assert(GetNecessaryBitCount(pHetHeader->dwEntryCount) == pHetHeader->dwIndexSizeTotal);
@@ -1171,7 +1184,7 @@ static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader)
assert(((pHetHeader->dwTotalCount * pHetHeader->dwIndexSizeTotal) + 7) / 8 == pHetHeader->dwIndexTableSize);
// Create translated table
- pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwNameHashBitSize, pbSrcData);
+ pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwTotalCount, pHetHeader->dwNameHashBitSize, pbSrcData);
if(pHetTable != NULL)
{
// Now the sizes in the hash table should be already set
@@ -1422,7 +1435,7 @@ static TMPQBetTable * TranslateBetTable(
ha = ha;
// Verify size of the HET table
- if(pBetHeader->ExtHdr.dwDataSize >= sizeof(TMPQBetHeader))
+ if(pBetHeader->ExtHdr.dwDataSize >= (sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader)))
{
// Verify the size of the table in the header
if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize)
@@ -2263,9 +2276,8 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha)
TMPQHetTable * pHetTable = NULL;
TMPQHeader * pHeader = ha->pHeader;
- // If the HET table position is not NULL, we expect
- // both HET and BET tables to be present.
- if(pHeader->HetTablePos64 != 0)
+ // If the HET table position is not 0, we expect the table to be present
+ if(pHeader->HetTablePos64 != 0 && pHeader->HetTableSize64 != 0)
{
// Attempt to load the HET table (Hash Extended Table)
pExtTable = LoadExtTable(ha, pHeader->HetTablePos64, (size_t)pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
@@ -2286,9 +2298,8 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha)
TMPQBetTable * pBetTable = NULL;
TMPQHeader * pHeader = ha->pHeader;
- // If the HET table position is not NULL, we expect
- // both HET and BET tables to be present.
- if(pHeader->BetTablePos64 != 0)
+ // If the BET table position is not 0, we expect the table to be present
+ if(pHeader->BetTablePos64 != 0 && pHeader->BetTableSize64 != 0)
{
// Attempt to load the HET table (Hash Extended Table)
pExtTable = LoadExtTable(ha, pHeader->BetTablePos64, (size_t)pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE);
@@ -2312,9 +2323,15 @@ int LoadAnyHashTable(TMPQArchive * ha)
if(pHeader->dwHashTableSize == 0 && pHeader->HetTableSize64 == 0)
return CreateHashTable(ha, HASH_TABLE_SIZE_DEFAULT);
- // Try to load HET table (the new hash table) and the classic HASH table
- ha->pHetTable = LoadHetTable(ha);
- ha->pHashTable = LoadHashTable(ha);
+ // Try to load HET table
+ if(pHeader->HetTablePos64 != 0)
+ ha->pHetTable = LoadHetTable(ha);
+
+ // Try to load the hash table
+ if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos)
+ ha->pHashTable = LoadHashTable(ha);
+
+ // At least one of the tables must be present
if(ha->pHetTable == NULL && ha->pHashTable == NULL)
return ERROR_FILE_CORRUPT;
@@ -2580,7 +2597,7 @@ int RebuildHetTable(TMPQArchive * ha)
// Create new HET table based on the total number of entries in the file table
// Note that if we fail to create it, we just stop using HET table
- ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL);
+ ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0, 0x40, NULL);
if(ha->pHetTable != NULL)
{
// Go through the file table again and insert all existing files
diff --git a/src/SFileAddFile.cpp b/src/SFileAddFile.cpp
index 5246f89..fdc1524 100644
--- a/src/SFileAddFile.cpp
+++ b/src/SFileAddFile.cpp
@@ -815,7 +815,7 @@ bool WINAPI SFileAddFileEx(
// Open added file
if(nError == ERROR_SUCCESS)
{
- pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
+ pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
if(pStream == NULL)
nError = GetLastError();
}
diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp
index a0ba774..7e63993 100644
--- a/src/SFileAttributes.cpp
+++ b/src/SFileAttributes.cpp
@@ -51,8 +51,7 @@ static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwFileTableSize)
return cbAttrFile;
}
-#ifdef _DEBUG
-static DWORD GetSizeOfAttributesFile_v2(DWORD dwAttrFlags, DWORD dwFileTableSize)
+static bool CheckSizeOfAttributesFile(DWORD cbSizeOfAttr, DWORD dwAttrFlags, DWORD dwFileTableSize)
{
DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER);
@@ -64,15 +63,31 @@ static DWORD GetSizeOfAttributesFile_v2(DWORD dwAttrFlags, DWORD dwFileTableSize
if(dwAttrFlags & MPQ_ATTRIBUTE_MD5)
cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE;
- // interface.MPQ.part from WoW build 10958 has
- // the MPQ_ATTRIBUTE_PATCH_BIT set, but there's an array of DWORDs instead.
- // The array is filled with zeros, so we don't know what it should contain
+ // Various variants with the patch bit
if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
- cbAttrFile += dwFileTableSize * sizeof(DWORD);
-
- return cbAttrFile;
+ {
+ // Check for expected size
+ if(cbSizeOfAttr == (cbAttrFile + (dwFileTableSize + 6) / 8))
+ return true;
+
+ // Zenith.SC2MAP has the MPQ_ATTRIBUTE_PATCH_BIT set, but the bit array is missing
+ if(cbSizeOfAttr == cbSizeOfAttr)
+ return false;
+
+ // interface.MPQ.part from WoW build 10958 has
+ // the MPQ_ATTRIBUTE_PATCH_BIT set, but there's an array of DWORDs instead.
+ // The array is filled with zeros, so we don't know what it should contain
+ if(cbSizeOfAttr == (cbAttrFile + dwFileTableSize * sizeof(DWORD)))
+ return false;
+
+ // Size mismatch
+ assert(false);
+ return false;
+ }
+
+ assert(cbSizeOfAttr == cbAttrFile);
+ return false;
}
-#endif
static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile)
{
@@ -80,6 +95,7 @@ static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrF
LPBYTE pbAttrPtr = pbAttrFile;
DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize;
DWORD i;
+ bool bPatchBitsValid;
// Load and verify the header
if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd)
@@ -90,15 +106,10 @@ static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrF
if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1)
return ERROR_BAD_FORMAT;
- // Verify the size of the file
+ // Verify the flags and of the file
assert((pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL) == 0);
- // Verify the size of the file
-#ifdef _DEBUG
- assert(cbAttrFile == GetSizeOfAttributesFile(pAttrHeader->dwFlags, dwBlockTableSize) ||
- cbAttrFile == GetSizeOfAttributesFile_v2(pAttrHeader->dwFlags, dwBlockTableSize));
-#endif
-
+ bPatchBitsValid = CheckSizeOfAttributesFile(cbAttrFile, pAttrHeader->dwFlags, dwBlockTableSize);
ha->dwAttrFlags = pAttrHeader->dwFlags;
pbAttrPtr = (LPBYTE)(pAttrHeader + 1);
}
@@ -154,7 +165,7 @@ static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrF
}
// Read the patch bit for each file (if present)
- if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ if((ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) && bPatchBitsValid)
{
LPBYTE pbBitArray = pbAttrPtr;
DWORD cbArraySize = (dwBlockTableSize + 7) / 8;
diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp
index b5482c8..00efbb4 100644
--- a/src/SFileCompactArchive.cpp
+++ b/src/SFileCompactArchive.cpp
@@ -504,7 +504,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
else
_tcscat(szTempFile, _T("_"));
- pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
+ pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
if(pTempStream == NULL)
nError = GetLastError();
}
diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp
index 4ae71e9..5f375c8 100644
--- a/src/SFileCreateArchive.cpp
+++ b/src/SFileCreateArchive.cpp
@@ -77,7 +77,7 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWO
memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ));
CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ);
CreateInfo.dwMpqVersion = (dwCreateFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT;
- CreateInfo.dwStreamFlags = STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE;
+ CreateInfo.dwStreamFlags = STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE;
CreateInfo.dwFileFlags1 = (dwCreateFlags & MPQ_CREATE_LISTFILE) ? MPQ_FILE_EXISTS : 0;
CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_EXISTS : 0;
CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? (MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5) : 0;
@@ -133,7 +133,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
// We verify if the file already exists and if it's a MPQ archive.
// If yes, we won't allow to overwrite it.
- if(SFileOpenArchive(szMpqName, 0, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE | MPQ_OPEN_NO_ATTRIBUTES | MPQ_OPEN_NO_LISTFILE, &hMpq))
+ if(SFileOpenArchive(szMpqName, 0, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE | MPQ_OPEN_NO_ATTRIBUTES | MPQ_OPEN_NO_LISTFILE, &hMpq))
{
SFileCloseArchive(hMpq);
SetLastError(ERROR_ALREADY_EXISTS);
@@ -227,7 +227,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
// Create initial HET table, if the caller required an MPQ format 3.0 or newer
if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3 && pCreateInfo->dwMaxFileCount != 0)
{
- ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL);
+ ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0, 0x40, NULL);
if(ha->pHetTable == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
diff --git a/src/SFileExtractFile.cpp b/src/SFileExtractFile.cpp
index c8053ed..314d8ff 100644
--- a/src/SFileExtractFile.cpp
+++ b/src/SFileExtractFile.cpp
@@ -28,7 +28,7 @@ bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR
// Create the local file
if(nError == ERROR_SUCCESS)
{
- pLocalFile = FileStream_CreateFile(szExtracted, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
+ pLocalFile = FileStream_CreateFile(szExtracted, 0);
if(pLocalFile == NULL)
nError = GetLastError();
}
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp
index e56e2f6..05bea29 100644
--- a/src/SFileGetFileInfo.cpp
+++ b/src/SFileGetFileInfo.cpp
@@ -163,20 +163,10 @@ bool WINAPI SFileGetFileInfo(
}
break;
- case SFileMpqStreamBlockSize:
+ case SFileMpqStreamBitmap:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
- {
- // TODO
- }
- break;
-
- case SFileMpqStreamBlockAvailable:
- ha = IsValidMpqHandle(hMpqOrFile);
- if(ha != NULL)
- {
- // TODO
- }
+ return FileStream_GetBitmap(ha->pStream, pvFileInfo, cbFileInfo, pcbLengthNeeded);
break;
case SFileMpqUserDataOffset:
@@ -616,7 +606,7 @@ bool WINAPI SFileGetFileInfo(
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY));
+ dwInt32Value = (ha->dwFlags & MPQ_FLAG_READ_ONLY) ? 1 : 0;
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
@@ -954,7 +944,7 @@ static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * sz
}
}
- return ERROR_NOT_SUPPORTED;
+ return ERROR_CAN_NOT_COMPLETE;
}
bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index 9970791..27c9972 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -177,7 +177,7 @@ bool WINAPI SFileOpenArchive(
{
FileStream_GetSize(pStream, &FileSize);
if(FileSize < MPQ_HEADER_SIZE_V1)
- nError = ERROR_FILE_CORRUPT;
+ nError = ERROR_BAD_FORMAT;
}
// Allocate the MPQhandle
@@ -191,6 +191,7 @@ bool WINAPI SFileOpenArchive(
if(nError == ERROR_SUCCESS)
{
ULONGLONG SearchOffset = 0;
+ DWORD dwStreamFlags = 0;
DWORD dwHeaderID;
memset(ha, 0, sizeof(TMPQArchive));
@@ -198,9 +199,9 @@ bool WINAPI SFileOpenArchive(
ha->pStream = pStream;
pStream = NULL;
- // Remember if the archive is open for write
- if(FileStream_IsReadOnly(ha->pStream))
- ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ // Set the archive read only if the stream is read-only
+ FileStream_GetFlags(ha->pStream, &dwStreamFlags);
+ ha->dwFlags |= (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0;
// Also remember if we shall check sector CRCs when reading file
if(dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC)
@@ -287,7 +288,7 @@ bool WINAPI SFileOpenArchive(
ha->MpqPos = SearchOffset;
// Sector size must be nonzero.
- if(ha->pHeader->wSectorSize == 0)
+ if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0)
nError = ERROR_BAD_FORMAT;
}
}
@@ -409,6 +410,26 @@ bool WINAPI SFileOpenArchive(
}
//-----------------------------------------------------------------------------
+// bool WINAPI SFileSetDownloadCallback(HANDLE, SFILE_DOWNLOAD_CALLBACK, void *);
+//
+// Sets a callback that is called when content is downloaded from the master MPQ
+//
+
+bool WINAPI SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK DownloadCB, void * pvUserData)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Do nothing if 'hMpq' is bad parameter
+ if(!IsValidMpqHandle(hMpq))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ return FileStream_SetCallback(ha->pStream, DownloadCB, pvUserData);
+}
+
+//-----------------------------------------------------------------------------
// bool SFileFlushArchive(HANDLE hMpq)
//
// Saves all dirty data into MPQ archive.
diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp
index c2cf5d4..31e235e 100644
--- a/src/SFileOpenFileEx.cpp
+++ b/src/SFileOpenFileEx.cpp
@@ -46,7 +46,7 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
CopyFileName(szFileNameT, szFileName, strlen(szFileName));
// Open the file and create the TMPQFile structure
- pStream = FileStream_OpenFile(szFileNameT, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY);
+ pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY);
if(pStream != NULL)
{
// Allocate and initialize file handle
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp
index cc10726..4b49c59 100644
--- a/src/SFilePatchArchives.cpp
+++ b/src/SFilePatchArchives.cpp
@@ -519,7 +519,7 @@ bool WINAPI SFileOpenPatchArchive(
if(nError == ERROR_SUCCESS)
{
- if(!FileStream_IsReadOnly(ha->pStream))
+ if(!(ha->dwFlags & MPQ_FLAG_READ_ONLY))
nError = ERROR_ACCESS_DENIED;
}
diff --git a/src/StormCommon.h b/src/StormCommon.h
index 2901c9f..2b2cf1d 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -193,7 +193,7 @@ int RebuildHetTable(TMPQArchive * ha);
int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount);
int SaveMPQTables(TMPQArchive * ha);
-TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwHashBitSize, LPBYTE pbSrcData);
+TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwHashBitSize, LPBYTE pbSrcData);
void FreeHetTable(TMPQHetTable * pHetTable);
TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount);
diff --git a/src/StormLib.h b/src/StormLib.h
index e528283..a0cfeb6 100644
--- a/src/StormLib.h
+++ b/src/StormLib.h
@@ -145,6 +145,7 @@ extern "C" {
#define ERROR_INTERNAL_FILE 10003 // The given operation is not allowed on internal file
#define ERROR_BASE_FILE_MISSING 10004 // The file is present as incremental patch file, but base file is missing
#define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ
+#define ERROR_FILE_INCOMPLETE 10006 // The required file part is missing
// Values for SFileCreateArchive
#define HASH_TABLE_SIZE_MIN 0x00000004 // Verified: If there is 1 file, hash table size is 4
@@ -264,9 +265,10 @@ extern "C" {
#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server
#define BASE_PROVIDER_MASK 0x0000000F // Mask for base provider value
-#define STREAM_PROVIDER_LINEAR 0x00000000 // Stream is linear with no offset mapping
+#define STREAM_PROVIDER_FLAT 0x00000000 // Stream is linear with no offset mapping
#define STREAM_PROVIDER_PARTIAL 0x00000010 // Stream is partial file (.part)
-#define STREAM_PROVIDER_ENCRYPTED 0x00000020 // Stream is an encrypted MPQ
+#define STREAM_PROVIDER_MPQE 0x00000020 // Stream is an encrypted MPQ
+#define STREAM_PROVIDER_BLOCK4 0x00000030 // 0x4000 per block, text MD5 after each block, max 0x2000 blocks per file
#define STREAM_PROVIDER_MASK 0x000000F0 // Mask for stream provider value
#define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only
@@ -282,11 +284,7 @@ extern "C" {
#define MPQ_OPEN_NO_HEADER_SEARCH 0x00040000 // Don't search for the MPQ header past the begin of the file
#define MPQ_OPEN_FORCE_MPQ_V1 0x00080000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header
#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00100000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file
-
-// Deprecated
#define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY
-#define MPQ_OPEN_ENCRYPTED STREAM_PROVIDER_ENCRYPTED
-#define MPQ_OPEN_PARTIAL STREAM_PROVIDER_PARTIAL
// Flags for SFileCreateArchive
#define MPQ_CREATE_LISTFILE 0x00100000 // Also add the (listfile) file
@@ -363,8 +361,7 @@ typedef enum _SFileInfoClass
{
// Info classes for archives
SFileMpqFileName, // Name of the archive file (TCHAR [])
- SFileMpqStreamBlockSize, // Size of one stream block in bytes (DWORD)
- SFileMpqStreamBlockAvailable, // Nonzero if the stream block at the given offset is available (input: ByteOffset, ULONGLONG)
+ SFileMpqStreamBitmap, // Array of bits, each bit means availability of one block (BYTE [])
SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG)
SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData)
SFileMpqUserData, // MPQ USer data, without the header (BYTE [])
@@ -426,6 +423,11 @@ typedef enum _SFileInfoClass
//-----------------------------------------------------------------------------
// Deprecated flags. These are going to be removed in next releases.
+STORMLIB_DEPRECATED_FLAG(DWORD, STREAM_PROVIDER_LINEAR, STREAM_PROVIDER_FLAT);
+STORMLIB_DEPRECATED_FLAG(DWORD, STREAM_PROVIDER_ENCRYPTED, STREAM_PROVIDER_MPQE);
+STORMLIB_DEPRECATED_FLAG(DWORD, MPQ_OPEN_ENCRYPTED, STREAM_PROVIDER_MPQE);
+STORMLIB_DEPRECATED_FLAG(DWORD, MPQ_OPEN_PARTIAL, STREAM_PROVIDER_PARTIAL);
+
// MPQ_FILE_COMPRESSED is deprecated. Do not use.
STORMLIB_DEPRECATED_FLAG(DWORD, MPQ_FILE_COMPRESSED, MPQ_FILE_COMPRESS_MASK);
@@ -465,6 +467,7 @@ STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_PATCH_CHAIN, SFileInfoPatchC
#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total)
#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used
+typedef void (WINAPI * SFILE_DOWNLOAD_CALLBACK)(void * pvUserData, ULONGLONG ByteOffset, DWORD dwTotalBytes);
typedef void (WINAPI * SFILE_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall);
typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes);
@@ -931,12 +934,28 @@ typedef struct _SFILE_CREATE_MPQ
//-----------------------------------------------------------------------------
// Stream support - functions
+// Structure used by FileStream_GetBitmap
+typedef struct _TStreamBitmap
+{
+ ULONGLONG StreamSize; // Size of the stream, in bytes
+ DWORD BitmapSize; // Size of the block map, in bytes
+ DWORD BlockCount; // Number of blocks in the stream
+ DWORD BlockSize; // Size of one block
+ DWORD IsComplete; // Nonzero if the file is complete
+
+ // Followed by the BYTE array, each bit means availability of one block
+
+} TStreamBitmap;
+
// UNICODE versions of the file access functions
TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags);
TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags);
const TCHAR * FileStream_GetFileName(TFileStream * pStream);
+size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider);
+
+bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData);
-bool FileStream_IsReadOnly(TFileStream * pStream);
+bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, LPDWORD pcbLengthNeeded);
bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead);
bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite);
bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize);
@@ -973,6 +992,7 @@ 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 SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK DownloadCB, void * pvUserData);
bool WINAPI SFileFlushArchive(HANDLE hMpq);
bool WINAPI SFileCloseArchive(HANDLE hMpq);
diff --git a/test/TLogHelper.cpp b/test/TLogHelper.cpp
index 6bad08e..1e0c787 100644
--- a/test/TLogHelper.cpp
+++ b/test/TLogHelper.cpp
@@ -60,9 +60,13 @@ class TLogHelper
#ifdef _MSC_VER
#define I64u_t _T("%I64u")
#define I64u_a "%I64u"
+#define I64X_t _T("%I64X")
+#define I64X_a "%I64X"
#else
-#define I64u_t "%llu"
+#define I64u_t _T("%llu")
#define I64u_a "%llu"
+#define I64X_t _T("%llX")
+#define I64X_a "%llX"
#endif
//-----------------------------------------------------------------------------
diff --git a/test/Test.cpp b/test/Test.cpp
index 30b2fd4..6f27f1e 100644
--- a/test/Test.cpp
+++ b/test/Test.cpp
@@ -174,6 +174,19 @@ static const char * PatchList_WoW16965[] =
static char szMpqDirectory[MAX_PATH];
size_t cchMpqDirectory = 0;
+static bool IsFullPath(const char * szFileName)
+{
+#ifdef PLATFORM_WINDOWS
+ if(('A' <= szFileName[0] && szFileName[0] <= 'Z') || ('a' <= szFileName[0] && szFileName[0] <= 'z'))
+ {
+ return (szFileName[1] == ':' && szFileName[2] == PATH_SEPARATOR);
+ }
+#endif
+
+ szFileName = szFileName;
+ return false;
+}
+
static bool IsMpqExtension(const char * szFileName)
{
const char * szExtension = strrchr(szFileName, '.');
@@ -196,6 +209,8 @@ static bool IsMpqExtension(const char * szFileName)
return true;
if(!_stricmp(szExtension, ".SC2Map"))
return true;
+ if(!_stricmp(szExtension, ".0")) // .MPQ.0
+ return true;
// if(!_stricmp(szExtension, ".link"))
// return true;
}
@@ -290,6 +305,19 @@ static void CopyPathPart(char * szBuffer, const char * szPath)
*szBuffer = 0;
}
+static bool CopyStringAndVerifyConversion(
+ const TCHAR * szFoundFile,
+ TCHAR * szBufferT,
+ char * szBufferA)
+{
+ // Convert the TCHAR name to ANSI name
+ CopyFileName(szBufferA, szFoundFile, _tcslen(szFoundFile));
+ CopyFileName(szBufferT, szBufferA, strlen(szBufferA));
+
+ // Compare both TCHAR strings
+ return (_tcsicmp(szBufferT, szFoundFile) == 0) ? true : false;
+}
+
static void CalculateRelativePath(const char * szFullPath1, const char * szFullPath2, char * szBuffer)
{
const char * szPathPart1 = szFullPath1;
@@ -346,44 +374,98 @@ static void CalculateRelativePath(const char * szFullPath1, const char * szFullP
strcpy(szBuffer, szFullPath1);
}
+static TFileStream * FileStream_OpenFileA(const char * szFileName, DWORD dwStreamFlags)
+{
+ TCHAR szFileNameT[MAX_PATH];
+
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ return FileStream_OpenFile(szFileNameT, dwStreamFlags);
+}
+
+static TFileStream * FileStream_CreateFileA(const char * szFileName, DWORD dwStreamFlags)
+{
+ TCHAR szFileNameT[MAX_PATH];
+
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ return FileStream_CreateFile(szFileNameT, dwStreamFlags);
+}
+
+static size_t FileStream_PrefixA(const char * szFileName, DWORD * pdwProvider)
+{
+ TCHAR szFileNameT[MAX_PATH];
+
+ if(szFileName != NULL)
+ {
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ return FileStream_Prefix(szFileNameT, pdwProvider);
+ }
+ return 0;
+}
+
static void CreateFullPathName(char * szBuffer, const char * szSubDir, const char * szNamePart1, const char * szNamePart2 = NULL)
{
+ char * szSaveBuffer = szBuffer;
+ size_t nPrefixLength = 0;
size_t nLength;
+ DWORD dwProvider = 0;
+ bool bIsFullPath = false;
+ char chSeparator = PATH_SEPARATOR;
- // Copy the master MPQ directory
- memcpy(szBuffer, szMpqDirectory, cchMpqDirectory);
- szBuffer += cchMpqDirectory;
+ // Determine the path prefix
+ if(szNamePart1 != NULL)
+ {
+ nPrefixLength = FileStream_PrefixA(szNamePart1, &dwProvider);
+ if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
+ {
+ bIsFullPath = true;
+ chSeparator = '/';
+ }
+ else
+ bIsFullPath = IsFullPath(szNamePart1 + nPrefixLength);
+ }
- // Append the subdirectory, if any
- if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0)
+ // Copy the MPQ prefix, if any
+ if(nPrefixLength > 0)
{
- // No leading or trailing separator must be there
- assert(szSubDir[0] != '/' && szSubDir[0] != '\\');
- assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\');
+ memcpy(szBuffer, szNamePart1, nPrefixLength);
+ szSaveBuffer += nPrefixLength;
+ szNamePart1 += nPrefixLength;
+ szBuffer += nPrefixLength;
+ }
- // Append file path separator
- *szBuffer++ = PATH_SEPARATOR;
+ // If the given name is not a full path, copy the MPQ directory
+ if(bIsFullPath == false)
+ {
+ // Copy the master MPQ directory
+ memcpy(szBuffer, szMpqDirectory, cchMpqDirectory);
+ szBuffer += cchMpqDirectory;
- // Copy the subdirectory
- memcpy(szBuffer, szSubDir, nLength);
+ // Append the subdirectory, if any
+ if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0)
+ {
+ // No leading or trailing separator are allowed
+ assert(szSubDir[0] != '/' && szSubDir[0] != '\\');
+ assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\');
- // Fix the path separators
- for(size_t i = 0; i < nLength; i++)
- szBuffer[i] = (szBuffer[i] != '\\' && szBuffer[i] != '/') ? szBuffer[i] : PATH_SEPARATOR;
-
- // Move the buffer pointer
- szBuffer += nLength;
+ // Append file path separator
+ *szBuffer++ = PATH_SEPARATOR;
+
+ // Append the subdirectory
+ memcpy(szBuffer, szSubDir, nLength);
+ szBuffer += nLength;
+ }
}
// Copy the file name, if any
if(szNamePart1 != NULL && (nLength = strlen(szNamePart1)) != 0)
{
- // No path separator can be there
- assert(strchr(szNamePart1, '\\') == NULL);
- assert(strchr(szNamePart1, '/') == NULL);
+ // Path separators are not allowed in the name part
+ assert(szNamePart1[0] != '\\' && szNamePart1[0] != '/');
+ assert(szNamePart1[nLength - 1] != '/' && szNamePart1[nLength - 1] != '\\');
// Append file path separator
- *szBuffer++ = PATH_SEPARATOR;
+ if(bIsFullPath == false)
+ *szBuffer++ = PATH_SEPARATOR;
// Copy the file name
memcpy(szBuffer, szNamePart1, nLength);
@@ -398,47 +480,61 @@ static void CreateFullPathName(char * szBuffer, const char * szSubDir, const cha
szBuffer += nLength;
}
+ // Normalize the path separators
+ while(szSaveBuffer < szBuffer)
+ {
+ szSaveBuffer[0] = (szSaveBuffer[0] != '/' && szSaveBuffer[0] != '\\') ? szSaveBuffer[0] : chSeparator;
+ szSaveBuffer++;
+ }
+
// Terminate the buffer with zero
*szBuffer = 0;
}
-static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, char * szDirectory)
+static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, TCHAR * szDirectoryT, char * szDirectoryA)
{
- char * szPlainName;
int nError = ERROR_SUCCESS;
- // Setup the search masks
- strcat(szDirectory, "\\*");
- szPlainName = strrchr(szDirectory, '*');
-
- if(szDirectory != NULL && szPlainName != NULL)
+ if(szDirectoryT != NULL && szDirectoryA != NULL)
{
#ifdef PLATFORM_WINDOWS
- WIN32_FIND_DATAA wf;
+ WIN32_FIND_DATA wf;
HANDLE hFind;
+ TCHAR * szPlainNameT;
+ char * szPlainNameA;
+ size_t nLength = strlen(szDirectoryA);
+
+ // Setup the search masks
+ _tcscat(szDirectoryT, _T("\\*"));
+ szPlainNameT = szDirectoryT + nLength + 1;
+
+ strcat(szDirectoryA, "\\*");
+ szPlainNameA = szDirectoryA + nLength + 1;
// Initiate search. Use ANSI function only
- hFind = FindFirstFileA(szDirectory, &wf);
+ hFind = FindFirstFile(szDirectoryT, &wf);
if(hFind != INVALID_HANDLE_VALUE)
{
// Skip the first entry, since it's always "." or ".."
- while(FindNextFileA(hFind, &wf) && nError == ERROR_SUCCESS)
+ while(FindNextFile(hFind, &wf) && nError == ERROR_SUCCESS)
{
- // Found a directory?
- if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ // If the file name can be converted to UNICODE and back
+ if(CopyStringAndVerifyConversion(wf.cFileName, szPlainNameT, szPlainNameA))
{
- if(wf.cFileName[0] != '.')
+ // Found a directory?
+ if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
- strcpy(szPlainName, wf.cFileName);
- nError = FindFilesInternal(pfnTest, szDirectory);
+ if(wf.cFileName[0] != '.')
+ {
+ nError = FindFilesInternal(pfnTest, szDirectoryT, szDirectoryA);
+ }
}
- }
- else
- {
- if(pfnTest != NULL)
+ else
{
- strcpy(szPlainName, wf.cFileName);
- nError = pfnTest(szDirectory);
+ if(pfnTest != NULL)
+ {
+ nError = pfnTest(szDirectoryA);
+ }
}
}
}
@@ -454,10 +550,12 @@ static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, char * szDirectory)
static int FindFiles(FIND_FILE_CALLBACK pfnFindFile, const char * szSubDirectory)
{
- char szWorkBuff[MAX_PATH];
+ TCHAR szWorkBuffT[MAX_PATH];
+ char szWorkBuffA[MAX_PATH];
- CreateFullPathName(szWorkBuff, szSubDirectory, NULL);
- return FindFilesInternal(pfnFindFile, szWorkBuff);
+ CreateFullPathName(szWorkBuffA, szSubDirectory, NULL);
+ CopyFileName(szWorkBuffT, szWorkBuffA, strlen(szWorkBuffA));
+ return FindFilesInternal(pfnFindFile, szWorkBuffT, szWorkBuffA);
}
static int FindFilePairsInternal(
@@ -529,22 +627,6 @@ static int FindFilePairs(FIND_PAIR_CALLBACK pfnFindPair, const char * szSourceSu
return FindFilePairsInternal(pfnFindPair, szSource, szTarget);
}
-TFileStream * OpenLocalFile(const char * szFileName, DWORD dwStreamFlags)
-{
- TCHAR szFileNameT[MAX_PATH];
-
- CopyFileName(szFileNameT, szFileName, strlen(szFileName));
- return FileStream_OpenFile(szFileNameT, dwStreamFlags);
-}
-
-TFileStream * CreateLocalFile(const char * szFileName, DWORD dwStreamFlags)
-{
- TCHAR szFileNameT[MAX_PATH];
-
- CopyFileName(szFileNameT, szFileName, strlen(szFileName));
- return FileStream_CreateFile(szFileNameT, dwStreamFlags);
-}
-
static int CalculateFileSha1(TLogHelper * pLogger, const char * szFullPath, char * szFileSha1)
{
TFileStream * pStream;
@@ -563,7 +645,7 @@ static int CalculateFileSha1(TLogHelper * pLogger, const char * szFullPath, char
szFileSha1[0] = 0;
// Open the file to be verified
- pStream = OpenLocalFile(szFullPath, STREAM_FLAG_READ_ONLY);
+ pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY);
if(pStream != NULL)
{
// Retrieve the size of the file
@@ -626,7 +708,7 @@ static int InitializeMpqDirectory(char * argv[], int argc)
// Retrieve the name of the MPQ directory
if(argc > 1 && argv[1] != NULL)
{
- szWhereFrom = "entered at command line";
+ szWhereFrom = "command line";
szDirName = argv[1];
}
else
@@ -649,16 +731,17 @@ static int InitializeMpqDirectory(char * argv[], int argc)
// Verify if the work MPQ directory is writable
CreateFullPathName(szFullPath, NULL, "TestFile.bin");
- pStream = CreateLocalFile(szFullPath, 0);
+ pStream = FileStream_CreateFileA(szFullPath, 0);
if(pStream == NULL)
- return Logger.PrintError("MPQ subdirectory is not writable");
+ return Logger.PrintError("MPQ subdirectory doesn't exist or is not writable");
// Close the stream
FileStream_Close(pStream);
+ remove(szFullPath);
// Verify if the working directory exists and if there is a subdirectory with the file name
CreateFullPathName(szFullPath, szMpqSubDir, "ListFile_Blizzard.txt");
- pStream = OpenLocalFile(szFullPath, STREAM_FLAG_READ_ONLY);
+ pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY);
if(pStream == NULL)
return Logger.PrintError("The main listfile (%s) was not found. Check your paths", GetShortPlainName(szFullPath));
@@ -756,7 +839,7 @@ static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONG
// Construct the full path and crete the file
CreateFullPathName(szFullPath, NULL, szPlainName);
- pStream = CreateLocalFile(szFullPath, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
+ pStream = FileStream_CreateFileA(szFullPath, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
if(pStream == NULL)
return pLogger->PrintError("Failed to create file %s", szBuffer);
@@ -962,7 +1045,7 @@ static int CreateFileCopy(
}
// Open the source file
- pStream1 = OpenLocalFile(szFileName1, STREAM_FLAG_READ_ONLY);
+ pStream1 = FileStream_OpenFileA(szFileName1, STREAM_FLAG_READ_ONLY);
if(pStream1 == NULL)
{
pLogger->PrintError("Failed to open the source file %s", szFileName1);
@@ -970,7 +1053,7 @@ static int CreateFileCopy(
}
// Create the destination file
- pStream2 = CreateLocalFile(szFileName2, 0);
+ pStream2 = FileStream_CreateFileA(szFileName2, 0);
if(pStream2 != NULL)
{
// If we should write some pre-MPQ data to the target file, do it
@@ -1015,13 +1098,21 @@ static int CreateMasterAndMirrorPaths(
char * szMirrorPath,
char * szMasterPath,
const char * szMirrorName,
- const char * szMasterName)
+ const char * szMasterName,
+ bool bCopyMirrorFile)
{
char szCopyPath[MAX_PATH];
- int nError;
+ int nError = ERROR_SUCCESS;
// Copy the mirrored file from the source to the work directory
- nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, szCopyPath);
+ if(bCopyMirrorFile)
+ nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, szCopyPath);
+ else
+ {
+ CreateFullPathName(szCopyPath, NULL, szMirrorName);
+ remove(szCopyPath);
+ }
+
if(nError == ERROR_SUCCESS)
{
// Create the full path name of the master file
@@ -1133,7 +1224,7 @@ static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName,
pLogger->PrintProgress("Loading local file ...");
// Attempt to open the file
- pStream = OpenLocalFile(szFileName, STREAM_FLAG_READ_ONLY);
+ pStream = FileStream_OpenFileA(szFileName, STREAM_FLAG_READ_ONLY);
if(pStream == NULL)
{
if(pLogger != NULL && bMustSucceed == true)
@@ -1170,7 +1261,8 @@ static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName,
static int CompareTwoLocalFilesRR(
TLogHelper * pLogger,
TFileStream * pStream1, // Master file
- TFileStream * pStream2) // Mirror file
+ TFileStream * pStream2, // Mirror file
+ int nIterations) // Number of iterations
{
ULONGLONG RandomNumber = 0x12345678; // We need pseudo-random number that will repeat each run of the program
ULONGLONG RandomSeed;
@@ -1199,7 +1291,7 @@ static int CompareTwoLocalFilesRR(
if(pbBuffer1 && pbBuffer2)
{
// Perform many random reads
- for(int i = 0; i < 0x10000; i++)
+ for(int i = 0; i < nIterations; i++)
{
// Generate psudo-random offsrt and data size
ByteOffset = (RandomNumber % FileSize1);
@@ -1495,18 +1587,25 @@ static int OpenExistingArchive(TLogHelper * pLogger, const char * szFullPath, DW
// Is it an encrypted MPQ ?
if(strstr(szFullPath, ".MPQE") != NULL)
- dwFlags |= STREAM_PROVIDER_ENCRYPTED;
+ dwFlags |= STREAM_PROVIDER_MPQE;
if(strstr(szFullPath, ".MPQ.part") != NULL)
dwFlags |= STREAM_PROVIDER_PARTIAL;
+ if(strstr(szFullPath, ".mpq.part") != NULL)
+ dwFlags |= STREAM_PROVIDER_PARTIAL;
+ if(strstr(szFullPath, ".MPQ.0") != NULL)
+ dwFlags |= STREAM_PROVIDER_BLOCK4;
// Open the copied archive
pLogger->PrintProgress("Opening archive %s ...", GetShortPlainName(szFullPath));
CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq))
{
- // Ignore the error if it's an AVI file
- if(GetLastError() == ERROR_AVI_FILE)
- return ERROR_AVI_FILE;
+ // Ignore the error if it's an AVI file or if the file is incomplete
+ nError = GetLastError();
+ if(nError == ERROR_AVI_FILE || nError == ERROR_FILE_INCOMPLETE)
+ return nError;
+
+ // Show the open error to the user
return pLogger->PrintError("Failed to open archive %s", szFullPath);
}
@@ -1810,25 +1909,55 @@ static int TestSearchListFile(const char * szPlainName)
return ERROR_SUCCESS;
}
+static void WINAPI TestReadFile_DownloadCallback(
+ void * UserData,
+ ULONGLONG ByteOffset,
+ DWORD DataLength)
+{
+ TLogHelper * pLogger = (TLogHelper *)UserData;
+
+ if(ByteOffset != 0 && DataLength != 0)
+ pLogger->PrintProgress("Downloading data (offset: " I64X_a ", length: %X)", ByteOffset, DataLength);
+ else
+ pLogger->PrintProgress("Download complete.");
+}
+
// Open a file stream with mirroring a master file
-static int TestReadFile_MasterMirror(const char * szMirrorName, const char * szMasterName)
+static int TestReadFile_MasterMirror(const char * szMirrorName, const char * szMasterName, bool bCopyMirrorFile)
{
TFileStream * pStream1; // Master file
TFileStream * pStream2; // Mirror file
- TLogHelper Logger("OpenMirrorFile", szMasterName);
+ TLogHelper Logger("OpenMirrorFile", szMirrorName);
+ DWORD dwProvider = 0;
char szMirrorPath[MAX_PATH + MAX_PATH];
char szMasterPath[MAX_PATH];
+ int nIterations = 0x10000;
int nError;
+ // Retrieve the provider
+ FileStream_PrefixA(szMasterName, &dwProvider);
+
+#ifndef PLATFORM_WINDOWS
+ if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
+ return ERROR_SUCCESS;
+#endif
+
// Create copy of the file to serve as mirror, keep master there
- nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName);
+ nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName, bCopyMirrorFile);
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);
+ pStream1 = FileStream_OpenFileA(szMasterPath, STREAM_FLAG_READ_ONLY);
+ pStream2 = FileStream_OpenFileA(szMirrorPath, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP);
if(pStream1 && pStream2)
- nError = CompareTwoLocalFilesRR(&Logger, pStream1, pStream2);
+ {
+ // For internet based files, we limit the number of operations
+ if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
+ nIterations = 0x80;
+
+ FileStream_SetCallback(pStream2, TestReadFile_DownloadCallback, &Logger);
+ nError = CompareTwoLocalFilesRR(&Logger, pStream1, pStream2, nIterations);
+ }
if(pStream2 != NULL)
FileStream_Close(pStream2);
@@ -1852,12 +1981,18 @@ static int TestFileStreamOperations(const char * szPlainName, DWORD dwStreamFlag
int nError = ERROR_SUCCESS;
// Copy the file so we won't screw up
- CreateFileCopy(&Logger, szPlainName, szPlainName, szFullPath);
+ if((dwStreamFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_BLOCK4)
+ CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
+ else
+ nError = CreateFileCopy(&Logger, szPlainName, szPlainName, szFullPath);
// Open the file stream
- pStream = OpenLocalFile(szFullPath, dwStreamFlags);
- if(pStream == NULL)
- nError = Logger.PrintError("Failed to open %s", szFullPath);
+ if(nError == ERROR_SUCCESS)
+ {
+ pStream = FileStream_OpenFileA(szFullPath, dwStreamFlags);
+ if(pStream == NULL)
+ nError = Logger.PrintError("Failed to open %s", szFullPath);
+ }
// Get the size of the file stream
if(nError == ERROR_SUCCESS)
@@ -1868,8 +2003,8 @@ static int TestFileStreamOperations(const char * szPlainName, DWORD dwStreamFlag
if(!FileStream_GetSize(pStream, &FileSize))
nError = Logger.PrintError("Failed to retrieve the file size");
- // Any other stream except STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE should be read-only
- if((dwStreamFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
+ // Any other stream except STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE should be read-only
+ if((dwStreamFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
// if(pStream->BlockPresent)
// dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
@@ -2233,7 +2368,7 @@ 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)
+static int TestOpenArchive_MasterMirror(const char * szMirrorName, const char * szMasterName, const char * szFileToExtract, bool bCopyMirrorFile)
{
TFileData * pFileData;
TLogHelper Logger("OpenServerMirror", szMirrorName);
@@ -2245,7 +2380,7 @@ static int TestOpenArchive_MasterMirror(const char * szMirrorName, const char *
int nError;
// Create both paths
- nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName);
+ nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName, bCopyMirrorFile);
// Now open both archives as local-server pair
if(nError == ERROR_SUCCESS)
@@ -2469,7 +2604,7 @@ static int ForEachFile_OpenArchive(const char * szFullPath)
// Open the MPQ name
nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
- if(nError == ERROR_AVI_FILE)
+ if(nError == ERROR_AVI_FILE || nError == ERROR_FILE_CORRUPT || nError == ERROR_BAD_FORMAT)
return ERROR_SUCCESS;
// Search the archive and load every file
@@ -2480,6 +2615,9 @@ static int ForEachFile_OpenArchive(const char * szFullPath)
}
}
+ // Correct some errors
+ if(nError == ERROR_FILE_CORRUPT || nError == ERROR_FILE_INCOMPLETE)
+ return ERROR_SUCCESS;
return nError;
}
@@ -3151,7 +3289,7 @@ static int CreateArchiveLinkFile(const char * szFullPath1, const char * szFullPa
nLength = sprintf(szLinkData, "LINK:%s\x0D\x0ASHA1:%s", szLinkPath, szFileHash);
// Create the link file
- pStream = CreateLocalFile(szLinkFile, 0);
+ pStream = FileStream_CreateFileA(szLinkFile, 0);
if(pStream == NULL)
return GetLastError();
@@ -3226,19 +3364,35 @@ int main(int argc, char * argv[])
// Test reading linear file with bitmap
if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("MPQ_2013_v4_alternate-downloaded.MPQ", STREAM_FLAG_USE_BITMAP);
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-complete.MPQ", STREAM_FLAG_USE_BITMAP);
// Test reading partial file
if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("MPQ_2009_v2_WoW_patch.MPQ.part", STREAM_PROVIDER_PARTIAL);
+ nError = TestFileStreamOperations("part-file://MPQ_2009_v2_WoW_patch.MPQ.part", 0);
+
+ // Test reading Block4K file
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("blk4-file://streaming/model.MPQ.0", STREAM_PROVIDER_BLOCK4);
// Test reading encrypted file
if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("MPQ_2011_v2_EncryptedMpq.MPQE", STREAM_PROVIDER_ENCRYPTED);
+ nError = TestFileStreamOperations("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE", STREAM_PROVIDER_MPQE);
- // Search in listfile
+ // Open a stream, paired with local master
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-created.MPQ", "MPQ_2013_v4_alternate-original.MPQ", false);
+
+ // Open a stream, paired with local master
if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "MPQ_2013_v4_alternate-original.MPQ");
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-incomplete.MPQ", "MPQ_2013_v4_alternate-incomplete.MPQ", true);
+
+ // Open a stream, paired with local master
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-complete.MPQ", "MPQ_2013_v4_alternate-original.MPQ", true);
+
+ // Open a stream, paired with remote master (takes hell lot of time!)
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "http://www.zezula.net\\mpqs\\alternate.zip", false);
// Search in listfile
if(nError == ERROR_SUCCESS)
@@ -3264,7 +3418,7 @@ int main(int argc, char * argv[])
if(nError == ERROR_SUCCESS)
nError = TestOpenArchive("MPQ_2013_v4_patch-base-16357.MPQ");
- // Open an empty archive (found in WoW cache - it's just a header)
+ // Open an empty archive (A buggy MPQ with invalid HET entry count)
if(nError == ERROR_SUCCESS)
nError = TestOpenArchive("MPQ_2011_v4_InvalidHetEntryCount.MPQ");
@@ -3294,7 +3448,7 @@ int main(int argc, char * argv[])
// Open an encrypted archive from Starcraft II installer
if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2011_v2_EncryptedMpq.MPQE");
+ nError = TestOpenArchive("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE");
// Open a MPK archive from Longwu online
if(nError == ERROR_SUCCESS)
@@ -3306,11 +3460,15 @@ int main(int argc, char * argv[])
// Open a partial MPQ with compressed hash table
if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2010_v2_HashTableCompressed.MPQ.part");
+ nError = TestOpenArchive("part-file://MPQ_2010_v2_HashTableCompressed.MPQ.part");
+
+ // Open an archive that is merged with multiple files
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("blk4-file://streaming/model.MPQ.0");
// Open every MPQ that we have in the storage
if(nError == ERROR_SUCCESS)
- nError = FindFiles(ForEachFile_OpenArchive, szMpqDirectory);
+ nError = FindFiles(ForEachFile_OpenArchive, NULL);
// Test on an archive that has been invalidated by extending an old valid MPQ
if(nError == ERROR_SUCCESS)
@@ -3342,7 +3500,7 @@ int main(int argc, char * argv[])
// 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");
+ 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", false);
// Check archive signature
if(nError == ERROR_SUCCESS)