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  | 
