diff options
author | Ladislav <Zezula> | 2014-01-03 18:56:23 +0100 |
---|---|---|
committer | Ladislav <Zezula> | 2014-01-03 18:56:23 +0100 |
commit | 6cd009bc7cb60b4000e2267c7d100a3f0d9a42a0 (patch) | |
tree | 7e2b5dd6f85855c284067442fc6ebce5c95409f3 | |
parent | 3a9a6ec46beaf839cfe4fe8b6a26e1ca5e2d0316 (diff) |
+ Version 9.00 released
-rw-r--r-- | StormLib.vcproj | 160 | ||||
-rw-r--r-- | src/FileStream.cpp | 359 | ||||
-rw-r--r-- | src/FileStream.h | 4 | ||||
-rw-r--r-- | src/SBaseFileTable.cpp | 4 | ||||
-rw-r--r-- | src/SBaseSubTypes.cpp | 7 | ||||
-rw-r--r-- | src/SFileAttributes.cpp | 2 | ||||
-rw-r--r-- | src/StormPort.h | 1 | ||||
-rw-r--r-- | stormlib_dll/StormLib.def | 1 | ||||
-rw-r--r-- | test/Test.cpp | 344 |
9 files changed, 681 insertions, 201 deletions
diff --git a/StormLib.vcproj b/StormLib.vcproj index c3e6866..1eac0a3 100644 --- a/StormLib.vcproj +++ b/StormLib.vcproj @@ -1561,6 +1561,166 @@ <File RelativePath=".\src\SBaseSubTypes.cpp" > + <FileConfiguration + Name="DebugAD|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugAD|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugAS|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugAS|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAD|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAD|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAS|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAS|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugUD|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugUD|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugUS|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugUS|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseUD|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseUD|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseUS|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseUS|x64" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + PrecompiledHeaderThrough="StormCommon.h" + WarningLevel="4" + /> + </FileConfiguration> </File> <File RelativePath=".\src\SCompression.cpp" diff --git a/src/FileStream.cpp b/src/FileStream.cpp index c196f11..9cea184 100644 --- a/src/FileStream.cpp +++ b/src/FileStream.cpp @@ -47,6 +47,19 @@ void SetLastError(int nError) } #endif +static DWORD StringToInt(const char * szString) +{ + DWORD dwValue = 0; + + while('0' <= szString[0] && szString[0] <= '9') + { + dwValue = (dwValue * 10) + (szString[0] - '9'); + szString++; + } + + return dwValue; +} + //----------------------------------------------------------------------------- // Dummy init function @@ -199,10 +212,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->FilePos) + if(ByteOffset != pStream->Base.File.FilePos) { lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET); - pStream->FilePos = ByteOffset; + pStream->Base.File.FilePos = ByteOffset; } // Perform the read operation @@ -320,6 +333,8 @@ static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize) // Set the current file pointer as the end of the file bResult = (bool)SetEndOfFile(pStream->Base.File.hFile); + if(bResult) + pStream->Base.File.FileSize = NewFileSize; // Restore the file position FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32); @@ -337,6 +352,7 @@ static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize) return false; } + pStream->Base.File.FileSize = NewFileSize; return true; } #endif @@ -489,9 +505,9 @@ static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD // 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->FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); - pStream->FileSize = (ULONGLONG)fileinfo.st_size; - pStream->FilePos = 0; + pStream->Base.Map.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); + pStream->Base.Map.FileSize = (ULONGLONG)fileinfo.st_size; + pStream->Base.Map.FilePos = 0; bResult = true; } } @@ -542,7 +558,7 @@ 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->FileSize); + munmap(pStream->Base.Map.pbFile, (size_t )pStream->Base.Map.FileSize); #endif pStream->Base.Map.pbFile = NULL; @@ -1111,6 +1127,7 @@ static bool FlatStream_LoadBitmap(TBlockStream * pStream) } // Update the stream size + pStream->BuildNumber = Footer.BuildNumber; pStream->StreamSize = ByteOffset; // Fill the bitmap information @@ -1241,7 +1258,7 @@ static void FlatStream_Close(TBlockStream * pStream) // 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.BuildNumber = pStream->BuildNumber; Footer.MapOffsetLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF); Footer.MapOffsetHi = (DWORD)(pStream->StreamSize >> 0x20); Footer.BlockSize = pStream->BlockSize; @@ -1273,6 +1290,7 @@ static bool FlatStream_CreateMirror(TBlockStream * pStream) dwBitmapSize = (DWORD)((dwBlockCount + 7) / 8); // Setup stream size and position + pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really??? pStream->StreamSize = MasterSize; pStream->StreamPos = 0; @@ -1332,6 +1350,7 @@ static bool FlatStream_CreateMirror(TBlockStream * pStream) static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { TBlockStream * pStream; + ULONGLONG ByteOffset = 0; // Create new empty stream pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); @@ -1357,11 +1376,6 @@ static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFla // Load the bitmap, if required to if(dwStreamFlags & STREAM_FLAG_USE_BITMAP) FlatStream_LoadBitmap(pStream); - - // Setup the stream size - if(pStream->FileBitmap == NULL) - pStream->StreamSize = pStream->Base.File.FileSize; - pStream->StreamPos = 0; } // If we have a stream bitmap, set the reading functions @@ -1385,8 +1399,12 @@ static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFla } else { - // Reset the file position to zero - pStream->Base.File.FilePos = 0; + // Reset the base position to zero + pStream->BaseRead(pStream, &ByteOffset, NULL, 0); + + // Setup stream size and position + pStream->StreamSize = pStream->Base.File.FileSize; + pStream->StreamPos = 0; // Set the base functions pStream->StreamRead = pStream->BaseRead; @@ -1420,7 +1438,32 @@ static bool IsPartHeader(PPART_FILE_HEADER pPartHdr) return false; } -static bool PartialStream_LoadBitmap(TBlockStream * pStream) +static DWORD PartStream_CheckFile(TBlockStream * pStream) +{ + PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap; + DWORD dwBlockCount; + + // Get the number of blocks + dwBlockCount = (DWORD)((pStream->StreamSize + pStream->BlockSize - 1) / pStream->BlockSize); + + // Check all blocks + for(DWORD i = 0; i < dwBlockCount; i++, FileBitmap++) + { + // Few sanity checks + assert(FileBitmap->LargeValueHi == 0); + assert(FileBitmap->LargeValueLo == 0); + assert(FileBitmap->Flags == 0 || FileBitmap->Flags == 3); + + // Check if this block is present + if(FileBitmap->Flags != 3) + return 0; + } + + // Yes, the file is complete + return 1; +} + +static bool PartStream_LoadBitmap(TBlockStream * pStream) { PPART_FILE_MAP_ENTRY FileBitmap; PART_FILE_HEADER PartHdr; @@ -1465,6 +1508,7 @@ static bool PartialStream_LoadBitmap(TBlockStream * pStream) BSWAP_ARRAY32_UNSIGNED(FileBitmap, BitmapSize); // Update the stream size + pStream->BuildNumber = StringToInt(PartHdr.GameBuildNumber); pStream->StreamSize = StreamSize; // Fill the bitmap information @@ -1472,7 +1516,7 @@ static bool PartialStream_LoadBitmap(TBlockStream * pStream) pStream->BitmapSize = BitmapSize; pStream->BlockSize = PartHdr.BlockSize; pStream->BlockCount = BlockCount; - pStream->IsComplete = 0; + pStream->IsComplete = PartStream_CheckFile(pStream); return true; } } @@ -1483,25 +1527,58 @@ static bool PartialStream_LoadBitmap(TBlockStream * pStream) return false; } -static bool PartialStream_BlockCheck( +static void PartStream_UpdateBitmap( + TBlockStream * pStream, // Pointer to an open stream + ULONGLONG StartOffset, + ULONGLONG EndOffset, + ULONGLONG RealOffset) +{ + PPART_FILE_MAP_ENTRY FileBitmap; + DWORD BlockSize = pStream->BlockSize; + + // Sanity checks + assert((StartOffset & (BlockSize - 1)) == 0); + assert(pStream->FileBitmap != NULL); + + // Calculate the first entry in the block map + FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (StartOffset / BlockSize); + + // Set all bits for the specified range + while(StartOffset < EndOffset) + { + // Set the bit + FileBitmap->BlockOffsHi = (DWORD)(RealOffset >> 0x20); + FileBitmap->BlockOffsLo = (DWORD)(RealOffset & 0xFFFFFFFF); + FileBitmap->Flags = 3; + + // Move all + StartOffset += BlockSize; + RealOffset += BlockSize; + FileBitmap++; + } + + // Increment the bitmap update count + pStream->IsModified = 1; +} + +static bool PartStream_BlockCheck( TBlockStream * pStream, // Pointer to an open stream ULONGLONG BlockOffset) { - PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap; - DWORD BlockIndex; + PPART_FILE_MAP_ENTRY FileBitmap; // Sanity checks assert((BlockOffset & (pStream->BlockSize - 1)) == 0); - assert(FileBitmap != NULL); + assert(pStream->FileBitmap != NULL); - // Calculate the index of the block - BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize); + // Calculate the block map entry + FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (BlockOffset / pStream->BlockSize); // Check if the flags are present - return (FileBitmap[BlockIndex].Flags & 0x03) ? true : false; + return (FileBitmap->Flags & 0x03) ? true : false; } -static bool PartialStream_BlockRead( +static bool PartStream_BlockRead( TBlockStream * pStream, ULONGLONG StartOffset, ULONGLONG EndOffset, @@ -1519,18 +1596,38 @@ static bool PartialStream_BlockRead( assert((StartOffset & (pStream->BlockSize - 1)) == 0); assert(StartOffset < EndOffset); - // Get the file map entry - FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex; - // If the blocks are not available, we need to load them from the master // and then save to the mirror if(bAvailable == false) { - // TODO: Support for downloading blocks - return false; + // If we have no master, we cannot satisfy read request + if(pStream->pMaster == NULL) + return false; + + // Load the blocks from the master stream + // Note that we always have to read complete blocks + // so they get properly stored to the mirror stream + BytesToRead = (DWORD)(EndOffset - StartOffset); + if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead)) + return false; + + // The loaded blocks are going to be stored to the end of the file + // Note that this operation is not required to succeed + if(pStream->BaseGetSize(pStream, &ByteOffset)) + { + // Store the loaded blocks to the mirror file. + if(pStream->BaseWrite(pStream, &ByteOffset, BlockBuffer, BytesToRead)) + { + PartStream_UpdateBitmap(pStream, StartOffset, EndOffset, ByteOffset); + } + } } else { + // Get the file map entry + FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex; + + // Read all blocks while(StartOffset < EndOffset) { // Get the number of bytes to be read @@ -1549,13 +1646,125 @@ static bool PartialStream_BlockRead( StartOffset += pStream->BlockSize; BlockBuffer += pStream->BlockSize; BytesNeeded -= pStream->BlockSize; + FileBitmap++; + } + } + + return true; +} + +static void PartStream_Close(TBlockStream * pStream) +{ + PART_FILE_HEADER PartHeader; + ULONGLONG ByteOffset = 0; + + if(pStream->FileBitmap && pStream->IsModified) + { + // Prepare the part file header + memset(&PartHeader, 0, sizeof(PART_FILE_HEADER)); + PartHeader.PartialVersion = 2; + PartHeader.FileSizeHi = (DWORD)(pStream->StreamSize >> 0x20); + PartHeader.FileSizeLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF); + PartHeader.BlockSize = pStream->BlockSize; + + // Make sure that the header is properly BSWAPed + BSWAP_ARRAY32_UNSIGNED(&PartHeader, sizeof(PART_FILE_HEADER)); + sprintf(PartHeader.GameBuildNumber, "%u", pStream->BuildNumber); + + // Write the part header + pStream->BaseWrite(pStream, &ByteOffset, &PartHeader, sizeof(PART_FILE_HEADER)); + + // Write the block bitmap + BSWAP_ARRAY32_UNSIGNED(pStream->FileBitmap, pStream->BitmapSize); + pStream->BaseWrite(pStream, NULL, pStream->FileBitmap, pStream->BitmapSize); + } + + // Close the base class + BlockStream_Close(pStream); +} + +static bool PartStream_CreateMirror(TBlockStream * pStream) +{ + ULONGLONG RemainingSize; + 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; + + // 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 * sizeof(PART_FILE_MAP_ENTRY)); + + // Setup stream size and position + pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really??? + 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 >= sizeof(PART_FILE_HEADER) + dwBitmapSize) + { + // Check if the remaining size is aligned to block + RemainingSize = MirrorSize - sizeof(PART_FILE_HEADER) - dwBitmapSize; + if((RemainingSize & (DEFAULT_BLOCK_SIZE - 1)) == 0 || RemainingSize == MasterSize) + { + // Attempt to load an existing file bitmap + if(PartStream_LoadBitmap(pStream)) + return true; + } } + + // 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, sizeof(PART_FILE_HEADER) + dwBitmapSize)) + 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 * PartialStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) + +static TFileStream * PartStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { TBlockStream * pStream; @@ -1564,44 +1773,49 @@ static TFileStream * PartialStream_Open(const TCHAR * szFileName, DWORD dwStream 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, pStream->szFileName, dwStreamFlags)) + // Do we have a master stream? + if(pStream->pMaster != NULL) { - // Do we have base create function and master stream? -// if(!PartialStream_CreateMirror(pStream)) + if(!PartStream_CreateMirror(pStream)) { FileStream_Close(pStream); SetLastError(ERROR_FILE_NOT_FOUND); return NULL; } } - - // Load the stream bitmap - if(PartialStream_LoadBitmap(pStream)) + else { - // 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 new function pointers - pStream->StreamRead = (STREAM_READ)BlockStream_Read; - pStream->StreamGetPos = BlockStream_GetPos; - pStream->StreamGetSize = BlockStream_GetSize; - pStream->StreamClose = (STREAM_CLOSE)BlockStream_Close; + // Attempt to open the base stream + if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags)) + { + FileStream_Close(pStream); + return NULL; + } - // Supply the block functions - pStream->BlockCheck = (BLOCK_CHECK)PartialStream_BlockCheck; - pStream->BlockRead = (BLOCK_READ)PartialStream_BlockRead; - return pStream; + // Load the part stream block map + if(!PartStream_LoadBitmap(pStream)) + { + FileStream_Close(pStream); + SetLastError(ERROR_BAD_FORMAT); + return NULL; + } } - // Cleanup the stream and return - FileStream_Close(pStream); - SetLastError(ERROR_BAD_FORMAT); - return 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 new function pointers + pStream->StreamRead = (STREAM_READ)BlockStream_Read; + pStream->StreamGetPos = BlockStream_GetPos; + pStream->StreamGetSize = BlockStream_GetSize; + pStream->StreamClose = (STREAM_CLOSE)PartStream_Close; + + // Supply the block functions + pStream->BlockCheck = (BLOCK_CHECK)PartStream_BlockCheck; + pStream->BlockRead = (BLOCK_READ)PartStream_BlockRead; + return pStream; } //----------------------------------------------------------------------------- @@ -1927,6 +2141,7 @@ static bool Block4Stream_BlockRead( DWORD BytesToRead; DWORD StreamIndex; DWORD BlockIndex; + bool bResult; // The starting offset must be aligned to size of the block assert(pStream->FileBitmap != NULL); @@ -1934,6 +2149,10 @@ static bool Block4Stream_BlockRead( assert(StartOffset < EndOffset); assert(bAvailable == true); + // Keep compiler happy + bAvailable = bAvailable; + EndOffset = EndOffset; + while(BytesNeeded != 0) { // Calculate the block index and the file index @@ -1942,15 +2161,17 @@ static bool Block4Stream_BlockRead( 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); + BytesToRead = STORMLIB_MIN(BytesNeeded, BLOCK4_BLOCK_SIZE); // Read from the base stream - if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead)) + pStream->Base = BaseArray[StreamIndex]; + bResult = pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead); + BaseArray[StreamIndex] = pStream->Base; + + // Did the result succeed? + if(bResult == false) return false; // Move pointers @@ -2197,7 +2418,7 @@ TFileStream * FileStream_OpenFile( return FlatStream_Open(szFileName, dwStreamFlags); case STREAM_PROVIDER_PARTIAL: - return PartialStream_Open(szFileName, dwStreamFlags); + return PartStream_Open(szFileName, dwStreamFlags); case STREAM_PROVIDER_MPQE: return MpqeStream_Open(szFileName, dwStreamFlags); @@ -2247,19 +2468,19 @@ size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider) nPrefixLength1 = 5; } - if(!_tcsnicmp(szFileName, _T("part-"), 5)) + else if(!_tcsnicmp(szFileName, _T("part-"), 5)) { dwProvider |= STREAM_PROVIDER_PARTIAL; nPrefixLength1 = 5; } - if(!_tcsnicmp(szFileName, _T("mpqe-"), 5)) + else if(!_tcsnicmp(szFileName, _T("mpqe-"), 5)) { dwProvider |= STREAM_PROVIDER_MPQE; nPrefixLength1 = 5; } - if(!_tcsnicmp(szFileName, _T("blk4-"), 5)) + else if(!_tcsnicmp(szFileName, _T("blk4-"), 5)) { dwProvider |= STREAM_PROVIDER_BLOCK4; nPrefixLength1 = 5; @@ -2275,13 +2496,13 @@ size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider) nPrefixLength2 = 5; } - if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4)) + else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4)) { dwProvider |= BASE_PROVIDER_MAP; nPrefixLength2 = 4; } - if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5)) + else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5)) { dwProvider |= BASE_PROVIDER_HTTP; nPrefixLength2 = 5; @@ -2339,7 +2560,7 @@ bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCa * \a cbLengthNeeded Length of the bitmap, in bytes */ -bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, PDWORD pcbLengthNeeded) +bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, DWORD * pcbLengthNeeded) { TStreamBitmap * pBitmap = (TStreamBitmap *)pvBitmap; TBlockStream * pBlockStream = (TBlockStream *)pStream; diff --git a/src/FileStream.h b/src/FileStream.h index 60f41d3..44beeed 100644 --- a/src/FileStream.h +++ b/src/FileStream.h @@ -70,7 +70,7 @@ typedef bool (*BLOCK_READ)( bool bAvailable // true if the block is available ); -typedef DWORD (*BLOCK_CHECK)( +typedef bool (*BLOCK_CHECK)( struct TFileStream * pStream, // Pointer to a block-oriented stream ULONGLONG BlockOffset // Offset of the file to check ); @@ -84,6 +84,7 @@ typedef void (*BLOCK_SAVEMAP)( #define ID_FILE_BITMAP_FOOTER 0x33767470 // Signature of the file bitmap footer ('ptv3') #define DEFAULT_BLOCK_SIZE 0x00004000 // Default size of the stream block +#define DEFAULT_BUILD_NUMBER 10958 // Build number for newly created partial MPQs typedef struct _PART_FILE_HEADER { @@ -182,6 +183,7 @@ struct TFileStream ULONGLONG StreamSize; // Stream size (can be less than file size) ULONGLONG StreamPos; // Stream position + DWORD BuildNumber; // Game build number DWORD dwFlags; // Stream flags // Followed by stream provider data, with variable length diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 322211c..54b66c4 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -270,6 +270,10 @@ static ULONGLONG DetermineArchiveSize_V1_V2( if(pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)) == pHeader->dwArchiveSize) return pHeader->dwArchiveSize; + // If both block table and archive size seem to be out of the file size + if(pHeader->dwBlockTablePos > FileSize && pHeader->dwArchiveSize > FileSize) + return pHeader->dwArchiveSize; + // If the archive size in the header is less than real file size dwArchiveSize32 = (DWORD)(FileSize - MpqOffset); if(pHeader->dwArchiveSize <= dwArchiveSize32) diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp index 288495f..b44cd9e 100644 --- a/src/SBaseSubTypes.cpp +++ b/src/SBaseSubTypes.cpp @@ -441,7 +441,7 @@ int ConvertMpkHeaderToFormat4( // Attempts to search a free hash entry in the hash table being converted. // The created hash table must always be of nonzero size, // should have no duplicated items and no deleted entries -TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2) +TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD dwStartIndex) { TMPQHash * pHash; DWORD dwIndex; @@ -456,7 +456,6 @@ TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD // We are not expecting to find matching entry in the hash table being built // We are not expecting to find deleted entry either pHash = pHashTable + dwIndex; - assert(pHash->dwName1 != dwName1 || pHash->dwName2 != dwName2); // If we found a free entry, we need to stop searching if(pHash->dwBlockIndex == HASH_ENTRY_FREE) @@ -542,8 +541,8 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) for(DWORD i = 0; i < dwHashTableSize; i++) { // Finds the free hash entry in the hash table - // We don;t expect any errors here, because we are putting files to empty hash table - pHash = FindFreeHashEntry(pHashTable, pHeader->dwHashTableSize, pMpkHash[i].dwName1, pMpkHash[i].dwName2, pMpkHash[i].dwName3); + // We don't expect any errors here, because we are putting files to empty hash table + pHash = FindFreeHashEntry(pHashTable, pHeader->dwHashTableSize, pMpkHash[i].dwName1); assert(pHash->dwBlockIndex == HASH_ENTRY_FREE); // Copy the MPK hash entry to the hash table diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp index 7e63993..f947d79 100644 --- a/src/SFileAttributes.cpp +++ b/src/SFileAttributes.cpp @@ -95,7 +95,7 @@ static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrF LPBYTE pbAttrPtr = pbAttrFile; DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize; DWORD i; - bool bPatchBitsValid; + bool bPatchBitsValid = false; // Load and verify the header if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd) diff --git a/src/StormPort.h b/src/StormPort.h index 0d98bcb..218d5cf 100644 --- a/src/StormPort.h +++ b/src/StormPort.h @@ -168,6 +168,7 @@ #define _tcslen strlen #define _tcscpy strcpy #define _tcscat strcat + #define _tcschr strchr #define _tcsrchr strrchr #define _tcsstr strstr #define _tprintf printf diff --git a/stormlib_dll/StormLib.def b/stormlib_dll/StormLib.def index 0ba9188..a85f942 100644 --- a/stormlib_dll/StormLib.def +++ b/stormlib_dll/StormLib.def @@ -13,7 +13,6 @@ EXPORTS SFileOpenArchive SFileCreateArchive - SFileGetArchiveBitmap SFileFlushArchive SFileCloseArchive diff --git a/test/Test.cpp b/test/Test.cpp index 6f27f1e..4dce839 100644 --- a/test/Test.cpp +++ b/test/Test.cpp @@ -28,6 +28,10 @@ #pragma comment(lib, "winmm.lib") #endif +#ifdef PLATFORM_LINUX +#include <dirent.h> +#endif + //------------------------------------------------------------------------------ // Defines @@ -393,13 +397,15 @@ static TFileStream * FileStream_CreateFileA(const char * szFileName, DWORD dwStr static size_t FileStream_PrefixA(const char * szFileName, DWORD * pdwProvider) { TCHAR szFileNameT[MAX_PATH]; + size_t nPrefixLength = 0; if(szFileName != NULL) { CopyFileName(szFileNameT, szFileName, strlen(szFileName)); - return FileStream_Prefix(szFileNameT, pdwProvider); + nPrefixLength = FileStream_Prefix(szFileNameT, pdwProvider); } - return 0; + + return nPrefixLength; } static void CreateFullPathName(char * szBuffer, const char * szSubDir, const char * szNamePart1, const char * szNamePart2 = NULL) @@ -491,57 +497,205 @@ static void CreateFullPathName(char * szBuffer, const char * szSubDir, const cha *szBuffer = 0; } -static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, TCHAR * szDirectoryT, char * szDirectoryA) +static int CalculateFileSha1(TLogHelper * pLogger, const char * szFullPath, char * szFileSha1) { + TFileStream * pStream; + unsigned char sha1_digest[SHA1_DIGEST_SIZE]; + const char * szShortPlainName = GetShortPlainName(szFullPath); + hash_state sha1_state; + ULONGLONG ByteOffset = 0; + ULONGLONG FileSize = 0; + BYTE * pbFileBlock; + DWORD cbBytesToRead; + DWORD cbFileBlock = 0x100000; int nError = ERROR_SUCCESS; - if(szDirectoryT != NULL && szDirectoryA != NULL) + // Notify the user + pLogger->PrintProgress("Hashing file %s", szShortPlainName); + szFileSha1[0] = 0; + + // Open the file to be verified + pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY); + if(pStream != NULL) { + // Retrieve the size of the file + FileStream_GetSize(pStream, &FileSize); + + // Allocate the buffer for loading file parts + pbFileBlock = STORM_ALLOC(BYTE, cbFileBlock); + if(pbFileBlock != NULL) + { + // Initialize SHA1 calculation + sha1_init(&sha1_state); + + // Calculate the SHA1 of the file + while(ByteOffset < FileSize) + { + // Notify the user + pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize); + + // Load the file block + cbBytesToRead = ((FileSize - ByteOffset) > cbFileBlock) ? cbFileBlock : (DWORD)(FileSize - ByteOffset); + if(!FileStream_Read(pStream, &ByteOffset, pbFileBlock, cbBytesToRead)) + { + nError = GetLastError(); + break; + } + + // Add to SHA1 + sha1_process(&sha1_state, pbFileBlock, cbBytesToRead); + ByteOffset += cbBytesToRead; + } + + // Notify the user + pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize); + + // Finalize SHA1 + sha1_done(&sha1_state, sha1_digest); + + // Convert the SHA1 to ANSI text + ConvertSha1ToText(sha1_digest, szFileSha1); + STORM_FREE(pbFileBlock); + } + + FileStream_Close(pStream); + } + + // If we calculated something, return OK + if(nError == ERROR_SUCCESS && szFileSha1[0] == 0) + nError = ERROR_CAN_NOT_COMPLETE; + return nError; +} + +//----------------------------------------------------------------------------- +// Directory search + +static HANDLE InitDirectorySearch(const char * szDirectory) +{ #ifdef PLATFORM_WINDOWS - 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; + WIN32_FIND_DATA wf; + HANDLE hFind; + TCHAR szSearchMask[MAX_PATH]; - strcat(szDirectoryA, "\\*"); - szPlainNameA = szDirectoryA + nLength + 1; + // Keep compilers happy + CopyFileName(szSearchMask, szDirectory, strlen(szDirectory)); + _tcscat(szSearchMask, _T("\\*")); - // Initiate search. Use ANSI function only - hFind = FindFirstFile(szDirectoryT, &wf); - if(hFind != INVALID_HANDLE_VALUE) + // Construct the directory mask + hFind = FindFirstFile(szSearchMask, &wf); + return (hFind != INVALID_HANDLE_VALUE) ? hFind : NULL; + +#endif + +#ifdef PLATFORM_LINUX + + // Keep compilers happy + return (HANDLE)opendir(szDirectory); + +#endif +} + +static bool SearchDirectory(HANDLE hFind, char * szDirEntry, bool & IsDirectory) +{ +#ifdef PLATFORM_WINDOWS + + WIN32_FIND_DATA wf; + TCHAR szDirEntryT[MAX_PATH]; + char szDirEntryA[MAX_PATH]; + + __SearchNextEntry: + + // Search for the hnext entry. + if(FindNextFile(hFind, &wf)) + { + // Verify if the directory entry is an UNICODE name that would be destroyed + // by Unicode->ANSI->Unicode conversion + if(CopyStringAndVerifyConversion(wf.cFileName, szDirEntryT, szDirEntryA) == false) + goto __SearchNextEntry; + + IsDirectory = (wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false; + CopyFileName(szDirEntry, wf.cFileName, _tcslen(wf.cFileName)); + return true; + } + + return false; + +#endif + +#ifdef PLATFORM_LINUX + + struct dirent * directory_entry; + + directory_entry = readdir((DIR *)hFind); + if(directory_entry != NULL) + { + IsDirectory = (directory_entry->d_type == DT_DIR) ? true : false; + strcpy(szDirEntry, directory_entry->d_name); + return true; + } + + return false; + +#endif +} + +static void FreeDirectorySearch(HANDLE hFind) +{ +#ifdef PLATFORM_WINDOWS + FindClose(hFind); +#endif + +#ifdef PLATFORM_LINUX + closedir((DIR *)hFind); +#endif +} + +static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, char * szDirectory) +{ + HANDLE hFind; + char * szPlainName; + size_t nLength; + char szDirEntry[MAX_PATH]; + bool IsDirectory = false; + int nError = ERROR_SUCCESS; + + if(szDirectory != NULL) + { + // Initiate directory search + hFind = InitDirectorySearch(szDirectory); + if(hFind != NULL) { + // Append slash at the end of the directory name + nLength = strlen(szDirectory); + szDirectory[nLength++] = PATH_SEPARATOR; + szPlainName = szDirectory + nLength; + // Skip the first entry, since it's always "." or ".." - while(FindNextFile(hFind, &wf) && nError == ERROR_SUCCESS) + while(SearchDirectory(hFind, szDirEntry, IsDirectory) && nError == ERROR_SUCCESS) { - // If the file name can be converted to UNICODE and back - if(CopyStringAndVerifyConversion(wf.cFileName, szPlainNameT, szPlainNameA)) + // Copy the directory entry name to both names + strcpy(szPlainName, szDirEntry); + + // Found a directory? + if(IsDirectory) { - // Found a directory? - if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + if(szDirEntry[0] != '.') { - if(wf.cFileName[0] != '.') - { - nError = FindFilesInternal(pfnTest, szDirectoryT, szDirectoryA); - } + nError = FindFilesInternal(pfnTest, szDirectory); } - else + } + else + { + if(pfnTest != NULL) { - if(pfnTest != NULL) - { - nError = pfnTest(szDirectoryA); - } + nError = pfnTest(szDirectory); } } } - FindClose(hFind); + FreeDirectorySearch(hFind); } -#endif } // Free the path buffer, if any @@ -550,12 +704,10 @@ static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, TCHAR * szDirectoryT, c static int FindFiles(FIND_FILE_CALLBACK pfnFindFile, const char * szSubDirectory) { - TCHAR szWorkBuffT[MAX_PATH]; - char szWorkBuffA[MAX_PATH]; + char szWorkBuff[MAX_PATH]; - CreateFullPathName(szWorkBuffA, szSubDirectory, NULL); - CopyFileName(szWorkBuffT, szWorkBuffA, strlen(szWorkBuffA)); - return FindFilesInternal(pfnFindFile, szWorkBuffT, szWorkBuffA); + CreateFullPathName(szWorkBuff, szSubDirectory, NULL); + return FindFilesInternal(pfnFindFile, szWorkBuff); } static int FindFilePairsInternal( @@ -627,76 +779,6 @@ static int FindFilePairs(FIND_PAIR_CALLBACK pfnFindPair, const char * szSourceSu return FindFilePairsInternal(pfnFindPair, szSource, szTarget); } -static int CalculateFileSha1(TLogHelper * pLogger, const char * szFullPath, char * szFileSha1) -{ - TFileStream * pStream; - unsigned char sha1_digest[SHA1_DIGEST_SIZE]; - const char * szShortPlainName = GetShortPlainName(szFullPath); - hash_state sha1_state; - ULONGLONG ByteOffset = 0; - ULONGLONG FileSize = 0; - BYTE * pbFileBlock; - DWORD cbBytesToRead; - DWORD cbFileBlock = 0x100000; - int nError = ERROR_SUCCESS; - - // Notify the user - pLogger->PrintProgress("Hashing file %s", szShortPlainName); - szFileSha1[0] = 0; - - // Open the file to be verified - pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY); - if(pStream != NULL) - { - // Retrieve the size of the file - FileStream_GetSize(pStream, &FileSize); - - // Allocate the buffer for loading file parts - pbFileBlock = STORM_ALLOC(BYTE, cbFileBlock); - if(pbFileBlock != NULL) - { - // Initialize SHA1 calculation - sha1_init(&sha1_state); - - // Calculate the SHA1 of the file - while(ByteOffset < FileSize) - { - // Notify the user - pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize); - - // Load the file block - cbBytesToRead = ((FileSize - ByteOffset) > cbFileBlock) ? cbFileBlock : (DWORD)(FileSize - ByteOffset); - if(!FileStream_Read(pStream, &ByteOffset, pbFileBlock, cbBytesToRead)) - { - nError = GetLastError(); - break; - } - - // Add to SHA1 - sha1_process(&sha1_state, pbFileBlock, cbBytesToRead); - ByteOffset += cbBytesToRead; - } - - // Notify the user - pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize); - - // Finalize SHA1 - sha1_done(&sha1_state, sha1_digest); - - // Convert the SHA1 to ANSI text - ConvertSha1ToText(sha1_digest, szFileSha1); - STORM_FREE(pbFileBlock); - } - - FileStream_Close(pStream); - } - - // If we calculated something, return OK - if(nError == ERROR_SUCCESS && szFileSha1[0] == 0) - nError = ERROR_CAN_NOT_COMPLETE; - return nError; -} - static int InitializeMpqDirectory(char * argv[], int argc) { TLogHelper Logger("InitWorkDir"); @@ -1033,11 +1115,12 @@ static int CreateFileCopy( int nError = ERROR_SUCCESS; // Notify the user + szPlainName += FileStream_PrefixA(szPlainName, NULL); pLogger->PrintProgress("Creating copy of %s ...", szPlainName); // Construct both file names. Check if they are not the same CreateFullPathName(szFileName1, szMpqSubDir, szPlainName); - CreateFullPathName(szFileName2, NULL, szFileCopy); + CreateFullPathName(szFileName2, NULL, szFileCopy + FileStream_PrefixA(szFileCopy, NULL)); if(!_stricmp(szFileName1, szFileName2)) { pLogger->PrintError("Failed to create copy of MPQ (the copy name is the same like the original name)"); @@ -1087,7 +1170,7 @@ static int CreateFileCopy( FileStream_Close(pStream1); if(szBuffer != NULL) - strcpy(szBuffer, szFileName2); + CreateFullPathName(szBuffer, NULL, szFileCopy); if(nError != ERROR_SUCCESS) pLogger->PrintError("Failed to create copy of MPQ"); return nError; @@ -1104,23 +1187,18 @@ static int CreateMasterAndMirrorPaths( char szCopyPath[MAX_PATH]; int nError = ERROR_SUCCESS; + // Always delete the mirror file + CreateFullPathName(szMasterPath, szMpqSubDir, szMasterName); + CreateFullPathName(szCopyPath, NULL, szMirrorName); + remove(szCopyPath + FileStream_PrefixA(szCopyPath, NULL)); + // Copy the mirrored file from the source to the work directory if(bCopyMirrorFile) - nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, szCopyPath); - else - { - CreateFullPathName(szCopyPath, NULL, szMirrorName); - remove(szCopyPath); - } + nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, NULL); + // Create the mirror*master path 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; } @@ -1314,7 +1392,7 @@ static int CompareTwoLocalFilesRR( if(!CompareBlocks(pbBuffer1, pbBuffer2, BytesToRead, &Difference)) { - pLogger->PrintMessage("Difference at %u (Offset " I64u_a ", Length %u)", Difference, ByteOffset, BytesToRead); + pLogger->PrintMessage("Difference at %u (Offset " I64X_a ", Length %X)", Difference, ByteOffset, BytesToRead); nError = ERROR_FILE_CORRUPT; break; } @@ -3351,8 +3429,8 @@ int main(int argc, char * argv[]) // nError = FindFilePairs(ForEachFile_CreateArchiveLink, "2004 - WoW\\06080", "2004 - WoW\\06299"); // Search all testing archives and verify their SHA1 hash -// if(nError == ERROR_SUCCESS) -// nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqDirectory); + if(nError == ERROR_SUCCESS) + nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqSubDir); // Test reading linear file without bitmap if(nError == ERROR_SUCCESS) @@ -3378,6 +3456,18 @@ int main(int argc, char * argv[]) if(nError == ERROR_SUCCESS) nError = TestFileStreamOperations("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE", STREAM_PROVIDER_MPQE); + // Open a stream, paired with local master. The mirror file is created new + if(nError == ERROR_SUCCESS) + nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-created.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", false); + + // Open a stream, paired with local master. Only part of the mirror exists + if(nError == ERROR_SUCCESS) + nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-partial.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", true); + + // Open a stream, paired with local master. Only part of the mirror exists + if(nError == ERROR_SUCCESS) + nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-complete.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", true); + // 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); @@ -3500,6 +3590,10 @@ int main(int argc, char * argv[]) // Downloadable MPQ archive if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_MasterMirror("part-file://MPQ_2009_v1_patch-partial.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", "world\\Azeroth\\DEADMINES\\PASSIVEDOODADS\\GOBLINMELTINGPOT\\DUST2.BLP", false); + + // 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", false); // Check archive signature |