diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:59:00 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:59:00 +0100 |
commit | c34c37b3418f1e5ab3678ce65d46f81803dec91d (patch) | |
tree | 4a9cf4c61634691981f9dc367b53dac4070f8d0d | |
parent | ff0c25952a28a927c48738ab5207b9bda69e588a (diff) |
+ StormLib 9.0 BETA
35 files changed, 5534 insertions, 5543 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bec366..ac80fd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set(SRC_FILES src/SFileCreateArchive.cpp src/SFileExtractFile.cpp src/SFileFindFile.cpp + src/SFileGetFileInfo.cpp src/SFileListFile.cpp src/SFileOpenArchive.cpp src/SFileOpenFileEx.cpp @@ -292,8 +293,8 @@ if(APPLE) endif() if(UNIX) - set_target_properties(storm PROPERTIES VERSION 8.21.0) - set_target_properties(storm PROPERTIES SOVERSION 8) + set_target_properties(storm PROPERTIES VERSION 9.0.0) + set_target_properties(storm PROPERTIES SOVERSION 9) endif() # On Win32, build StormLib.dll since we don't want to clash with Storm.dll diff --git a/Makefile.linux b/Makefile.linux index 0afe0ea..48c6be9 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -40,6 +40,7 @@ OBJS = src/adpcm/adpcm.o \ src/SFileCreateArchive.o \ src/SFileExtractFile.o \ src/SFileFindFile.o \ + src/SFileGetFileInfo.o \ src/SFileListFile.o \ src/SFileOpenArchive.o \ src/SFileOpenFileEx.o \ diff --git a/Makefile.mac b/Makefile.mac index 8d17ce0..66d8aa1 100644 --- a/Makefile.mac +++ b/Makefile.mac @@ -33,6 +33,7 @@ OBJS_CPP = src/adpcm/adpcm.obj \ src/SFileCreateArchive.obj \ src/SFileExtractFile.obj \ src/SFileFindFile.obj \ + src/SFileGetFileInfo.obj \ src/SFileListFile.obj \ src/SFileOpenArchive.obj \ src/SFileOpenFileEx.obj \ diff --git a/StormLib.vcproj b/StormLib.vcproj index 8fc4f4e..c3e6866 100644 --- a/StormLib.vcproj +++ b/StormLib.vcproj @@ -2711,6 +2711,170 @@ </FileConfiguration> </File> <File + RelativePath=".\src\SFileGetFileInfo.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\SFileListFile.cpp" > <FileConfiguration diff --git a/StormLib.vcxproj b/StormLib.vcxproj index eeadbfd..e1b22aa 100644 --- a/StormLib.vcxproj +++ b/StormLib.vcxproj @@ -666,6 +666,11 @@ <PrecompiledHeaderFile>StormCommon.h</PrecompiledHeaderFile> <WarningLevel>Level4</WarningLevel> </ClCompile> + <ClCompile Include="src\SFileGetFileInfo.cpp"> + <PrecompiledHeader>Create</PrecompiledHeader> + <PrecompiledHeaderFile>StormCommon.h</PrecompiledHeaderFile> + <WarningLevel>Level4</WarningLevel> + </ClCompile> <ClCompile Include="src\SFileListFile.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> <PrecompiledHeaderFile>StormCommon.h</PrecompiledHeaderFile> diff --git a/StormLib.vcxproj.filters b/StormLib.vcxproj.filters index d4fd79e..b80d6b2 100644 --- a/StormLib.vcxproj.filters +++ b/StormLib.vcxproj.filters @@ -133,6 +133,9 @@ <ClCompile Include="src\SFileFindFile.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\SFileGetFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="src\SFileListFile.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/StormLib_dll.vcproj b/StormLib_dll.vcproj index 4991465..ee12d66 100644 --- a/StormLib_dll.vcproj +++ b/StormLib_dll.vcproj @@ -1145,6 +1145,90 @@ </FileConfiguration> </File> <File + RelativePath=".\src\SFileGetFileInfo.cpp" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugAD|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="DebugAD|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAD|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAD|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAS|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="ReleaseAS|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + </File> + <File RelativePath=".\src\SFileListFile.cpp" > <FileConfiguration diff --git a/StormLib_dll.vcxproj b/StormLib_dll.vcxproj index c0f9b7b..8f3eb4a 100644 --- a/StormLib_dll.vcxproj +++ b/StormLib_dll.vcxproj @@ -260,6 +260,12 @@ <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Level4</WarningLevel> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Level4</WarningLevel> </ClCompile> + <ClCompile Include="src\SFileGetFileInfo.cpp"> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Level4</WarningLevel> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Level4</WarningLevel> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Level4</WarningLevel> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Level4</WarningLevel> + </ClCompile> <ClCompile Include="src\SFileListFile.cpp"> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Level4</WarningLevel> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Level4</WarningLevel> diff --git a/StormLib_dll.vcxproj.filters b/StormLib_dll.vcxproj.filters index 8b1fe89..c1fff27 100644 --- a/StormLib_dll.vcxproj.filters +++ b/StormLib_dll.vcxproj.filters @@ -136,6 +136,9 @@ <ClCompile Include="src\SFileFindFile.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\SFileGetFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="src\SFileListFile.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/StormLib_test.vcproj b/StormLib_test.vcproj index 61e04ea..3276b53 100644 --- a/StormLib_test.vcproj +++ b/StormLib_test.vcproj @@ -779,6 +779,42 @@ </FileConfiguration> </File> <File + RelativePath=".\src\SFileGetFileInfo.cpp" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|x64" + > + <Tool + Name="VCCLCompilerTool" + WarningLevel="4" + /> + </FileConfiguration> + </File> + <File RelativePath=".\src\SFileListFile.cpp" > <FileConfiguration diff --git a/StormLib_test.vcxproj b/StormLib_test.vcxproj index ea4c6b4..3dcb035 100644 --- a/StormLib_test.vcxproj +++ b/StormLib_test.vcxproj @@ -265,6 +265,12 @@ <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Level4</WarningLevel> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Level4</WarningLevel> </ClCompile> + <ClCompile Include="src\SFileGetFileInfo.cpp"> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Level4</WarningLevel> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Level4</WarningLevel> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Level4</WarningLevel> + <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Level4</WarningLevel> + </ClCompile> <ClCompile Include="src\SFileListFile.cpp"> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Level4</WarningLevel> <WarningLevel Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Level4</WarningLevel> diff --git a/StormLib_test.vcxproj.filters b/StormLib_test.vcxproj.filters index cb6197d..e442747 100644 --- a/StormLib_test.vcxproj.filters +++ b/StormLib_test.vcxproj.filters @@ -139,6 +139,9 @@ <ClCompile Include="src\SFileFindFile.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\SFileGetFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="src\SFileListFile.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/makefile.w32 b/makefile.w32 index 8f3424a..7435bd8 100644 --- a/makefile.w32 +++ b/makefile.w32 @@ -35,6 +35,7 @@ OBJS_CPP = src/adpcm/adpcm.o \ src/SFileCreateArchive.o \ src/SFileExtractFile.o \ src/SFileFindFile.o \ + src/SFileGetFileInfo.o \ src/SFileListFile.o \ src/SFileOpenArchive.o \ src/SFileOpenFileEx.o \ diff --git a/src/FileStream.cpp b/src/FileStream.cpp index 373562a..81933fd 100644 --- a/src/FileStream.cpp +++ b/src/FileStream.cpp @@ -2006,7 +2006,7 @@ bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream) * * \a pStream Pointer to an open stream */ -TCHAR * FileStream_GetFileName(TFileStream * pStream) +const TCHAR * FileStream_GetFileName(TFileStream * pStream) { assert(pStream != NULL); return pStream->szFileName; @@ -2095,6 +2095,61 @@ void FileStream_Close(TFileStream * pStream) } //----------------------------------------------------------------------------- +// Utility functions (ANSI) + +const char * GetPlainFileName(const char * szFileName) +{ + const char * szPlainName = szFileName; + + while(*szFileName != 0) + { + if(*szFileName == '\\' || *szFileName == '/') + szPlainName = szFileName + 1; + szFileName++; + } + + return szPlainName; +} + +void CopyFileName(char * szTarget, const char * szSource, size_t cchLength) +{ + memcpy(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} + +//----------------------------------------------------------------------------- +// Utility functions (UNICODE) only exist in the ANSI version of the library +// In ANSI builds, TCHAR = char, so we don't need these functions implemented + +#ifdef _UNICODE +const TCHAR * GetPlainFileName(const TCHAR * szFileName) +{ + const TCHAR * szPlainName = szFileName; + + while(*szFileName != 0) + { + if(*szFileName == '\\' || *szFileName == '/') + szPlainName = szFileName + 1; + szFileName++; + } + + return szPlainName; +} + +void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength) +{ + mbstowcs(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} + +void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength) +{ + wcstombs(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} +#endif + +//----------------------------------------------------------------------------- // main - for testing purposes #ifdef __STORMLIB_TEST__ @@ -2141,154 +2196,3 @@ int FileStream_Test(const TCHAR * szFileName, DWORD dwStreamFlags) return ERROR_SUCCESS; } #endif - -/* -int FileStream_Test() -{ - TFileStream * pStream; - - InitializeMpqCryptography(); - - // - // Test 1: Write to a stream - // - - pStream = FileStream_CreateFile("E:\\Stream.bin", 0); - if(pStream != NULL) - { - char szString1[100] = "This is a single line\n\r"; - DWORD dwLength = strlen(szString1); - - for(int i = 0; i < 10; i++) - { - if(!FileStream_Write(pStream, NULL, szString1, dwLength)) - { - printf("Failed to write to the stream\n"); - return ERROR_CAN_NOT_COMPLETE; - } - } - FileStream_Close(pStream); - } - - // - // Test2: Read from the stream - // - - pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - char szString1[100] = "This is a single line\n\r"; - char szString2[100]; - DWORD dwLength = strlen(szString1); - - // This call must end with an error - if(FileStream_Write(pStream, NULL, "aaa", 3)) - { - printf("Write succeeded while it should fail\n"); - return -1; - } - - for(int i = 0; i < 10; i++) - { - if(!FileStream_Read(pStream, NULL, szString2, dwLength)) - { - printf("Failed to read from the stream\n"); - return -1; - } - - szString2[dwLength] = 0; - if(strcmp(szString1, szString2)) - { - printf("Data read from file are different from data written\n"); - return -1; - } - } - FileStream_Close(pStream); - } - - // - // Test3: Open the temp stream, write some data and switch it to the original stream - // - - pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - TFileStream * pTempStream; - ULONGLONG FileSize; - - pTempStream = FileStream_CreateFile("E:\\TempStream.bin", 0); - if(pTempStream == NULL) - { - printf("Failed to create temp stream\n"); - return -1; - } - - // Copy the original stream to the temp - if(!FileStream_GetSize(pStream, FileSize)) - { - printf("Failed to get the file size\n"); - return -1; - } - - while(FileSize != 0) - { - DWORD dwBytesToRead = (DWORD)FileSize; - char Buffer[0x80]; - - if(dwBytesToRead > sizeof(Buffer)) - dwBytesToRead = sizeof(Buffer); - - if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead)) - { - printf("CopyStream: Read source file failed\n"); - return -1; - } - - if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead)) - { - printf("CopyStream: Write target file failed\n"); - return -1; - } - - FileSize -= dwBytesToRead; - } - - // Switch the streams - // Note that the pTempStream is closed by the operation - FileStream_Switch(pStream, pTempStream); - FileStream_Close(pStream); - } - - // - // Test4: Read from the stream again - // - - pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - char szString1[100] = "This is a single line\n\r"; - char szString2[100]; - DWORD dwLength = strlen(szString1); - - for(int i = 0; i < 10; i++) - { - if(!FileStream_Read(pStream, NULL, szString2, dwLength)) - { - printf("Failed to read from the stream\n"); - return -1; - } - - szString2[dwLength] = 0; - if(strcmp(szString1, szString2)) - { - printf("Data read from file are different from data written\n"); - return -1; - } - } - FileStream_Close(pStream); - } - - return 0; -} -*/ - diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 2e9366f..3bb10de 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -15,7 +15,7 @@ #include "StormLib.h" #include "StormCommon.h" -char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2012"; +char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014"; //----------------------------------------------------------------------------- // Local variables @@ -97,6 +97,8 @@ unsigned char AsciiToUpperTable_Slash[256] = #define STORM_BUFFER_SIZE 0x500 +#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0) + static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine static bool bMpqCryptographyInitialized = false; @@ -204,19 +206,17 @@ DWORD HashStringLower(const char * szFileName, DWORD dwHashType) DWORD GetHashTableSizeForFileCount(DWORD dwFileCount) { - DWORD dwPowerOfTwo; - - // Round the hash table size up to the nearest power of two - for(dwPowerOfTwo = HASH_TABLE_SIZE_MIN; dwPowerOfTwo < HASH_TABLE_SIZE_MAX; dwPowerOfTwo <<= 1) - { - if(dwPowerOfTwo >= dwFileCount) - { - return dwPowerOfTwo; - } - } + DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN; + // For zero files, there is no hash table needed + if(dwFileCount == 0) + return 0; + + // Round the hash table size up to the nearest power of two // Don't allow the hash table size go over allowed maximum - return HASH_TABLE_SIZE_MAX; + while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount) + dwPowerOfTwo <<= 1; + return dwPowerOfTwo; } //----------------------------------------------------------------------------- @@ -252,224 +252,12 @@ ULONGLONG HashStringJenkins(const char * szFileName) } //----------------------------------------------------------------------------- -// Copies the string from char * to TCHAR * and back - -#ifdef _UNICODE - -// For UNICODE builds, we need two functions -void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength) -{ - mbstowcs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength) -{ - wcstombs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -#else - -// For ANSI build, we only need one -void CopyFileName(char * szTarget, const char * szSource, size_t cchLength) -{ - memcpy(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -#endif - -//----------------------------------------------------------------------------- -// This function converts the MPQ header so it always looks like version 4 - -int ConvertMpqHeaderToFormat4( - TMPQArchive * ha, - ULONGLONG FileSize, - DWORD dwFlags) -{ - TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; - ULONGLONG ByteOffset; - DWORD dwExpectedArchiveSize; - USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion); - int nError = ERROR_SUCCESS; - - // If version 1.0 is forced, then the format version is forced to be 1.0 - // Reason: Storm.dll in Warcraft III ignores format version value - if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) - wFormatVersion = MPQ_FORMAT_VERSION_1; - - // Format-specific fixes - switch(wFormatVersion) - { - case MPQ_FORMAT_VERSION_1: - - // Make sure that the V1 header part is BSWAPPed - BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1); - - // Check for malformed MPQ header version 1.0 - if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) - { - pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; - ha->dwFlags |= MPQ_FLAG_PROTECTED; - } - - // - // The value of "dwArchiveSize" member in the MPQ header - // is ignored by Storm.dll and can contain garbage value - // ("w3xmaster" protector). - // - - dwExpectedArchiveSize = (DWORD)(FileSize - ha->MpqPos); - if(pHeader->dwArchiveSize != dwExpectedArchiveSize) - { - // Note: dwExpectedArchiveSize might be incorrect at this point. - // MPQs version 1.0 can have strong digital signature appended at the end, - // or they might just have arbitrary data there. - // In either case, we recalculate the archive size later when - // block table is loaded and positions of all files is known. - pHeader->dwArchiveSize = dwExpectedArchiveSize; - ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE; - } - - // Zero the fields in 2.0 part of the MPQ header - pHeader->HiBlockTablePos64 = 0; - pHeader->wHashTablePosHi = 0; - pHeader->wBlockTablePosHi = 0; - // No break here !!! - - case MPQ_FORMAT_VERSION_2: - case MPQ_FORMAT_VERSION_3: - - // Make sure that the V2+V3 header part is BSWAPPed - BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3); - - // In MPQ format 3.0, the entire header is optional - // and the size of the header can actually be identical - // to size of header 2.0 - if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3) - { - ULONGLONG ArchiveSize64 = pHeader->dwArchiveSize; - - // In format 2.0, the archive size is obsolete and is calculated - // as the highest offset of hash table, block table or hi-block table. - // However, we can still rely on it, if the size of the archive is under 4 GB - if((FileSize - ha->MpqPos) >> 32) - { - ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + (pHeader->dwHashTableSize * sizeof(TMPQHash)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - - ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - - // Only if we actually have a hi-block table - if(pHeader->HiBlockTablePos64) - { - ByteOffset = pHeader->HiBlockTablePos64 + (pHeader->dwBlockTableSize * sizeof(USHORT)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - } - - // We need to recalculate archive size later, - // when block table is loaded and the position of files is known - ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE; - } - - // Initialize the rest of the 3.0 header - pHeader->ArchiveSize64 = ArchiveSize64; - pHeader->HetTablePos64 = 0; - pHeader->BetTablePos64 = 0; - } - - // - // Calculate compressed size of each table. We assume the following order: - // 1) HET table - // 2) BET table - // 3) Classic hash table - // 4) Classic block table - // 5) Hi-block table - // - - // Set all sizes to zero - pHeader->HetTableSize64 = 0; - pHeader->BetTableSize64 = 0; - - // Either both HET and BET table exist or none of them does. - if(pHeader->HetTablePos64) - { - // Compressed size of the HET and BET tables - pHeader->HetTableSize64 = pHeader->BetTablePos64 - pHeader->HetTablePos64; - pHeader->BetTableSize64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) - pHeader->HetTablePos64; - } - - // Compressed size of hash and block table - if(wFormatVersion >= MPQ_FORMAT_VERSION_2) - { - // Compressed size of the hash table - pHeader->HashTableSize64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); - - // Block and hi-block table - if(pHeader->HiBlockTablePos64) - { - pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64; - } - else - { - pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - pHeader->HiBlockTableSize64 = 0; - } - } - else - { - // No known MPQ in format 1.0 has any of the tables compressed - pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - pHeader->HiBlockTableSize64 = 0; - } - - // Set the data chunk size for MD5 to zero - pHeader->dwRawChunkSize = 0; - - // Fill the MD5's - memset(pHeader->MD5_BlockTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HashTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HiBlockTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_BetTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HetTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_MpqHeader, 0, MD5_DIGEST_SIZE); - // No break here !!!! - - case MPQ_FORMAT_VERSION_4: - - // Make sure that the V4 header part is BSWAPPed - BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4); - - // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A' - // signature until the position of header MD5 at offset 0xC0 - if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader)) - nError = ERROR_FILE_CORRUPT; - break; - - default: - - // Last resort: Check if it's a War of the Immortal data file (SQP) - nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags); - break; - } - - return nError; -} - -//----------------------------------------------------------------------------- // Default flags for (attributes) and (listfile) -DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize) +DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion) { // Fixed for format 1.0 - if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) + if(wFormatVersion == MPQ_FORMAT_VERSION_1) return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY; // Size-dependent for formats 2.0-4.0 @@ -664,7 +452,7 @@ DWORD DecryptFileKey( DWORD dwMpqPos = (DWORD)MpqPos; // File key is calculated from plain name - szFileName = GetPlainFileNameA(szFileName); + szFileName = GetPlainFileName(szFileName); dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY); // Fix the key, if needed @@ -678,28 +466,30 @@ DWORD DecryptFileKey( //----------------------------------------------------------------------------- // Handle validation functions -bool IsValidMpqHandle(TMPQArchive * ha) +TMPQArchive * IsValidMpqHandle(HANDLE hMpq) { - if(ha == NULL) - return false; - if(ha->pHeader == NULL || ha->pHeader->dwID != ID_MPQ) - return false; + TMPQArchive * ha = (TMPQArchive *)hMpq; - return (bool)(ha->pHeader->dwID == ID_MPQ); + return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL; } -bool IsValidFileHandle(TMPQFile * hf) +TMPQFile * IsValidFileHandle(HANDLE hFile) { - if(hf == NULL) - return false; + TMPQFile * hf = (TMPQFile *)hFile; - if(hf->dwMagic != ID_MPQ_FILE) - return false; + // Must not be NULL + if(hf != NULL && hf->dwMagic == ID_MPQ_FILE) + { + // Local file handle? + if(hf->pStream != NULL) + return hf; - if(hf->pStream != NULL) - return true; + // Also verify the MPQ handle within the file handle + if(IsValidMpqHandle(hf->ha)) + return hf; + } - return IsValidMpqHandle(hf->ha); + return NULL; } //----------------------------------------------------------------------------- @@ -710,10 +500,11 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1 { TMPQHash * pDeletedEntry = NULL; // If a deleted entry was found in the continuous hash range TMPQHash * pFreeEntry = NULL; // If a free entry was found in the continuous hash range + DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); DWORD dwIndex; // Set the initial index - dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask); + dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask); // Search the hash table and return the found entries in the following priority: // 1) <MATCHING_ENTRY> @@ -741,7 +532,7 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1 // Move to the next hash entry. // If we reached the starting entry, it's failure. - dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + dwIndex = (dwIndex + 1) & dwHashIndexMask; if(dwIndex == dwStartIndex) break; } @@ -750,18 +541,18 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1 return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry; } - // Retrieves the first hash entry for the given file. // Every locale version of a file has its own hash entry TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) { + DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX); DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A); DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B); DWORD dwIndex; // Set the initial index - dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask); + dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask); // Search the hash table for(;;) @@ -778,7 +569,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) // Move to the next hash entry. Stop searching // if we got reached the original hash entry - dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + dwIndex = (dwIndex + 1) & dwHashIndexMask; if(dwIndex == dwStartIndex) return NULL; } @@ -786,6 +577,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash) { + DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable); DWORD dwName1 = pHash->dwName1; DWORD dwName2 = pHash->dwName2; @@ -797,7 +589,7 @@ TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * { // Move to the next hash entry. Stop searching // if we got reached the original hash entry - dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + dwIndex = (dwIndex + 1) & dwHashIndexMask; if(dwIndex == dwStartIndex) return NULL; pHash = ha->pHashTable + dwIndex; @@ -904,21 +696,23 @@ void * LoadMpqTable( TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, - DWORD dwRealSize, + DWORD dwTableSize, DWORD dwKey) { LPBYTE pbCompressed = NULL; LPBYTE pbMpqTable; LPBYTE pbToRead; + DWORD dwBytesToRead = dwCompressedSize; + DWORD dwValidLength = dwTableSize; int nError = ERROR_SUCCESS; // Allocate the MPQ table - pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwRealSize); + pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize); if(pbMpqTable != NULL) { // "interface.MPQ.part" in trial version of World of Warcraft // has block table and hash table compressed. - if(dwCompressedSize < dwRealSize) + if(dwCompressedSize < dwTableSize) { // Allocate temporary buffer for holding compressed data pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize); @@ -930,7 +724,7 @@ void * LoadMpqTable( } // If everything succeeded, read the raw table form the MPQ - if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize)) + if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead)) { // First of all, decrypt the table if(dwKey != 0) @@ -941,17 +735,21 @@ void * LoadMpqTable( } // If the table is compressed, decompress it - if(dwCompressedSize < dwRealSize) + if(dwCompressedSize < dwTableSize) { - int cbOutBuffer = (int)dwRealSize; + int cbOutBuffer = (int)dwTableSize; int cbInBuffer = (int)dwCompressedSize; if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer)) nError = GetLastError(); } - // Makre sure that the table is properly byte-swapped - BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwRealSize); + // Make sure that the table is properly byte-swapped + BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize); + + // If the table was not fully readed, fill the rest with zeros + if(dwValidLength < dwTableSize) + memset(pbMpqTable + dwValidLength, 0, (dwTableSize - dwValidLength)); } else { @@ -1563,8 +1361,8 @@ void FreeMPQFile(TMPQFile *& hf) if(hf != NULL) { // If we have patch file attached to this one, free it first - if(hf->hfPatchFile != NULL) - FreeMPQFile(hf->hfPatchFile); + if(hf->hfPatch != NULL) + FreeMPQFile(hf->hfPatch); // Then free all buffers allocated in the file structure if(hf->pPatchHeader != NULL) @@ -1623,34 +1421,6 @@ void FreeMPQArchive(TMPQArchive *& ha) } } -const char * GetPlainFileNameA(const char * szFileName) -{ - const char * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - -const TCHAR * GetPlainFileNameT(const TCHAR * szFileName) -{ - const TCHAR * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - bool IsInternalMpqFileName(const char * szFileName) { if(szFileName != NULL && szFileName[0] == '(') @@ -1824,17 +1594,6 @@ void ConvertUInt64Buffer(void * ptr, size_t length) } } -// Swaps the TMPQUserData structure -void ConvertTMPQUserData(void *userData) -{ - TMPQUserData * theData = (TMPQUserData *)userData; - - theData->dwID = SwapUInt32(theData->dwID); - theData->cbUserDataSize = SwapUInt32(theData->cbUserDataSize); - theData->dwHeaderOffs = SwapUInt32(theData->dwHeaderOffs); - theData->cbUserDataHeader = SwapUInt32(theData->cbUserDataHeader); -} - // Swaps the TMPQHeader structure void ConvertTMPQHeader(void *header, uint16_t version) { @@ -1854,11 +1613,15 @@ void ConvertTMPQHeader(void *header, uint16_t version) theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize); } - if(version == MPQ_FORMAT_VERSION_2 || version == MPQ_FORMAT_VERSION_3) + if(version == MPQ_FORMAT_VERSION_2) { theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64); theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi); theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi); + } + + if(version == MPQ_FORMAT_VERSION_3) + { theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64); theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64); theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64); diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 51d01e4..6b4eada 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -19,54 +19,11 @@ #define MAX_FLAG_INDEX 512 //----------------------------------------------------------------------------- -// Local structures - -// Structure for HET table header -typedef struct _HET_TABLE_HEADER -{ - DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes) - DWORD dwFileCount; // Number of occupied entries in the hash table - DWORD dwHashTableSize; // Size of the hash table (in bytes) - DWORD dwHashEntrySize; // Effective size of the hash entry (in bits) - DWORD dwIndexSizeTotal; // Total size of file index (in bits) - DWORD dwIndexSizeExtra; // Extra bits in the file index - DWORD dwIndexSize; // Effective size of the file index (in bits) - DWORD dwIndexTableSize; // Size of the block index subtable (in bytes) - -} HET_TABLE_HEADER, *PHET_TABLE_HEADER; - -// Structure for BET table header -typedef struct _BET_TABLE_HEADER -{ - DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes) - DWORD dwFileCount; // Number of files in the BET table - DWORD dwUnknown08; - DWORD dwTableEntrySize; // Size of one table entry (in bits) - DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record) - DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record) - DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record) - DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record) - DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record) - DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record) - DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record) - DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record) - DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record) - DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record) - DWORD dwBetHashSizeTotal; // Total size of the BET hash - DWORD dwBetHashSizeExtra; // Extra bits in the BET hash - DWORD dwBetHashSize; // Effective size of BET hash (in bits) - DWORD dwBetHashArraySize; // Size of BET hashes array, in bytes - DWORD dwFlagCount; // Number of flags in the following array - -} BET_TABLE_HEADER, *PBET_TABLE_HEADER; - -//----------------------------------------------------------------------------- // Support for calculating bit sizes static void InitFileFlagArray(LPDWORD FlagArray) { - for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) - FlagArray[dwFlagIndex] = INVALID_FLAG_VALUE; + memset(FlagArray, 0xCC, MAX_FLAG_INDEX * sizeof(DWORD)); } static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) @@ -99,6 +56,19 @@ static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) return dwBitCount; } +static int CompareFilePositions(const void * p1, const void * p2) +{ + TMPQBlock * pBlock1 = *(TMPQBlock **)p1; + TMPQBlock * pBlock2 = *(TMPQBlock **)p2; + + if(pBlock1->dwFilePos < pBlock2->dwFilePos) + return -1; + if(pBlock1->dwFilePos > pBlock2->dwFilePos) + return +1; + + return 0; +} + //----------------------------------------------------------------------------- // Support functions for BIT_ARRAY @@ -116,6 +86,7 @@ static TBitArray * CreateBitArray( if(pBitArray != NULL) { memset(pBitArray, FillValue, nSize); + pBitArray->NumberOfBytes = (NumberOfBits + 7) / 8; pBitArray->NumberOfBits = NumberOfBits; } @@ -255,6 +226,242 @@ void SetBits( } } +//----------------------------------------------------------------------------- +// Support for MPQ header + +static DWORD GetArchiveSize32(TMPQArchive * ha, TMPQBlock * pBlockTable, DWORD dwBlockTableSize) +{ + TMPQHeader * pHeader = ha->pHeader; + ULONGLONG FileSize = 0; + DWORD dwArchiveSize = pHeader->dwHeaderSize; + DWORD dwByteOffset; + DWORD dwBlockIndex; + + // Increment by hash table size + dwByteOffset = pHeader->dwHashTablePos + (pHeader->dwHashTableSize * sizeof(TMPQHash)); + if(dwByteOffset > dwArchiveSize) + dwArchiveSize = dwByteOffset; + + // Increment by block table size + dwByteOffset = pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + if(dwByteOffset > dwArchiveSize) + dwArchiveSize = dwByteOffset; + + // If any of the MPQ files is beyond the hash table/block table, set the end to the file size + for(dwBlockIndex = 0; dwBlockIndex < dwBlockTableSize; dwBlockIndex++) + { + // Only count files that exists + if(pBlockTable[dwBlockIndex].dwFlags & MPQ_FILE_EXISTS) + { + // If this file begins past the end of tables, + // assume that the hash/block table is not at the end of the archive + if(pBlockTable[dwBlockIndex].dwFilePos > dwArchiveSize) + { + FileStream_GetSize(ha->pStream, &FileSize); + dwArchiveSize = (DWORD)(FileSize - ha->MpqPos); + break; + } + } + } + + // Return what we found + return dwArchiveSize; +} + +// This function converts the MPQ header so it always looks like version 4 +static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader) +{ + ULONGLONG ArchiveSize = pHeader->dwHeaderSize; + ULONGLONG ByteOffset = pHeader->dwHeaderSize; + + // If there is HET table + if(pHeader->HetTablePos64 != 0) + { + ByteOffset = pHeader->HetTablePos64 + pHeader->HetTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is BET table + if(pHeader->BetTablePos64 != 0) + { + ByteOffset = pHeader->BetTablePos64 + pHeader->BetTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is hash table + if(pHeader->dwHashTablePos || pHeader->wHashTablePosHi) + { + ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + pHeader->HashTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is block table + if(pHeader->dwBlockTablePos || pHeader->wBlockTablePosHi) + { + ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + pHeader->BlockTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is hi-block table + if(pHeader->HiBlockTablePos64) + { + ByteOffset = pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + return ArchiveSize; +} + +int ConvertMpqHeaderToFormat4( + TMPQArchive * ha, + ULONGLONG FileSize, + DWORD dwFlags) +{ + TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; + ULONGLONG ByteOffset; + USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion); + int nError = ERROR_SUCCESS; + + // If version 1.0 is forced, then the format version is forced to be 1.0 + // Reason: Storm.dll in Warcraft III ignores format version value + if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) + wFormatVersion = MPQ_FORMAT_VERSION_1; + + // Format-specific fixes + switch(wFormatVersion) + { + case MPQ_FORMAT_VERSION_1: + + // Check for malformed MPQ header version 1.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1); + if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) + { + pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + + // + // Note: The value of "dwArchiveSize" member in the MPQ header + // is ignored by Storm.dll and can contain garbage value + // ("w3xmaster" protector). + // + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V1, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V1); + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + break; + + case MPQ_FORMAT_VERSION_2: + + // Check for malformed MPQ header version 2.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_2); + if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V2) + { + pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; + pHeader->HiBlockTablePos64 = 0; + pHeader->wHashTablePosHi = 0; + pHeader->wBlockTablePosHi = 0; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2); + if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) + pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); + if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + if(pHeader->HiBlockTablePos64) + pHeader->HiBlockTableSize64 = pHeader->dwBlockTableSize * sizeof(USHORT); + pHeader->ArchiveSize64 = GetArchiveSize64(pHeader); + break; + + case MPQ_FORMAT_VERSION_3: + + // In MPQ format 3.0, the entire header is optional + // and the size of the header can actually be identical + // to size of header 2.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3); + if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3) + { + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + pHeader->HetTablePos64 = 0; + pHeader->BetTablePos64 = 0; + } + + // + // We need to calculate the compressed size of each table. We assume the following order: + // 1) HET table + // 2) BET table + // 3) Classic hash table + // 4) Classic block table + // 5) Hi-block table + // + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3); + ByteOffset = pHeader->ArchiveSize64; + + // Size of the hi-block table + if(pHeader->HiBlockTablePos64) + { + pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64; + ByteOffset = pHeader->HiBlockTablePos64; + } + + // Size of the block table + if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) + { + pHeader->BlockTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + } + + // Size of the hash table + if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) + { + pHeader->HashTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + } + + // Size of the BET table + if(pHeader->BetTablePos64) + { + pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64; + ByteOffset = pHeader->BetTablePos64; + } + + // Size of the HET table + if(pHeader->HetTablePos64) + { + pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64; + ByteOffset = pHeader->HetTablePos64; + } + break; + + case MPQ_FORMAT_VERSION_4: + + // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A' + // signature until the position of header MD5 at offset 0xC0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4); + if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader)) + nError = ERROR_FILE_CORRUPT; + break; + + default: + + // Last resort: Check if it's a War of the Immortal data file (SQP) + nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags); + break; + } + + return nError; +} //----------------------------------------------------------------------------- // Support for hash table @@ -339,14 +546,14 @@ static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, L return NULL; } -static int ConvertMpqBlockTable( +static int BuildFileTableFromBlockTable( TMPQArchive * ha, TFileEntry * pFileTable, TMPQBlock * pBlockTable) { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; - TMPQBlock * pMpqBlock; + TMPQBlock * pBlock; TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; TMPQHash * pHash; @@ -355,7 +562,7 @@ static int ConvertMpqBlockTable( if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { pFileEntry = pFileTable + pHash->dwBlockIndex; - pMpqBlock = pBlockTable + pHash->dwBlockIndex; + pBlock = pBlockTable + pHash->dwBlockIndex; // // Yet another silly map protector: For each valid file, @@ -367,14 +574,17 @@ static int ConvertMpqBlockTable( // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid // - if(!(pMpqBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pMpqBlock->dwFlags & MPQ_FILE_EXISTS)) + if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) { - // Fill the entry - pFileEntry->ByteOffset = pMpqBlock->dwFilePos; + // ByteOffset is only valid if file size is not zero + pFileEntry->ByteOffset = pBlock->dwFilePos; + if(pFileEntry->ByteOffset == 0 && pBlock->dwCSize == 0) + pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize; + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->dwFileSize = pMpqBlock->dwFSize; - pFileEntry->dwCmpSize = pMpqBlock->dwCSize; - pFileEntry->dwFlags = pMpqBlock->dwFlags; + pFileEntry->dwFileSize = pBlock->dwFSize; + pFileEntry->dwCmpSize = pBlock->dwCSize; + pFileEntry->dwFlags = pBlock->dwFlags; pFileEntry->lcLocale = pHash->lcLocale; pFileEntry->wPlatform = pHash->wPlatform; } @@ -394,6 +604,31 @@ static int ConvertMpqBlockTable( return ERROR_SUCCESS; } +static int UpdateFileTableFromHashTable( + TMPQArchive * ha, + TFileEntry * pFileTable) +{ + TFileEntry * pFileEntry; + TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; + TMPQHash * pHash; + + for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + { + if(pHash->dwBlockIndex < ha->dwFileTableSize) + { + pFileEntry = pFileTable + pHash->dwBlockIndex; + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); + pFileEntry->lcLocale = pHash->lcLocale; + pFileEntry->wPlatform = pHash->wPlatform; + } + } + } + + return ERROR_SUCCESS; +} + static TMPQHash * TranslateHashTable( TMPQArchive * ha, ULONGLONG * pcbTableSize) @@ -419,7 +654,8 @@ static TMPQHash * TranslateHashTable( return pHashTable; } -static TMPQBlock * TranslateBlockTable( +// Also used in SFileGetFileInfo +TMPQBlock * TranslateBlockTable( TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable) @@ -427,8 +663,8 @@ static TMPQBlock * TranslateBlockTable( TFileEntry * pFileEntry = ha->pFileTable; TMPQBlock * pBlockTable; TMPQBlock * pBlock; + size_t NeedHiBlockTable = 0; size_t BlockTableSize; - bool bNeedHiBlockTable = false; // Allocate copy of the hash table pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, ha->dwFileTableSize); @@ -438,7 +674,7 @@ static TMPQBlock * TranslateBlockTable( BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize; for(DWORD i = 0; i < ha->dwFileTableSize; i++) { - bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false; + NeedHiBlockTable |= (pFileEntry->ByteOffset >> 32); pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset; pBlock->dwFSize = pFileEntry->dwFileSize; pBlock->dwCSize = pFileEntry->dwCmpSize; @@ -453,7 +689,7 @@ static TMPQBlock * TranslateBlockTable( *pcbTableSize = (ULONGLONG)BlockTableSize; if(pbNeedHiBlockTable != NULL) - *pbNeedHiBlockTable = bNeedHiBlockTable; + *pbNeedHiBlockTable = NeedHiBlockTable ? true : false; } return pBlockTable; @@ -488,21 +724,21 @@ static USHORT * TranslateHiBlockTable( //----------------------------------------------------------------------------- // General EXT table functions -TMPQExtTable * LoadExtTable( +TMPQExtHeader * LoadExtTable( TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey) { - TMPQExtTable * pCompressed = NULL; // Compressed table - TMPQExtTable * pExtTable = NULL; // Uncompressed table + TMPQExtHeader * pCompressed = NULL; // Compressed table + TMPQExtHeader * pExtTable = NULL; // Uncompressed table // Do nothing if the size is zero if(ByteOffset != 0 && Size != 0) { // Allocate size for the compressed table - pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, Size); + pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, Size); if(pExtTable != NULL) { // Load the table from the MPQ @@ -514,7 +750,7 @@ TMPQExtTable * LoadExtTable( } // Swap the ext table header - BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtTable)); + BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtHeader)); if(pExtTable->dwSignature != dwSignature) { STORM_FREE(pExtTable); @@ -523,14 +759,14 @@ TMPQExtTable * LoadExtTable( // Decrypt the block BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); - DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtTable)), dwKey); + DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtHeader)), dwKey); BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); // If the table is compressed, decompress it - if((pExtTable->dwDataSize + sizeof(TMPQExtTable)) > Size) + if((pExtTable->dwDataSize + sizeof(TMPQExtHeader)) > Size) { pCompressed = pExtTable; - pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + pCompressed->dwDataSize); + pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + pCompressed->dwDataSize); if(pExtTable != NULL) { int cbOutBuffer = (int)pCompressed->dwDataSize; @@ -557,12 +793,6 @@ TMPQExtTable * LoadExtTable( return pExtTable; } -// Used in MPQ Editor -void FreeMpqBuffer(void * pvBuffer) -{ - STORM_FREE(pvBuffer); -} - static int SaveMpqTable( TMPQArchive * ha, void * pMpqTable, @@ -630,7 +860,7 @@ static int SaveMpqTable( static int SaveExtTable( TMPQArchive * ha, - TMPQExtTable * pExtTable, + TMPQExtHeader * pExtTable, ULONGLONG ByteOffset, DWORD dwTableSize, unsigned char * md5, @@ -639,7 +869,7 @@ static int SaveExtTable( LPDWORD pcbTotalSize) { ULONGLONG FileOffset; - TMPQExtTable * pCompressed = NULL; + TMPQExtHeader * pCompressed = NULL; DWORD cbTotalSize = 0; int nError = ERROR_SUCCESS; @@ -650,7 +880,7 @@ static int SaveExtTable( int cbInBuffer = (int)dwTableSize; // Allocate extra space for compressed table - pCompressed = (TMPQExtTable *)STORM_ALLOC(BYTE, dwTableSize); + pCompressed = (TMPQExtHeader *)STORM_ALLOC(BYTE, dwTableSize); if(pCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; @@ -676,7 +906,7 @@ static int SaveExtTable( if(dwKey != 0) { BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); - EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtTable)), dwKey); + EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtHeader)), dwKey); BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); } @@ -719,125 +949,170 @@ static int SaveExtTable( static void CreateHetHeader( TMPQHetTable * pHetTable, - PHET_TABLE_HEADER pHetHeader) + TMPQHetHeader * pHetHeader) { - // Fill the BET header - pHetHeader->dwFileCount = pHetTable->dwFileCount; - pHetHeader->dwHashTableSize = pHetTable->dwHashTableSize; - pHetHeader->dwHashEntrySize = pHetTable->dwHashBitSize; - pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwHashTableSize); - pHetHeader->dwIndexSizeExtra = 0; - pHetHeader->dwIndexSize = pHetHeader->dwIndexSizeTotal; - pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwHashTableSize) + 7) / 8; + // Fill the common header + pHetHeader->ExtHdr.dwSignature = HET_TABLE_SIGNATURE; + pHetHeader->ExtHdr.dwVersion = 1; + pHetHeader->ExtHdr.dwDataSize = 0; + + // Fill the HET header + pHetHeader->dwEntryCount = pHetTable->dwEntryCount; + pHetHeader->dwTotalCount = pHetTable->dwTotalCount; + pHetHeader->dwNameHashBitSize = pHetTable->dwNameHashBitSize; + pHetHeader->dwIndexSizeTotal = pHetTable->dwIndexSizeTotal; + pHetHeader->dwIndexSizeExtra = pHetTable->dwIndexSizeExtra; + pHetHeader->dwIndexSize = pHetTable->dwIndexSize; + pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwTotalCount) + 7) / 8; // Calculate the total size needed for holding HET table - pHetHeader->dwTableSize = sizeof(HET_TABLE_HEADER) + - pHetHeader->dwHashTableSize + + pHetHeader->ExtHdr.dwDataSize = + pHetHeader->dwTableSize = sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize; } -TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty) +TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData) { TMPQHetTable * pHetTable; pHetTable = STORM_ALLOC(TMPQHetTable, 1); if(pHetTable != NULL) { - pHetTable->dwIndexSizeTotal = 0; - pHetTable->dwIndexSizeExtra = 0; - pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; - pHetTable->dwHashBitSize = dwHashBitSize; + // Zero the HET table + memset(pHetTable, 0, sizeof(TMPQHetTable)); - // If the hash table size is not entered, calculate an optimal - // hash table size as 4/3 of the current file count. - if(dwHashTableSize == 0) - { - dwHashTableSize = (dwFileCount * 4 / 3); - assert(dwFileCount != 0); - } + // Hash sizes less than 0x40 bits are not tested + assert(dwNameHashBitSize == 0x40); - // Store the hash table size and file count - pHetTable->dwHashTableSize = dwHashTableSize; - pHetTable->dwFileCount = dwFileCount; + // Calculate masks + pHetTable->AndMask64 = ((dwNameHashBitSize != 0x40) ? ((ULONGLONG)1 << dwNameHashBitSize) : 0) - 1; + pHetTable->OrMask64 = (ULONGLONG)1 << (dwNameHashBitSize - 1); - // Size of one index is calculated from hash table size - pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwHashTableSize); - pHetTable->dwIndexSizeExtra = 0; - pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; + // Store the HET table parameters + pHetTable->dwEntryCount = dwFileCount; + pHetTable->dwTotalCount = (dwFileCount * 4) / 3; + pHetTable->dwNameHashBitSize = dwNameHashBitSize; + pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwFileCount); + pHetTable->dwIndexSizeExtra = 0; + pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; - // Allocate hash table - pHetTable->pHetHashes = STORM_ALLOC(BYTE, pHetTable->dwHashTableSize); - if(pHetTable->pHetHashes != NULL) + // Allocate array of hashes + pHetTable->pNameHashes = STORM_ALLOC(BYTE, pHetTable->dwTotalCount); + if(pHetTable->pNameHashes != NULL) { - // Make sure that the HET hashes are initialized - memset(pHetTable->pHetHashes, 0, pHetTable->dwHashTableSize); + // Make sure the data are initialized + memset(pHetTable->pNameHashes, 0, pHetTable->dwTotalCount); - // If we shall create empty HET table, we have to allocate empty block index table as well - if(bCreateEmpty) - pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwHashTableSize * pHetTable->dwIndexSizeTotal, 0xFF); + // Allocate the bit array for file indexes + pHetTable->pBetIndexes = CreateBitArray(pHetTable->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); - // Calculate masks - pHetTable->AndMask64 = 0; - if(dwHashBitSize != 0x40) - pHetTable->AndMask64 = (ULONGLONG)1 << dwHashBitSize; - pHetTable->AndMask64--; + // Copy the file indexes + memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + pHetTable->dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes); + } + + // Return the result HET table + return pHetTable; + } - pHetTable->OrMask64 = (ULONGLONG)1 << (dwHashBitSize - 1); + // Free the name hashes + STORM_FREE(pHetTable->pNameHashes); } - else + + STORM_FREE(pHetTable); + } + + // Failed + return NULL; +} + +static int InsertHetEntry(TMPQHetTable * pHetTable, ULONGLONG FileNameHash, DWORD dwFileIndex) +{ + DWORD StartIndex; + DWORD Index; + BYTE NameHash1; + + // Get the start index and the high 8 bits of the name hash + StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount); + NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8)); + + // Find a place where to put it + for(;;) + { + // Did we find a free HET entry? + if(pHetTable->pNameHashes[Index] == HET_ENTRY_FREE) { - STORM_FREE(pHetTable); - pHetTable = NULL; + // Set the entry in the name hash table + pHetTable->pNameHashes[Index] = NameHash1; + + // Set the entry in the file index table + SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, + pHetTable->dwIndexSize, + &dwFileIndex, + 4); + return ERROR_SUCCESS; } + + // Move to the next entry in the HET table + // If we came to the start index again, we are done + Index = (Index + 1) % pHetTable->dwEntryCount; + if(Index == StartIndex) + break; } - return pHetTable; + // No space in the HET table. Should never happen, + // because the HET table is created according to the number of files + assert(false); + return ERROR_DISK_FULL; } -static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable) +static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader) { - HET_TABLE_HEADER HetHeader; TMPQHetTable * pHetTable = NULL; - LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1); + LPBYTE pbSrcData = (LPBYTE)(pHetHeader + 1); // Sanity check - assert(pExtTable->dwSignature == HET_TABLE_SIGNATURE); - assert(pExtTable->dwVersion == 1); + assert(pHetHeader->ExtHdr.dwSignature == HET_TABLE_SIGNATURE); + assert(pHetHeader->ExtHdr.dwVersion == 1); // Verify size of the HET table - if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(HET_TABLE_HEADER)) + if(pHetHeader->ExtHdr.dwDataSize >= sizeof(TMPQHetHeader)) { - // Copy the table header in order to have it aligned and swapped - memcpy(&HetHeader, pbSrcData, sizeof(HET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(&HetHeader, sizeof(HET_TABLE_HEADER)); - pbSrcData += sizeof(HET_TABLE_HEADER); - // Verify the size of the table in the header - if(HetHeader.dwTableSize == pExtTable->dwDataSize) + if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize) { + // The size of the HET table must be sum of header, hash and index table size + 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); + + // The size of one index is predictable as well + assert(GetNecessaryBitCount(pHetHeader->dwEntryCount) == pHetHeader->dwIndexSizeTotal); + // The size of index table (in entries) is expected // to be the same like the hash table size (in bytes) - assert(((HetHeader.dwIndexTableSize * 8) / HetHeader.dwIndexSize) == HetHeader.dwHashTableSize); - + assert(((pHetHeader->dwTotalCount * pHetHeader->dwIndexSizeTotal) + 7) / 8 == pHetHeader->dwIndexTableSize); + // Create translated table - pHetTable = CreateHetTable(HetHeader.dwHashTableSize, HetHeader.dwFileCount, HetHeader.dwHashEntrySize, false); + pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwNameHashBitSize, pbSrcData); if(pHetTable != NULL) { - // Copy the hash table size, index size and extra bits from the HET header - pHetTable->dwHashTableSize = HetHeader.dwHashTableSize; - pHetTable->dwIndexSizeTotal = HetHeader.dwIndexSizeTotal; - pHetTable->dwIndexSizeExtra = HetHeader.dwIndexSizeExtra; - - // Fill the hash table - if(pHetTable->pHetHashes != NULL) - memcpy(pHetTable->pHetHashes, pbSrcData, pHetTable->dwHashTableSize); - pbSrcData += pHetTable->dwHashTableSize; - - // Copy the block index table - pHetTable->pBetIndexes = CreateBitArray(HetHeader.dwIndexTableSize * 8, 0xFF); - if(pHetTable->pBetIndexes != NULL) - memcpy(pHetTable->pBetIndexes->Elements, pbSrcData, HetHeader.dwIndexTableSize); - pbSrcData += HetHeader.dwIndexTableSize; + // Now the sizes in the hash table should be already set + assert(pHetTable->dwEntryCount == pHetHeader->dwEntryCount); + assert(pHetTable->dwTotalCount == pHetHeader->dwTotalCount); + assert(pHetTable->dwIndexSizeTotal == pHetHeader->dwIndexSizeTotal); + + // Copy the missing variables + pHetTable->dwIndexSizeExtra = pHetHeader->dwIndexSizeExtra; + pHetTable->dwIndexSize = pHetHeader->dwIndexSize; } } } @@ -845,90 +1120,78 @@ static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable) return pHetTable; } -static TMPQExtTable * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable) +static TMPQExtHeader * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable) { - TMPQExtTable * pExtTable = NULL; - HET_TABLE_HEADER HetHeader; + TMPQHetHeader * pHetHeader = NULL; + TMPQHetHeader HetHeader; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; - size_t HetTableSize; // Prepare header of the HET table CreateHetHeader(pHetTable, &HetHeader); - // Calculate the total size needed for holding the encrypted HET table - HetTableSize = HetHeader.dwTableSize; - // Allocate space for the linear table - pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + HetTableSize); + pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + HetHeader.dwTableSize); if(pbLinearTable != NULL) { - // Create the common ext table header - pExtTable = (TMPQExtTable *)pbLinearTable; - pExtTable->dwSignature = HET_TABLE_SIGNATURE; - pExtTable->dwVersion = 1; - pExtTable->dwDataSize = (DWORD)HetTableSize; - pbTrgData = (LPBYTE)(pExtTable + 1); + // Copy the table header + pHetHeader = (TMPQHetHeader *)pbLinearTable; + memcpy(pHetHeader, &HetHeader, sizeof(TMPQHetHeader)); + pbTrgData = (LPBYTE)(pHetHeader + 1); - // Copy the HET table header - memcpy(pbTrgData, &HetHeader, sizeof(HET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(HET_TABLE_HEADER)); - pbTrgData += sizeof(HET_TABLE_HEADER); - - // Copy the array of HET hashes - memcpy(pbTrgData, pHetTable->pHetHashes, pHetTable->dwHashTableSize); - pbTrgData += pHetTable->dwHashTableSize; + // Copy the array of name hashes + memcpy(pbTrgData, pHetTable->pNameHashes, pHetTable->dwTotalCount); + pbTrgData += pHetTable->dwTotalCount; // Copy the bit array of BET indexes memcpy(pbTrgData, pHetTable->pBetIndexes->Elements, HetHeader.dwIndexTableSize); - // Calculate the total size of the table, including the TMPQExtTable + // Calculate the total size of the table, including the TMPQExtHeader if(pcbHetTable != NULL) { - *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtTable) + HetTableSize); + *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + HetHeader.dwTableSize); } } - return pExtTable; + return &pHetHeader->ExtHdr; } DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) { TMPQHetTable * pHetTable = ha->pHetTable; ULONGLONG FileNameHash; - ULONGLONG AndMask64; - ULONGLONG OrMask64; - ULONGLONG BetHash; DWORD StartIndex; DWORD Index; - BYTE HetHash; // Upper 8 bits of the masked file name hash + BYTE NameHash1; // Upper 8 bits of the masked file name hash + + // If there are no entries in the HET table, do nothing + if(pHetTable->dwEntryCount == 0) + return HASH_ENTRY_FREE; // Do nothing if the MPQ has no HET table assert(ha->pHetTable != NULL); // Calculate 64-bit hash of the file name - AndMask64 = pHetTable->AndMask64; - OrMask64 = pHetTable->OrMask64; - FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64; + FileNameHash = (HashStringJenkins(szFileName) & pHetTable->AndMask64) | pHetTable->OrMask64; // Split the file name hash into two parts: - // Part 1: The highest 8 bits of the name hash - // Part 2: The rest of the name hash (without the highest 8 bits) - HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8)); - BetHash = FileNameHash & (AndMask64 >> 0x08); + // NameHash1: The highest 8 bits of the name hash + // NameHash2: File name hash limited to hash size + // Note: Our file table contains full name hash, no need to cut the high 8 bits before comparison + NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8)); // Calculate the starting index to the hash table - StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize); + StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount); // Go through HET table until we find a terminator - while(pHetTable->pHetHashes[Index] != HET_ENTRY_FREE) + while(pHetTable->pNameHashes[Index] != HET_ENTRY_FREE) { // Did we find a match ? - if(pHetTable->pHetHashes[Index] == HetHash) + if(pHetTable->pNameHashes[Index] == NameHash1) { DWORD dwFileIndex = 0; - // Get the index of the BetHash + // Get the file index GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, pHetTable->dwIndexSize, &dwFileIndex, @@ -940,14 +1203,14 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) // assert(dwFileIndex <= ha->dwFileTableSize); // - // Verify the BetHash against the entry in the table of BET hashes - if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash) + // Verify the FileNameHash against the entry in the table of name hashes + if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash) return dwFileIndex; } - // Move to the next entry in the primary search table + // Move to the next entry in the HET table // If we came to the start index again, we are done - Index = (Index + 1) % pHetTable->dwHashTableSize; + Index = (Index + 1) % pHetTable->dwEntryCount; if(Index == StartIndex) break; } @@ -956,103 +1219,12 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) return HASH_ENTRY_FREE; } -DWORD AllocateHetEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) -{ - TMPQHetTable * pHetTable = ha->pHetTable; - ULONGLONG FileNameHash; - ULONGLONG AndMask64; - ULONGLONG OrMask64; - ULONGLONG BetHash; - DWORD FileCountIncrement = 0; - DWORD FreeHetIndex = HASH_ENTRY_FREE; - DWORD dwFileIndex; - DWORD StartIndex; - DWORD Index; - BYTE HetHash; // Upper 8 bits of the masked file name hash - - // Do nothing if the MPQ has no HET table - assert(ha->pHetTable != NULL); - - // Calculate 64-bit hash of the file name - AndMask64 = pHetTable->AndMask64; - OrMask64 = pHetTable->OrMask64; - FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64; - - // Calculate the starting index to the hash table - StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize); - - // Split the file name hash into two parts: - // Part 1: The highest 8 bits of the name hash - // Part 2: The rest of the name hash (without the highest 8 bits) - HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8)); - BetHash = FileNameHash & (AndMask64 >> 0x08); - - // Go through HET table until we find a terminator - for(;;) - { - // Did we find a match ? - if(pHetTable->pHetHashes[Index] == HetHash) - { - DWORD dwFileIndex = 0; - - // Get the index of the BetHash - GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, - pHetTable->dwIndexSize, - &dwFileIndex, - 4); - // - // TODO: This condition only happens when we are opening a MPQ - // where some files were deleted by StormLib. Perhaps - // we should not allow shrinking of the file table in MPQs v 4.0? - // assert(dwFileIndex <= ha->dwFileTableSize); - // - - // Verify the BetHash against the entry in the table of BET hashes - if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash) - { - FreeHetIndex = Index; - break; - } - } - - // Check for entries that might have been deleted - if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED || pHetTable->pHetHashes[Index] == HET_ENTRY_FREE) - { - FileCountIncrement++; - FreeHetIndex = Index; - break; - } - - // Move to the next entry in the primary search table - // If we came to the start index again, we are done - Index = (Index + 1) % pHetTable->dwHashTableSize; - if(Index == StartIndex) - return HASH_ENTRY_FREE; - } - - // Fill the HET table entry - dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable); - pHetTable->pHetHashes[FreeHetIndex] = HetHash; - pHetTable->dwFileCount += FileCountIncrement; - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * FreeHetIndex, - pHetTable->dwIndexSize, - &dwFileIndex, - 4); - - // Fill the file entry - pFileEntry->BetHash = BetHash; - pFileEntry->dwHetIndex = FreeHetIndex; - return FreeHetIndex; -} - void FreeHetTable(TMPQHetTable * pHetTable) { if(pHetTable != NULL) { - if(pHetTable->pHetHashes != NULL) - STORM_FREE(pHetTable->pHetHashes); + if(pHetTable->pNameHashes != NULL) + STORM_FREE(pHetTable->pNameHashes); if(pHetTable->pBetIndexes != NULL) STORM_FREE(pHetTable->pBetIndexes); @@ -1065,7 +1237,7 @@ void FreeHetTable(TMPQHetTable * pHetTable) static void CreateBetHeader( TMPQArchive * ha, - PBET_TABLE_HEADER pBetHeader) + TMPQBetHeader * pBetHeader) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; @@ -1079,9 +1251,18 @@ static void CreateBetHeader( // Initialize array of flag combinations InitFileFlagArray(FlagArray); + // Fill the common header + pBetHeader->ExtHdr.dwSignature = BET_TABLE_SIGNATURE; + pBetHeader->ExtHdr.dwVersion = 1; + pBetHeader->ExtHdr.dwDataSize = 0; + // Get the maximum values for the BET table for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { + // + // Note: Deleted files must be counted as well + // + // Highest file position in the MPQ if(pFileEntry->ByteOffset > MaxByteOffset) MaxByteOffset = pFileEntry->ByteOffset; @@ -1124,24 +1305,25 @@ static void CreateBetHeader( pBetHeader->dwBitCount_Unknown; // Save the file count and flag count - pBetHeader->dwFileCount = ha->dwFileTableSize; + pBetHeader->dwEntryCount = ha->dwFileTableSize; pBetHeader->dwFlagCount = dwMaxFlagIndex + 1; pBetHeader->dwUnknown08 = 0x10; // Save the total size of the BET hash - pBetHeader->dwBetHashSizeTotal = ha->pHetTable->dwHashBitSize - 0x08; - pBetHeader->dwBetHashSizeExtra = 0; - pBetHeader->dwBetHashSize = pBetHeader->dwBetHashSizeTotal; - pBetHeader->dwBetHashArraySize = ((pBetHeader->dwBetHashSizeTotal * pBetHeader->dwFileCount) + 7) / 8; + pBetHeader->dwBitTotal_NameHash2 = ha->pHetTable->dwNameHashBitSize - 0x08; + pBetHeader->dwBitExtra_NameHash2 = 0; + pBetHeader->dwBitCount_NameHash2 = pBetHeader->dwBitTotal_NameHash2; + pBetHeader->dwNameHashArraySize = ((pBetHeader->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount) + 7) / 8; // Save the total table size - pBetHeader->dwTableSize = sizeof(BET_TABLE_HEADER) + + pBetHeader->ExtHdr.dwDataSize = + pBetHeader->dwTableSize = sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader) + pBetHeader->dwFlagCount * sizeof(DWORD) + - ((pBetHeader->dwTableEntrySize * pBetHeader->dwFileCount) + 7) / 8 + - pBetHeader->dwBetHashArraySize; + ((pBetHeader->dwTableEntrySize * pBetHeader->dwEntryCount) + 7) / 8 + + pBetHeader->dwNameHashArraySize; } -TMPQBetTable * CreateBetTable(DWORD dwFileCount) +TMPQBetTable * CreateBetTable(DWORD dwEntryCount) { TMPQBetTable * pBetTable; @@ -1150,7 +1332,7 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount) if(pBetTable != NULL) { memset(pBetTable, 0, sizeof(TMPQBetTable)); - pBetTable->dwFileCount = dwFileCount; + pBetTable->dwEntryCount = dwEntryCount; } return pBetTable; @@ -1158,90 +1340,87 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount) static TMPQBetTable * TranslateBetTable( TMPQArchive * ha, - TMPQExtTable * pExtTable) + TMPQBetHeader * pBetHeader) { - BET_TABLE_HEADER BetHeader; TMPQBetTable * pBetTable = NULL; - LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1); + LPBYTE pbSrcData = (LPBYTE)(pBetHeader + 1); DWORD LengthInBytes; // Sanity check - assert(pExtTable->dwSignature == BET_TABLE_SIGNATURE); - assert(pExtTable->dwVersion == 1); + assert(pBetHeader->ExtHdr.dwSignature == BET_TABLE_SIGNATURE); + assert(pBetHeader->ExtHdr.dwVersion == 1); assert(ha->pHetTable != NULL); ha = ha; // Verify size of the HET table - if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(BET_TABLE_HEADER)) + if(pBetHeader->ExtHdr.dwDataSize >= sizeof(TMPQBetHeader)) { - // Copy the table header in order to have it aligned and swapped - memcpy(&BetHeader, pbSrcData, sizeof(BET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(&BetHeader, sizeof(BET_TABLE_HEADER)); - pbSrcData += sizeof(BET_TABLE_HEADER); - - // Some MPQs affected by a bug in StormLib have pBetTable->dwFileCount - // greater than ha->dwMaxFileCount - if(BetHeader.dwFileCount > ha->dwMaxFileCount) - return NULL; - // Verify the size of the table in the header - if(BetHeader.dwTableSize == pExtTable->dwDataSize) + if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize) { + // The number of entries in the BET table must be the same like number of entries in the block table + assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize); + assert(pBetHeader->dwEntryCount <= ha->dwMaxFileCount); + + // The number of entries in the BET table must be the same like number of entries in the HET table + // Note that if it's not, it is not a problem + //assert(pBetHeader->dwEntryCount == ha->pHetTable->dwEntryCount); + // Create translated table - pBetTable = CreateBetTable(BetHeader.dwFileCount); + pBetTable = CreateBetTable(pBetHeader->dwEntryCount); if(pBetTable != NULL) { // Copy the variables from the header to the BetTable - pBetTable->dwTableEntrySize = BetHeader.dwTableEntrySize; - pBetTable->dwBitIndex_FilePos = BetHeader.dwBitIndex_FilePos; - pBetTable->dwBitIndex_FileSize = BetHeader.dwBitIndex_FileSize; - pBetTable->dwBitIndex_CmpSize = BetHeader.dwBitIndex_CmpSize; - pBetTable->dwBitIndex_FlagIndex = BetHeader.dwBitIndex_FlagIndex; - pBetTable->dwBitIndex_Unknown = BetHeader.dwBitIndex_Unknown; - pBetTable->dwBitCount_FilePos = BetHeader.dwBitCount_FilePos; - pBetTable->dwBitCount_FileSize = BetHeader.dwBitCount_FileSize; - pBetTable->dwBitCount_CmpSize = BetHeader.dwBitCount_CmpSize; - pBetTable->dwBitCount_FlagIndex = BetHeader.dwBitCount_FlagIndex; - pBetTable->dwBitCount_Unknown = BetHeader.dwBitCount_Unknown; - - // Since we don't know what the "unknown" is, we'll assert when it's nonzero + pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize; + pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos; + pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize; + pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize; + pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex; + pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown; + pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos; + pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize; + pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize; + pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex; + pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown; + + // Since we don't know what the "unknown" is, we'll assert when it's zero assert(pBetTable->dwBitCount_Unknown == 0); // Allocate array for flags - if(BetHeader.dwFlagCount != 0) + if(pBetHeader->dwFlagCount != 0) { // Allocate array for file flags and load it - pBetTable->pFileFlags = STORM_ALLOC(DWORD, BetHeader.dwFlagCount); + pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount); if(pBetTable->pFileFlags != NULL) { - LengthInBytes = BetHeader.dwFlagCount * sizeof(DWORD); + LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD); memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes); BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes); pbSrcData += LengthInBytes; } // Save the number of flags - pBetTable->dwFlagCount = BetHeader.dwFlagCount; + pBetTable->dwFlagCount = pBetHeader->dwFlagCount; } // Load the bit-based file table - pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * BetHeader.dwFileCount, 0); + pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0); LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8; if(pBetTable->pFileTable != NULL) memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes); pbSrcData += LengthInBytes; // Fill the sizes of BET hash - pBetTable->dwBetHashSizeTotal = BetHeader.dwBetHashSizeTotal; - pBetTable->dwBetHashSizeExtra = BetHeader.dwBetHashSizeExtra; - pBetTable->dwBetHashSize = BetHeader.dwBetHashSize; + pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2; + pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2; + pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2; // Create and load the array of BET hashes - pBetTable->pBetHashes = CreateBitArray(pBetTable->dwBetHashSizeTotal * BetHeader.dwFileCount, 0); - LengthInBytes = (pBetTable->pBetHashes->NumberOfBits + 7) / 8; - if(pBetTable->pBetHashes != NULL) - memcpy(pBetTable->pBetHashes->Elements, pbSrcData, LengthInBytes); - pbSrcData += BetHeader.dwBetHashArraySize; + pBetTable->pNameHashes = CreateBitArray(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0); + LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8; + if(pBetTable->pNameHashes != NULL) + memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes); + pbSrcData += pBetHeader->dwNameHashArraySize; // Dump both tables // DumpHetAndBetTable(ha->pHetTable, pBetTable); @@ -1252,63 +1431,47 @@ static TMPQBetTable * TranslateBetTable( return pBetTable; } -TMPQExtTable * TranslateBetTable( +TMPQExtHeader * TranslateBetTable( TMPQArchive * ha, ULONGLONG * pcbBetTable) { - TMPQExtTable * pExtTable = NULL; - BET_TABLE_HEADER BetHeader; + TMPQBetHeader * pBetHeader = NULL; + TMPQBetHeader BetHeader; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pFileEntry; TBitArray * pBitArray = NULL; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; - size_t BetTableSize; DWORD LengthInBytes; DWORD FlagArray[MAX_FLAG_INDEX]; - DWORD i; // Calculate the bit sizes of various entries InitFileFlagArray(FlagArray); CreateBetHeader(ha, &BetHeader); - // Calculate the size of the BET table - BetTableSize = sizeof(BET_TABLE_HEADER) + - BetHeader.dwFlagCount * sizeof(DWORD) + - ((BetHeader.dwTableEntrySize * BetHeader.dwFileCount) + 7) / 8 + - BetHeader.dwBetHashArraySize; - // Allocate space - pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + BetTableSize); + pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + BetHeader.dwTableSize); if(pbLinearTable != NULL) { - // Create the common ext table header - pExtTable = (TMPQExtTable *)pbLinearTable; - pExtTable->dwSignature = BET_TABLE_SIGNATURE; - pExtTable->dwVersion = 1; - pExtTable->dwDataSize = (DWORD)BetTableSize; - pbTrgData = (LPBYTE)(pExtTable + 1); - - // Copy the BET table header - memcpy(pbTrgData, &BetHeader, sizeof(BET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(BET_TABLE_HEADER)); - pbTrgData += sizeof(BET_TABLE_HEADER); + // Copy the BET header to the linear buffer + pBetHeader = (TMPQBetHeader *)pbLinearTable; + memcpy(pBetHeader, &BetHeader, sizeof(TMPQBetHeader)); + pbTrgData = (LPBYTE)(pBetHeader + 1); // Save the bit-based block table - pBitArray = CreateBitArray(BetHeader.dwFileCount * BetHeader.dwTableEntrySize, 0); + pBitArray = CreateBitArray(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0); if(pBitArray != NULL) { - TFileEntry * pFileEntry = ha->pFileTable; DWORD dwFlagIndex = 0; DWORD nBitOffset = 0; - // Construct the array of flag values and bit-based file table - for(i = 0; i < BetHeader.dwFileCount; i++, pFileEntry++) + // Construct the bit-based file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // - // Note: Blizzard MPQs contain valid values even for non-existant files - // (FilePos, FileSize, CmpSize and FlagIndex) - // Note: If flags is zero, it must be in the flag table too !!! + // Note: Missing files must be included as well // - + // Save the byte offset SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FilePos, BetHeader.dwBitCount_FilePos, @@ -1349,33 +1512,22 @@ TMPQExtTable * TranslateBetTable( STORM_FREE(pBitArray); } - // Create bit array for BET hashes - pBitArray = CreateBitArray(BetHeader.dwBetHashSizeTotal * BetHeader.dwFileCount, 0); + // Create bit array for name hashes + pBitArray = CreateBitArray(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0); if(pBitArray != NULL) { - TFileEntry * pFileEntry = ha->pFileTable; - ULONGLONG AndMask64 = ha->pHetTable->AndMask64; - ULONGLONG OrMask64 = ha->pHetTable->OrMask64; + DWORD dwFileIndex = 0; - for(i = 0; i < BetHeader.dwFileCount; i++) + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - ULONGLONG FileNameHash = 0; - - // Calculate 64-bit hash of the file name - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) - { - FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64; - FileNameHash = FileNameHash & (AndMask64 >> 0x08); - } - // Insert the name hash to the bit array - SetBits(pBitArray, BetHeader.dwBetHashSizeTotal * i, - BetHeader.dwBetHashSize, - &FileNameHash, + SetBits(pBitArray, BetHeader.dwBitTotal_NameHash2 * dwFileIndex, + BetHeader.dwBitCount_NameHash2, + &pFileEntry->FileNameHash, 8); - - // Move to the next file entry - pFileEntry++; + + assert(dwFileIndex < BetHeader.dwEntryCount); + dwFileIndex++; } // Write the array of BET hashes @@ -1390,11 +1542,11 @@ TMPQExtTable * TranslateBetTable( // Write the size of the BET table in the MPQ if(pcbBetTable != NULL) { - *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtTable) + BetTableSize); + *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + BetHeader.dwTableSize); } } - return pExtTable; + return &pBetHeader->ExtHdr; } void FreeBetTable(TMPQBetTable * pBetTable) @@ -1405,8 +1557,8 @@ void FreeBetTable(TMPQBetTable * pBetTable) STORM_FREE(pBetTable->pFileTable); if(pBetTable->pFileFlags != NULL) STORM_FREE(pBetTable->pFileFlags); - if(pBetTable->pBetHashes != NULL) - STORM_FREE(pBetTable->pBetHashes); + if(pBetTable->pNameHashes != NULL) + STORM_FREE(pBetTable->pNameHashes); STORM_FREE(pBetTable); } @@ -1498,7 +1650,7 @@ TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex) return NULL; } -void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) +void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName) { // Sanity check assert(pFileEntry != NULL); @@ -1518,129 +1670,104 @@ void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) if(pFileEntry->szFileName != NULL) strcpy(pFileEntry->szFileName, szFileName); } -} + // We also need to create the file name hash + if(ha->pHetTable != NULL) + { + ULONGLONG AndMask64 = ha->pHetTable->AndMask64; + ULONGLONG OrMask64 = ha->pHetTable->OrMask64; + + pFileEntry->FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64; + } +} -// Finds a free file entry. Does NOT increment table size. -TFileEntry * FindFreeFileEntry(TMPQArchive * ha) +TFileEntry * FindDeletedFileEntry(TMPQArchive * ha) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFreeEntry = NULL; TFileEntry * pFileEntry; - // Try to find a free entry + // Go through the entire file table and try to find a deleted entry for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - // If that entry is free, we reuse it - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - pFreeEntry = pFileEntry; - break; - } + // Return the entry that is within the current file table (i.e. a deleted file) + if(!(pFileEntry->dwFlags & MPQ_FILE_EXISTS)) + return pFileEntry; // // Note: Files with "delete marker" are not deleted. - // Don't consider them free entries + // Don't treat them as available // } - // Do we have a deleted entry? - if(pFreeEntry != NULL) - { - ClearFileEntry(ha, pFreeEntry); - return pFreeEntry; - } - - // If no file entry within the existing file table is free, - // we try the reserve space after current file table - if(ha->dwFileTableSize < ha->dwMaxFileCount) - return ha->pFileTable + ha->dwFileTableSize; - - // If we reached maximum file count, we cannot add more files to the MPQ - assert(ha->dwFileTableSize == ha->dwMaxFileCount); + // No deleted entries found return NULL; } - TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale) { TFileEntry * pFileEntry = NULL; TMPQHash * pHash; - DWORD dwHashIndex; - DWORD dwFileIndex; - bool bHashEntryExists = false; - bool bHetEntryExists = false; - // If the archive has classic hash table, we try to - // find the file in the hash table - if(ha->pHashTable != NULL) + // The entry in the hash table should not be there. + // This case must be handled by the caller + assert(ha->pHashTable == NULL || GetHashEntryExact(ha, szFileName, lcLocale) == NULL); + assert(ha->pHetTable == NULL || GetFileIndex_Het(ha, szFileName) == HASH_ENTRY_FREE); + + // If we are in the middle of saving listfile, we need to allocate + // the file entry at the end of the file table + if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0) { - // If the hash entry is already there, we reuse the file entry - pHash = GetHashEntryExact(ha, szFileName, lcLocale); - if(pHash != NULL) + // Attempt to find a deleted file entry in the file table. + // If it suceeds, we reuse that entry + pFileEntry = FindDeletedFileEntry(ha); + if(pFileEntry == NULL) { - pFileEntry = ha->pFileTable + pHash->dwBlockIndex; - bHashEntryExists = true; - } - } + // If there is no space in the file table, we are sorry + if((ha->dwFileTableSize + ha->dwReservedFiles) >= ha->dwMaxFileCount) + return NULL; - // If the archive has HET table, try to use it for - // finding the file - if(ha->pHetTable != NULL) - { - dwFileIndex = GetFileIndex_Het(ha, szFileName); - if(dwFileIndex != HASH_ENTRY_FREE) + // Invalidate the internal files. Then we need to search for a deleted entry again, + // because the previous call to InvalidateInternalFiles might have created some + InvalidateInternalFiles(ha); + + // Re-check for deleted entries + pFileEntry = FindDeletedFileEntry(ha); + if(pFileEntry == NULL) + { + // If there is still no deleted entry, we allocate an entry at the end of the file table + assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount); + pFileEntry = ha->pFileTable + ha->dwFileTableSize++; + } + } + else { - pFileEntry = ha->pFileTable + dwFileIndex; - bHetEntryExists = true; + // Invalidate the internal files + InvalidateInternalFiles(ha); } } - - // If still haven't found the file entry, we allocate new one - if(pFileEntry == NULL) + else { - pFileEntry = FindFreeFileEntry(ha); - if(pFileEntry == NULL) - return NULL; + // There should be at least one entry for that internal file + assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount); + pFileEntry = ha->pFileTable + ha->dwFileTableSize++; } - // Fill the rest of the file entry - pFileEntry->ByteOffset = 0; - pFileEntry->FileTime = 0; - pFileEntry->dwFileSize = 0; - pFileEntry->dwCmpSize = 0; - pFileEntry->dwFlags = 0; - pFileEntry->lcLocale = (USHORT)lcLocale; - pFileEntry->wPlatform = 0; - pFileEntry->dwCrc32 = 0; - memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE); - - // Allocate space for file name, if it's not there yet - AllocateFileName(pFileEntry, szFileName); - - // If the free file entry is at the end of the file table, - // we have to increment file table size - if(pFileEntry == ha->pFileTable + ha->dwFileTableSize) + // Did we find an usable file entry? + if(pFileEntry != NULL) { - assert(ha->dwFileTableSize < ha->dwMaxFileCount); - ha->pHeader->dwBlockTableSize++; - ha->dwFileTableSize++; - } + // Make sure that the entry is properly initialized + memset(pFileEntry, 0, sizeof(TFileEntry)); + pFileEntry->lcLocale = (USHORT)lcLocale; + AllocateFileName(ha, pFileEntry, szFileName); - // If the MPQ has hash table, we have to insert the new entry into the hash table - if(ha->pHashTable != NULL && bHashEntryExists == false) - { - pHash = AllocateHashEntry(ha, pFileEntry); - assert(pHash != NULL); - } - - // If the MPQ has HET table, we have to insert it to the HET table as well - if(ha->pHetTable != NULL && bHetEntryExists == false) - { - // TODO: Does HET table even support locales? - // Most probably, Blizzard gave up that silly idea long ago. - dwHashIndex = AllocateHetEntry(ha, pFileEntry); - assert(dwHashIndex != HASH_ENTRY_FREE); + // If the MPQ has hash table, we have to insert the new entry into the hash table + // We expect it to succeed because there must be a free hash entry if there is a free file entry + // Note: Don't bother with the HET table. It will be rebuilt anyway + if(ha->pHashTable != NULL) + { + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); + } } // Return the file entry @@ -1653,10 +1780,8 @@ int RenameFileEntry( const char * szNewFileName) { TMPQHash * pHash; - DWORD dwFileIndex; - int nError = ERROR_SUCCESS; - // If the MPQ has classic hash table, clear the entry there + // Mark the entry as deleted in the hash table if(ha->pHashTable != NULL) { assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); @@ -1666,23 +1791,10 @@ int RenameFileEntry( pHash->dwBlockIndex = HASH_ENTRY_DELETED; } - // If the MPQ has HET table, clear the entry there as well - if(ha->pHetTable != NULL) - { - TMPQHetTable * pHetTable = ha->pHetTable; - DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1; - - assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize); - - // Clear the entry in the HET hash array - pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED; - - // Set the BET index to invalid index - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex, - pHetTable->dwIndexSize, - &dwInvalidFileIndex, - 4); - } + // + // Note: Don't bother with the HET table. + // It will be rebuilt from scratch anyway + // // Free the old file name if(pFileEntry->szFileName != NULL) @@ -1690,157 +1802,131 @@ int RenameFileEntry( pFileEntry->szFileName = NULL; // Allocate new file name - AllocateFileName(pFileEntry, szNewFileName); + AllocateFileName(ha, pFileEntry, szNewFileName); // Now find a hash entry for the new file name if(ha->pHashTable != NULL) { // Try to find the hash table entry for the new file name - // Note: If this fails, we leave the MPQ in a corrupt state + // Note: Since we deleted one hash entry, this will always succeed pHash = AllocateHashEntry(ha, pFileEntry); - if(pHash == NULL) - nError = ERROR_FILE_CORRUPT; - } - - // If the archive has HET table, we have to allocate HET table for the file as well - // finding the file - if(ha->pHetTable != NULL) - { - dwFileIndex = AllocateHetEntry(ha, pFileEntry); - if(dwFileIndex == HASH_ENTRY_FREE) - nError = ERROR_FILE_CORRUPT; + assert(pHash != NULL); } // Invalidate the entries for (listfile) and (attributes) // After we are done with MPQ changes, we need to re-create them InvalidateInternalFiles(ha); - return nError; + return ERROR_SUCCESS; } -void ClearFileEntry( +void DeleteFileEntry( TMPQArchive * ha, TFileEntry * pFileEntry) { - TMPQHash * pHash = NULL; + TMPQHash * pHash; // If the MPQ has classic hash table, clear the entry there if(ha->pHashTable != NULL) { - assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); - - pHash = ha->pHashTable + pFileEntry->dwHashIndex; - memset(pHash, 0xFF, sizeof(TMPQHash)); - pHash->dwBlockIndex = HASH_ENTRY_DELETED; - } - - // If the MPQ has HET table, clear the entry there as well - if(ha->pHetTable != NULL) - { - TMPQHetTable * pHetTable = ha->pHetTable; - DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1; - - assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize); - - // Clear the entry in the HET hash array - pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED; - - // Set the BET index to invalid index - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex, - pHetTable->dwIndexSize, - &dwInvalidFileIndex, - 4); + // Only if the file entry is still an existing one + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + // We expect dwHashIndex to be within the hash table + pHash = ha->pHashTable + pFileEntry->dwHashIndex; + assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); + + // Set the hash table entry as deleted + pHash->dwName1 = 0xFFFFFFFF; + pHash->dwName2 = 0xFFFFFFFF; + pHash->lcLocale = 0xFFFF; + pHash->wPlatform = 0xFFFF; + pHash->dwBlockIndex = HASH_ENTRY_DELETED; + } } // Free the file name, and set the file entry as deleted if(pFileEntry->szFileName != NULL) STORM_FREE(pFileEntry->szFileName); + pFileEntry->szFileName = NULL; + + // + // Don't modify the HET table, because it gets recreated from scratch at every modification operation + // Don't decrement the number of entries in the file table + // Keep Byte Offset, file size and compressed size in the file table + // Also keep the CRC32 and MD5 in the file attributes + // Clear the file name hash + // Clear the MPQ_FILE_EXISTS bit. + // - // Invalidate the file entry - memset(pFileEntry, 0, sizeof(TFileEntry)); + pFileEntry->FileNameHash = 0; + pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; } -int FreeFileEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) +void InvalidateInternalFiles(TMPQArchive * ha) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pTempEntry; - int nError = ERROR_SUCCESS; - - // - // If we have HET table, we cannot just get rid of the file - // Doing so would lead to empty gaps in the HET table - // We have to keep BET hash, hash index, HET index, locale, platform and file name - // + TFileEntry * pFileEntry; + DWORD dwReservedFiles = 0; - if(ha->pHetTable == NULL) + // Do nothing if we are in the middle of saving internal files + if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES)) { - TFileEntry * pLastFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; - TFileEntry * pLastUsedEntry = pLastFileEntry; - - // Zero the file entry - ClearFileEntry(ha, pFileEntry); + // + // We clear the file entries of (listfile) and (attributes) + // For each internal file cleared, we increment the number + // of reserved entries in the file table. + // - // Now there is a chance that we created a chunk of free - // file entries at the end of the file table. We check this - // and eventually free all deleted file entries at the end - for(pTempEntry = ha->pFileTable; pTempEntry < pFileTableEnd; pTempEntry++) + // Invalidate the (listfile), if not done yet + if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)) { - // Is that an occupied file entry? - if(pTempEntry->dwFlags & MPQ_FILE_EXISTS) - pLastUsedEntry = pTempEntry; - } + pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL); + if(pFileEntry != NULL) + { + DeleteFileEntry(ha, pFileEntry); + dwReservedFiles++; + } - // Can we free some entries at the end? - if(pLastUsedEntry < pLastFileEntry) - { - // Fix the size of the file table entry - ha->dwFileTableSize = (DWORD)(pLastUsedEntry - ha->pFileTable) + 1; - ha->pHeader->dwBlockTableSize = ha->dwFileTableSize; + ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID; } - } - else - { - // Note: Deleted entries in Blizzard MPQs version 4.0 - // normally contain valid byte offset and length - pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; - nError = ERROR_SUCCESS; - } - return nError; -} + // Invalidate the (attributes), if not done yet + if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)) + { + pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); + if(pFileEntry != NULL) + { + DeleteFileEntry(ha, pFileEntry); + dwReservedFiles++; + } -void InvalidateInternalFiles(TMPQArchive * ha) -{ - TFileEntry * pFileEntry; + ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID; + } - // - // Note: We set the size of both (listfile) and (attributes) to zero. - // This causes allocating space for newly added files straight over - // (listfile)/(attributes), if these were the last ones in the MPQ - // + // If the internal files are at the end of the file table (they usually are), + // we want to free these 2 entries, so when new files are added, they get + // added to the freed entries and the internal files get added after that + if(ha->dwFileTableSize > 0 && dwReservedFiles != 0) + { + // Go backwards while there are free entries + for(pFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; pFileEntry >= ha->pFileTable; pFileEntry--) + { + // Stop searching if a file is present + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry++; + break; + } + } - // Invalidate the (listfile), if not done yet - if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)) - { - pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0; - ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID; - } + // Calculate the new file table size + ha->dwFileTableSize = (DWORD)(pFileEntry - ha->pFileTable); + ha->dwReservedFiles = dwReservedFiles; + } - // Invalidate the (attributes), if not done yet - if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)) - { - pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0; - ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID; + // Remember that the MPQ has been changed and it will be necessary + // to update the tables + ha->dwFlags |= MPQ_FLAG_CHANGED; } - - // Remember that the MPQ has been changed and it will be necessary - // to update the tables - ha->dwFlags |= MPQ_FLAG_CHANGED; } //----------------------------------------------------------------------------- @@ -1853,7 +1939,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl ULONGLONG BitmapOffset; ULONGLONG EndOfMpq; DWORD DataBlockCount = 0; - DWORD BitmapByteSize; + DWORD BitmapByteSize = 0; DWORD WholeByteCount; DWORD ExtraBitsCount; @@ -1865,7 +1951,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl { // Calculate the number of extra bytes for data bitmap DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / ha->pHeader->dwRawChunkSize) + 1); - BitmapByteSize = ((DataBlockCount - 1) / 8) + 1; + BitmapByteSize = ((DataBlockCount + 7) / 8); BitmapOffset = EndOfMpq + BitmapByteSize; // Try to load the data bitmap from the end of the file @@ -1876,7 +1962,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE) { // Several sanity checks to ensure integrity of the bitmap - assert(MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); + assert(ha->MpqPos + MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); assert(ha->pHeader->dwRawChunkSize == DataBitmap.dwBlockSize); // Allocate space for the data bitmap @@ -1928,6 +2014,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl *pbFileIsComplete = bFileIsComplete; } + ha->dwBitmapSize = sizeof(TMPQBitmap) + BitmapByteSize; ha->pBitmap = pBitmap; return ERROR_SUCCESS; } @@ -1943,6 +2030,10 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) assert((dwHashTableSize & (dwHashTableSize - 1)) == 0); assert(ha->pHashTable == NULL); + // If the required hash table size is zero, don't create anything + if(dwHashTableSize == 0) + dwHashTableSize = HASH_TABLE_SIZE_DEFAULT; + // Create the hash table pHashTable = STORM_ALLOC(TMPQHash, dwHashTableSize); if(pHashTable == NULL) @@ -1950,11 +2041,8 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) // Fill it memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash)); + ha->dwMaxFileCount = dwHashTableSize; ha->pHashTable = pHashTable; - - // Set the max file count, if needed - if(ha->pHetTable == NULL) - ha->dwMaxFileCount = dwHashTableSize; return ERROR_SUCCESS; } @@ -2004,53 +2092,124 @@ TMPQHash * LoadHashTable(TMPQArchive * ha) break; } - // Calculate mask value that will serve for calculating - // the hash table index from the MPQ_HASH_TABLE_INDEX hash - ha->dwHashIndexMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - // Return the hash table return pHashTable; } +// This function fixes the scenario then dwBlockTableSize +// is greater and goes into a MPQ file static void FixBlockTableSize( TMPQArchive * ha, - TMPQBlock * pBlockTable, - DWORD dwClaimedSize) + TMPQBlock * pBlockTable) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG BlockTableStart; ULONGLONG BlockTableEnd; ULONGLONG FileDataStart; + DWORD dwFixedTableSize = pHeader->dwBlockTableSize; + + // Only perform this check on MPQs version 1.0 + assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1); + + // Calculate claimed block table begin and end + BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + + for(DWORD i = 0; i < dwFixedTableSize; i++) + { + // If the block table end goes into that file, fix the block table end + FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; + if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) + { + dwFixedTableSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); + BlockTableEnd = FileDataStart; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + } + + // If we found a mismatch in the block table size + if(dwFixedTableSize < pHeader->dwBlockTableSize) + { + // Fill the additional space with zeros + memset(pBlockTable + dwFixedTableSize, 0, (pHeader->dwBlockTableSize - dwFixedTableSize) * sizeof(TMPQBlock)); + + // Fix the block table size + pHeader->dwBlockTableSize = dwFixedTableSize; + pHeader->BlockTableSize64 = dwFixedTableSize * sizeof(TMPQBlock); + + // + // Note: We should recalculate the archive size in the header, + // (it might be invalid as well) but we don't care about it anyway + // + + // In theory, a MPQ could have bigger block table than hash table + assert(pHeader->dwBlockTableSize <= ha->dwMaxFileCount); + } +} + +// This function fixes the scenario then dwBlockTableSize +// is greater and goes into a MPQ file +static void FixCompressedFileSize( + TMPQArchive * ha, + TMPQBlock * pBlockTable) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPQBlock ** SortTable; + TMPQBlock * pBlockTableEnd = pBlockTable + pHeader->dwBlockTableSize; + TMPQBlock * pBlock; + size_t nElements = 0; + size_t nIndex; + DWORD dwArchiveSize; // Only perform this check on MPQs version 1.0 - if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1); + + // Allocate sort table for all entries + SortTable = STORM_ALLOC(TMPQBlock*, pHeader->dwBlockTableSize); + if(SortTable != NULL) { - // Calculate claimed block table begin and end - BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + // Calculate the end of the archive + dwArchiveSize = GetArchiveSize32(ha, pBlockTable, pHeader->dwBlockTableSize); + + // Put all blocks to a sort table + for(pBlock = pBlockTable; pBlock < pBlockTableEnd; pBlock++) + { + if(pBlock->dwFlags & MPQ_FILE_EXISTS) + SortTable[nElements++] = pBlock; + } - for(DWORD i = 0; i < dwClaimedSize; i++) + // Have we found at least one compressed + if(nElements > 0) { - // If the block table end goes into that file, fix the block table end - FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; - if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) + // Sort the table + qsort(SortTable, nElements, sizeof(TMPQBlock *), CompareFilePositions); + + // Walk the table and set all compressed sizes to they + // match the difference (dwFilePos1 - dwFilePos0) + for(nIndex = 0; nIndex < nElements - 1; nIndex++) { - dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); - BlockTableEnd = FileDataStart; + TMPQBlock * pBlock1 = SortTable[nIndex + 1]; + TMPQBlock * pBlock0 = SortTable[nIndex]; + + pBlock0->dwCSize = (pBlock->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (pBlock1->dwFilePos - pBlock0->dwFilePos) : pBlock0->dwFSize; } + + // Fix the last entry + pBlock = SortTable[nElements - 1]; + pBlock->dwCSize = dwArchiveSize - pBlock->dwFilePos; } - } - // Fix the block table size - pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); - pHeader->dwBlockTableSize = dwClaimedSize; + STORM_FREE(SortTable); + } } -TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) + +TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries) { TMPQHeader * pHeader = ha->pHeader; TMPQBlock * pBlockTable = NULL; ULONGLONG ByteOffset; + ULONGLONG FileSize; DWORD dwTableSize; DWORD dwCmpSize; @@ -2067,22 +2226,20 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) { case MPQ_SUBTYPE_MPQ: - // Sanity check, enforced by LoadAnyHashTable - assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); - // Calculate sizes of both tables ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); dwCmpSize = (DWORD)pHeader->BlockTableSize64; - // I found a MPQ which claimed 0x200 entries in the block table, - // but the file was cut and there was only 0x1A0 entries. - // We will handle this case properly. + // Handle case when either the MPQ is cut in the middle of the block table + // or the MPQ is malformed so that block table size is greater than should be + FileStream_GetSize(ha->pStream, &FileSize); if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize) { - pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock)); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + pHeader->BlockTableSize64 = FileSize - ByteOffset; + pHeader->dwBlockTableSize = (DWORD)(pHeader->BlockTableSize64 / sizeof(TMPQBlock)); + dwTableSize = dwCmpSize = (DWORD)pHeader->BlockTableSize64; + ha->dwFlags |= MPQ_FLAG_PROTECTED; } // @@ -2094,9 +2251,17 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) // If failed to load the block table, delete it pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - // Defense against MPQs that claim block table to be bigger than it really is - if(pBlockTable != NULL) - FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); + // De-protecting maps for Warcraft III + if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) + { + // Some protectors set the block table size bigger than really is + if(pBlockTable != NULL) + FixBlockTableSize(ha, pBlockTable); + + // Defense against protectors that set invalid compressed size + if((ha->dwFlags & MPQ_FLAG_PROTECTED) && (bDontFixEntries == false)) + FixCompressedFileSize(ha, pBlockTable); + } break; case MPQ_SUBTYPE_SQP: @@ -2115,8 +2280,8 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) TMPQHetTable * LoadHetTable(TMPQArchive * ha) { + TMPQExtHeader * pExtTable; TMPQHetTable * pHetTable = NULL; - TMPQExtTable * pExtTable; TMPQHeader * pHeader = ha->pHeader; // If the HET table position is not NULL, we expect @@ -2128,7 +2293,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) if(pExtTable != NULL) { // If loading HET table fails, we ignore the result. - pHetTable = TranslateHetTable(pExtTable); + pHetTable = TranslateHetTable((TMPQHetHeader *)pExtTable); STORM_FREE(pExtTable); } } @@ -2138,7 +2303,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) TMPQBetTable * LoadBetTable(TMPQArchive * ha) { - TMPQExtTable * pExtTable; + TMPQExtHeader * pExtTable; TMPQBetTable * pBetTable = NULL; TMPQHeader * pHeader = ha->pHeader; @@ -2152,7 +2317,7 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha) { // If succeeded, we translate the BET table // to more readable form - pBetTable = TranslateBetTable(ha, pExtTable); + pBetTable = TranslateBetTable(ha, (TMPQBetHeader *)pExtTable); STORM_FREE(pExtTable); } } @@ -2174,26 +2339,16 @@ int LoadAnyHashTable(TMPQArchive * ha) if(ha->pHetTable == NULL && ha->pHashTable == NULL) return ERROR_FILE_CORRUPT; - // Set the maximum file count - if(ha->pHetTable != NULL && ha->pHashTable != NULL) - ha->dwMaxFileCount = STORMLIB_MIN(ha->pHetTable->dwHashTableSize, pHeader->dwHashTableSize); - else - ha->dwMaxFileCount = (ha->pHetTable != NULL) ? ha->pHetTable->dwHashTableSize : pHeader->dwHashTableSize; - - // In theory, a MPQ could have bigger block table than hash table - if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount) - { - ha->dwMaxFileCount = ha->pHeader->dwBlockTableSize; - ha->dwFlags |= MPQ_FLAG_READ_ONLY; - } - + // Set the maximum file count to the size of the hash table. + // Note: We don't care about HET table limits, because HET table is rebuilt + // after each file add/rename/delete. + ha->dwMaxFileCount = (ha->pHashTable != NULL) ? pHeader->dwHashTableSize : HASH_TABLE_SIZE_MAX; return ERROR_SUCCESS; } int BuildFileTable_Classic( TMPQArchive * ha, - TFileEntry * pFileTable, - ULONGLONG FileSize) + TFileEntry * pFileTable) { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; @@ -2203,33 +2358,23 @@ int BuildFileTable_Classic( // Sanity checks assert(ha->pHashTable != NULL); + // If the MPQ has no block table, do nothing + if(ha->pHeader->dwBlockTableSize == 0) + return ERROR_SUCCESS; + // Load the block table - pBlockTable = (TMPQBlock *)LoadBlockTable(ha, FileSize); + pBlockTable = (TMPQBlock *)LoadBlockTable(ha); if(pBlockTable != NULL) { - TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; - TMPQHash * pHash; - // If we don't have HET table, we build the file entries from the hash&block tables + // If we do, we only update it from the hash table if(ha->pHetTable == NULL) { - nError = ConvertMpqBlockTable(ha, pFileTable, pBlockTable); + nError = BuildFileTableFromBlockTable(ha, pFileTable, pBlockTable); } else { - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) - { - if(pHash->dwBlockIndex < ha->dwFileTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - } - } + nError = UpdateFileTableFromHashTable(ha, pFileTable); } // Free the block table @@ -2260,13 +2405,13 @@ int BuildFileTable_Classic( // Now merge the hi-block table to the file table if(nError == ERROR_SUCCESS) { + BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, dwTableSize); pFileEntry = pFileTable; // Add the high file offset to the base file offset. - // We also need to swap it during the process. for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++) { - pFileEntry->ByteOffset |= ((ULONGLONG)BSWAP_INT16_UNSIGNED(pHiBlockTable[i]) << 32); + pFileEntry->ByteOffset = MAKE_OFFSET64(pHiBlockTable[i], pFileEntry->ByteOffset); pFileEntry++; } } @@ -2301,13 +2446,18 @@ int BuildFileTable_HetBet( pBetTable = LoadBetTable(ha); if(pBetTable != NULL) { - // Step one: Fill the indexes to the HET table - for(i = 0; i < pHetTable->dwHashTableSize; i++) + // Verify the size of NameHash2 in the BET table. + // It has to be 8 bits less than the information in HET table + if((pBetTable->dwBitCount_NameHash2 + 8) != pHetTable->dwNameHashBitSize) + return ERROR_FILE_CORRUPT; + + // Step one: Fill the name indexes + for(i = 0; i < pHetTable->dwEntryCount; i++) { DWORD dwFileIndex = 0; // Is the entry in the HET table occupied? - if(pHetTable->pHetHashes[i] != 0) + if(pHetTable->pNameHashes[i] != 0) { // Load the index to the BET table GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i, @@ -2315,17 +2465,20 @@ int BuildFileTable_HetBet( &dwFileIndex, 4); // Overflow test - if(dwFileIndex < pBetTable->dwFileCount) + if(dwFileIndex < pBetTable->dwEntryCount) { - // Get the file entry and save HET index - pFileEntry = pFileTable + dwFileIndex; - pFileEntry->dwHetIndex = i; + ULONGLONG NameHash1 = pHetTable->pNameHashes[i]; + ULONGLONG NameHash2 = 0; // Load the BET hash - GetBits(pBetTable->pBetHashes, pBetTable->dwBetHashSizeTotal * dwFileIndex, - pBetTable->dwBetHashSize, - &pFileEntry->BetHash, - 8); + GetBits(pBetTable->pNameHashes, pBetTable->dwBitTotal_NameHash2 * dwFileIndex, + pBetTable->dwBitCount_NameHash2, + &NameHash2, + 8); + + // Combine both part of the name hash and put it to the file table + pFileEntry = pFileTable + dwFileIndex; + pFileEntry->FileNameHash = (NameHash1 << pBetTable->dwBitCount_NameHash2) | NameHash2; } } } @@ -2333,7 +2486,7 @@ int BuildFileTable_HetBet( // Go through the entire BET table and convert it to the file table. pFileEntry = pFileTable; pBitArray = pBetTable->pFileTable; - for(i = 0; i < pBetTable->dwFileCount; i++) + for(i = 0; i < pBetTable->dwEntryCount; i++) { DWORD dwFlagIndex = 0; @@ -2363,7 +2516,6 @@ int BuildFileTable_HetBet( pBetTable->dwBitCount_FlagIndex, &dwFlagIndex, 4); - pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex]; } @@ -2377,7 +2529,7 @@ int BuildFileTable_HetBet( } // Set the current size of the file table - ha->dwFileTableSize = pBetTable->dwFileCount; + ha->dwFileTableSize = pBetTable->dwEntryCount; FreeBetTable(pBetTable); nError = ERROR_SUCCESS; } @@ -2389,7 +2541,7 @@ int BuildFileTable_HetBet( return nError; } -int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) +int BuildFileTable(TMPQArchive * ha) { TFileEntry * pFileTable; bool bFileTableCreated = false; @@ -2420,7 +2572,7 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) // Note: If block table is corrupt or missing, we set the archive as read only if(ha->pHashTable != NULL) { - if(BuildFileTable_Classic(ha, pFileTable, FileSize) != ERROR_SUCCESS) + if(BuildFileTable_Classic(ha, pFileTable) != ERROR_SUCCESS) ha->dwFlags |= MPQ_FLAG_READ_ONLY; else bFileTableCreated = true; @@ -2438,11 +2590,128 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) return ERROR_SUCCESS; } +// Rebuilds the HET table from scratch based on the file table +// Used after a modifying operation (add, rename, delete) +int RebuildHetTable(TMPQArchive * ha) +{ + TMPQHetTable * pOldHetTable = ha->pHetTable; + ULONGLONG FileNameHash; + DWORD i; + int nError = ERROR_SUCCESS; + + // 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); + if(ha->pHetTable != NULL) + { + // Go through the file table again and insert all existing files + for(i = 0; i < ha->dwFileTableSize; i++) + { + if(ha->pFileTable[i].dwFlags & MPQ_FILE_EXISTS) + { + // Get the high + FileNameHash = ha->pFileTable[i].FileNameHash; + nError = InsertHetEntry(ha->pHetTable, FileNameHash, i); + if(nError != ERROR_SUCCESS) + break; + } + } + } + + // Free the old HET table + FreeHetTable(pOldHetTable); + return nError; +} + +// Rebuilds the file table, removing all deleted file entries. +// Used when compacting the archive +int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount) +{ + TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pOldFileTable = ha->pFileTable; + TFileEntry * pFileTable; + TFileEntry * pFileEntry; + TFileEntry * pOldEntry; + TMPQHash * pOldHashTable = ha->pHashTable; + TMPQHash * pHashTable = NULL; + TMPQHash * pHash; + int nError = ERROR_SUCCESS; + + // The new hash table size must be greater or equal to the current hash table size + assert(dwNewHashTableSize >= ha->pHeader->dwHashTableSize); + assert(dwNewMaxFileCount >= ha->dwFileTableSize); + + // The new hash table size must be a power of two + assert((dwNewHashTableSize & (dwNewHashTableSize - 1)) == 0); + + // Allocate the new file table + pFileTable = pFileEntry = STORM_ALLOC(TFileEntry, dwNewMaxFileCount); + if(pFileTable == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; + + // Allocate new hash table + if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) + { + pHashTable = STORM_ALLOC(TMPQHash, dwNewHashTableSize); + if(pHashTable == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; + } + + // If both succeeded, we need to rebuild the file table + if(nError == ERROR_SUCCESS) + { + // Make sure that the hash table is properly filled + memset(pFileTable, 0x00, sizeof(TFileEntry) * dwNewMaxFileCount); + memset(pHashTable, 0xFF, sizeof(TMPQHash) * dwNewHashTableSize); + + // Set the new tables to the MPQ archive + ha->pFileTable = pFileTable; + ha->pHashTable = pHashTable; + + // Set the new limits to the MPQ archive + ha->pHeader->dwHashTableSize = dwNewHashTableSize; + ha->dwMaxFileCount = dwNewMaxFileCount; + + // Now copy all the file entries + for(pOldEntry = pOldFileTable; pOldEntry < pOldFileTableEnd; pOldEntry++) + { + // If the file entry exists, we copy it to the new table + // Otherwise, we skip it + if(pOldEntry->dwFlags & MPQ_FILE_EXISTS) + { + // Copy the file entry + *pFileEntry = *pOldEntry; + + // Create hash entry for it + if(ha->pHashTable != NULL) + { + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); + } + + // Move the file entry by one + *pFileEntry++; + } + } + + // Update the file table size + ha->dwFileTableSize = (DWORD)(pFileEntry - pFileTable); + ha->dwFlags |= MPQ_FLAG_CHANGED; + } + + // Now free the remaining entries + if(pOldFileTable != NULL) + STORM_FREE(pOldFileTable); + if(pOldHashTable != NULL) + STORM_FREE(pOldHashTable); + return nError; +} + // Saves MPQ header, hash table, block table and hi-block table. int SaveMPQTables(TMPQArchive * ha) { - TMPQExtTable * pHetTable = NULL; - TMPQExtTable * pBetTable = NULL; + TMPQExtHeader * pHetTable = NULL; + TMPQExtHeader * pBetTable = NULL; TMPQHeader * pHeader = ha->pHeader; TMPQBlock * pBlockTable = NULL; TMPQHash * pHashTable = NULL; @@ -2488,7 +2757,7 @@ int SaveMPQTables(TMPQArchive * ha) } // Create block table - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) + if(nError == ERROR_SUCCESS && ha->pFileTable != NULL) { pBlockTable = TranslateBlockTable(ha, &BlockTableSize64, &bNeedHiBlockTable); if(pBlockTable == NULL) @@ -2581,6 +2850,7 @@ int SaveMPQTables(TMPQArchive * ha) // Write the MPQ header to the file memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize)) diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp index 5c78004..0aae9f5 100644 --- a/src/SBaseSubTypes.cpp +++ b/src/SBaseSubTypes.cpp @@ -439,6 +439,42 @@ int ConvertMpkHeaderToFormat4( return ERROR_FILE_CORRUPT; } +// 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 * pHash; + DWORD dwIndex; + + // Set the initial index + dwStartIndex = dwIndex = (dwStartIndex & (dwHashTableSize - 1)); + assert(dwHashTableSize != 0); + + // Search the hash table and return the found entries in the following priority: + for(;;) + { + // 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) + return pHash; + + // Move to the next hash entry. + // If we reached the starting entry, it's failure. + dwIndex = (dwIndex + 1) & (dwHashTableSize - 1); + if(dwIndex == dwStartIndex) + break; + } + + // We haven't found anything + assert(false); + return NULL; +} + void DecryptMpkTable(void * pvMpkTable, size_t cbSize) { LPBYTE pbMpkTable = (LPBYTE)pvMpkTable; @@ -477,6 +513,7 @@ void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize) TMPQHash * LoadMpkHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; + TMPQHash * pHashTable = NULL; TMPKHash * pMpkHash; TMPQHash * pHash = NULL; DWORD dwHashTableSize = pHeader->dwHashTableSize; @@ -484,7 +521,7 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) // MPKs use different hash table searching. // Instead of using MPQ_HASH_TABLE_INDEX hash as index, // they store the value directly in the hash table. - // Also, for faster searching, the hash table is sorted ascending by the value + // Also for faster searching, the hash table is sorted ascending by the value // Load and decrypt the MPK hash table. pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash)); @@ -493,25 +530,21 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) // Calculate the hash table size as if it was real MPQ hash table pHeader->dwHashTableSize = GetHashTableSizeForFileCount(dwHashTableSize); pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); - ha->dwHashIndexMask = pHeader->dwHashTableSize ? (pHeader->dwHashTableSize - 1) : 0; // Now allocate table that will serve like a true MPQ hash table, // so we translate the MPK hash table to MPQ hash table - ha->pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); - if(ha->pHashTable != NULL) + pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); + if(pHashTable != NULL) { // Set the entire hash table to free - memset(ha->pHashTable, 0xFF, (size_t)pHeader->HashTableSize64); + memset(pHashTable, 0xFF, (size_t)pHeader->HashTableSize64); // Copy the MPK hash table into MPQ hash table for(DWORD i = 0; i < dwHashTableSize; i++) { // Finds the free hash entry in the hash table - pHash = FindFreeHashEntry(ha, pMpkHash[i].dwName1, pMpkHash[i].dwName2, pMpkHash[i].dwName3, 0); - if(pHash == NULL) - break; - - // Sanity check + // 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); assert(pHash->dwBlockIndex == HASH_ENTRY_FREE); // Copy the MPK hash entry to the hash table @@ -521,21 +554,13 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) pHash->dwName1 = pMpkHash[i].dwName2; pHash->dwName2 = pMpkHash[i].dwName3; } - - // If an error was found during conversion, - // free the hash table - if(pHash == NULL) - { - STORM_FREE(ha->pHashTable); - ha->pHashTable = NULL; - } } // Free the temporary hash table STORM_FREE(pMpkHash); } - return ha->pHashTable; + return pHashTable; } static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags) diff --git a/src/SCompression.cpp b/src/SCompression.cpp index 933dc61..5402771 100644 --- a/src/SCompression.cpp +++ b/src/SCompression.cpp @@ -425,7 +425,7 @@ static void LZMA_Callback_Free(void *p, void *address) // the data compressed by StormLib. // -/*static */ void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) +static void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { ICompressProgress Progress; CLzmaEncProps props; @@ -1076,6 +1076,16 @@ int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInB // is not supported by newer MPQs. // + case (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN): + pfnDecompress1 = Decompress_huff; + pfnDecompress2 = Decompress_ADPCM_mono; + break; + + case (MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN): + pfnDecompress1 = Decompress_huff; + pfnDecompress2 = Decompress_ADPCM_stereo; + break; + default: SetLastError(ERROR_FILE_CORRUPT); return 0; diff --git a/src/SFileAddFile.cpp b/src/SFileAddFile.cpp index 775a969..bb8f4f1 100644 --- a/src/SFileAddFile.cpp +++ b/src/SFileAddFile.cpp @@ -85,23 +85,17 @@ static int WriteDataToMpqFile( { TFileEntry * pFileEntry = hf->pFileEntry; ULONGLONG ByteOffset; - LPBYTE pbCompressed = NULL; // Compressed (target) data - LPBYTE pbToWrite = NULL; // Data to write to the file - int nCompressionLevel = -1; // ADPCM compression level (only used for wave files) + LPBYTE pbCompressed = NULL; // Compressed (target) data + LPBYTE pbToWrite = hf->pbFileSector; // Data to write to the file + int nCompressionLevel; // ADPCM compression level (only used for wave files) int nError = ERROR_SUCCESS; - // If the caller wants ADPCM compression, we will set wave compression level to 4, - // which corresponds to medium quality - if(dwCompression & LOSSY_COMPRESSION_MASK) - nCompressionLevel = 4; - // Make sure that the caller won't overrun the previously initiated file size assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize); assert(hf->dwSectorCount != 0); assert(hf->pbFileSector != NULL); if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize) return ERROR_DISK_FULL; - pbToWrite = hf->pbFileSector; // Now write all data to the file sector buffer if(nError == ERROR_SUCCESS) @@ -159,8 +153,8 @@ static int WriteDataToMpqFile( } // - // Note that both SCompImplode and SCompCompress give original buffer, - // if they are unable to comperss the data. + // Note that both SCompImplode and SCompCompress copy data as-is, + // if they are unable to compress the data. // if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) @@ -170,6 +164,21 @@ static int WriteDataToMpqFile( if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { + // If this is the first sector, we need to override the given compression + // by the first sector compression. This is because the entire sector must + // be compressed by the same compression. + // + // Test case: + // + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_PKWARE) // Write 0x10 bytes (sector 0) + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) + dwCompression = (dwSectorIndex == 0) ? hf->dwCompression0 : dwCompression; + + // If the caller wants ADPCM compression, we will set wave compression level to 4, + // which corresponds to medium quality + nCompressionLevel = (dwCompression & LOSSY_COMPRESSION_MASK) ? 4 : -1; SCompCompress(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel); } @@ -236,8 +245,8 @@ static int RecryptFileData( assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED); // File decryption key is calculated from the plain name - szNewFileName = GetPlainFileNameA(szNewFileName); - szFileName = GetPlainFileNameA(szFileName); + szNewFileName = GetPlainFileName(szNewFileName); + szFileName = GetPlainFileName(szFileName); // Calculate both file keys dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); @@ -382,7 +391,7 @@ int SFileAddFile_Init( if(hf == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; - // Find a free space in the MPQ, as well as free block table entry + // Find a free space in the MPQ and verify if it's not over 4 GB on MPQs v1 if(nError == ERROR_SUCCESS) { // Find the position where the file will be stored @@ -390,9 +399,6 @@ int SFileAddFile_Init( hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; hf->bIsWriteHandle = true; - // Sanity check: The MPQ must be marked as changed at this point - assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0); - // When format V1, the size of the archive cannot exceed 4 GB if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) { @@ -418,38 +424,26 @@ int SFileAddFile_Init( } else { - // Only if the file really exists - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // If the file exists and "replace existing" is not set, fail it - if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0) - nError = ERROR_ALREADY_EXISTS; - - // If the file entry already contains a file - // and it is a pseudo-name, replace it - if(nError == ERROR_SUCCESS) - { - AllocateFileName(pFileEntry, szFileName); - } - } + // If the caller didn't set MPQ_FILE_REPLACEEXISTING, fail it + if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0) + nError = ERROR_ALREADY_EXISTS; } } - // - // At this point, the file name in file entry must be non-NULL - // - - // Create key for file encryption - if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED)) - { - hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags); - } - + // Fill the file entry and TMPQFile structure if(nError == ERROR_SUCCESS) { + // At this point, the file name in the file entry must be set + assert(pFileEntry->szFileName != NULL); + assert(_stricmp(pFileEntry->szFileName, szFileName) == 0); + // Initialize the hash entry for the file hf->pFileEntry = pFileEntry; hf->dwDataSize = dwFileSize; + + // Decrypt the file key + if(dwFlags & MPQ_FILE_ENCRYPTED) + hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags); // Initialize the block table entry for the file pFileEntry->ByteOffset = hf->MpqFilePos; @@ -467,14 +461,17 @@ int SFileAddFile_Init( // If the caller gave us a file time, use it. pFileEntry->FileTime = FileTime; + // Mark the archive as modified + ha->dwFlags |= MPQ_FLAG_CHANGED; + // Call the callback, if needed if(ha->pfnAddFileCB != NULL) ha->pfnAddFileCB(ha->pvAddFileUserData, 0, hf->dwDataSize, false); } - // If an error occured, remember it - if(nError != ERROR_SUCCESS && hf != NULL) - hf->bErrorOccured = true; + // Store the error code from Add File operation + if(hf != NULL) + hf->nAddFileError = nError; *phf = hf; return nError; } @@ -499,12 +496,9 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d ULONGLONG RawFilePos = hf->RawFilePos; // Allocate buffer for file sector - nError = AllocateSectorBuffer(hf); + hf->nAddFileError = nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } // Allocate patch info, if the data is patch if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize)) @@ -513,34 +507,25 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE; // Allocate the patch info - nError = AllocatePatchInfo(hf, false); + hf->nAddFileError = nError = AllocatePatchInfo(hf, false); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } } // Allocate sector offsets if(hf->SectorOffsets == NULL) { - nError = AllocateSectorOffsets(hf, false); + hf->nAddFileError = nError = AllocateSectorOffsets(hf, false); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } } // Create array of sector checksums if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)) { - nError = AllocateSectorChecksums(hf, false); + hf->nAddFileError = nError = AllocateSectorChecksums(hf, false); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } } // Pre-save the patch info, if any @@ -568,7 +553,16 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d // Write the MPQ data to the file if(nError == ERROR_SUCCESS) + { + // Save the first sector compression to the file structure + // Note that the entire first file sector will be compressed + // by compression that was passed to the first call of SFileAddFile_Write + if(hf->dwFilePos == 0) + hf->dwCompression0 = dwCompression; + + // Write the data to the MPQ nError = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression); + } // If it succeeded and we wrote all the file data, // we need to re-save sector offset table @@ -586,8 +580,6 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d if(hf->SectorChksums != NULL) { nError = WriteSectorChecksums(hf); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } // Now write patch info @@ -597,16 +589,12 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d hf->pPatchInfo->dwDataSize = hf->pFileEntry->dwFileSize; hf->pFileEntry->dwFileSize = hf->dwPatchedFileSize; nError = WritePatchInfo(hf); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } // Now write sector offsets to the file if(hf->SectorOffsets != NULL) { nError = WriteSectorOffsets(hf); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } // Write the MD5 hashes of each file chunk, if required @@ -616,16 +604,12 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d ha->MpqPos + hf->pFileEntry->ByteOffset, hf->pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } } } - else - { - hf->bErrorOccured = true; - } + // Store the error code from the Write File operation + hf->nAddFileError = nError; return nError; } @@ -633,45 +617,43 @@ int SFileAddFile_Finish(TMPQFile * hf) { TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; - int nError = ERROR_SUCCESS; + int nError = hf->nAddFileError; // If all previous operations succeeded, we can update the MPQ - if(!hf->bErrorOccured) + if(nError == ERROR_SUCCESS) { // Verify if the caller wrote the file properly if(hf->pPatchInfo == NULL) { assert(pFileEntry != NULL); if(hf->dwFilePos != pFileEntry->dwFileSize) - { nError = ERROR_CAN_NOT_COMPLETE; - hf->bErrorOccured = true; - } } else { if(hf->dwFilePos != hf->pPatchInfo->dwDataSize) - { nError = ERROR_CAN_NOT_COMPLETE; - hf->bErrorOccured = true; - } } } - if(!hf->bErrorOccured) + // Now we need to recreate the HET table, if exists + if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) + { + nError = RebuildHetTable(ha); + } + + // Update the block table size + if(nError == ERROR_SUCCESS) { // Call the user callback, if any if(ha->pfnAddFileCB != NULL) ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwDataSize, hf->dwDataSize, true); - - // Update the size of the block table - ha->pHeader->dwBlockTableSize = ha->dwFileTableSize; } else { // Free the file entry in MPQ tables if(pFileEntry != NULL) - FreeFileEntry(ha, pFileEntry); + DeleteFileEntry(ha, pFileEntry); } // Clear the add file callback @@ -695,7 +677,7 @@ bool WINAPI SFileCreateFile( int nError = ERROR_SUCCESS; // Check valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szArchivedName == NULL || *szArchivedName == 0) nError = ERROR_INVALID_PARAMETER; @@ -725,16 +707,9 @@ bool WINAPI SFileCreateFile( nError = ERROR_INVALID_PARAMETER; } - // Create the file in MPQ + // Initiate the add file operation if(nError == ERROR_SUCCESS) - { - // Invalidate the entries for (listfile) and (attributes) - // After we are done with MPQ changes, we need to re-create them anyway - InvalidateInternalFiles(ha); - - // Initiate the add file operation nError = SFileAddFile_Init(ha, szArchivedName, FileTime, dwFileSize, lcLocale, dwFlags, (TMPQFile **)phFile); - } // Deal with the errors if(nError != ERROR_SUCCESS) @@ -752,7 +727,7 @@ bool WINAPI SFileWriteFile( int nError = ERROR_SUCCESS; // Check the proper parameters - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) nError = ERROR_INVALID_HANDLE; if(hf->bIsWriteHandle == false) nError = ERROR_INVALID_HANDLE; @@ -794,7 +769,7 @@ bool WINAPI SFileFinishFile(HANDLE hFile) int nError = ERROR_SUCCESS; // Check the proper parameters - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) nError = ERROR_INVALID_HANDLE; if(hf->bIsWriteHandle == false) nError = ERROR_INVALID_HANDLE; @@ -881,6 +856,8 @@ bool WINAPI SFileAddFileEx( if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) dwCompression = MPQ_COMPRESSION_PKWARE; + // Remove both flag mono and stereo flags. + // They will be re-added according to WAVE type dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO); bIsAdpcmCompression = true; } @@ -908,7 +885,7 @@ bool WINAPI SFileAddFileEx( // If the file being added is a WAVE file, we check number of channels if(bIsFirstSector && bIsAdpcmCompression) { - // The file must really be a wave file, otherwise it's data corruption + // The file must really be a wave file, otherwise it will corrupt the file if(!IsWaveFile(pbFileData, dwBytesToRead, &dwChannels)) { nError = ERROR_BAD_FORMAT; @@ -1024,7 +1001,7 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch // Check the parameters if(nError == ERROR_SUCCESS) { - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) nError = ERROR_INVALID_PARAMETER; @@ -1067,8 +1044,12 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch // After we are done with MPQ changes, we need to re-create them anyway InvalidateInternalFiles(ha); - // Mark the file entry as free - nError = FreeFileEntry(ha, pFileEntry); + // Delete the file entry in the file table and defragment the file table + DeleteFileEntry(ha, pFileEntry); + + // We also need to rebuild the HET table, if present + if(ha->pHetTable != NULL) + nError = RebuildHetTable(ha); } // Resolve error and exit @@ -1089,7 +1070,7 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s // Test the valid parameters if(nError == ERROR_SUCCESS) { - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0) nError = ERROR_INVALID_PARAMETER; @@ -1136,6 +1117,10 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s nError = RenameFileEntry(ha, pFileEntry, szNewFileName); } + // Now we need to recreate the HET table, if we have one + if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) + nError = RebuildHetTable(ha); + // Now we copy the existing file entry to the new one if(nError == ERROR_SUCCESS) { @@ -1170,9 +1155,8 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s } } - // // Note: MPQ_FLAG_CHANGED is set by RenameFileEntry - // + // assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0); // Resolve error and return if(nError != ERROR_SUCCESS) @@ -1207,7 +1191,7 @@ bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale) TMPQFile * hf = (TMPQFile *)hFile; // Invalid handle => do nothing - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return false; @@ -1274,7 +1258,7 @@ bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileC { TMPQArchive * ha = (TMPQArchive *) hMpq; - if (!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_HANDLE); return false; diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp index 995e5b1..bca1f66 100644 --- a/src/SFileAttributes.cpp +++ b/src/SFileAttributes.cpp @@ -27,343 +27,352 @@ typedef struct _MPQ_ATTRIBUTES_HEADER } MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER; //----------------------------------------------------------------------------- -// Public functions (internal use by StormLib) +// Local functions -int SAttrLoadAttributes(TMPQArchive * ha) +static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwFileTableSize) { - MPQ_ATTRIBUTES_HEADER AttrHeader; - HANDLE hFile = NULL; + DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER); + + // Calculate size of the (attributes) file + if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32) + cbAttrFile += dwFileTableSize * sizeof(DWORD); + if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + cbAttrFile += dwFileTableSize * sizeof(ULONGLONG); + if(dwAttrFlags & MPQ_ATTRIBUTE_MD5) + cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE; + + // Weird: When there's 1 extra bit in the patch bit array, it's ignored + // wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes + if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) + cbAttrFile += (dwFileTableSize + 6) / 8; + + return cbAttrFile; +} + +static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile) +{ + LPBYTE pbAttrFileEnd = pbAttrFile + cbAttrFile; + LPBYTE pbAttrPtr = pbAttrFile; DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize; - DWORD dwArraySize; - DWORD dwBytesRead; DWORD i; - int nError = ERROR_SUCCESS; - // File table must be initialized - assert(ha->pFileTable != NULL); + // Load and verify the header + if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd) + { + PMPQ_ATTRIBUTES_HEADER pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr; + + BSWAP_ARRAY32_UNSIGNED(pAttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER)); + if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1) + return ERROR_BAD_FORMAT; + + // Verify the flags and size of the file + assert((pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL) == 0); + assert(GetSizeOfAttributesFile(pAttrHeader->dwFlags, dwBlockTableSize) == cbAttrFile); + + ha->dwAttrFlags = pAttrHeader->dwFlags; + pbAttrPtr = (LPBYTE)(pAttrHeader + 1); + } - // Attempt to open the "(attributes)" file. - // If it's not there, then the archive doesn't support attributes - if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile)) + // Load the CRC32 (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32) { - // Load the content of the attributes file - SFileReadFile(hFile, &AttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER), &dwBytesRead, NULL); - if(dwBytesRead != sizeof(MPQ_ATTRIBUTES_HEADER)) - nError = ERROR_FILE_CORRUPT; + LPDWORD ArrayCRC32 = (LPDWORD)pbAttrPtr; + DWORD cbArraySize = dwBlockTableSize * sizeof(DWORD); - // Verify the header of the (attributes) file - if(nError == ERROR_SUCCESS) - { - AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(AttrHeader.dwVersion); - AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED(AttrHeader.dwFlags); - ha->dwAttrFlags = AttrHeader.dwFlags; - if(dwBytesRead != sizeof(MPQ_ATTRIBUTES_HEADER)) - nError = ERROR_FILE_CORRUPT; - } + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; - // Verify format of the attributes - if(nError == ERROR_SUCCESS) - { - if(AttrHeader.dwVersion > MPQ_ATTRIBUTES_V1) - nError = ERROR_BAD_FORMAT; - } + BSWAP_ARRAY32_UNSIGNED(ArrayCRC32, cbCRC32Size); + for(i = 0; i < dwBlockTableSize; i++) + ha->pFileTable[i].dwCrc32 = ArrayCRC32[i]; + pbAttrPtr += cbArraySize; + } - // Load the CRC32 (if any) - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_CRC32)) - { - LPDWORD pArrayCRC32 = STORM_ALLOC(DWORD, dwBlockTableSize); + // Load the FILETIME (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + { + ULONGLONG * ArrayFileTime = (ULONGLONG *)pbAttrPtr; + DWORD cbArraySize = dwBlockTableSize * sizeof(ULONGLONG); - if(pArrayCRC32 != NULL) - { - dwArraySize = dwBlockTableSize * sizeof(DWORD); - SFileReadFile(hFile, pArrayCRC32, dwArraySize, &dwBytesRead, NULL); - if(dwBytesRead == dwArraySize) - { - for(i = 0; i < dwBlockTableSize; i++) - ha->pFileTable[i].dwCrc32 = BSWAP_INT32_UNSIGNED(pArrayCRC32[i]); - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pArrayCRC32); - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; - // Read the array of file times - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_FILETIME)) - { - ULONGLONG * pArrayFileTime = STORM_ALLOC(ULONGLONG, dwBlockTableSize); + BSWAP_ARRAY64_UNSIGNED(ArrayFileTime, cbFileTimeSize); + for(i = 0; i < dwBlockTableSize; i++) + ha->pFileTable[i].FileTime = ArrayFileTime[i]; + pbAttrPtr += cbArraySize; + } - if(pArrayFileTime != NULL) - { - dwArraySize = dwBlockTableSize * sizeof(ULONGLONG); - SFileReadFile(hFile, pArrayFileTime, dwArraySize, &dwBytesRead, NULL); - if(dwBytesRead == dwArraySize) - { - for(i = 0; i < dwBlockTableSize; i++) - ha->pFileTable[i].FileTime = BSWAP_INT64_UNSIGNED(pArrayFileTime[i]); - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pArrayFileTime); - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } + // Load the MD5 (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) + { + LPBYTE ArrayMd5 = pbAttrPtr; + DWORD cbArraySize = dwBlockTableSize * MD5_DIGEST_SIZE; - // Read the MD5 (if any) - // Note: MD5 array can be incomplete, if it's the last array in the (attributes) - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_MD5)) - { - unsigned char * pArrayMD5 = STORM_ALLOC(unsigned char, (dwBlockTableSize * MD5_DIGEST_SIZE)); - unsigned char * md5; + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; - if(pArrayMD5 != NULL) - { - dwArraySize = dwBlockTableSize * MD5_DIGEST_SIZE; - SFileReadFile(hFile, pArrayMD5, dwArraySize, &dwBytesRead, NULL); - if(dwBytesRead == dwArraySize) - { - md5 = pArrayMD5; - for(i = 0; i < dwBlockTableSize; i++) - { - memcpy(ha->pFileTable[i].md5, md5, MD5_DIGEST_SIZE); - md5 += MD5_DIGEST_SIZE; - } - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pArrayMD5); - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; + for(i = 0; i < dwBlockTableSize; i++) + { + memcpy(ha->pFileTable[i].md5, ArrayMd5, MD5_DIGEST_SIZE); + ArrayMd5 += MD5_DIGEST_SIZE; } + pbAttrPtr += cbArraySize; + } - // Read the patch bit for each file - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_PATCH_BIT)) - { - LPBYTE pbBitArray; - DWORD dwByteSize = ((dwBlockTableSize - 1) / 8) + 1; + // Read the patch bit for each file (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) + { + LPBYTE pbBitArray = pbAttrPtr; + DWORD cbArraySize = (dwBlockTableSize + 7) / 8; + DWORD dwByteIndex = 0; + DWORD dwBitMask = 0x80; - pbBitArray = STORM_ALLOC(BYTE, dwByteSize); - if(pbBitArray != NULL) - { - SFileReadFile(hFile, pbBitArray, dwByteSize, &dwBytesRead, NULL); - if(dwBytesRead == dwByteSize) - { - for(i = 0; i < dwBlockTableSize; i++) - { - DWORD dwByteIndex = i / 8; - DWORD dwBitMask = 0x80 >> (i & 7); - - // Is the appropriate bit set? - if(pbBitArray[dwByteIndex] & dwBitMask) - { - // At the moment, we assume that the patch bit is present - // in both file table and (attributes) - assert((ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) != 0); - ha->pFileTable[i].dwFlags |= MPQ_FILE_PATCH_FILE; - } - } - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pbBitArray); - } + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; + + for(i = 0; i < dwBlockTableSize; i++) + { + ha->pFileTable[i].dwFlags |= (pbBitArray[dwByteIndex] & dwBitMask) ? MPQ_FILE_PATCH_FILE : 0; + dwByteIndex += (dwBitMask & 0x01); + dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01); } - // - // Note: Version 7.00 of StormLib saved the (attributes) incorrectly. - // Sometimes, number of entries in the (attributes) was 1 item less - // than block table size. - // If we encounter such table, we will zero all three arrays - // + pbAttrPtr += cbArraySize; + } - if(nError != ERROR_SUCCESS) - ha->dwAttrFlags = 0; + // + // Note: Version 7.00 of StormLib saved the (attributes) incorrectly. + // Sometimes, number of entries in the (attributes) was 1 item less + // than block table size. + // If we encounter such table, we will zero all three arrays + // - // Cleanup & exit - SFileCloseFile(hFile); - } - return nError; + if(pbAttrPtr != pbAttrFileEnd) + ha->dwAttrFlags = 0; + return ERROR_SUCCESS; } -int SAttrFileSaveToMpq(TMPQArchive * ha) +static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile) { - MPQ_ATTRIBUTES_HEADER AttrHeader; + PMPQ_ATTRIBUTES_HEADER pAttrHeader; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; - TMPQFile * hf = NULL; - DWORD dwFinalBlockTableSize = ha->dwFileTableSize; - DWORD dwFileSize = 0; - DWORD dwToWrite; - DWORD i; - int nError = ERROR_SUCCESS; + LPBYTE pbAttrFile; + LPBYTE pbAttrPtr; + size_t cbAttrFile; + DWORD dwFinalEntries = ha->dwFileTableSize + ha->dwReservedFiles; - // Now we have to check if we need patch bits in the (attributes) - if(nError == ERROR_SUCCESS) + // Check if we need patch bits in the (attributes) file + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - for(i = 0; i < ha->dwFileTableSize; i++) + if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { - if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) - { - ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; - break; - } + ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; + break; } } - // If the (attributes) is not in the file table yet, - // we have to increase the final block table size - pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - { - // If "(attributes)" file exists, and it's set to 0, then remove it - if(ha->dwAttrFlags == 0) - { - FreeFileEntry(ha, pFileEntry); - return ERROR_SUCCESS; - } - } - else + // Allocate the buffer for holding the entire (attributes) + // Allodate 1 byte more (See GetSizeOfAttributesFile for more info) + cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, dwFinalEntries); + pbAttrFile = pbAttrPtr = STORM_ALLOC(BYTE, cbAttrFile + 1); + if(pbAttrFile != NULL) { - // If we don't want to create file atributes, do nothing - if(ha->dwAttrFlags == 0) - return ERROR_SUCCESS; - - // Check where the file entry is going to be allocated. - // If at the end of the file table, we have to increment - // the expected size of the (attributes) file. - pFileEntry = FindFreeFileEntry(ha); - if(pFileEntry == ha->pFileTable + ha->dwFileTableSize) - dwFinalBlockTableSize++; - } + // Make sure it's all zeroed + memset(pbAttrFile, 0, cbAttrFile + 1); - // Calculate the size of the attributes file - if(nError == ERROR_SUCCESS) - { - dwFileSize = sizeof(MPQ_ATTRIBUTES_HEADER); // Header + // Write the header of the (attributes) file + pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr; + pAttrHeader->dwVersion = BSWAP_INT32_UNSIGNED(100); + pAttrHeader->dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL)); + pbAttrPtr = (LPBYTE)(pAttrHeader + 1); + + // Write the array of CRC32, if present if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32) - dwFileSize += dwFinalBlockTableSize * sizeof(DWORD); - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) - dwFileSize += dwFinalBlockTableSize * sizeof(ULONGLONG); - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) - dwFileSize += dwFinalBlockTableSize * MD5_DIGEST_SIZE; - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) - dwFileSize += ((dwFinalBlockTableSize - 1)) / 8 + 1; - } + { + LPDWORD pArrayCRC32 = (LPDWORD)pbAttrPtr; - // Determine the flags for (attributes) - if(ha->dwFileFlags2 == 0) - ha->dwFileFlags2 = GetDefaultSpecialFileFlags(ha, dwFileSize); + // Copy from file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + *pArrayCRC32++ = BSWAP_INT32_UNSIGNED(pFileEntry->dwCrc32); - // Create the attributes file in the MPQ - nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME, - 0, - dwFileSize, - LANG_NEUTRAL, - ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING, - &hf); + // Skip the reserved entries + pbAttrPtr = (LPBYTE)(pArrayCRC32 + ha->dwReservedFiles); + } - // Write all parts of the (attributes) file - if(nError == ERROR_SUCCESS) - { - assert(ha->dwFileTableSize == dwFinalBlockTableSize); + // Write the array of file time + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + { + ULONGLONG * pArrayFileTime = (ULONGLONG *)pbAttrPtr; - // Note that we don't know what the new bit (0x08) means. - AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(100); - AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL)); - dwToWrite = sizeof(MPQ_ATTRIBUTES_HEADER); - nError = SFileAddFile_Write(hf, &AttrHeader, dwToWrite, MPQ_COMPRESSION_ZLIB); - } + // Copy from file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + *pArrayFileTime++ = BSWAP_INT64_UNSIGNED(pFileEntry->FileTime); - // Write the array of CRC32 - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32)) - { - LPDWORD pArrayCRC32 = STORM_ALLOC(DWORD, dwFinalBlockTableSize); + // Skip the reserved entries + pbAttrPtr = (LPBYTE)(pArrayFileTime + ha->dwReservedFiles); + } - if(pArrayCRC32 != NULL) + // Write the array of MD5s + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) { + LPBYTE pbArrayMD5 = pbAttrPtr; + // Copy from file table - for(i = 0; i < ha->dwFileTableSize; i++) - pArrayCRC32[i] = BSWAP_INT32_UNSIGNED(ha->pFileTable[i].dwCrc32); + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + memcpy(pbArrayMD5, pFileEntry->md5, MD5_DIGEST_SIZE); + pbArrayMD5 += MD5_DIGEST_SIZE; + } - dwToWrite = ha->dwFileTableSize * sizeof(DWORD); - nError = SFileAddFile_Write(hf, pArrayCRC32, dwToWrite, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pArrayCRC32); + // Skip the reserved items + pbAttrPtr = pbArrayMD5 + (ha->dwReservedFiles * MD5_DIGEST_SIZE); } - } - - // Write the array of file time - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)) - { - ULONGLONG * pArrayFileTime = STORM_ALLOC(ULONGLONG, ha->dwFileTableSize); - if(pArrayFileTime != NULL) + // Write the array of patch bits + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) { + LPBYTE pbBitArray = pbAttrPtr; + DWORD dwByteSize = (dwFinalEntries + 7) / 8; + DWORD dwByteIndex = 0; + DWORD dwBitMask = 0x80; + // Copy from file table - for(i = 0; i < ha->dwFileTableSize; i++) - pArrayFileTime[i] = BSWAP_INT64_UNSIGNED(ha->pFileTable[i].FileTime); + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + // Set the bit, if needed + if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) + pbBitArray[dwByteIndex] |= dwBitMask; + + // Update bit index and bit mask + dwByteIndex += (dwBitMask & 0x01); + dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01); + } - dwToWrite = ha->dwFileTableSize * sizeof(ULONGLONG); - nError = SFileAddFile_Write(hf, pArrayFileTime, dwToWrite, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pArrayFileTime); + // Move past the bit array + pbAttrPtr = (pbBitArray + dwByteSize); } + + // Now we expect that current position matches the estimated size + // Note that if there is 1 extra bit above the byte size, + // the table is actually 1 byte shorted in Blizzard MPQs. See GetSizeOfAttributesFile + assert((size_t)(pbAttrPtr - pbAttrFile) == cbAttrFile + ((dwFinalEntries & 0x07) == 1) ? 1 : 0); } - // Write the array of MD5s - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)) - { - char * pArrayMD5 = STORM_ALLOC(char, ha->dwFileTableSize * MD5_DIGEST_SIZE); + // Give away the attributes file + if(pcbAttrFile != NULL) + *pcbAttrFile = (DWORD)cbAttrFile; + return pbAttrFile; +} - if(pArrayMD5 != NULL) - { - // Copy from file table - for(i = 0; i < ha->dwFileTableSize; i++) - memcpy(&pArrayMD5[i * MD5_DIGEST_SIZE], ha->pFileTable[i].md5, MD5_DIGEST_SIZE); +//----------------------------------------------------------------------------- +// Public functions (internal use by StormLib) - dwToWrite = ha->dwFileTableSize * MD5_DIGEST_SIZE; - nError = SFileAddFile_Write(hf, pArrayMD5, dwToWrite, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pArrayMD5); - } - } +int SAttrLoadAttributes(TMPQArchive * ha) +{ + HANDLE hFile = NULL; + LPBYTE pbAttrFile; + DWORD dwBytesRead; + DWORD cbAttrFile = 0; + int nError = ERROR_FILE_CORRUPT; + + // File table must be initialized + assert(ha->pFileTable != NULL); - // Write the array of patch bits - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)) + // Attempt to open the "(attributes)" file. + // If it's not there, then the archive doesn't support attributes + if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile)) { - LPBYTE pbBitArray; - DWORD dwByteSize = ((ha->dwFileTableSize - 1) / 8) + 1; + // Retrieve and check size of the (attributes) file + cbAttrFile = SFileGetFileSize(hFile, NULL); - pbBitArray = STORM_ALLOC(BYTE, dwByteSize); - if(pbBitArray != NULL) + // Size of the (attributes) might be 1 byte less than expected + // See GetSizeOfAttributesFile for more info + pbAttrFile = STORM_ALLOC(BYTE, cbAttrFile + 1); + if(pbAttrFile != NULL) { - memset(pbBitArray, 0, dwByteSize); - for(i = 0; i < ha->dwFileTableSize; i++) - { - DWORD dwByteIndex = i / 8; - DWORD dwBitMask = 0x80 >> (i & 7); + // Set the last byte to 0 in case the size should be 1 byte greater + pbAttrFile[cbAttrFile] = 0; - if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) - pbBitArray[dwByteIndex] |= dwBitMask; - } + // Load the entire file to memory + SFileReadFile(hFile, pbAttrFile, cbAttrFile, &dwBytesRead, NULL); + if(dwBytesRead == cbAttrFile) + nError = LoadAttributesFile(ha, pbAttrFile, cbAttrFile); - nError = SFileAddFile_Write(hf, pbBitArray, dwByteSize, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pbBitArray); + // Free the buffer + STORM_FREE(pbAttrFile); } + + // Close the attributes file + SFileCloseFile(hFile); } - // Finalize the file in the archive - if(hf != NULL) + return nError; +} + +// Saves the (attributes) to the MPQ +int SAttrFileSaveToMpq(TMPQArchive * ha) +{ + TMPQFile * hf = NULL; + LPBYTE pbAttrFile; + DWORD cbAttrFile = 0; + int nError = ERROR_SUCCESS; + + // If there are no file flags for (attributes) file, do nothing + if(ha->dwFileFlags2 == 0 || ha->dwMaxFileCount == 0) + return ERROR_SUCCESS; + + // We expect at least one reserved entry to be there + assert(ha->dwReservedFiles >= 1); + + // Create the raw data that is to be written to (attributes) + // Note: Blizzard MPQs have entries for (listfile) and (attributes), + // but they are filled empty + pbAttrFile = CreateAttributesFile(ha, &cbAttrFile); + + // Now we decrement the number of reserved files. + // This frees one slot in the file table, so the subsequent file create operation should succeed + // This must happen even if CreateAttributesFile failed + ha->dwReservedFiles--; + + // If we created something, write the attributes to the MPQ + if(pbAttrFile != NULL) { - SFileAddFile_Finish(hf); - } + // We expect it to be nonzero size + assert(cbAttrFile != 0); + + // Determine the real flags for (attributes) + if(ha->dwFileFlags2 == MPQ_FILE_EXISTS) + ha->dwFileFlags2 = GetDefaultSpecialFileFlags(cbAttrFile, ha->pHeader->wFormatVersion); + + // Create the attributes file in the MPQ + nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME, + 0, + cbAttrFile, + LANG_NEUTRAL, + ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING, + &hf); + + // Write the attributes file raw data to it + if(nError == ERROR_SUCCESS) + { + // Write the content of the attributes file to the MPQ + nError = SFileAddFile_Write(hf, pbAttrFile, cbAttrFile, MPQ_COMPRESSION_ZLIB); + SFileAddFile_Finish(hf); + + // Clear the invalidate flag + ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID; + } - if(nError == ERROR_SUCCESS) - ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID; + // Free the attributes buffer + STORM_FREE(pbAttrFile); + } + return nError; } @@ -375,7 +384,7 @@ DWORD WINAPI SFileGetAttributes(HANDLE hMpq) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify the parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_PARAMETER); return SFILE_INVALID_ATTRIBUTES; @@ -389,7 +398,7 @@ bool WINAPI SFileSetAttributes(HANDLE hMpq, DWORD dwFlags) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify the parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_PARAMETER); return false; @@ -439,7 +448,7 @@ bool WINAPI SFileUpdateFileAttributes(HANDLE hMpq, const char * szFileName) // Get the file size hf = (TMPQFile *)hFile; - SFileGetFileInfo(hFile, SFILE_INFO_FILE_SIZE, &dwTotalBytes, sizeof(DWORD), NULL); + dwTotalBytes = hf->pFileEntry->dwFileSize; // Initialize the CRC32 and MD5 contexts md5_init(&md5_state); diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp index a726d43..64c599d 100644 --- a/src/SFileCompactArchive.cpp +++ b/src/SFileCompactArchive.cpp @@ -441,7 +441,8 @@ bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK pfnCompa { TMPQArchive * ha = (TMPQArchive *) hMpq; - if (!IsValidMpqHandle(ha)) { + if (!IsValidMpqHandle(hMpq)) + { SetLastError(ERROR_INVALID_HANDLE); return false; } @@ -466,7 +467,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR int nError = ERROR_SUCCESS; // Test the valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; @@ -544,6 +545,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // Write the MPQ header to the file memcpy(&SaveMpqHeader, ha->pHeader, ha->pHeader->dwHeaderSize); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize)) @@ -555,9 +557,21 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // Now copy all files if(nError == ERROR_SUCCESS) - { nError = CopyMpqFiles(ha, pFileKeys, pTempStream); - ha->dwFlags |= MPQ_FLAG_CHANGED; + + // Defragment the file table + if(nError == ERROR_SUCCESS) + nError = RebuildFileTable(ha, ha->pHeader->dwHashTableSize, ha->dwMaxFileCount); + + // We also need to rebuild the HET table, if any + if(nError == ERROR_SUCCESS) + { + // Invalidate (listfile) and (attributes) + InvalidateInternalFiles(ha); + + // Rebuild the HET table, if we have any + if(ha->pHetTable != NULL) + nError = RebuildHetTable(ha); } // If succeeded, switch the streams @@ -608,25 +622,16 @@ DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq) bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount) { - TMPQHetTable * pOldHetTable = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; - TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pOldFileTable = NULL; - TFileEntry * pOldFileEntry; - TFileEntry * pFileEntry; - TMPQHash * pOldHashTable = NULL; - DWORD dwOldHashTableSize = 0; - DWORD dwOldFileTableSize = 0; + DWORD dwNewHashTableSize = 0; int nError = ERROR_SUCCESS; // Test the valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; - - // The new limit must be greater than the current file table size - if(nError == ERROR_SUCCESS && ha->dwFileTableSize > dwMaxFileCount) + if(dwMaxFileCount < ha->dwFileTableSize) nError = ERROR_DISK_FULL; // ALL file names must be known in order to be able @@ -637,126 +642,28 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount) } // If the MPQ has a hash table, then we relocate the hash table - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) - { - // Save parameters for the current hash table - dwOldHashTableSize = ha->pHeader->dwHashTableSize; - pOldHashTable = ha->pHashTable; - - // Allocate new hash table - ha->pHeader->dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); - ha->pHashTable = STORM_ALLOC(TMPQHash, ha->pHeader->dwHashTableSize); - if(ha->pHashTable != NULL) - memset(ha->pHashTable, 0xFF, ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // If the MPQ has HET table, allocate new one as well - if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) - { - // Save the original HET table - pOldHetTable = ha->pHetTable; - - // Create new one - ha->pHetTable = CreateHetTable(0, dwMaxFileCount, 0x40, true); - if(ha->pHetTable == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Now reallocate the file table - if(nError == ERROR_SUCCESS) - { - // Save the current file table - dwOldFileTableSize = ha->dwFileTableSize; - pOldFileTable = ha->pFileTable; - - // Create new one - ha->pFileTable = STORM_ALLOC(TFileEntry, dwMaxFileCount); - if(ha->pFileTable != NULL) - memset(ha->pFileTable, 0, dwMaxFileCount * sizeof(TFileEntry)); - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Now we have to build both classic hash table and HET table. if(nError == ERROR_SUCCESS) { - DWORD dwFileIndex = 0; - DWORD dwHashIndex = 0; + // Calculate the hash table size for the new file limit + dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); - // Create new hash and HET entry for each file - pFileEntry = ha->pFileTable; - for(pOldFileEntry = pOldFileTable; pOldFileEntry < pOldFileTableEnd; pOldFileEntry++) - { - if(pOldFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // Copy the old file entry to the new one - memcpy(pFileEntry, pOldFileEntry, sizeof(TFileEntry)); - assert(pFileEntry->szFileName != NULL); - - // Create new entry in the hash table - if(ha->pHashTable != NULL) - { - if(AllocateHashEntry(ha, pFileEntry) == NULL) - { - nError = ERROR_CAN_NOT_COMPLETE; - break; - } - } - - // Create new entry in the HET table, if needed - if(ha->pHetTable != NULL) - { - dwHashIndex = AllocateHetEntry(ha, pFileEntry); - if(dwHashIndex == HASH_ENTRY_FREE) - { - nError = ERROR_CAN_NOT_COMPLETE; - break; - } - } - - // Move to the next file entry in the new table - pFileEntry++; - dwFileIndex++; - } - } + // Rebuild both file tables + nError = RebuildFileTable(ha, dwNewHashTableSize, dwMaxFileCount); } - // Mark the archive as changed - // Note: We always have to rebuild the (attributes) file due to file table change + // We always have to rebuild the (attributes) file due to file table change if(nError == ERROR_SUCCESS) { - ha->dwMaxFileCount = dwMaxFileCount; + // Invalidate (listfile) and (attributes) InvalidateInternalFiles(ha); - } - else - { - // Revert the hash table - if(ha->pHashTable != NULL && pOldHashTable != NULL) - { - STORM_FREE(ha->pHashTable); - ha->pHeader->dwHashTableSize = dwOldHashTableSize; - ha->pHashTable = pOldHashTable; - } - - // Revert the HET table - if(ha->pHetTable != NULL && pOldHetTable != NULL) - { - FreeHetTable(ha->pHetTable); - ha->pHetTable = pOldHetTable; - } - // Revert the file table - if(pOldFileTable != NULL) - { - STORM_FREE(ha->pFileTable); - ha->pFileTable = pOldFileTable; - } - - SetLastError(nError); + // Rebuild the HET table, if we have any + if(ha->pHetTable != NULL) + nError = RebuildHetTable(ha); } - // Return the result + // Return the error + if(nError != ERROR_SUCCESS) + SetLastError(nError); return (nError == ERROR_SUCCESS); } diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp index 78ddd71..2b51efa 100644 --- a/src/SFileCreateArchive.cpp +++ b/src/SFileCreateArchive.cpp @@ -57,6 +57,7 @@ static int WriteNakedMPQHeader(TMPQArchive * ha) // Write it to the file BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(ha->pStream, &ha->MpqPos, &Header, dwBytesToWrite)) @@ -68,19 +69,27 @@ static int WriteNakedMPQHeader(TMPQArchive * ha) //----------------------------------------------------------------------------- // Creates a new MPQ archive. -bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) { SFILE_CREATE_MPQ CreateInfo; // Fill the create structure memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ)); CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ); - CreateInfo.dwMpqVersion = (dwFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT; + CreateInfo.dwMpqVersion = (dwCreateFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT; CreateInfo.dwStreamFlags = STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE; - CreateInfo.dwAttrFlags = (dwFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0; + 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_ALL : 0; CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000; CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0; CreateInfo.dwMaxFileCount = dwMaxFileCount; + + // Backward compatibility: SFileCreateArchive always used to add (listfile) + // We would break loads of applications if we change that + CreateInfo.dwFileFlags1 = MPQ_FILE_EXISTS; + + // Let the main function create the archive return SFileCreateArchive2(szMpqName, &CreateInfo, phMpq); } @@ -93,7 +102,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea HANDLE hMpq = NULL; DWORD dwBlockTableSize = 0; // Initial block table size DWORD dwHashTableSize = 0; - DWORD dwMaxFileCount; + DWORD dwReservedFiles = 0; // Number of reserved file entries int nError = ERROR_SUCCESS; // Check the parameters, if they are valid @@ -109,8 +118,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea (pCreateInfo->pvUserData != NULL || pCreateInfo->cbUserData != 0) || (pCreateInfo->dwAttrFlags & ~MPQ_ATTRIBUTE_ALL) || (pCreateInfo->dwSectorSize & (pCreateInfo->dwSectorSize - 1)) || - (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)) || - (pCreateInfo->dwMaxFileCount < 4)) + (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1))) { SetLastError(ERROR_INVALID_PARAMETER); return false; @@ -142,15 +150,14 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea return false; } - // Increment the maximum amount of files to have space - // for listfile and attributes file - dwMaxFileCount = pCreateInfo->dwMaxFileCount; - if(pCreateInfo->dwAttrFlags != 0) - dwMaxFileCount++; - dwMaxFileCount++; + // Increment the maximum amount of files to have space for (listfile) and (attributes) + if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags1) + dwReservedFiles++; + if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags2 && pCreateInfo->dwAttrFlags) + dwReservedFiles++; // If file count is not zero, initialize the hash table size - dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); + dwHashTableSize = GetHashTableSizeForFileCount(pCreateInfo->dwMaxFileCount + dwReservedFiles); // Retrieve the file size and round it up to 0x200 bytes FileStream_GetSize(pStream, &MpqPos); @@ -180,14 +187,13 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea ha->UserDataPos = MpqPos; ha->MpqPos = MpqPos; ha->pHeader = pHeader = (TMPQHeader *)ha->HeaderData; - ha->dwMaxFileCount = dwMaxFileCount; + ha->dwMaxFileCount = dwHashTableSize; ha->dwFileTableSize = 0; + ha->dwReservedFiles = dwReservedFiles; ha->dwFileFlags1 = pCreateInfo->dwFileFlags1; ha->dwFileFlags2 = pCreateInfo->dwFileFlags2; - ha->dwFlags = 0; - - // Setup the attributes ha->dwAttrFlags = pCreateInfo->dwAttrFlags; + ha->dwFlags = 0; pStream = NULL; // Fill the MPQ header @@ -215,21 +221,21 @@ 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) + if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3 && pCreateInfo->dwMaxFileCount != 0) { - ha->pHetTable = CreateHetTable(0, ha->dwFileTableSize, 0x40, true); + ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL); if(ha->pHetTable == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Create initial hash table - if(nError == ERROR_SUCCESS) + if(nError == ERROR_SUCCESS && dwHashTableSize != 0) { nError = CreateHashTable(ha, dwHashTableSize); } // Create initial file table - if(nError == ERROR_SUCCESS) + if(nError == ERROR_SUCCESS && ha->dwMaxFileCount != 0) { ha->pFileTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount); if(ha->pFileTable != NULL) diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp index af7e6bc..21c9499 100644 --- a/src/SFileFindFile.cpp +++ b/src/SFileFindFile.cpp @@ -37,12 +37,14 @@ struct TMPQSearch //----------------------------------------------------------------------------- // Local functions -static bool IsValidSearchHandle(TMPQSearch * hs) +static TMPQSearch * IsValidSearchHandle(HANDLE hFind) { - if(hs == NULL) - return false; + TMPQSearch * hs = (TMPQSearch *)hFind; - return IsValidMpqHandle(hs->ha); + if(hs != NULL && IsValidMpqHandle(hs->ha)) + return hs; + + return NULL; } bool CheckWildCard(const char * szString, const char * szWildCard) @@ -140,7 +142,7 @@ static DWORD GetSearchTableItems(TMPQArchive * ha) while(ha != NULL) { // Append the number of files - dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwFileCount + dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount : ha->pHeader->dwBlockTableSize; // Move to the patched archive ha = ha->haPatch; @@ -295,25 +297,29 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) } } - // Check the file name against the wildcard - if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) + // If the file name is still NULL, we cannot include the file to the search + if(szFileName != NULL) { - // Fill the found entry - lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex; - lpFindFileData->dwBlockIndex = dwBlockIndex; - lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; - lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; - lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; - lpFindFileData->lcLocale = pPatchEntry->lcLocale; - - // Fill the filetime - lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); - lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); - - // Fill the file name and plain file name - strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength); - lpFindFileData->szPlainName = (char *)GetPlainFileNameA(lpFindFileData->cFileName); - return ERROR_SUCCESS; + // Check the file name against the wildcard + if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) + { + // Fill the found entry + lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex; + lpFindFileData->dwBlockIndex = dwBlockIndex; + lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; + lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; + lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; + lpFindFileData->lcLocale = pPatchEntry->lcLocale; + + // Fill the filetime + lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); + lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); + + // Fill the file name and plain file name + strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength); + lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName); + return ERROR_SUCCESS; + } } } } @@ -352,7 +358,7 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA int nError = ERROR_SUCCESS; // Check for the valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szMask == NULL || lpFindFileData == NULL) nError = ERROR_INVALID_PARAMETER; @@ -412,11 +418,11 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) { - TMPQSearch * hs = (TMPQSearch *)hFind; + TMPQSearch * hs = IsValidSearchHandle(hFind); int nError = ERROR_SUCCESS; // Check the parameters - if(!IsValidSearchHandle(hs)) + if(hs == NULL) nError = ERROR_INVALID_HANDLE; if(lpFindFileData == NULL) nError = ERROR_INVALID_PARAMETER; @@ -431,10 +437,10 @@ bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) bool WINAPI SFileFindClose(HANDLE hFind) { - TMPQSearch * hs = (TMPQSearch *)hFind; + TMPQSearch * hs = IsValidSearchHandle(hFind); // Check the parameters - if(!IsValidSearchHandle(hs)) + if(hs == NULL) { SetLastError(ERROR_INVALID_HANDLE); return false; diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp index 6a85bd9..06c8d6a 100644 --- a/src/SFileGetFileInfo.cpp +++ b/src/SFileGetFileInfo.cpp @@ -1,12 +1,11 @@ /*****************************************************************************/ -/* SFileReadFile.cpp Copyright (c) Ladislav Zezula 2003 */ +/* SFileGetFileInfo.cpp Copyright (c) Ladislav Zezula 2013 */ /*---------------------------------------------------------------------------*/ -/* Description : */ +/* Description: */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ -/* xx.xx.99 1.00 Lad The first version of SFileReadFile.cpp */ -/* 24.03.99 1.00 Lad Added the SFileGetFileInfo function */ +/* 30.11.13 1.00 Lad The first version of SFileGetFileInfo.cpp */ /*****************************************************************************/ #define __STORMLIB_SELF__ @@ -14,8 +13,34 @@ #include "StormCommon.h" //----------------------------------------------------------------------------- +// Local defines + +// Information types for SFileGetFileInfo +#define SFILE_INFO_TYPE_UNKNOWN 0 +#define SFILE_INFO_TYPE_DIRECT_POINTER 1 +#define SFILE_INFO_TYPE_ALLOCATED 2 +#define SFILE_INFO_TYPE_READ_FROM_FILE 3 +#define SFILE_INFO_TYPE_TABLE_POINTER 4 +#define SFILE_INFO_TYPE_FILE_ENTRY 5 + +//----------------------------------------------------------------------------- // Local functions +static void ConvertFileEntryToSelfRelative(TFileEntry * pFileEntry, TFileEntry * pSrcFileEntry) +{ + // Copy the file entry itself + memcpy(pFileEntry, pSrcFileEntry, sizeof(TFileEntry)); + + // If source is NULL, leave it NULL + if(pSrcFileEntry->szFileName != NULL) + { + // Set the file name pointer after the file entry + pFileEntry->szFileName = (char *)(pFileEntry + 1); + strcpy(pFileEntry->szFileName, pSrcFileEntry->szFileName); + } +} + + static DWORD GetMpqFileCount(TMPQArchive * ha) { TFileEntry * pFileTableEnd; @@ -43,1245 +68,680 @@ static DWORD GetMpqFileCount(TMPQArchive * ha) return dwFileCount; } -static TCHAR * GetFilePatchChain(TMPQFile * hf, DWORD * pcbChainLength) +static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, DWORD * pcbLengthNeeded) { TMPQFile * hfTemp; - TCHAR * szPatchChain = NULL; - TCHAR * szPatchItem = NULL; - TCHAR * szFileName; + TCHAR * szFileInfo = (TCHAR *)pvFileInfo; size_t cchCharsNeeded = 1; + size_t cchFileInfo = (cbFileInfo / sizeof(TCHAR)); size_t nLength; // Patch chain is only supported on MPQ files. - if(hf->pStream == NULL) + if(hf->pStream != NULL) { - // Calculate the necessary length of the multi-string - for(hfTemp = hf; hfTemp != NULL; hfTemp->hfPatchFile) - cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - - // Allocate space for the multi-string - szPatchChain = szPatchItem = STORM_ALLOC(TCHAR, cchCharsNeeded); - if(szPatchChain != NULL) - { - // Fill-in all the names - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - { - szFileName = FileStream_GetFileName(hfTemp->ha->pStream); - nLength = _tcslen(szFileName) + 1; - - memcpy(szPatchItem, szFileName, nLength * sizeof(TCHAR)); - szPatchItem += nLength; - } - - // Terminate the multi-string - *szPatchItem++ = 0; - } - - // The length must match - assert((size_t)(szPatchItem - szPatchChain) == cchCharsNeeded); + SetLastError(ERROR_INVALID_PARAMETER); + return false; } - // Give the length of the patch chain, in bytes - if(pcbChainLength != NULL) - pcbChainLength[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - return szPatchChain; -} - -// hf - MPQ File handle. -// pbBuffer - Pointer to target buffer to store sectors. -// dwByteOffset - Position of sector in the file (relative to file begin) -// dwBytesToRead - Number of bytes to read. Must be multiplier of sector size. -// pdwBytesRead - Stored number of bytes loaded -static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG RawFilePos; - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbRawSector = NULL; - LPBYTE pbOutSector = pbBuffer; - LPBYTE pbInSector = pbBuffer; - DWORD dwRawBytesToRead; - DWORD dwRawSectorOffset = dwByteOffset; - DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize; - DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize; - DWORD dwSectorsDone = 0; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Note that dwByteOffset must be aligned to size of one sector - // Note that dwBytesToRead must be a multiplier of one sector size - // This is local function, so we won't check if that's true. - // Note that files stored in single units are processed by a separate function + // Calculate the necessary length of the multi-string + for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch) + cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - // If there is not enough bytes remaining, cut dwBytesToRead - if((dwByteOffset + dwBytesToRead) > hf->dwDataSize) - dwBytesToRead = hf->dwDataSize - dwByteOffset; - dwRawBytesToRead = dwBytesToRead; + // Give the caller the needed length + if(pcbLengthNeeded != NULL) + pcbLengthNeeded[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - // Perform all necessary work to do with compressed files - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) + // If the caller gave both buffer pointer and data length, + // try to copy the patch chain + if(szFileInfo != NULL && cchFileInfo != 0) { - // If the sector positions are not loaded yet, do it - if(hf->SectorOffsets == NULL) + // If there is enough space in the buffer, copy the patch chain + if(cchCharsNeeded > cchFileInfo) { - nError = AllocateSectorOffsets(hf, true); - if(nError != ERROR_SUCCESS) - return nError; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return false; } - // If the sector checksums are not loaded yet, load them now. - if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->bLoadedSectorCRCs == false) + // Copy each patch + for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch) { - // - // Sector CRCs is plain crap feature. It is almost never present, - // often it's empty, or the end offset of sector CRCs is zero. - // We only try to load sector CRCs once, and regardless if it fails - // or not, we won't try that again for the given file. - // - - AllocateSectorChecksums(hf, true); - hf->bLoadedSectorCRCs = true; + // Get the file name and its length + const TCHAR * szFileName = FileStream_GetFileName(hfTemp->ha->pStream); + nLength = _tcslen(szFileName) + 1; + + // Copy the file name + memcpy(szFileInfo, szFileName, nLength * sizeof(TCHAR)); + szFileInfo += nLength; } - // TODO: If the raw data MD5s are not loaded yet, load them now - // Only do it if the MPQ is of format 4.0 -// if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0) -// { -// nError = AllocateRawMD5s(hf, true); -// if(nError != ERROR_SUCCESS) -// return nError; -// } - - // If the file is compressed, also allocate secondary buffer - pbInSector = pbRawSector = STORM_ALLOC(BYTE, dwBytesToRead); - if(pbRawSector == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Assign the temporary buffer as target for read operation - dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex]; - dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset; + // Make it multi-string + szFileInfo[0] = 0; } - // Calculate raw file offset where the sector(s) are stored. - CalculateRawSectorOffset(RawFilePos, hf, dwRawSectorOffset); + return true; +} - // Set file pointer and read all required sectors - if(!FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead)) - return GetLastError(); - dwBytesRead = 0; +//----------------------------------------------------------------------------- +// Retrieves an information about an archive or about a file within the archive +// +// hMpqOrFile - Handle to an MPQ archive or to a file +// InfoClass - Information to obtain +// pvFileInfo - Pointer to buffer to store the information +// cbFileInfo - Size of the buffer pointed by pvFileInfo +// pcbLengthNeeded - Receives number of bytes necessary to store the information - // Now we have to decrypt and decompress all file sectors that have been loaded - for(DWORD i = 0; i < dwSectorsToRead; i++) - { - DWORD dwRawBytesInThisSector = ha->dwSectorSize; - DWORD dwBytesInThisSector = ha->dwSectorSize; - DWORD dwIndex = dwSectorIndex + i; - - // If there is not enough bytes in the last sector, - // cut the number of bytes in this sector - if(dwRawBytesInThisSector > dwBytesToRead) - dwRawBytesInThisSector = dwBytesToRead; - if(dwBytesInThisSector > dwBytesToRead) - dwBytesInThisSector = dwBytesToRead; - - // If the file is compressed, we have to adjust the raw sector size - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex]; - - // If the file is encrypted, we have to decrypt the sector - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); +bool WINAPI SFileGetFileInfo( + HANDLE hMpqOrFile, + SFileInfoClass InfoClass, + void * pvFileInfo, + DWORD cbFileInfo, + LPDWORD pcbLengthNeeded) +{ + MPQ_SIGNATURE_INFO SignatureInfo; + TMPQArchive * ha = NULL; + TFileEntry * pFileEntry = NULL; + ULONGLONG Int64Value = 0; + ULONGLONG ByteOffset = 0; + TMPQFile * hf = NULL; + void * pvSrcFileInfo = NULL; + DWORD cbSrcFileInfo = 0; + DWORD dwInt32Value = 0; + int nInfoType = SFILE_INFO_TYPE_UNKNOWN; + int nError = ERROR_INVALID_PARAMETER; - // If we don't know the key, try to detect it by file content - if(hf->dwFileKey == 0) + switch(InfoClass) + { + case SFileMpqFileName: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector); - if(hf->dwFileKey == 0) - { - nError = ERROR_UNKNOWN_FILE_KEY; - break; - } + pvSrcFileInfo = (void *)FileStream_GetFileName(ha->pStream); + cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } + break; - DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex); - BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); - } - - // If the file has sector CRC check turned on, perform it - if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL) - { - DWORD dwAdlerExpected = hf->SectorChksums[dwIndex]; - DWORD dwAdlerValue = 0; - - // We can only check sector CRC when it's not zero - // Neither can we check it if it's 0xFFFFFFFF. - if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF) + case SFileMpqUserDataOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && ha->pUserData != NULL) { - dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector); - if(dwAdlerValue != dwAdlerExpected) - { - nError = ERROR_CHECKSUM_ERROR; - break; - } + pvSrcFileInfo = &ha->UserDataPos; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - } - - // If the sector is really compressed, decompress it. - // WARNING : Some sectors may not be compressed, it can be determined only - // by comparing uncompressed and compressed size !!! - if(dwRawBytesInThisSector < dwBytesInThisSector) - { - int cbOutSector = dwBytesInThisSector; - int cbInSector = dwRawBytesInThisSector; - int nResult = 0; + break; - // Is the file compressed by Blizzard's multiple compression ? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) + case SFileMpqUserDataHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && ha->pUserData != NULL) { - if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) - nResult = SCompDecompress2(pbOutSector, &cbOutSector, pbInSector, cbInSector); - else - nResult = SCompDecompress(pbOutSector, &cbOutSector, pbInSector, cbInSector); + ByteOffset = ha->UserDataPos; + cbSrcFileInfo = sizeof(TMPQUserData); + nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; } + break; - // Is the file compressed by PKWARE Data Compression Library ? - else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) + case SFileMpqUserData: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && ha->pUserData != NULL) { - nResult = SCompExplode(pbOutSector, &cbOutSector, pbInSector, cbInSector); + ByteOffset = ha->UserDataPos + sizeof(TMPQUserData); + cbSrcFileInfo = ha->pUserData->dwHeaderOffs - sizeof(TMPQUserData); + nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; } + break; - // Did the decompression fail ? - if(nResult == 0) + case SFileMpqHeaderOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - nError = ERROR_FILE_CORRUPT; - break; + pvSrcFileInfo = &ha->MpqPos; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - } - else - { - if(pbOutSector != pbInSector) - memcpy(pbOutSector, pbInSector, dwBytesInThisSector); - } - - // Move pointers - dwBytesToRead -= dwBytesInThisSector; - dwByteOffset += dwBytesInThisSector; - dwBytesRead += dwBytesInThisSector; - pbOutSector += dwBytesInThisSector; - pbInSector += dwRawBytesInThisSector; - dwSectorsDone++; - } - - // Free all used buffers - if(pbRawSector != NULL) - STORM_FREE(pbRawSector); - - // Give the caller thenumber of bytes read - *pdwBytesRead = dwBytesRead; - return nError; -} - -static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG RawFilePos = hf->RawFilePos; - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbCompressed = NULL; - LPBYTE pbRawData = NULL; - int nError = ERROR_SUCCESS; - - // If the file buffer is not allocated yet, do it. - if(hf->pbFileSector == NULL) - { - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - pbRawData = hf->pbFileSector; - } - - // If the file is a patch file, adjust raw data offset - if(hf->pPatchInfo != NULL) - RawFilePos += hf->pPatchInfo->dwLength; - - // If the file sector is not loaded yet, do it - if(hf->dwSectorOffs != 0) - { - // Is the file compressed? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - // Allocate space for compressed data - pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - pbRawData = pbCompressed; - } - - // Load the raw (compressed, encrypted) data - if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) - { - STORM_FREE(pbCompressed); - return GetLastError(); - } - - // If the file is encrypted, we have to decrypt the data first - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); - DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); - BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); - } + break; - // If the file is compressed, we have to decompress it now - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - int cbOutBuffer = (int)hf->dwDataSize; - int cbInBuffer = (int)pFileEntry->dwCmpSize; - int nResult = 0; - - // - // If the file is an incremental patch, the size of compressed data - // is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo) - // - // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: - // - // File CmprSize DcmpSize DataSize Compressed? - // -------------------------------------- ---------- -------- -------- --------------- - // esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes - // deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No - // - - if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) - cbInBuffer = cbInBuffer - sizeof(TPatchInfo); - - // Is the file compressed by Blizzard's multiple compression ? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) + case SFileMpqHeaderSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) - nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); - else - nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); + pvSrcFileInfo = &ha->pHeader->dwHeaderSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } + break; - // Is the file compressed by PKWARE Data Compression Library ? - // Note: Single unit files compressed with IMPLODE are not supported by Blizzard - else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) - nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); - - nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; - } - else - { - if(pbRawData != hf->pbFileSector) - memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); - } - - // Free the decompression buffer. - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - - // The file sector is now properly loaded - hf->dwSectorOffs = 0; - } - - // At this moment, we have the file loaded into the file buffer. - // Copy as much as the caller wants - if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) - { - // File position is greater or equal to file size ? - if(dwFilePos >= hf->dwDataSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // If not enough bytes remaining in the file, cut them - if((hf->dwDataSize - dwFilePos) < dwToRead) - dwToRead = (hf->dwDataSize - dwFilePos); - - // Copy the bytes - memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); - - // Give the number of bytes read - *pdwBytesRead = dwToRead; - return ERROR_SUCCESS; - } - - // An error, sorry - return ERROR_CAN_NOT_COMPLETE; -} - -static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C) - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbCompressed = NULL; - LPBYTE pbRawData = hf->pbFileSector; - int nError = ERROR_SUCCESS; - - // We do not support patch files in MPK archives - assert(hf->pPatchInfo == NULL); - - // If the file buffer is not allocated yet, do it. - if(hf->pbFileSector == NULL) - { - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - - // Is the file compressed? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - // Allocate space for compressed data - pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - pbRawData = pbCompressed; - } - - // Load the raw (compressed, encrypted) data - if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) - { - STORM_FREE(pbCompressed); - return GetLastError(); - } - - // If the file is encrypted, we have to decrypt the data first - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - DecryptMpkTable(pbRawData, pFileEntry->dwCmpSize); - } - - // If the file is compressed, we have to decompress it now - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - int cbOutBuffer = (int)hf->dwDataSize; - - if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, pbRawData, (int)pFileEntry->dwCmpSize)) - nError = ERROR_FILE_CORRUPT; - } - else - { - if(pbRawData != hf->pbFileSector) - memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); - } - - // Free the decompression buffer. - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - - // The file sector is now properly loaded - hf->dwSectorOffs = 0; - } - - // At this moment, we have the file loaded into the file buffer. - // Copy as much as the caller wants - if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) - { - // File position is greater or equal to file size ? - if(dwFilePos >= hf->dwDataSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // If not enough bytes remaining in the file, cut them - if((hf->dwDataSize - dwFilePos) < dwToRead) - dwToRead = (hf->dwDataSize - dwFilePos); - - // Copy the bytes - memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); - - // Give the number of bytes read - *pdwBytesRead = dwToRead; - return ERROR_SUCCESS; - } - - // An error, sorry - return ERROR_CAN_NOT_COMPLETE; -} - - -static int ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead) -{ - TMPQArchive * ha = hf->ha; - LPBYTE pbBuffer = (BYTE *)pvBuffer; - DWORD dwTotalBytesRead = 0; // Total bytes read in all three parts - DWORD dwSectorSizeMask = ha->dwSectorSize - 1; // Mask for block size, usually 0x0FFF - DWORD dwFileSectorPos; // File offset of the loaded sector - DWORD dwBytesRead; // Number of bytes read (temporary variable) - int nError; - - // If the file position is at or beyond end of file, do nothing - if(dwFilePos >= hf->dwDataSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // If not enough bytes in the file remaining, cut them - if(dwBytesToRead > (hf->dwDataSize - dwFilePos)) - dwBytesToRead = (hf->dwDataSize - dwFilePos); - - // Compute sector position in the file - dwFileSectorPos = dwFilePos & ~dwSectorSizeMask; // Position in the block - - // If the file sector buffer is not allocated yet, do it now - if(hf->pbFileSector == NULL) - { - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - } - - // Load the first (incomplete) file sector - if(dwFilePos & dwSectorSizeMask) - { - DWORD dwBytesInSector = ha->dwSectorSize; - DWORD dwBufferOffs = dwFilePos & dwSectorSizeMask; - DWORD dwToCopy; - - // Is the file sector already loaded ? - if(hf->dwSectorOffs != dwFileSectorPos) - { - // Load one MPQ sector into archive buffer - nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesInSector); - if(nError != ERROR_SUCCESS) - return nError; - - // Remember that the data loaded to the sector have new file offset - hf->dwSectorOffs = dwFileSectorPos; - } - else - { - if((dwFileSectorPos + dwBytesInSector) > hf->dwDataSize) - dwBytesInSector = hf->dwDataSize - dwFileSectorPos; - } - - // Copy the data from the offset in the loaded sector to the end of the sector - dwToCopy = dwBytesInSector - dwBufferOffs; - if(dwToCopy > dwBytesToRead) - dwToCopy = dwBytesToRead; - - // Copy data from sector buffer into target buffer - memcpy(pbBuffer, hf->pbFileSector + dwBufferOffs, dwToCopy); - - // Update pointers and byte counts - dwTotalBytesRead += dwToCopy; - dwFileSectorPos += dwBytesInSector; - pbBuffer += dwToCopy; - dwBytesToRead -= dwToCopy; - } - - // Load the whole ("middle") sectors only if there is at least one full sector to be read - if(dwBytesToRead >= ha->dwSectorSize) - { - DWORD dwBlockBytes = dwBytesToRead & ~dwSectorSizeMask; - - // Load all sectors to the output buffer - nError = ReadMpqSectors(hf, pbBuffer, dwFileSectorPos, dwBlockBytes, &dwBytesRead); - if(nError != ERROR_SUCCESS) - return nError; - - // Update pointers - dwTotalBytesRead += dwBytesRead; - dwFileSectorPos += dwBytesRead; - pbBuffer += dwBytesRead; - dwBytesToRead -= dwBytesRead; - } - - // Read the terminating sector - if(dwBytesToRead > 0) - { - DWORD dwToCopy = ha->dwSectorSize; - - // Is the file sector already loaded ? - if(hf->dwSectorOffs != dwFileSectorPos) - { - // Load one MPQ sector into archive buffer - nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesRead); - if(nError != ERROR_SUCCESS) - return nError; - - // Remember that the data loaded to the sector have new file offset - hf->dwSectorOffs = dwFileSectorPos; - } - - // Check number of bytes read - if(dwToCopy > dwBytesToRead) - dwToCopy = dwBytesToRead; - - // Copy the data from the cached last sector to the caller's buffer - memcpy(pbBuffer, hf->pbFileSector, dwToCopy); - - // Update pointers - dwTotalBytesRead += dwToCopy; - } - - // Store total number of bytes read to the caller - *pdwBytesRead = dwTotalBytesRead; - return ERROR_SUCCESS; -} - -static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - DWORD dwBytesToRead = dwToRead; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Make sure that the patch file is loaded completely - if(hf->pbFileData == NULL) - { - // Load the original file and store its content to "pbOldData" - hf->pbFileData = STORM_ALLOC(BYTE, hf->pFileEntry->dwFileSize); - hf->cbFileData = hf->pFileEntry->dwFileSize; - if(hf->pbFileData == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Read the file data - if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) - nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); - else - nError = ReadMpqFileSectorFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); - - // Fix error code - if(nError == ERROR_SUCCESS && dwBytesRead != hf->cbFileData) - nError = ERROR_FILE_CORRUPT; - - // Patch the file data - if(nError == ERROR_SUCCESS) - nError = PatchFileData(hf); - - // Reset number of bytes read to zero - dwBytesRead = 0; - } - - // If there is something to read, do it - if(nError == ERROR_SUCCESS) - { - if(dwFilePos < hf->cbFileData) - { - // Make sure we don't copy more than file size - if((dwFilePos + dwToRead) > hf->cbFileData) - dwToRead = hf->cbFileData - dwFilePos; - - // Copy the appropriate amount of the file data to the caller's buffer - memcpy(pvBuffer, hf->pbFileData + dwFilePos, dwToRead); - dwBytesRead = dwToRead; - } - - // Set the proper error code - nError = (dwBytesRead == dwBytesToRead) ? ERROR_SUCCESS : ERROR_HANDLE_EOF; - } - - // Give the result to the caller - if(pdwBytesRead != NULL) - *pdwBytesRead = dwBytesRead; - return nError; -} - -static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG FilePosition1 = dwFilePos; - ULONGLONG FilePosition2; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - assert(hf->pStream != NULL); - - // Because stream I/O functions are designed to read - // "all or nothing", we compare file position before and after, - // and if they differ, we assume that number of bytes read - // is the difference between them - - if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead)) - { - // If not all bytes have been read, then return the number of bytes read - if((nError = GetLastError()) == ERROR_HANDLE_EOF) - { - FileStream_GetPos(hf->pStream, &FilePosition2); - dwBytesRead = (DWORD)(FilePosition2 - FilePosition1); - } - } - else - { - dwBytesRead = dwToRead; - } - - *pdwBytesRead = dwBytesRead; - return nError; -} - -//----------------------------------------------------------------------------- -// SFileReadFile - -bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped) -{ - TMPQFile * hf = (TMPQFile *)hFile; - DWORD dwBytesRead = 0; // Number of bytes read - int nError = ERROR_SUCCESS; - - // Keep compilers happy - lpOverlapped = lpOverlapped; - - // Check valid parameters - if(!IsValidFileHandle(hFile)) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - if(pvBuffer == NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // If the file is local file, read the data directly from the stream - if(hf->pStream != NULL) - { - nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // If the file is a patch file, we have to read it special way - else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - { - nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // If the archive is a MPK archive, we need special way to read the file - else if(hf->ha->dwSubType == MPQ_SUBTYPE_MPK) - { - nError = ReadMpkFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // If the file is single unit file, redirect it to read file - else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) - { - nError = ReadMpqFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // Otherwise read it as sector based MPQ file - else - { - nError = ReadMpqFileSectorFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // Increment the file position - hf->dwFilePos += dwBytesRead; - - // Give the caller the number of bytes read - if(pdwRead != NULL) - *pdwRead = dwBytesRead; - - // If the read operation succeeded, but not full number of bytes was read, - // set the last error to ERROR_HANDLE_EOF - if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead)) - nError = ERROR_HANDLE_EOF; - - // If something failed, set the last error value - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// SFileGetFileSize - -DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) -{ - ULONGLONG FileSize; - TMPQFile * hf = (TMPQFile *)hFile; - - // Validate the file handle before we go on - if(IsValidFileHandle(hFile)) - { - // Make sure that the variable is initialized - FileSize = 0; - - // If the file is patched file, we have to get the size of the last version - if(hf->hfPatchFile != NULL) - { - // Walk through the entire patch chain, take the last version - while(hf != NULL) + case SFileMpqHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - // Get the size of the currently pointed version - FileSize = hf->pFileEntry->dwFileSize; - - // Move to the next patch file in the hierarchy - hf = hf->hfPatchFile; + ByteOffset = ha->MpqPos; + cbSrcFileInfo = ha->pHeader->dwHeaderSize; + nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; } - } - else - { - // Is it a local file ? - if(hf->pStream != NULL) + break; + + case SFileMpqHetTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileStream_GetSize(hf->pStream, &FileSize); + pvSrcFileInfo = &ha->pHeader->HetTablePos64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - else + break; + + case SFileMpqHetTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileSize = hf->dwDataSize; + pvSrcFileInfo = &ha->pHeader->HetTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - } - - // If opened from archive, return file size - if(pdwFileSizeHigh != NULL) - *pdwFileSizeHigh = (DWORD)(FileSize >> 32); - return (DWORD)FileSize; - } - - SetLastError(ERROR_INVALID_HANDLE); - return SFILE_INVALID_SIZE; -} - -DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) -{ - TMPQFile * hf = (TMPQFile *)hFile; - ULONGLONG FilePosition; - ULONGLONG MoveOffset; - DWORD dwFilePosHi; - - // If the hFile is not a valid file handle, return an error. - if(!IsValidFileHandle(hFile)) - { - SetLastError(ERROR_INVALID_HANDLE); - return SFILE_INVALID_POS; - } - - // Get the relative point where to move from - switch(dwMoveMethod) - { - case FILE_BEGIN: - FilePosition = 0; break; - case FILE_CURRENT: - if(hf->pStream != NULL) + case SFileMpqHetHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileStream_GetPos(hf->pStream, &FilePosition); + pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->HetTablePos64, (size_t)ha->pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE); + cbSrcFileInfo = sizeof(TMPQHetHeader); + nInfoType = SFILE_INFO_TYPE_ALLOCATED; } - else + break; + + case SFileMpqHetTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FilePosition = hf->dwFilePos; + pvSrcFileInfo = LoadHetTable(ha); + cbSrcFileInfo = sizeof(void *); + nInfoType = SFILE_INFO_TYPE_TABLE_POINTER; } break; - case FILE_END: - if(hf->pStream != NULL) + case SFileMpqBetTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileStream_GetSize(hf->pStream, &FilePosition); + pvSrcFileInfo = &ha->pHeader->BetTablePos64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - else + break; + + case SFileMpqBetTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FilePosition = SFileGetFileSize(hFile, NULL); + pvSrcFileInfo = &ha->pHeader->BetTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - default: - SetLastError(ERROR_INVALID_PARAMETER); - return SFILE_INVALID_POS; - } - - // Now get the move offset. Note that both values form - // a signed 64-bit value (a file pointer can be moved backwards) - if(plFilePosHigh != NULL) - dwFilePosHi = *plFilePosHigh; - else - dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0; - MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos); - - // Now calculate the new file pointer - // Do not allow the file pointer to go before the begin of the file - FilePosition += MoveOffset; - if(FilePosition < 0) - FilePosition = 0; - - // Now apply the file pointer to the file - if(hf->pStream != NULL) - { - // Apply the new file position - if(!FileStream_Read(hf->pStream, &FilePosition, NULL, 0)) - return SFILE_INVALID_POS; - - // Return the new file position - if(plFilePosHigh != NULL) - *plFilePosHigh = (LONG)(FilePosition >> 32); - return (DWORD)FilePosition; - } - else - { - // Files in MPQ can't be bigger than 4 GB. - // We don't allow to go past 4 GB - if(FilePosition >> 32) - { - SetLastError(ERROR_INVALID_PARAMETER); - return SFILE_INVALID_POS; - } - - // Change the file position - hf->dwFilePos = (DWORD)FilePosition; - - // Return the new file position - if(plFilePosHigh != NULL) - *plFilePosHigh = 0; - return (DWORD)FilePosition; - } -} - -//----------------------------------------------------------------------------- -// Tries to retrieve the file name + case SFileMpqBetHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->BetTablePos64, (size_t)ha->pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE); + if(pvSrcFileInfo != NULL) + { + // It is allowed for the caller to only require BET header. + cbSrcFileInfo = sizeof(TMPQBetHeader) + ((TMPQBetHeader *)pvSrcFileInfo)->dwFlagCount * sizeof(DWORD); + if(cbFileInfo == sizeof(TMPQBetHeader)) + cbSrcFileInfo = sizeof(TMPQBetHeader); + nInfoType = SFILE_INFO_TYPE_ALLOCATED; + } + } + break; -struct TFileHeader2Ext -{ - DWORD dwOffset00Data; // Required data at offset 00 (32-bits) - DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored - DWORD dwOffset04Data; // Required data at offset 04 (32-bits) - DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored - const char * szExt; // Supplied extension, if the condition is true -}; + case SFileMpqBetTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = LoadBetTable(ha); + cbSrcFileInfo = sizeof(void *); + nInfoType = SFILE_INFO_TYPE_TABLE_POINTER; + } + break; -static TFileHeader2Ext data2ext[] = -{ - {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files - {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files - {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') - {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' - {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' - {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) - {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I - {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II - {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' - {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM - {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files - {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures - {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) - {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files - {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps - {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' - {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 - {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc - {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders - {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image - {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension - {0, 0, 0, 0, NULL} // Terminator -}; + case SFileMpqHashTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + Int64Value = MAKE_OFFSET64(ha->pHeader->wHashTablePosHi, ha->pHeader->dwHashTablePos); + pvSrcFileInfo = &Int64Value; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; -static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file - DWORD dwBytesRead = 0; - DWORD dwFilePos; // Saved file position + case SFileMpqHashTableSize64: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->HashTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Read the first 2 DWORDs bytes from the file - dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); - SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); - SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); + case SFileMpqHashTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwHashTableSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // If we read at least 8 bytes - if(dwBytesRead == sizeof(FirstBytes)) - { - // Make sure that the array is properly BSWAP-ed - BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); + case SFileMpqHashTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); + pvSrcFileInfo = ha->pHashTable; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Try to guess file extension from those 2 DWORDs - for(size_t i = 0; data2ext[i].szExt != NULL; i++) - { - if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && - (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) + case SFileMpqBlockTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - char szPseudoName[20] = ""; + Int64Value = MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos); + pvSrcFileInfo = &Int64Value; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Format the pseudo-name - sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); + case SFileMpqBlockTableSize64: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->BlockTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Save the pseudo-name in the file entry as well - AllocateFileName(hf->ha, pFileEntry, szPseudoName); + case SFileMpqBlockTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // If the caller wants to copy the file name, do it - if(szFileName != NULL) - strcpy(szFileName, szPseudoName); - return ERROR_SUCCESS; + case SFileMpqBlockTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + cbSrcFileInfo = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); + if(cbFileInfo >= cbSrcFileInfo) + pvSrcFileInfo = LoadBlockTable(ha, true); + nInfoType = SFILE_INFO_TYPE_ALLOCATED; } - } - } + break; - return ERROR_NOT_SUPPORTED; -} + case SFileMpqHiBlockTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->HiBlockTablePos64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; -bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - TCHAR * szFileNameT; - int nError = ERROR_INVALID_HANDLE; + case SFileMpqHiBlockTableSize64: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->HiBlockTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Pre-zero the output buffer - if(szFileName != NULL) - *szFileName = 0; + case SFileMpqHiBlockTable: + assert(false); + break; - // Check valid parameters - if(IsValidFileHandle(hFile)) - { - TFileEntry * pFileEntry = hf->pFileEntry; + case SFileMpqSignatures: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) + { + pvSrcFileInfo = &SignatureInfo.SignatureTypes; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // For MPQ files, retrieve the file name from the file entry - if(hf->pStream == NULL) - { - if(pFileEntry != NULL) + case SFileMpqStrongSignatureOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) { - // If the file name is not there yet, create a pseudo name - if(pFileEntry->szFileName == NULL) + // Is a strong signature present? + if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG) { - nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); + pvSrcFileInfo = &SignatureInfo.EndMpqData; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - else + } + break; + + case SFileMpqStrongSignatureSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) + { + // Is a strong signature present? + if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG) { - if(szFileName != NULL) - strcpy(szFileName, pFileEntry->szFileName); - nError = ERROR_SUCCESS; + dwInt32Value = MPQ_STRONG_SIGNATURE_SIZE + 4; + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } } - } + break; - // For local files, copy the file name from the stream - else - { - if(szFileName != NULL) + case SFileMpqStrongSignature: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) { - szFileNameT = FileStream_GetFileName(hf->pStream); - CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT)); + // Is a strong signature present? + if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG) + { + pvSrcFileInfo = SignatureInfo.Signature; + cbSrcFileInfo = MPQ_STRONG_SIGNATURE_SIZE + 4; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } } - nError = ERROR_SUCCESS; - } - } - - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Retrieves an information about an archive or about a file within the archive -// -// hMpqOrFile - Handle to an MPQ archive or to a file -// dwInfoType - Information to obtain - -bool WINAPI SFileGetFileInfo( - HANDLE hMpqOrFile, - DWORD dwInfoType, - void * pvFileInfo, - DWORD cbFileInfo, - LPDWORD pcbLengthNeeded) -{ - TMPQArchive * ha = NULL; - TMPQBlock * pBlockTable = NULL; - ULONGLONG Int64Value = 0; - TMPQFile * hf = NULL; - TCHAR * szPatchChain = NULL; - void * pvSrcFileInfo = NULL; - DWORD cbSrcFileInfo = 0; - DWORD dwInt32Value = 0; - int nError = ERROR_INVALID_PARAMETER; + break; - switch(dwInfoType) - { - case SFILE_INFO_ARCHIVE_NAME: + case SFileMpqBitmapOffset: ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) + if(ha != NULL && ha->pBitmap != NULL) { - pvSrcFileInfo = FileStream_GetFileName(ha->pStream); - cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); + Int64Value = MAKE_OFFSET64(ha->pBitmap->dwMapOffsetHi, ha->pBitmap->dwMapOffsetLo); + pvSrcFileInfo = &Int64Value; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive + case SFileMpqBitmapSize: ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) + if(ha != NULL && ha->pBitmap != NULL) { - pvSrcFileInfo = &ha->pHeader->dwArchiveSize; + pvSrcFileInfo = &ha->dwBitmapSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ + case SFileMpqBitmap: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->dwMaxFileCount; - cbSrcFileInfo = sizeof(DWORD); + pvSrcFileInfo = ha->pBitmap; + cbSrcFileInfo = ha->dwBitmapSize; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table + case SFileMpqArchiveSize64: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->pHeader->dwHashTableSize; + pvSrcFileInfo = &ha->pHeader->ArchiveSize64; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table + case SFileMpqArchiveSize: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; + pvSrcFileInfo = &ha->pHeader->dwArchiveSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_SECTOR_SIZE: + case SFileMpqMaxFileCount: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->dwSectorSize; + pvSrcFileInfo = &ha->dwMaxFileCount; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_HASH_TABLE: + case SFileMpqFileTableSize: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = ha->pHashTable; - cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); + pvSrcFileInfo = &ha->dwFileTableSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_BLOCK_TABLE: + case SFileMpqSectorSize: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = pBlockTable = TranslateBlockTable(ha, &Int64Value, NULL); - cbSrcFileInfo = (DWORD)(Int64Value / sizeof(TMPQBlock)); + pvSrcFileInfo = &ha->dwSectorSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_NUM_FILES: + case SFileMpqNumberOfFiles: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); dwInt32Value = GetMpqFileCount(ha); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileMpqRawChunkSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwRawChunkSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_STREAM_FLAGS: + case SFileMpqStreamFlags: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { FileStream_GetFlags(ha->pStream, &dwInt32Value); pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_IS_READ_ONLY: + case SFileMpqIsReadOnly: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { + dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_HASH_INDEX: + case SFileInfoPatchChain: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + return GetFilePatchChain(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded); + break; + + case SFileInfoFileEntry: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->pFileEntry != NULL) + { + pvSrcFileInfo = pFileEntry = hf->pFileEntry; + cbSrcFileInfo = sizeof(TFileEntry); + if(pFileEntry->szFileName != NULL) + cbSrcFileInfo += (DWORD)strlen(pFileEntry->szFileName) + 1; + nInfoType = SFILE_INFO_TYPE_FILE_ENTRY; + } + break; + + case SFileInfoHashEntry: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) + { + pvSrcFileInfo = hf->ha->pHashTable + hf->pFileEntry->dwHashIndex; + cbSrcFileInfo = sizeof(TMPQHash); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileInfoHashIndex: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_CODENAME1: + case SFileInfoNameHash1: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) { pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_CODENAME2: + case SFileInfoNameHash2: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) { pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileInfoNameHash3: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->pFileEntry != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->FileNameHash; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_LOCALEID: + case SFileInfoLocale: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { + dwInt32Value = hf->pFileEntry->lcLocale; pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = hf->pFileEntry->lcLocale; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_BLOCKINDEX: + case SFileInfoFileIndex: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL && hf->ha != NULL) { + dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileInfoByteOffset: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->pFileEntry != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->ByteOffset; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_FILE_SIZE: + case SFileInfoFileTime: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->dwFileSize; - cbSrcFileInfo = sizeof(DWORD); + pvSrcFileInfo = &hf->pFileEntry->FileTime; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_COMPRESSED_SIZE: + case SFileInfoFileSize: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; + pvSrcFileInfo = &hf->pFileEntry->dwFileSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_FLAGS: + case SFileInfoCompressedSize: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->dwFlags; + pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_POSITION: + case SFileInfoFlags: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->ByteOffset; - cbSrcFileInfo = sizeof(ULONGLONG); + pvSrcFileInfo = &hf->pFileEntry->dwFlags; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_KEY: + case SFileInfoEncryptionKey: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { pvSrcFileInfo = &hf->dwFileKey; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_KEY_UNFIXED: + case SFileInfoEncryptionKeyRaw: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { @@ -1290,52 +750,222 @@ bool WINAPI SFileGetFileInfo( dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos; pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_FILETIME: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->FileTime; - cbSrcFileInfo = sizeof(ULONGLONG); - } - break; - - case SFILE_INFO_PATCH_CHAIN: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL) - pvSrcFileInfo = szPatchChain = GetFilePatchChain(hf, &cbSrcFileInfo); - break; + default: // Invalid info class + SetLastError(ERROR_INVALID_PARAMETER); + return false; } - // Check if one of the cases case yielded a result - if(pvSrcFileInfo != NULL && pvFileInfo != NULL) + // If we validated the handle and info class, give as much info as possible + if(nInfoType != SFILE_INFO_TYPE_UNKNOWN) { - // Give the length needed + // Give the length needed, if wanted if(pcbLengthNeeded != NULL) pcbLengthNeeded[0] = cbSrcFileInfo; - // Verify if we have enough space in the output buffer - if(cbSrcFileInfo <= cbFileInfo) + // If the caller entered an output buffer, the output size must also be entered + if(pvFileInfo != NULL && cbFileInfo != 0) { - memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo); - nError = ERROR_SUCCESS; + // Check if there is enough space in the output buffer + if(cbSrcFileInfo <= cbFileInfo) + { + switch(nInfoType) + { + case SFILE_INFO_TYPE_DIRECT_POINTER: + case SFILE_INFO_TYPE_ALLOCATED: + memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo); + nError = ERROR_SUCCESS; + break; + + case SFILE_INFO_TYPE_READ_FROM_FILE: + if(FileStream_Read(ha->pStream, &ByteOffset, pvFileInfo, cbSrcFileInfo)) + nError = ERROR_SUCCESS; + break; + + case SFILE_INFO_TYPE_TABLE_POINTER: + *(void **)pvFileInfo = pvSrcFileInfo; + pvSrcFileInfo = NULL; + nError = ERROR_SUCCESS; + break; + + case SFILE_INFO_TYPE_FILE_ENTRY: + assert(pFileEntry != NULL); + ConvertFileEntryToSelfRelative((TFileEntry *)pvFileInfo, pFileEntry); + nError = ERROR_SUCCESS; + break; + } + } + else + { + nError = ERROR_INSUFFICIENT_BUFFER; + } } else { - nError = ERROR_INSUFFICIENT_BUFFER; + nError = ERROR_SUCCESS; } - } - // Free the allocated buffers, if any - if(szPatchChain != NULL) - STORM_FREE(szPatchChain); - if(pBlockTable != NULL) - STORM_FREE(pBlockTable); + // Free the file info if needed + if(nInfoType == SFILE_INFO_TYPE_ALLOCATED && pvSrcFileInfo != NULL) + STORM_FREE(pvSrcFileInfo); + if(nInfoType == SFILE_INFO_TYPE_TABLE_POINTER && pvSrcFileInfo != NULL) + SFileFreeFileInfo(pvSrcFileInfo, InfoClass); + } // Set the last error value, if needed if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); } + +bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass) +{ + switch(InfoClass) + { + case SFileMpqHetTable: + FreeHetTable((TMPQHetTable *)pvFileInfo); + return true; + + case SFileMpqBetTable: + FreeBetTable((TMPQBetTable *)pvFileInfo); + return true; + } + + SetLastError(ERROR_INVALID_PARAMETER); + return false; +} + +//----------------------------------------------------------------------------- +// Tries to retrieve the file name + +struct TFileHeader2Ext +{ + DWORD dwOffset00Data; // Required data at offset 00 (32-bits) + DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored + DWORD dwOffset04Data; // Required data at offset 04 (32-bits) + DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored + const char * szExt; // Supplied extension, if the condition is true +}; + +static TFileHeader2Ext data2ext[] = +{ + {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files + {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files + {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') + {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' + {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' + {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) + {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I + {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II + {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' + {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM + {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files + {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures + {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) + {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files + {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps + {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' + {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 + {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc + {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders + {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image + {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension + {0, 0, 0, 0, NULL} // Terminator +}; + +static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) +{ + TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle + DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file + DWORD dwBytesRead = 0; + DWORD dwFilePos; // Saved file position + + // Read the first 2 DWORDs bytes from the file + dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); + SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); + SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); + + // If we read at least 8 bytes + if(dwBytesRead == sizeof(FirstBytes)) + { + // Make sure that the array is properly BSWAP-ed + BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); + + // Try to guess file extension from those 2 DWORDs + for(size_t i = 0; data2ext[i].szExt != NULL; i++) + { + if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && + (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) + { + char szPseudoName[20] = ""; + + // Format the pseudo-name + sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); + + // Save the pseudo-name in the file entry as well + AllocateFileName(hf->ha, pFileEntry, szPseudoName); + + // If the caller wants to copy the file name, do it + if(szFileName != NULL) + strcpy(szFileName, szPseudoName); + return ERROR_SUCCESS; + } + } + } + + return ERROR_NOT_SUPPORTED; +} + +bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) +{ + TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle + int nError = ERROR_INVALID_HANDLE; + + // Pre-zero the output buffer + if(szFileName != NULL) + *szFileName = 0; + + // Check valid parameters + if(IsValidFileHandle(hFile)) + { + TFileEntry * pFileEntry = hf->pFileEntry; + + // For MPQ files, retrieve the file name from the file entry + if(hf->pStream == NULL) + { + if(pFileEntry != NULL) + { + // If the file name is not there yet, create a pseudo name + if(pFileEntry->szFileName == NULL) + { + nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); + } + else + { + if(szFileName != NULL) + strcpy(szFileName, pFileEntry->szFileName); + nError = ERROR_SUCCESS; + } + } + } + + // For local files, copy the file name from the stream + else + { + if(szFileName != NULL) + { + const TCHAR * szStreamName = FileStream_GetFileName(hf->pStream); + CopyFileName(szFileName, szStreamName, _tcslen(szStreamName)); + } + nError = ERROR_SUCCESS; + } + } + + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return (nError == ERROR_SUCCESS); +} + diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp index 2044125..f98c92b 100644 --- a/src/SFileListFile.cpp +++ b/src/SFileListFile.cpp @@ -35,6 +35,18 @@ struct TListFileCache //----------------------------------------------------------------------------- // Local functions (cache) +static char * CopyListLine(char * szListLine, const char * szFileName) +{ + // Copy the string + while(szFileName[0] != 0) + *szListLine++ = *szFileName++; + + // Append the end-of-line + *szListLine++ = 0x0D; + *szListLine++ = 0x0A; + return szListLine; +} + static bool FreeListFileCache(TListFileCache * pCache) { // Valid parameter check @@ -216,19 +228,91 @@ static int CompareFileNodes(const void * p1, const void * p2) return _stricmp(szFileName1, szFileName2); } -static int WriteListFileLine( - TMPQFile * hf, - const char * szLine) +static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile) { - char szNewLine[2] = {0x0D, 0x0A}; - size_t nLength = strlen(szLine); - int nError; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pFileEntry; + char ** SortTable = NULL; + char * szListFile = NULL; + char * szListLine; + size_t nFileNodes = 0; + size_t cbListFile = 0; + size_t nIndex0; + size_t nIndex1; - nError = SFileAddFile_Write(hf, szLine, (DWORD)nLength, MPQ_COMPRESSION_ZLIB); - if(nError != ERROR_SUCCESS) - return nError; + // Allocate the table for sorting listfile + SortTable = STORM_ALLOC(char*, ha->dwFileTableSize); + if(SortTable == NULL) + return NULL; + + // Construct the sort table + // Note: in MPQs with multiple locale versions of the same file, + // this code causes adding multiple listfile entries. + // They will get removed after the listfile sorting + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + // Only take existing items + if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) + { + // Ignore pseudo-names and internal names + if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName)) + { + SortTable[nFileNodes++] = pFileEntry->szFileName; + } + } + } + + // Remove duplicities + if(nFileNodes > 0) + { + // Sort the table + qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes); + + // Count the 0-th item + cbListFile += strlen(SortTable[0]) + 2; + + // Walk through the items and only use the ones that are not duplicated + for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++) + { + // If the next file node is different, we will include it to the result listfile + if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0) + { + cbListFile += strlen(SortTable[nIndex1]) + 2; + nIndex0 = nIndex1; + } + } + + // Now allocate buffer for the entire listfile + szListFile = szListLine = STORM_ALLOC(char, cbListFile + 1); + if(szListFile != NULL) + { + // Copy the 0-th item + szListLine = CopyListLine(szListLine, SortTable[0]); + + // Walk through the items and only use the ones that are not duplicated + for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++) + { + // If the next file node is different, we will include it to the result listfile + if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0) + { + // Copy the listfile line + szListLine = CopyListLine(szListLine, SortTable[nIndex1]); + nIndex0 = nIndex1; + } + } + + // Sanity check - does the size match? + assert((size_t)(szListLine - szListFile) == cbListFile); + } + } + + // Free the sort table + STORM_FREE(SortTable); - return SFileAddFile_Write(hf, szNewLine, sizeof(szNewLine), MPQ_COMPRESSION_ZLIB); + // Give away the listfile + if(pcbListFile != NULL) + *pcbListFile = (DWORD)cbListFile; + return (LPBYTE)szListFile; } //----------------------------------------------------------------------------- @@ -252,7 +336,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil if(pFileEntry != NULL) { // Allocate file name for the file entry - AllocateFileName(pFileEntry, szFileName); + AllocateFileName(ha, pFileEntry, szFileName); bNameEntryCreated = true; } @@ -272,7 +356,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { // Allocate file name for the file entry - AllocateFileName(ha->pFileTable + pHash->dwBlockIndex, szFileName); + AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName); bNameEntryCreated = true; } @@ -284,123 +368,65 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil return ERROR_CAN_NOT_COMPLETE; } -// Saves the whole listfile into the MPQ. +// Saves the whole listfile to the MPQ int SListFileSaveToMpq(TMPQArchive * ha) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; TMPQFile * hf = NULL; - char * szPrevItem; - char ** SortTable = NULL; - DWORD dwFileSize = 0; - size_t nFileNodes = 0; - size_t i; + LPBYTE pbListFile; + DWORD cbListFile = 0; int nError = ERROR_SUCCESS; - // Allocate the table for sorting listfile - SortTable = STORM_ALLOC(char*, ha->dwFileTableSize); - if(SortTable == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + // Only save (listfile) if we should do so + if(ha->dwFileFlags1 == 0 || ha->dwMaxFileCount == 0) + return ERROR_SUCCESS; - // Construct the sort table - // Note: in MPQs with multiple locale versions of the same file, - // this code causes adding multiple listfile entries. - // Since those MPQs were last time used in Starcraft, - // we leave it as it is. - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Only take existing items - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) - { - // Ignore pseudo-names - if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName)) - { - SortTable[nFileNodes++] = pFileEntry->szFileName; - } - } - } + // At this point, we expect to have at least one reserved entry in the file table + assert(ha->dwReservedFiles >= 1); + + // Create the raw data that is to be written to (listfile) + // Note: Creating the raw data before the (listfile) has been created in the MPQ + // causes that the name of the listfile will not be included in the listfile itself. + // That is OK, because (listfile) in Blizzard MPQs does not contain it either. + pbListFile = CreateListFile(ha, &cbListFile); - // Sort the table - qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes); + // Now we decrement the number of reserved files. + // This frees one slot in the file table, so the subsequent file create operation should succeed + // This must happen even if the listfile cannot be created + ha->dwReservedFiles--; - // Now parse the table of file names again - remove duplicates - // and count file size. - if(nFileNodes != 0) + // If the listfile create succeeded, we write it to the MPQ + if(pbListFile != NULL) { - // Count the 0-th item - dwFileSize += (DWORD)strlen(SortTable[0]) + 2; - szPrevItem = SortTable[0]; - - // Count all next items - for(i = 1; i < nFileNodes; i++) - { - // If the item is different from the previous one, include its size to the file size - if(_stricmp(SortTable[i], szPrevItem)) - { - dwFileSize += (DWORD)strlen(SortTable[i]) + 2; - szPrevItem = SortTable[i]; - } - } + // We expect it to be nonzero size + assert(cbListFile != 0); - // Determine the flags for (listfile) - if(ha->dwFileFlags1 == 0) - ha->dwFileFlags1 = GetDefaultSpecialFileFlags(ha, dwFileSize); + // Determine the real flags for (listfile) + if(ha->dwFileFlags1 == MPQ_FILE_EXISTS) + ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion); // Create the listfile in the MPQ nError = SFileAddFile_Init(ha, LISTFILE_NAME, 0, - dwFileSize, + cbListFile, LANG_NEUTRAL, ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING, &hf); - // Add all file names + + // Write the listfile raw data to it if(nError == ERROR_SUCCESS) { - // Each name is followed by newline ("\x0D\x0A") - szPrevItem = SortTable[0]; - nError = WriteListFileLine(hf, SortTable[0]); + // Write the content of the listfile to the MPQ + nError = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB); + SFileAddFile_Finish(hf); - // Count all next items - for(i = 1; i < nFileNodes; i++) - { - // If the item is the same like the last one, skip it - if(_stricmp(SortTable[i], szPrevItem)) - { - WriteListFileLine(hf, SortTable[i]); - szPrevItem = SortTable[i]; - } - } + // Clear the invalidate flag + ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID; } - } - else - { - // Create the listfile in the MPQ - dwFileSize = (DWORD)strlen(LISTFILE_NAME) + 2; - nError = SFileAddFile_Init(ha, LISTFILE_NAME, - 0, - dwFileSize, - LANG_NEUTRAL, - MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING, - &hf); - // Just add "(listfile)" there - if(nError == ERROR_SUCCESS) - { - WriteListFileLine(hf, LISTFILE_NAME); - } + // Free the listfile buffer + STORM_FREE(pbListFile); } - // Finalize the file in the MPQ - if(hf != NULL) - { - SFileAddFile_Finish(hf); - } - - // Free buffers - if(nError == ERROR_SUCCESS) - ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID; - if(SortTable != NULL) - STORM_FREE(SortTable); return nError; } diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index bf8d19e..b05cff3 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -32,6 +32,33 @@ static bool IsAviFile(void * pvFileBegin) return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C); } +static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData) +{ + TMPQUserData * pUserData; + + // BSWAP the source data and copy them to our buffer + BSWAP_ARRAY32_UNSIGNED(&pvUserData, sizeof(TMPQUserData)); + pUserData = (TMPQUserData *)pvUserData; + + // Check the sizes + if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs) + { + // Move to the position given by the userdata + ByteOffset += pUserData->dwHeaderOffs; + + // The MPQ header should be within range of the file size + if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize) + { + // Note: We should verify if there is the MPQ header. + // However, the header could be at any position below that + // that is multiplier of 0x200 + return (TMPQUserData *)pvUserData; + } + } + + return NULL; +} + static TFileBitmap * CreateFileBitmap(TMPQArchive * ha, TMPQBitmap * pMpqBitmap, bool bFileIsComplete) { TFileBitmap * pBitmap; @@ -144,6 +171,7 @@ bool WINAPI SFileOpenArchive( DWORD dwFlags, HANDLE * phMpq) { + TMPQUserData * pUserData; TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle TFileEntry * pFileEntry; @@ -166,11 +194,18 @@ bool WINAPI SFileOpenArchive( if(pStream == NULL) nError = GetLastError(); } - - // Allocate the MPQhandle + + // Check the file size. There must be at least 0x20 bytes if(nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, &FileSize); + if(FileSize < MPQ_HEADER_SIZE_V1) + nError = ERROR_FILE_CORRUPT; + } + + // Allocate the MPQhandle + if(nError == ERROR_SUCCESS) + { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } @@ -219,18 +254,18 @@ bool WINAPI SFileOpenArchive( // If there is the MPQ user data signature, process it dwHeaderID = BSWAP_INT32_UNSIGNED(*(LPDWORD)ha->HeaderData); - if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL) + if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { - // Ignore the MPQ user data completely if the caller wants to open the MPQ as V1.0 - if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) + // Verify if this looks like a valid user data + pUserData = IsValidMpqUserData(SearchPos, FileSize, ha->HeaderData); + if(pUserData != NULL) { // Fill the user data header + ha->UserDataPos = SearchPos; ha->pUserData = &ha->UserData; - memcpy(ha->pUserData, ha->HeaderData, sizeof(TMPQUserData)); - BSWAP_TMPQUSERDATA(ha->pUserData); + memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); - // Remember the position of the user data and continue search - ha->UserDataPos = SearchPos; + // Continue searching from that position SearchPos += ha->pUserData->dwHeaderOffs; continue; } @@ -266,9 +301,11 @@ bool WINAPI SFileOpenArchive( // Did we identify one of the supported headers? if(nError == ERROR_SUCCESS) { - // Set the position of user data, header and file offset of the header + // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) ha->UserDataPos = SearchPos; + + // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchPos; @@ -285,7 +322,7 @@ bool WINAPI SFileOpenArchive( // DumpMpqHeader(ha->pHeader); // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, - // and probably ignores the MPQ format version as well. The trick is to + // and ignores the MPQ format version as well. The trick is to // fake MPQ format 2, with an improper hi-word position of hash table and block table // We can overcome such protectors by forcing opening the archive as MPQ v 1.0 if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) @@ -336,13 +373,13 @@ bool WINAPI SFileOpenArchive( // the block table, BET table, hi-block table, (attributes) and (listfile). if(nError == ERROR_SUCCESS) { - nError = BuildFileTable(ha, FileSize); + nError = BuildFileTable(ha); } // Verify the file table, if no kind of protection was detected if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; ULONGLONG RawFilePos; // Parse all file entries @@ -379,10 +416,11 @@ bool WINAPI SFileOpenArchive( // Save the flags for (listfile) pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) + { + // Ignore result of the operation. (listfile) is optional. + SFileAddListFile((HANDLE)ha, NULL); ha->dwFileFlags1 = pFileEntry->dwFlags; - - // Ignore result of the operation. (listfile) is optional. - SFileAddListFile((HANDLE)ha, NULL); + } } // Load the "(attributes)" file and merge it to the file table @@ -391,10 +429,11 @@ bool WINAPI SFileOpenArchive( // Save the flags for (attributes) pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) + { + // Ignore result of the operation. (attributes) is optional. + SAttrLoadAttributes(ha); ha->dwFileFlags2 = pFileEntry->dwFlags; - - // Ignore result of the operation. (attributes) is optional. - SAttrLoadAttributes(ha); + } } // Cleanup and exit @@ -436,12 +475,15 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq) int nError; // Do nothing if 'hMpq' is bad parameter - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_HANDLE); return false; } + // Indicate that we are saving MPQ internal structures + ha->dwFlags |= MPQ_FLAG_SAVING_TABLES; + // If the (listfile) has been invalidated, save it if(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID) { @@ -466,6 +508,9 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq) nResultError = nError; } + // We are no longer saving internal MPQ structures + ha->dwFlags &= ~MPQ_FLAG_SAVING_TABLES; + // Return the error if(nResultError != ERROR_SUCCESS) SetLastError(nResultError); diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp index 4180cd7..c2cf5d4 100644 --- a/src/SFileOpenFileEx.cpp +++ b/src/SFileOpenFileEx.cpp @@ -16,11 +16,19 @@ /* Local functions */ /*****************************************************************************/ -static const char * GetPrefixedName(TMPQArchive * ha, const char * szFileName, char * szBuffer) +static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer) { if(ha->cchPatchPrefix != 0) { + // Copy the patch prefix memcpy(szBuffer, ha->szPatchPrefix, ha->cchPatchPrefix); + + // The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY" + // We need to remove the "Oldworld\\" prefix + if(!_strnicmp(szFileName, "OldWorld\\", 9)) + szFileName += 9; + + // Copy the rest of the name strcpy(szBuffer + ha->cchPatchPrefix, szFileName); szFileName = szBuffer; } @@ -61,10 +69,11 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile) bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HANDLE * phFile) { + TMPQArchive * haBase = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; + TFileEntry * pFileEntry; TMPQFile * hfPatch; // Pointer to patch file TMPQFile * hfBase = NULL; // Pointer to base open file - TMPQFile * hfLast = NULL; // The highest file in the chain that is not patch file TMPQFile * hf = NULL; HANDLE hPatchFile; char szPrefixBuffer[MAX_PATH]; @@ -72,66 +81,52 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN // Keep this flag here for future updates dwReserved = dwReserved; - // First of all, try to open the original version of the file in any of the patch chain + // First of all, find the latest archive where the file is in base version + // (i.e. where the original, unpatched version of the file exists) while(ha != NULL) { - // Prepare the file name with a correct prefix - if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) - { - // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE - if((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - { - hf = hfLast = hfBase; - break; - } - - SFileCloseFile((HANDLE)hfBase); - } + // If the file is there, then we remember the archive + pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), 0); + if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + haBase = ha; - // Move to the next file in the patch chain + // Move to the patch archive ha = ha->haPatch; } - // If we couldn't find the file in any of the patches, it doesn't exist - if(hf == NULL) + // If we couldn't find the base file in any of the patches, it doesn't exist + if((ha = haBase) == NULL) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } - // Now keep going in the patch chain and open every patch file that is there - for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) + // Now open the base file + if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) { - // Prepare the file name with a correct prefix - if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) + // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE + assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0); + hf = hfBase; + + // Now open all patches and attach them on top of the base file + for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) { - // Remember the new version - hfPatch = (TMPQFile *)hPatchFile; + // Prepare the file name with a correct prefix + if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) + { + // Remember the new version + hfPatch = (TMPQFile *)hPatchFile; - // If we encountered a full replacement of the file, - // we have to remember the highest full file - if((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - hfLast = hfPatch; + // We should not find patch file + assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0); - // Set current patch to base file and move on - hf->hfPatchFile = hfPatch; - hf = hfPatch; + // Attach the patch to the base file + hf->hfPatch = hfPatch; + hf = hfPatch; + } } } - // Now we need to free all files that are below the highest unpatched version - while(hfBase != hfLast) - { - TMPQFile * hfNext = hfBase->hfPatchFile; - - // Free the file below - hfBase->hfPatchFile = NULL; - FreeMPQFile(hfBase); - - // Move the base to the next file - hfBase = hfNext; - } - // Give the updated base MPQ if(phFile != NULL) *phFile = (HANDLE)hfBase; @@ -163,7 +158,7 @@ int WINAPI SFileEnumLocales( DWORD dwLocales = 0; // Test the parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) return ERROR_INVALID_PARAMETER; @@ -236,7 +231,7 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) bool bIsPseudoName; int nError = ERROR_SUCCESS; - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) nError = ERROR_INVALID_PARAMETER; @@ -251,7 +246,7 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) while(ha != NULL) { // Verify presence of the file - pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), lcFileLocale) + pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), lcFileLocale) : GetFileEntryByIndex(ha, dwFileIndex); // Verify the file flags if(pFileEntry != NULL && (pFileEntry->dwFlags & dwFlagsToCheck) == MPQ_FILE_EXISTS) @@ -301,7 +296,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch case SFILE_OPEN_FROM_MPQ: case SFILE_OPEN_BASE_FILE: - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { nError = ERROR_INVALID_HANDLE; break; @@ -369,6 +364,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch if(nError != ERROR_SUCCESS) { SetLastError(nError); + *phFile = NULL; return false; } } @@ -409,7 +405,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch if(bOpenByIndex == false) { // If there is no file name yet, allocate it - AllocateFileName(pFileEntry, szFileName); + AllocateFileName(ha, pFileEntry, szFileName); // If the file is encrypted, we should detect the file key if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) @@ -454,7 +450,7 @@ bool WINAPI SFileCloseFile(HANDLE hFile) { TMPQFile * hf = (TMPQFile *)hFile; - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return false; diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp index bfb4100..7f67749 100644 --- a/src/SFilePatchArchives.cpp +++ b/src/SFilePatchArchives.cpp @@ -34,7 +34,7 @@ static bool GetDefaultPatchPrefix( const TCHAR * szDash; // Ensure that both names are plain names - szBaseMpqName = GetPlainFileNameT(szBaseMpqName); + szBaseMpqName = GetPlainFileName(szBaseMpqName); // Patch prefix is for the Cataclysm MPQs, whose names // are like "locale-enGB.MPQ" or "speech-enGB.MPQ" @@ -431,7 +431,7 @@ int PatchFileData(TMPQFile * hf) int nError = ERROR_SUCCESS; // Move to the first patch - hf = hf->hfPatchFile; + hf = hf->hfPatch; // Now go through all patches and patch the original data while(hf != NULL) @@ -450,7 +450,7 @@ int PatchFileData(TMPQFile * hf) break; // Move to the next patch - hf = hf->hfPatchFile; + hf = hf->hfPatch; } return nError; @@ -493,7 +493,7 @@ bool WINAPI SFileOpenPatchArchive( dwFlags = dwFlags; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szPatchMpqName == NULL || *szPatchMpqName == 0) nError = ERROR_INVALID_PARAMETER; @@ -580,7 +580,7 @@ bool WINAPI SFileIsPatchedArchive(HANDLE hMpq) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return false; return (ha->haPatch != NULL); diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp index 6eb0d10..cfc8d2a 100644 --- a/src/SFileReadFile.cpp +++ b/src/SFileReadFile.cpp @@ -14,103 +14,8 @@ #include "StormCommon.h" //----------------------------------------------------------------------------- -// Local structures - -struct TFileHeader2Ext -{ - DWORD dwOffset00Data; // Required data at offset 00 (32-bits) - DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored - DWORD dwOffset04Data; // Required data at offset 04 (32-bits) - DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored - const char * szExt; // Supplied extension, if the condition is true -}; - -//----------------------------------------------------------------------------- // Local functions -static DWORD GetMpqFileCount(TMPQArchive * ha) -{ - TFileEntry * pFileTableEnd; - TFileEntry * pFileEntry; - DWORD dwFileCount = 0; - - // Go through all open MPQs, including patches - while(ha != NULL) - { - // Only count files that are not patch files - pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // If the file is patch file and this is not primary archive, skip it - // BUGBUG: This errorneously counts non-patch files that are in both - // base MPQ and in patches, and increases the number of files by cca 50% - if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS) - dwFileCount++; - } - - // Move to the next patch archive - ha = ha->haPatch; - } - - return dwFileCount; -} - -static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded) -{ - TMPQFile * hfTemp; - TCHAR * szPatchChain = (TCHAR *)pvFileInfo; - TCHAR * szFileName; - size_t cchCharsNeeded = 1; - size_t nLength; - DWORD cbLengthNeeded; - - // Check if the "hf" is a MPQ file - if(hf->pStream != NULL) - { - // Calculate the length needed - szFileName = FileStream_GetFileName(hf->pStream); - cchCharsNeeded += _tcslen(szFileName) + 1; - cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - - // If we have enough space, copy the file name - if(cbFileInfo >= cbLengthNeeded) - { - nLength = _tcslen(szFileName) + 1; - memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR)); - szPatchChain += nLength; - - // Terminate the multi-string - *szPatchChain = 0; - } - } - else - { - // Calculate number of characters needed - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - - // If we have enough space, the copy the patch chain - if(cbFileInfo >= cbLengthNeeded) - { - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - { - szFileName = FileStream_GetFileName(hfTemp->ha->pStream); - nLength = _tcslen(szFileName) + 1; - memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR)); - szPatchChain += nLength; - } - - // Terminate the multi-string - *szPatchChain = 0; - } - } - - // Give result length, terminate multi-string and return - *pcbLengthNeeded = cbLengthNeeded; - return true; -} - // hf - MPQ File handle. // pbBuffer - Pointer to target buffer to store sectors. // dwByteOffset - Position of sector in the file (relative to file begin) @@ -453,7 +358,12 @@ static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; + pbRawData = hf->pbFileSector; + } + // If the file sector is not loaded yet, do it + if(hf->dwSectorOffs != 0) + { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { @@ -750,7 +660,7 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD lpOverlapped = lpOverlapped; // Check valid parameters - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return false; @@ -769,7 +679,7 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD } // If the file is a patch file, we have to read it special way - else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + else if(hf->hfPatch != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) { nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); } @@ -819,13 +729,13 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) TMPQFile * hf = (TMPQFile *)hFile; // Validate the file handle before we go on - if(IsValidFileHandle(hf)) + if(IsValidFileHandle(hFile)) { // Make sure that the variable is initialized FileSize = 0; // If the file is patched file, we have to get the size of the last version - if(hf->hfPatchFile != NULL) + if(hf->hfPatch != NULL) { // Walk through the entire patch chain, take the last version while(hf != NULL) @@ -834,7 +744,7 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) FileSize = hf->pFileEntry->dwFileSize; // Move to the next patch file in the hierarchy - hf = hf->hfPatchFile; + hf = hf->hfPatch; } } else @@ -868,7 +778,7 @@ DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHi DWORD dwFilePosHi; // If the hFile is not a valid file handle, return an error. - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return SFILE_INVALID_POS; @@ -954,367 +864,3 @@ DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHi } } -//----------------------------------------------------------------------------- -// Tries to retrieve the file name - -static TFileHeader2Ext data2ext[] = -{ - {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files - {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files - {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') - {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' - {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' - {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) - {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I - {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II - {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' - {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM - {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files - {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures - {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) - {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files - {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps - {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' - {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 - {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc - {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders - {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image - {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension - {0, 0, 0, 0, NULL} // Terminator -}; - -static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file - DWORD dwBytesRead = 0; - DWORD dwFilePos; // Saved file position - - // Read the first 2 DWORDs bytes from the file - dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); - SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); - SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); - - // If we read at least 8 bytes - if(dwBytesRead == sizeof(FirstBytes)) - { - // Make sure that the array is properly BSWAP-ed - BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); - - // Try to guess file extension from those 2 DWORDs - for(size_t i = 0; data2ext[i].szExt != NULL; i++) - { - if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && - (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) - { - char szPseudoName[20] = ""; - - // Format the pseudo-name - sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); - - // Save the pseudo-name in the file entry as well - AllocateFileName(pFileEntry, szPseudoName); - - // If the caller wants to copy the file name, do it - if(szFileName != NULL) - strcpy(szFileName, szPseudoName); - return ERROR_SUCCESS; - } - } - } - - return ERROR_NOT_SUPPORTED; -} - -bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - TCHAR * szFileNameT; - int nError = ERROR_INVALID_HANDLE; - - // Pre-zero the output buffer - if(szFileName != NULL) - *szFileName = 0; - - // Check valid parameters - if(IsValidFileHandle(hf)) - { - TFileEntry * pFileEntry = hf->pFileEntry; - - // For MPQ files, retrieve the file name from the file entry - if(hf->pStream == NULL) - { - if(pFileEntry != NULL) - { - // If the file name is not there yet, create a pseudo name - if(pFileEntry->szFileName == NULL) - { - nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); - } - else - { - if(szFileName != NULL) - strcpy(szFileName, pFileEntry->szFileName); - nError = ERROR_SUCCESS; - } - } - } - - // For local files, copy the file name from the stream - else - { - if(szFileName != NULL) - { - szFileNameT = FileStream_GetFileName(hf->pStream); - CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT)); - } - nError = ERROR_SUCCESS; - } - } - - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Retrieves an information about an archive or about a file within the archive -// -// hMpqOrFile - Handle to an MPQ archive or to a file -// dwInfoType - Information to obtain - -#define VERIFY_MPQ_HANDLE(h) \ - if(!IsValidMpqHandle(h)) \ - { \ - nError = ERROR_INVALID_HANDLE; \ - break; \ - } - -#define VERIFY_FILE_HANDLE(h) \ - if(!IsValidFileHandle(h)) \ - { \ - nError = ERROR_INVALID_HANDLE; \ - break; \ - } - -bool WINAPI SFileGetFileInfo( - HANDLE hMpqOrFile, - DWORD dwInfoType, - void * pvFileInfo, - DWORD cbFileInfo, - LPDWORD pcbLengthNeeded) -{ - TMPQArchive * ha = (TMPQArchive *)hMpqOrFile; - TMPQBlock * pBlock; - TMPQFile * hf = (TMPQFile *)hMpqOrFile; - void * pvSrcFileInfo = NULL; - DWORD cbLengthNeeded = 0; - DWORD dwIsReadOnly; - DWORD dwFileCount = 0; - DWORD dwFileIndex; - DWORD dwFileKey; - DWORD i; - int nError = ERROR_SUCCESS; - - switch(dwInfoType) - { - case SFILE_INFO_ARCHIVE_NAME: - VERIFY_MPQ_HANDLE(ha); - - // pvFileInfo receives the name of the archive, terminated by 0 - pvSrcFileInfo = FileStream_GetFileName(ha->pStream); - cbLengthNeeded = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); - break; - - case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pHeader->dwArchiveSize; - break; - - case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->dwMaxFileCount; - break; - - case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pHeader->dwHashTableSize; - break; - - case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; - break; - - case SFILE_INFO_SECTOR_SIZE: - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->dwSectorSize; - break; - - case SFILE_INFO_HASH_TABLE: - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); - pvSrcFileInfo = ha->pHashTable; - break; - - case SFILE_INFO_BLOCK_TABLE: - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = ha->dwFileTableSize * sizeof(TMPQBlock); - if(cbFileInfo < cbLengthNeeded) - { - nError = ERROR_INSUFFICIENT_BUFFER; - break; - } - - // Construct block table from file table size - pBlock = (TMPQBlock *)pvFileInfo; - for(i = 0; i < ha->dwFileTableSize; i++) - { - pBlock->dwFilePos = (DWORD)ha->pFileTable[i].ByteOffset; - pBlock->dwFSize = ha->pFileTable[i].dwFileSize; - pBlock->dwCSize = ha->pFileTable[i].dwCmpSize; - pBlock->dwFlags = ha->pFileTable[i].dwFlags; - pBlock++; - } - break; - - case SFILE_INFO_NUM_FILES: - VERIFY_MPQ_HANDLE(ha); - dwFileCount = GetMpqFileCount(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileCount; - break; - - case SFILE_INFO_STREAM_FLAGS: - VERIFY_MPQ_HANDLE(ha); - FileStream_GetFlags(ha->pStream, &dwFileKey); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileKey; - break; - - case SFILE_INFO_IS_READ_ONLY: - VERIFY_MPQ_HANDLE(ha); - dwIsReadOnly = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwIsReadOnly; - break; - - case SFILE_INFO_HASH_INDEX: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; - break; - - case SFILE_INFO_CODENAME1: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; - if(ha->pHashTable != NULL) - pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1; - break; - - case SFILE_INFO_CODENAME2: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - if(ha->pHashTable != NULL) - pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2; - break; - - case SFILE_INFO_LOCALEID: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->lcLocale; - break; - - case SFILE_INFO_BLOCKINDEX: - VERIFY_FILE_HANDLE(hf); - dwFileIndex = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileIndex; - break; - - case SFILE_INFO_FILE_SIZE: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwFileSize; - break; - - case SFILE_INFO_COMPRESSED_SIZE: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; - break; - - case SFILE_INFO_FLAGS: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwFlags; - break; - - case SFILE_INFO_POSITION: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(ULONGLONG); - pvSrcFileInfo = &hf->pFileEntry->ByteOffset; - break; - - case SFILE_INFO_KEY: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->dwFileKey; - break; - - case SFILE_INFO_KEY_UNFIXED: - VERIFY_FILE_HANDLE(hf); - dwFileKey = hf->dwFileKey; - if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) - dwFileKey = (dwFileKey ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos; - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileKey; - break; - - case SFILE_INFO_FILETIME: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(ULONGLONG); - pvSrcFileInfo = &hf->pFileEntry->FileTime; - break; - - case SFILE_INFO_PATCH_CHAIN: - VERIFY_FILE_HANDLE(hf); - GetFilePatchChain(hf, pvFileInfo, cbFileInfo, &cbLengthNeeded); - break; - - default: - nError = ERROR_INVALID_PARAMETER; - break; - } - - // If everything is OK so far, copy the information - if(nError == ERROR_SUCCESS) - { - // Is the output buffer large enough? - if(cbFileInfo >= cbLengthNeeded) - { - // Copy the data - if(pvSrcFileInfo != NULL) - memcpy(pvFileInfo, pvSrcFileInfo, cbLengthNeeded); - } - else - { - nError = ERROR_INSUFFICIENT_BUFFER; - } - - // Give the size to the caller - if(pcbLengthNeeded != NULL) - *pcbLengthNeeded = cbLengthNeeded; - } - - // Set the last error value, if needed - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp index 4509388..219e187 100644 --- a/src/SFileVerify.cpp +++ b/src/SFileVerify.cpp @@ -20,25 +20,8 @@ //----------------------------------------------------------------------------- // Local defines -#define SIGNATURE_TYPE_NONE 0 -#define SIGNATURE_TYPE_WEAK 1 -#define SIGNATURE_TYPE_STRONG 2 - #define MPQ_DIGEST_UNIT_SIZE 0x10000 -typedef struct _MPQ_SIGNATURE_INFO -{ - ULONGLONG BeginMpqData; // File offset where the hashing starts - ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) - ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) - ULONGLONG EndMpqData; // File offset where the hashing ends - ULONGLONG EndOfFile; // Size of the entire file - BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; - DWORD cbSignatureSize; // Length of the signature - int nSignatureType; // See SIGNATURE_TYPE_XXX - -} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; - //----------------------------------------------------------------------------- // Known Blizzard public keys // Created by Jean-Francois Roy using OpenSSL @@ -156,7 +139,7 @@ static void GetPlainAnsiFileName( const TCHAR * szFileName, char * szPlainName) { - const TCHAR * szPlainNameT = GetPlainFileNameT(szFileName); + const TCHAR * szPlainNameT = GetPlainFileName(szFileName); // Convert the plain name to ANSI while(*szPlainNameT != 0) @@ -186,67 +169,13 @@ static void CalculateArchiveRange( } } - // Get the MPQ data end. This is stored in our MPQ header, - // and it's been already prepared by SFileOpenArchive, + // Get the MPQ data end. This is stored in the MPQ header pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64; // Get the size of the entire file FileStream_GetSize(ha->pStream, &pSI->EndOfFile); } -static bool QueryMpqSignatureInfo( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI) -{ - ULONGLONG ExtraBytes; - TMPQFile * hf; - HANDLE hFile; - DWORD dwFileSize; - - // Calculate the range of the MPQ - CalculateArchiveRange(ha, pSI); - - // If there is "(signature)" file in the MPQ, it has a weak signature - if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile)) - { - // Get the content of the signature - SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL); - - // Verify the size of the signature - hf = (TMPQFile *)hFile; - - // We have to exclude the signature file from the digest - pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset; - pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize; - dwFileSize = hf->dwDataSize; - - // Close the file - SFileCloseFile(hFile); - pSI->nSignatureType = SIGNATURE_TYPE_WEAK; - return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false; - } - - // If there is extra bytes beyond the end of the archive, - // it's the strong signature - ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; - if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) - { - // Read the strong signature - if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) - return false; - - // Check the signature header "NGIS" - if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') - return false; - - pSI->nSignatureType = SIGNATURE_TYPE_STRONG; - return true; - } - - // Succeeded, but no known signature found - return true; -} - static bool CalculateMpqHashMd5( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI, @@ -723,7 +652,7 @@ static DWORD VerifyFile( if(dwTotalBytes == 0) { // Check CRC32 and MD5 only if there is no patches - if(hf->hfPatchFile == NULL) + if(hf->hfPatch == NULL) { // Check if the CRC32 matches. if(dwFlags & SFILE_VERIFY_FILE_CRC) @@ -781,6 +710,63 @@ static DWORD VerifyFile( return dwVerifyResult; } +// Used in SFileGetFileInfo +bool QueryMpqSignatureInfo( + TMPQArchive * ha, + PMPQ_SIGNATURE_INFO pSI) +{ + ULONGLONG ExtraBytes; + TMPQFile * hf; + HANDLE hFile; + DWORD dwFileSize; + + // Make sure it's all zeroed + memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO)); + + // Calculate the range of the MPQ + CalculateArchiveRange(ha, pSI); + + // If there is "(signature)" file in the MPQ, it has a weak signature + if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile)) + { + // Get the content of the signature + SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL); + + // Verify the size of the signature + hf = (TMPQFile *)hFile; + + // We have to exclude the signature file from the digest + pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset; + pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize; + dwFileSize = hf->dwDataSize; + + // Close the file + SFileCloseFile(hFile); + pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK; + return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false; + } + + // If there is extra bytes beyond the end of the archive, + // it's the strong signature + ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; + if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) + { + // Read the strong signature + if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) + return false; + + // Check the signature header "NGIS" + if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') + return false; + + pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG; + return true; + } + + // Succeeded, but no known signature found + return true; +} + //----------------------------------------------------------------------------- // Public (exported) functions @@ -828,7 +814,7 @@ int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * sz TMPQHeader * pHeader; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return ERROR_INVALID_PARAMETER; pHeader = ha->pHeader; @@ -900,7 +886,7 @@ DWORD WINAPI SFileVerifyArchive(HANDLE hMpq) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return ERROR_VERIFY_FAILED; // Get the MPQ signature and signature type @@ -908,18 +894,20 @@ DWORD WINAPI SFileVerifyArchive(HANDLE hMpq) if(!QueryMpqSignatureInfo(ha, &si)) return ERROR_VERIFY_FAILED; - // Verify the signature - switch(si.nSignatureType) - { - case SIGNATURE_TYPE_NONE: - return ERROR_NO_SIGNATURE; + // If there is no signature + if(si.SignatureTypes == 0) + return ERROR_NO_SIGNATURE; - case SIGNATURE_TYPE_WEAK: - return VerifyWeakSignature(ha, &si); + // We haven't seen a MPQ with both signatures + assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG); - case SIGNATURE_TYPE_STRONG: - return VerifyStrongSignature(ha, &si); - } + // Verify the strong signature, if present + if(si.SignatureTypes & SIGNATURE_TYPE_STRONG) + return VerifyStrongSignature(ha, &si); + + // Verify the weak signature, if present + if(si.SignatureTypes & SIGNATURE_TYPE_WEAK) + return VerifyWeakSignature(ha, &si); - return ERROR_VERIFY_FAILED; + return ERROR_NO_SIGNATURE; } diff --git a/src/StormCommon.h b/src/StormCommon.h index dcc9c7f..bfe99f7 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -61,9 +61,6 @@ #define ID_MPQ_FILE 0x46494c45 // Used internally for checking TMPQFile ('FILE') -#define MPQ_WEAK_SIGNATURE_SIZE 64 -#define MPQ_STRONG_SIGNATURE_SIZE 256 - // Prevent problems with CRT "min" and "max" functions, // as they are not defined on all platforms #define STORMLIB_MIN(a, b) ((a < b) ? a : b) @@ -71,39 +68,50 @@ #define STORMLIB_UNUSED(p) ((void)(p)) // Macro for building 64-bit file offset from two 32-bit -#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | lo) +#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo) + +//----------------------------------------------------------------------------- +// MPQ signature information + +// Size of each signature type +#define MPQ_WEAK_SIGNATURE_SIZE 64 +#define MPQ_STRONG_SIGNATURE_SIZE 256 + +// MPQ signature info +typedef struct _MPQ_SIGNATURE_INFO +{ + ULONGLONG BeginMpqData; // File offset where the hashing starts + ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) + ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) + ULONGLONG EndMpqData; // File offset where the hashing ends + ULONGLONG EndOfFile; // Size of the entire file + BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; + DWORD cbSignatureSize; // Length of the signature + DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX + +} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; //----------------------------------------------------------------------------- // Memory management // // We use our own macros for allocating/freeing memory. If you want -// to redefine them, please keep the following rules +// to redefine them, please keep the following rules: // // - The memory allocation must return NULL if not enough memory // (i.e not to throw exception) -// - It is not necessary to fill the allocated buffer with zeros -// - Memory freeing function doesn't have to test the pointer to NULL. +// - The allocating function does not need to fill the allocated buffer with zeros +// - Memory freeing function doesn't have to test the pointer to NULL // #if defined(_MSC_VER) && defined(_DEBUG) -__inline void * DebugMalloc(char * /* szFile */, int /* nLine */, size_t nSize) -{ -// return new BYTE[nSize]; - return HeapAlloc(GetProcessHeap(), 0, nSize); -} -__inline void DebugFree(void * ptr) -{ -// delete [] ptr; - HeapFree(GetProcessHeap(), 0, ptr); -} +#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type))) +#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr) -#define STORM_ALLOC(type, nitems) (type *)DebugMalloc(__FILE__, __LINE__, (nitems) * sizeof(type)) -#define STORM_FREE(ptr) DebugFree(ptr) #else -#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type)) -#define STORM_FREE(ptr) free(ptr) +#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type)) +#define STORM_FREE(ptr) free(ptr) #endif @@ -137,12 +145,7 @@ DWORD GetHashTableSizeForFileCount(DWORD dwFileCount); bool IsPseudoFileName(const char * szFileName, LPDWORD pdwFileIndex); ULONGLONG HashStringJenkins(const char * szFileName); -void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength); -void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength); - -int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); - -DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize); +DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion); void EncryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey); void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey); @@ -158,17 +161,26 @@ void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_ha //----------------------------------------------------------------------------- // Handle validation functions -bool IsValidMpqHandle(TMPQArchive * ha); -bool IsValidFileHandle(TMPQFile * hf); +TMPQArchive * IsValidMpqHandle(HANDLE hMpq); +TMPQFile * IsValidFileHandle(HANDLE hFile); //----------------------------------------------------------------------------- -// Hash table and block table manipulation +// Support for MPQ file tables + +int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale); TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName); TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash); TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry); -DWORD AllocateHetEntry(TMPQArchive * ha, TFileEntry * pFileEntry); + +TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey); +TMPQHetTable * LoadHetTable(TMPQArchive * ha); +TMPQBetTable * LoadBetTable(TMPQArchive * ha); + +TMPQHash * LoadHashTable(TMPQArchive * ha); +TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries = false); +TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable); ULONGLONG FindFreeMpqSpace(TMPQArchive * ha); @@ -178,10 +190,12 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComp // Functions that load the HET and BET tables int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize); int LoadAnyHashTable(TMPQArchive * ha); -int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize); +int BuildFileTable(TMPQArchive * ha); +int RebuildHetTable(TMPQArchive * ha); +int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount); int SaveMPQTables(TMPQArchive * ha); -TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty); +TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwHashBitSize, LPBYTE pbSrcData); void FreeHetTable(TMPQHetTable * pHetTable); TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount); @@ -194,18 +208,19 @@ TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID l TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex); // Allocates file name in the file entry -void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName); +void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName); // Allocates new file entry in the MPQ tables. Reuses existing, if possible -TFileEntry * FindFreeFileEntry(TMPQArchive * ha); TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale); int RenameFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szNewFileName); -void ClearFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); -int FreeFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); +void DeleteFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); // Invalidates entries for (listfile) and (attributes) void InvalidateInternalFiles(TMPQArchive * ha); +// Retrieves information about the strong signature +bool QueryMpqSignatureInfo(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSignatureInfo); + //----------------------------------------------------------------------------- // Support for alternate file formats (SBaseSubTypes.cpp) @@ -245,10 +260,14 @@ void FreeMPQArchive(TMPQArchive *& ha); // Utility functions bool CheckWildCard(const char * szString, const char * szWildCard); -const char * GetPlainFileNameA(const char * szFileName); -const TCHAR * GetPlainFileNameT(const TCHAR * szFileName); bool IsInternalMpqFileName(const char * szFileName); +const TCHAR * GetPlainFileName(const TCHAR * szFileName); +const char * GetPlainFileName(const char * szFileName); + +void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength); +void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength); + //----------------------------------------------------------------------------- // Support for adding files to the MPQ diff --git a/src/StormLib.h b/src/StormLib.h index 3f91478..ce65f7f 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -67,6 +67,7 @@ /* 26.04.12 8.10 Lad Support for data map, added SFileGetArchiveBitmap */ /* 29.05.12 8.20 Lad C-only interface */ /* 14.01.13 8.21 Lad ADPCM and Huffmann (de)compression refactored */ +/* 04.12.13 9.00 Lad Unit tests, bug fixes */ /*****************************************************************************/ #ifndef __STORMLIB_H__ @@ -131,8 +132,8 @@ extern "C" { //----------------------------------------------------------------------------- // Defines -#define STORMLIB_VERSION 0x0817 // Current version of StormLib (8.23) -#define STORMLIB_VERSION_STRING "8.23" // String version of StormLib version +#define STORMLIB_VERSION 0x0900 // Current version of StormLib (9.0) +#define STORMLIB_VERSION_STRING "9.00" // String version of StormLib version #define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A') #define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B') @@ -146,15 +147,15 @@ extern "C" { #define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ // Values for SFileCreateArchive -#define HASH_TABLE_SIZE_MIN 0x00000004 // Minimum acceptable hash table size +#define HASH_TABLE_SIZE_MIN 0x00000004 // Verified: If there is 1 file, hash table size is 4 #define HASH_TABLE_SIZE_DEFAULT 0x00001000 // Default hash table size for empty MPQs #define HASH_TABLE_SIZE_MAX 0x00080000 // Maximum acceptable hash table size #define HASH_ENTRY_DELETED 0xFFFFFFFE // Block index for deleted entry in the hash table #define HASH_ENTRY_FREE 0xFFFFFFFF // Block index for free entry in the hash table -#define HET_ENTRY_DELETED 0x80 // HET hash value for a deleted entry -#define HET_ENTRY_FREE 0x00 // HET hash value for free entry +#define HET_ENTRY_DELETED 0x80 // NameHash1 value for a deleted entry +#define HET_ENTRY_FREE 0x00 // NameHash1 value for free entry #define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure @@ -173,11 +174,11 @@ extern "C" { // Flags for TMPQArchive::dwFlags #define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access #define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed -#define MPQ_FLAG_PROTECTED 0x00000004 // Set on protected MPQs (like W3M maps) +#define MPQ_FLAG_PROTECTED 0x00000004 // Some kind of protector detected (W3M maps) #define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000008 // Checking sector CRC when reading files -#define MPQ_FLAG_NEED_FIX_SIZE 0x00000010 // Used during opening the archive #define MPQ_FLAG_LISTFILE_INVALID 0x00000020 // If set, it means that the (listfile) has been invalidated #define MPQ_FLAG_ATTRIBUTES_INVALID 0x00000040 // If set, it means that the (attributes) has been invalidated +#define MPQ_FLAG_SAVING_TABLES 0x00000080 // If set, we are saving MPQ internal files and MPQ tables // Values for TMPQArchive::dwSubType #define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games) @@ -214,9 +215,6 @@ extern "C" { MPQ_FILE_SECTOR_CRC | \ MPQ_FILE_EXISTS) -// A notification that people should stop using this flag -const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK") unsigned int MPQ_FILE_COMPRESSED = 0x0000FF00; - // Compression types for multiple compressions #define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only) #define MPQ_COMPRESSION_ZLIB 0x02 // ZLIB compression @@ -244,35 +242,6 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK // Block map defines #define MPQ_DATA_BITMAP_SIGNATURE 0x33767470 // Signature of the MPQ data bitmap ('ptv3') -// Constants for SFileGetFileInfo -#define SFILE_INFO_ARCHIVE_NAME 1 // MPQ size (value from header) -#define SFILE_INFO_ARCHIVE_SIZE 2 // MPQ size (value from header) -#define SFILE_INFO_MAX_FILE_COUNT 3 // Max number of files in the MPQ -#define SFILE_INFO_HASH_TABLE_SIZE 4 // Size of hash table, in entries -#define SFILE_INFO_BLOCK_TABLE_SIZE 5 // Number of entries in the block table -#define SFILE_INFO_SECTOR_SIZE 6 // Size of file sector (in bytes) -#define SFILE_INFO_HASH_TABLE 7 // Pointer to Hash table (TMPQHash *) -#define SFILE_INFO_BLOCK_TABLE 8 // Pointer to Block Table (TMPQBlock *) -#define SFILE_INFO_NUM_FILES 9 // Real number of files within archive -#define SFILE_INFO_STREAM_FLAGS 10 // Stream flags for the MPQ. See STREAM_FLAG_XXX -#define SFILE_INFO_IS_READ_ONLY 11 // TRUE of the MPQ was open as read only -//------ -#define SFILE_INFO_HASH_INDEX 100 // Hash index of file in MPQ -#define SFILE_INFO_CODENAME1 101 // The first codename of the file -#define SFILE_INFO_CODENAME2 102 // The second codename of the file -#define SFILE_INFO_LOCALEID 103 // Locale ID of file in MPQ -#define SFILE_INFO_BLOCKINDEX 104 // Index to Block Table -#define SFILE_INFO_FILE_SIZE 105 // Original file size (from the block table) -#define SFILE_INFO_COMPRESSED_SIZE 106 // Compressed file size (from the block table) -#define SFILE_INFO_FLAGS 107 // File flags -#define SFILE_INFO_POSITION 108 // File position within archive - // Note: for current pointer in open MPQ file, - // use SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); -#define SFILE_INFO_KEY 109 // File decryption key -#define SFILE_INFO_KEY_UNFIXED 110 // Decryption key not fixed to file pos and size -#define SFILE_INFO_FILETIME 111 // TMPQFileTime -#define SFILE_INFO_PATCH_CHAIN 112 // Chain of patches - #define LISTFILE_NAME "(listfile)" // Name of internal listfile #define SIGNATURE_NAME "(signature)" // Name of internal signature #define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file @@ -319,7 +288,8 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK #define MPQ_OPEN_ENCRYPTED STREAM_PROVIDER_ENCRYPTED // Flags for SFileCreateArchive -#define MPQ_CREATE_ATTRIBUTES 0x00100000 // Also add the (attributes) file +#define MPQ_CREATE_LISTFILE 0x00100000 // Also add the (listfile) file +#define MPQ_CREATE_ATTRIBUTES 0x00200000 // Also add the (attributes) file #define MPQ_CREATE_ARCHIVE_V1 0x00000000 // Creates archive of version 1 (size up to 4GB) #define MPQ_CREATE_ARCHIVE_V2 0x01000000 // Creates archive of version 2 (larger than 4 GB) #define MPQ_CREATE_ARCHIVE_V3 0x02000000 // Creates archive of version 3 @@ -357,6 +327,11 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK #define SFILE_VERIFY_HIBLOCK_TABLE 0x0006 // Verify raw data of the hi-block table #define SFILE_VERIFY_FILE 0x0007 // Verify raw data of a file +// Signature types +#define SIGNATURE_TYPE_NONE 0x0000 // The archive has no signature in it +#define SIGNATURE_TYPE_WEAK 0x0001 // The archive has weak signature +#define SIGNATURE_TYPE_STRONG 0x0002 // The archive has strong signature + // Return values for SFileVerifyArchive #define ERROR_NO_SIGNATURE 0 // There is no signature in the MPQ #define ERROR_VERIFY_FAILED 1 // There was an error during verifying signature (like no memory) @@ -381,14 +356,114 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK typedef DWORD (*HASH_STRING)(const char * szFileName, DWORD dwHashType); //----------------------------------------------------------------------------- +// File information classes for SFileGetFileInfo and SFileFreeFileInfo + +typedef enum _SFileInfoClass +{ + // Info classes for archives + SFileMpqFileName, // Name of the archive file (TCHAR []) + SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG) + SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData) + SFileMpqUserData, // MPQ USer data, without the header (BYTE []) + SFileMpqHeaderOffset, // Offset of the MPQ header (ULONGLONG) + SFileMpqHeaderSize, // Fixed size of the MPQ header + SFileMpqHeader, // Raw (unfixed) archive header (TMPQHeader) + SFileMpqHetTableOffset, // Offset of the HET table, relative to MPQ header (ULONGLONG) + SFileMpqHetTableSize, // Compressed size of the HET table (ULONGLONG) + SFileMpqHetHeader, // HET table header (TMPQHetHeader) + SFileMpqHetTable, // HET table as pointer. Must be freed using SFileFreeFileInfo + SFileMpqBetTableOffset, // Offset of the BET table, relative to MPQ header (ULONGLONG) + SFileMpqBetTableSize, // Compressed size of the BET table (ULONGLONG) + SFileMpqBetHeader, // BET table header, followed by the flags (TMPQBetHeader + DWORD[]) + SFileMpqBetTable, // BET table as pointer. Must be freed using SFileFreeFileInfo + SFileMpqHashTableOffset, // Hash table offset, relative to MPQ header (ULONGLONG) + SFileMpqHashTableSize64, // Compressed size of the hash table (ULONGLONG) + SFileMpqHashTableSize, // Size of the hash table, in entries (DWORD) + SFileMpqHashTable, // Raw (unfixed) hash table (TMPQBlock []) + SFileMpqBlockTableOffset, // Block table offset, relative to MPQ header (ULONGLONG) + SFileMpqBlockTableSize64, // Compressed size of the block table (ULONGLONG) + SFileMpqBlockTableSize, // Size of the block table, in entries (DWORD) + SFileMpqBlockTable, // Raw (unfixed) block table (TMPQBlock []) + SFileMpqHiBlockTableOffset, // Hi-block table offset, relative to MPQ header (ULONGLONG) + SFileMpqHiBlockTableSize64, // Compressed size of the hi-block table (ULONGLONG) + SFileMpqHiBlockTable, // The hi-block table (USHORT []) + SFileMpqSignatures, // Signatures present in the MPQ (DWORD) + SFileMpqStrongSignatureOffset, // Byte offset of the strong signature, relative to begin of the file (ULONGLONG) + SFileMpqStrongSignatureSize, // Size of the strong signature (DWORD) + SFileMpqStrongSignature, // The strong signature (BYTE []) + SFileMpqBitmapOffset, // Byte offset of the MPQ bitmap, relative to begin of the file (ULONGLONG) + SFileMpqBitmapSize, // Size of the MPQ bitmap (DWORD) + SFileMpqBitmap, // The MPQ Bitmap (BYTE []) + SFileMpqArchiveSize64, // Archive size from the header (ULONGLONG) + SFileMpqArchiveSize, // Archive size from the header (DWORD) + SFileMpqMaxFileCount, // Max number of files in the archive (DWORD) + SFileMpqFileTableSize, // Number of entries in the file table (DWORD) + SFileMpqSectorSize, // Sector size (DWORD) + SFileMpqNumberOfFiles, // Number of files (DWORD) + SFileMpqRawChunkSize, // Size of the raw data chunk for MD5 + SFileMpqStreamFlags, // Stream flags (DWORD) + SFileMpqIsReadOnly, // Nonzero if the MPQ is read only (DWORD) + + // Info classes for files + SFileInfoPatchChain, // Chain of patches where the file is (TCHAR []) + SFileInfoFileEntry, // The file entry for the file (TFileEntry) + SFileInfoHashEntry, // Hash table entry for the file (TMPQHash) + SFileInfoHashIndex, // Index of the hash table entry (DWORD) + SFileInfoNameHash1, // The first name hash in the hash table (DWORD) + SFileInfoNameHash2, // The second name hash in the hash table (DWORD) + SFileInfoNameHash3, // 64-bit file name hash for the HET/BET tables (ULONGLONG) + SFileInfoLocale, // File locale (DWORD) + SFileInfoFileIndex, // Block index (DWORD) + SFileInfoByteOffset, // File position in the archive (ULONGLONG) + SFileInfoFileTime, // File time (ULONGLONG) + SFileInfoFileSize, // Size of the file (DWORD) + SFileInfoCompressedSize, // Compressed file size (DWORD) + SFileInfoFlags, // File flags from (DWORD) + SFileInfoEncryptionKey, // File encryption key + SFileInfoEncryptionKeyRaw, // Unfixed value of the file key +} SFileInfoClass; + +//----------------------------------------------------------------------------- +// Deprecated flags. These are going to be removed in next releases. + +// MPQ_FILE_COMPRESSED is deprecated. Do not use. +STORMLIB_DEPRECATED_FLAG(DWORD, MPQ_FILE_COMPRESSED, MPQ_FILE_COMPRESS_MASK); + +// Legacy values for file info classes. Included for backward compatibility, do not use. +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_ARCHIVE_NAME, SFileMpqFileName); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_ARCHIVE_SIZE, SFileMpqArchiveSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_MAX_FILE_COUNT, SFileMpqMaxFileCount); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_TABLE_SIZE, SFileMpqHashTableSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCK_TABLE_SIZE, SFileMpqBlockTableSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_SECTOR_SIZE, SFileMpqSectorSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_TABLE, SFileMpqHashTable); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCK_TABLE, SFileMpqBlockTable); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_NUM_FILES, SFileMpqNumberOfFiles); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_STREAM_FLAGS, SFileMpqStreamFlags); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_IS_READ_ONLY, SFileMpqIsReadOnly); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_INDEX, SFileInfoHashIndex); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_CODENAME1, SFileInfoNameHash1); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_CODENAME2, SFileInfoNameHash2); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_LOCALEID, SFileInfoLocale); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCKINDEX, SFileInfoFileIndex); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FILE_SIZE, SFileInfoFileSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_COMPRESSED_SIZE, SFileInfoCompressedSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FLAGS, SFileInfoFlags); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_POSITION, SFileInfoByteOffset); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_KEY, SFileInfoEncryptionKey); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_KEY_UNFIXED, SFileInfoEncryptionKeyRaw); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FILETIME, SFileInfoFileTime); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_PATCH_CHAIN, SFileInfoPatchChain); + +//----------------------------------------------------------------------------- // Callback functions // Values for compact callback -#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total) -#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total) -#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used -#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total) -#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used +#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total) +#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total) +#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used +#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_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall); typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes); @@ -400,6 +475,7 @@ typedef struct TFileStream TFileStream; typedef struct _TBitArray { + DWORD NumberOfBytes; // Total number of bytes in "Elements" DWORD NumberOfBits; // Total number of bits that are available BYTE Elements[1]; // Array of elements (variable length) } TBitArray; @@ -602,10 +678,10 @@ typedef struct _TMPQBlock // Patch file information, preceding the sector offset table typedef struct _TPatchInfo { - DWORD dwLength; // Length of patch info header, in bytes - DWORD dwFlags; // Flags. 0x80000000 = MD5 (?) - DWORD dwDataSize; // Uncompressed size of the patch file - BYTE md5[0x10]; // MD5 of the entire patch file after decompression + DWORD dwLength; // Length of patch info header, in bytes + DWORD dwFlags; // Flags. 0x80000000 = MD5 (?) + DWORD dwDataSize; // Uncompressed size of the patch file + BYTE md5[0x10]; // MD5 of the entire patch file after decompression // Followed by the sector table (variable length) } TPatchInfo; @@ -614,21 +690,21 @@ typedef struct _TPatchInfo typedef struct _TPatchHeader { //-- PATCH header ----------------------------------- - DWORD dwSignature; // 'PTCH' - DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed) - DWORD dwSizeBeforePatch; // Size of the file before patch - DWORD dwSizeAfterPatch; // Size of file after patch + DWORD dwSignature; // 'PTCH' + DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed) + DWORD dwSizeBeforePatch; // Size of the file before patch + DWORD dwSizeAfterPatch; // Size of file after patch //-- MD5 block -------------------------------------- - DWORD dwMD5; // 'MD5_' - DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself - BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file - BYTE md5_after_patch[0x10]; // MD5 of the patched file + DWORD dwMD5; // 'MD5_' + DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself + BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file + BYTE md5_after_patch[0x10]; // MD5 of the patched file //-- XFRM block ------------------------------------- - DWORD dwXFRM; // 'XFRM' - DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data - DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY') + DWORD dwXFRM; // 'XFRM' + DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data + DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY') // Followed by the patch data } TPatchHeader; @@ -640,32 +716,74 @@ typedef struct _TPatchHeader // (attributes) file and from (listfile). typedef struct _TFileEntry { - ULONGLONG ByteOffset; // Position of the file content in the MPQ, relative to the MPQ header - ULONGLONG FileTime; // FileTime from the (attributes) file. 0 if not present. - ULONGLONG BetHash; // Lower part of the file name hash. Only used when the MPQ has BET table. - DWORD dwHashIndex; // Index to the hash table. Only used when the MPQ has classic hash table - DWORD dwHetIndex; // Index to the HET table. Only used when the MPQ has HET table - DWORD dwFileSize; // Decompressed size of the file - DWORD dwCmpSize; // Compressed size of the file (i.e., size of the file data in the MPQ) - DWORD dwFlags; // File flags (from block table) - USHORT lcLocale; // Locale ID for the file - USHORT wPlatform; // Platform ID for the file - DWORD dwCrc32; // CRC32 from (attributes) file. 0 if not present. - unsigned char md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present. - char * szFileName; // File name. NULL if not known. + ULONGLONG FileNameHash; // Jenkins hash of the file name. Only used when the MPQ has BET table. + ULONGLONG ByteOffset; // Position of the file content in the MPQ, relative to the MPQ header + ULONGLONG FileTime; // FileTime from the (attributes) file. 0 if not present. + DWORD dwHashIndex; // Index to the hash table. Only used when the MPQ has classic hash table + DWORD dwFileSize; // Decompressed size of the file + DWORD dwCmpSize; // Compressed size of the file (i.e., size of the file data in the MPQ) + DWORD dwFlags; // File flags (from block table) + USHORT lcLocale; // Locale ID for the file + USHORT wPlatform; // Platform ID for the file + DWORD dwCrc32; // CRC32 from (attributes) file. 0 if not present. + unsigned char md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present. + char * szFileName; // File name. NULL if not known. } TFileEntry; // Common header for HET and BET tables -typedef struct _TMPQExtTable +typedef struct _TMPQExtHeader { - DWORD dwSignature; // 'HET\x1A' or 'BET\x1A' - DWORD dwVersion; // Version. Seems to be always 1 - DWORD dwDataSize; // Size of the contained table + DWORD dwSignature; // 'HET\x1A' or 'BET\x1A' + DWORD dwVersion; // Version. Seems to be always 1 + DWORD dwDataSize; // Size of the contained table // Followed by the table header // Followed by the table data -} TMPQExtTable; +} TMPQExtHeader; + +// Structure for HET table header +typedef struct _TMPQHetHeader +{ + TMPQExtHeader ExtHdr; + + DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes) + DWORD dwEntryCount; // Number of occupied entries in the HET table + DWORD dwTotalCount; // Total number of entries in the HET table + DWORD dwNameHashBitSize; // Size of the name hash entry (in bits) + DWORD dwIndexSizeTotal; // Total size of file index (in bits) + DWORD dwIndexSizeExtra; // Extra bits in the file index + DWORD dwIndexSize; // Effective size of the file index (in bits) + DWORD dwIndexTableSize; // Size of the block index subtable (in bytes) + +} TMPQHetHeader; + +// Structure for BET table header +typedef struct _TMPQBetHeader +{ + TMPQExtHeader ExtHdr; + + DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes) + DWORD dwEntryCount; // Number of entries in the BET table. Must match HET_TABLE_HEADER::dwEntryCount + DWORD dwUnknown08; + DWORD dwTableEntrySize; // Size of one table entry (in bits) + DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record) + DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record) + DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record) + DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record) + DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record) + DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record) + DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record) + DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record) + DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record) + DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record) + DWORD dwBitTotal_NameHash2; // Total bit size of the NameHash2 + DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2 + DWORD dwBitCount_NameHash2; // Effective size of NameHash2 (in bits) + DWORD dwNameHashArraySize; // Size of NameHash2 table, in bytes + DWORD dwFlagCount; // Number of flags in the following array + +} TMPQBetHeader; // // MPQ data bitmap, can be found at (FileSize - sizeof(TMPQBlockMap)) @@ -675,175 +793,185 @@ typedef struct _TMPQExtTable // typedef struct _TMPQBitmap { - DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE) - DWORD dwAlways3; // Unknown, seems to always have value of 3 - DWORD dwBuildNumber; // Game build number for that MPQ - DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map - DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map - DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) + DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE) + DWORD dwAlways3; // Unknown, seems to always have value of 3 + DWORD dwBuildNumber; // Game build number for that MPQ + DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map + DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map + DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) } TMPQBitmap; // Structure for parsed HET table typedef struct _TMPQHetTable { - TBitArray * pBetIndexes; // Bit array of indexes to BET tables - LPBYTE pHetHashes; // Array of HET hashes. Each entry has size of 1 byte - ULONGLONG AndMask64; // AND mask used for calculating file name hash - ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash - - DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits) - DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes - DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits) - DWORD dwFileCount; // Number of occupied entries in the HET table - DWORD dwHashTableSize; // Number of entries in pBetHashes - DWORD dwHashBitSize; // Effective number of bits in the hash + TBitArray * pBetIndexes; // Bit array of FileIndex values + LPBYTE pNameHashes; // Array of NameHash1 values (NameHash1 = upper 8 bits of FileName hashe) + ULONGLONG AndMask64; // AND mask used for calculating file name hash + ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash + + DWORD dwEntryCount; // Number of occupied entries in the HET table + DWORD dwTotalCount; // Number of entries in both NameHash and FileIndex table + DWORD dwNameHashBitSize; // Size of the name hash entry (in bits) + DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits) + DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes + DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits) } TMPQHetTable; // Structure for parsed BET table typedef struct _TMPQBetTable { - TBitArray * pBetHashes; // Array of BET hashes - TBitArray * pFileTable; // Bit-based file table - LPDWORD pFileFlags; // Array of file flags - - DWORD dwTableEntrySize; // Size of one table entry, in bits - DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry - DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry - DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry - DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry - DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry - DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry - DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry - DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry - DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry - DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry - DWORD dwBetHashSizeTotal; // Total size of bet hash - DWORD dwBetHashSizeExtra; // Extra bits in the bet hash - DWORD dwBetHashSize; // Effective size of the bet hash - DWORD dwFileCount; // Number of files (usually equal to maximum number of files) - DWORD dwFlagCount; // Number of entries in pFileFlags + TBitArray * pNameHashes; // Array of NameHash2 entries (lower 24 bits of FileName hash) + TBitArray * pFileTable; // Bit-based file table + LPDWORD pFileFlags; // Array of file flags + + DWORD dwTableEntrySize; // Size of one table entry, in bits + DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry + DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry + DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry + DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry + DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry + DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry + DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry + DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry + DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry + DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry + DWORD dwBitTotal_NameHash2; // Total size of the NameHash2 + DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2 + DWORD dwBitCount_NameHash2; // Effective size of the NameHash2 + DWORD dwEntryCount; // Number of entries + DWORD dwFlagCount; // Number of fil flags in pFileFlags } TMPQBetTable; // Archive handle structure typedef struct _TMPQArchive { - TFileStream * pStream; // Open stream for the MPQ - - ULONGLONG UserDataPos; // Position of user data (relative to the begin of the file) - ULONGLONG MpqPos; // MPQ header offset (relative to the begin of the file) - - struct _TMPQArchive * haPatch; // Pointer to patch archive, if any - struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any - char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs - size_t cchPatchPrefix; // Length of the patch prefix, in characters - - TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file) - TMPQHeader * pHeader; // MPQ file header - TMPQBitmap * pBitmap; // MPQ bitmap - TMPQHash * pHashTable; // Hash table - TMPQHetTable * pHetTable; // Het table - TFileEntry * pFileTable; // File table - HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash + TFileStream * pStream; // Open stream for the MPQ + + ULONGLONG UserDataPos; // Position of user data (relative to the begin of the file) + ULONGLONG MpqPos; // MPQ header offset (relative to the begin of the file) + + struct _TMPQArchive * haPatch; // Pointer to patch archive, if any + struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any + char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs + size_t cchPatchPrefix; // Length of the patch prefix, in characters + + TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file) + TMPQHeader * pHeader; // MPQ file header + TMPQBitmap * pBitmap; // MPQ bitmap + TMPQHash * pHashTable; // Hash table + TMPQHetTable * pHetTable; // HET table + TFileEntry * pFileTable; // File table + HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash - TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found + TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found BYTE HeaderData[MPQ_HEADER_SIZE_V4]; // Storage for MPQ header DWORD dwHETBlockSize; DWORD dwBETBlockSize; - DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one - DWORD dwMaxFileCount; // Maximum number of files in the MPQ - DWORD dwHashIndexMask; // Mask for converting MPQ_HASH_TABLE_INDEX into real index - DWORD dwSectorSize; // Default size of one file sector - DWORD dwFileFlags1; // Flags for (listfile) - DWORD dwFileFlags2; // Flags for (attributes) - DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX - DWORD dwFlags; // See MPQ_FLAG_XXXXX - DWORD dwSubType; // See MPQ_SUBTYPE_XXX - - SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files - void * pvAddFileUserData; // User data thats passed to the callback - - SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive - ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call - ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted - void * pvCompactUserData; // User data thats passed to the callback + DWORD dwBitmapSize; // sizeof(TMPQBitmap) + size of the bit array + DWORD dwMaxFileCount; // Maximum number of files in the MPQ. Also total size of the file table. + DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one + DWORD dwReservedFiles; // Number of entries reserved for internal MPQ files (listfile, attributes) + DWORD dwSectorSize; // Default size of one file sector + DWORD dwFileFlags1; // Flags for (listfile) + DWORD dwFileFlags2; // Flags for (attributes) + DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX + DWORD dwFlags; // See MPQ_FLAG_XXXXX + DWORD dwSubType; // See MPQ_SUBTYPE_XXX + + SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files + void * pvAddFileUserData; // User data thats passed to the callback + + SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive + ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call + ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted + void * pvCompactUserData; // User data thats passed to the callback } TMPQArchive; // File handle structure typedef struct _TMPQFile { - TFileStream * pStream; // File stream. Only used on local files - TMPQArchive * ha; // Archive handle - TFileEntry * pFileEntry; // File entry for the file - DWORD dwFileKey; // Decryption key - DWORD dwFilePos; // Current file position - ULONGLONG RawFilePos; // Offset in MPQ archive (relative to file begin) - ULONGLONG MpqFilePos; // Offset in MPQ archive (relative to MPQ header) - DWORD dwMagic; // 'FILE' - - struct _TMPQFile * hfPatchFile; // Pointer to opened patch file - TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file - LPBYTE pbFileData; // Loaded and patched file data. Only used if the file is a patch file - DWORD cbFileData; // Size of loaded patched data - - TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table - DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files. - DWORD * SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector - DWORD dwSectorCount; // Number of sectors in the file - DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ - DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry) - - LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content - DWORD dwSectorOffs; // File position of currently loaded file sector - DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size - - unsigned char hctx[HASH_STATE_SIZE];// Hash state for MD5. Used when saving file to MPQ - DWORD dwCrc32; // CRC32 value, used when saving file to MPQ - - bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs - bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file - bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile - bool bErrorOccured; // If true, then at least one error occured during saving the file to the archive + TFileStream * pStream; // File stream. Only used on local files + TMPQArchive * ha; // Archive handle + TFileEntry * pFileEntry; // File entry for the file + DWORD dwFileKey; // Decryption key + DWORD dwFilePos; // Current file position + ULONGLONG RawFilePos; // Offset in MPQ archive (relative to file begin) + ULONGLONG MpqFilePos; // Offset in MPQ archive (relative to MPQ header) + DWORD dwMagic; // 'FILE' + + struct _TMPQFile * hfPatch; // Pointer to opened patch file + TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file + LPBYTE pbFileData; // Loaded and patched file data. Only used if the file is a patch file + DWORD cbFileData; // Size of loaded patched data + + TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table + DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files. + DWORD * SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector + DWORD dwCompression0; // Compression that will be used on the first file sector + DWORD dwSectorCount; // Number of sectors in the file + DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ + DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry) + + LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content + DWORD dwSectorOffs; // File position of currently loaded file sector + DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size + + unsigned char hctx[HASH_STATE_SIZE]; // Hash state for MD5. Used when saving file to MPQ + DWORD dwCrc32; // CRC32 value, used when saving file to MPQ + + int nAddFileError; // Result of the "Add File" operations + + bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs + bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file + bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile } TMPQFile; // Structure for SFileFindFirstFile and SFileFindNextFile typedef struct _SFILE_FIND_DATA { - char cFileName[MAX_PATH]; // Full name of the found file - char * szPlainName; // Plain name of the found file - DWORD dwHashIndex; // Hash table index for the file - DWORD dwBlockIndex; // Block table index for the file - DWORD dwFileSize; // File size in bytes - DWORD dwFileFlags; // MPQ file flags - DWORD dwCompSize; // Compressed file size - DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present) - DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present) - LCID lcLocale; // Locale version + char cFileName[MAX_PATH]; // Full name of the found file + char * szPlainName; // Plain name of the found file + DWORD dwHashIndex; // Hash table index for the file + DWORD dwBlockIndex; // Block table index for the file + DWORD dwFileSize; // File size in bytes + DWORD dwFileFlags; // MPQ file flags + DWORD dwCompSize; // Compressed file size + DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present) + DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present) + LCID lcLocale; // Locale version } SFILE_FIND_DATA, *PSFILE_FIND_DATA; typedef struct _SFILE_CREATE_MPQ { - DWORD cbSize; // Size of this structure, in bytes - DWORD dwMpqVersion; // Version of the MPQ to be created - void *pvUserData; // Reserved, must be NULL - DWORD cbUserData; // Reserved, must be 0 - DWORD dwStreamFlags; // Stream flags for creating the MPQ - DWORD dwFileFlags1; // File flags for (listfile). 0 = default - DWORD dwFileFlags2; // File flags for (attributes). 0 = default - DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created - DWORD dwSectorSize; // Sector size for compressed files - DWORD dwRawChunkSize; // Size of raw data chunk - DWORD dwMaxFileCount; // File limit for the MPQ + DWORD cbSize; // Size of this structure, in bytes + DWORD dwMpqVersion; // Version of the MPQ to be created + void *pvUserData; // Reserved, must be NULL + DWORD cbUserData; // Reserved, must be 0 + DWORD dwStreamFlags; // Stream flags for creating the MPQ + DWORD dwFileFlags1; // File flags for (listfile). 0 = default + DWORD dwFileFlags2; // File flags for (attributes). 0 = default + DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created + DWORD dwSectorSize; // Sector size for compressed files + DWORD dwRawChunkSize; // Size of raw data chunk + DWORD dwMaxFileCount; // File limit for the MPQ } SFILE_CREATE_MPQ, *PSFILE_CREATE_MPQ; //----------------------------------------------------------------------------- // Stream support - functions +// UNICODE versions of the file access functions TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags); TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags); -TCHAR * FileStream_GetFileName(TFileStream * pStream); +const TCHAR * FileStream_GetFileName(TFileStream * pStream); + +//#ifdef _UNICODE +//TFileStream * FileStream_CreateFile(const char * szFileName, DWORD dwStreamFlags); +//TFileStream * FileStream_OpenFile(const char * szFileName, DWORD dwStreamFlags); +//#endif + bool FileStream_IsReadOnly(TFileStream * pStream); bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead); bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite); @@ -881,7 +1009,7 @@ LCID WINAPI SFileSetLocale(LCID lcNewLocale); // Functions for archive manipulation bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq); -bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq); +bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq); bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq); bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded); @@ -916,16 +1044,17 @@ bool WINAPI SFileIsPatchedArchive(HANDLE hMpq); // Functions for file manipulation // Reading from MPQ file +bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName); bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile); DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh); DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod); bool WINAPI SFileReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped); bool WINAPI SFileCloseFile(HANDLE hFile); -// Retrieving info about the file -bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName); +// Retrieving info about a file in the archive +bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, SFileInfoClass InfoClass, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded); bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName); -bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, DWORD dwInfoType, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded); +bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass); // High-level extract function bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR * szExtracted, DWORD dwSearchScope); diff --git a/src/StormPort.h b/src/StormPort.h index 4e34280..83d8624 100644 --- a/src/StormPort.h +++ b/src/StormPort.h @@ -169,15 +169,17 @@ #define _tcscpy strcpy #define _tcscat strcat #define _tcsrchr strrchr + #define _tcsstr strstr #define _tprintf printf #define _stprintf sprintf #define _tremove remove #define _stricmp strcasecmp #define _strnicmp strncasecmp + #define _tcsicmp strcasecmp #define _tcsnicmp strncasecmp -#endif // !WIN32 +#endif // !PLATFORM_WINDOWS // 64-bit calls are supplied by "normal" calls on Mac #if defined(PLATFORM_MAC) @@ -221,7 +223,6 @@ #define BSWAP_ARRAY32_UNSIGNED(a,b) {} #define BSWAP_ARRAY64_UNSIGNED(a,b) {} #define BSWAP_PART_HEADER(a) {} - #define BSWAP_TMPQUSERDATA(a) {} #define BSWAP_TMPQHEADER(a,b) {} #define BSWAP_TMPKHEADER(a) {} #else @@ -255,7 +256,6 @@ #define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b)) #define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b)) #define BSWAP_PART_HEADER(a) ConvertPartHeader(a) - #define BSWAP_TMPQUSERDATA(a) ConvertTMPQUserData((a)) #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b)) #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a)) #endif @@ -273,4 +273,12 @@ #define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated(_Text))) #endif +// When a flag is deprecated, use this macro +#ifndef _STORMLIB_NO_DEPRECATE + #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) \ + const STORMLIB_DEPRECATED(#oldflag " is deprecated. Use " #newflag ". To supress this warning, define _STORMLIB_NO_DEPRECATE") type oldflag = (type)newflag; +#else + #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) const type oldflag = (type)newflag; +#endif + #endif // __STORMPORT_H__ diff --git a/test/TLogHelper.cpp b/test/TLogHelper.cpp index 47a4909..bd1bd56 100644 --- a/test/TLogHelper.cpp +++ b/test/TLogHelper.cpp @@ -16,13 +16,14 @@ class TLogHelper { public: - TLogHelper(const char * szTestName); + TLogHelper(const char * szNewMainTitle = NULL, const char * szNewSubTitle = NULL); ~TLogHelper(); #if defined(UNICODE) || defined(UNICODE) // TCHAR-based functions. They are only needed on UNICODE builds. // On ANSI builds is TCHAR = char, so we don't need them at all int PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine); + void PrintProgress(const TCHAR * szFormat, ...); int PrintErrorVa(const TCHAR * szFormat, ...); int PrintError(const TCHAR * szFormat, const TCHAR * szFileName = NULL); #endif // defined(UNICODE) || defined(UNICODE) @@ -34,11 +35,16 @@ class TLogHelper int PrintErrorVa(const char * szFormat, ...); int PrintError(const char * szFormat, const char * szFileName = NULL); + const char * UserString; + unsigned int UserCount; + unsigned int UserTotal; + protected: int GetConsoleWidth(); - const char * szTestName; // Title of the text + const char * szMainTitle; // Title of the text (usually name) + const char * szSubTitle; // Title of the text (can be name of the tested file) size_t nTextLength; // Length of the previous progress message bool bMessagePrinted; }; @@ -46,22 +52,45 @@ class TLogHelper //----------------------------------------------------------------------------- // Constructor and destructor -TLogHelper::TLogHelper(const char * szName) +TLogHelper::TLogHelper(const char * szNewTestTitle, const char * szNewSubTitle) { + UserString = ""; + UserCount = 1; + UserTotal = 1; + // Fill the test line structure - szTestName = szName; + szMainTitle = szNewTestTitle; + szSubTitle = szNewSubTitle; nTextLength = 0; bMessagePrinted = false; - // Show the user that a test is running - PrintProgress("Running test \"%s\" ...", szTestName); + // Print the initial information + if(szMainTitle != NULL) + { + if(szSubTitle != NULL) + printf("Running test %s (%s) ...", szMainTitle, szSubTitle); + else + printf("Running test %s ...", szMainTitle); + } } TLogHelper::~TLogHelper() { - // If no message has been printed, show "OK" - if(bMessagePrinted == false) - PrintMessage("Running test \"%s\" ... OK", szTestName); + const char * szSaveMainTitle = szMainTitle; + const char * szSaveSubTitle = szSubTitle; + + // Set both to NULL so the won't be printed + szMainTitle = NULL; + szSubTitle = NULL; + + // Print the final information + if(szSaveMainTitle != NULL && bMessagePrinted == false) + { + if(szSaveSubTitle != NULL) + PrintMessage("The test %s (%s) succeeded.", szSaveMainTitle, szSaveSubTitle); + else + PrintMessage("The test %s succeeded.", szSaveMainTitle); + } } //----------------------------------------------------------------------------- @@ -71,9 +100,9 @@ TLogHelper::~TLogHelper() #if defined(UNICODE) || defined(UNICODE) int TLogHelper::PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine) { - TCHAR szOneLineBuff[0x200]; - TCHAR * szSaveBuffer; - TCHAR * szBuffer = szOneLineBuff; + TCHAR szFormatBuff[0x200]; + TCHAR szMessage[0x200]; + TCHAR * szBuffer = szFormatBuff; int nRemainingWidth; int nConsoleWidth = GetConsoleWidth(); int nLength = 0; @@ -81,45 +110,58 @@ int TLogHelper::PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bP // Always start the buffer with '\r' *szBuffer++ = '\r'; - szSaveBuffer = szBuffer; // Print the prefix, if needed - if(szTestName != NULL && bPrintPrefix) + if(szMainTitle != NULL && bPrintPrefix) { - while(szTestName[nLength] != 0) - *szBuffer++ = szTestName[nLength++]; + while(szMainTitle[nLength] != 0) + *szBuffer++ = szMainTitle[nLength++]; *szBuffer++ = ':'; *szBuffer++ = ' '; } - // Format the message itself + // Copy the message format itself. Replace %s with "%s", unless it's (%s) if(szFormat != NULL) { - nLength = _vstprintf(szBuffer, szFormat, argList); - szBuffer += nLength; + while(szFormat[0] != 0) + { + if(szFormat[0] == '%' && szFormat[1] == 's' && szFormat[2] != ')') + { + *szBuffer++ = '\"'; + *szBuffer++ = '%'; + *szBuffer++ = 's'; + *szBuffer++ = '\"'; + szFormat += 2; + } + else + { + *szBuffer++ = *szFormat++; + } + } } - // Print the last error, if needed + // Append the last error if(bPrintLastError) { nLength = _stprintf(szBuffer, _T(" (error code: %u)"), nError); szBuffer += nLength; } + // Create the result string + szBuffer[0] = 0; + nLength = _vstprintf(szMessage, szFormatBuff, argList); + szBuffer = szMessage + nLength; + // Shall we pad the string? - if((szBuffer - szSaveBuffer) < nConsoleWidth) + if(nLength < nConsoleWidth) { // Calculate the remaining width - nRemainingWidth = GetConsoleWidth() - (int)(szBuffer - szSaveBuffer) - 1; + nRemainingWidth = nConsoleWidth - nLength - 1; // Pad the string with spaces to fill it up to the end of the line for(int i = 0; i < nRemainingWidth; i++) *szBuffer++ = 0x20; - - // Pad the buffer with backslashes to fill it up to the end of the line - for(int i = 0; i < nRemainingWidth; i++) - *szBuffer++ = 0x08; } // Put the newline, if requested @@ -131,10 +173,19 @@ int TLogHelper::PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bP bMessagePrinted = true; // Spit out the text in one single printf - _tprintf(szOneLineBuff); + _tprintf(szMessage); return nError; } +void TLogHelper::PrintProgress(const TCHAR * szFormat, ...) +{ + va_list argList; + + va_start(argList, szFormat); + PrintWithClreol(szFormat, argList, true, false, false); + va_end(argList); +} + int TLogHelper::PrintErrorVa(const TCHAR * szFormat, ...) { va_list argList; @@ -158,9 +209,9 @@ int TLogHelper::PrintError(const TCHAR * szFormat, const TCHAR * szFileName) int TLogHelper::PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine) { - char szOneLineBuff[0x200]; - char * szSaveBuffer; - char * szBuffer = szOneLineBuff; + char szFormatBuff[0x200]; + char szMessage[0x200]; + char * szBuffer = szFormatBuff; int nRemainingWidth; int nConsoleWidth = GetConsoleWidth(); int nLength = 0; @@ -168,45 +219,58 @@ int TLogHelper::PrintWithClreol(const char * szFormat, va_list argList, bool bPr // Always start the buffer with '\r' *szBuffer++ = '\r'; - szSaveBuffer = szBuffer; // Print the prefix, if needed - if(szTestName != NULL && bPrintPrefix) + if(szMainTitle != NULL && bPrintPrefix) { - while(szTestName[nLength] != 0) - *szBuffer++ = szTestName[nLength++]; + while(szMainTitle[nLength] != 0) + *szBuffer++ = szMainTitle[nLength++]; *szBuffer++ = ':'; *szBuffer++ = ' '; } - // Format the message itself + // Copy the message format itself. Replace %s with "%s", unless it's (%s) if(szFormat != NULL) { - nLength = vsprintf(szBuffer, szFormat, argList); - szBuffer += nLength; + while(szFormat[0] != 0) + { + if(szFormat[0] == '%' && szFormat[1] == 's' && szFormat[2] != ')') + { + *szBuffer++ = '\"'; + *szBuffer++ = '%'; + *szBuffer++ = 's'; + *szBuffer++ = '\"'; + szFormat += 2; + } + else + { + *szBuffer++ = *szFormat++; + } + } } - // Print the last error, if needed + // Append the last error if(bPrintLastError) { nLength = sprintf(szBuffer, " (error code: %u)", nError); szBuffer += nLength; } + // Create the result string + szBuffer[0] = 0; + nLength = vsprintf(szMessage, szFormatBuff, argList); + // Shall we pad the string? - if((szBuffer - szSaveBuffer) < nConsoleWidth) + szBuffer = szMessage + nLength; + if(nLength < nConsoleWidth) { // Calculate the remaining width - nRemainingWidth = GetConsoleWidth() - (int)(szBuffer - szSaveBuffer) - 1; + nRemainingWidth = nConsoleWidth - nLength - 1; // Pad the string with spaces to fill it up to the end of the line for(int i = 0; i < nRemainingWidth; i++) *szBuffer++ = 0x20; - - // Pad the buffer with backslashes to fill it up to the end of the line - for(int i = 0; i < nRemainingWidth; i++) - *szBuffer++ = 0x08; } // Put the newline, if requested @@ -218,7 +282,7 @@ int TLogHelper::PrintWithClreol(const char * szFormat, va_list argList, bool bPr bMessagePrinted = true; // Spit out the text in one single printf - printf(szOneLineBuff); + printf(szMessage, 0); return nError; } diff --git a/test/Test.cpp b/test/Test.cpp index c21a708..41a310b 100644 --- a/test/Test.cpp +++ b/test/Test.cpp @@ -8,6 +8,7 @@ /* 25.03.03 1.00 Lad The first version of StormLibTest.cpp */ /*****************************************************************************/ +#define _CRT_NON_CONFORMING_SWPRINTFS #define _CRT_SECURE_NO_DEPRECATE #define __INCLUDE_CRYPTOGRAPHY__ #define __STORMLIB_SELF__ // Don't use StormLib.lib @@ -20,56 +21,38 @@ #include "../src/StormLib.h" #include "../src/StormCommon.h" +#include "TLogHelper.cpp" // Helper class for showing test results + #ifdef _MSC_VER #pragma warning(disable: 4505) // 'XXX' : unreferenced local function has been removed #pragma comment(lib, "winmm.lib") #endif -#ifndef ERROR_BAD_LENGTH -#define ERROR_INVALID_DATA 13L -#define ERROR_BAD_LENGTH 24L -#endif - //------------------------------------------------------------------------------ // Defines #ifdef PLATFORM_WINDOWS -#define WORK_PATH_ROOT "E:\\Multimedia\\MPQs\\" +#define WORK_PATH_ROOT "E:\\Multimedia\\MPQs" #endif #ifdef PLATFORM_LINUX -#define WORK_PATH_ROOT "/home/ladik/MPQs/" +#define WORK_PATH_ROOT "/home/ladik/MPQs" #endif #ifdef PLATFORM_MAC #define WORK_PATH_ROOT "/Users/sam/StormLib/test" #endif -#ifndef LANG_CZECH -#define LANG_CZECH 0x0405 -#endif - -#define MPQ_SECTOR_SIZE 0x1000 +// Global for the work MPQ +static const char * szMpqSubDir = "1996 - Test MPQs"; +static const char * szMpqPatchDir = "1996 - Test MPQs\\patches"; -#define MAKE_PATH(path) _T(WORK_PATH_ROOT) _T(path) -#define MAKE_PATHA(path) (WORK_PATH_ROOT path) +typedef int (*ARCHIVE_TEST)(const char * szMpqName); //----------------------------------------------------------------------------- -// Unicode MPQ names - -/* Czech */ static const wchar_t szUnicodeName1[] = {0x010C, 0x0065, 0x0073, 0x006B, 0x00FD, _T('.'), _T('m'), _T('p'), _T('q'), 0}; -/* Russian */ static const wchar_t szUnicodeName2[] = {0x0420, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, _T('.'), _T('m'), _T('p'), _T('q'), 0}; -/* Greece */ static const wchar_t szUnicodeName3[] = {0x03B5, 0x03BB, 0x03BB, 0x03B7, 0x03BD, 0x03B9, 0x03BA, 0x03AC, _T('.'), _T('m'), _T('p'), _T('q'), 0}; -/* Chinese */ static const wchar_t szUnicodeName4[] = {0x65E5, 0x672C, 0x8A9E, _T('.'), _T('m'), _T('p'), _T('q'), 0}; -/* Japanese */ static const wchar_t szUnicodeName5[] = {0x7B80, 0x4F53, 0x4E2D, 0x6587, _T('.'), _T('m'), _T('p'), _T('q'), 0}; -/* Arabic */ static const wchar_t szUnicodeName6[] = {0x0627, 0x0644, 0x0639, 0x0639, 0x0631, 0x0628, 0x064A, 0x0629, _T('.'), _T('m'), _T('p'), _T('q'), 0}; - -//----------------------------------------------------------------------------- -// Constants - -static const TCHAR * szWorkDir = MAKE_PATH("Work"); +// Testing data -static unsigned int AddFlags[] = +static DWORD AddFlags[] = { // Compression Encryption Fixed key Single Unit Sector CRC 0 | 0 | 0 | 0 | 0, @@ -99,2266 +82,2625 @@ static unsigned int AddFlags[] = 0xFFFFFFFF }; -//----------------------------------------------------------------------------- -// Local testing functions +static DWORD Compressions[] = +{ + MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN, + MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN, + MPQ_COMPRESSION_PKWARE, + MPQ_COMPRESSION_ZLIB, + MPQ_COMPRESSION_BZIP2 +}; + +static const wchar_t szUnicodeName1[] = { // Czech + 0x010C, 0x0065, 0x0073, 0x006B, 0x00FD, _T('.'), _T('m'), _T('p'), _T('q'), 0 +}; + +static const wchar_t szUnicodeName2[] = { // Russian + 0x0420, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, _T('.'), _T('m'), _T('p'), _T('q'), 0 +}; + +static const wchar_t szUnicodeName3[] = { // Greek + 0x03B5, 0x03BB, 0x03BB, 0x03B7, 0x03BD, 0x03B9, 0x03BA, 0x03AC, _T('.'), _T('m'), _T('p'), _T('q'), 0 +}; -static void clreol() +static const wchar_t szUnicodeName4[] = { // Chinese + 0x65E5, 0x672C, 0x8A9E, _T('.'), _T('m'), _T('p'), _T('q'), 0 +}; + +static const wchar_t szUnicodeName5[] = { // Japanese + 0x7B80, 0x4F53, 0x4E2D, 0x6587, _T('.'), _T('m'), _T('p'), _T('q'), 0 +}; + +static const wchar_t szUnicodeName6[] = { // Arabic + 0x0627, 0x0644, 0x0639, 0x0639, 0x0631, 0x0628, 0x064A, 0x0629, _T('.'), _T('m'), _T('p'), _T('q'), 0 +}; + +static const char * PatchList_WoW_OldWorld13286[] = +{ + "MPQ_2012_v4_OldWorld.MPQ", + "wow-update-oldworld-13154.MPQ", + "wow-update-oldworld-13286.MPQ", + NULL +}; + +static const char * PatchList_WoW15050[] = { + "MPQ_2013_v4_world.MPQ", + "wow-update-13164.MPQ", + "wow-update-13205.MPQ", + "wow-update-13287.MPQ", + "wow-update-13329.MPQ", + "wow-update-13596.MPQ", + "wow-update-13623.MPQ", + "wow-update-base-13914.MPQ", + "wow-update-base-14007.MPQ", + "wow-update-base-14333.MPQ", + "wow-update-base-14480.MPQ", + "wow-update-base-14545.MPQ", + "wow-update-base-14946.MPQ", + "wow-update-base-15005.MPQ", + "wow-update-base-15050.MPQ", + NULL +}; + +static const char * PatchList_WoW16965[] = +{ + "MPQ_2013_v4_locale-enGB.MPQ", + "wow-update-enGB-16016.MPQ", + "wow-update-enGB-16048.MPQ", + "wow-update-enGB-16057.MPQ", + "wow-update-enGB-16309.MPQ", + "wow-update-enGB-16357.MPQ", + "wow-update-enGB-16516.MPQ", + "wow-update-enGB-16650.MPQ", + "wow-update-enGB-16844.MPQ", + "wow-update-enGB-16965.MPQ", + NULL +}; + +//----------------------------------------------------------------------------- +// Local file functions + +// Definition of the path separator #ifdef PLATFORM_WINDOWS - CONSOLE_SCREEN_BUFFER_INFO ScreenInfo; - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - LPTSTR szConsoleLine; - int nConsoleChars; - int i = 0; - - GetConsoleScreenBufferInfo(hConsole, &ScreenInfo); - nConsoleChars = (ScreenInfo.srWindow.Right - ScreenInfo.srWindow.Left); - if(nConsoleChars > 0) - { - szConsoleLine = new TCHAR[nConsoleChars + 3]; - if(szConsoleLine != NULL) - { - szConsoleLine[i++] = '\r'; - for(; i < nConsoleChars; i++) - szConsoleLine[i] = ' '; - szConsoleLine[i++] = '\r'; - szConsoleLine[i] = 0; - - _tprintf(szConsoleLine); - delete [] szConsoleLine; - } +#define PATH_SEPARATOR '\\' // Path separator for Windows platforms +#else +#define PATH_SEPARATOR '/' // Path separator for Windows platforms +#endif + +// This must be the directory where our test MPQs are stored. +// We also expect a subdirectory named +static char szMpqDirectory[MAX_PATH]; +size_t cchMpqDirectory = 0; + +static size_t ConvertSha1ToText(const unsigned char * sha1_digest, char * szSha1Text) +{ + const char * szTable = "0123456789abcdef"; + + for(size_t i = 0; i < SHA1_DIGEST_SIZE; i++) + { + *szSha1Text++ = szTable[(sha1_digest[0] >> 0x04)]; + *szSha1Text++ = szTable[(sha1_digest[0] & 0x0F)]; + sha1_digest++; } -#endif // PLATFORM_WINDOWS + + *szSha1Text = 0; + return (SHA1_DIGEST_SIZE * 2); } -static void PrintfTA(const TCHAR * szFormat, const TCHAR * szStrT, const char * szStrA, int lcLocale = 0) +#ifdef _UNICODE +static const TCHAR * GetShortPlainName(const TCHAR * szFileName) { - TCHAR * szTemp; - TCHAR szBuffer[MAX_PATH]; + const TCHAR * szPlainName = szFileName; + const TCHAR * szPlainEnd = szFileName + _tcslen(szFileName); - // Convert ANSI string to TCHAR - for(szTemp = szBuffer; *szStrA != 0; szTemp++, szStrA++) - szTemp[0] = szStrA[0]; - szTemp[0] = 0; + // If there is terminating slash or backslash, move to it + while(szFileName < szPlainEnd) + { + if(szFileName[0] == _T('\\') || szFileName[0] == _T('/')) + szPlainName = szFileName + 1; + szFileName++; + } - _tprintf(szFormat, szStrT, szBuffer, lcLocale); + // If the name is still too long, cut it + if((szPlainEnd - szPlainName) > 50) + szPlainName = szPlainEnd - 50; + return szPlainName; } -static void MergeLocalPath(TCHAR * szBuffer, const TCHAR * szPart1, const char * szPart2) +static void CreateFullPathName(TCHAR * szBuffer, const char * szSubDir, const char * szFileName) { - // Copy directory name - while(*szPart1 != 0) - *szBuffer++ = *szPart1++; + size_t nLength; + + // Copy the master MPQ directory + mbstowcs(szBuffer, szMpqDirectory, cchMpqDirectory); + szBuffer += cchMpqDirectory; + + // Append the subdirectory, if any + if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0) + { + // No leading or trailing separator must be there + assert(szSubDir[0] != '/' && szSubDir[0] != '\\'); + assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\'); + + // Append file path separator + *szBuffer++ = PATH_SEPARATOR; + + // Copy the subdirectory + mbstowcs(szBuffer, szSubDir, nLength); + szBuffer += nLength; + } + + // Copy the file name, if any + if(szFileName != NULL && (nLength = strlen(szFileName)) != 0) + { + // No path separator can be there + assert(strchr(szFileName, '\\') == NULL); + assert(strchr(szFileName, '/') == NULL); - // Add separator - *szBuffer++ = _T('/'); + // Append file path separator + *szBuffer++ = PATH_SEPARATOR; - // Copy file name - while(*szPart2 != 0) - *szBuffer++ = *szPart2++; + // Copy the subdirectory + mbstowcs(szBuffer, szFileName, nLength); + szBuffer += nLength; + } - // Terminate the string + // Terminate the buffer with zero *szBuffer = 0; } -int GetFirstDiffer(void * ptr1, void * ptr2, int nSize) +TFileStream * FileStream_OpenFile(const char * szFileName, DWORD dwStreamFlags) { - char * buff1 = (char *)ptr1; - char * buff2 = (char *)ptr2; - int nDiffer; + TFileStream * pStream = NULL; + TCHAR * szFileNameT; + size_t nLength = strlen(szFileName); - for(nDiffer = 0; nDiffer < nSize; nDiffer++) + // Allocate buffer for the UNICODE file name + szFileNameT = STORM_ALLOC(TCHAR, nLength + 1); + if(szFileNameT != NULL) { - if(*buff1++ != *buff2++) - return nDiffer; + CopyFileName(szFileNameT, szFileName, nLength); + pStream = FileStream_OpenFile(szFileNameT, dwStreamFlags); + STORM_FREE(szFileNameT); } - return -1; + + // Return what we got + return pStream; } +#endif -static void WINAPI CompactCB(void * /* lpParam */, DWORD dwWork, ULONGLONG BytesDone, ULONGLONG TotalBytes) +static const char * GetShortPlainName(const char * szFileName) { - clreol(); + const char * szPlainName = szFileName; + const char * szPlainEnd = szFileName + strlen(szFileName); - _tprintf(_T("%u of %u "), (DWORD)BytesDone, (DWORD)TotalBytes); - switch(dwWork) + // If there is terminating slash or backslash, move to it + while(szFileName < szPlainEnd) { - case CCB_CHECKING_FILES: - _tprintf(_T("Checking files in archive ...\r")); - break; + if(szFileName[0] == '\\' || szFileName[0] == '/') + szPlainName = szFileName + 1; + szFileName++; + } - case CCB_CHECKING_HASH_TABLE: - _tprintf(_T("Checking hash table ...\r")); - break; + // If the name is still too long, cut it + if((szPlainEnd - szPlainName) > 50) + szPlainName = szPlainEnd - 50; + return szPlainName; +} - case CCB_COPYING_NON_MPQ_DATA: - _tprintf(_T("Copying non-MPQ data ...\r")); - break; +static void CreateFullPathName(char * szBuffer, const char * szSubDir, const char * szFileName) +{ + size_t nLength; - case CCB_COMPACTING_FILES: - _tprintf(_T("Compacting archive ...\r")); - break; + // Copy the master MPQ directory + memcpy(szBuffer, szMpqDirectory, cchMpqDirectory); + szBuffer += cchMpqDirectory; - case CCB_CLOSING_ARCHIVE: - _tprintf(_T("Closing archive ...\r")); - break; + // Append the subdirectory, if any + if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0) + { + // No leading or trailing separator must be there + assert(szSubDir[0] != '/' && szSubDir[0] != '\\'); + assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\'); + + // Append file path separator + *szBuffer++ = PATH_SEPARATOR; + + // Copy the subdirectory + memcpy(szBuffer, szSubDir, nLength); + szBuffer += nLength; + } + + // Copy the file name, if any + if(szFileName != NULL && (nLength = strlen(szFileName)) != 0) + { + // No path separator can be there + assert(strchr(szFileName, '\\') == NULL); + assert(strchr(szFileName, '/') == NULL); + + // Append file path separator + *szBuffer++ = PATH_SEPARATOR; + + // Copy the subdirectory + memcpy(szBuffer, szFileName, nLength); + szBuffer += nLength; } + + // Terminate the buffer with zero + *szBuffer = 0; } -static void GenerateRandomDataBlock(LPBYTE pbBuffer, DWORD cbBuffer) +static int InitializeMpqDirectory(char * argv[], int argc) { - LPBYTE pbBufferEnd = pbBuffer + cbBuffer; - LPBYTE pbPtr = pbBuffer; - DWORD cbBytesToPut = 0; - BYTE ByteToPut = 0; - bool bRandomData = false; + TLogHelper Logger("InitWorkDir"); + TFileStream * pStream; + const char * szWhereFrom = NULL; + const char * szDirName; + TCHAR szFileName[MAX_PATH]; - while(pbPtr < pbBufferEnd) - { - // If there are no bytes to put, we will generate new byte and length - if(cbBytesToPut == 0) - { - bRandomData = false; - switch(rand() % 10) - { - case 0: // A short sequence of zeros - cbBytesToPut = rand() % 0x08; - ByteToPut = 0; - break; +#ifdef _MSC_VER + // Mix the random number generator + srand(GetTickCount()); +#endif - case 1: // A long sequence of zeros - cbBytesToPut = rand() % 0x80; - ByteToPut = 0; - break; + // Retrieve the name of the MPQ directory + if(argc > 1 && argv[1] != NULL) + { + szWhereFrom = "entered at command line"; + szDirName = argv[1]; + } + else + { + szWhereFrom = "default"; + szDirName = WORK_PATH_ROOT; + } - case 2: // A short sequence of non-zeros - cbBytesToPut = rand() % 0x08; - ByteToPut = (BYTE)(rand() % 0x100); - break; + // Copy the name of the MPQ directory. + strcpy(szMpqDirectory, szDirName); + cchMpqDirectory = strlen(szMpqDirectory); - case 3: // A long sequence of non-zeros - cbBytesToPut = rand() % 0x80; - ByteToPut = (BYTE)(rand() % 0x100); - break; + // Cut trailing slashes and/or backslashes + while(cchMpqDirectory > 0 && szMpqDirectory[cchMpqDirectory - 1] == '/' || szMpqDirectory[cchMpqDirectory - 1] == '\\') + cchMpqDirectory--; + szMpqDirectory[cchMpqDirectory] = 0; - case 4: // A short random data - cbBytesToPut = rand() % 0x08; - bRandomData = true; - break; + // Print the work directory info + Logger.PrintMessage("Work directory %s (%s)", szMpqDirectory, szWhereFrom); - case 5: // A long random data - cbBytesToPut = rand() % 0x80; - bRandomData = true; - break; + // Verify if the work MPQ directory is writable + CreateFullPathName(szFileName, NULL, "TestFile.bin"); + pStream = FileStream_CreateFile(szFileName, 0); + if(pStream == NULL) + return Logger.PrintError("MPQ subdirectory is not writable"); - default: // A single random byte - cbBytesToPut = 1; - ByteToPut = (BYTE)(rand() % 0x100); - break; - } - } + // Close the stream + FileStream_Close(pStream); - // Generate random byte, if needed - if(bRandomData) - ByteToPut = (BYTE)(rand() % 0x100); + // Verify if the working directory exists and if there is a subdirectory with the file name + CreateFullPathName(szFileName, szMpqSubDir, "ListFile_Blizzard.txt"); + pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY); + if(pStream == NULL) + return Logger.PrintError(_T("The main listfile (%s) was not found. Check your paths"), szFileName); - // Put next byte to the output buffer - *pbPtr++ = ByteToPut; - cbBytesToPut--; - } + // Close the stream + FileStream_Close(pStream); + return ERROR_SUCCESS; } -static bool CompareArchivedFiles(const char * szFileName, HANDLE hFile1, HANDLE hFile2, DWORD dwBlockSize) +static int GetFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName) { - LPBYTE pbBuffer1 = NULL; - LPBYTE pbBuffer2 = NULL; - DWORD dwRead1; // Number of bytes read (Storm.dll) - DWORD dwRead2; // Number of bytes read (StormLib) - bool bResult1 = false; // Result from Storm.dll - bool bResult2 = false; // Result from StormLib - bool bResult = true; - int nDiff; - - szFileName = szFileName; - - // Allocate buffers - pbBuffer1 = new BYTE[dwBlockSize]; - pbBuffer2 = new BYTE[dwBlockSize]; - - for(;;) - { - // Read the file's content by both methods and compare the result - memset(pbBuffer1, 0, dwBlockSize); - memset(pbBuffer2, 0, dwBlockSize); - bResult1 = SFileReadFile(hFile1, pbBuffer1, dwBlockSize, &dwRead1, NULL); - bResult2 = SFileReadFile(hFile2, pbBuffer2, dwBlockSize, &dwRead2, NULL); - if(bResult1 != bResult2) - { - _tprintf(_T("Different results from SFileReadFile, Mpq1 %u, Mpq2 %u\n"), bResult1, bResult2); - bResult = false; - break; - } + TCHAR * szPatchName; + HANDLE hFile; + TCHAR szPatchChain[0x400]; + int nPatchCount = 0; + int nError = ERROR_SUCCESS; + + // Open the MPQ file + if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) + { + // Notify the user + pLogger->PrintProgress("Verifying patch chain for %s ...", GetShortPlainName(szFileName)); - // Test the number of bytes read - if(dwRead1 != dwRead2) + // Query the patch chain + if(!SFileGetFileInfo(hFile, SFileInfoPatchChain, szPatchChain, sizeof(szPatchChain), NULL)) + nError = pLogger->PrintError("Failed to retrieve the patch chain on %s", szFileName); + + // Is there anything at all in the patch chain? + if(nError == ERROR_SUCCESS && szPatchChain[0] == 0) { - _tprintf(_T("Different bytes read from SFileReadFile, Mpq1 %u, Mpq2 %u\n"), dwRead1, dwRead2); - bResult = false; - break; + pLogger->PrintError("The patch chain for %s is empty", szFileName); + nError = ERROR_FILE_CORRUPT; } - // No more bytes ==> OK - if(dwRead1 == 0) - break; - - // Test the content - if((nDiff = GetFirstDiffer(pbBuffer1, pbBuffer2, dwRead1)) != -1) + // Now calculate the number of patches + if(nError == ERROR_SUCCESS) { - bResult = false; - break; + // Get the pointer to the patch + szPatchName = szPatchChain; + + // Skip the base name + for(;;) + { + // Skip the current name + szPatchName = szPatchName + _tcslen(szPatchName) + 1; + if(szPatchName[0] == 0) + break; + + // Increment number of patches + nPatchCount++; + } } + + SFileCloseFile(hFile); + } + else + { + pLogger->PrintError("Failed to open file %s", szFileName); } - delete [] pbBuffer2; - delete [] pbBuffer1; - return bResult; + return nPatchCount; } -// Random read version -static bool CompareArchivedFilesRR(const char * /* szFileName */, HANDLE hFile1, HANDLE hFile2, DWORD dwBlockSize) +static int VerifyFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, int nExpectedPatchCount) { - const char * szPositions[3] = {"FILE_BEGIN ", "FILE_CURRENT", "FILE_END "}; - LPBYTE pbBuffer1 = NULL; - LPBYTE pbBuffer2 = NULL; - DWORD dwFileSize1; // File size (Storm.dll) - DWORD dwFileSize2; // File size (StormLib) - DWORD dwRead1; // Number of bytes read (Storm.dll) - DWORD dwRead2; // Number of bytes read (StormLib) - bool bResult1 = false; // Result from Storm.dll - bool bResult2 = false; // Result from StormLib - int nError = ERROR_SUCCESS; + int nPatchCount = 0; + + // Retrieve the patch count + pLogger->PrintProgress("Verifying patch count for %s ...", szFileName); + nPatchCount = GetFilePatchCount(pLogger, hMpq, szFileName); - // Test the file size - dwFileSize1 = SFileGetFileSize(hFile1, NULL); - dwFileSize2 = SFileGetFileSize(hFile2, NULL); - if(dwFileSize1 != dwFileSize2) + // Check if there are any patches at all + if(nExpectedPatchCount != 0 && nPatchCount == 0) { - _tprintf(_T("Different size from SFileGetFileSize (file1: %u, file2: %u)\n"), dwFileSize1, dwFileSize2); - return false; + pLogger->PrintMessage("There are no patches beyond %s", szFileName); + return ERROR_FILE_CORRUPT; } - if(dwFileSize1 != 0) + // Check if the number of patches fits + if(nPatchCount != nExpectedPatchCount) { - for(int i = 0; i < 10000; i++) - { - DWORD dwRandom = rand() * rand(); - DWORD dwMoveMethod = dwRandom % 3; - DWORD dwPosition = dwRandom % dwFileSize1; - DWORD dwToRead = dwRandom % dwBlockSize; + pLogger->PrintMessage("Unexpected number of patches for %s", szFileName); + return ERROR_FILE_CORRUPT; + } - // Also test negative seek - if(rand() & 1) - { - int nPosition = (int)dwPosition; - dwPosition = (DWORD)(-nPosition); - } + return ERROR_SUCCESS; +} - // Allocate buffers - pbBuffer1 = new BYTE[dwToRead]; - pbBuffer2 = new BYTE[dwToRead]; +static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONGLONG FileSize, TCHAR * szBuffer) +{ + TFileStream * pStream; - // Set the file pointer - _tprintf(_T("RndRead (%u): pos %8i from %s, size %u ...\r"), i, dwPosition, szPositions[dwMoveMethod], dwToRead); - dwRead1 = SFileSetFilePointer(hFile1, dwPosition, NULL, dwMoveMethod); - dwRead2 = SFileSetFilePointer(hFile2, dwPosition, NULL, dwMoveMethod); - if(dwRead1 != dwRead2) - { - _tprintf(_T("Difference returned by SFileSetFilePointer (file1: %u, file2: %u)\n"), dwRead1, dwRead2); - nError = ERROR_CAN_NOT_COMPLETE; - break; - } + // Notify the user + pLogger->PrintProgress("Creating empty file %s ...", szPlainName); + + // Construct the full path and crete the file + CreateFullPathName(szBuffer, NULL, szPlainName); + pStream = FileStream_CreateFile(szBuffer, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + if(pStream == NULL) + return pLogger->PrintError(_T("Failed to create file %s"), szBuffer); + + // Write the required size + FileStream_SetSize(pStream, FileSize); + FileStream_Close(pStream); + return ERROR_SUCCESS; +} + +static int WriteMpqUserDataHeader( + TLogHelper * pLogger, + TFileStream * pStream, + ULONGLONG ByteOffset, + DWORD dwByteCount) +{ + TMPQUserData UserData; + int nError = ERROR_SUCCESS; + + // Notify the user + pLogger->PrintProgress("Writing user data header..."); + + // Fill the user data header + UserData.dwID = ID_MPQ_USERDATA; + UserData.cbUserDataSize = dwByteCount; + UserData.dwHeaderOffs = (dwByteCount + sizeof(TMPQUserData)); + UserData.cbUserDataHeader = dwByteCount / 2; + if(!FileStream_Write(pStream, &ByteOffset, &UserData, sizeof(TMPQUserData))) + nError = GetLastError(); + return nError; +} + +static int WriteFileData( + TLogHelper * pLogger, + TFileStream * pStream, + ULONGLONG ByteOffset, + ULONGLONG ByteCount) +{ + ULONGLONG SaveByteCount = ByteCount; + ULONGLONG BytesWritten = 0; + LPBYTE pbDataBuffer; + DWORD cbDataBuffer = 0x10000; + int nError = ERROR_SUCCESS; + + // Write some data + pbDataBuffer = new BYTE[cbDataBuffer]; + if(pbDataBuffer != NULL) + { + memset(pbDataBuffer, 0, cbDataBuffer); + strcpy((char *)pbDataBuffer, "This is a test data written to a file."); + + // Perform the write + while(ByteCount > 0) + { + DWORD cbToWrite = (ByteCount > cbDataBuffer) ? cbDataBuffer : (DWORD)ByteCount; - // Read the file's content by both methods and compare the result - bResult1 = SFileReadFile(hFile1, pbBuffer1, dwToRead, &dwRead1, NULL); - bResult2 = SFileReadFile(hFile2, pbBuffer2, dwToRead, &dwRead2, NULL); - if(bResult1 != bResult2) + // Notify the user + pLogger->PrintProgress("Writing file data (%I64u of %I64u) ...", BytesWritten, SaveByteCount); + + // Write the data + if(!FileStream_Write(pStream, &ByteOffset, pbDataBuffer, cbToWrite)) { - _tprintf(_T("Different results from SFileReadFile (file1: %u, file2: %u)\n\n"), bResult1, bResult2); - nError = ERROR_CAN_NOT_COMPLETE; + nError = GetLastError(); break; } - // Test the number of bytes read - if(dwRead1 != dwRead2) + BytesWritten += cbToWrite; + ByteOffset += cbToWrite; + ByteCount -= cbToWrite; + } + + delete [] pbDataBuffer; + } + return nError; +} + +static int CopyFileData( + TLogHelper * pLogger, + TFileStream * pStream1, + TFileStream * pStream2, + ULONGLONG ByteOffset, + ULONGLONG ByteCount) +{ + ULONGLONG BytesCopied = 0; + ULONGLONG EndOffset = ByteOffset + ByteCount; + LPBYTE pbCopyBuffer; + DWORD BytesToRead; + DWORD BlockLength = 0x100000; + int nError = ERROR_SUCCESS; + + // Allocate copy buffer + pbCopyBuffer = STORM_ALLOC(BYTE, BlockLength); + if(pbCopyBuffer != NULL) + { + while(ByteOffset < EndOffset) + { + // Notify the user + pLogger->PrintProgress("Copying %I64u of %I64u ...", BytesCopied, ByteCount); + + // Read source + BytesToRead = ((EndOffset - ByteOffset) > BlockLength) ? BlockLength : (DWORD)(EndOffset - ByteOffset); + if(!FileStream_Read(pStream1, &ByteOffset, pbCopyBuffer, BytesToRead)) { - _tprintf(_T("Different bytes read from SFileReadFile (file1: %u, file2: %u)\n\n"), dwRead1, dwRead2); - nError = ERROR_CAN_NOT_COMPLETE; + nError = GetLastError(); break; } - - // Test the content - if(dwRead1 != 0 && memcmp(pbBuffer1, pbBuffer2, dwRead1)) + + // Write to the destination file + if(!FileStream_Write(pStream2, NULL, pbCopyBuffer, BytesToRead)) { - _tprintf(_T("Different data content from SFileReadFile\n")); - nError = ERROR_CAN_NOT_COMPLETE; + nError = GetLastError(); break; } - delete [] pbBuffer2; - delete [] pbBuffer1; + BytesCopied += BytesToRead; + ByteOffset += BytesToRead; } - } - clreol(); - return (nError == ERROR_SUCCESS) ? true : false; -} - -//----------------------------------------------------------------------------- -// Opening local file - -static int TestOpenLocalFile(const char * szFileName) -{ - HANDLE hFile; - char szRetrievedName[MAX_PATH]; - if(SFileOpenFileEx(NULL, szFileName, SFILE_OPEN_LOCAL_FILE, &hFile)) - { - SFileGetFileName(hFile, szRetrievedName); - SFileCloseFile(hFile); + STORM_FREE(pbCopyBuffer); } - return ERROR_SUCCESS; + return nError; } -//----------------------------------------------------------------------------- -// Partial file reading - -static int TestPartFileRead(const TCHAR * szFileName) +// Support function for copying file +static int CreateMpqCopy( + TLogHelper * pLogger, + const char * szPlainName, + const char * szFileCopy, + TCHAR * szBuffer, + ULONGLONG PreMpqDataSize = 0, + ULONGLONG UserDataSize = 0) { - ULONGLONG ByteOffset; + TFileStream * pStream1; // Source file + TFileStream * pStream2; // Target file + ULONGLONG ByteOffset = 0; ULONGLONG FileSize = 0; - TFileStream * pStream; - BYTE BigBuffer[0x7000]; - BYTE Buffer[0x100]; + TCHAR szFileName1[MAX_PATH]; + TCHAR szFileName2[MAX_PATH]; int nError = ERROR_SUCCESS; - // Open the partial file - pStream = FileStream_OpenFile(szFileName, false); - if(pStream == NULL) - nError = GetLastError(); + // Notify the user + pLogger->PrintProgress("Creating copy of %s ...", szPlainName); - // Get the size of the stream - if(nError == ERROR_SUCCESS) + // Construct both file names. Check if they are not the same + CreateFullPathName(szFileName1, szMpqSubDir, szPlainName); + CreateFullPathName(szFileName2, NULL, szFileCopy); + if(!_tcsicmp(szFileName1, szFileName2)) { - if(!FileStream_GetSize(pStream, &FileSize)) - nError = GetLastError(); + pLogger->PrintError("Failed to create copy of MPQ (the copy name is the same like the original name)"); + return ERROR_CAN_NOT_COMPLETE; } - // Read the last 0x7000 bytes - if(nError == ERROR_SUCCESS) + // Open the source file + pStream1 = FileStream_OpenFile(szFileName1, STREAM_FLAG_READ_ONLY); + if(pStream1 == NULL) { - ByteOffset = FileSize - sizeof(BigBuffer); - if(!FileStream_Read(pStream, &ByteOffset, BigBuffer, sizeof(BigBuffer))) - nError = GetLastError(); + pLogger->PrintError(_T("Failed to open the source file %s"), szFileName1); + return ERROR_CAN_NOT_COMPLETE; } - // Read the last 0x100 bytes - if(nError == ERROR_SUCCESS) + // Create the destination file + pStream2 = FileStream_CreateFile(szFileName2, 0); + if(pStream2 != NULL) { - ByteOffset = FileSize - sizeof(Buffer); - if(!FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer))) - nError = GetLastError(); + // If we should write some pre-MPQ data to the target file, do it + if(PreMpqDataSize != 0) + { + nError = WriteFileData(pLogger, pStream2, ByteOffset, PreMpqDataSize); + ByteOffset += PreMpqDataSize; + } + + // If we should write some MPQ user data, write the header first + if(UserDataSize != 0) + { + nError = WriteMpqUserDataHeader(pLogger, pStream2, ByteOffset, (DWORD)UserDataSize); + ByteOffset += sizeof(TMPQUserData); + + nError = WriteFileData(pLogger, pStream2, ByteOffset, UserDataSize); + ByteOffset += UserDataSize; + } + + // Copy the file data from the source file to the destination file + FileStream_GetSize(pStream1, &FileSize); + if(FileSize != 0) + { + nError = CopyFileData(pLogger, pStream1, pStream2, 0, FileSize); + ByteOffset += FileSize; + } + FileStream_Close(pStream2); } - // Read 0x100 bytes from position (FileSize - 0xFF) - if(nError == ERROR_SUCCESS) + // Close the source file + FileStream_Close(pStream1); + + if(szBuffer != NULL) + _tcscpy(szBuffer, szFileName2); + if(nError != ERROR_SUCCESS) + pLogger->PrintError("Failed to create copy of MPQ"); + return nError; +} + +static void WINAPI AddFileCallback(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall) +{ + TLogHelper * pLogger = (TLogHelper *)pvUserData; + + // Keep compiler happy + bFinalCall = bFinalCall; + + pLogger->PrintProgress("Adding file (%s) (%u of %u) (%u of %u) ...", pLogger->UserString, + pLogger->UserCount, + pLogger->UserTotal, + dwBytesWritten, + dwTotalBytes); +} + +static void WINAPI CompactCallback(void * pvUserData, DWORD dwWork, ULONGLONG BytesDone, ULONGLONG TotalBytes) +{ + TLogHelper * pLogger = (TLogHelper *)pvUserData; + const char * szWork = NULL; + + switch(dwWork) { - ByteOffset = FileSize - sizeof(Buffer) + 1; - if(!FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer))) - nError = GetLastError(); + case CCB_CHECKING_FILES: + szWork = "Checking files in archive"; + break; + + case CCB_CHECKING_HASH_TABLE: + szWork = "Checking hash table"; + break; + + case CCB_COPYING_NON_MPQ_DATA: + szWork = "Copying non-MPQ data"; + break; + + case CCB_COMPACTING_FILES: + szWork = "Compacting files"; + break; + + case CCB_CLOSING_ARCHIVE: + szWork = "Closing archive"; + break; } - FileStream_Close(pStream); - return nError; + if(szWork != NULL) + { + if(pLogger != NULL) + pLogger->PrintProgress("%s (%I64u of %I64u) ...", szWork, BytesDone, TotalBytes); + else + printf("%s (%I64u of %I64u) ... \r", szWork, (DWORD)BytesDone, (DWORD)TotalBytes); + } } //----------------------------------------------------------------------------- -// Compare Huffmann decompression +// MPQ file utilities + +#define TEST_FLAG_LOAD_FILES 0x00000001 // Test function should load all files in the MPQ +#define TEST_FLAG_HASH_FILES 0x00000002 // Test function should load all files in the MPQ +#define TEST_FLAG_PLAY_WAVES 0x00000004 // Play extracted WAVE files +#define TEST_FLAG_MOST_PATCHED 0x00000008 // Find the most patched file struct TFileData { + DWORD dwBlockIndex; DWORD dwFileSize; + DWORD dwFlags; + DWORD dwReserved; // Alignment BYTE FileData[1]; }; -static TFileData * ReadFileContent(const TCHAR * szMpqName, const char * szFileName) +static bool CheckIfFileIsPresent(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, bool bShouldExist) { - TFileData * pFileData = NULL; - HANDLE hMpq = NULL; HANDLE hFile = NULL; - DWORD dwSearchScope = (szMpqName == NULL) ? SFILE_OPEN_LOCAL_FILE : SFILE_OPEN_FROM_MPQ; - DWORD dwBytesRead = 0; - DWORD dwFileSize = 0; - // Open the MPQ, if any - if(szMpqName != NULL) + if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) { - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - return NULL; + if(bShouldExist == false) + pLogger->PrintMessage("The file %s is present, but it should not be", szFileName); + SFileCloseFile(hFile); + return true; + } + else + { + if(bShouldExist) + pLogger->PrintMessage("The file %s is not present, but it should be", szFileName); + return false; } +} - // Open the file - if(SFileOpenFileEx(hMpq, szFileName, dwSearchScope, &hFile)) +static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName, bool bMustSucceed) +{ + TFileStream * pStream; + TFileData * pFileData = NULL; + ULONGLONG FileSize = 0; + size_t nAllocateBytes; + + // Notify the user + if(pLogger != NULL) + pLogger->PrintProgress("Loading local file ..."); + + // Attempt to open the file + pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY); + if(pStream == NULL) { - dwFileSize = SFileGetFileSize(hFile, NULL); - pFileData = (TFileData *)(new BYTE[sizeof(TFileData) + dwFileSize]); + if(pLogger != NULL && bMustSucceed == true) + pLogger->PrintError("Failed to open the file %s", szFileName); + return NULL; + } + + // Verify the size + FileStream_GetSize(pStream, &FileSize); + if((FileSize >> 0x20) == 0) + { + // Allocate space for the file + nAllocateBytes = sizeof(TFileData) + (size_t)FileSize; + pFileData = (TFileData *)STORM_ALLOC(BYTE, nAllocateBytes); if(pFileData != NULL) { - SFileReadFile(hFile, pFileData->FileData, dwFileSize, &dwBytesRead, NULL); - pFileData->dwFileSize = dwFileSize; + // Make sure it;s properly zeroed + memset(pFileData, 0, nAllocateBytes); + pFileData->dwFileSize = (DWORD)FileSize; + + // Load to memory + if(!FileStream_Read(pStream, NULL, pFileData->FileData, pFileData->dwFileSize)) + { + STORM_FREE(pFileData); + pFileData = NULL; + } } - SFileCloseFile(hFile); } - if(hMpq != NULL) - SFileCloseArchive(hMpq); + FileStream_Close(pStream); return pFileData; } -static TFileData * CompressFileContent(TFileData * pDecompressed, BYTE Compression1, BYTE Compression2) +static TFileData * LoadMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName) { - TFileData * pRecompressed; - LPBYTE pbOutBuffer; - LPBYTE pbInBuffer; - DWORD dwBytesRemaining; - int cbOutBuffer; - int cbInBuffer; - - // Allocate buffer for compressed data - pRecompressed = (TFileData *)(new BYTE[sizeof(TFileData) + pDecompressed->dwFileSize]); - if(pRecompressed != NULL) - { - // Set the source and target - dwBytesRemaining = pDecompressed->dwFileSize; - pbInBuffer = pDecompressed->FileData; - pbOutBuffer = pRecompressed->FileData; - - while(dwBytesRemaining != 0) + TFileData * pFileData = NULL; + HANDLE hFile; + DWORD dwFileSizeHi = 0xCCCCCCCC; + DWORD dwFileSizeLo = 0; + DWORD dwBytesRead; + int nError = ERROR_SUCCESS; + + // Notify the user that we are loading a file from MPQ + pLogger->PrintProgress("Loading file %s ...", GetShortPlainName(szFileName)); + + // Open the file from MPQ + if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) + nError = pLogger->PrintError("Failed to open the file %s", szFileName); + + // Get the size of the file + if(nError == ERROR_SUCCESS) + { + dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi); + if(dwFileSizeLo == SFILE_INVALID_SIZE || dwFileSizeHi != 0) + nError = pLogger->PrintError("Failed to query the file size"); + } + + // Allocate buffer for the file content + if(nError == ERROR_SUCCESS) + { + pFileData = (TFileData *)STORM_ALLOC(BYTE, sizeof(TFileData) + dwFileSizeLo); + if(pFileData == NULL) { - // Perform the compression - cbOutBuffer = cbInBuffer = (int)STORMLIB_MIN(dwBytesRemaining, 0x1000); - SCompCompress((char *)pbOutBuffer, &cbOutBuffer, (char *)pbInBuffer, cbInBuffer, Compression1, 0x00, 0); - assert(cbOutBuffer < cbInBuffer); - - // Move buffers - dwBytesRemaining -= cbInBuffer; - pbOutBuffer += cbOutBuffer; - pbInBuffer += cbInBuffer; - Compression1 = Compression2; + pLogger->PrintError("Failed to allocate buffer for the file content"); + nError = ERROR_NOT_ENOUGH_MEMORY; } + } - // Put the size of the decompressed part - pRecompressed->dwFileSize = (DWORD)(pbOutBuffer - pRecompressed->FileData); + // get the file index of the MPQ file + if(nError == ERROR_SUCCESS) + { + // Store the file size + memset(pFileData, 0, sizeof(TFileData) + dwFileSizeLo); + pFileData->dwFileSize = dwFileSizeLo; + + // Retrieve the block index and file flags + if(!SFileGetFileInfo(hFile, SFileInfoFileIndex, &pFileData->dwBlockIndex, sizeof(DWORD), NULL)) + nError = pLogger->PrintError("Failed retrieve the file index of %s", szFileName); + if(!SFileGetFileInfo(hFile, SFileInfoFlags, &pFileData->dwFlags, sizeof(DWORD), NULL)) + nError = pLogger->PrintError("Failed retrieve the file flags of %s", szFileName); } - return pRecompressed; + // Load the entire file + if(nError == ERROR_SUCCESS) + { + // Read the file data + SFileReadFile(hFile, pFileData->FileData, dwFileSizeLo, &dwBytesRead, NULL); + if(dwBytesRead != dwFileSizeLo) + nError = pLogger->PrintError("Failed to read the content of the file %s", szFileName); + } + + // Close the file and return what we got + if(hFile != NULL) + SFileCloseFile(hFile); + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return pFileData; } -static int WriteFileContent(const TCHAR * szFileName, TFileData * pFileData) +static bool CompareTwoFiles(TLogHelper * pLogger, TFileData * pFileData1, TFileData * pFileData2) { - TFileStream * pLocalFile = NULL; - int nError = ERROR_SUCCESS; - - // Create the local file - if(nError == ERROR_SUCCESS) + // Compare the file size + if(pFileData1->dwFileSize != pFileData2->dwFileSize) { - pLocalFile = FileStream_CreateFile(szFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pLocalFile == NULL) - nError = GetLastError(); + pLogger->PrintErrorVa(_T("The files have different size (%u vs %u)"), pFileData1->dwFileSize, pFileData2->dwFileSize); + SetLastError(ERROR_FILE_CORRUPT); + return false; } - // Write the file data - if(nError == ERROR_SUCCESS) + // Compare the files + for(DWORD i = 0; i < pFileData1->dwFileSize; i++) { - if(!FileStream_Write(pLocalFile, NULL, pFileData->FileData, pFileData->dwFileSize)) - nError = GetLastError(); + if(pFileData1->FileData[i] != pFileData2->FileData[i]) + { + pLogger->PrintErrorVa(_T("Files are different at offset %08X"), i); + SetLastError(ERROR_FILE_CORRUPT); + return false; + } } - // Close handles and return - if(pLocalFile != NULL) - FileStream_Close(pLocalFile); - return nError; + // The files are identical + return true; } -static int CompareHuffmanCompressions7() +static int SearchArchive( + TLogHelper * pLogger, + HANDLE hMpq, + DWORD dwTestFlags = 0, + DWORD * pdwFileCount = NULL, + LPBYTE pbFileHash = NULL) { - TFileData * pDecompressed1 = NULL; - TFileData * pDecompressed2 = NULL; - TFileData * pRecompressed1 = NULL; - TFileData * pRecompressed2 = NULL; - int nDifference; + SFILE_FIND_DATA sf; + TFileData * pFileData; + HANDLE hFind; + DWORD dwFileCount = 0; + hash_state md5state; + char szMostPatched[MAX_PATH] = ""; + char szListFile[MAX_PATH]; + bool bFound = true; + int nMaxPatchCount = 0; + int nPatchCount = 0; int nError = ERROR_SUCCESS; - // Load the decompressed data - if(nError == ERROR_SUCCESS) + // Construct the full name of the listfile + CreateFullPathName(szListFile, szMpqSubDir, "ListFile_Blizzard.txt"); + + // Prepare hashing + md5_init(&md5state); + + // Initiate the MPQ search + pLogger->PrintProgress("Searching the archive ..."); + hFind = SFileFindFirstFile(hMpq, "*", &sf, szListFile); + if(hFind == NULL) + { + nError = GetLastError(); + nError = (nError == ERROR_NO_MORE_FILES) ? ERROR_SUCCESS : nError; + return nError; + } + + // Perform the search + while(bFound == true) { - pDecompressed1 = ReadFileContent(MAKE_PATH("BroodWar.mpq"), "music\\prdyroom.wav"); - pDecompressed2 = ReadFileContent(NULL, MAKE_PATHA("mpq_decompressed.wav")); - if(pDecompressed1 != NULL && pDecompressed2 != NULL) + // Increment number of files + dwFileCount++; + + if(dwTestFlags & TEST_FLAG_MOST_PATCHED) { - // Compare decompressed size - if(pDecompressed1->dwFileSize == pDecompressed2->dwFileSize) - { - // Compare the data - nDifference = GetFirstDiffer(pDecompressed1->FileData, pDecompressed2->FileData, pDecompressed1->dwFileSize); - if(nDifference != -1) - { - _tprintf(_T("Different decompressed data at offset %08X\n\n"), nDifference); - nError = ERROR_BAD_FORMAT; - } - else - { - _tprintf(_T("Huffmann decompression OK\n")); - } - } - else + // Load the patch count + nPatchCount = GetFilePatchCount(pLogger, hMpq, sf.cFileName); + + // Check if it's greater than maximum + if(nPatchCount > nMaxPatchCount) { - _tprintf(_T("Different decompressed size: %u <-> %u\n"), pDecompressed1->dwFileSize, pDecompressed2->dwFileSize); - nError = ERROR_BAD_LENGTH; + strcpy(szMostPatched, sf.cFileName); + nMaxPatchCount = nPatchCount; } } - else - { - _tprintf(_T("Failed to real one of the compressed files.\n")); - nError = ERROR_CAN_NOT_COMPLETE; - } - } - // Get the recompressed data - if(nError == ERROR_SUCCESS) - { - pRecompressed1 = CompressFileContent(pDecompressed1, MPQ_COMPRESSION_PKWARE, MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN); - pRecompressed2 = ReadFileContent(NULL, MAKE_PATHA("mpq_recompressed.bin")); - if(pRecompressed1 != NULL && pRecompressed2 != NULL) + // Load the file to memory, if required + if(dwTestFlags & TEST_FLAG_LOAD_FILES) { - // Comprare decompressed size - if(pRecompressed1->dwFileSize == pRecompressed2->dwFileSize) + // Load the entire file to the MPQ + pFileData = LoadMpqFile(pLogger, hMpq, sf.cFileName); + if(pFileData == NULL) { - // Compare the data - nDifference = GetFirstDiffer(pRecompressed1->FileData, pRecompressed2->FileData, pRecompressed1->dwFileSize); - if(nDifference != -1) - { - _tprintf(_T("Different recompressed data at offset %08X\n"), nDifference); - nError = ERROR_BAD_FORMAT; - } - else - { - _tprintf(_T("Huffmann recompression OK\n")); - } + nError = pLogger->PrintError("Failed to load the file %s", sf.cFileName); + break; } - else + + // Hash the file data, if needed + if((dwTestFlags & TEST_FLAG_HASH_FILES) && !IsInternalMpqFileName(sf.cFileName)) + md5_process(&md5state, pFileData->FileData, pFileData->dwFileSize); + + // Play sound files, if required + if((dwTestFlags & TEST_FLAG_PLAY_WAVES) && strstr(sf.cFileName, ".wav") != NULL) { - _tprintf(_T("Different recompressed size: %u <-> %u\n"), pRecompressed1->dwFileSize, pRecompressed2->dwFileSize); - nError = ERROR_BAD_LENGTH; +#ifdef _MSC_VER + pLogger->PrintProgress("Playing sound %s", sf.cFileName); + PlaySound((LPCTSTR)pFileData->FileData, NULL, SND_MEMORY); +#endif } + + STORM_FREE(pFileData); } - else - { - _tprintf(_T("Failed to compress the file or read the compressed muster.\n")); - nError = ERROR_CAN_NOT_COMPLETE; - } + + bFound = SFileFindNextFile(hFind, &sf); } - - assert(nError == ERROR_SUCCESS); - - if(pRecompressed1 != NULL) - delete [] pRecompressed1; - if(pRecompressed2 != NULL) - delete [] pRecompressed2; - if(pDecompressed1 != NULL) - delete [] pDecompressed1; - if(pDecompressed2 != NULL) - delete [] pDecompressed2; - return ERROR_SUCCESS; + SFileFindClose(hFind); + + // Give the file count, if required + if(pdwFileCount != NULL) + pdwFileCount[0] = dwFileCount; + + // Give the hash, if required + if(pbFileHash != NULL && (dwTestFlags & TEST_FLAG_HASH_FILES)) + md5_done(&md5state, pbFileHash); + + return nError; } -// Just some binary data -static unsigned char Decompressed[2048] = +static int CreateNewArchive_FullPath(TLogHelper * pLogger, const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) { - 0x2E, 0x56, 0xF8, 0x44, 0xB0, 0xE2, 0x58, 0x20, 0x44, 0xBC, 0xE2, 0x46, 0x00, 0x44, 0x4A, 0x1D, - 0x59, 0x44, 0xC4, 0xE3, 0x00, 0x00, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x34, 0xE2, 0x01, 0x01, - 0x01, 0x6C, 0x44, 0xB4, 0xE2, 0x58, 0x01, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0xB4, 0xE2, 0x58, - 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x40, 0x58, - 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x58, 0x44, 0x04, 0xB4, 0xE0, 0x58, 0x40, - 0x44, 0xC0, 0xC3, 0x58, 0x58, 0x08, 0x8B, 0x8A, 0x4B, 0xDE, 0xF6, 0x80, 0x92, 0x5B, 0xDC, 0x2F, - 0xA1, 0x78, 0x10, 0x10, 0x65, 0x1C, 0x32, 0x8A, 0xA2, 0xF4, 0x02, 0xBC, 0x02, 0x02, 0x9A, 0x7A, - 0x06, 0x9A, 0x9E, 0x01, 0xF4, 0x38, 0x20, 0xB2, 0xA2, 0x5C, 0xA2, 0x01, 0x9C, 0x8E, 0xF4, 0x06, - 0xB2, 0x98, 0x80, 0x80, 0xF4, 0x08, 0x84, 0x8C, 0x7E, 0x40, 0x06, 0x18, 0xEA, 0x66, 0x10, 0x3E, - 0x3E, 0x08, 0x20, 0xAE, 0xF6, 0x10, 0x2A, 0xB4, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, - 0x00, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0x04, 0xB4, 0xE2, 0x58, 0x80, 0x44, 0xB4, 0xE2, 0x58, 0x44, - 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x10, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, - 0x08, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x80, 0x80, 0x58, 0x44, 0xB4, - 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, - 0x00, 0x00, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, - 0x44, 0xB4, 0x01, 0x01, 0x01, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x58, 0x44, 0xB4, 0xE2, 0x58, - 0x44, 0xB4, 0xE2, 0x58, 0x02, 0x02, 0x44, 0xB4, 0xE2, 0x20, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, - 0x01, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x08, - 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, - 0x40, 0x40, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, - 0x01, 0x01, 0x58, 0x44, 0x14, 0x68, 0x58, 0x44, 0x2C, 0xE0, 0x5C, 0x01, 0x01, 0x01, 0x44, 0x20, - 0x85, 0x3B, 0xD3, 0xB4, 0xE2, 0x02, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x20, 0x44, 0x75, 0xE3, - 0x44, 0x08, 0x47, 0xA2, 0xE0, 0x5C, 0x76, 0xB4, 0x00, 0xBA, 0x7E, 0x44, 0x40, 0xB4, 0xB2, 0x4B, - 0x44, 0xB4, 0x08, 0x08, 0xE2, 0x58, 0x44, 0xB4, 0xC2, 0x58, 0x01, 0x44, 0xB4, 0xC2, 0x80, 0x58, - 0x44, 0xB4, 0x62, 0x10, 0x7E, 0x44, 0xB4, 0xE2, 0xD8, 0x10, 0x44, 0xB4, 0x20, 0xC2, 0x58, 0x44, - 0xB4, 0xE6, 0x58, 0x80, 0x80, 0x44, 0xBE, 0xE2, 0x58, 0x44, 0x04, 0xB4, 0xE2, 0x58, 0x44, 0xBE, - 0xE2, 0x58, 0x44, 0x10, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x04, 0x82, 0x63, 0x44, 0xB4, 0xEA, - 0x58, 0x80, 0x80, 0x44, 0xAE, 0x6E, 0x42, 0x10, 0x44, 0xB0, 0xE2, 0x58, 0x44, 0x01, 0x01, 0xB4, - 0xE2, 0x78, 0x44, 0xB4, 0x62, 0x58, 0x04, 0x44, 0xB4, 0xE2, 0x78, 0x44, 0xB4, 0x01, 0x01, 0xC2, - 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x58, 0x44, 0x94, 0x02, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, - 0xB4, 0x80, 0x80, 0xE2, 0x58, 0x44, 0x2C, 0xDC, 0x63, 0x44, 0x02, 0x02, 0xDC, 0xE4, 0x58, 0x44, - 0xB4, 0x02, 0x02, 0xE2, 0x63, 0x44, 0x64, 0xD8, 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x04, 0x58, 0x44, - 0xB4, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0x00, 0xE2, 0x58, 0x44, 0x02, - 0xB4, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x10, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x44, 0xB4, - 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x00, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x80, - 0x80, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x2C, 0x01, 0x01, 0xBC, 0x63, 0x44, 0x0C, 0xE2, 0x58, - 0x44, 0x20, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0xB4, 0xE2, 0x58, - 0x44, 0xB4, 0x20, 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x01, 0xE2, 0x58, 0x44, - 0xB4, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x00, 0x00, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x58, 0x44, - 0xB4, 0xE2, 0x80, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x08, 0x08, 0xE2, 0x58, 0x44, 0xE8, - 0x0A, 0x92, 0xB4, 0x04, 0x04, 0x5C, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x62, 0x44, 0xB4, 0xC2, 0x01, - 0x01, 0x58, 0x44, 0xB4, 0xBE, 0x08, 0x41, 0x44, 0xB4, 0xEA, 0x80, 0x80, 0x58, 0x44, 0x14, 0x68, - 0xDE, 0x20, 0x1C, 0x7C, 0x20, 0x20, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0xF5, 0xE2, 0x01, - 0x58, 0x84, 0xE8, 0x06, 0xBE, 0xA0, 0x72, 0xE2, 0x02, 0x02, 0x58, 0x44, 0xB4, 0x82, 0x58, 0x44, - 0xB4, 0xE2, 0x63, 0x20, 0x20, 0x44, 0xB4, 0x82, 0x58, 0x44, 0xB4, 0x86, 0x02, 0x02, 0x41, 0x44, - 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x01, 0x58, 0x40, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xF5, - 0xE2, 0x58, 0x08, 0x08, 0x84, 0xB4, 0xE2, 0x58, 0x08, 0x44, 0xB4, 0xE2, 0x04, 0x58, 0x44, 0xB4, - 0xE2, 0x58, 0x10, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x20, 0x44, 0x80, 0xB4, - 0xE2, 0x58, 0x44, 0xB4, 0x40, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x80, 0x80, 0xE2, - 0x58, 0x44, 0xB4, 0x20, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0x20, 0xB4, 0xE2, 0x58, - 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x20, 0x58, 0x44, 0xB4, 0x04, 0xE2, 0x58, - 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x10, 0x58, 0x44, 0xB4, 0x00, 0xE2, 0x58, 0x44, - 0xB4, 0xE2, 0x58, 0x08, 0x44, 0xB4, 0x40, 0xE2, 0x58, 0x44, 0xB4, 0x08, 0xE2, 0x58, 0x80, 0x44, - 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x80, 0x80, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, - 0x04, 0x04, 0x58, 0x44, 0xB4, 0xE2, 0x08, 0x58, 0x44, 0xB4, 0x04, 0xE2, 0x58, 0x44, 0xB4, 0xE2, - 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x02, 0x02, 0x02, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x04, 0xE2, - 0x58, 0x44, 0xB4, 0xE2, 0x10, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x10, 0x10, 0xB4, 0xE2, 0x58, - 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x80, 0x80, - 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x40, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, - 0x80, 0x80, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x44, 0xB4, 0xE2, 0x01, 0x58, 0x44, 0x40, - 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, - 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, - 0x04, 0x04, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x04, 0x58, 0x44, 0xB4, 0x02, 0xE2, - 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x04, 0x04, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x58, - 0x44, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0x20, 0xB4, 0xE2, 0x58, 0x44, - 0x20, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x08, 0x08, - 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0x02, 0xE2, 0x58, 0x44, 0xB4, - 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0x01, 0x01, 0xB4, - 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x10, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0x01, 0xE2, - 0x58, 0x44, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x08, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, - 0xB4, 0x01, 0x01, 0xE2, 0x04, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x01, 0x44, 0xB4, 0xE2, 0x58, 0x44, - 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0x02, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, - 0xE2, 0x20, 0x20, 0x58, 0x44, 0xB4, 0x08, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0x20, - 0xB4, 0xE2, 0x58, 0x20, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x08, 0x08, 0x58, 0x44, 0xB4, - 0xE2, 0x58, 0x01, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, - 0x58, 0x40, 0x44, 0xB4, 0x20, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x10, 0x10, 0xE2, - 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x10, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, - 0x10, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x02, 0x02, 0x02, 0x58, - 0x44, 0xB4, 0xE2, 0x58, 0x02, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, - 0xE2, 0x08, 0x08, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0xB4, - 0xE2, 0x58, 0x44, 0xB4, 0x20, 0x20, 0xE2, 0x58, 0x04, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, - 0xE2, 0x58, 0x44, 0xC4, 0xEB, 0x05, 0x10, 0x10, 0xFF, 0xB5, 0x42, 0x90, 0xBA, 0xDE, 0x20, 0xE2, - 0x58, 0x44, 0x40, 0xB4, 0x2A, 0x40, 0x4A, 0x0F, 0xB4, 0x80, 0xE2, 0x58, 0x44, 0xD2, 0x40, 0x62, - 0x4B, 0x55, 0x14, 0x68, 0x02, 0xDE, 0x9A, 0x00, 0x6E, 0x02, 0x9A, 0x82, 0x5C, 0x86, 0x00, 0x58, - 0x62, 0x0B, 0xBA, 0x7F, 0xA9, 0x00, 0x00, 0x00, 0xFF, 0xCC, 0x38, 0x71, 0xD8, 0x90, 0x4C, 0x01, - 0x01, 0x27, 0xB2, 0x6B, 0x10, 0x30, 0x43, 0xC4, 0x9B, 0xB1, 0x1E, 0x45, 0x02, 0x02, 0x34, 0xDF, - 0x72, 0x36, 0x00, 0xF5, 0xDC, 0xCE, 0x7A, 0x01, 0x93, 0xEF, 0x4F, 0xB0, 0xDA, 0x22, 0xA4, 0xBB, - 0x01, 0x01, 0xC5, 0x47, 0x27, 0x50, 0x80, 0x23, 0x7B, 0xC1, 0x5F, 0x00, 0x13, 0x47, 0xC1, 0x52, - 0x46, 0xD9, 0xAB, 0xB6, 0x20, 0x20, 0x8F, 0x04, 0xEF, 0xD5, 0x9E, 0x2B, 0x73, 0x5F, 0x91, 0xF4, - 0x40, 0x40, 0x9C, 0xB2, 0x9B, 0x76, 0xDE, 0xDF, 0x90, 0x01, 0x01, 0xFC, 0x27, 0x9C, 0x66, 0x17, - 0x6B, 0xB1, 0x5E, 0x00, 0x00, 0xEC, 0x8E, 0x0A, 0xEF, 0x1B, 0xFA, 0xE3, 0xF8, 0xDF, 0x08, 0x08, - 0x8E, 0x38, 0x77, 0x24, 0x40, 0xCA, 0x10, 0x2B, 0x10, 0x9D, 0xF7, 0x8E, 0x67, 0xD1, 0x20, 0xEF, - 0xF2, 0x94, 0x8F, 0xFC, 0x70, 0xF8, 0x3F, 0x01, 0x01, 0x01, 0xF7, 0xBF, 0x6B, 0xD1, 0x50, 0xA5, - 0x10, 0x66, 0x6A, 0x6F, 0xB0, 0x69, 0x68, 0x21, 0xAA, 0x01, 0x01, 0x17, 0x00, 0x48, 0x85, 0x6A, - 0xBB, 0xAA, 0x51, 0x0F, 0x01, 0xCE, 0xBB, 0x08, 0x08, 0x1C, 0x2D, 0x84, 0x04, 0x4A, 0xBD, 0x01, - 0x7E, 0x88, 0x04, 0x04, 0xE4, 0x29, 0x3E, 0x6C, 0xD0, 0x10, 0x49, 0xA7, 0x93, 0x09, 0xA7, 0x52, - 0x48, 0x01, 0x01, 0x4A, 0x24, 0xA5, 0x5E, 0xBE, 0x04, 0x0C, 0x77, 0x01, 0x23, 0xE0, 0x5D, 0xFB, - 0x02, 0xD0, 0x1F, 0xD2, 0x3A, 0xFC, 0x5F, 0x08, 0x9A, 0xF2, 0x20, 0x77, 0x54, 0x95, 0x22, 0xD2, - 0x32, 0x17, 0x9D, 0x02, 0x02, 0x6B, 0xC0, 0xD6, 0x5B, 0xF4, 0xE5, 0x50, 0x00, 0x00, 0x9A, 0xF7, - 0xC6, 0xD6, 0x02, 0x5F, 0x8D, 0x71, 0x80, 0xD8, 0xD2, 0x87, 0x40, 0x7C, 0xF3, 0xB2, 0xBC, 0x84, - 0x4B, 0x83, 0xCE, 0x80, 0x80, 0xE3, 0x71, 0x89, 0xAF, 0xE2, 0x01, 0xF8, 0x1B, 0x86, 0x66, 0x4F, - 0x40, 0x0F, 0x73, 0x4C, 0x4E, 0xF3, 0x0B, 0x01, 0x01, 0x18, 0x7F, 0x04, 0xBD, 0x7D, 0x51, 0x3C, - 0xEB, 0x5B, 0x04, 0xC6, 0xFE, 0x7B, 0x9B, 0x2F, 0x86, 0x62, 0x02, 0x02, 0x56, 0x35, 0x20, 0x05, - 0xE6, 0x1C, 0x4C, 0x68, 0x01, 0x29, 0xEA, 0xB0, 0xD3, 0xB0, 0xB5, 0xFE, 0x40, 0x40, 0x9D, 0xE0, - 0xC4, 0x6B, 0xF8, 0x6C, 0x00, 0x00, 0xE4, 0x74, 0x6A, 0x95, 0x04, 0x7A, 0xDC, 0x8C, 0x1B, 0x02, - 0xBB, 0x8C, 0x29, 0x1F, 0x01, 0xC6, 0xA7, 0x9C, 0x9C, 0xC7, 0x02, 0xB8, 0xD1, 0x10, 0x73, 0xA5, - 0x95, 0x92, 0xCC, 0xE8, 0x10, 0x67, 0x14, 0x39, 0xB3, 0x22, 0xB2, 0xAB, 0x00, 0x00, 0xF1, 0x5E, - 0x56, 0x90, 0x5F, 0x9D, 0xC5, 0x00, 0x00, 0xA4, 0xA3, 0x36, 0x20, 0xAF, 0x4E, 0x5A, 0xB3, 0x96, - 0x41, 0xDE, 0x04, 0x04, 0x77, 0x92, 0x16, 0x8D, 0x02, 0x2D, 0xB9, 0xCA, 0xC2, 0x40, 0xEF, 0x88, - 0x5D, 0x5F, 0x81, 0x93, 0xF7, 0x5A, 0x08, 0x08, 0xE3, 0xA3, 0x37, 0xE4, 0x10, 0xE1, 0xEA, 0x8B, - 0xDE, 0x02, 0xAC, 0x2D, 0xFE, 0x9C, 0x09, 0x05, 0xBF, 0xA4, 0x40, 0x40, 0xF0, 0x02, 0x92, 0xBB, - 0x5D, 0x42, 0x9D, 0x83, 0xF5, 0x2C, 0x40, 0x40, 0x3A, 0xE4, 0x52, 0x62, 0x36, 0x53, 0x8E, 0x40, - 0x40, 0x71, 0x91, 0xB7, 0x3E, 0xC0, 0x55, 0x88, 0x1C, 0x40, 0x40, 0xEF, 0x3F, 0xB4, 0x70, 0xF8, - 0xE9, 0x2A, 0x7A, 0x4D, 0x02, 0x02, 0xC0, 0x9B, 0x1C, 0x05, 0x08, 0xE2, 0x6C, 0x5E, 0x80, 0xDC, - 0x2A, 0xC0, 0x91, 0x05, 0x10, 0x10, 0x1B, 0x85, 0x09, 0x47, 0xBC, 0x27, 0x38, 0x02, 0x04, 0x04, - 0xCC, 0x7F, 0x55, 0x1A, 0xF0, 0x06, 0x02, 0xDF, 0xC6, 0xF8, 0xB5, 0x18, 0xB5, 0xF1, 0x97, 0x08, - 0x08, 0x08, 0x20, 0xF5, 0x1F, 0xCD, 0x02, 0x20, 0x07, 0xC5, 0x98, 0x44, 0x57, 0x21, 0x00, 0x24, - 0xBC, 0x78, 0x10, 0xD6, 0xA9, 0xCC, 0xD1, 0xC4, 0x20, 0x20, 0x30, 0x61, 0x51, 0xEC, 0x5F, 0x80, - 0x06, 0xAE, 0x5F, 0x6E, 0x32, 0x2E, 0x8B, 0x01, 0x01, 0x8F, 0xC5, 0x9D, 0x4F, 0x5E, 0x00, 0x07, - 0x6A, 0x02, 0xF9, 0xF6, 0x9B, 0xA1, 0x20, 0xF6, 0x8B, 0x19, 0xCA, 0x6D, 0x7E, 0x01, 0xBE, 0x7D, - 0x40, 0x18, 0x2D, 0xC2, 0xD5, 0xF8, 0x14, 0x83, 0x49, 0x80, 0x80, 0x1A, 0x7E, 0xDC, 0x6F, 0x0F, - 0xB1, 0xA4, 0x04, 0x04, 0x13, 0x0A, 0x2E, 0xF0, 0x08, 0x38, 0x74, 0xAB, 0x40, 0x13, 0xBE, 0xC5, - 0x20, 0xFD, 0xFB, 0xC5, 0xDD, 0x37, 0xF3, 0x47, 0xB6, 0x10, 0x10, 0xE0, 0x01, 0x7C, 0xB3, 0xE8, - 0x80, 0x95, 0x91, 0xF7, 0x00, 0x63, 0x01, 0x01, 0xFE, 0xBA, 0x86, 0xBE, 0xF0, 0xA8, 0x20, 0xBE, - 0xA3, 0x20, 0x90, 0x5A, 0x84, 0xB4, 0x9F, 0x63, 0x20, 0xC2, 0x8E, 0x3A, 0x38, 0xFD, 0xB7, 0x1D, - 0x20, 0x20, 0x26, 0x02, 0xDA, 0xDA, 0xA5, 0xFC, 0xA3, 0x65, 0x08, 0x88, 0xF6, 0xCD, 0xAF, 0x81, - 0x98, 0x90, 0x10, 0x10, 0xED, 0x7D, 0x2B, 0x74, 0xF5, 0x0B, 0x08, 0x08, 0xE8, 0xB3, 0x92, 0x04, - 0x7D, 0xE5, 0x7E, 0x36, 0x48, 0x02, 0xC2, 0x0D, 0x93, 0x4C, 0x20, 0x02, 0xC8, 0x73, 0x81, 0xC5 -}; + HANDLE hMpq = NULL; -static int CompareHuffmanCompressions0() -{ - unsigned char * pbDecompressed = Decompressed; - unsigned char * pbCompressed; - unsigned char * pbUncompressed; - int cbOutLength; - int cbInLength; - int nLength = sizeof(Decompressed); - int nError = ERROR_NOT_ENOUGH_MEMORY; - - // Allocate buffers for compressed buffer - pbCompressed = new unsigned char[nLength]; - if(pbCompressed != NULL) - { - // Perform the compression - cbInLength = - cbOutLength = nLength; - SCompCompress(pbCompressed, &cbOutLength, pbDecompressed, nLength, MPQ_COMPRESSION_HUFFMANN, 0x00, 0); - assert(cbOutLength < cbInLength); - - // Allocate space for decompressed buffer - pbUncompressed = new unsigned char[nLength]; - if(pbUncompressed != NULL) - { - // Perform the decompression - cbInLength = cbOutLength; - cbOutLength = nLength; - SCompDecompress(pbUncompressed, &cbOutLength, pbCompressed, cbInLength); + // Make sure that the MPQ is deleted + _tremove(szMpqName); - // Compare length - if(cbOutLength == nLength) - { - if(!memcmp(pbUncompressed, pbDecompressed, nLength)) - { - _tprintf(_T("Huffmann compression OK\n")); - nError = ERROR_SUCCESS; - } - else - { - _tprintf(_T("Error: Decompressed data are not the same like the compressed ones\n")); - nError = ERROR_INVALID_DATA; - } - } - else - { - _tprintf(_T("Error: Length of the uncompressed data is different from original length\n")); - nError = ERROR_BAD_LENGTH; - } + // Fix the flags + dwCreateFlags |= (MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES); - delete [] pbUncompressed; - } + // Create the new MPQ + if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq)) + return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName); + + // Shall we close it right away? + if(phMpq == NULL) + SFileCloseArchive(hMpq); + else + *phMpq = hMpq; - delete [] pbCompressed; - } - - assert(nError == ERROR_SUCCESS); return ERROR_SUCCESS; } -//----------------------------------------------------------------------------- -// Compare PKLIB decompression +static int CreateNewArchive(TLogHelper * pLogger, const TCHAR * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +{ + TCHAR szMpqName[MAX_PATH]; -FILE * data_file; + CreateFullPathName(szMpqName, "StormLibTest_", NULL); + _tcscat(szMpqName, szPlainName); + return CreateNewArchive_FullPath(pLogger, szMpqName, dwCreateFlags, dwMaxFileCount, phMpq); +} -BYTE pbCompressed1[] = {0x00, 0x04, 0x00, 0x00, 0x04, 0xF0, 0x1F, 0x7B, 0x01, 0xFF}; -BYTE pbCompressed2[] = {0x00, 0x04, 0x00, 0x00, 0x04, 0xF0, 0x1F, 0x00, 0x00, 0x04, 0xFC, 0x03}; +#ifdef _UNICODE +static int CreateNewArchive(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +{ + TCHAR szMpqName[MAX_PATH]; + + CreateFullPathName(szMpqName, NULL, szPlainName); + return CreateNewArchive_FullPath(pLogger, szMpqName, dwCreateFlags, dwMaxFileCount, phMpq); +} +#endif -static int ComparePklibCompressions() +static int OpenExistingArchive(TLogHelper * pLogger, const char * szFileName, const char * szCopyName, HANDLE * phMpq) { - TFileStream * pStream; - ULONGLONG ByteOffset = 0; - ULONGLONG FileSize = 0; - unsigned char * pbRawData; - unsigned char * pbCompressed; - unsigned char * pbDecompressed; - int cbOutBuffer; - int cbInBuffer; - - pStream = FileStream_OpenFile(_T("doc\\data_to_compress.dat"), BASE_PROVIDER_FILE | STREAM_PROVIDER_LINEAR); - if(pStream != NULL) - { - FileStream_GetSize(pStream, &FileSize); - cbOutBuffer = (int)FileSize; - - pbRawData = new unsigned char[cbOutBuffer]; - pbCompressed = new unsigned char[cbOutBuffer]; - pbDecompressed = new unsigned char[cbOutBuffer]; - if(pbRawData && pbCompressed && pbDecompressed) - { - FileStream_Read(pStream, &ByteOffset, pbRawData, (DWORD)FileSize); - SCompImplode(pbCompressed, &cbOutBuffer, pbRawData, (DWORD)FileSize); - cbInBuffer = cbOutBuffer; - cbOutBuffer = (int)FileSize; + TCHAR szMpqName[MAX_PATH]; + HANDLE hMpq = NULL; + DWORD dwFlags = 0; + int nError; - SCompExplode(pbDecompressed, &cbOutBuffer, pbCompressed, cbInBuffer); - } + // We expect MPQ directory to be already prepared by InitializeMpqDirectory + assert(szMpqDirectory[0] != 0); - delete [] pbDecompressed; - delete [] pbCompressed; - delete [] pbRawData; + // At least one name must be entered + assert(szFileName != NULL || szCopyName != NULL); - FileStream_Close(pStream); + // If both names entered, create a copy + if(szFileName != NULL && szCopyName != NULL) + { + nError = CreateMpqCopy(pLogger, szFileName, szCopyName, szMpqName); + if(nError != ERROR_SUCCESS) + return nError; + } + + // If only source name entered, open it for read-only access + else if(szFileName != NULL && szCopyName == NULL) + { + CreateFullPathName(szMpqName, szMpqSubDir, szFileName); + dwFlags |= MPQ_OPEN_READ_ONLY; + } + + // If only target name entered, open it directly + else if(szFileName == NULL && szCopyName != NULL) + { + CreateFullPathName(szMpqName, NULL, szCopyName); } + + // Is it an encrypted MPQ ? + if(_tcsstr(szMpqName, _T(".MPQE")) != NULL) + dwFlags |= MPQ_OPEN_ENCRYPTED; + + // Open the copied archive + pLogger->PrintProgress("Opening archive %s ...", (szCopyName != NULL) ? szCopyName : szFileName); + if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) + return pLogger->PrintError(_T("Failed to open archive %s"), szMpqName); + + // Store the archive handle or close the archive + if(phMpq == NULL) + SFileCloseArchive(hMpq); + else + *phMpq = hMpq; return ERROR_SUCCESS; } -//----------------------------------------------------------------------------- -// Compare LZMA decompression +static int OpenPatchedArchive(TLogHelper * pLogger, HANDLE * phMpq, const char * PatchList[]) +{ + TCHAR szMpqName[MAX_PATH]; + HANDLE hMpq = NULL; + int nError = ERROR_SUCCESS; -#ifdef PLATFORM_WINDOWS -typedef void * (*ALLOC_MEMORY)(size_t); -typedef void (*FREE_MEMORY)(void *); -typedef int (GIVE_DATA)(void *); + // The first file is expected to be valid + assert(PatchList[0] != NULL); -extern "C" int starcraft_decompress_lzma(char * pbInBuffer, int cbInBuffer, char * pbOutBuffer, int cbOutBuffer, int * pcbOutBuffer, ALLOC_MEMORY pfnAllocMemory, FREE_MEMORY pfnFreeMemory); -extern "C" int starcraft_compress_lzma(char * pbInBuffer, int cbInBuffer, int dummy1, char * pbOutBuffer, int cbOutBuffer, int dummy2, int * pcbOutBuffer, ALLOC_MEMORY pfnAllocMemory, FREE_MEMORY pfnFreeMemory, GIVE_DATA pfnGiveData); -void Compress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer, int *, int); -int Decompress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer); + // Open the primary MPQ + CreateFullPathName(szMpqName, szMpqSubDir, PatchList[0]); + pLogger->PrintProgress("Opening base MPQ %s ...", PatchList[0]); + if(!SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq)) + nError = pLogger->PrintError(_T("Failed to open the archive %s"), szMpqName); -extern "C" void * operator_new(size_t sz) -{ - return malloc(sz); + // Add all patches + if(nError == ERROR_SUCCESS) + { + for(size_t i = 1; PatchList[i] != NULL; i++) + { + CreateFullPathName(szMpqName, szMpqPatchDir, PatchList[i]); + pLogger->PrintProgress("Adding patch %s ...", PatchList[i]); + if(!SFileOpenPatchArchive(hMpq, szMpqName, NULL, 0)) + { + nError = pLogger->PrintError(_T("Failed to add patch %s ..."), szMpqName); + break; + } + } + } + + // Store the archive handle or close the archive + if(phMpq == NULL) + SFileCloseArchive(hMpq); + else + *phMpq = hMpq; + return ERROR_SUCCESS; } -void * Memory_Allocate(size_t byte_size) +static int AddFileToMpq( + TLogHelper * pLogger, + HANDLE hMpq, + const char * szFileName, + const char * szFileData, + DWORD dwFlags = 0, + DWORD dwCompression = 0, + bool bMustSucceed = false) { - return malloc(byte_size); + HANDLE hFile = NULL; + DWORD dwFileSize = (DWORD)strlen(szFileData); + int nError = ERROR_SUCCESS; + + // Notify the user + pLogger->PrintProgress("Adding file %s ...", szFileName); + + // Get the default flags + if(dwFlags == 0) + dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED; + if(dwCompression == 0) + dwCompression = MPQ_COMPRESSION_ZLIB; + + // Create the file within the MPQ + if(!SFileCreateFile(hMpq, szFileName, 0, dwFileSize, 0, dwFlags, &hFile)) + { + // If success is not expected, it is actually a good thing + if(bMustSucceed == true) + return pLogger->PrintError("Failed to create MPQ file %s", szFileName); + + return GetLastError(); + } + + // Write the file + if(!SFileWriteFile(hFile, szFileData, dwFileSize, dwCompression)) + nError = pLogger->PrintError("Failed to write data to the MPQ"); + + SFileCloseFile(hFile); + return nError; } -void Memory_Free(void * address) +static int AddLocalFileToMpq( + TLogHelper * pLogger, + HANDLE hMpq, + const char * szArchivedName, + const TCHAR * szFileName, + DWORD dwFlags = 0, + DWORD dwCompression = 0, + bool bMustSucceed = false) { - if(address != NULL) - free(address); + DWORD dwVerifyResult; + + // Notify the user + pLogger->PrintProgress("Adding file %s (%u of %u)...", szArchivedName, pLogger->UserCount, pLogger->UserTotal); + pLogger->UserString = szArchivedName; + + // Get the default flags + if(dwFlags == 0) + dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED; + if(dwCompression == 0) + dwCompression = MPQ_COMPRESSION_ZLIB; + + // Set the notification callback + SFileSetAddFileCallback(hMpq, AddFileCallback, pLogger); + + // Add the file to the MPQ + if(!SFileAddFileEx(hMpq, szFileName, szArchivedName, dwFlags, dwCompression, MPQ_COMPRESSION_NEXT_SAME)) + { + if(bMustSucceed) + return pLogger->PrintError("Failed to add the file %s", szArchivedName); + return GetLastError(); + } + + // Verify the file unless it was lossy compression + if((dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) == 0) + { + // Notify the user + pLogger->PrintProgress("Verifying file %s (%u of %u) ...", szArchivedName, pLogger->UserCount, pLogger->UserTotal); + + // Perform the verification + dwVerifyResult = SFileVerifyFile(hMpq, szArchivedName, MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_MD5); + if(dwVerifyResult & (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR)) + return pLogger->PrintError("CRC error on %s", szArchivedName); + } + + return ERROR_SUCCESS; } -int GiveData(void *) +static int RenameMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szOldFileName, const char * szNewFileName, bool bMustSucceed) { - return 0; + // Notify the user + pLogger->PrintProgress("Renaming %s to %s ...", szOldFileName, szNewFileName); + + // Perform the deletion + if(!SFileRenameFile(hMpq, szOldFileName, szNewFileName)) + { + if(bMustSucceed == true) + return pLogger->PrintErrorVa("Failed to rename %s to %s", szOldFileName, szNewFileName); + return GetLastError(); + } + + return ERROR_SUCCESS; } -static int StarcraftCompress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer) +static int RemoveMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, bool bMustSucceed) { - return starcraft_compress_lzma(pbInBuffer, - cbInBuffer, - 0, - pbOutBuffer, - *pcbOutBuffer, - 0, - pcbOutBuffer, - Memory_Allocate, - Memory_Free, - GiveData); + // Notify the user + pLogger->PrintProgress("Removing file %s ...", szFileName); + + // Perform the deletion + if(!SFileRemoveFile(hMpq, szFileName, 0)) + { + if(bMustSucceed == true) + return pLogger->PrintError("Failed to remove the file %s from the archive", szFileName); + return GetLastError(); + } + + return ERROR_SUCCESS; } -static int StarcraftDecompress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer) +//----------------------------------------------------------------------------- +// Tests + +static void TestGetFileInfo( + TLogHelper * pLogger, + HANDLE hMpqOrFile, + SFileInfoClass InfoClass, + void * pvFileInfo, + DWORD cbFileInfo, + DWORD * pcbLengthNeeded, + bool bExpectedResult, + int nExpectedError) { - return starcraft_decompress_lzma(pbInBuffer, - cbInBuffer, - pbOutBuffer, - *pcbOutBuffer, - pcbOutBuffer, - Memory_Allocate, - Memory_Free); + bool bResult; + int nError = ERROR_SUCCESS; + + // Call the get file info + bResult = SFileGetFileInfo(hMpqOrFile, InfoClass, pvFileInfo, cbFileInfo, pcbLengthNeeded); + if(!bResult) + nError = GetLastError(); + + if(bResult != bExpectedResult) + pLogger->PrintMessage("Different result of SFileGetFileInfo."); + if(nError != nExpectedError) + pLogger->PrintMessage("Different error from SFileGetFileInfo (expected %u, returned %u)", nExpectedError, nError); } -static int CompareLzmaCompressions(int nSectorSize) +static int TestVerifyFileChecksum(const char * szFullPath) { - LPBYTE pbCompressed1 = NULL; // Compressed by our code - LPBYTE pbCompressed2 = NULL; // Compressed by Blizzard's code - LPBYTE pbDecompressed1 = NULL; // Decompressed by our code - LPBYTE pbDecompressed2 = NULL; // Decompressed by Blizzard's code - LPBYTE pbOriginalData = NULL; + const char * szShortPlainName = GetShortPlainName(szFullPath); + unsigned char sha1_digest[SHA1_DIGEST_SIZE]; + TFileStream * pStream; + TFileData * pFileData; + hash_state sha1_state; + ULONGLONG ByteOffset = 0; + ULONGLONG FileSize = 0; + char * szExtension; + LPBYTE pbFileBlock; + char szShaFileName[MAX_PATH]; + char Sha1Text[0x40]; + DWORD cbBytesToRead; + DWORD cbFileBlock = 0x10000; + size_t nLength; int nError = ERROR_SUCCESS; - // Allocate buffers - // Must allocate twice blocks due to probable bug in Storm.dll. - // Storm.dll corrupts stack when uncompresses data with PKWARE DCL - // and no compression occurs. - pbDecompressed1 = new BYTE [nSectorSize]; - pbDecompressed2 = new BYTE [nSectorSize]; - pbCompressed1 = new BYTE [nSectorSize]; - pbCompressed2 = new BYTE [nSectorSize]; - pbOriginalData = new BYTE[nSectorSize]; - if(!pbDecompressed1 || !pbDecompressed2 || !pbCompressed1 || !pbCompressed2 || !pbOriginalData) - nError = ERROR_NOT_ENOUGH_MEMORY; - - if(nError == ERROR_SUCCESS) - { - for(int i = 0; i < 100000; i++) - { - int nDcmpLength1; - int nDcmpLength2; - int nCmpLength1; - int nCmpLength2; - int nDiff; + // Try to load the file with the SHA extension + strcpy(szShaFileName, szFullPath); + szExtension = strrchr(szShaFileName, '.'); + if(szExtension == NULL) + return ERROR_SUCCESS; - clreol(); - _tprintf(_T("Testing compression of sector %u\r"), i + 1); + // Skip .SHA and .TXT files + if(!_stricmp(szExtension, ".sha") || !_stricmp(szExtension, ".txt")) + return ERROR_SUCCESS; - // Generate random data sector - GenerateRandomDataBlock(pbOriginalData, nSectorSize); + // Load the local file to memory + strcpy(szExtension, ".sha"); + pFileData = LoadLocalFile(NULL, szShaFileName, false); + if(pFileData != NULL) + { + TLogHelper Logger("VerifyFileHash", szShortPlainName); - // Compress the sector by both methods - nCmpLength1 = nCmpLength2 = nSectorSize; -// Compress_LZMA((char *)pbCompressed1, &nCmpLength1, (char *)pbOriginalData, nSectorSize, 0, 0); - StarcraftCompress_LZMA((char *)pbCompressed1, &nCmpLength2, (char *)pbOriginalData, nSectorSize); + // Open the file to be verified + pStream = FileStream_OpenFile(szFullPath, STREAM_FLAG_READ_ONLY); + if(pStream != NULL) + { + // Notify the user + Logger.PrintProgress("Verifying file %s", szShortPlainName); -__TryToDecompress: + // Retrieve the size of the file + FileStream_GetSize(pStream, &FileSize); - // Only test decompression when the compression actually succeeded - if(nCmpLength1 < nSectorSize) + // Allocate the buffer for loading file parts + pbFileBlock = STORM_ALLOC(BYTE, cbFileBlock); + if(pbFileBlock != NULL) { - // Decompress both data - nDcmpLength2 = nDcmpLength1 = nSectorSize; -// Decompress_LZMA((char *)pbDecompressed1, &nDcmpLength1, (char *)pbCompressed1, nCmpLength1); - StarcraftDecompress_LZMA((char *)pbDecompressed2, &nDcmpLength2, (char *)pbCompressed1, nCmpLength1); + // Initialize SHA1 calculation + sha1_init(&sha1_state); - // Compare the length of the output data - if(nDcmpLength1 != nDcmpLength2) + // Calculate the SHA1 of the file + while(ByteOffset < FileSize) { - _tprintf(_T("Difference in compressed blocks lengths (%u vs %u)\n"), nDcmpLength1, nDcmpLength2); - goto __TryToDecompress; - } + // Notify the user + Logger.PrintProgress("Verifying file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize); - // Compare the output - if((nDiff = GetFirstDiffer(pbDecompressed1, pbDecompressed2, nDcmpLength1)) != -1) - { - _tprintf(_T("Difference in decompressed blocks (offset 0x%08X)\n"), nDiff); - goto __TryToDecompress; - } + // Load the file block + cbBytesToRead = ((FileSize - ByteOffset) > cbFileBlock) ? cbFileBlock : (DWORD)(FileSize - ByteOffset); + if(!FileStream_Read(pStream, &ByteOffset, pbFileBlock, cbBytesToRead)) + { + nError = GetLastError(); + break; + } - // Check for data overflow - if(pbDecompressed1[nSectorSize] != 0xFD || pbDecompressed1[nSectorSize] != 0xFD) - { - _tprintf(_T("Damage after decompressed sector !!!\n")); - goto __TryToDecompress; + // Add to SHA1 + sha1_process(&sha1_state, pbFileBlock, cbBytesToRead); + ByteOffset += cbBytesToRead; } - // Compare the decompressed data against original data - if((nDiff = GetFirstDiffer(pbDecompressed1, pbOriginalData, nDcmpLength1)) != -1) + // Finalize SHA1 + sha1_done(&sha1_state, sha1_digest); + STORM_FREE(pbFileBlock); + + // Compare with what we loaded from the file + if(pFileData->dwFileSize >= (SHA1_DIGEST_SIZE * 2)) { - _tprintf(_T("Difference between original data and decompressed data (offset 0x%08X)\n"), nDiff); - goto __TryToDecompress; + // Compare the Sha1 + nLength = ConvertSha1ToText(sha1_digest, Sha1Text); + if(_strnicmp(Sha1Text, (char *)pFileData->FileData, nLength)) + { + Logger.PrintError("File CRC check failed: %s", szFullPath); + nError = ERROR_FILE_CORRUPT; + } } } + + // Close the file + FileStream_Close(pStream); } + + STORM_FREE(pFileData); } - // Cleanup - if(pbOriginalData != NULL) - delete [] pbOriginalData; - if(pbCompressed2 != NULL) - delete [] pbCompressed2; - if(pbCompressed1 != NULL) - delete [] pbCompressed1; - if(pbDecompressed2 != NULL) - delete [] pbDecompressed2; - if(pbDecompressed1 != NULL) - delete [] pbDecompressed1; - clreol(); return nError; } -#endif // PLATFORM_WINDOWS - -//----------------------------------------------------------------------------- -// Compression method test -static int TestSectorCompress(int nSectorSize) +// StormLib is able to open local files (as well as the original Storm.dll) +// I want to keep this for occasional use +static int TestOpenLocalFile(const char * szPlainName) { - LPBYTE pbDecompressed = NULL; - LPBYTE pbCompressed = NULL; - LPBYTE pbOriginal = NULL; - int nError = ERROR_SUCCESS; - - // Allocate buffers - pbDecompressed = new BYTE[nSectorSize]; - pbCompressed = new BYTE[nSectorSize]; - pbOriginal = new BYTE[nSectorSize]; - if(!pbDecompressed || !pbCompressed || !pbOriginal) - nError = ERROR_NOT_ENOUGH_MEMORY; - - if(nError == ERROR_SUCCESS) + TLogHelper Logger("OpenLocalFile", szPlainName); + HANDLE hFile; + DWORD dwFileSizeHi = 0; + DWORD dwFileSizeLo = 0; + char szFileName1[MAX_PATH]; + char szFileName2[MAX_PATH]; + char szFileLine[0x40]; + + CreateFullPathName(szFileName1, szMpqSubDir, szPlainName); + if(SFileOpenFileEx(NULL, szFileName1, SFILE_OPEN_LOCAL_FILE, &hFile)) { - for(int i = 0; i < 100000; i++) - { - int nOriginalLength = nSectorSize % (rand() + 1); - int nCompressedLength; - int nDecompressedLength; - int nCmp = MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_ZLIB | MPQ_COMPRESSION_BZIP2 | MPQ_COMPRESSION_PKWARE; - int nDiff; - - clreol(); - _tprintf(_T("Testing compression of sector %u\r"), i + 1); - - // Generate random data sector - GenerateRandomDataBlock(pbOriginal, nOriginalLength); - if(nOriginalLength == 0x123) - nOriginalLength = 0; - -__TryAgain: - - // Compress the sector - nCompressedLength = nOriginalLength; - SCompCompress((char *)pbCompressed, &nCompressedLength, (char *)pbOriginal, nOriginalLength, nCmp, 0, -1); -// SCompImplode((char *)pbCompressed, &nCompressedLength, (char *)pbOriginal, nOriginalLength); - - // When the method was unable to compress data, - // the compressed data must be identical to original data - if(nCompressedLength == nOriginalLength) - { - if((nDiff = GetFirstDiffer(pbCompressed, pbOriginal, nOriginalLength)) != -1) - { - _tprintf(_T("Compression error: Fail when unable to compress the data (Offset 0x%08X).\n"), nDiff); - goto __TryAgain; - } - } + // Retrieve the file name. It must match the name under which the file was open + SFileGetFileName(hFile, szFileName2); + if(strcmp(szFileName2, szFileName1)) + Logger.PrintMessage("The retrieved name does not match the open name"); - // Uncompress the sector - nDecompressedLength = nOriginalLength; - SCompDecompress((char *)pbDecompressed, &nDecompressedLength, (char *)pbCompressed, nCompressedLength); -// SCompExplode((char *)pbDecompressed, &nDecompressedLength, (char *)pbCompressed, nCompressedLength); + // Retrieve the file size + dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi); + if(dwFileSizeHi != 0 || dwFileSizeLo != 3904784) + Logger.PrintMessage("Local file size mismatch"); - // Check the decompressed length against original length - if(nDecompressedLength != nOriginalLength) - { - _tprintf(_T("Length of uncompressed data does not agree with original data length !!!\n")); - goto __TryAgain; - } - - // Check decompressed block against original block - if((nDiff = GetFirstDiffer(pbDecompressed, pbOriginal, nOriginalLength)) != -1) - { - _tprintf(_T("Decompressed sector does not agree with the original data !!! (Offset 0x%08X)\n"), nDiff); - goto __TryAgain; - } - } + // Read the first line + memset(szFileLine, 0, sizeof(szFileLine)); + SFileReadFile(hFile, szFileLine, 18, NULL, NULL); + if(strcmp(szFileLine, "(1)Enslavers01.scm")) + Logger.PrintMessage("Content of the listfile does not match"); + + SFileCloseFile(hFile); } - // Cleanup - delete [] pbOriginal; - delete [] pbCompressed; - delete [] pbDecompressed; - clreol(); - return nError; + return ERROR_SUCCESS; } -static int TestArchiveOpenAndClose(const TCHAR * szMpqName) -{ -// const char * szFileName = "../Bin/Config/Setting/ErroeString.str"; - const char * szFileName = "Scp\\LifeSkills.csv"; -// const char * szFileName = "File00000000.xxx"; - TMPQArchive * ha = NULL; - HANDLE hFile1 = NULL; -// HANDLE hFile2 = NULL; - HANDLE hMpq = NULL; +// +static int TestPartFileRead(const char * szPlainName) +{ + TLogHelper Logger("PartFileRead", szPlainName); + TMPQHeader Header; + ULONGLONG ByteOffset; + ULONGLONG FileSize = 0; + TFileStream * pStream; + TCHAR szFileName[MAX_PATH]; + BYTE Buffer[0x100]; int nError = ERROR_SUCCESS; + // Open the partial file + CreateFullPathName(szFileName, szMpqSubDir, szPlainName); + pStream = FileStream_OpenFile(szFileName, STREAM_PROVIDER_PARTIAL | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY); + if(pStream == NULL) + nError = Logger.PrintError(_T("Failed to open %s"), szFileName); + + // Get the size of the stream if(nError == ERROR_SUCCESS) { - _tprintf(_T("Opening archive %s ...\n"), szMpqName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - nError = GetLastError(); - ha = (TMPQArchive *)hMpq; + if(!FileStream_GetSize(pStream, &FileSize)) + nError = Logger.PrintError("Failed to retrieve virtual file size"); } + // Read the MPQ header if(nError == ERROR_SUCCESS) { - SFileAddListFile(hMpq, "c:\\Tools32\\Listfiles\\ListFile.txt"); + ByteOffset = 0; + if(!FileStream_Read(pStream, &ByteOffset, &Header, MPQ_HEADER_SIZE_V2)) + nError = Logger.PrintError("Failed to read the MPQ header"); + if(Header.dwID != ID_MPQ || Header.dwHeaderSize != MPQ_HEADER_SIZE_V2) + nError = Logger.PrintError("MPQ Header error"); } - // Verify the raw data in the archive + // Read the last 0x100 bytes if(nError == ERROR_SUCCESS) { - // Try to open a file - if(!SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile1)) - { - nError = GetLastError(); - printf("%s - file not found in the MPQ\n", szFileName); - } + ByteOffset = FileSize - sizeof(Buffer); + if(!FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer))) + nError = Logger.PrintError("Failed to read from the file"); } - // Dummy read from the file + // Read 0x100 bytes from position (FileSize - 0xFF) + // This test must fail if(nError == ERROR_SUCCESS) - { - DWORD dwBytesRead = 0; - BYTE Buffer[0x1000]; - char szNameBuff[MAX_PATH]; + { + ByteOffset = FileSize - sizeof(Buffer) + 1; + if(FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer))) + nError = Logger.PrintError("Test Failed: Reading 0x100 bytes from (FileSize - 0xFF)"); + } - SFileGetFileName(hFile1, szNameBuff); - SFileSetFilePointer(hFile1, 0x1000, NULL, FILE_BEGIN); - SFileReadFile(hFile1, Buffer, sizeof(Buffer), &dwBytesRead, NULL); - } + FileStream_Close(pStream); + return nError; +} - // Verify the MPQ listfile -#ifdef _MSC_VER +static int TestOpenFile_OpenById(const char * szPlainName) +{ + TLogHelper Logger("OpenFileById", szPlainName); + TFileData * pFileData1 = NULL; + TFileData * pFileData2 = NULL; + HANDLE hMpq; + int nError; + + // Copy the archive so we won't fuck up the original one + nError = OpenExistingArchive(&Logger, szPlainName, NULL, &hMpq); + + // Now try to open a file without knowing the file name if(nError == ERROR_SUCCESS) { - SFileExtractFile(hMpq, szFileName, _T("E:\\extracted.wav"), 0); - PlaySound(_T("E:\\extracted.wav"), NULL, SND_FILENAME); + // File00000023.xxx = music\dintro.wav + pFileData1 = LoadMpqFile(&Logger, hMpq, "File00000023.xxx"); + if(pFileData1 == NULL) + nError = Logger.PrintError("Failed to load the file %s", "File00000023.xxx"); + } + + // Now try to open the file again with its original name + if(nError == ERROR_SUCCESS) + { + // File00000023.xxx = music\dintro.wav + pFileData2 = LoadMpqFile(&Logger, hMpq, "music\\dintro.wav"); + if(pFileData2 == NULL) + nError = Logger.PrintError("Failed to load the file %s", "music\\dintro.wav"); + } + + // Now compare both files + if(nError == ERROR_SUCCESS) + { + if(!CompareTwoFiles(&Logger, pFileData1, pFileData1)) + nError = Logger.PrintError("The file has different size/content when open without name"); } -#endif - if(hFile1 != NULL) - SFileCloseFile(hFile1); + // Close the archive + if(pFileData2 != NULL) + STORM_FREE(pFileData2); + if(pFileData1 != NULL) + STORM_FREE(pFileData1); if(hMpq != NULL) SFileCloseArchive(hMpq); return nError; } -static int TestAddFilesToArchive(const TCHAR * szMpqName) +// Open an empty archive (found in WoW cache - it's just a header) +static int TestOpenArchive(const char * szPlainName, const char * szListFile = NULL) { - HANDLE hFile; + TLogHelper Logger("OpenMpqTest", szPlainName); + TFileData * pFileData; HANDLE hMpq; - LPCSTR szFileData = "0123456789"; - char szAddedFile[128]; - DWORD dwFileSize = 10; - int nIndex = 0; - -#ifdef _MSC_VER - CopyFile(MAKE_PATH("2013 - Starcraft II\\!maps\\Tya's Zerg Defense.SC2Map"), MAKE_PATH("Tya's Zerg Defense.SC2Map"), FALSE); -#endif + DWORD dwFileCount = 0; + char szListFileBuff[MAX_PATH]; + int nError; - if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + // Copy the archive so we won't fuck up the original one + nError = OpenExistingArchive(&Logger, szPlainName, NULL, &hMpq); + if(nError == ERROR_SUCCESS) { - SFileRemoveFile(hMpq, "BankList.xml", 0); - - sprintf(szAddedFile, "BankList.xml", nIndex++); - if(SFileCreateFile(hMpq, szAddedFile, 0, dwFileSize, 0, MPQ_FILE_COMPRESS, &hFile)) + // If the listfile was given, add it to the MPQ + if(szListFile != NULL) { - SFileWriteFile(hFile, szFileData, dwFileSize, MPQ_COMPRESSION_ZLIB); - SFileFinishFile(hFile); + Logger.PrintProgress("Adding listfile %s ...", szListFile); + CreateFullPathName(szListFileBuff, szMpqSubDir, szListFile); + nError = SFileAddListFile(hMpq, szListFileBuff); + if(nError != ERROR_SUCCESS) + Logger.PrintMessage("Failed to add the listfile to the MPQ"); } -/* - sprintf(szAddedFile, "AddedFile%04u.txt", nIndex++); - if(SFileCreateFile(hMpq, szAddedFile, 0, dwFileSize, 0, MPQ_FILE_COMPRESS, &hFile)) + + // Attempt to open the listfile and attributes + if(SFileHasFile(hMpq, LISTFILE_NAME)) { - SFileWriteFile(hFile, szFileData, dwFileSize, MPQ_COMPRESSION_ZLIB); - SFileFinishFile(hFile); + pFileData = LoadMpqFile(&Logger, hMpq, LISTFILE_NAME); + if(pFileData != NULL) + STORM_FREE(pFileData); } - sprintf(szAddedFile, "AddedFile%04u.txt", nIndex++); - if(SFileCreateFile(hMpq, szAddedFile, 0, dwFileSize, 0, MPQ_FILE_COMPRESS, &hFile)) + // Attempt to open the listfile and attributes + if(SFileHasFile(hMpq, ATTRIBUTES_NAME)) { - SFileWriteFile(hFile, szFileData, dwFileSize, MPQ_COMPRESSION_ZLIB); - SFileFinishFile(hFile); + pFileData = LoadMpqFile(&Logger, hMpq, ATTRIBUTES_NAME); + if(pFileData != NULL) + STORM_FREE(pFileData); } -*/ + // Search the archive and load every file + nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES, &dwFileCount); SFileCloseArchive(hMpq); } -/* - for(int i = 0; i < 3; i++) + return nError; +} + +// Opens a patched MPQ archive +static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatchedFile = NULL, int nExpectedPatchCount = 0) +{ + TLogHelper Logger("OpenPatchedMpqTest", PatchList[0]); + HANDLE hMpq; + DWORD dwFileCount = 0; + int nError; + + // Open a patched MPQ archive + nError = OpenPatchedArchive(&Logger, &hMpq, PatchList); + if(nError == ERROR_SUCCESS) { - if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - { - sprintf(szAddedFile, "AddedFile%04u.txt", i); - SFileRemoveFile(hFile, szAddedFile, 0); + // Check patch count + if(szPatchedFile != NULL) + nError = VerifyFilePatchCount(&Logger, hMpq, szPatchedFile, nExpectedPatchCount); - SFileCloseArchive(hMpq); - } + // Search the archive and load every file + if(nError == ERROR_SUCCESS) + nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES, &dwFileCount); + + // Close the archive + SFileCloseArchive(hMpq); } -*/ - return ERROR_SUCCESS; + + return nError; } -static int TestFindFiles(const TCHAR * szMpqName) +// Open an archive for read-only access +static int TestOpenArchive_ReadOnly(const char * szPlainName, bool bReadOnly) { - TMPQFile * hf; - HANDLE hFile; - HANDLE hMpq = NULL; - BYTE Buffer[100]; - int nError = ERROR_SUCCESS; - int nFiles = 0; - int nFound = 0; + const char * szCopyName; + TLogHelper Logger("ReadOnlyTest", szPlainName); + HANDLE hMpq; + TCHAR szMpqName[MAX_PATH]; + DWORD dwFlags = 0; + bool bMustSucceed; + int nError; - // Open the archive + // Copy the fiel so we wont screw up something + szCopyName = bReadOnly ? "StormLibTest_ReadOnly.mpq" : "StormLibTest_ReadWrite.mpq"; + nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szMpqName); + + // Now open the archive for read-only access if(nError == ERROR_SUCCESS) { - _tprintf(_T("Opening \"%s\" for finding files ...\n"), szMpqName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - nError = GetLastError(); + Logger.PrintProgress("Opening archive %s ...", szCopyName); + + dwFlags = bReadOnly ? MPQ_OPEN_READ_ONLY : 0; + if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) + nError = Logger.PrintError("Failed to open the archive %s", szCopyName); } - // Compact the archive + // Now try to add a file. This must fail if the MPQ is read only if(nError == ERROR_SUCCESS) { - SFILE_FIND_DATA sf; - HANDLE hFind; - DWORD dwExtraDataSize; - bool bFound = true; - - hFind = SFileFindFirstFile(hMpq, "*", &sf, "c:\\Tools32\\ListFiles\\ListFile.txt"); - while(hFind != NULL && bFound != false) - { - if(SFileOpenFileEx(hMpq, sf.cFileName, 0, &hFile)) - { - hf = (TMPQFile *)hFile; - SFileReadFile(hFile, Buffer, sizeof(Buffer), NULL, NULL); - nFiles++; - - if(sf.dwFileFlags & MPQ_FILE_SECTOR_CRC) - { - dwExtraDataSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount]; - if(dwExtraDataSize != 0) - nFound++; - } + bMustSucceed = (bReadOnly == false); + nError = AddFileToMpq(&Logger, hMpq, "AddedFile.txt", "This is an added file.", MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, 0, bMustSucceed); + if(nError != ERROR_SUCCESS && bMustSucceed == false) + nError = ERROR_SUCCESS; + } - SFileCloseFile(hFile); - } + // Now try to rename a file in the MPQ. This must only succeed if the MPQ is not read only + if(nError == ERROR_SUCCESS) + { + bMustSucceed = (bReadOnly == false); + nError = RenameMpqFile(&Logger, hMpq, "spawn.mpq", "spawn-renamed.mpq", bMustSucceed); + if(nError != ERROR_SUCCESS && bMustSucceed == false) + nError = ERROR_SUCCESS; + } - bFound = SFileFindNextFile(hFind, &sf); - } + // Now try to delete a file in the MPQ. This must only succeed if the MPQ is not read only + if(nError == ERROR_SUCCESS) + { + bMustSucceed = (bReadOnly == false); + nError = RemoveMpqFile(&Logger, hMpq, "spawn-renamed.mpq", bMustSucceed); + if(nError != ERROR_SUCCESS && bMustSucceed == false) + nError = ERROR_SUCCESS; } + // Close the archive if(hMpq != NULL) SFileCloseArchive(hMpq); - if(nError == ERROR_SUCCESS) - _tprintf(_T("Search complete\n")); return nError; } -static int TestMpqCompacting(const TCHAR * szMpqName) +static int TestOpenArchive_GetFileInfo(const char * szPlainName1, const char * szPlainName4) { - HANDLE hMpq = NULL; + TLogHelper Logger("GetFileInfoTest"); + HANDLE hMpq4; + HANDLE hMpq1; + DWORD cbLength; + BYTE DataBuff[0x400]; + int nError1; + int nError4; + + // Copy the archive so we won't fuck up the original one + nError1 = OpenExistingArchive(&Logger, szPlainName1, NULL, &hMpq1); + nError4 = OpenExistingArchive(&Logger, szPlainName4, NULL, &hMpq4); + if(nError1 == ERROR_SUCCESS && nError4 == ERROR_SUCCESS) + { + // Invalid handle - expected (false, ERROR_INVALID_PARAMETER) + TestGetFileInfo(&Logger, NULL, SFileMpqBetHeader, NULL, 0, NULL, false, ERROR_INVALID_PARAMETER); + + // Valid handle and all parameters NULL + // Returns (true, ERROR_SUCCESS), if BET table is present, otherwise (false, ERROR_CAN_NOT_COMPLETE) + TestGetFileInfo(&Logger, hMpq1, SFileMpqBetHeader, NULL, 0, NULL, false, ERROR_INVALID_PARAMETER); + TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 0, NULL, true, ERROR_SUCCESS); + + // Now try to retrieve the required size of the BET table header + TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 0, &cbLength, true, ERROR_SUCCESS); + + // When we call SFileInfo with buffer = NULL and nonzero buffer size, it is ignored + TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 3, &cbLength, true, ERROR_SUCCESS); + + // When we call SFileInfo with buffer != NULL and nonzero buffer size, it should return error + TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, 3, &cbLength, false, ERROR_INSUFFICIENT_BUFFER); + + // Request for bet table header should also succeed if we want header only + TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, sizeof(TMPQBetHeader), &cbLength, true, ERROR_SUCCESS); + + // Request for bet table header should also succeed if we want header+flag table only + TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS); + + // Try to retrieve strong signature from the MPQ + TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, NULL, 0, NULL, true, ERROR_SUCCESS); + TestGetFileInfo(&Logger, hMpq4, SFileMpqStrongSignature, NULL, 0, NULL, false, ERROR_INVALID_PARAMETER); + + // Strong signature is returned including the signature ID + TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, NULL, 0, &cbLength, true, ERROR_SUCCESS); + assert(cbLength == MPQ_STRONG_SIGNATURE_SIZE + 4); + + // Retrieve the signature + TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS); + assert(memcmp(DataBuff, "NGIS", 4) == 0); + } + + if(hMpq4 != NULL) + SFileCloseArchive(hMpq4); + if(hMpq1 != NULL) + SFileCloseArchive(hMpq1); + return ERROR_SUCCESS; +} + +static int TestOpenArchive_VerifySignature(const char * szPlainName, const char * szOriginalName) +{ + TLogHelper Logger("VerifySignatureTest", szPlainName); + HANDLE hMpq; + DWORD dwSignatures = 0; + int nVerifyError; int nError = ERROR_SUCCESS; - // Open the archive + // We need original name for the signature check + nError = OpenExistingArchive(&Logger, szPlainName, szOriginalName, &hMpq); if(nError == ERROR_SUCCESS) { - _tprintf(_T("Opening \"%s\" for compacting ...\n"), szMpqName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - nError = GetLastError(); + // Query the signature types + Logger.PrintProgress("Retrieving signatures ..."); + TestGetFileInfo(&Logger, hMpq, SFileMpqSignatures, &dwSignatures, sizeof(DWORD), NULL, true, ERROR_SUCCESS); + + // Verify any of the present signatures + Logger.PrintProgress("Verifying archive signature ..."); + nVerifyError = SFileVerifyArchive(hMpq); + + // Verify the result + if((dwSignatures & SIGNATURE_TYPE_STRONG) && (nVerifyError != ERROR_STRONG_SIGNATURE_OK)) + { + Logger.PrintMessage("Strong signature verification error"); + nError = ERROR_FILE_CORRUPT; + } + + // Verify the result + if((dwSignatures & SIGNATURE_TYPE_WEAK) && (nVerifyError != ERROR_WEAK_SIGNATURE_OK)) + { + Logger.PrintMessage("Weak signature verification error"); + nError = ERROR_FILE_CORRUPT; + } + + SFileCloseArchive(hMpq); } + return nError; +} +// Open an empty archive (found in WoW cache - it's just a header) +static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char * szCopyName) +{ + TLogHelper Logger("CraftedMpqTest", szPlainName); + HANDLE hMpq; + DWORD dwFileCount1 = 0; + DWORD dwFileCount2 = 0; + TCHAR szMpqName[MAX_PATH]; + BYTE FileHash1[MD5_DIGEST_SIZE]; + BYTE FileHash2[MD5_DIGEST_SIZE]; + int nError; + + // Create copy of the archive, with interleaving some user data + nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szMpqName, 0x400, 0x531); + + // Open the archive and load some files if(nError == ERROR_SUCCESS) { - const char * szFileName = "Shaders\\Effects\\shadowmap.wfx"; + Logger.PrintProgress("Opening archive %s ...", szCopyName); + if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + return Logger.PrintError(_T("Failed to open archive %s"), szMpqName); - printf("Deleting file %s ...\r", szFileName); - if(!SFileRemoveFile(hMpq, szFileName, SFILE_OPEN_FROM_MPQ)) - nError = GetLastError(); + // Verify presence of (listfile) and (attributes) + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true); + + // Search the archive and load every file + nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES, &dwFileCount1, FileHash1); + SFileCloseArchive(hMpq); } -/* - // Compact the archive + + // Try to compact the MPQ if(nError == ERROR_SUCCESS) { - _tprintf(_T("Compacting archive ...\r")); - SFileSetCompactCallback(hMpq, CompactCB, NULL); - if(!SFileCompactArchive(hMpq, "c:\\Tools32\\ListFiles\\ListFile.txt")) - nError = GetLastError(); + // Open the archive again + Logger.PrintProgress("Reopening archive %s ...", szCopyName); + if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + return Logger.PrintError(_T("Failed to open archive %s"), szMpqName); + + // Compact the archive + Logger.PrintProgress("Compacting archive %s ...", szMpqName); + if(!SFileSetCompactCallback(hMpq, CompactCallback, &Logger)) + nError = Logger.PrintError(_T("Failed to compact archive %s"), szMpqName); + + SFileCompactArchive(hMpq, NULL, false); + SFileCloseArchive(hMpq); } -*/ - if(hMpq != NULL) + + // Open the archive and load some files + if(nError == ERROR_SUCCESS) + { + Logger.PrintProgress("Reopening archive %s ...", szCopyName); + if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + return Logger.PrintError(_T("Failed to open archive %s"), szMpqName); + + // Verify presence of (listfile) and (attributes) + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true); + + // Search the archive and load every file + nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES, &dwFileCount2, FileHash2); SFileCloseArchive(hMpq); + } + + // Compare the file counts and their hashes if(nError == ERROR_SUCCESS) - _tprintf(_T("Compacting complete (No errors)\n")); + { + if(dwFileCount2 != dwFileCount1) + Logger.PrintMessage("Different file count after compacting archive: %u vs %u", dwFileCount2, dwFileCount1); + + if(memcmp(FileHash2, FileHash1, MD5_DIGEST_SIZE)) + Logger.PrintMessage("Different file hash after compacting archive"); + } + return nError; } -static int TestCreateArchive(const TCHAR * szMpqName) +// Adding a file to MPQ that had no (listfile) and no (attributes). +// We expect that neither of these will be present after the archive is closed +static int TestAddFile_ListFileTest(const char * szSourceMpq, bool bShouldHaveListFile, bool bShouldHaveAttributes) { - TFileStream * pStream; - const TCHAR * szFileName1 = MAKE_PATH("FileTest.exe"); - const TCHAR * szFileName2 = MAKE_PATH("ZeroSize.txt"); - HANDLE hMpq = NULL; // Handle of created archive - DWORD dwVerifyResult; - DWORD dwFileCount = 0; - LCID LocaleIDs[] = {0x000, 0x405, 0x406, 0x407, 0xFFFF}; - char szMpqFileName[MAX_PATH]; + TLogHelper Logger("ListFileTest", szSourceMpq); + TFileData * pFileData = NULL; + const char * szBackupMpq = bShouldHaveListFile ? "StormLibTest_HasListFile.mpq" : "StormLibTest_NoListFile.mpq"; + const char * szFileName = "AddedFile001.txt"; + const char * szFileData = "0123456789ABCDEF"; + HANDLE hMpq = NULL; + DWORD dwFileSize = (DWORD)strlen(szFileData); int nError = ERROR_SUCCESS; - int i; - // Create the new file - _tprintf(_T("Creating %s ...\n"), szMpqName); - pStream = FileStream_CreateFile(szMpqName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream == NULL) - nError = GetLastError(); + // Copy the archive so we won't fuck up the original one + nError = OpenExistingArchive(&Logger, szSourceMpq, szBackupMpq, &hMpq); - // Write some data + // Add a file if(nError == ERROR_SUCCESS) { - ULONGLONG FileSize = 0x100000; - - FileStream_SetSize(pStream, FileSize); - FileStream_Close(pStream); + // Now add a file + nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, MPQ_FILE_IMPLODE, MPQ_COMPRESSION_PKWARE); + SFileCloseArchive(hMpq); } - // Well, now create the MPQ archive + // Now reopen the archive if(nError == ERROR_SUCCESS) - { - if(!SFileCreateArchive(szMpqName, - MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_ATTRIBUTES, - 17, - &hMpq)) - { - nError = GetLastError(); - } - } + nError = OpenExistingArchive(&Logger, NULL, szBackupMpq, &hMpq); - // Add the same file multiple times + // Now the file has been written and the MPQ has been saved. + // We Reopen the MPQ and check if there is no (listfile) nor (attributes). if(nError == ERROR_SUCCESS) { - // Add FileTest.exe - for(i = 0; AddFlags[i] != 0xFFFFFFFF; i++) + // Verify presence of (listfile) and (attributes) + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, bShouldHaveListFile); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, bShouldHaveAttributes); + + // Try to open the file that we recently added + pFileData = LoadMpqFile(&Logger, hMpq, szFileName); + if(pFileData != NULL) { - sprintf(szMpqFileName, "FileTest_%02u.exe", i); - PrintfTA(_T("Adding %s as %s ...\n"), szFileName1, szMpqFileName); - if(SFileAddFileEx(hMpq, szFileName1, szMpqFileName, AddFlags[i], MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) + // Verify if the file size matches + if(pFileData->dwFileSize == dwFileSize) { - dwVerifyResult = SFileVerifyFile(hMpq, szMpqFileName, MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_MD5); - if(dwVerifyResult & (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR)) - printf("CRC error on \"%s\"\n", szMpqFileName); - dwFileCount++; + // Verify if the file data match + if(memcmp(pFileData->FileData, szFileData, dwFileSize)) + { + Logger.PrintError("The data of the added file does not match"); + nError = ERROR_FILE_CORRUPT; + } } else { - printf("Failed to add the file \"%s\".\n", szMpqFileName); + Logger.PrintError("The size of the added file does not match"); + nError = ERROR_FILE_CORRUPT; } - } - - // Delete a file in the middle of the file table - SFileRemoveFile(hMpq, "FileTest_10.exe", SFILE_OPEN_FROM_MPQ); - SFileAddFileEx(hMpq, szFileName1, "FileTest_xx.exe", MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME); - - // Try to decrement max file count - dwFileCount = SFileGetMaxFileCount(hMpq); - SFileSetMaxFileCount(hMpq, dwFileCount - 1); - - // Add ZeroSize.txt (1) - sprintf(szMpqFileName, "ZeroSize_1.txt"); - for(i = 0; LocaleIDs[i] != 0xFFFF; i++) - { - PrintfTA(_T("Adding %s as %s (locale %04x) ...\n"), szFileName2, szMpqFileName, LocaleIDs[i]); - SFileSetLocale(LocaleIDs[i]); - if(!SFileAddFileEx(hMpq, szFileName2, szMpqFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) - printf("Cannot add the file\n"); + // Delete the file data + STORM_FREE(pFileData); } - - // Add ZeroSize.txt (1) - sprintf(szMpqFileName, "ZeroSize_2.txt"); - for(int i = 0; LocaleIDs[i] != 0xFFFF; i++) + else { - PrintfTA(_T("Adding %s as %s (locale %04x) ...\n"), szFileName2, szMpqFileName, LocaleIDs[i]); - SFileSetLocale(LocaleIDs[i]); - if(!SFileAddFileEx(hMpq, szFileName2, szMpqFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) - printf("Cannot add the file\n"); + nError = Logger.PrintError("Failed to open the file previously added"); } } - // Test rename function + // Close the MPQ archive + if(hMpq != NULL) + SFileCloseArchive(hMpq); + return nError; +} + +static int TestCreateArchive_EmptyMpq(const char * szPlainName, DWORD dwCreateFlags) +{ + TLogHelper Logger("CreateEmptyMpq", szPlainName); + HANDLE hMpq = NULL; + DWORD dwFileCount = 0; + int nError; + + // Create the full path name + nError = CreateNewArchive(&Logger, szPlainName, dwCreateFlags, 0, &hMpq); if(nError == ERROR_SUCCESS) { - _tprintf(_T("Testing rename files ...\n")); - SFileSetLocale(LANG_NEUTRAL); - if(!SFileRenameFile(hMpq, "FileTest_08.exe", "FileTest_08a.exe")) - { - nError = GetLastError(); - _tprintf(_T("Failed to rename the file\n")); - } + SearchArchive(&Logger, hMpq); + SFileCloseArchive(hMpq); + } - if(!SFileRenameFile(hMpq, "FileTest_08a.exe", "FileTest_08.exe")) + // Reopen the empty MPQ + if(nError == ERROR_SUCCESS) + { + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + if(nError == ERROR_SUCCESS) { - nError = GetLastError(); - _tprintf(_T("Failed to rename the file\n")); - } + SFileGetFileInfo(hMpq, SFileMpqNumberOfFiles, &dwFileCount, sizeof(dwFileCount), NULL); - if(!SFileRenameFile(hMpq, "FileTest_10.exe", "FileTest_10a.exe")) - { - nError = GetLastError(); - _tprintf(_T("Failed to rename the file\n")); + CheckIfFileIsPresent(&Logger, hMpq, "File00000000.xxx", false); + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, false); + SearchArchive(&Logger, hMpq); + SFileCloseArchive(hMpq); } + } - if(!SFileRenameFile(hMpq, "FileTest_10a.exe", "FileTest_10.exe")) + return nError; +} + +static int TestCreateArchive_FillArchive(const char * szPlainName) +{ + TLogHelper Logger("CreateFullMpq", szPlainName); + const char * szFileData = "TestCreateArchive_FillArchive: Testing file data"; + char szFileName[MAX_PATH]; + HANDLE hMpq = NULL; + DWORD dwMaxFileCount = 6; + DWORD dwCompression = MPQ_COMPRESSION_ZLIB; + DWORD dwFlags = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS; + int nError; + + // Create the new MPQ + nError = CreateNewArchive(&Logger, szPlainName, 0, dwMaxFileCount, &hMpq); + + // Now we should be able to add 6 files + if(nError == ERROR_SUCCESS) + { + for(DWORD i = 0; i < dwMaxFileCount; i++) { - nError = GetLastError(); - _tprintf(_T("Failed to rename the file\n")); + sprintf(szFileName, "AddedFile%03u.txt", i); + nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression); + if(nError != ERROR_SUCCESS) + break; } - - if(nError == ERROR_SUCCESS) - _tprintf(_T("Rename test succeeded.\n\n")); - else - _tprintf(_T("Rename test failed.\n\n")); } - // Compact the archive -// if(nError == ERROR_SUCCESS) -// SFileCompactArchive(hMpq); - - // Test changing hash table size + // Now the MPQ should be full. It must not be possible to add another file if(nError == ERROR_SUCCESS) - SFileSetMaxFileCount(hMpq, 0x95); + { + nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, false); + assert(nError != ERROR_SUCCESS); + nError = ERROR_SUCCESS; + } + // Close the archive to enforce saving all tables if(hMpq != NULL) SFileCloseArchive(hMpq); + hMpq = NULL; - // Try to reopen the archive - if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + // Reopen the archive again + if(nError == ERROR_SUCCESS) + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + + // The archive should still be full + if(nError == ERROR_SUCCESS) + { + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true); + AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, false); + } + + // The (listfile) must be present + if(nError == ERROR_SUCCESS) + { + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true); + nError = RemoveMpqFile(&Logger, hMpq, szFileName, true); + } + + // Now add the file again. This time, it should be possible OK + if(nError == ERROR_SUCCESS) + { + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, false); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, false); + nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression, true); + } + + // Now add the file again. This time, it should be fail + if(nError == ERROR_SUCCESS) + { + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, false); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, false); + AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression, false); + } + + // Close the archive and return + if(hMpq != NULL) SFileCloseArchive(hMpq); + hMpq = NULL; + + // Reopen the archive for the third time to verify that both internal files are there + if(nError == ERROR_SUCCESS) + { + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + if(nError == ERROR_SUCCESS) + { + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true); + SFileCloseArchive(hMpq); + } + } - _tprintf(_T("\n")); return nError; } -static int TestCreateArchive_PaliRoharBug(const TCHAR * szMpqName) +static int TestCreateArchive_IncMaxFileCount(const char * szPlainName) { - const TCHAR * szFileName = MAKE_PATH("FileTest.exe"); - HANDLE hMpq = NULL; // Handle of created archive - DWORD dwMaxFileCount = 0; - DWORD dwMpqFlags = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS; - char szMpqFileName[MAX_PATH]; - int nError = ERROR_SUCCESS; - int i; + TLogHelper Logger("IncMaxFileCount", szPlainName); + const char * szFileData = "TestCreateArchive_IncMaxFileCount: Testing file data"; + char szFileName[MAX_PATH]; + HANDLE hMpq = NULL; + DWORD dwMaxFileCount = 1; + int nError; - _tremove(szMpqName); - if(SFileCreateArchive(szMpqName, - MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_ATTRIBUTES, - 1, - &hMpq)) + // Create the new MPQ + nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4, dwMaxFileCount, &hMpq); + + // Now add exactly one file + if(nError == ERROR_SUCCESS) { - // Add the file there - SFileAddFileEx(hMpq, szFileName, "FileTest_base.exe", dwMpqFlags, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME); + nError = AddFileToMpq(&Logger, hMpq, "AddFile_base.txt", szFileData); SFileFlushArchive(hMpq); SFileCloseArchive(hMpq); + } - // Add the same file 10 times - for(i = 0; i < 10; i++) + // Now add 10 files. Each time we cannot add the file due to archive being full, + // we increment the max file count + if(nError == ERROR_SUCCESS) + { + for(DWORD i = 0; i < 10; i++) { - if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + // Open the archive again + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + if(nError != ERROR_SUCCESS) + break; + + // Add one file + sprintf(szFileName, "AddFile_%04u.txt", i); + nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData); + if(nError != ERROR_SUCCESS) { + // Increment the ma file count by one dwMaxFileCount = SFileGetMaxFileCount(hMpq) + 1; - _tprintf(_T("Increasing max file count to %u ...\n"), dwMaxFileCount); + Logger.PrintProgress("Increasing max file count to %u ...", dwMaxFileCount); SFileSetMaxFileCount(hMpq, dwMaxFileCount); - sprintf(szMpqFileName, "FileTest_%02u.exe", dwMaxFileCount); - PrintfTA(_T("Adding %s as %s\n"), szFileName, szMpqFileName); - if(!SFileAddFileEx(hMpq, szFileName, szMpqFileName, dwMpqFlags, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) - { - printf("Failed to add the file \"%s\".\n", szMpqFileName); - break; - } - - SFileFlushArchive(hMpq); - SFileCompactArchive(hMpq, NULL, false); - SFileCloseArchive(hMpq); + // Attempt to create the file again + nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, 0, 0, true); } + + // Compact the archive and close it + SFileSetCompactCallback(hMpq, CompactCallback, &Logger); + SFileCompactArchive(hMpq, NULL, false); + SFileCloseArchive(hMpq); + if(nError != ERROR_SUCCESS) + break; } } - _tprintf(_T("\n")); return nError; } - -static int TestAddFilesToMpq( - const TCHAR * szMpqName, - ... - ) +static int TestCreateArchive_UnicodeNames() { - const TCHAR * szFileName; - const TCHAR * szSrc; - char * szTrg; - HANDLE hMpq; - va_list argList; - char szMpqFileName[MAX_PATH]; + TLogHelper Logger("MpqUnicodeName"); int nError = ERROR_SUCCESS; - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - return GetLastError(); +#ifdef _UNICODE + nError = CreateNewArchive(&Logger, szUnicodeName1, MPQ_CREATE_ARCHIVE_V1, 15, NULL); + if(nError != ERROR_SUCCESS) + return nError; - va_start(argList, szMpqName); - while((szFileName = va_arg(argList, const TCHAR *)) != NULL) - { - // Convert the plain name to ANSI - szSrc = GetPlainFileNameT(szFileName); - szTrg = szMpqFileName; - while(*szSrc != 0) - *szTrg++ = (char)*szSrc++; - *szTrg = 0; - - // Add the file to MPQ - if(!SFileAddFileEx(hMpq, szFileName, - szMpqFileName, - MPQ_FILE_COMPRESS, - MPQ_COMPRESSION_ZLIB, - MPQ_COMPRESSION_NEXT_SAME)) - { - nError = GetLastError(); - printf("Failed to add the file \"%s\"\n", szFileName); - } - } + nError = CreateNewArchive(&Logger, szUnicodeName2, MPQ_CREATE_ARCHIVE_V2, 58, NULL); + if(nError != ERROR_SUCCESS) + return nError; + + nError = CreateNewArchive(&Logger, szUnicodeName3, MPQ_CREATE_ARCHIVE_V3, 15874, NULL); + if(nError != ERROR_SUCCESS) + return nError; + + nError = CreateNewArchive(&Logger, szUnicodeName4, MPQ_CREATE_ARCHIVE_V4, 87541, NULL); + if(nError != ERROR_SUCCESS) + return nError; + + nError = CreateNewArchive(&Logger, szUnicodeName5, MPQ_CREATE_ARCHIVE_V3, 87541, NULL); + if(nError != ERROR_SUCCESS) + return nError; - SFileCloseArchive(hMpq); + nError = CreateNewArchive(&Logger, szUnicodeName5, MPQ_CREATE_ARCHIVE_V2, 87541, NULL); +#endif // _UNICODE return nError; } -static int TestCreateArchiveFromMemory(const TCHAR * szMpqName) +static int TestCreateArchive_FileFlagTest(const char * szPlainName) { -#define FILE_SIZE 65535 + TLogHelper Logger("FileFlagTest", szPlainName); + HANDLE hMpq = NULL; // Handle of created archive + TCHAR szFileName1[MAX_PATH]; + TCHAR szFileName2[MAX_PATH]; + TCHAR szMpqName[MAX_PATH]; + const char * szMiddleFile = "FileTest_10.exe"; + LCID LocaleIDs[] = {0x000, 0x405, 0x406, 0x407, 0xFFFF}; + char szArchivedName[MAX_PATH]; + DWORD dwMaxFileCount = 0; + DWORD dwFileCount = 0; + size_t i; + int nError; - HANDLE hFile; - HANDLE hMPQ; - char* data = new char [FILE_SIZE]; // random memory data - char szFileName[100]; - int i; - - // Create an mpq file for testing - if(SFileCreateArchive(szMpqName, MPQ_CREATE_ARCHIVE_V2|MPQ_CREATE_ATTRIBUTES, 0x100000, &hMPQ)) - { - for(i = 0; i < 1000; i++) + // Create paths for local file to be added + CreateFullPathName(szFileName1, szMpqSubDir, "AddFile.exe"); + CreateFullPathName(szFileName2, szMpqSubDir, "AddFile.bin"); + + // Create an empty file that will serve as holder for the MPQ + nError = CreateEmptyFile(&Logger, szPlainName, 0x100000, szMpqName); + + // Create new MPQ archive over that file + if(nError == ERROR_SUCCESS) + nError = CreateNewArchive_FullPath(&Logger, szMpqName, MPQ_CREATE_ARCHIVE_V1, 17, &hMpq); + + // Add the same file multiple times + if(nError == ERROR_SUCCESS) + { + dwMaxFileCount = SFileGetMaxFileCount(hMpq); + for(i = 0; AddFlags[i] != 0xFFFFFFFF; i++) { - sprintf(szFileName, "File%03u.bin", i); - printf("Adding file %s\r", szFileName); + sprintf(szArchivedName, "FileTest_%02u.exe", i); + nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName1, AddFlags[i], 0); + if(nError != ERROR_SUCCESS) + break; - if(SFileCreateFile(hMPQ, szFileName, 0, FILE_SIZE, 0, MPQ_FILE_COMPRESS, &hFile)) - { - SFileWriteFile(hFile, data, FILE_SIZE, MPQ_COMPRESSION_ZLIB); - SFileFinishFile(hFile); - } + dwFileCount++; } } - SFileCloseArchive(hMPQ); - delete [] data; - return ERROR_SUCCESS; -} - -static int TestFileReadAndWrite( - const TCHAR * szMpqName, - const char * szFileName) -{ - LPBYTE pvFile = NULL; - HANDLE hFile = NULL; - HANDLE hMpq = NULL; - DWORD dwBytesRead; - DWORD dwFileSize = 0; - int nError = ERROR_SUCCESS; - - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + + // Delete a file in the middle of the file table + if(nError == ERROR_SUCCESS) { - nError = GetLastError(); - _tprintf(_T("Failed to open the archive %s (%u).\n"), szMpqName, nError); + Logger.PrintProgress("Removing file %s ...", szMiddleFile); + nError = RemoveMpqFile(&Logger, hMpq, szMiddleFile, true); + dwFileCount--; } + // Add one more file if(nError == ERROR_SUCCESS) { - if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) - { - nError = GetLastError(); - printf("Failed to open the file %s (%u).\n", szFileName, nError); - } + nError = AddLocalFileToMpq(&Logger, hMpq, "FileTest_xx.exe", szFileName1); + dwFileCount++; + } + + // Try to decrement max file count. This must succeed + if(nError == ERROR_SUCCESS) + { + Logger.PrintProgress("Attempting to decrement max file count ..."); + if(SFileSetMaxFileCount(hMpq, 5)) + nError = Logger.PrintError("Max file count decremented, even if it should fail"); } + // Add ZeroSize.txt several times under a different locale if(nError == ERROR_SUCCESS) { - if(!SFileGetFileInfo(hFile, SFILE_INFO_FILE_SIZE, &dwFileSize, sizeof(DWORD), NULL)) + for(i = 0; LocaleIDs[i] != 0xFFFF; i++) { - nError = GetLastError(); - _tprintf(_T("Failed to get the file size (%u).\n"), nError); + bool bMustSucceed = ((dwFileCount + 2) < dwMaxFileCount); + + SFileSetLocale(LocaleIDs[i]); + nError = AddLocalFileToMpq(&Logger, hMpq, "ZeroSize_1.txt", szFileName2); + if(nError != ERROR_SUCCESS) + { + if(bMustSucceed == false) + nError = ERROR_SUCCESS; + break; + } + + dwFileCount++; } } + // Add ZeroSize.txt again several times under a different locale if(nError == ERROR_SUCCESS) { - pvFile = new BYTE[dwFileSize]; - if(pvFile == NULL) + for(i = 0; LocaleIDs[i] != 0xFFFF; i++) { - nError = ERROR_NOT_ENOUGH_MEMORY; - printf("Failed to allocate buffer for the file (%u).\n", nError); + bool bMustSucceed = ((dwFileCount + 2) < dwMaxFileCount); + + SFileSetLocale(LocaleIDs[i]); + nError = AddLocalFileToMpq(&Logger, hMpq, "ZeroSize_2.txt", szFileName2, 0, 0, bMustSucceed); + if(nError != ERROR_SUCCESS) + { + if(bMustSucceed == false) + nError = ERROR_SUCCESS; + break; + } + + dwFileCount++; } } + // Verify how many files did we add to the MPQ if(nError == ERROR_SUCCESS) { - if(!SFileReadFile(hFile, pvFile, dwFileSize, &dwBytesRead, NULL)) + if(dwFileCount + 2 != dwMaxFileCount) { - nError = GetLastError(); - printf("Failed to read file (%u).\n", nError); + Logger.PrintErrorVa("Number of files added to MPQ was unexpected (expected %u, added %u)", dwFileCount, dwMaxFileCount - 2); + nError = ERROR_FILE_CORRUPT; } } - if(hFile != NULL) + // Test rename function + if(nError == ERROR_SUCCESS) { - SFileCloseFile(hFile); - hFile = NULL; + Logger.PrintProgress("Testing rename files ..."); + SFileSetLocale(LANG_NEUTRAL); + if(!SFileRenameFile(hMpq, "FileTest_08.exe", "FileTest_08a.exe")) + nError = Logger.PrintError("Failed to rename the file"); } if(nError == ERROR_SUCCESS) { - if(!SFileCreateFile(hMpq, szFileName, 0, dwFileSize, 0, MPQ_FILE_REPLACEEXISTING, &hFile)) - { - nError = GetLastError(); - printf("Failed to create %s in the archive (%u).\n", szFileName, nError); - } + if(!SFileRenameFile(hMpq, "FileTest_08a.exe", "FileTest_08.exe")) + nError = Logger.PrintError("Failed to rename the file"); } if(nError == ERROR_SUCCESS) { - if(!SFileWriteFile(hFile, pvFile, dwFileSize, 0)) + if(SFileRenameFile(hMpq, "FileTest_10.exe", "FileTest_10a.exe")) { - nError = GetLastError(); - printf("Failed to write the data to the MPQ (%u).\n", nError); + Logger.PrintError("Rename test succeeded even if it shouldn't"); + nError = ERROR_FILE_CORRUPT; } } - if(hFile != NULL) + if(nError == ERROR_SUCCESS) { - if(!SFileFinishFile(hFile)) + if(SFileRenameFile(hMpq, "FileTest_10a.exe", "FileTest_10.exe")) { - nError = GetLastError(); - printf("Failed to finalize file creation (%u).\n", nError); + Logger.PrintError("Rename test succeeded even if it shouldn't"); + nError = ERROR_FILE_CORRUPT; } } - if(pvFile != NULL) - delete [] pvFile; + // Close the archive if(hMpq != NULL) SFileCloseArchive(hMpq); + hMpq = NULL; + + // Try to reopen the archive + nError = OpenExistingArchive(&Logger, NULL, szPlainName, NULL); return nError; } -static int TestSignatureVerify(const TCHAR * szMpqName) +static int TestCreateArchive_CompressionsTest(const char * szPlainName) { - HANDLE hMpq; - - if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) + TLogHelper Logger("CompressionsTest", szPlainName); + HANDLE hMpq = NULL; // Handle of created archive + TCHAR szFileName[MAX_PATH]; // Source file to be added + TCHAR szMpqName[MAX_PATH]; + char szArchivedName[MAX_PATH]; + DWORD dwCmprCount = sizeof(Compressions) / sizeof(DWORD); + DWORD dwAddedFiles = 0; + DWORD dwFoundFiles = 0; + size_t i; + int nError; + + // Create paths for local file to be added + CreateFullPathName(szFileName, szMpqSubDir, "AddFile.wav"); + CreateFullPathName(szMpqName, NULL, szPlainName); + + // Create new archive + nError = CreateNewArchive_FullPath(&Logger, szMpqName, MPQ_CREATE_ARCHIVE_V4, 0x40, &hMpq); + + // Add the same file multiple times + if(nError == ERROR_SUCCESS) { - _tprintf(_T("Verifying digital signature in %s:\n"), szMpqName); - switch(SFileVerifyArchive(hMpq)) + Logger.UserTotal = dwCmprCount; + for(i = 0; i < dwCmprCount; i++) { - case ERROR_NO_SIGNATURE: - _tprintf(_T("No digital signature present.\n")); - break; - - case ERROR_VERIFY_FAILED: - _tprintf(_T("Failed to verify signature.\n")); - break; - - case ERROR_WEAK_SIGNATURE_OK: - _tprintf(_T("Weak signature is OK.\n")); + sprintf(szArchivedName, "WaveFile_%02u.wav", i + 1); + nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_SECTOR_CRC, Compressions[i]); + if(nError != ERROR_SUCCESS) break; - case ERROR_WEAK_SIGNATURE_ERROR: - _tprintf(_T("Weak signature mismatch.\n")); - break; + Logger.UserCount++; + dwAddedFiles++; + } - case ERROR_STRONG_SIGNATURE_OK: - _tprintf(_T("Strong signature is OK.\n")); - break; + SFileCloseArchive(hMpq); + } - case ERROR_STRONG_SIGNATURE_ERROR: - _tprintf(_T("Strong signature mismatch.\n")); - break; + // Reopen the archive extract each WAVE file and try to play it + if(nError == ERROR_SUCCESS) + { + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + if(nError == ERROR_SUCCESS) + { + SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_PLAY_WAVES, &dwFoundFiles, NULL); + SFileCloseArchive(hMpq); + } + + // Check if the number of found files is the same like the number of added files + // DOn;t forget that there will be (listfile) and (attributes) + if(dwFoundFiles != (dwAddedFiles + 2)) + { + Logger.PrintError("Number of found files does not match number of added files."); + nError = ERROR_FILE_CORRUPT; } - - SFileCloseArchive(hMpq); - _tprintf(_T("\n")); } - return 0; + return nError; } - -static int TestCreateArchiveCopy(const TCHAR * szMpqName, const TCHAR * szMpqCopyName, const char * szListFile) +static int TestCreateArchive_ListFilePos(const char * szPlainName) { - TFileStream * pStream; - TCHAR szLocalFile[MAX_PATH]; - HANDLE hMpq1 = NULL; // Handle of existing archive - HANDLE hMpq2 = NULL; // Handle of created archive - DWORD dwHashTableSize = 0; - int nError = ERROR_SUCCESS; - - // If no listfile or an empty one, use NULL - if(szListFile == NULL || *szListFile == 0) - szListFile = NULL; + TFileData * pFileData; + const char * szReaddedFile = "AddedFile_##.txt"; + const char * szFileMask = "AddedFile_%02u.txt"; + TLogHelper Logger("ListFilePos", szPlainName); + HANDLE hMpq = NULL; // Handle of created archive + char szArchivedName[MAX_PATH]; + DWORD dwMaxFileCount = 0x1E; + DWORD dwAddedCount = 0; + size_t i; + int nError; - // Create the new file - pStream = FileStream_CreateFile(szMpqCopyName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream == NULL) - nError = GetLastError(); + // Create a new archive with the limit of 0x20 files + nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4, dwMaxFileCount, &hMpq); - // Write some data + // Add 0x1E files if(nError == ERROR_SUCCESS) { - ULONGLONG FileSize = 0x100000; - - FileStream_SetSize(pStream, FileSize); - FileStream_Close(pStream); + for(i = 0; i < dwMaxFileCount; i++) + { + sprintf(szArchivedName, szFileMask, i); + nError = AddFileToMpq(&Logger, hMpq, szArchivedName, "This is a text data.", 0, 0, true); + if(nError != ERROR_SUCCESS) + break; + + dwAddedCount++; + } } - // Open the existing MPQ archive + // Delete few middle files if(nError == ERROR_SUCCESS) { - _tprintf(_T("Opening %s ...\n"), szMpqName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq1)) - nError = GetLastError(); + for(i = 0; i < (dwMaxFileCount / 2); i++) + { + sprintf(szArchivedName, szFileMask, i); + nError = RemoveMpqFile(&Logger, hMpq, szArchivedName, true); + if(nError != ERROR_SUCCESS) + break; + } } - // Well, now create the MPQ archive + // Close the archive + if(hMpq != NULL) + SFileCloseArchive(hMpq); + hMpq = NULL; + + // Reopen the archive to catch any asserts if(nError == ERROR_SUCCESS) - { - _tprintf(_T("Creating %s ...\n"), szMpqCopyName); - SFileGetFileInfo(hMpq1, SFILE_INFO_HASH_TABLE_SIZE, &dwHashTableSize, 4, NULL); - if(!SFileCreateArchive(szMpqCopyName, 0, dwHashTableSize, &hMpq2)) - nError = GetLastError(); - } + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); - // Copy all files from one archive to another + // Check that (listfile) is at the end if(nError == ERROR_SUCCESS) { - SFILE_FIND_DATA sf; - HANDLE hFind = SFileFindFirstFile(hMpq1, "*", &sf, szListFile); - bool bResult = true; + pFileData = LoadMpqFile(&Logger, hMpq, LISTFILE_NAME); + if(pFileData != NULL) + { + if(pFileData->dwBlockIndex < dwAddedCount) + Logger.PrintMessage("Unexpected file index of %s", LISTFILE_NAME); + STORM_FREE(pFileData); + } - _tprintf(_T("Copying files ...\n")); + pFileData = LoadMpqFile(&Logger, hMpq, ATTRIBUTES_NAME); + if(pFileData != NULL) + { + if(pFileData->dwBlockIndex <= dwAddedCount) + Logger.PrintMessage("Unexpected file index of %s", ATTRIBUTES_NAME); + STORM_FREE(pFileData); + } - if(hFind != NULL) + // Add new file to the archive. It should be added to position 0 + // (since position 0 should be free) + nError = AddFileToMpq(&Logger, hMpq, szReaddedFile, "This is a re-added file.", 0, 0, true); + if(nError == ERROR_SUCCESS) { - while(bResult) + pFileData = LoadMpqFile(&Logger, hMpq, szReaddedFile); + if(pFileData != NULL) { - if(strcmp(sf.cFileName, LISTFILE_NAME) && strcmp(sf.cFileName, ATTRIBUTES_NAME)) - { - SFileSetLocale(sf.lcLocale); - - // Create the local file name - MergeLocalPath(szLocalFile, szWorkDir, sf.szPlainName); - if(SFileExtractFile(hMpq1, sf.cFileName, szLocalFile, SFILE_OPEN_FROM_MPQ)) - { - printf("Extracting %s ... OK\n", sf.cFileName); - if(!SFileAddFile(hMpq2, szLocalFile, sf.cFileName, sf.dwFileFlags)) - { - nError = GetLastError(); - printf("Adding %s ... Failed\n\n", sf.cFileName); - _tremove(szLocalFile); - break; - } - else - { - printf("Adding %s ... OK\n", sf.cFileName); - } - } - else - { - printf("Extracting %s ... Failed\n", sf.cFileName); - } - - // Delete the added file - _tremove(szLocalFile); - } - - // Find the next file - bResult = SFileFindNextFile(hFind, &sf); + if(pFileData->dwBlockIndex != 0) + Logger.PrintMessage("Unexpected file index of %s", szReaddedFile); + STORM_FREE(pFileData); } - - // Close the search handle - SFileFindClose(hFind); - printf("\n"); } + + SFileCloseArchive(hMpq); } - // Close both archives - if(hMpq2 != NULL) - SFileCloseArchive(hMpq2); - if(hMpq1 != NULL) - SFileCloseArchive(hMpq1); return nError; } -static int TestOpenPatchedArchive(const TCHAR * szMpqName, ...) +static int TestCreateArchive_BigArchive(const char * szPlainName) { - TFileStream * pStream; - HANDLE hFile = NULL; - HANDLE hMpq = NULL; - va_list argList; - const char * szFileName = "Creature\\ALLIANCELIONMOUNT\\AllianceLion.M2"; - TCHAR szLocFileName[MAX_PATH]; - LPBYTE pbFullFile = NULL; - DWORD dwFileSize; - int nError = ERROR_SUCCESS; - - // Open the primary MPQ - _tprintf(_T("Opening %s ...\n"), szMpqName); - if(!SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq)) - { - nError = GetLastError(); - _tprintf(_T("Failed to open the archive %s ...\n"), szMpqName); - } - - // Add all patches - if(nError == ERROR_SUCCESS) - { - va_start(argList, szMpqName); - while((szMpqName = va_arg(argList, const TCHAR *)) != NULL) - { - _tprintf(_T("Adding patch %s ...\n"), szMpqName); - if(!SFileOpenPatchArchive(hMpq, szMpqName, NULL, 0)) - { - nError = GetLastError(); - printf("Failed to add patch %s ...\n", szMpqName); - } - } - va_end(argList); - } -/* - // Now search all files + const char * szFileMask = "AddedFile_%02u.txt"; + TLogHelper Logger("BigMpqTest"); + HANDLE hMpq = NULL; // Handle of created archive + TCHAR szFileName[MAX_PATH]; + char szArchivedName[MAX_PATH]; + DWORD dwMaxFileCount = 0x20; + DWORD dwAddedCount = 0; + size_t i; + int nError; + + // Create a new archive with the limit of 0x20 files + nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V3, dwMaxFileCount, &hMpq); if(nError == ERROR_SUCCESS) { - SFILE_FIND_DATA sf; - HANDLE hFind; - bool bResult = true; + // Now add few really big files + CreateFullPathName(szFileName, szMpqSubDir, "MPQ_1997_v1_Diablo1_DIABDAT.MPQ"); + Logger.UserTotal = (dwMaxFileCount / 2); - hFind = SFileFindFirstFile(hMpq, "World\\Minimaps\\Azeroth\\noLiquid_map20_44.*", &sf, NULL); - while(hFind && bResult) + for(i = 0; i < dwMaxFileCount / 2; i++) { - printf("%s\n", sf.cFileName); - bResult = SFileFindNextFile(hFind, &sf); + sprintf(szArchivedName, szFileMask, i + 1); + nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName, 0, 0, true); + if(nError != ERROR_SUCCESS) + break; + + Logger.UserCount++; + dwAddedCount++; } } - // Now try to open patched version of a file + // Close the archive + if(hMpq != NULL) + SFileCloseArchive(hMpq); + hMpq = NULL; + + // Reopen the archive to catch any asserts + if(nError == ERROR_SUCCESS) + nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + + // Check that (listfile) is at the end if(nError == ERROR_SUCCESS) { - SFileExtractFile(hMpq, szFileName, _T("E:\\noLiquid_map20_44.blp"), SFILE_OPEN_FROM_MPQ); + CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); + CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true); + + SFileCloseArchive(hMpq); } -*/ - // Now try to open patched version of "Achievement.dbc" - if(nError == ERROR_SUCCESS) + + return nError; +} + +static int TestForEachArchive(ARCHIVE_TEST pfnTest, char * szSearchMask, char * szPlainName) +{ + char * szPathBuff = NULL; + int nError = ERROR_SUCCESS; + + // If the name was not entered, use new one + if(szSearchMask == NULL) { - printf("Opening patched file \"%s\" ...\n", szFileName); - SFileHasFile(hMpq, szFileName); - SFileVerifyFile(hMpq, szFileName, SFILE_VERIFY_RAW_MD5); - if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) + szPathBuff = STORM_ALLOC(char, MAX_PATH); + if(szPathBuff != NULL) { - nError = GetLastError(); - printf("Failed to open patched file \"%s\"\n", szFileName); + CreateFullPathName(szPathBuff, szMpqSubDir, "*"); + szSearchMask = szPathBuff; + szPlainName = strrchr(szSearchMask, '*'); } } - // Verify of the patched version is correct - if(nError == ERROR_SUCCESS) - { - TCHAR * szPatchChain = NULL; - DWORD cbPatchChain = 0; - - // Get the patch chain - SFileGetFileInfo(hFile, SFILE_INFO_PATCH_CHAIN, szPatchChain, cbPatchChain, &cbPatchChain); - szPatchChain = (TCHAR *)(new BYTE[cbPatchChain]); - SFileGetFileInfo(hFile, SFILE_INFO_PATCH_CHAIN, szPatchChain, cbPatchChain, &cbPatchChain); - delete [] szPatchChain; - - // Get the size of the full patched file - dwFileSize = SFileGetFileSize(hFile, NULL); - if(dwFileSize != 0) - { - DWORD dwBytesRead = 0; - BYTE TempData[0x100]; + // At this point, both pointers must be valid + assert(szSearchMask != NULL && szPlainName != NULL); - SFileReadFile(hFile, TempData, sizeof(TempData), &dwBytesRead, NULL); - SFileSetFilePointer(hFile, 0, NULL, FILE_BEGIN); + // Now both must be entered + if(szSearchMask != NULL && szPlainName != NULL) + { +#ifdef PLATFORM_WINDOWS + WIN32_FIND_DATAA wf; + HANDLE hFind; - // Allocate space for the full file - pbFullFile = new BYTE[dwFileSize]; - if(pbFullFile != NULL) + // Initiate search. Use ANSI function only + hFind = FindFirstFileA(szSearchMask, &wf); + if(hFind != INVALID_HANDLE_VALUE) + { + // Skip the first entry, since it's always "." or ".." + while(FindNextFileA(hFind, &wf) && nError == ERROR_SUCCESS) { - if(!SFileReadFile(hFile, pbFullFile, dwFileSize, NULL, NULL)) - { - nError = GetLastError(); - printf("Failed to read full patched file data \"%s\"\n", szFileName); + // Found a directory? + if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if(wf.cFileName[0] != '.') + { + sprintf(szPlainName, "%s\\*", wf.cFileName); + nError = TestForEachArchive(pfnTest, szSearchMask, strrchr(szSearchMask, '*')); + } } - - if(nError == ERROR_SUCCESS) + else { - MergeLocalPath(szLocFileName, MAKE_PATH("Work//"), GetPlainFileNameA(szFileName)); - pStream = FileStream_CreateFile(szLocFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) + if(pfnTest != NULL) { - FileStream_Write(pStream, NULL, pbFullFile, dwFileSize); - FileStream_Close(pStream); + strcpy(szPlainName, wf.cFileName); + nError = pfnTest(szSearchMask); } } - - delete [] pbFullFile; } + + FindClose(hFind); } +#endif } - // Close handles - if(hFile != NULL) - SFileCloseFile(hFile); - if(hMpq != NULL) - SFileCloseArchive(hMpq); + // Free the path buffer, if any + if(szPathBuff != NULL) + STORM_FREE(szPathBuff); + szPathBuff = NULL; return nError; } -static int TestCompareTwoArchives( - const TCHAR * szMpqName1, - const TCHAR * szMpqName2, - const char * szListFile, - DWORD dwBlockSize) +//----------------------------------------------------------------------------- +// Main + +int main(int argc, char * argv[]) { - TMPQArchive * ha1 = NULL; - TMPQArchive * ha2 = NULL; - HANDLE hMpq1 = NULL; // Handle of the first archive - HANDLE hMpq2 = NULL; // Handle of the second archive - HANDLE hFile1 = NULL; - HANDLE hFile2 = NULL; int nError = ERROR_SUCCESS; - // If no listfile or an empty one, use NULL - if(szListFile == NULL || *szListFile == 0) - szListFile = NULL; +#if defined(_MSC_VER) && defined(_DEBUG) + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif // defined(_MSC_VER) && defined(_DEBUG) - _tprintf(_T("=============== Comparing MPQ archives ===============\n")); + // Initialize storage and mix the random number generator + printf("==== Test Suite for StormLib version %s ====\n", STORMLIB_VERSION_STRING); + nError = InitializeMpqDirectory(argv, argc); - // Open the first MPQ archive - if(nError == ERROR_SUCCESS && szMpqName1 != NULL) - { - _tprintf(_T("Opening %s ...\n"), szMpqName1); - if(!SFileOpenArchive(szMpqName1, 0, 0, &hMpq1)) - nError = GetLastError(); - ha1 = (TMPQArchive *)hMpq1; - } + // Search all testing archives and verify their SHA1 hash + if(nError == ERROR_SUCCESS) + nError = TestForEachArchive(TestVerifyFileChecksum, NULL, NULL); - // Open the second MPQ archive - if(nError == ERROR_SUCCESS && szMpqName2 != NULL) - { - _tprintf(_T("Opening %s ...\n"), szMpqName2); - if(!SFileOpenArchive(szMpqName2, 0, 0, &hMpq2)) - nError = GetLastError(); - ha2 = (TMPQArchive *)hMpq2; - } + // Test opening local file with SFileOpenFileEx + if(nError == ERROR_SUCCESS) + nError = TestOpenLocalFile("ListFile_Blizzard.txt"); - // Compare the header - if(nError == ERROR_SUCCESS && (ha1 != NULL && ha2 != NULL)) - { - if(ha1->pHeader->dwHeaderSize != ha2->pHeader->dwHeaderSize) - printf(" - Header size is different\n"); - if(ha1->pHeader->wFormatVersion != ha2->pHeader->wFormatVersion) - printf(" - Format version is different\n"); - if(ha1->pHeader->wSectorSize != ha2->pHeader->wSectorSize) - printf(" - Sector size is different\n"); - if(ha1->pHeader->HetTableSize64 != ha2->pHeader->HetTableSize64) - printf(" - HET table size is different\n"); - if(ha1->pHeader->BetTableSize64 != ha2->pHeader->BetTableSize64) - printf(" - BET table size is different\n"); - if(ha1->pHeader->dwHashTableSize != ha2->pHeader->dwHashTableSize) - printf(" - Hash table size is different\n"); - if(ha1->pHeader->dwBlockTableSize != ha2->pHeader->dwBlockTableSize) - printf(" - Block table size is different\n"); - } + // Test reading partial file + if(nError == ERROR_SUCCESS) + nError = TestPartFileRead("MPQ_2009_v2_WoW_patch.MPQ.part"); - // Find all files in the first archive and compare them + // Test working with an archive that has no listfile if(nError == ERROR_SUCCESS) - { - SFILE_FIND_DATA sf; - TMPQFile * hf1; - TMPQFile * hf2; - HANDLE hFind = SFileFindFirstFile(hMpq1, "*", &sf, szListFile); - bool bResult = true; + nError = TestOpenFile_OpenById("MPQ_1997_v1_Diablo1_DIABDAT.MPQ"); - while(hFind != NULL && bResult == true) - { -// printf("%s \r", sf.cFileName); - SFileSetLocale(sf.lcLocale); + // Open an empty archive (found in WoW cache - it's just a header) + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2012_v2_EmptyMpq.MPQ"); - // Open the first file - if(!SFileOpenFileEx(hMpq1, sf.cFileName, 0, &hFile1)) - printf("Failed to open the file %s in the first archive\n", sf.cFileName); + // Open an empty archive (created artificially - it's just a header) + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2013_v4_EmptyMpq.MPQ"); - if(!SFileOpenFileEx(hMpq2, sf.cFileName, 0, &hFile2)) - printf("Failed to open the file %s in the second archive\n", sf.cFileName); + // Open an empty archive (found in WoW cache - it's just a header) + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2013_v4_patch-base-16357.MPQ"); - if(hFile1 != NULL && hFile2 != NULL) - { - // Get the TMPQFile pointers - hf1 = (TMPQFile *)hFile1; - hf2 = (TMPQFile *)hFile2; + // Open an empty archive (found in WoW cache - it's just a header) + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2011_v4_InvalidHetEntryCount.MPQ"); - // Compare the file sizes - if(hf1->pFileEntry->dwFileSize != hf2->pFileEntry->dwFileSize) - printf("Different file size: %s (%u x %u)\n", sf.cFileName, hf1->pFileEntry->dwFileSize, hf2->pFileEntry->dwFileSize); + // Open an truncated archive + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2002_v1_BlockTableCut.MPQ"); - if(hf1->pFileEntry->dwCmpSize != hf2->pFileEntry->dwCmpSize) - printf("Different cmpr size: %s (%u x %u)\n", sf.cFileName, hf1->pFileEntry->dwCmpSize, hf2->pFileEntry->dwCmpSize); + // Open an Warcraft III map locked by a protector + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.w3x"); - if(hf1->pFileEntry->dwFlags != hf2->pFileEntry->dwFlags) - printf("Different mpq flags: %s (%08X x %08X)\n", sf.cFileName, hf1->pFileEntry->dwFlags, hf2->pFileEntry->dwFlags); + // Open an Warcraft III map locked by a protector + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_InvalidUserData.w3x"); - if(!CompareArchivedFiles(sf.cFileName, hFile1, hFile2, dwBlockSize)) - printf("Different file data: %s\n", sf.cFileName); + // Open an Warcraft III map locked by a protector + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_InvalidMpqFormat.w3x"); -// if(!CompareArchivedFilesRR(sf.cFileName, hFile1, hFile2, dwBlockSize)) -// printf("Different file data: %s\n", sf.cFileName); - } + // Open a MPQ that actually has user data + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2010_v2_HasUserData.s2ma"); - // Close both files - if(hFile2 != NULL) - SFileCloseFile(hFile2); - if(hFile1 != NULL) - SFileCloseFile(hFile1); - hFile2 = hFile1 = NULL; + // Open a MPQ archive v 3.0 + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2010_v3_expansion-locale-frFR.MPQ"); - // Find the next file - bResult = SFileFindNextFile(hFind, &sf); - } + // Open an encrypted archive from Starcraft II installer + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2011_v2_EncryptedMpq.MPQE"); - // Close the find handle - if(hFind != NULL) - SFileFindClose(hFind); - } + // Open a MPK archive from Longwu online + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPx_2013_v1_LongwuOnline.mpk"); - // Close both archives - clreol(); - printf("================ MPQ compare complete ================\n"); - if(hMpq2 != NULL) - SFileCloseArchive(hMpq2); - if(hMpq1 != NULL) - SFileCloseArchive(hMpq1); - return nError; -} + // Open a SQP archive from War of the Immortals + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPx_2013_v1_WarOfTheImmortals.sqp", "ListFile_WarOfTheImmortals.txt"); -//----------------------------------------------------------------------------- -// Searching all known MPQs + // Open a patched archive + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_Patched(PatchList_WoW_OldWorld13286, "OldWorld\\World\\Model.blob", 2); -#ifdef _WIN32 -static int TestSearchOneArchive(const TCHAR * szMpqName) -{ - SFILE_FIND_DATA sf; - HANDLE hFind; - HANDLE hMpq; - const TCHAR * szExtension; - bool bFound = true; + // Open a patched archive + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_Patched(PatchList_WoW15050, "World\\Model.blob", 8); - // Get the file extension - szExtension = _tcsrchr(szMpqName, _T('.')); - if(szExtension == NULL) - return ERROR_SUCCESS; + // Open a patched archive. The file is in each patch as full, so there is 0 patches in the chain + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_Patched(PatchList_WoW16965, "DBFilesClient\\BattlePetNPCTeamMember.db2", 0); - // Only search defined extensions - if(_tcsicmp(szExtension, _T(".mpq")) && _tcsnicmp(szExtension, _T(".SC2"), 4)) - return ERROR_SUCCESS; + // Check the opening archive for read-only + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_ReadOnly("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", true); - _tprintf(_T("Searching %s ...\n"), szMpqName); + // Check the opening archive for read-only + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_ReadOnly("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false); - // Try to open the MPQ - if(SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq)) - { - hFind = SFileFindFirstFile(hMpq, "*", &sf, NULL); - if(hFind != NULL) - { - while(bFound) - { - if(sf.dwFileFlags & MPQ_FILE_DELETE_MARKER) - _tprintf(_T("Delete marker: %s:%hs\n"), szMpqName, sf.cFileName); + // Check the SFileGetFileInfo function + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_GetFileInfo("MPQ_2002_v1_StrongSignature.w3m", "MPQ_2013_v4_SC2_EmptyMap.SC2Map"); - bFound = SFileFindNextFile(hFind, &sf); - } - } + // Check archive signature + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_VerifySignature("MPQ_1999_v1_WeakSignature.exe", "War2Patch_202.exe"); - SFileCloseArchive(hMpq); - } + // Check archive signature + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_VerifySignature("MPQ_2002_v1_StrongSignature.w3m", "(10)DustwallowKeys.w3m"); - return ERROR_SUCCESS; -} + // Compact the archive + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_CraftedUserData("MPQ_2010_v3_expansion-locale-frFR.MPQ", "StormLibTest_CraftedMpq1_v3.mpq"); -static int TestSearchAllArchives(const TCHAR * szSearchMask) -{ - WIN32_FIND_DATA wf; - LPTSTR szFilePart; - HANDLE hFind; - TCHAR szFullPath[MAX_PATH]; - BOOL bFound = TRUE; + // Open a MPQ (add custom user data to it + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_SC2_EmptyMap.SC2Map", "StormLibTest_CraftedMpq2_v4.mpq"); - // Initiate search - _tcscpy(szFullPath, szSearchMask); - szFilePart = _tcschr(szFullPath, _T('*')); - assert(szFilePart != NULL); + // Open a MPQ (add custom user data to it) + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_expansion1.MPQ", "StormLibTest_CraftedMpq3_v4.mpq"); - // Begin search - hFind = FindFirstFile(szSearchMask, &wf); - if(hFind != INVALID_HANDLE_VALUE) - { - while(bFound) - { - // Eliminate "." and ".." - if(wf.cFileName[0] != _T('.')) - { - // Construct the full path - _tcscpy(szFilePart, wf.cFileName); + // Test modifying file with no (listfile) and no (attributes) + if(nError == ERROR_SUCCESS) + nError = TestAddFile_ListFileTest("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false, false); - // If it a directory? - if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - _tcscat(szFullPath, _T("\\*")); - TestSearchAllArchives(szFullPath); - } - else - { - TestSearchOneArchive(szFullPath); - } - } - bFound = FindNextFile(hFind, &wf); - } - FindClose(hFind); - } + // Test modifying an archive that contains (listfile) and (attributes) + if(nError == ERROR_SUCCESS) + nError = TestAddFile_ListFileTest("MPQ_2013_v4_SC2_EmptyMap.SC2Map", true, true); - return ERROR_SUCCESS; -} -#endif + // Create an empty archive v2 + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_EmptyMpq("StormLibTest_EmptyMpq_v2.mpq", MPQ_CREATE_ARCHIVE_V2); -//----------------------------------------------------------------------------- -// Main -// + // Create an empty archive v4 + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_EmptyMpq("StormLibTest_EmptyMpq_v4.mpq", MPQ_CREATE_ARCHIVE_V4); -int main(void) -{ - int nError = ERROR_SUCCESS; + // Create an archive and fill it with files up to the max file count + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq"); -#if defined(_MSC_VER) && defined(_DEBUG) - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); -#endif // defined(_MSC_VER) && defined(_DEBUG) + // Create an archive, and increment max file count several times + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_IncMaxFileCount("StormLibTest_IncMaxFileCount.mpq"); - // Mix the random number generator -// srand(GetTickCount()); + // Create a MPQ archive with UNICODE names + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_UnicodeNames(); - // Test for encrypted streams -// FileStream_OpenFile(_T("e:\\Hry\\StarCraft II\\Updates\\SC2_HotS_20_BGDL\\SC2_HotS_20_BGDL_deDE.MPQE"), STREAM_PROVIDER_ENCRYPTED | BASE_PROVIDER_FILE); + // Create a MPQ file, add files with various flags + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_FileFlagTest("StormLibTest_FileFlagTest.mpq"); - // Test structure sizes -// if(nError == ERROR_SUCCESS) -// nError = TestStructureSizes(); + // Create a MPQ file, add files with various compressions + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_CompressionsTest("StormLibTest_CompressionTest.mpq"); -// if(nError == ERROR_SUCCESS) -// nError = TestOpenLocalFile("C:\\Windows\\System32\\kernel32.dll"); + // Check if the listfile is always created at the end of the file table in the archive + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_ListFilePos("StormLibTest_ListFilePos.mpq"); - // Test reading partial file -// if(nError == ERROR_SUCCESS) -// nError = TestPartFileRead(MAKE_PATH("2009 - PartialMPQs/patch.MPQ.part")); - -// if(nError == ERROR_SUCCESS) -// { -// CompareHuffmanCompressions7(); -// nError = CompareHuffmanCompressions0(); -// } - -// if(nError == ERROR_SUCCESS) -// nError = ComparePklibCompressions(); - - // Test LZMA compression method against the code ripped from Starcraft II -// if(nError == ERROR_SUCCESS) -// nError = CompareLzmaCompressions(MPQ_SECTOR_SIZE); - - // Test compression methods -// if(nError == ERROR_SUCCESS) -// nError = TestSectorCompress(MPQ_SECTOR_SIZE); - - // Test the archive open and close -// if(nError == ERROR_SUCCESS) -// nError = TestArchiveOpenAndClose(MAKE_PATH("2012 - Longwu Online\\Data\\Scp.mpk")); -// nError = TestArchiveOpenAndClose(MAKE_PATH("1997 - Diablo I\\DIABDAT.MPQ")); -// nError = TestArchiveOpenAndClose(MAKE_PATH("2012 - War of the Immortals\\1\\Other.sqp")); - - // Test for bug reported by BlueRaja - if(nError == ERROR_SUCCESS) - nError = TestAddFilesToArchive(MAKE_PATH("Tya's Zerg Defense.SC2Map")); - -// if(nError == ERROR_SUCCESS) -// nError = TestFindFiles(MAKE_PATH("2002 - Warcraft III/HumanEd.mpq")); - - // Create a big MPQ archive -// if(nError == ERROR_SUCCESS) -// nError = TestCreateArchive_PaliRoharBug(MAKE_PATH("Test.mpq")); -// nError = TestCreateArchive(MAKE_PATH("Test.mpq")); -// nError = TestCreateArchive((const TCHAR*)szUnicodeName1); -// nError = TestCreateArchive((const TCHAR*)szUnicodeName2); -// nError = TestCreateArchive((const TCHAR*)szUnicodeName3); -// nError = TestCreateArchive((const TCHAR*)szUnicodeName4); -// nError = TestCreateArchive((const TCHAR*)szUnicodeName5); -// nError = TestCreateArchive((const TCHAR*)szUnicodeName6); - -// if(nError == ERROR_SUCCESS) -// nError = TestAddFilesToMpq(MAKE_PATH("wow-update-13202.MPQ"), -// "c:\\Tools32\\Arj32.exe", -// "c:\\Tools32\\autoruns.chm", -// "c:\\Tools32\\CPUEater.exe", -// "c:\\Tools32\\dumpbin.exe", -// "c:\\Tools32\\editbin.exe", -// "c:\\Tools32\\fsg.ini", -// "c:\\Tools32\\hiew8.ini", -// "c:\\Tools32\\ida.bat", -// "c:\\Tools32\\mp3.ini", -// NULL); - -// if(nError == ERROR_SUCCESS) -// nError = TestCreateArchiveFromMemory(MAKE_PATH("Test-leak.mpq")); - -// if(nError == ERROR_SUCCESS) -// nError = TestFileReadAndWrite(MAKE_PATH("2002 - Warcraft III/(10)DustwallowKeys.w3m"), "war3map.j"); - - // Verify the archive signature -// if(nError == ERROR_SUCCESS) -// nError = TestSignatureVerify(MAKE_PATH("1998 - Starcraft/BW-1152.exe")); -// nError = TestSignatureVerify(MAKE_PATH("2002 - Warcraft III/(10)DustwallowKeys.w3m")); -// nError = TestSignatureVerify(MAKE_PATH("2002 - Warcraft III/War3TFT_121b_English.exe")); -// nError = TestSignatureVerify(MAKE_PATH("2004 - World of Warcraft/WoW-2.3.3.7799-to-2.4.0.8089-enUS-patch.exe")); -// nError = TestSignatureVerify(MAKE_PATH("2004 - World of Warcraft/standalone.MPQ")); - - // Compact the archive -// if(nError == ERROR_SUCCESS) -// nError = TestMpqCompacting(MAKE_PATH("wow-update-base-14333.MPQ")); - - // Create copy of the archive, appending some bytes before the MPQ header -// if(nError == ERROR_SUCCESS) -// nError = TestCreateArchiveCopy(MAKE_PATH("PartialMPQs/interface.MPQ.part"), MAKE_PATH("PartialMPQs/interface-copy.MPQ.part"), NULL); - - -// if(nError == ERROR_SUCCESS) -// { -// nError = TestOpenPatchedArchive(MAKE_PATH("2013 - WoW\\12911\\world.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-13164.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-13205.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-13287.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-13329.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-13596.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-13623.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-13914.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14007.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14333.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14480.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14545.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14946.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-15005.MPQ"), -// MAKE_PATH("2013 - WoW\\12911\\wow-update-base-15050.MPQ"), -// NULL); -// } - -// if(nError == ERROR_SUCCESS) -// { -// nError = TestCompareTwoArchives(MAKE_PATH("Sound-copy.mpq"), -// MAKE_PATH("Sound.mpq"), -// NULL, -// 0x1001); -// } - -// if(nError == ERROR_SUCCESS) -// nError = TestSearchAllArchives(MAKE_PATH("*")); + // Open a MPQ (add custom user data to it) + if(nError == ERROR_SUCCESS) + nError = TestCreateArchive_BigArchive("StormLibTest_BigArchive_v4.mpq"); return nError; } |