diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-11-13 07:56:31 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-11-13 07:56:31 +0100 |
commit | a70863499e5e9e6560477b8f4a0a594d6e62650e (patch) | |
tree | 21118705bf1d412ed9aea0b51daf25f98b4fabad | |
parent | 66b71713c2a60e6f6ccc55245b067dad34fd6154 (diff) |
+ Support for SQP and MPK archives
+ Makefiles fixed
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | Makefile.linux | 2 | ||||
-rw-r--r-- | StormLib.vcproj | 198 | ||||
-rw-r--r-- | StormLib_dll.vcproj | 4 | ||||
-rw-r--r-- | StormLib_test.vcproj | 4 | ||||
-rw-r--r-- | makefile.w32 | 2 | ||||
-rw-r--r-- | src/SBaseCommon.cpp | 513 | ||||
-rw-r--r-- | src/SBaseFileTable.cpp | 249 | ||||
-rw-r--r-- | src/SBaseSubTypes.cpp | 598 | ||||
-rw-r--r-- | src/SCompression.cpp | 53 | ||||
-rw-r--r-- | src/SFileCompactArchive.cpp | 17 | ||||
-rw-r--r-- | src/SFileCreateArchive.cpp | 5 | ||||
-rw-r--r-- | src/SFileFindFile.cpp | 1 | ||||
-rw-r--r-- | src/SFileOpenArchive.cpp | 39 | ||||
-rw-r--r-- | src/SFileOpenFileEx.cpp | 16 | ||||
-rw-r--r-- | src/SFileReadFile.cpp | 306 | ||||
-rw-r--r-- | src/StormCommon.h | 23 | ||||
-rw-r--r-- | src/StormLib.h | 89 | ||||
-rw-r--r-- | src/StormPort.h | 9 | ||||
-rw-r--r-- | test/Test.cpp | 23 |
20 files changed, 1506 insertions, 647 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e5b869..3fb206a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,7 +223,7 @@ set(ZLIB_BZIP2_FILES src/bzip2/huffman.c src/bzip2/randtable.c src/zlib/adler32.c - src/zlib/compress2.c + src/zlib/compress.c src/zlib/crc32.c src/zlib/deflate.c src/zlib/inffast.c diff --git a/Makefile.linux b/Makefile.linux index e9d89c7..34440cf 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -231,7 +231,7 @@ COBJS = src/libtomcrypt/src/hashes/sha1.o \ src/pklib/explode.o \ src/zlib/crc32.o \ src/zlib/trees.o \ - src/zlib/compress2.o \ + src/zlib/compress.o \ src/zlib/adler32.o \ src/zlib/inftrees.o \ src/zlib/inffast.o \ diff --git a/StormLib.vcproj b/StormLib.vcproj index 8475695..8fc4f4e 100644 --- a/StormLib.vcproj +++ b/StormLib.vcproj @@ -1559,6 +1559,10 @@ </FileConfiguration> </File> <File + RelativePath=".\src\SBaseSubTypes.cpp" + > + </File> + <File RelativePath=".\src\SCompression.cpp" > <FileConfiguration @@ -3690,50 +3694,156 @@ /> </FileConfiguration> </File> - <Filter Name="adpcm"> - <File RelativePath=".\src\adpcm\adpcm.cpp" /> - <File RelativePath=".\src\adpcm\adpcm.h" /> + <Filter + Name="adpcm" + > + <File + RelativePath=".\src\adpcm\adpcm.cpp" + > + </File> + <File + RelativePath=".\src\adpcm\adpcm.h" + > + </File> </Filter> - <Filter Name="bzip2"> - <File RelativePath=".\src\bzip2\blocksort.c" /> - <File RelativePath=".\src\bzip2\bzlib.c" /> - <File RelativePath=".\src\bzip2\compress.c" /> - <File RelativePath=".\src\bzip2\crctable.c" /> - <File RelativePath=".\src\bzip2\decompress.c"/> - <File RelativePath=".\src\bzip2\huffman.c" /> - <File RelativePath=".\src\bzip2\randtable.c" /> + <Filter + Name="bzip2" + > + <File + RelativePath=".\src\bzip2\blocksort.c" + > + </File> + <File + RelativePath=".\src\bzip2\bzlib.c" + > + </File> + <File + RelativePath=".\src\bzip2\compress.c" + > + </File> + <File + RelativePath=".\src\bzip2\crctable.c" + > + </File> + <File + RelativePath=".\src\bzip2\decompress.c" + > + </File> + <File + RelativePath=".\src\bzip2\huffman.c" + > + </File> + <File + RelativePath=".\src\bzip2\randtable.c" + > + </File> </Filter> - <Filter Name="huffman"> - <File RelativePath=".\src\huffman\huff.cpp" /> - <File RelativePath=".\src\huffman\huff.h" /> + <Filter + Name="huffman" + > + <File + RelativePath=".\src\huffman\huff.cpp" + > + </File> + <File + RelativePath=".\src\huffman\huff.h" + > + </File> </Filter> - <Filter Name="libtomcrypt"> - <Filter Name="hashes"> - <File RelativePath=".\src\libtomcrypt\src\hashes\hash_memory.c" /> - <File RelativePath=".\src\libtomcrypt\src\hashes\md5.c" /> - <File RelativePath=".\src\libtomcrypt\src\hashes\sha1.c" /> + <Filter + Name="libtomcrypt" + > + <Filter + Name="hashes" + > + <File + RelativePath=".\src\libtomcrypt\src\hashes\hash_memory.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\hashes\md5.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\hashes\sha1.c" + > + </File> </Filter> - <Filter Name="math"> - <File RelativePath=".\src\libtomcrypt\src\math\ltm_desc.c" /> - <File RelativePath=".\src\libtomcrypt\src\math\multi.c" /> - <File RelativePath=".\src\libtomcrypt\src\math\rand_prime.c" /> + <Filter + Name="math" + > + <File + RelativePath=".\src\libtomcrypt\src\math\ltm_desc.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\math\multi.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\math\rand_prime.c" + > + </File> </Filter> - <Filter Name="misc"> - <File RelativePath=".\src\libtomcrypt\src\misc\base64_decode.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_argchk.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_find_hash.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_find_prng.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_descriptor.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_is_valid.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_libc.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_ltc_mp_descriptor.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_descriptor.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_is_valid.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_register_hash.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\crypt_register_prng.c" /> - <File RelativePath=".\src\libtomcrypt\src\misc\zeromem.c" /> + <Filter + Name="misc" + > + <File + RelativePath=".\src\libtomcrypt\src\misc\base64_decode.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_argchk.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_find_hash.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_find_prng.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_descriptor.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_is_valid.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_libc.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_ltc_mp_descriptor.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_descriptor.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_is_valid.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_register_hash.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\crypt_register_prng.c" + > + </File> + <File + RelativePath=".\src\libtomcrypt\src\misc\zeromem.c" + > + </File> </Filter> - <Filter Name="pk"> + <Filter + Name="pk" + > <Filter Name="asn1" > @@ -3923,9 +4033,15 @@ <Filter Name="libtommath" > - <File RelativePath=".\src\libtommath\bn_fast_mp_invmod.c" /> - <File RelativePath=".\src\libtommath\bn_fast_mp_montgomery_reduce.c" /> - <File + <File + RelativePath=".\src\libtommath\bn_fast_mp_invmod.c" + > + </File> + <File + RelativePath=".\src\libtommath\bn_fast_mp_montgomery_reduce.c" + > + </File> + <File RelativePath=".\src\libtommath\bn_fast_s_mp_mul_digs.c" > </File> diff --git a/StormLib_dll.vcproj b/StormLib_dll.vcproj index 92b5ef0..4991465 100644 --- a/StormLib_dll.vcproj +++ b/StormLib_dll.vcproj @@ -553,6 +553,10 @@ </FileConfiguration> </File> <File + RelativePath=".\src\SBaseSubTypes.cpp" + > + </File> + <File RelativePath=".\src\SCompression.cpp" > <FileConfiguration diff --git a/StormLib_test.vcproj b/StormLib_test.vcproj index d928bbe..61e04ea 100644 --- a/StormLib_test.vcproj +++ b/StormLib_test.vcproj @@ -523,6 +523,10 @@ </FileConfiguration> </File> <File + RelativePath=".\src\SBaseSubTypes.cpp" + > + </File> + <File RelativePath=".\src\SCompression.cpp" > <FileConfiguration diff --git a/makefile.w32 b/makefile.w32 index 8826ccd..2e3b743 100644 --- a/makefile.w32 +++ b/makefile.w32 @@ -235,7 +235,7 @@ OBJS_C = src/bzip2/blocksort.o \ src/pklib/explode.o \ src/pklib/implode.o \ src/zlib/adler32.o \ - src/zlib/compress2.o \ + src/zlib/compress.o \ src/zlib/crc32.o \ src/zlib/deflate.o \ src/zlib/inffast.o \ diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 58b2fd5..78537cf 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -26,7 +26,8 @@ USHORT wPlatform = 0; // File platform //----------------------------------------------------------------------------- // Conversion to uppercase/lowercase -// This table converts ASCII characters to lowercase and slash to backslash +// Converts ASCII characters to lowercase +// Converts slash (0x2F) to backslash (0x5C) unsigned char AsciiToLowerTable[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, @@ -47,13 +48,34 @@ unsigned char AsciiToLowerTable[256] = 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; -// This table converts ASCII characters to uppercase and slash to backslash -// BUGBUG: Reverted conversion of normal slash to backslash -// Will we have issues on Linux/Mac, when adding files like /home/Ladik/Files/File.ext ? +// Converts ASCII characters to uppercase +// Converts slash (0x2F) to backslash (0x5C) unsigned char AsciiToUpperTable[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +// Converts ASCII characters to uppercase +// Does NOT convert slash (0x2F) to backslash (0x5C) +unsigned char AsciiToUpperTable_Slash[256] = +{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, @@ -126,9 +148,8 @@ DWORD HashString(const char * szFileName, DWORD dwHashType) while(*pbKey != 0) { - // Note: AsciiToUpperTable conversion table must not convert '/' to '\', - // due to SQP data files for War of the Immortal, - // who commonly use file names like "../Data/Task/1315.str" + // Convert the input character to uppercase + // Convert slash (0x2F) to backslash (0x5C) ch = AsciiToUpperTable[*pbKey++]; dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); @@ -138,6 +159,46 @@ DWORD HashString(const char * szFileName, DWORD dwHashType) return dwSeed1; } +DWORD HashStringSlash(const char * szFileName, DWORD dwHashType) +{ + LPBYTE pbKey = (BYTE *)szFileName; + DWORD dwSeed1 = 0x7FED7FED; + DWORD dwSeed2 = 0xEEEEEEEE; + DWORD ch; + + while(*pbKey != 0) + { + // Convert the input character to uppercase + // DON'T convert slash (0x2F) to backslash (0x5C) + ch = AsciiToUpperTable_Slash[*pbKey++]; + + dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); + dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; + } + + return dwSeed1; +} + +DWORD HashStringLower(const char * szFileName, DWORD dwHashType) +{ + LPBYTE pbKey = (BYTE *)szFileName; + DWORD dwSeed1 = 0x7FED7FED; + DWORD dwSeed2 = 0xEEEEEEEE; + DWORD ch; + + while(*pbKey != 0) + { + // Convert the input character to lower + // DON'T convert slash (0x2F) to backslash (0x5C) + ch = AsciiToLowerTable[*pbKey++]; + + dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); + dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; + } + + return dwSeed1; +} + //----------------------------------------------------------------------------- // Calculates the hash table size for a given amount of files @@ -191,71 +252,34 @@ ULONGLONG HashStringJenkins(const char * szFileName) } //----------------------------------------------------------------------------- -// Processes War of the Immortals data files (SQP) -// +// Copies the string from char * to TCHAR * and back -int ConvertSqpHeaderToFormat4( - TMPQArchive * ha, - ULONGLONG FileSize, - DWORD dwFlags) -{ - TMPQHeader * pHeader = ha->pHeader; - TSQPHeader SqpHeader; +#ifdef _UNICODE - // SQP files from War of the Immortal use MPQ file format with slightly - // modified structure. These fields have different position: - // - // Offset TMPQHeader TSQPHeader - // ------ ---------- ----------- - // 000C wFormatVersion dwHashTablePos (lo) - // 000E wSectorSize dwHashTablePos (hi) - // 001C dwBlockTableSize (lo) wBlockSize - // 001E dwHashTableSize (hi) wFormatVersion - - // Can't open the archive with ceraint flags - if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) - return ERROR_FILE_CORRUPT; - - // The file must not be greater than 4 GB - if((FileSize >> 0x20) != 0) - return ERROR_FILE_CORRUPT; +// For UNICODE builds, we need two functions +void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength) +{ + mbstowcs(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} - // Copy the MPQ header into the SQP header - memcpy(&SqpHeader, ha->pHeader, sizeof(TSQPHeader)); - assert(SqpHeader.dwHeaderSize == sizeof(TSQPHeader)); +void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength) +{ + wcstombs(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} - // SQP format uses header size of 0x20 - if(SqpHeader.dwID == ID_MPQ && SqpHeader.dwHeaderSize == MPQ_HEADER_SIZE_V1 && SqpHeader.dwArchiveSize == FileSize) - { - // Check for fixed value in the SQP files - if(SqpHeader.wFormatVersion == MPQ_FORMAT_VERSION_1 && SqpHeader.wSectorSize == 3) - { - // Translate the SQP header into MPQ Header - pHeader->wFormatVersion = SqpHeader.wFormatVersion; - pHeader->wSectorSize = SqpHeader.wSectorSize; - pHeader->dwHashTablePos = SqpHeader.dwHashTablePos; - pHeader->dwBlockTablePos = SqpHeader.dwBlockTablePos; - pHeader->dwHashTableSize = SqpHeader.dwHashTableSize; - pHeader->dwBlockTableSize = SqpHeader.dwBlockTableSize; - - // Initialize the fields of header 2.0+ - memset(&pHeader->HiBlockTablePos64, 0, MPQ_HEADER_SIZE_V4 - MPQ_HEADER_SIZE_V1); - - // Initialize the fields of 3.0 header - pHeader->ArchiveSize64 = SqpHeader.dwArchiveSize; - pHeader->HashTableSize64 = SqpHeader.dwHashTableSize * sizeof(TMPQHash); - pHeader->BlockTableSize64 = SqpHeader.dwBlockTableSize * sizeof(TMPQBlock); - - // Mark this file as SQP file - ha->dwFlags |= MPQ_FLAG_READ_ONLY; - ha->dwSubType = MPQ_SUBTYPE_SQP; - return ERROR_SUCCESS; - } - } +#else - return ERROR_FILE_CORRUPT; +// 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 @@ -264,10 +288,10 @@ int ConvertMpqHeaderToFormat4( ULONGLONG FileSize, DWORD dwFlags) { + TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; ULONGLONG ByteOffset; - TMPQHeader * pHeader = ha->pHeader; DWORD dwExpectedArchiveSize; - USHORT wFormatVersion = pHeader->wFormatVersion; + 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 @@ -280,6 +304,9 @@ int ConvertMpqHeaderToFormat4( { 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) { @@ -314,6 +341,9 @@ int ConvertMpqHeaderToFormat4( 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 @@ -414,9 +444,12 @@ int ConvertMpqHeaderToFormat4( 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(ha->pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, ha->pHeader->MD5_MpqHeader)) + if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader)) nError = ERROR_FILE_CORRUPT; break; @@ -487,18 +520,6 @@ void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwSeed1) } } -/* -void EncryptMpqTable(void * pvMpqTable, DWORD dwLength, const char * szKey) -{ - EncryptMpqBlock(pvMpqTable, dwLength, HashString(szKey, MPQ_HASH_FILE_KEY)); -} - -void DecryptMpqTable(void * pvMpqTable, DWORD dwLength, const char * szKey) -{ - DecryptMpqBlock(pvMpqTable, dwLength, HashString(szKey, MPQ_HASH_FILE_KEY)); -} -*/ - /** * Functions tries to get file decryption key. The trick comes from sector * positions which are stored at the begin of each compressed file. We know the @@ -684,114 +705,139 @@ bool IsValidFileHandle(TMPQFile * hf) //----------------------------------------------------------------------------- // Hash table and block table manipulation +// Attempts to search a free hash entry, or an entry whose names and locale matches +TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale) +{ + 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 dwIndex; + + // Set the initial index + dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask); + + // Search the hash table and return the found entries in the following priority: + // 1) <MATCHING_ENTRY> + // 2) <DELETED-ENTRY> + // 3) <FREE-ENTRY> + // 4) NULL + for(;;) + { + TMPQHash * pHash = ha->pHashTable + dwIndex; + + // If we found a matching entry, return that one + if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == lcLocale) + return pHash; + + // If we found a deleted entry, remember it but keep searching + if(pHash->dwBlockIndex == HASH_ENTRY_DELETED && pDeletedEntry == NULL) + pDeletedEntry = pHash; + + // If we found a free entry, we need to stop searching + if(pHash->dwBlockIndex == HASH_ENTRY_FREE) + { + pFreeEntry = pHash; + break; + } + + // Move to the next hash entry. + // If we reached the starting entry, it's failure. + dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + if(dwIndex == dwStartIndex) + break; + } + + // If we found a deleted entry, return that one preferentially + 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) { - TMPQHash * pStartHash; // File hash entry (start) - TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash; // File hash entry (current) - DWORD dwHashTableSizeMask; - DWORD dwIndex = HashString(szFileName, MPQ_HASH_TABLE_INDEX); - DWORD dwName1 = HashString(szFileName, MPQ_HASH_NAME_A); - DWORD dwName2 = HashString(szFileName, MPQ_HASH_NAME_B); - - // Get the first possible has entry that might be the one - dwHashTableSizeMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - pStartHash = pHash = ha->pHashTable + (dwIndex & dwHashTableSizeMask); - - // There might be deleted entries in the hash table prior to our desired entry. - while(pHash->dwBlockIndex != HASH_ENTRY_FREE) + 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); + + // Search the hash table + for(;;) { - // If the entry agrees, we found it. + TMPQHash * pHash = ha->pHashTable + dwIndex; + + // If the entry matches, we found it. if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize) return pHash; + // If that hash entry is a free entry, it means we haven't found the file + if(pHash->dwBlockIndex == HASH_ENTRY_FREE) + return NULL; + // Move to the next hash entry. Stop searching // if we got reached the original hash entry - if(++pHash >= pHashEnd) - pHash = ha->pHashTable; - if(pHash == pStartHash) - break; + dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + if(dwIndex == dwStartIndex) + return NULL; } - - // The apropriate hash entry was not found - return NULL; } -TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash) +TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash) { - TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash = pPrevHash; - DWORD dwName1 = pPrevHash->dwName1; - DWORD dwName2 = pPrevHash->dwName2; + DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable); + DWORD dwName1 = pHash->dwName1; + DWORD dwName2 = pHash->dwName2; + DWORD dwIndex = (DWORD)(pHash - ha->pHashTable); - // Now go for any next entry that follows the pPrevHash, + // Now go for any next entry that follows the pHash, // until either free hash entry was found, or the start entry was reached for(;;) { // Move to the next hash entry. Stop searching // if we got reached the original hash entry - if(++pHash >= pHashEnd) - pHash = ha->pHashTable; - if(pHash == pFirstHash) - break; - - // If the entry is a free entry, stop search - if(pHash->dwBlockIndex == HASH_ENTRY_FREE) - break; + dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + if(dwIndex == dwStartIndex) + return NULL; + pHash = ha->pHashTable + dwIndex; - // If the entry is not free and the name agrees, we found it + // If the entry matches, we found it. if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->pHeader->dwBlockTableSize) return pHash; - } - // No next entry - return NULL; + // If that hash entry is a free entry, it means we haven't found the file + if(pHash->dwBlockIndex == HASH_ENTRY_FREE) + return NULL; + } } // Allocates an entry in the hash table -DWORD AllocateHashEntry( +TMPQHash * AllocateHashEntry( TMPQArchive * ha, TFileEntry * pFileEntry) { - TMPQHash * pStartHash; // File hash entry (start) - TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash; // File hash entry (current) - DWORD dwHashTableSizeMask; - DWORD dwIndex = HashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX); - DWORD dwName1 = HashString(pFileEntry->szFileName, MPQ_HASH_NAME_A); - DWORD dwName2 = HashString(pFileEntry->szFileName, MPQ_HASH_NAME_B); - - // Get the first possible has entry that might be the one - dwHashTableSizeMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - pStartHash = pHash = ha->pHashTable + (dwIndex & dwHashTableSizeMask); - - // There might be deleted entries in the hash table prior to our desired entry. - while(pHash->dwBlockIndex < HASH_ENTRY_DELETED) + TMPQHash * pHash; + DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX); + DWORD dwName1 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_A); + DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B); + + // Attempt to find a free hash entry + pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, pFileEntry->lcLocale); + if(pHash != NULL) { - // If there already is an existing entry, reuse it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == pFileEntry->lcLocale) - break; - - // Move to the next hash entry. - // If we reached the starting entry, it's failure. - if(++pHash >= pHashEnd) - pHash = ha->pHashTable; - if(pHash == pStartHash) - return HASH_ENTRY_FREE; + // Fill the free hash entry + pHash->dwName1 = dwName1; + pHash->dwName2 = dwName2; + pHash->lcLocale = pFileEntry->lcLocale; + pHash->wPlatform = pFileEntry->wPlatform; + pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); + + // Fill the hash index in the file entry + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); } - // Fill the free hash entry - pHash->dwName1 = dwName1; - pHash->dwName2 = dwName2; - pHash->lcLocale = pFileEntry->lcLocale; - pHash->wPlatform = pFileEntry->wPlatform; - pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); - - // Fill the hash index in the file entry - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - return pFileEntry->dwHashIndex; + return pHash; } // Finds a free space in the MPQ where to store next data @@ -855,62 +901,78 @@ TMPQFile * CreateMpqFile(TMPQArchive * ha) // Loads a table from MPQ. // Can be used for hash table, block table, sector offset table or sector checksum table -int LoadMpqTable( +void * LoadMpqTable( TMPQArchive * ha, ULONGLONG ByteOffset, - void * pvTable, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey) { LPBYTE pbCompressed = NULL; - LPBYTE pbToRead = (LPBYTE)pvTable; + LPBYTE pbMpqTable; + LPBYTE pbToRead; int nError = ERROR_SUCCESS; - // "interface.MPQ.part" in trial version of World of Warcraft - // has block table and hash table compressed. - if(dwCompressedSize < dwRealSize) + // Allocate the MPQ table + pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwRealSize); + if(pbMpqTable != NULL) { - // Allocate temporary buffer for holding compressed data - pbCompressed = STORM_ALLOC(BYTE, dwCompressedSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + // "interface.MPQ.part" in trial version of World of Warcraft + // has block table and hash table compressed. + if(dwCompressedSize < dwRealSize) + { + // Allocate temporary buffer for holding compressed data + pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize); + if(pbCompressed == NULL) + { + STORM_FREE(pbMpqTable); + return NULL; + } + } - // Assign the temporary buffer as target for read operation - pbToRead = pbCompressed; - } + // If everything succeeded, read the raw table form the MPQ + if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize)) + { + // First of all, decrypt the table + if(dwKey != 0) + { + BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); + DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey); + BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); + } - // Read the table - if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize)) - { - // First of all, decrypt the table - if(dwKey != 0) + // If the table is compressed, decompress it + if(dwCompressedSize < dwRealSize) + { + int cbOutBuffer = (int)dwRealSize; + 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); + } + else { - BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); - DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey); - BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); + nError = GetLastError(); } - // If the table is compressed, decompress it - if(dwCompressedSize < dwRealSize) + // If read failed, free the table and return + if(nError != ERROR_SUCCESS) { - int cbOutBuffer = (int)dwRealSize; - int cbInBuffer = (int)dwCompressedSize; - - if(!SCompDecompress2(pvTable, &cbOutBuffer, pbCompressed, cbInBuffer)) - nError = GetLastError(); + STORM_FREE(pbMpqTable); + pbMpqTable = NULL; + } - // Free the temporary buffer + // Free the compression buffer, if any + if(pbCompressed != NULL) STORM_FREE(pbCompressed); - } - } - else - { - nError = GetLastError(); } - BSWAP_ARRAY32_UNSIGNED(pvTable, dwRealSize); - return nError; + // Return the MPQ table + return pbMpqTable; } void CalculateRawSectorOffset( @@ -1259,18 +1321,15 @@ int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile) if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize) return ERROR_SUCCESS; - // Allocate the array for the sector checksums - hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount); - if(hf->SectorChksums == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - // Calculate offset of the CRC table dwCrcSize = hf->dwSectorCount * sizeof(DWORD); dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount]; CalculateRawSectorOffset(RawFilePos, hf, dwCrcOffset); // Now read the table from the MPQ - return LoadMpqTable(ha, RawFilePos, hf->SectorChksums, dwCompressedSize, dwCrcSize, 0); + hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0); + if(hf->SectorChksums == NULL) + return ERROR_NOT_ENOUGH_MEMORY; } } @@ -1778,44 +1837,42 @@ void ConvertTMPQUserData(void *userData) } // Swaps the TMPQHeader structure -void ConvertTMPQHeader(void *header) +void ConvertTMPQHeader(void *header, uint16_t version) { TMPQHeader * theHeader = (TMPQHeader *)header; - - theHeader->dwID = SwapUInt32(theHeader->dwID); - theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize); - theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize); - theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion); - theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize); - theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos); - theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos); - theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize); - theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize); - - if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) + + // Swap header part version 1 + if(version == MPQ_FORMAT_VERSION_1) + { + theHeader->dwID = SwapUInt32(theHeader->dwID); + theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize); + theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize); + theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion); + theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize); + theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos); + theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos); + theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize); + theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize); + } + + if(version == MPQ_FORMAT_VERSION_2 || version == MPQ_FORMAT_VERSION_3) { - // Swap the hi-block table position theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64); - theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi); theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi); + theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64); + theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64); + theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64); + } - if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3) - { - theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64); - theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64); - theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64); - - if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4) - { - theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64); - theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64); - theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64); - theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64); - theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64); - } - } - } + if(version == MPQ_FORMAT_VERSION_4) + { + theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64); + theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64); + theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64); + theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64); + theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64); + } } #endif // PLATFORM_LITTLE_ENDIAN diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 7b6d2a6..011badf 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -394,52 +394,6 @@ static int ConvertMpqBlockTable( return ERROR_SUCCESS; } -static int ConvertSqpBlockTable( - TMPQArchive * ha, - TFileEntry * pFileTable, - TSQPBlock * pBlockTable) -{ - TFileEntry * pFileEntry; - TMPQHeader * pHeader = ha->pHeader; - TSQPBlock * pSqpBlock; - TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; - TMPQHash * pHash; - - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) - { - if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - pSqpBlock = pBlockTable + pHash->dwBlockIndex; - - if(!(pSqpBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pSqpBlock->dwFlags & MPQ_FILE_EXISTS)) - { - // Convert SQP block table entry to the file entry - pFileEntry->ByteOffset = pSqpBlock->dwFilePos; - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->dwFileSize = pSqpBlock->dwFSize; - pFileEntry->dwCmpSize = pSqpBlock->dwCSize; - pFileEntry->dwFlags = pSqpBlock->dwFlags; - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - else - { - // If the hash table entry doesn't point to the valid file item, - // we invalidate the hash table entry - pHash->dwName1 = 0xFFFFFFFF; - pHash->dwName2 = 0xFFFFFFFF; - pHash->lcLocale = 0xFFFF; - pHash->wPlatform = 0xFFFF; - pHash->dwBlockIndex = HASH_ENTRY_DELETED; - } - } - } - - return ERROR_SUCCESS; -} - - static TMPQHash * TranslateHashTable( TMPQArchive * ha, ULONGLONG * pcbTableSize) @@ -1654,8 +1608,8 @@ TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID l // If the MPQ has hash table, we have to insert the new entry into the hash table if(ha->pHashTable != NULL && bHashEntryExists == false) { - dwHashIndex = AllocateHashEntry(ha, pFileEntry); - assert(dwHashIndex != HASH_ENTRY_FREE); + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); } // If the MPQ has HET table, we have to insert it to the HET table as well @@ -1721,8 +1675,8 @@ int RenameFileEntry( { // Try to find the hash table entry for the new file name // Note: If this fails, we leave the MPQ in a corrupt state - dwFileIndex = AllocateHashEntry(ha, pFileEntry); - if(dwFileIndex == HASH_ENTRY_FREE) + pHash = AllocateHashEntry(ha, pFileEntry); + if(pHash == NULL) nError = ERROR_FILE_CORRUPT; } @@ -1976,52 +1930,13 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) return ERROR_SUCCESS; } -int VerifyAndConvertSqpHashTable(TMPQArchive * ha, TMPQHash * pHashTable, DWORD dwTableSize) -{ - TMPQHash * pMpqHashEnd = pHashTable + dwTableSize; - TSQPHash * pSqpHash = (TSQPHash *)pHashTable; - TMPQHash * pMpqHash; - DWORD dwBlockIndex; - - // Parse all SQP hash table entries - for(pMpqHash = pHashTable; pMpqHash < pMpqHashEnd; pMpqHash++, pSqpHash++) - { - // Ignore free entries - if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE) - { - // Check block index against the size of the block table - dwBlockIndex = pSqpHash->dwBlockIndex; - if(ha->pHeader->dwBlockTableSize <= dwBlockIndex && dwBlockIndex < HASH_ENTRY_DELETED) - return ERROR_FILE_CORRUPT; - - // We do not support nonzero locale and platform ID - if(pSqpHash->dwAlwaysZero != 0 && pSqpHash->dwAlwaysZero != HASH_ENTRY_FREE) - return ERROR_FILE_CORRUPT; - - // Store the file name hash - pMpqHash->dwName1 = pSqpHash->dwName1; - pMpqHash->dwName2 = pSqpHash->dwName2; - - // Store the rest. Note that this must be done last, - // because pSqpHash->dwBlockIndex corresponds to pMpqHash->dwName2 - pMpqHash->dwBlockIndex = dwBlockIndex; - pMpqHash->wPlatform = 0; - pMpqHash->lcLocale = 0; - } - } - - // The conversion went OK - return ERROR_SUCCESS; -} - TMPQHash * LoadHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG ByteOffset; - TMPQHash * pHashTable; + TMPQHash * pHashTable = NULL; DWORD dwTableSize; DWORD dwCmpSize; - int nError; // If the MPQ has no hash table, do nothing if(pHeader->dwHashTablePos == 0 && pHeader->wHashTablePosHi == 0) @@ -2031,37 +1946,40 @@ TMPQHash * LoadHashTable(TMPQArchive * ha) if(pHeader->dwHashTableSize == 0) return NULL; - // Allocate buffer for the hash table - dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash); - pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); - if(pHashTable == NULL) - return NULL; + // Load the hash table for MPQ variations + switch(ha->dwSubType) + { + case MPQ_SUBTYPE_MPQ: - // Compressed size of the hash table - dwCmpSize = (DWORD)pHeader->HashTableSize64; + // Get the compressed and uncompressed hash table size + dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash); + dwCmpSize = (DWORD)pHeader->HashTableSize64; - // - // Load the table from the MPQ, with decompression - // - // Note: We will NOT check if the hash table is properly decrypted. - // Some MPQ protectors corrupt the hash table by rewriting part of it. - // Hash table, the way how it works, allows arbitrary values for unused entries. - // + // + // Load the table from the MPQ, with decompression + // + // Note: We will NOT check if the hash table is properly decrypted. + // Some MPQ protectors corrupt the hash table by rewriting part of it. + // Hash table, the way how it works, allows arbitrary values for unused entries. + // - ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); - nError = LoadMpqTable(ha, ByteOffset, pHashTable, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE); - - // If the file is a SQP file, we need to verify the SQP hash table and convert it to MPQ hash table - if(ha->dwSubType == MPQ_SUBTYPE_SQP) - nError = VerifyAndConvertSqpHashTable(ha, pHashTable, pHeader->dwHashTableSize); - - // If anything failed, free the hash table - if(nError != ERROR_SUCCESS) - { - STORM_FREE(pHashTable); - pHashTable = NULL; + ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE); + break; + + case MPQ_SUBTYPE_SQP: + pHashTable = LoadSqpHashTable(ha); + break; + + case MPQ_SUBTYPE_MPK: + pHashTable = LoadMpkHashTable(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; } @@ -2103,11 +2021,10 @@ static void FixBlockTableSize( TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) { TMPQHeader * pHeader = ha->pHeader; - TMPQBlock * pBlockTable; + TMPQBlock * pBlockTable = NULL; ULONGLONG ByteOffset; DWORD dwTableSize; DWORD dwCmpSize; - int nError; // Do nothing if the block table position is zero if(pHeader->dwBlockTablePos == 0 && pHeader->wBlockTablePosHi == 0) @@ -2117,50 +2034,54 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) if(pHeader->dwBlockTableSize == 0) return NULL; - // Sanity check, enforced by LoadAnyHashTable - assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); + // Load the block table for MPQ variations + switch(ha->dwSubType) + { + 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; + // Calculate sizes of both tables + ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + dwCmpSize = (DWORD)pHeader->BlockTableSize64; - // Allocate space for the block table - // Note: pHeader->dwBlockTableSize can be zero !!! - pBlockTable = STORM_ALLOC(TMPQBlock, ha->dwMaxFileCount); - if(pBlockTable == NULL) - return NULL; + // 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. + 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); + } - // Fill the block table with zeros - memset(pBlockTable, 0, dwTableSize); + // + // One of the first cracked versions of Diablo I had block table unencrypted + // StormLib does NOT support such MPQs anymore, as they are incompatible + // with compressed block table feature + // - // 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. - 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); - } + // If failed to load the block table, delete it + pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - // - // One of the first cracked versions of Diablo I had block table unencrypted - // StormLib does NOT support such MPQs anymore, as they are incompatible - // with compressed block table feature - // + // Defense against MPQs that claim block table to be bigger than it really is + if(pBlockTable != NULL) + FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); + break; - // Load the block table - nError = LoadMpqTable(ha, ByteOffset, pBlockTable, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - if(nError != ERROR_SUCCESS) - { - // Failed, sorry - STORM_FREE(pBlockTable); - return NULL; + case MPQ_SUBTYPE_SQP: + + pBlockTable = LoadSqpBlockTable(ha); + break; + + case MPQ_SUBTYPE_MPK: + + pBlockTable = LoadMpkBlockTable(ha); + break; } - // Defense against MPQs that claim block table to be bigger than it really is - FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); return pBlockTable; } @@ -2260,14 +2181,14 @@ int BuildFileTable_Classic( { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; - void * pBlockTable; + TMPQBlock * pBlockTable; int nError = ERROR_SUCCESS; // Sanity checks assert(ha->pHashTable != NULL); // Load the block table - pBlockTable = LoadBlockTable(ha, FileSize); + pBlockTable = (TMPQBlock *)LoadBlockTable(ha, FileSize); if(pBlockTable != NULL) { TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; @@ -2276,11 +2197,7 @@ int BuildFileTable_Classic( // If we don't have HET table, we build the file entries from the hash&block tables if(ha->pHetTable == NULL) { - // For MPQ files, treat the block table as MPQ Block Table - if(ha->dwSubType == MPQ_SUBTYPE_MPQ) - nError = ConvertMpqBlockTable(ha, pFileTable, (TMPQBlock *)pBlockTable); - else - nError = ConvertSqpBlockTable(ha, pFileTable, (TSQPBlock *)pBlockTable); + nError = ConvertMpqBlockTable(ha, pFileTable, pBlockTable); } else { @@ -2636,6 +2553,8 @@ int SaveMPQTables(TMPQArchive * ha) // Write the MPQ header if(nError == ERROR_SUCCESS) { + TMPQHeader SaveMpqHeader; + // Update the size of the archive pHeader->ArchiveSize64 = TablePos; pHeader->dwArchiveSize = (DWORD)TablePos; @@ -2644,10 +2563,12 @@ int SaveMPQTables(TMPQArchive * ha) CalculateDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader); // Write the MPQ header to the file - BSWAP_TMPQHEADER(pHeader); - if(!FileStream_Write(ha->pStream, &ha->MpqPos, pHeader, pHeader->dwHeaderSize)) + memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); + if(!FileStream_Write(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize)) nError = GetLastError(); - BSWAP_TMPQHEADER(pHeader); } // Clear the changed flag diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp new file mode 100644 index 0000000..5c78004 --- /dev/null +++ b/src/SBaseSubTypes.cpp @@ -0,0 +1,598 @@ +/*****************************************************************************/ +/* SBaseSubTypes.cpp Copyright (c) Ladislav Zezula 2013 */ +/*---------------------------------------------------------------------------*/ +/* Conversion routines for archive formats that are similar to MPQ format */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 02.11.11 1.00 Lad The first version of SBaseSubTypes.cpp */ +/*****************************************************************************/ + +#define __STORMLIB_SELF__ +#include "StormLib.h" +#include "StormCommon.h" + +/*****************************************************************************/ +/* */ +/* Support for SQP file format (War of the Immortals) */ +/* */ +/*****************************************************************************/ + +typedef struct _TSQPHeader +{ + // The ID_MPQ ('MPQ\x1A') signature + DWORD dwID; + + // Size of the archive header + DWORD dwHeaderSize; + + // 32-bit size of MPQ archive + DWORD dwArchiveSize; + + // Offset to the beginning of the hash table, relative to the beginning of the archive. + DWORD dwHashTablePos; + + // Offset to the beginning of the block table, relative to the beginning of the archive. + DWORD dwBlockTablePos; + + // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for + // the original MoPaQ format, or less than 2^20 for the Burning Crusade format. + DWORD dwHashTableSize; + + // Number of entries in the block table + DWORD dwBlockTableSize; + + // Must be zero for SQP files + USHORT wFormatVersion; + + // Power of two exponent specifying the number of 512-byte disk sectors in each file sector + // in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize. + USHORT wSectorSize; + +} TSQPHeader; + +typedef struct _TSQPHash +{ + // Most likely the lcLocale+wPlatform. + DWORD dwAlwaysZero; + + // If the hash table entry is valid, this is the index into the block table of the file. + // Otherwise, one of the following two values: + // - FFFFFFFFh: Hash table entry is empty, and has always been empty. + // Terminates searches for a given file. + // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file). + // Does not terminate searches for a given file. + DWORD dwBlockIndex; + + // The hash of the file path, using method A. + DWORD dwName1; + + // The hash of the file path, using method B. + DWORD dwName2; + +} TSQPHash; + +typedef struct _TSQPBlock +{ + // Offset of the beginning of the file, relative to the beginning of the archive. + DWORD dwFilePos; + + // Flags for the file. See MPQ_FILE_XXXX constants + DWORD dwFlags; + + // Compressed file size + DWORD dwCSize; + + // Uncompressed file size + DWORD dwFSize; + +} TSQPBlock; + +//----------------------------------------------------------------------------- +// Functions - SQP file format + +// This function converts SQP file header into MPQ file header +int ConvertSqpHeaderToFormat4( + TMPQArchive * ha, + ULONGLONG FileSize, + DWORD dwFlags) +{ + TSQPHeader * pSqpHeader = (TSQPHeader *)ha->HeaderData; + TMPQHeader Header; + + // SQP files from War of the Immortal use MPQ file format with slightly + // modified structure. These fields have different position: + // + // Offset TMPQHeader TSQPHeader + // ------ ---------- ----------- + // 000C wFormatVersion dwHashTablePos (lo) + // 000E wSectorSize dwHashTablePos (hi) + // 001C dwBlockTableSize (lo) wBlockSize + // 001E dwHashTableSize (hi) wFormatVersion + + // Can't open the archive with certain flags + if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) + return ERROR_FILE_CORRUPT; + + // The file must not be greater than 4 GB + if((FileSize >> 0x20) != 0) + return ERROR_FILE_CORRUPT; + + // Translate the SQP header into a MPQ header + memset(&Header, 0, sizeof(TMPQHeader)); + Header.dwID = BSWAP_INT32_UNSIGNED(pSqpHeader->dwID); + Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHeaderSize); + Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwArchiveSize); + Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTablePos); + Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTablePos); + Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTableSize); + Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTableSize); + Header.wFormatVersion = BSWAP_INT16_UNSIGNED(pSqpHeader->wFormatVersion); + Header.wSectorSize = BSWAP_INT16_UNSIGNED(pSqpHeader->wSectorSize); + assert(Header.dwHeaderSize == sizeof(TSQPHeader)); + + // Verify the SQP header + if(Header.dwID == ID_MPQ && Header.dwHeaderSize == sizeof(TSQPHeader) && Header.dwArchiveSize == FileSize) + { + // Check for fixed values of version and sector size + if(Header.wFormatVersion == MPQ_FORMAT_VERSION_1 && Header.wSectorSize == 3) + { + // Initialize the fields of 3.0 header + Header.ArchiveSize64 = Header.dwArchiveSize; + Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash); + Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock); + + // Copy the converted MPQ header back + memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader)); + + // Mark this file as SQP file + ha->pfnHashString = HashStringSlash; + ha->dwFlags |= MPQ_FLAG_READ_ONLY; + ha->dwSubType = MPQ_SUBTYPE_SQP; + return ERROR_SUCCESS; + } + } + + return ERROR_FILE_CORRUPT; +} + +void * LoadSqpTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize, DWORD dwKey) +{ + TMPQHeader * pHeader = ha->pHeader; + ULONGLONG ByteOffset; + LPBYTE pbSqpTable; + + // Allocate buffer for the table + pbSqpTable = STORM_ALLOC(BYTE, cbTableSize); + if(pbSqpTable != NULL) + { + // Load the table + ByteOffset = ha->MpqPos + dwByteOffset; + if(FileStream_Read(ha->pStream, &ByteOffset, pbSqpTable, cbTableSize)) + { + // Decrypt the SQP table + DecryptMpqBlock(pbSqpTable, cbTableSize, dwKey); + return pbSqpTable; + } + + // Free the table + STORM_FREE(pbSqpTable); + } + + return NULL; +} + +TMPQHash * LoadSqpHashTable(TMPQArchive * ha) +{ + TMPQHeader * pHeader = ha->pHeader; + TSQPHash * pSqpHashTable; + TSQPHash * pSqpHashEnd; + TSQPHash * pSqpHash; + TMPQHash * pMpqHash; + DWORD dwBlockIndex; + int nError = ERROR_SUCCESS; + + // Load the hash table + pSqpHashTable = (TSQPHash *)LoadSqpTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TSQPHash), MPQ_KEY_HASH_TABLE); + if(pSqpHashTable != NULL) + { + // Parse the entire hash table and convert it to MPQ hash table + pSqpHashEnd = pSqpHashTable + pHeader->dwHashTableSize; + pMpqHash = (TMPQHash *)pSqpHashTable; + for(pSqpHash = pSqpHashTable; pSqpHash < pSqpHashEnd; pSqpHash++, pMpqHash++) + { + // Ignore free entries + if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE) + { + // Check block index against the size of the block table + dwBlockIndex = pSqpHash->dwBlockIndex; + if(pHeader->dwBlockTableSize <= dwBlockIndex && dwBlockIndex < HASH_ENTRY_DELETED) + nError = ERROR_FILE_CORRUPT; + + // We do not support nonzero locale and platform ID + if(pSqpHash->dwAlwaysZero != 0 && pSqpHash->dwAlwaysZero != HASH_ENTRY_FREE) + nError = ERROR_FILE_CORRUPT; + + // Store the file name hash + pMpqHash->dwName1 = pSqpHash->dwName1; + pMpqHash->dwName2 = pSqpHash->dwName2; + + // Store the rest. Note that this must be done last, + // because pSqpHash->dwBlockIndex corresponds to pMpqHash->dwName2 + pMpqHash->dwBlockIndex = dwBlockIndex; + pMpqHash->wPlatform = 0; + pMpqHash->lcLocale = 0; + } + } + + // If an error occured, we need to free the hash table + if(nError != ERROR_SUCCESS) + { + STORM_FREE(pSqpHashTable); + pSqpHashTable = NULL; + } + } + + // Return the converted hash table (or NULL on failure) + return (TMPQHash *)pSqpHashTable; +} + +// Loads the SQP Block table and converts it to a MPQ block table +TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha) +{ + TMPQHeader * pHeader = ha->pHeader; + TSQPBlock * pSqpBlockTable; + TSQPBlock * pSqpBlockEnd; + TSQPBlock * pSqpBlock; + TMPQBlock * pMpqBlock; + DWORD dwFlags; + int nError = ERROR_SUCCESS; + + // Load the hash table + pSqpBlockTable = (TSQPBlock *)LoadSqpTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TSQPBlock), MPQ_KEY_BLOCK_TABLE); + if(pSqpBlockTable != NULL) + { + // Parse the entire hash table and convert it to MPQ hash table + pSqpBlockEnd = pSqpBlockTable + pHeader->dwBlockTableSize; + pMpqBlock = (TMPQBlock *)pSqpBlockTable; + for(pSqpBlock = pSqpBlockTable; pSqpBlock < pSqpBlockEnd; pSqpBlock++, pMpqBlock++) + { + // Check for valid flags + if(pSqpBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) + nError = ERROR_FILE_CORRUPT; + + // Convert SQP block table entry to MPQ block table entry + dwFlags = pSqpBlock->dwFlags; + pMpqBlock->dwCSize = pSqpBlock->dwCSize; + pMpqBlock->dwFSize = pSqpBlock->dwFSize; + pMpqBlock->dwFlags = dwFlags; + } + + // If an error occured, we need to free the hash table + if(nError != ERROR_SUCCESS) + { + STORM_FREE(pSqpBlockTable); + pSqpBlockTable = NULL; + } + } + + // Return the converted hash table (or NULL on failure) + return (TMPQBlock *)pSqpBlockTable; +} + +/*****************************************************************************/ +/* */ +/* Support for MPK file format (Longwu Online) */ +/* */ +/*****************************************************************************/ + +#define MPK_FILE_UNKNOWN_0001 0x00000001 // Seems to be always present +#define MPK_FILE_UNKNOWN_0010 0x00000010 // Seems to be always present +#define MPK_FILE_COMPRESSED 0x00000100 // Indicates a compressed file +#define MPK_FILE_UNKNOWN_2000 0x00002000 // Seems to be always present +#define MPK_FILE_EXISTS 0x01000000 // Seems to be always present + +typedef struct _TMPKHeader +{ + // The ID_MPK ('MPK\x1A') signature + DWORD dwID; + + // Contains '2000' + DWORD dwVersion; + + // 32-bit size of the archive + DWORD dwArchiveSize; + + // Size of the archive header + DWORD dwHeaderSize; + + DWORD dwHashTablePos; + DWORD dwHashTableSize; + DWORD dwBlockTablePos; + DWORD dwBlockTableSize; + DWORD dwUnknownPos; + DWORD dwUnknownSize; +} TMPKHeader; + + +typedef struct _TMPKHash +{ + // The hash of the file path, using method A. + DWORD dwName1; + + // The hash of the file path, using method B. + DWORD dwName2; + + // The hash of the file path, using method C. + DWORD dwName3; + + // If the hash table entry is valid, this is the index into the block table of the file. + // Otherwise, one of the following two values: + // - FFFFFFFFh: Hash table entry is empty, and has always been empty. + // Terminates searches for a given file. + // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file). + // Does not terminate searches for a given file. + DWORD dwBlockIndex; + +} TMPKHash; + +typedef struct _TMPKBlock +{ + DWORD dwFlags; // 0x1121 - Compressed , 0x1120 - Not compressed + DWORD dwFilePos; // Offset of the beginning of the file, relative to the beginning of the archive. + DWORD dwFSize; // Uncompressed file size + DWORD dwCSize; // Compressed file size + DWORD dwUnknown; // 0x86364E6D +} TMPKBlock; + +//----------------------------------------------------------------------------- +// Local variables - MPK file format + +static const unsigned char MpkDecryptionKey[512] = +{ + 0x60, 0x20, 0x29, 0xE1, 0x01, 0xCE, 0xAA, 0xFE, 0xA3, 0xAB, 0x8E, 0x30, 0xAF, 0x02, 0xD1, 0x7D, + 0x41, 0x24, 0x06, 0xBD, 0xAE, 0xBE, 0x43, 0xC3, 0xBA, 0xB7, 0x08, 0x13, 0x51, 0xCF, 0xF8, 0xF7, + 0x25, 0x42, 0xA5, 0x4A, 0xDA, 0x0F, 0x52, 0x1C, 0x90, 0x3B, 0x63, 0x49, 0x36, 0xF6, 0xDD, 0x1B, + 0xEA, 0x58, 0xD4, 0x40, 0x70, 0x61, 0x55, 0x09, 0xCD, 0x0B, 0xA2, 0x4B, 0x68, 0x2C, 0x8A, 0xF1, + 0x3C, 0x3A, 0x65, 0xBB, 0xA1, 0xA8, 0x23, 0x97, 0xFD, 0x15, 0x00, 0x94, 0x88, 0x33, 0x59, 0xE9, + 0xFB, 0x69, 0x21, 0xEF, 0x85, 0x5B, 0x57, 0x6C, 0xFA, 0xB5, 0xEE, 0xB8, 0x71, 0xDC, 0xB1, 0x38, + 0x0C, 0x0A, 0x5C, 0x56, 0xC9, 0xB4, 0x84, 0x17, 0x1E, 0xE5, 0xD3, 0x5A, 0xCC, 0xFC, 0x11, 0x86, + 0x7F, 0x45, 0x4F, 0x54, 0xC8, 0x8D, 0x73, 0x89, 0x79, 0x5D, 0xB3, 0xBF, 0xB9, 0xE3, 0x93, 0xE4, + 0x6F, 0x35, 0x2D, 0x46, 0xF2, 0x76, 0xC5, 0x7E, 0xE2, 0xA4, 0xE6, 0xD9, 0x6E, 0x48, 0x34, 0x2B, + 0xC6, 0x5F, 0xBC, 0xA0, 0x6D, 0x0D, 0x47, 0x6B, 0x95, 0x96, 0x92, 0x91, 0xB2, 0x27, 0xEB, 0x9E, + 0xEC, 0x8F, 0xDF, 0x9C, 0x74, 0x99, 0x64, 0xF5, 0xFF, 0x28, 0xB6, 0x37, 0xF3, 0x7C, 0x81, 0x03, + 0x44, 0x62, 0x1F, 0xDB, 0x04, 0x7B, 0xB0, 0x9B, 0x31, 0xA7, 0xDE, 0x78, 0x9F, 0xAD, 0x0E, 0x3F, + 0x3E, 0x4D, 0xC7, 0xD7, 0x39, 0x19, 0x5E, 0xC2, 0xD0, 0xAC, 0xE8, 0x1A, 0x87, 0x8B, 0x07, 0x05, + 0x22, 0xED, 0x72, 0x2E, 0x1D, 0xC1, 0xA9, 0xD6, 0xE0, 0x83, 0xD5, 0xD8, 0xCB, 0x80, 0xF0, 0x66, + 0x7A, 0x9D, 0x50, 0xF9, 0x10, 0x4E, 0x16, 0x14, 0x77, 0x75, 0x6A, 0x67, 0xD2, 0xC0, 0xA6, 0xC4, + 0x53, 0x8C, 0x32, 0xCA, 0x82, 0x2A, 0x18, 0x9A, 0xF4, 0x4C, 0x3D, 0x26, 0x12, 0xE7, 0x98, 0x2F, + 0x4A, 0x04, 0x0D, 0xAF, 0xB4, 0xCF, 0x12, 0xCE, 0x1A, 0x37, 0x61, 0x39, 0x60, 0x95, 0xBE, 0x25, + 0xE4, 0x6E, 0xFC, 0x1B, 0xE7, 0x49, 0xE6, 0x67, 0xF6, 0xC5, 0xCB, 0x2F, 0x27, 0xD4, 0x68, 0xB2, + 0x01, 0x52, 0xD0, 0x46, 0x11, 0x20, 0xFB, 0x9D, 0xA9, 0x02, 0xF5, 0x8F, 0x3D, 0x82, 0xD3, 0xFF, + 0x0B, 0xB8, 0xF2, 0x4D, 0x8E, 0x81, 0x2C, 0xAB, 0x5F, 0xC4, 0x41, 0x29, 0x40, 0xFA, 0xC0, 0xBF, + 0x33, 0x10, 0x21, 0x16, 0xB0, 0x71, 0x83, 0x96, 0x8D, 0x2B, 0x23, 0x3B, 0xF9, 0xC1, 0xE5, 0x72, + 0xE2, 0x1C, 0x26, 0xF0, 0x73, 0x36, 0x63, 0x56, 0x31, 0x4E, 0x6B, 0x55, 0x62, 0x79, 0xC6, 0x91, + 0x00, 0x35, 0xB1, 0x2A, 0xA6, 0x42, 0xDF, 0xEB, 0x3C, 0x51, 0xEA, 0x97, 0x57, 0x94, 0x8C, 0x80, + 0x34, 0x5C, 0xD2, 0x76, 0xA4, 0xE9, 0x85, 0xE8, 0xBB, 0x78, 0xE0, 0xB5, 0xAD, 0x0F, 0x87, 0x70, + 0xDD, 0xAE, 0xF4, 0xD9, 0x66, 0x54, 0x6F, 0xCC, 0x4C, 0x77, 0x3E, 0xCD, 0xF1, 0x75, 0x0A, 0xA1, + 0x28, 0x9B, 0x9A, 0x7E, 0x4B, 0x98, 0x99, 0x47, 0xFE, 0xA5, 0xF7, 0xB7, 0xA3, 0xE1, 0x9F, 0xBC, + 0x93, 0x44, 0x3A, 0x08, 0x89, 0x22, 0xEE, 0xB9, 0x45, 0xD6, 0x06, 0x09, 0xC9, 0xBD, 0x14, 0x0C, + 0xB6, 0x5E, 0x9C, 0x7A, 0x65, 0x59, 0xAA, 0x19, 0x5B, 0x7C, 0x18, 0x43, 0x92, 0x13, 0x15, 0x7B, + 0xED, 0xD5, 0xC7, 0x17, 0xEF, 0x86, 0x90, 0xC2, 0x74, 0x64, 0xF3, 0xDC, 0x6C, 0x38, 0x05, 0x1D, + 0xC8, 0x0E, 0xEC, 0x6A, 0x32, 0xDA, 0xD7, 0xC3, 0xDB, 0x8B, 0x24, 0xB3, 0x5D, 0x2E, 0xBA, 0xA2, + 0xD8, 0x03, 0x88, 0x7D, 0x7F, 0x69, 0x8A, 0xFD, 0xCA, 0x4F, 0x30, 0x9E, 0xA0, 0xD1, 0x5A, 0x53, + 0xDE, 0x3F, 0x84, 0xAC, 0xF8, 0xA7, 0x2D, 0x1F, 0x1E, 0xE3, 0x58, 0x50, 0x6D, 0x48, 0x07, 0xA8 +}; + +//----------------------------------------------------------------------------- +// Functions - MPK file format + +// This function converts MPK file header into MPQ file header +int ConvertMpkHeaderToFormat4( + TMPQArchive * ha, + ULONGLONG FileSize, + DWORD dwFlags) +{ + TMPKHeader * pMpkHeader = (TMPKHeader *)ha->HeaderData; + TMPQHeader Header; + + // Can't open the archive with certain flags + if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) + return ERROR_FILE_CORRUPT; + + // Translate the MPK header into a MPQ header + // Note: Hash table size and block table size are in bytes, not in entries + memset(&Header, 0, sizeof(TMPQHeader)); + Header.dwID = BSWAP_INT32_UNSIGNED(pMpkHeader->dwID); + Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwArchiveSize); + Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHeaderSize); + Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTablePos); + Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTableSize) / sizeof(TMPKHash); + Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTablePos); + Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTableSize) / sizeof(TMPKBlock); +// Header.dwUnknownPos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownPos); +// Header.dwUnknownSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownSize); + assert(Header.dwHeaderSize == sizeof(TMPKHeader)); + + // Verify the MPK header + if(Header.dwID == ID_MPK && Header.dwHeaderSize == sizeof(TMPKHeader) && Header.dwArchiveSize == (DWORD)FileSize) + { + // The header ID must be ID_MPQ + Header.dwID = ID_MPQ; + Header.wFormatVersion = MPQ_FORMAT_VERSION_1; + Header.wSectorSize = 3; + + // Initialize the fields of 3.0 header + Header.ArchiveSize64 = Header.dwArchiveSize; + Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash); + Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock); + + // Copy the converted MPQ header back + memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader)); + + // Mark this file as MPK file + ha->pfnHashString = HashStringLower; + ha->dwFlags |= MPQ_FLAG_READ_ONLY; + ha->dwSubType = MPQ_SUBTYPE_MPK; + return ERROR_SUCCESS; + } + return ERROR_FILE_CORRUPT; +} + +void DecryptMpkTable(void * pvMpkTable, size_t cbSize) +{ + LPBYTE pbMpkTable = (LPBYTE)pvMpkTable; + + for(size_t i = 0; i < cbSize; i++) + pbMpkTable[i] = MpkDecryptionKey[pbMpkTable[i]]; +} + +void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize) +{ + ULONGLONG ByteOffset; + LPBYTE pbMpkTable = NULL; + + // Allocate space for the table + pbMpkTable = STORM_ALLOC(BYTE, cbTableSize); + if(pbMpkTable != NULL) + { + // Load and the MPK hash table + ByteOffset = ha->MpqPos + dwByteOffset; + if(FileStream_Read(ha->pStream, &ByteOffset, pbMpkTable, cbTableSize)) + { + // Decrypt the table + DecryptMpkTable(pbMpkTable, cbTableSize); + return pbMpkTable; + } + + // Free the MPK table + STORM_FREE(pbMpkTable); + pbMpkTable = NULL; + } + + // Return the table + return pbMpkTable; +} + +TMPQHash * LoadMpkHashTable(TMPQArchive * ha) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPKHash * pMpkHash; + TMPQHash * pHash = NULL; + DWORD dwHashTableSize = pHeader->dwHashTableSize; + + // 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 + + // Load and decrypt the MPK hash table. + pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash)); + if(pMpkHash != NULL) + { + // 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) + { + // Set the entire hash table to free + memset(ha->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 + assert(pHash->dwBlockIndex == HASH_ENTRY_FREE); + + // Copy the MPK hash entry to the hash table + pHash->dwBlockIndex = pMpkHash[i].dwBlockIndex; + pHash->wPlatform = 0; + pHash->lcLocale = 0; + 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; +} + +static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags) +{ + DWORD dwMpqFlags = MPQ_FILE_EXISTS; + + // Check for flags that are always present + assert((dwMpkFlags & MPK_FILE_UNKNOWN_0001) != 0); + assert((dwMpkFlags & MPK_FILE_UNKNOWN_0010) != 0); + assert((dwMpkFlags & MPK_FILE_UNKNOWN_2000) != 0); + assert((dwMpkFlags & MPK_FILE_EXISTS) != 0); + + // Append the compressed flag + dwMpqFlags |= (dwMpkFlags & MPK_FILE_COMPRESSED) ? MPQ_FILE_COMPRESS : 0; + + // All files in the MPQ seem to be single unit files + dwMpqFlags |= MPQ_FILE_ENCRYPTED | MPQ_FILE_SINGLE_UNIT; + + return dwMpqFlags; +} + +TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPKBlock * pMpkBlockTable; + TMPKBlock * pMpkBlockEnd; + TMPQBlock * pBlockTable = NULL; + TMPKBlock * pMpkBlock; + TMPQBlock * pMpqBlock; + + // Load and decrypt the MPK block table + pMpkBlockTable = pMpkBlock = (TMPKBlock *)LoadMpkTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TMPKBlock)); + if(pMpkBlockTable != NULL) + { + // Allocate buffer for MPQ-like block table + pBlockTable = pMpqBlock = STORM_ALLOC(TMPQBlock, pHeader->dwBlockTableSize); + if(pBlockTable != NULL) + { + // Convert the MPK block table to MPQ block table + pMpkBlockEnd = pMpkBlockTable + pHeader->dwBlockTableSize; + while(pMpkBlock < pMpkBlockEnd) + { + // Translate the MPK block table entry to MPQ block table entry + pMpqBlock->dwFilePos = pMpkBlock->dwFilePos; + pMpqBlock->dwCSize = pMpkBlock->dwCSize; + pMpqBlock->dwFSize = pMpkBlock->dwFSize; + pMpqBlock->dwFlags = ConvertMpkFlagsToMpqFlags(pMpkBlock->dwFlags); + + // Move both + pMpkBlock++; + pMpqBlock++; + } + } + + // Free the MPK block table + STORM_FREE(pMpkBlockTable); + } + + return pBlockTable; +} diff --git a/src/SCompression.cpp b/src/SCompression.cpp index 8e4fcac..933dc61 100644 --- a/src/SCompression.cpp +++ b/src/SCompression.cpp @@ -532,6 +532,47 @@ static int Decompress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBu return 1; } +static int Decompress_LZMA_MPK(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) +{ + ELzmaStatus LzmaStatus; + ISzAlloc SzAlloc; + Byte * destBuffer = (Byte *)pvOutBuffer; + Byte * srcBuffer = (Byte *)pvInBuffer; + SizeT destLen = *pcbOutBuffer; + SizeT srcLen = cbInBuffer; + SRes nResult; + BYTE LZMA_Props[] = {0x5D, 0x00, 0x00, 0x00, 0x01}; + + // There must be at least 0x0E bytes in the buffer + if(srcLen <= sizeof(LZMA_Props)) + return 0; + + // Verify the props header + if(memcmp(pvInBuffer, LZMA_Props, sizeof(LZMA_Props))) + return 0; + + // Fill the callbacks in structures + SzAlloc.Alloc = LZMA_Callback_Alloc; + SzAlloc.Free = LZMA_Callback_Free; + + // Perform compression + srcLen = cbInBuffer - sizeof(LZMA_Props); + nResult = LzmaDecode(destBuffer, + &destLen, + srcBuffer + sizeof(LZMA_Props), + &srcLen, + srcBuffer, + sizeof(LZMA_Props), + LZMA_FINISH_END, + &LzmaStatus, + &SzAlloc); + if(nResult != SZ_OK) + return 0; + + *pcbOutBuffer = (unsigned int)destLen; + return 1; +} + /******************************************************************************/ /* */ /* Support functions for SPARSE compression (0x20) */ @@ -1073,3 +1114,15 @@ int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInB SetLastError(ERROR_FILE_CORRUPT); return nResult; } + +/*****************************************************************************/ +/* */ +/* File decompression for MPK archives */ +/* */ +/*****************************************************************************/ + +int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer) +{ + return Decompress_LZMA_MPK(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer); +} + diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp index 3362a20..a7706d0 100644 --- a/src/SFileCompactArchive.cpp +++ b/src/SFileCompactArchive.cpp @@ -539,13 +539,15 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // Write the MPQ header if(nError == ERROR_SUCCESS) { - // Remember the header size before swapping - DWORD dwBytesToWrite = ha->pHeader->dwHeaderSize; - - BSWAP_TMPQHEADER(ha->pHeader); - if(!FileStream_Write(pTempStream, NULL, ha->pHeader, dwBytesToWrite)) + TMPQHeader SaveMpqHeader; + + // 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_3); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); + if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize)) nError = GetLastError(); - BSWAP_TMPQHEADER(ha->pHeader); // Update the progress ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize; @@ -696,8 +698,7 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount) // Create new entry in the hash table if(ha->pHashTable != NULL) { - dwHashIndex = AllocateHashEntry(ha, pFileEntry); - if(dwHashIndex == HASH_ENTRY_FREE) + if(AllocateHashEntry(ha, pFileEntry) == NULL) { nError = ERROR_CAN_NOT_COMPLETE; break; diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp index 3a6a0d8..39332a6 100644 --- a/src/SFileCreateArchive.cpp +++ b/src/SFileCreateArchive.cpp @@ -56,7 +56,9 @@ static int WriteNakedMPQHeader(TMPQArchive * ha) Header.wSectorSize = pHeader->wSectorSize; // Write it to the file - BSWAP_TMPQHEADER(&Header); + BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_3); + BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(ha->pStream, &ha->MpqPos, &Header, dwBytesToWrite)) nError = GetLastError(); @@ -172,6 +174,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea if(nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); + ha->pfnHashString = HashString; ha->pStream = pStream; ha->dwSectorSize = pCreateInfo->dwSectorSize; ha->UserDataPos = MpqPos; diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp index 80aa6e1..17c9e24 100644 --- a/src/SFileFindFile.cpp +++ b/src/SFileFindFile.cpp @@ -315,7 +315,6 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) lpFindFileData->szPlainName = (char *)GetPlainFileNameA(lpFindFileData->cFileName); return ERROR_SUCCESS; } - } } diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index 97b2b05..a058136 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -182,6 +182,7 @@ bool WINAPI SFileOpenArchive( DWORD dwHeaderID; memset(ha, 0, sizeof(TMPQArchive)); + ha->pfnHashString = HashString; ha->pStream = pStream; pStream = NULL; @@ -238,25 +239,43 @@ bool WINAPI SFileOpenArchive( // There must be MPQ header signature if(dwHeaderID == ID_MPQ) { - // Save the position where the MPQ header has been found - if(ha->pUserData == NULL) - ha->UserDataPos = SearchPos; - ha->pHeader = (TMPQHeader *)ha->HeaderData; - ha->MpqPos = SearchPos; - // Now convert the header to version 4 - BSWAP_TMPQHEADER(ha->pHeader); nError = ConvertMpqHeaderToFormat4(ha, FileSize, dwFlags); break; } + // Check for MPK archives (Longwu Online - MPQ fork) + if(dwHeaderID == ID_MPK) + { + // Now convert the MPK header to MPQ Header version 4 + nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags); + break; + } + + // If searching for the MPQ header is disabled, return an error + if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH) + { + nError = ERROR_NOT_SUPPORTED; + break; + } + // Move to the next possible offset SearchPos += 0x200; } - // If we haven't found MPQ header in the file, it's an error - if(ha->pHeader == NULL || ha->pHeader->wSectorSize == 0) - nError = ERROR_BAD_FORMAT; + // 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 + if(ha->pUserData == NULL) + ha->UserDataPos = SearchPos; + ha->pHeader = (TMPQHeader *)ha->HeaderData; + ha->MpqPos = SearchPos; + + // Sector size must be nonzero. + if(ha->pHeader->wSectorSize == 0) + nError = ERROR_BAD_FORMAT; + } } // Fix table positions according to format diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp index a3aa901..4180cd7 100644 --- a/src/SFileOpenFileEx.cpp +++ b/src/SFileOpenFileEx.cpp @@ -32,21 +32,13 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile) { TFileStream * pStream; TMPQFile * hf = NULL; - - // We have to convert the local file name to UNICODE, if needed -#ifdef _UNICODE TCHAR szFileNameT[MAX_PATH]; - int i; - - for(i = 0; szFileName[i] != 0; i++) - szFileNameT[i] = szFileName[i]; - szFileNameT[i] = 0; - pStream = FileStream_OpenFile(szFileNameT, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); -#else - pStream = FileStream_OpenFile(szFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); -#endif + // Convert the file name to UNICODE (if needed) + CopyFileName(szFileNameT, szFileName, strlen(szFileName)); + // Open the file and create the TMPQFile structure + pStream = FileStream_OpenFile(szFileNameT, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY); if(pStream != NULL) { // Allocate and initialize file handle diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp index 164b646..5a034b3 100644 --- a/src/SFileReadFile.cpp +++ b/src/SFileReadFile.cpp @@ -28,13 +28,6 @@ struct TFileHeader2Ext //----------------------------------------------------------------------------- // Local functions -static void CopyFileName(char * szTarget, const TCHAR * szSource) -{ - while(*szSource != 0) - *szTarget++ = (char)*szSource++; - *szTarget = 0; -} - static DWORD GetMpqFileCount(TMPQArchive * ha) { TFileEntry * pFileTableEnd; @@ -442,7 +435,99 @@ static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos return ERROR_CAN_NOT_COMPLETE; } -static int ReadMpqFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead) +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_COMPRESSED) + { + // 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_COMPRESSED) + { + 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; @@ -582,7 +667,7 @@ static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); else - nError = ReadMpqFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); + nError = ReadMpqFileSectorFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); // Fix error code if(nError == ERROR_SUCCESS && dwBytesRead != hf->cbFileData) @@ -620,6 +705,38 @@ static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, 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 @@ -648,58 +765,36 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD // If the file is local file, read the data directly from the stream if(hf->pStream != NULL) { - ULONGLONG FilePosition1; - ULONGLONG FilePosition2; - - // 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 - - FileStream_GetPos(hf->pStream, &FilePosition1); - if(!FileStream_Read(hf->pStream, NULL, 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 - { - nError = GetLastError(); - } - } - else - { - dwBytesRead = dwToRead; - } + nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); } - else + + // 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) { - // If the file is a patch file, we have to read it special way - if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - { - nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } + nError = ReadMpqFilePatchFile(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); - } + // 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); + } - // Otherwise read it as sector based MPQ file - else - { - nError = ReadMpqFile(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); + } - // Increment the file position - hf->dwFilePos += 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; @@ -888,58 +983,97 @@ static TFileHeader2Ext data2ext[] = {0, 0, 0, 0, NULL} // Terminator }; -bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) +static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) { - TFileEntry * pFileEntry; TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - char szPseudoName[20]; - DWORD FirstBytes[2]; // The first 4 bytes of the file + DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file + DWORD dwBytesRead = 0; DWORD dwFilePos; // Saved file position - int nError = ERROR_SUCCESS; - int i; - // Pre-zero the output buffer - if(szFileName != NULL) - *szFileName = 0; + // 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); - // Check valid parameters - if(!IsValidFileHandle(hf)) - nError = ERROR_INVALID_HANDLE; - pFileEntry = hf->pFileEntry; - - // Only do something if the file name is not filled - if(nError == ERROR_SUCCESS && pFileEntry != NULL && pFileEntry->szFileName == NULL) + // If we read at least 8 bytes + if(dwBytesRead == sizeof(FirstBytes)) { - // Read the first 2 DWORDs bytes from the file - FirstBytes[0] = FirstBytes[1] = 0; - dwFilePos = SFileSetFilePointer(hf, 0, NULL, FILE_CURRENT); - SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), NULL, NULL); + // Make sure that the array is properly BSWAP-ed BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); - SFileSetFilePointer(hf, dwFilePos, NULL, FILE_BEGIN); // Try to guess file extension from those 2 DWORDs - for(i = 0; data2ext[i].szExt != NULL; i++) + 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); - break; + + // 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; } } + } - // Put the file name to the file table - AllocateFileName(pFileEntry, szPseudoName); - } + 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; - // Now put the file name to the file structure - if(nError == ERROR_SUCCESS && szFileName != NULL) + // Check valid parameters + if(IsValidFileHandle(hf)) { - if(pFileEntry != NULL && pFileEntry->szFileName != NULL) - strcpy(szFileName, pFileEntry->szFileName); - else if(hf->pStream != NULL) - CopyFileName(szFileName, FileStream_GetFileName(hf->pStream)); + 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); } diff --git a/src/StormCommon.h b/src/StormCommon.h index 2d5c2ab..766b692 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -127,6 +127,8 @@ extern unsigned char AsciiToUpperTable[256]; #define MPQ_HASH_FILE_KEY 0x300 DWORD HashString(const char * szFileName, DWORD dwHashType); +DWORD HashStringSlash(const char * szFileName, DWORD dwHashType); +DWORD HashStringLower(const char * szFileName, DWORD dwHashType); void InitializeMpqCryptography(); @@ -135,6 +137,9 @@ 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); @@ -159,9 +164,10 @@ bool IsValidFileHandle(TMPQFile * hf); //----------------------------------------------------------------------------- // Hash table and block table manipulation +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); -DWORD AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry); +TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry); DWORD AllocateHetEntry(TMPQArchive * ha, TFileEntry * pFileEntry); void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos); @@ -201,10 +207,23 @@ int FreeFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); void InvalidateInternalFiles(TMPQArchive * ha); //----------------------------------------------------------------------------- +// Support for alternate file formats (SBaseSubTypes.cpp) + +int ConvertSqpHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); +TMPQHash * LoadSqpHashTable(TMPQArchive * ha); +TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha); + +int ConvertMpkHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); +void DecryptMpkTable(void * pvMpkTable, size_t cbSize); +TMPQHash * LoadMpkHashTable(TMPQArchive * ha); +TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha); +int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer); + +//----------------------------------------------------------------------------- // Common functions - MPQ File TMPQFile * CreateMpqFile(TMPQArchive * ha); -int LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, void * pvTable, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey); +void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey); int AllocateSectorBuffer(TMPQFile * hf); int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile); int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile); diff --git a/src/StormLib.h b/src/StormLib.h index 85aabab..972e22b 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -136,6 +136,7 @@ extern "C" { #define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A') #define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B') +#define ID_MPK 0x1A4B504D // MPK archive header ID ('MPK\x1A') #define ERROR_AVI_FILE 10000 // No MPQ file, but AVI file. #define ERROR_UNKNOWN_FILE_KEY 10001 // Returned by SFileReadFile when can't find file key @@ -181,6 +182,7 @@ extern "C" { // Values for TMPQArchive::dwSubType #define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games) #define MPQ_SUBTYPE_SQP 0x00000001 // The file is a SQP file (War of the Immortals) +#define MPQ_SUBTYPE_MPK 0x00000002 // The file is a MPK file (Longwu Online) // Return value for SFileGetFileSize and SFileSetFilePointer #define SFILE_INVALID_SIZE 0xFFFFFFFF @@ -304,8 +306,9 @@ extern "C" { #define MPQ_OPEN_NO_LISTFILE 0x00010000 // Don't load the internal listfile #define MPQ_OPEN_NO_ATTRIBUTES 0x00020000 // Don't open the attributes -#define MPQ_OPEN_FORCE_MPQ_V1 0x00040000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header -#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00080000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file +#define MPQ_OPEN_NO_HEADER_SEARCH 0x00040000 // Don't search for the MPQ header past the begin of the file +#define MPQ_OPEN_FORCE_MPQ_V1 0x00080000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header +#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00100000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file // Deprecated #define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY @@ -370,6 +373,9 @@ extern "C" { #define LANG_NEUTRAL 0x00 // Neutral locale #endif +// Pointer to hashing function +typedef DWORD (*HASH_STRING)(const char * szFileName, DWORD dwHashType); + //----------------------------------------------------------------------------- // Callback functions @@ -449,10 +455,10 @@ typedef struct _TMPQUserData typedef struct _TMPQHeader { // The ID_MPQ ('MPQ\x1A') signature - DWORD dwID; + DWORD dwID; // Size of the archive header - DWORD dwHeaderSize; + DWORD dwHeaderSize; // 32-bit size of MPQ archive // This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive @@ -534,43 +540,8 @@ typedef struct _TMPQHeader unsigned char MD5_HetTable[MD5_DIGEST_SIZE]; // MD5 of the HET table before decryption unsigned char MD5_MpqHeader[MD5_DIGEST_SIZE]; // MD5 of the MPQ header from signature to (including) MD5_HetTable } TMPQHeader; - -// MPQ Header for SQP data files -typedef struct _TSQPHeader -{ - // The ID_MPQ ('MPQ\x1A') signature - DWORD dwID; - - // Size of the archive header - DWORD dwHeaderSize; - - // 32-bit size of MPQ archive - DWORD dwArchiveSize; - - // Offset to the beginning of the hash table, relative to the beginning of the archive. - DWORD dwHashTablePos; - - // Offset to the beginning of the block table, relative to the beginning of the archive. - DWORD dwBlockTablePos; - - // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for - // the original MoPaQ format, or less than 2^20 for the Burning Crusade format. - DWORD dwHashTableSize; - - // Number of entries in the block table - DWORD dwBlockTableSize; - - // Must be zero for SQP files - USHORT wFormatVersion; - - // Power of two exponent specifying the number of 512-byte disk sectors in each file sector - // in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize. - USHORT wSectorSize; - -} TSQPHeader; #pragma pack(pop) - // Hash table entry. All files in the archive are searched by their hashes. typedef struct _TMPQHash { @@ -607,27 +578,6 @@ typedef struct _TMPQHash DWORD dwBlockIndex; } TMPQHash; -typedef struct _TSQPHash -{ - // Most likely the lcLocale+wPlatform. - DWORD dwAlwaysZero; - - // If the hash table entry is valid, this is the index into the block table of the file. - // Otherwise, one of the following two values: - // - FFFFFFFFh: Hash table entry is empty, and has always been empty. - // Terminates searches for a given file. - // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file). - // Does not terminate searches for a given file. - DWORD dwBlockIndex; - - // The hash of the file path, using method A. - DWORD dwName1; - - // The hash of the file path, using method B. - DWORD dwName2; - -} TSQPHash; - // File description block contains informations about the file typedef struct _TMPQBlock { @@ -645,23 +595,6 @@ typedef struct _TMPQBlock DWORD dwFlags; } TMPQBlock; -// File description block for SQP files -typedef struct _TSQPBlock -{ - // Offset of the beginning of the file, relative to the beginning of the archive. - DWORD dwFilePos; - - // Flags for the file. See MPQ_FILE_XXXX constants - DWORD dwFlags; - - // Compressed file size - DWORD dwCSize; - - // Uncompressed file size - DWORD dwFSize; - -} TSQPBlock; - // Patch file information, preceding the sector offset table typedef struct _TPatchInfo { @@ -806,6 +739,7 @@ typedef struct _TMPQArchive 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 BYTE HeaderData[MPQ_HEADER_SIZE_V4]; // Storage for MPQ header @@ -814,6 +748,7 @@ typedef struct _TMPQArchive 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) diff --git a/src/StormPort.h b/src/StormPort.h index 65fbb21..b2d3158 100644 --- a/src/StormPort.h +++ b/src/StormPort.h @@ -211,7 +211,8 @@ #define BSWAP_ARRAY64_UNSIGNED(a,b) {} #define BSWAP_PART_HEADER(a) {} #define BSWAP_TMPQUSERDATA(a) {} - #define BSWAP_TMPQHEADER(a) {} + #define BSWAP_TMPQHEADER(a,b) {} + #define BSWAP_TMPKHEADER(a) {} #else #ifdef __cplusplus @@ -228,7 +229,8 @@ void ConvertUInt64Buffer(void * ptr, size_t length); void ConvertPartHeader(void * partHeader); void ConvertTMPQUserData(void *userData); - void ConvertTMPQHeader(void *header); + void ConvertTMPQHeader(void *header, uint16_t wPart); + void ConvertTMPKHeader(void *header); #ifdef __cplusplus } #endif @@ -243,7 +245,8 @@ #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) ConvertTMPQHeader((a)) + #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b)) + #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a)) #endif #endif // __STORMPORT_H__ diff --git a/test/Test.cpp b/test/Test.cpp index d40f080..3e4b1fb 100644 --- a/test/Test.cpp +++ b/test/Test.cpp @@ -1161,8 +1161,9 @@ __TryAgain: static int TestArchiveOpenAndClose(const TCHAR * szMpqName) { - const char * szFileName1 = "../Data/Task/1315.str"; -// const char * szFileName2 = "items\\map\\mapz_deleted.cel"; +// 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; @@ -1185,14 +1186,11 @@ static int TestArchiveOpenAndClose(const TCHAR * szMpqName) // Verify the raw data in the archive if(nError == ERROR_SUCCESS) { - // Verify the archive - SFileVerifyRawData(hMpq, SFILE_VERIFY_FILE, szFileName1); - // Try to open a file - if(!SFileOpenFileEx(hMpq, szFileName1, SFILE_OPEN_FROM_MPQ, &hFile1)) + if(!SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile1)) { nError = GetLastError(); - printf("%s - file not found in the MPQ\n", szFileName1); + printf("%s - file not found in the MPQ\n", szFileName); } } @@ -1201,7 +1199,9 @@ static int TestArchiveOpenAndClose(const TCHAR * szMpqName) { DWORD dwBytesRead = 0; BYTE Buffer[0x1000]; + char szNameBuff[MAX_PATH]; + SFileGetFileName(hFile1, szNameBuff); SFileSetFilePointer(hFile1, 0x1000, NULL, FILE_BEGIN); SFileReadFile(hFile1, Buffer, sizeof(Buffer), &dwBytesRead, NULL); } @@ -1210,7 +1210,7 @@ static int TestArchiveOpenAndClose(const TCHAR * szMpqName) #ifdef _MSC_VER if(nError == ERROR_SUCCESS) { - SFileExtractFile(hMpq, szFileName1, _T("E:\\extracted.wav"), 0); + SFileExtractFile(hMpq, szFileName, _T("E:\\extracted.wav"), 0); PlaySound(_T("E:\\extracted.wav"), NULL, SND_FILENAME); } #endif @@ -2189,7 +2189,7 @@ int main(void) // nError = TestStructureSizes(); // if(nError == ERROR_SUCCESS) -// nError = TestOpenLocalFile("C:\\autoexec.bat"); +// nError = TestOpenLocalFile("C:\\Windows\\System32\\kernel32.dll"); // Test reading partial file // if(nError == ERROR_SUCCESS) @@ -2213,9 +2213,10 @@ int main(void) // nError = TestSectorCompress(MPQ_SECTOR_SIZE); // Test the archive open and close - if(nError == ERROR_SUCCESS) - nError = TestArchiveOpenAndClose(MAKE_PATH("2013 - War of the Immortals\\1\\Other.sqp")); + 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")); // if(nError == ERROR_SUCCESS) // nError = TestFindFiles(MAKE_PATH("2002 - Warcraft III/HumanEd.mpq")); |