aboutsummaryrefslogtreecommitdiff
path: root/src/SBaseCommon.cpp
diff options
context:
space:
mode:
authorunknown <E:\Ladik\Mail>2015-05-28 17:24:56 +0200
committerunknown <E:\Ladik\Mail>2015-05-28 17:24:56 +0200
commita8a92ccc92f786f56c0c306152de2b7bde53cc45 (patch)
treefe4852eef33c35fc7e783308d301eda429643f60 /src/SBaseCommon.cpp
parent61f552875dbcc0879ad69409fff25632460fe087 (diff)
+ Fixed bug in SFileReadFile
Diffstat (limited to 'src/SBaseCommon.cpp')
-rw-r--r--src/SBaseCommon.cpp3548
1 files changed, 1774 insertions, 1774 deletions
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index b16472e..54fc6d0 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -1,1774 +1,1774 @@
-/*****************************************************************************/
-/* SBaseCommon.cpp Copyright (c) Ladislav Zezula 2003 */
-/*---------------------------------------------------------------------------*/
-/* Common functions for StormLib, used by all SFile*** modules */
-/*---------------------------------------------------------------------------*/
-/* Date Ver Who Comment */
-/* -------- ---- --- ------- */
-/* 24.03.03 1.00 Lad The first version of SFileCommon.cpp */
-/* 19.11.03 1.01 Dan Big endian handling */
-/* 12.06.04 1.01 Lad Renamed to SCommon.cpp */
-/* 06.09.10 1.01 Lad Renamed to SBaseCommon.cpp */
-/*****************************************************************************/
-
-#define __STORMLIB_SELF__
-#include "StormLib.h"
-#include "StormCommon.h"
-
-char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014";
-
-//-----------------------------------------------------------------------------
-// Local variables
-
-LCID lcFileLocale = LANG_NEUTRAL; // File locale
-USHORT wPlatform = 0; // File platform
-
-//-----------------------------------------------------------------------------
-// Conversion to uppercase/lowercase
-
-// 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,
- 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, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
- 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
- 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
- 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 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
-// 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,
- 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
-};
-
-//-----------------------------------------------------------------------------
-// Safe string functions
-
-void StringCopyA(char * dest, const char * src, size_t nMaxChars)
-{
- size_t nLength = strlen(src);
-
- // Don't copy more than nMaxChars
- nLength = STORMLIB_MIN(nLength, nMaxChars);
- memcpy(dest, src, nLength);
- dest[nLength] = 0;
-}
-
-void StringCatA(char * dest, const char * src, size_t nMaxChars)
-{
- size_t nLength1 = strlen(dest);
- size_t nLength2 = strlen(src);
-
- // Don't copy more than nMaxChars
- if(nLength1 < nMaxChars)
- {
- nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1));
- memcpy(dest + nLength1, src, nLength2);
- dest[nLength1 + nLength2] = 0;
- }
-}
-
-void StringCopyT(TCHAR * dest, const TCHAR * src, size_t nMaxChars)
-{
- size_t nLength = _tcslen(src);
-
- // Don't copy more than nMaxChars
- nLength = STORMLIB_MIN(nLength, nMaxChars);
- memcpy(dest, src, (nLength * sizeof(TCHAR)));
- dest[nLength] = 0;
-}
-
-void StringCatT(TCHAR * dest, const TCHAR * src, size_t nMaxChars)
-{
- size_t nLength1 = _tcslen(dest);
- size_t nLength2 = _tcslen(src);
-
- // Don't copy more than nMaxChars
- if(nLength1 < nMaxChars)
- {
- nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1));
- memcpy(dest + nLength1, src, (nLength2 * sizeof(TCHAR)));
- dest[nLength1 + nLength2] = 0;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Storm hashing functions
-
-#define STORM_BUFFER_SIZE 0x500
-
-#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0)
-
-static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine
-static bool bMpqCryptographyInitialized = false;
-
-void InitializeMpqCryptography()
-{
- DWORD dwSeed = 0x00100001;
- DWORD index1 = 0;
- DWORD index2 = 0;
- int i;
-
- // Initialize the decryption buffer.
- // Do nothing if already done.
- if(bMpqCryptographyInitialized == false)
- {
- for(index1 = 0; index1 < 0x100; index1++)
- {
- for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
- {
- DWORD temp1, temp2;
-
- dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
- temp1 = (dwSeed & 0xFFFF) << 0x10;
-
- dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
- temp2 = (dwSeed & 0xFFFF);
-
- StormBuffer[index2] = (temp1 | temp2);
- }
- }
-
- // Also register both MD5 and SHA1 hash algorithms
- register_hash(&md5_desc);
- register_hash(&sha1_desc);
-
- // Use LibTomMath as support math library for LibTomCrypt
- ltc_mp = ltm_desc;
-
- // Don't do that again
- bMpqCryptographyInitialized = true;
- }
-}
-
-DWORD HashString(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
- // Convert slash (0x2F) to backslash (0x5C)
- ch = AsciiToUpperTable[*pbKey++];
-
- dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
- dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
- }
-
- 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
-
-DWORD GetHashTableSizeForFileCount(DWORD dwFileCount)
-{
- DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN;
-
- // For zero files, there is no hash table needed
- if(dwFileCount == 0)
- return 0;
-
- // Round the hash table size up to the nearest power of two
- // Don't allow the hash table size go over allowed maximum
- while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount)
- dwPowerOfTwo <<= 1;
- return dwPowerOfTwo;
-}
-
-//-----------------------------------------------------------------------------
-// Calculates a Jenkin's Encrypting and decrypting MPQ file data
-
-ULONGLONG HashStringJenkins(const char * szFileName)
-{
- LPBYTE pbFileName = (LPBYTE)szFileName;
- char szNameBuff[0x108];
- size_t nLength = 0;
- unsigned int primary_hash = 1;
- unsigned int secondary_hash = 2;
-
- // Normalize the file name - convert to uppercase, and convert "/" to "\\".
- if(pbFileName != NULL)
- {
- char * szNamePtr = szNameBuff;
- char * szNameEnd = szNamePtr + sizeof(szNameBuff);
-
- // Normalize the file name. Doesn't have to be zero terminated for hashing
- while(szNamePtr < szNameEnd && pbFileName[0] != 0)
- *szNamePtr++ = (char)AsciiToLowerTable[*pbFileName++];
- nLength = szNamePtr - szNameBuff;
- }
-
- // Thanks Quantam for finding out what the algorithm is.
- // I am really getting old for reversing large chunks of assembly
- // that does hashing :-)
- hashlittle2(szNameBuff, nLength, &secondary_hash, &primary_hash);
-
- // Combine those 2 together
- return ((ULONGLONG)primary_hash << 0x20) | (ULONGLONG)secondary_hash;
-}
-
-//-----------------------------------------------------------------------------
-// Default flags for (attributes) and (listfile)
-
-DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion)
-{
- // Fixed for format 1.0
- if(wFormatVersion == MPQ_FORMAT_VERSION_1)
- return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY;
-
- // Size-dependent for formats 2.0-4.0
- return (dwFileSize > 0x4000) ? (MPQ_FILE_COMPRESS | MPQ_FILE_SECTOR_CRC) : (MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT);
-}
-
-
-//-----------------------------------------------------------------------------
-// Encrypting/Decrypting MPQ data block
-
-void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
-{
- LPDWORD DataBlock = (LPDWORD)pvDataBlock;
- DWORD dwValue32;
- DWORD dwKey2 = 0xEEEEEEEE;
-
- // Round to DWORDs
- dwLength >>= 2;
-
- // Encrypt the data block at array of DWORDs
- for(DWORD i = 0; i < dwLength; i++)
- {
- // Modify the second key
- dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
-
- dwValue32 = DataBlock[i];
- DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2);
-
- dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
- dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
- }
-}
-
-void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
-{
- LPDWORD DataBlock = (LPDWORD)pvDataBlock;
- DWORD dwValue32;
- DWORD dwKey2 = 0xEEEEEEEE;
-
- // Round to DWORDs
- dwLength >>= 2;
-
- // Decrypt the data block at array of DWORDs
- for(DWORD i = 0; i < dwLength; i++)
- {
- // Modify the second key
- dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
-
- DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2);
- dwValue32 = DataBlock[i];
-
- dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
- dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
- }
-}
-
-/**
- * Functions tries to get file decryption key. This comes from these facts
- *
- * - We know the decrypted value of the first DWORD in the encrypted data
- * - We know the decrypted value of the second DWORD (at least aproximately)
- * - There is only 256 variants of how the second key is modified
- *
- * The first iteration of dwKey1 and dwKey2 is this:
- *
- * dwKey2 = 0xEEEEEEEE + StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]
- * dwDecrypted0 = DataBlock[0] ^ (dwKey1 + dwKey2);
- *
- * This means:
- *
- * (dwKey1 + dwKey2) = DataBlock[0] ^ dwDecrypted0;
- *
- */
-
-DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwDecrypted0)
-{
- DWORD dwDecrypted1Max = dwSectorSize + dwDecrypted0;
- DWORD dwKey1PlusKey2;
- DWORD DataBlock[2];
-
- // We must have at least 2 DWORDs there to be able to decrypt something
- if(dwSectorSize < 0x08)
- return 0;
-
- // Get the value of the combined encryption key
- dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
-
- // Try all 256 combinations of dwKey1
- for(DWORD i = 0; i < 0x100; i++)
- {
- DWORD dwSaveKey1;
- DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
- DWORD dwKey2 = 0xEEEEEEEE;
-
- // Modify the second key and decrypt the first DWORD
- dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
- DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
-
- // Did we obtain the same value like dwDecrypted0?
- if(DataBlock[0] == dwDecrypted0)
- {
- // Save this key value. Increment by one because
- // we are decrypting sector offset table
- dwSaveKey1 = dwKey1 + 1;
-
- // Rotate both keys
- dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
- dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
-
- // Modify the second key again and decrypt the second DWORD
- dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
- DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
-
- // Now compare the results
- if(DataBlock[1] <= dwDecrypted1Max)
- return dwSaveKey1;
- }
- }
-
- // Key not found
- return 0;
-}
-
-// Function tries to detect file encryption key based on expected file content
-// It is the same function like before, except that we know the value of the second DWORD
-DWORD DetectFileKeyByKnownContent(void * pvEncryptedData, DWORD dwDecrypted0, DWORD dwDecrypted1)
-{
- LPDWORD EncryptedData = (LPDWORD)pvEncryptedData;
- DWORD dwKey1PlusKey2;
- DWORD DataBlock[2];
-
- // Get the value of the combined encryption key
- dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
-
- // Try all 256 combinations of dwKey1
- for(DWORD i = 0; i < 0x100; i++)
- {
- DWORD dwSaveKey1;
- DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
- DWORD dwKey2 = 0xEEEEEEEE;
-
- // Modify the second key and decrypt the first DWORD
- dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
- DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
-
- // Did we obtain the same value like dwDecrypted0?
- if(DataBlock[0] == dwDecrypted0)
- {
- // Save this key value
- dwSaveKey1 = dwKey1;
-
- // Rotate both keys
- dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
- dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
-
- // Modify the second key again and decrypt the second DWORD
- dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
- DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
-
- // Now compare the results
- if(DataBlock[1] == dwDecrypted1)
- return dwSaveKey1;
- }
- }
-
- // Key not found
- return 0;
-}
-
-DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize)
-{
- DWORD dwFileKey;
-
- // Try to break the file encryption key as if it was a WAVE file
- if(dwSectorSize >= 0x0C)
- {
- dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x46464952, dwFileSize - 8);
- if(dwFileKey != 0)
- return dwFileKey;
- }
-
- // Try to break the encryption key as if it was an EXE file
- if(dwSectorSize > 0x40)
- {
- dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x00905A4D, 0x00000003);
- if(dwFileKey != 0)
- return dwFileKey;
- }
-
- // Try to break the encryption key as if it was a XML file
- if(dwSectorSize > 0x04)
- {
- dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x6D783F3C, 0x6576206C);
- if(dwFileKey != 0)
- return dwFileKey;
- }
-
- // Not detected, sorry
- return 0;
-}
-
-DWORD DecryptFileKey(
- const char * szFileName,
- ULONGLONG MpqPos,
- DWORD dwFileSize,
- DWORD dwFlags)
-{
- DWORD dwFileKey;
- DWORD dwMpqPos = (DWORD)MpqPos;
-
- // File key is calculated from plain name
- szFileName = GetPlainFileName(szFileName);
- dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY);
-
- // Fix the key, if needed
- if(dwFlags & MPQ_FILE_FIX_KEY)
- dwFileKey = (dwFileKey + dwMpqPos) ^ dwFileSize;
-
- // Return the key
- return dwFileKey;
-}
-
-//-----------------------------------------------------------------------------
-// Handle validation functions
-
-TMPQArchive * IsValidMpqHandle(HANDLE hMpq)
-{
- TMPQArchive * ha = (TMPQArchive *)hMpq;
-
- return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL;
-}
-
-TMPQFile * IsValidFileHandle(HANDLE hFile)
-{
- TMPQFile * hf = (TMPQFile *)hFile;
-
- // Must not be NULL
- if(hf != NULL && hf->dwMagic == ID_MPQ_FILE)
- {
- // Local file handle?
- if(hf->pStream != NULL)
- return hf;
-
- // Also verify the MPQ handle within the file handle
- if(IsValidMpqHandle(hf->ha))
- return hf;
- }
-
- return NULL;
-}
-
-//-----------------------------------------------------------------------------
-// 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 dwHashIndexMask = HASH_INDEX_MASK(ha);
- DWORD dwIndex;
-
- // Set the initial index
- dwStartIndex = dwIndex = (dwStartIndex & 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) & 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)
-{
- DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
- DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX);
- DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A);
- DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B);
- DWORD dwIndex;
-
- // Set the initial index
- dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
-
- // Search the hash table
- for(;;)
- {
- 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
- dwIndex = (dwIndex + 1) & dwHashIndexMask;
- if(dwIndex == dwStartIndex)
- return NULL;
- }
-}
-
-TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
-{
- DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
- DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
- DWORD dwName1 = pHash->dwName1;
- DWORD dwName2 = pHash->dwName2;
- DWORD dwIndex = (DWORD)(pHash - ha->pHashTable);
-
- // 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
- dwIndex = (dwIndex + 1) & dwHashIndexMask;
- if(dwIndex == dwStartIndex)
- return NULL;
- 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;
- }
-}
-
-// Allocates an entry in the hash table
-TMPQHash * AllocateHashEntry(
- TMPQArchive * ha,
- TFileEntry * pFileEntry,
- LCID lcLocale)
-{
- 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, lcLocale);
- if(pHash != NULL)
- {
- // Fill the free hash entry
- pHash->dwName1 = dwName1;
- pHash->dwName2 = dwName2;
- pHash->lcLocale = (USHORT)lcLocale;
- pHash->wPlatform = 0;
- pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
- }
-
- return pHash;
-}
-
-// Finds a free space in the MPQ where to store next data
-// The free space begins beyond the file that is stored at the fuhrtest
-// position in the MPQ. (listfile), (attributes) and (signature) are ignored,
-// unless the MPQ is being flushed.
-ULONGLONG FindFreeMpqSpace(TMPQArchive * ha)
-{
- TMPQHeader * pHeader = ha->pHeader;
- TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- TFileEntry * pFileEntry;
- ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize;
- DWORD dwChunkCount;
-
- // Parse the entire block table
- for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
- {
- // Only take existing files with nonzero size
- if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0))
- {
- // If we are not saving MPQ tables, ignore internal MPQ files
- if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0 && IsInternalMpqFileName(pFileEntry->szFileName))
- continue;
-
- // If the end of the file is bigger than current MPQ table pos, update it
- if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos)
- {
- // Get the end of the file data
- FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize;
-
- // Add the MD5 chunks, if present
- if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0)
- {
- dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1;
- FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE;
- }
- }
- }
- }
-
- // Give the free space position to the caller
- return FreeSpacePos;
-}
-
-//-----------------------------------------------------------------------------
-// Common functions - MPQ File
-
-TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry)
-{
- TMPQFile * hf;
-
- // Allocate space for TMPQFile
- hf = STORM_ALLOC(TMPQFile, 1);
- if(hf != NULL)
- {
- // Fill the file structure
- memset(hf, 0, sizeof(TMPQFile));
- hf->dwMagic = ID_MPQ_FILE;
- hf->pStream = NULL;
- hf->ha = ha;
-
- // If the called entered a file entry, we also copy informations from the file entry
- if(ha != NULL && pFileEntry != NULL)
- {
- // Set the raw position and MPQ position
- hf->RawFilePos = FileOffsetFromMpqOffset(ha, pFileEntry->ByteOffset);
- hf->MpqFilePos = pFileEntry->ByteOffset;
-
- // Set the data size
- hf->dwDataSize = pFileEntry->dwFileSize;
- hf->pFileEntry = pFileEntry;
- }
- }
-
- return hf;
-}
-
-TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize)
-{
- ULONGLONG FreeMpqSpace;
- ULONGLONG TempPos;
- TMPQFile * hf;
-
- // We need to find the position in the MPQ where we save the file data
- FreeMpqSpace = FindFreeMpqSpace(ha);
-
- // When format V1, the size of the archive cannot exceed 4 GB
- if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
- {
- TempPos = FreeMpqSpace +
- dwFileSize +
- (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)) +
- (ha->dwFileTableSize * sizeof(TMPQBlock));
- if((TempPos >> 32) != 0)
- {
- SetLastError(ERROR_DISK_FULL);
- return NULL;
- }
- }
-
- // Allocate the file handle
- hf = CreateFileHandle(ha, NULL);
- if(hf == NULL)
- {
- SetLastError(ERROR_NOT_ENOUGH_MEMORY);
- return NULL;
- }
-
- // We need to find the position in the MPQ where we save the file data
- hf->MpqFilePos = FreeMpqSpace;
- hf->bIsWriteHandle = true;
- return hf;
-}
-
-// Loads a table from MPQ.
-// Can be used for hash table, block table, sector offset table or sector checksum table
-void * LoadMpqTable(
- TMPQArchive * ha,
- ULONGLONG ByteOffset,
- DWORD dwCompressedSize,
- DWORD dwTableSize,
- DWORD dwKey,
- bool * pbTableIsCut)
-{
- ULONGLONG FileSize = 0;
- LPBYTE pbCompressed = NULL;
- LPBYTE pbMpqTable;
- LPBYTE pbToRead;
- DWORD dwBytesToRead = dwCompressedSize;
- int nError = ERROR_SUCCESS;
-
- // Allocate the MPQ table
- pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize);
- if(pbMpqTable != NULL)
- {
- // Check if the MPQ table is encrypted
- if(dwCompressedSize < dwTableSize)
- {
- // Allocate temporary buffer for holding compressed data
- pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
- if(pbCompressed == NULL)
- {
- STORM_FREE(pbMpqTable);
- return NULL;
- }
- }
-
- // Get the file offset from which we will read the table
- // Note: According to Storm.dll from Warcraft III (version 2002),
- // if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
- // and the table is loaded from the current file offset
- if(ByteOffset == SFILE_INVALID_POS)
- FileStream_GetPos(ha->pStream, &ByteOffset);
-
- // On archives v 1.0, hash table and block table can go beyond EOF.
- // Storm.dll reads as much as possible, then fills the missing part with zeros.
- // Abused by Spazzler map protector which sets hash table size to 0x00100000
- if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
- {
- // Cut the table size
- FileStream_GetSize(ha->pStream, &FileSize);
- if((ByteOffset + dwBytesToRead) > FileSize)
- {
- // Fill the extra data with zeros
- dwBytesToRead = (DWORD)(FileSize - ByteOffset);
- memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
-
- // Give the caller information that the table was cut
- if(pbTableIsCut != NULL)
- pbTableIsCut[0] = true;
- }
- }
-
- // If everything succeeded, read the raw table form the MPQ
- if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
- {
- // First of all, decrypt the table
- if(dwKey != 0)
- {
- BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
- DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
- BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
- }
-
- // If the table is compressed, decompress it
- if(dwCompressedSize < dwTableSize)
- {
- int cbOutBuffer = (int)dwTableSize;
- int cbInBuffer = (int)dwCompressedSize;
-
- if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer))
- nError = GetLastError();
- }
-
- // Make sure that the table is properly byte-swapped
- BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
- }
- else
- {
- nError = GetLastError();
- }
-
- // If read failed, free the table and return
- if(nError != ERROR_SUCCESS)
- {
- STORM_FREE(pbMpqTable);
- pbMpqTable = NULL;
- }
-
- // Free the compression buffer, if any
- if(pbCompressed != NULL)
- STORM_FREE(pbCompressed);
- }
-
- // Return the MPQ table
- return pbMpqTable;
-}
-
-unsigned char * AllocateMd5Buffer(
- DWORD dwRawDataSize,
- DWORD dwChunkSize,
- LPDWORD pcbMd5Size)
-{
- unsigned char * md5_array;
- DWORD cbMd5Size;
-
- // Sanity check
- assert(dwRawDataSize != 0);
- assert(dwChunkSize != 0);
-
- // Calculate how many MD5's we will calculate
- cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE;
-
- // Allocate space for array or MD5s
- md5_array = STORM_ALLOC(BYTE, cbMd5Size);
-
- // Give the size of the MD5 array
- if(pcbMd5Size != NULL)
- *pcbMd5Size = cbMd5Size;
- return md5_array;
-}
-
-// Allocates sector buffer and sector offset table
-int AllocateSectorBuffer(TMPQFile * hf)
-{
- TMPQArchive * ha = hf->ha;
-
- // Caller of AllocateSectorBuffer must ensure these
- assert(hf->pbFileSector == NULL);
- assert(hf->pFileEntry != NULL);
- assert(hf->ha != NULL);
-
- // Don't allocate anything if the file has zero size
- if(hf->pFileEntry->dwFileSize == 0 || hf->dwDataSize == 0)
- return ERROR_SUCCESS;
-
- // Determine the file sector size and allocate buffer for it
- hf->dwSectorSize = (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) ? hf->dwDataSize : ha->dwSectorSize;
- hf->pbFileSector = STORM_ALLOC(BYTE, hf->dwSectorSize);
- hf->dwSectorOffs = SFILE_INVALID_POS;
-
- // Return result
- return (hf->pbFileSector != NULL) ? (int)ERROR_SUCCESS : (int)ERROR_NOT_ENOUGH_MEMORY;
-}
-
-// Allocates sector offset table
-int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile)
-{
- TMPQArchive * ha = hf->ha;
- DWORD dwLength = sizeof(TPatchInfo);
-
- // The following conditions must be true
- assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
- assert(hf->pPatchInfo == NULL);
-
-__AllocateAndLoadPatchInfo:
-
- // Allocate space for patch header. Start with default size,
- // and if its size if bigger, then we reload them
- hf->pPatchInfo = STORM_ALLOC(TPatchInfo, 1);
- if(hf->pPatchInfo == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Do we have to load the patch header from the file ?
- if(bLoadFromFile)
- {
- // Load the patch header
- if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength))
- {
- // Free the patch info
- STORM_FREE(hf->pPatchInfo);
- hf->pPatchInfo = NULL;
- return GetLastError();
- }
-
- // Perform necessary swapping
- hf->pPatchInfo->dwLength = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwLength);
- hf->pPatchInfo->dwFlags = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwFlags);
- hf->pPatchInfo->dwDataSize = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwDataSize);
-
- // Verify the size of the patch header
- // If it's not default size, we have to reload them
- if(hf->pPatchInfo->dwLength > dwLength)
- {
- // Free the patch info
- dwLength = hf->pPatchInfo->dwLength;
- STORM_FREE(hf->pPatchInfo);
- hf->pPatchInfo = NULL;
-
- // If the length is out of all possible ranges, fail the operation
- if(dwLength > 0x400)
- return ERROR_FILE_CORRUPT;
- goto __AllocateAndLoadPatchInfo;
- }
-
- // Patch file data size according to the patch header
- hf->dwDataSize = hf->pPatchInfo->dwDataSize;
- }
- else
- {
- memset(hf->pPatchInfo, 0, dwLength);
- }
-
- // Save the final length to the patch header
- hf->pPatchInfo->dwLength = dwLength;
- hf->pPatchInfo->dwFlags = 0x80000000;
- return ERROR_SUCCESS;
-}
-
-// Allocates sector offset table
-int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile)
-{
- TMPQArchive * ha = hf->ha;
- TFileEntry * pFileEntry = hf->pFileEntry;
- DWORD dwSectorOffsLen;
- bool bSectorOffsetTableCorrupt = false;
-
- // Caller of AllocateSectorOffsets must ensure these
- assert(hf->SectorOffsets == NULL);
- assert(hf->pFileEntry != NULL);
- assert(hf->dwDataSize != 0);
- assert(hf->ha != NULL);
-
- // If the file is stored as single unit, just set number of sectors to 1
- if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
- {
- hf->dwSectorCount = 1;
- return ERROR_SUCCESS;
- }
-
- // Calculate the number of data sectors
- // Note that this doesn't work if the file size is zero
- hf->dwSectorCount = ((hf->dwDataSize - 1) / hf->dwSectorSize) + 1;
-
- // Calculate the number of file sectors
- dwSectorOffsLen = (hf->dwSectorCount + 1) * sizeof(DWORD);
-
- // If MPQ_FILE_SECTOR_CRC flag is set, there will either be extra DWORD
- // or an array of MD5's. Either way, we read at least 4 bytes more
- // in order to save additional read from the file.
- if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
- dwSectorOffsLen += sizeof(DWORD);
-
- // Only allocate and load the table if the file is compressed
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
- {
- __LoadSectorOffsets:
-
- // Allocate the sector offset table
- hf->SectorOffsets = STORM_ALLOC(DWORD, (dwSectorOffsLen / sizeof(DWORD)));
- if(hf->SectorOffsets == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Only read from the file if we are supposed to do so
- if(bLoadFromFile)
- {
- ULONGLONG RawFilePos = hf->RawFilePos;
-
- // Append the length of the patch info, if any
- if(hf->pPatchInfo != NULL)
- {
- if((RawFilePos + hf->pPatchInfo->dwLength) < RawFilePos)
- return ERROR_FILE_CORRUPT;
- RawFilePos += hf->pPatchInfo->dwLength;
- }
-
- // Load the sector offsets from the file
- if(!FileStream_Read(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
- {
- // Free the sector offsets
- STORM_FREE(hf->SectorOffsets);
- hf->SectorOffsets = NULL;
- return GetLastError();
- }
-
- // Swap the sector positions
- BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
-
- // Decrypt loaded sector positions if necessary
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
- {
- // If we don't know the file key, try to find it.
- if(hf->dwFileKey == 0)
- {
- hf->dwFileKey = DetectFileKeyBySectorSize(hf->SectorOffsets, ha->dwSectorSize, dwSectorOffsLen);
- if(hf->dwFileKey == 0)
- {
- STORM_FREE(hf->SectorOffsets);
- hf->SectorOffsets = NULL;
- return ERROR_UNKNOWN_FILE_KEY;
- }
- }
-
- // Decrypt sector positions
- DecryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
- }
-
- //
- // Validate the sector offset table
- //
- // Note: Some MPQ protectors put the actual file data before the sector offset table.
- // In this case, the sector offsets are negative (> 0x80000000).
- //
-
- for(DWORD i = 0; i < hf->dwSectorCount; i++)
- {
- DWORD dwSectorOffset1 = hf->SectorOffsets[i+1];
- DWORD dwSectorOffset0 = hf->SectorOffsets[i];
-
- // Every following sector offset must be bigger than the previous one
- if(dwSectorOffset1 <= dwSectorOffset0)
- {
- bSectorOffsetTableCorrupt = true;
- break;
- }
-
- // The sector size must not be bigger than compressed file size
- // Edit: Yes, but apparently, in original Storm.dll, the compressed
- // size is not checked anywhere. However, we need to do this check
- // in order to sector offset table malformed by MPQ protectors
- if((dwSectorOffset1 - dwSectorOffset0) > ha->dwSectorSize)
- {
- bSectorOffsetTableCorrupt = true;
- break;
- }
- }
-
- // If data corruption detected, free the sector offset table
- if(bSectorOffsetTableCorrupt)
- {
- STORM_FREE(hf->SectorOffsets);
- hf->SectorOffsets = NULL;
- return ERROR_FILE_CORRUPT;
- }
-
- //
- // There may be various extra DWORDs loaded after the sector offset table.
- // They are mostly empty on WoW release MPQs, but on MPQs from PTR,
- // they contain random non-zero data. Their meaning is unknown.
- //
- // These extra values are, however, include in the dwCmpSize in the file
- // table. We cannot ignore them, because compacting archive would fail
- //
-
- if(hf->SectorOffsets[0] > dwSectorOffsLen)
- {
- // MPQ protectors put some ridiculous values there. We must limit the extra bytes
- if(hf->SectorOffsets[0] > (dwSectorOffsLen + 0x400))
- return ERROR_FILE_CORRUPT;
-
- // Free the old sector offset table
- dwSectorOffsLen = hf->SectorOffsets[0];
- STORM_FREE(hf->SectorOffsets);
- goto __LoadSectorOffsets;
- }
- }
- else
- {
- memset(hf->SectorOffsets, 0, dwSectorOffsLen);
- hf->SectorOffsets[0] = dwSectorOffsLen;
- }
- }
-
- return ERROR_SUCCESS;
-}
-
-int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
-{
- TMPQArchive * ha = hf->ha;
- TFileEntry * pFileEntry = hf->pFileEntry;
- ULONGLONG RawFilePos;
- DWORD dwCompressedSize = 0;
- DWORD dwExpectedSize;
- DWORD dwCrcOffset; // Offset of the CRC table, relative to file offset in the MPQ
- DWORD dwCrcSize;
-
- // Caller of AllocateSectorChecksums must ensure these
- assert(hf->SectorChksums == NULL);
- assert(hf->SectorOffsets != NULL);
- assert(hf->pFileEntry != NULL);
- assert(hf->ha != NULL);
-
- // Single unit files don't have sector checksums
- if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
- return ERROR_SUCCESS;
-
- // Caller must ensure that we are only called when we have sector checksums
- assert(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
-
- //
- // Older MPQs store an array of CRC32's after
- // the raw file data in the MPQ.
- //
- // In newer MPQs, the (since Cataclysm BETA) the (attributes) file
- // contains additional 32-bit values beyond the sector table.
- // Their number depends on size of the (attributes), but their
- // meaning is unknown. They are usually zeroed in retail game files,
- // but contain some sort of checksum in BETA MPQs
- //
-
- // Does the size of the file table match with the CRC32-based checksums?
- dwExpectedSize = (hf->dwSectorCount + 2) * sizeof(DWORD);
- if(hf->SectorOffsets[0] != 0 && hf->SectorOffsets[0] == dwExpectedSize)
- {
- // If we are not loading from the MPQ file, we just allocate the sector table
- // In that case, do not check any sizes
- if(bLoadFromFile == false)
- {
- hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount);
- if(hf->SectorChksums == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Fill the checksum table with zeros
- memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD));
- return ERROR_SUCCESS;
- }
- else
- {
- // Is there valid size of the sector checksums?
- if(hf->SectorOffsets[hf->dwSectorCount + 1] >= hf->SectorOffsets[hf->dwSectorCount])
- dwCompressedSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
-
- // Ignore cases when the length is too small or too big.
- if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize)
- return ERROR_SUCCESS;
-
- // Calculate offset of the CRC table
- dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
- dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount];
- RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
-
- // Now read the table from the MPQ
- hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0, NULL);
- if(hf->SectorChksums == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
- }
- }
-
- // If the size doesn't match, we ignore sector checksums
-// assert(false);
- return ERROR_SUCCESS;
-}
-
-int WritePatchInfo(TMPQFile * hf)
-{
- TMPQArchive * ha = hf->ha;
- TPatchInfo * pPatchInfo = hf->pPatchInfo;
-
- // The caller must make sure that this function is only called
- // when the following is true.
- assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
- assert(pPatchInfo != NULL);
-
- BSWAP_ARRAY32_UNSIGNED(pPatchInfo, 3 * sizeof(DWORD));
- if(!FileStream_Write(ha->pStream, &hf->RawFilePos, pPatchInfo, sizeof(TPatchInfo)))
- return GetLastError();
-
- return ERROR_SUCCESS;
-}
-
-int WriteSectorOffsets(TMPQFile * hf)
-{
- TMPQArchive * ha = hf->ha;
- TFileEntry * pFileEntry = hf->pFileEntry;
- ULONGLONG RawFilePos = hf->RawFilePos;
- DWORD dwSectorOffsLen;
-
- // The caller must make sure that this function is only called
- // when the following is true.
- assert(hf->pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
- assert(hf->SectorOffsets != NULL);
- dwSectorOffsLen = hf->SectorOffsets[0];
-
- // If file is encrypted, sector positions are also encrypted
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
- EncryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
- BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
-
- // Adjust sector offset table position, if we also have patch info
- if(hf->pPatchInfo != NULL)
- RawFilePos += hf->pPatchInfo->dwLength;
-
- // Write sector offsets to the archive
- if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
- return GetLastError();
-
- // Not necessary, as the sector checksums
- // are going to be freed when this is done.
-// BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
- return ERROR_SUCCESS;
-}
-
-
-int WriteSectorChecksums(TMPQFile * hf)
-{
- TMPQArchive * ha = hf->ha;
- ULONGLONG RawFilePos;
- TFileEntry * pFileEntry = hf->pFileEntry;
- LPBYTE pbCompressed;
- DWORD dwCompressedSize = 0;
- DWORD dwCrcSize;
- int nOutSize;
- int nError = ERROR_SUCCESS;
-
- // The caller must make sure that this function is only called
- // when the following is true.
- assert(hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
- assert(hf->SectorOffsets != NULL);
- assert(hf->SectorChksums != NULL);
-
- // If the MPQ has MD5 of each raw data chunk,
- // we leave sector offsets empty
- if(ha->pHeader->dwRawChunkSize != 0)
- {
- hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount];
- return ERROR_SUCCESS;
- }
-
- // Calculate size of the checksum array
- dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
-
- // Allocate buffer for compressed sector CRCs.
- pbCompressed = STORM_ALLOC(BYTE, dwCrcSize);
- if(pbCompressed == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Perform the compression
- BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
-
- nOutSize = (int)dwCrcSize;
- SCompCompress(pbCompressed, &nOutSize, hf->SectorChksums, (int)dwCrcSize, MPQ_COMPRESSION_ZLIB, 0, 0);
- dwCompressedSize = (DWORD)nOutSize;
-
- // Write the sector CRCs to the archive
- RawFilePos = hf->RawFilePos + hf->SectorOffsets[hf->dwSectorCount];
- if(hf->pPatchInfo != NULL)
- RawFilePos += hf->pPatchInfo->dwLength;
- if(!FileStream_Write(ha->pStream, &RawFilePos, pbCompressed, dwCompressedSize))
- nError = GetLastError();
-
- // Not necessary, as the sector checksums
- // are going to be freed when this is done.
-// BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
-
- // Store the sector CRCs
- hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount] + dwCompressedSize;
- pFileEntry->dwCmpSize += dwCompressedSize;
- STORM_FREE(pbCompressed);
- return nError;
-}
-
-int WriteMemDataMD5(
- TFileStream * pStream,
- ULONGLONG RawDataOffs,
- void * pvRawData,
- DWORD dwRawDataSize,
- DWORD dwChunkSize,
- LPDWORD pcbTotalSize)
-{
- unsigned char * md5_array;
- unsigned char * md5;
- LPBYTE pbRawData = (LPBYTE)pvRawData;
- DWORD dwBytesRemaining = dwRawDataSize;
- DWORD dwMd5ArraySize = 0;
- int nError = ERROR_SUCCESS;
-
- // Allocate buffer for array of MD5
- md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
- if(md5_array == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // For every file chunk, calculate MD5
- while(dwBytesRemaining != 0)
- {
- // Get the remaining number of bytes to read
- dwChunkSize = STORMLIB_MIN(dwBytesRemaining, dwChunkSize);
-
- // Calculate MD5
- CalculateDataBlockHash(pbRawData, dwChunkSize, md5);
- md5 += MD5_DIGEST_SIZE;
-
- // Move offset and size
- dwBytesRemaining -= dwChunkSize;
- pbRawData += dwChunkSize;
- }
-
- // Write the array od MD5's to the file
- RawDataOffs += dwRawDataSize;
- if(!FileStream_Write(pStream, &RawDataOffs, md5_array, dwMd5ArraySize))
- nError = GetLastError();
-
- // Give the caller the size of the MD5 array
- if(pcbTotalSize != NULL)
- *pcbTotalSize = dwRawDataSize + dwMd5ArraySize;
-
- // Free buffers and exit
- STORM_FREE(md5_array);
- return nError;
-}
-
-
-// Writes the MD5 for each chunk of the raw file data
-int WriteMpqDataMD5(
- TFileStream * pStream,
- ULONGLONG RawDataOffs,
- DWORD dwRawDataSize,
- DWORD dwChunkSize)
-{
- unsigned char * md5_array;
- unsigned char * md5;
- LPBYTE pbFileChunk;
- DWORD dwMd5ArraySize = 0;
- DWORD dwToRead = dwRawDataSize;
- int nError = ERROR_SUCCESS;
-
- // Allocate buffer for array of MD5
- md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
- if(md5_array == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Allocate space for file chunk
- pbFileChunk = STORM_ALLOC(BYTE, dwChunkSize);
- if(pbFileChunk == NULL)
- {
- STORM_FREE(md5_array);
- return ERROR_NOT_ENOUGH_MEMORY;
- }
-
- // For every file chunk, calculate MD5
- while(dwRawDataSize != 0)
- {
- // Get the remaining number of bytes to read
- dwToRead = STORMLIB_MIN(dwRawDataSize, dwChunkSize);
-
- // Read the chunk
- if(!FileStream_Read(pStream, &RawDataOffs, pbFileChunk, dwToRead))
- {
- nError = GetLastError();
- break;
- }
-
- // Calculate MD5
- CalculateDataBlockHash(pbFileChunk, dwToRead, md5);
- md5 += MD5_DIGEST_SIZE;
-
- // Move offset and size
- RawDataOffs += dwToRead;
- dwRawDataSize -= dwToRead;
- }
-
- // Write the array od MD5's to the file
- if(nError == ERROR_SUCCESS)
- {
- if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize))
- nError = GetLastError();
- }
-
- // Free buffers and exit
- STORM_FREE(pbFileChunk);
- STORM_FREE(md5_array);
- return nError;
-}
-
-// Frees the structure for MPQ file
-void FreeFileHandle(TMPQFile *& hf)
-{
- if(hf != NULL)
- {
- // If we have patch file attached to this one, free it first
- if(hf->hfPatch != NULL)
- FreeFileHandle(hf->hfPatch);
-
- // Then free all buffers allocated in the file structure
- if(hf->pbFileData != NULL)
- STORM_FREE(hf->pbFileData);
- if(hf->pPatchInfo != NULL)
- STORM_FREE(hf->pPatchInfo);
- if(hf->SectorOffsets != NULL)
- STORM_FREE(hf->SectorOffsets);
- if(hf->SectorChksums != NULL)
- STORM_FREE(hf->SectorChksums);
- if(hf->pbFileSector != NULL)
- STORM_FREE(hf->pbFileSector);
- if(hf->pStream != NULL)
- FileStream_Close(hf->pStream);
- STORM_FREE(hf);
- hf = NULL;
- }
-}
-
-// Frees the MPQ archive
-void FreeArchiveHandle(TMPQArchive *& ha)
-{
- if(ha != NULL)
- {
- // First of all, free the patch archive, if any
- if(ha->haPatch != NULL)
- FreeArchiveHandle(ha->haPatch);
-
- // Free the patch prefix, if any
- if(ha->pPatchPrefix != NULL)
- STORM_FREE(ha->pPatchPrefix);
-
- // Close the file stream
- FileStream_Close(ha->pStream);
- ha->pStream = NULL;
-
- // Free the file names from the file table
- if(ha->pFileTable != NULL)
- {
- for(DWORD i = 0; i < ha->dwFileTableSize; i++)
- {
- if(ha->pFileTable[i].szFileName != NULL)
- STORM_FREE(ha->pFileTable[i].szFileName);
- ha->pFileTable[i].szFileName = NULL;
- }
-
- // Then free all buffers allocated in the archive structure
- STORM_FREE(ha->pFileTable);
- }
-
- if(ha->pHashTable != NULL)
- STORM_FREE(ha->pHashTable);
- if(ha->pHetTable != NULL)
- FreeHetTable(ha->pHetTable);
- STORM_FREE(ha);
- ha = NULL;
- }
-}
-
-bool IsInternalMpqFileName(const char * szFileName)
-{
- if(szFileName != NULL && szFileName[0] == '(')
- {
- if(!_stricmp(szFileName, LISTFILE_NAME) ||
- !_stricmp(szFileName, ATTRIBUTES_NAME) ||
- !_stricmp(szFileName, SIGNATURE_NAME))
- {
- return true;
- }
- }
-
- return false;
-}
-
-// Verifies if the file name is a pseudo-name
-bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex)
-{
- DWORD dwFileIndex = 0;
-
- if(szFileName != NULL)
- {
- // Must be "File########.ext"
- if(!_strnicmp(szFileName, "File", 4))
- {
- // Check 8 digits
- for(int i = 4; i < 4+8; i++)
- {
- if(szFileName[i] < '0' || szFileName[i] > '9')
- return false;
- dwFileIndex = (dwFileIndex * 10) + (szFileName[i] - '0');
- }
-
- // An extension must follow
- if(szFileName[12] == '.')
- {
- if(pdwFileIndex != NULL)
- *pdwFileIndex = dwFileIndex;
- return true;
- }
- }
- }
-
- // Not a pseudo-name
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Functions calculating and verifying the MD5 signature
-
-bool IsValidMD5(LPBYTE pbMd5)
-{
- LPDWORD Md5 = (LPDWORD)pbMd5;
-
- return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
-}
-
-bool IsValidSignature(LPBYTE pbSignature)
-{
- LPDWORD Signature = (LPDWORD)pbSignature;
- DWORD SigValid = 0;
-
- for(int i = 0; i < MPQ_WEAK_SIGNATURE_SIZE / sizeof(DWORD); i++)
- SigValid |= Signature[i];
-
- return (SigValid != 0) ? true : false;
-}
-
-
-bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
-{
- hash_state md5_state;
- BYTE md5_digest[MD5_DIGEST_SIZE];
-
- // Don't verify the block if the MD5 is not valid.
- if(!IsValidMD5(expected_md5))
- return true;
-
- // Calculate the MD5 of the data block
- md5_init(&md5_state);
- md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
- md5_done(&md5_state, md5_digest);
-
- // Does the MD5's match?
- return (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
-}
-
-void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
-{
- hash_state md5_state;
-
- md5_init(&md5_state);
- md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
- md5_done(&md5_state, md5_hash);
-}
-
-
-//-----------------------------------------------------------------------------
-// Swapping functions
-
-#ifndef PLATFORM_LITTLE_ENDIAN
-
-//
-// Note that those functions are implemented for Mac operating system,
-// as this is the only supported platform that uses big endian.
-//
-
-// Swaps a signed 16-bit integer
-int16_t SwapInt16(uint16_t data)
-{
- return (int16_t)CFSwapInt16(data);
-}
-
-// Swaps an unsigned 16-bit integer
-uint16_t SwapUInt16(uint16_t data)
-{
- return CFSwapInt16(data);
-}
-
-// Swaps signed 32-bit integer
-int32_t SwapInt32(uint32_t data)
-{
- return (int32_t)CFSwapInt32(data);
-}
-
-// Swaps an unsigned 32-bit integer
-uint32_t SwapUInt32(uint32_t data)
-{
- return CFSwapInt32(data);
-}
-
-// Swaps signed 64-bit integer
-int64_t SwapInt64(int64_t data)
-{
- return (int64_t)CFSwapInt64(data);
-}
-
-// Swaps an unsigned 64-bit integer
-uint64_t SwapUInt64(uint64_t data)
-{
- return CFSwapInt64(data);
-}
-
-// Swaps array of unsigned 16-bit integers
-void ConvertUInt16Buffer(void * ptr, size_t length)
-{
- uint16_t * buffer = (uint16_t *)ptr;
- uint32_t nElements = (uint32_t)(length / sizeof(uint16_t));
-
- while(nElements-- > 0)
- {
- *buffer = SwapUInt16(*buffer);
- buffer++;
- }
-}
-
-// Swaps array of unsigned 32-bit integers
-void ConvertUInt32Buffer(void * ptr, size_t length)
-{
- uint32_t * buffer = (uint32_t *)ptr;
- uint32_t nElements = (uint32_t)(length / sizeof(uint32_t));
-
- while(nElements-- > 0)
- {
- *buffer = SwapUInt32(*buffer);
- buffer++;
- }
-}
-
-// Swaps array of unsigned 64-bit integers
-void ConvertUInt64Buffer(void * ptr, size_t length)
-{
- uint64_t * buffer = (uint64_t *)ptr;
- uint32_t nElements = (uint32_t)(length / sizeof(uint64_t));
-
- while(nElements-- > 0)
- {
- *buffer = SwapUInt64(*buffer);
- buffer++;
- }
-}
-
-// Swaps the TMPQHeader structure
-void ConvertTMPQHeader(void *header, uint16_t version)
-{
- TMPQHeader * theHeader = (TMPQHeader *)header;
-
- // 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)
- {
- theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
- theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
- theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
- }
-
- if(version == MPQ_FORMAT_VERSION_3)
- {
- theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
- theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
- theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
- }
-
- 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
+/*****************************************************************************/
+/* SBaseCommon.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Common functions for StormLib, used by all SFile*** modules */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 24.03.03 1.00 Lad The first version of SFileCommon.cpp */
+/* 19.11.03 1.01 Dan Big endian handling */
+/* 12.06.04 1.01 Lad Renamed to SCommon.cpp */
+/* 06.09.10 1.01 Lad Renamed to SBaseCommon.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014";
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+LCID lcFileLocale = LANG_NEUTRAL; // File locale
+USHORT wPlatform = 0; // File platform
+
+//-----------------------------------------------------------------------------
+// Conversion to uppercase/lowercase
+
+// 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,
+ 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, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 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
+// 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,
+ 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
+};
+
+//-----------------------------------------------------------------------------
+// Safe string functions
+
+void StringCopyA(char * dest, const char * src, size_t nMaxChars)
+{
+ size_t nLength = strlen(src);
+
+ // Don't copy more than nMaxChars
+ nLength = STORMLIB_MIN(nLength, nMaxChars);
+ memcpy(dest, src, nLength);
+ dest[nLength] = 0;
+}
+
+void StringCatA(char * dest, const char * src, size_t nMaxChars)
+{
+ size_t nLength1 = strlen(dest);
+ size_t nLength2 = strlen(src);
+
+ // Don't copy more than nMaxChars
+ if(nLength1 < nMaxChars)
+ {
+ nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1));
+ memcpy(dest + nLength1, src, nLength2);
+ dest[nLength1 + nLength2] = 0;
+ }
+}
+
+void StringCopyT(TCHAR * dest, const TCHAR * src, size_t nMaxChars)
+{
+ size_t nLength = _tcslen(src);
+
+ // Don't copy more than nMaxChars
+ nLength = STORMLIB_MIN(nLength, nMaxChars);
+ memcpy(dest, src, (nLength * sizeof(TCHAR)));
+ dest[nLength] = 0;
+}
+
+void StringCatT(TCHAR * dest, const TCHAR * src, size_t nMaxChars)
+{
+ size_t nLength1 = _tcslen(dest);
+ size_t nLength2 = _tcslen(src);
+
+ // Don't copy more than nMaxChars
+ if(nLength1 < nMaxChars)
+ {
+ nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1));
+ memcpy(dest + nLength1, src, (nLength2 * sizeof(TCHAR)));
+ dest[nLength1 + nLength2] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Storm hashing functions
+
+#define STORM_BUFFER_SIZE 0x500
+
+#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0)
+
+static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine
+static bool bMpqCryptographyInitialized = false;
+
+void InitializeMpqCryptography()
+{
+ DWORD dwSeed = 0x00100001;
+ DWORD index1 = 0;
+ DWORD index2 = 0;
+ int i;
+
+ // Initialize the decryption buffer.
+ // Do nothing if already done.
+ if(bMpqCryptographyInitialized == false)
+ {
+ for(index1 = 0; index1 < 0x100; index1++)
+ {
+ for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
+ {
+ DWORD temp1, temp2;
+
+ dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
+ temp1 = (dwSeed & 0xFFFF) << 0x10;
+
+ dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
+ temp2 = (dwSeed & 0xFFFF);
+
+ StormBuffer[index2] = (temp1 | temp2);
+ }
+ }
+
+ // Also register both MD5 and SHA1 hash algorithms
+ register_hash(&md5_desc);
+ register_hash(&sha1_desc);
+
+ // Use LibTomMath as support math library for LibTomCrypt
+ ltc_mp = ltm_desc;
+
+ // Don't do that again
+ bMpqCryptographyInitialized = true;
+ }
+}
+
+DWORD HashString(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
+ // Convert slash (0x2F) to backslash (0x5C)
+ ch = AsciiToUpperTable[*pbKey++];
+
+ dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
+ dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
+ }
+
+ 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
+
+DWORD GetHashTableSizeForFileCount(DWORD dwFileCount)
+{
+ DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN;
+
+ // For zero files, there is no hash table needed
+ if(dwFileCount == 0)
+ return 0;
+
+ // Round the hash table size up to the nearest power of two
+ // Don't allow the hash table size go over allowed maximum
+ while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount)
+ dwPowerOfTwo <<= 1;
+ return dwPowerOfTwo;
+}
+
+//-----------------------------------------------------------------------------
+// Calculates a Jenkin's Encrypting and decrypting MPQ file data
+
+ULONGLONG HashStringJenkins(const char * szFileName)
+{
+ LPBYTE pbFileName = (LPBYTE)szFileName;
+ char szNameBuff[0x108];
+ size_t nLength = 0;
+ unsigned int primary_hash = 1;
+ unsigned int secondary_hash = 2;
+
+ // Normalize the file name - convert to uppercase, and convert "/" to "\\".
+ if(pbFileName != NULL)
+ {
+ char * szNamePtr = szNameBuff;
+ char * szNameEnd = szNamePtr + sizeof(szNameBuff);
+
+ // Normalize the file name. Doesn't have to be zero terminated for hashing
+ while(szNamePtr < szNameEnd && pbFileName[0] != 0)
+ *szNamePtr++ = (char)AsciiToLowerTable[*pbFileName++];
+ nLength = szNamePtr - szNameBuff;
+ }
+
+ // Thanks Quantam for finding out what the algorithm is.
+ // I am really getting old for reversing large chunks of assembly
+ // that does hashing :-)
+ hashlittle2(szNameBuff, nLength, &secondary_hash, &primary_hash);
+
+ // Combine those 2 together
+ return ((ULONGLONG)primary_hash << 0x20) | (ULONGLONG)secondary_hash;
+}
+
+//-----------------------------------------------------------------------------
+// Default flags for (attributes) and (listfile)
+
+DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion)
+{
+ // Fixed for format 1.0
+ if(wFormatVersion == MPQ_FORMAT_VERSION_1)
+ return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY;
+
+ // Size-dependent for formats 2.0-4.0
+ return (dwFileSize > 0x4000) ? (MPQ_FILE_COMPRESS | MPQ_FILE_SECTOR_CRC) : (MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT);
+}
+
+
+//-----------------------------------------------------------------------------
+// Encrypting/Decrypting MPQ data block
+
+void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
+{
+ LPDWORD DataBlock = (LPDWORD)pvDataBlock;
+ DWORD dwValue32;
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Round to DWORDs
+ dwLength >>= 2;
+
+ // Encrypt the data block at array of DWORDs
+ for(DWORD i = 0; i < dwLength; i++)
+ {
+ // Modify the second key
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+
+ dwValue32 = DataBlock[i];
+ DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2);
+
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
+ }
+}
+
+void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
+{
+ LPDWORD DataBlock = (LPDWORD)pvDataBlock;
+ DWORD dwValue32;
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Round to DWORDs
+ dwLength >>= 2;
+
+ // Decrypt the data block at array of DWORDs
+ for(DWORD i = 0; i < dwLength; i++)
+ {
+ // Modify the second key
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+
+ DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2);
+ dwValue32 = DataBlock[i];
+
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
+ }
+}
+
+/**
+ * Functions tries to get file decryption key. This comes from these facts
+ *
+ * - We know the decrypted value of the first DWORD in the encrypted data
+ * - We know the decrypted value of the second DWORD (at least aproximately)
+ * - There is only 256 variants of how the second key is modified
+ *
+ * The first iteration of dwKey1 and dwKey2 is this:
+ *
+ * dwKey2 = 0xEEEEEEEE + StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]
+ * dwDecrypted0 = DataBlock[0] ^ (dwKey1 + dwKey2);
+ *
+ * This means:
+ *
+ * (dwKey1 + dwKey2) = DataBlock[0] ^ dwDecrypted0;
+ *
+ */
+
+DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwDecrypted0)
+{
+ DWORD dwDecrypted1Max = dwSectorSize + dwDecrypted0;
+ DWORD dwKey1PlusKey2;
+ DWORD DataBlock[2];
+
+ // We must have at least 2 DWORDs there to be able to decrypt something
+ if(dwSectorSize < 0x08)
+ return 0;
+
+ // Get the value of the combined encryption key
+ dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
+
+ // Try all 256 combinations of dwKey1
+ for(DWORD i = 0; i < 0x100; i++)
+ {
+ DWORD dwSaveKey1;
+ DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Modify the second key and decrypt the first DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
+
+ // Did we obtain the same value like dwDecrypted0?
+ if(DataBlock[0] == dwDecrypted0)
+ {
+ // Save this key value. Increment by one because
+ // we are decrypting sector offset table
+ dwSaveKey1 = dwKey1 + 1;
+
+ // Rotate both keys
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
+
+ // Modify the second key again and decrypt the second DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
+
+ // Now compare the results
+ if(DataBlock[1] <= dwDecrypted1Max)
+ return dwSaveKey1;
+ }
+ }
+
+ // Key not found
+ return 0;
+}
+
+// Function tries to detect file encryption key based on expected file content
+// It is the same function like before, except that we know the value of the second DWORD
+DWORD DetectFileKeyByKnownContent(void * pvEncryptedData, DWORD dwDecrypted0, DWORD dwDecrypted1)
+{
+ LPDWORD EncryptedData = (LPDWORD)pvEncryptedData;
+ DWORD dwKey1PlusKey2;
+ DWORD DataBlock[2];
+
+ // Get the value of the combined encryption key
+ dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
+
+ // Try all 256 combinations of dwKey1
+ for(DWORD i = 0; i < 0x100; i++)
+ {
+ DWORD dwSaveKey1;
+ DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Modify the second key and decrypt the first DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
+
+ // Did we obtain the same value like dwDecrypted0?
+ if(DataBlock[0] == dwDecrypted0)
+ {
+ // Save this key value
+ dwSaveKey1 = dwKey1;
+
+ // Rotate both keys
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
+
+ // Modify the second key again and decrypt the second DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
+
+ // Now compare the results
+ if(DataBlock[1] == dwDecrypted1)
+ return dwSaveKey1;
+ }
+ }
+
+ // Key not found
+ return 0;
+}
+
+DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize)
+{
+ DWORD dwFileKey;
+
+ // Try to break the file encryption key as if it was a WAVE file
+ if(dwSectorSize >= 0x0C)
+ {
+ dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x46464952, dwFileSize - 8);
+ if(dwFileKey != 0)
+ return dwFileKey;
+ }
+
+ // Try to break the encryption key as if it was an EXE file
+ if(dwSectorSize > 0x40)
+ {
+ dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x00905A4D, 0x00000003);
+ if(dwFileKey != 0)
+ return dwFileKey;
+ }
+
+ // Try to break the encryption key as if it was a XML file
+ if(dwSectorSize > 0x04)
+ {
+ dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x6D783F3C, 0x6576206C);
+ if(dwFileKey != 0)
+ return dwFileKey;
+ }
+
+ // Not detected, sorry
+ return 0;
+}
+
+DWORD DecryptFileKey(
+ const char * szFileName,
+ ULONGLONG MpqPos,
+ DWORD dwFileSize,
+ DWORD dwFlags)
+{
+ DWORD dwFileKey;
+ DWORD dwMpqPos = (DWORD)MpqPos;
+
+ // File key is calculated from plain name
+ szFileName = GetPlainFileName(szFileName);
+ dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY);
+
+ // Fix the key, if needed
+ if(dwFlags & MPQ_FILE_FIX_KEY)
+ dwFileKey = (dwFileKey + dwMpqPos) ^ dwFileSize;
+
+ // Return the key
+ return dwFileKey;
+}
+
+//-----------------------------------------------------------------------------
+// Handle validation functions
+
+TMPQArchive * IsValidMpqHandle(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL;
+}
+
+TMPQFile * IsValidFileHandle(HANDLE hFile)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+
+ // Must not be NULL
+ if(hf != NULL && hf->dwMagic == ID_MPQ_FILE)
+ {
+ // Local file handle?
+ if(hf->pStream != NULL)
+ return hf;
+
+ // Also verify the MPQ handle within the file handle
+ if(IsValidMpqHandle(hf->ha))
+ return hf;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// 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 dwHashIndexMask = HASH_INDEX_MASK(ha);
+ DWORD dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & 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) & 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)
+{
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
+ DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX);
+ DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A);
+ DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B);
+ DWORD dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
+
+ // Search the hash table
+ for(;;)
+ {
+ 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
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ return NULL;
+ }
+}
+
+TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
+{
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
+ DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
+ DWORD dwName1 = pHash->dwName1;
+ DWORD dwName2 = pHash->dwName2;
+ DWORD dwIndex = (DWORD)(pHash - ha->pHashTable);
+
+ // 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
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ return NULL;
+ 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;
+ }
+}
+
+// Allocates an entry in the hash table
+TMPQHash * AllocateHashEntry(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry,
+ LCID lcLocale)
+{
+ 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, lcLocale);
+ if(pHash != NULL)
+ {
+ // Fill the free hash entry
+ pHash->dwName1 = dwName1;
+ pHash->dwName2 = dwName2;
+ pHash->lcLocale = (USHORT)lcLocale;
+ pHash->wPlatform = 0;
+ pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
+ }
+
+ return pHash;
+}
+
+// Finds a free space in the MPQ where to store next data
+// The free space begins beyond the file that is stored at the fuhrtest
+// position in the MPQ. (listfile), (attributes) and (signature) are ignored,
+// unless the MPQ is being flushed.
+ULONGLONG FindFreeMpqSpace(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize;
+ DWORD dwChunkCount;
+
+ // Parse the entire block table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Only take existing files with nonzero size
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0))
+ {
+ // If we are not saving MPQ tables, ignore internal MPQ files
+ if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0 && IsInternalMpqFileName(pFileEntry->szFileName))
+ continue;
+
+ // If the end of the file is bigger than current MPQ table pos, update it
+ if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos)
+ {
+ // Get the end of the file data
+ FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize;
+
+ // Add the MD5 chunks, if present
+ if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0)
+ {
+ dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1;
+ FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE;
+ }
+ }
+ }
+ }
+
+ // Give the free space position to the caller
+ return FreeSpacePos;
+}
+
+//-----------------------------------------------------------------------------
+// Common functions - MPQ File
+
+TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry)
+{
+ TMPQFile * hf;
+
+ // Allocate space for TMPQFile
+ hf = STORM_ALLOC(TMPQFile, 1);
+ if(hf != NULL)
+ {
+ // Fill the file structure
+ memset(hf, 0, sizeof(TMPQFile));
+ hf->dwMagic = ID_MPQ_FILE;
+ hf->pStream = NULL;
+ hf->ha = ha;
+
+ // If the called entered a file entry, we also copy informations from the file entry
+ if(ha != NULL && pFileEntry != NULL)
+ {
+ // Set the raw position and MPQ position
+ hf->RawFilePos = FileOffsetFromMpqOffset(ha, pFileEntry->ByteOffset);
+ hf->MpqFilePos = pFileEntry->ByteOffset;
+
+ // Set the data size
+ hf->dwDataSize = pFileEntry->dwFileSize;
+ hf->pFileEntry = pFileEntry;
+ }
+ }
+
+ return hf;
+}
+
+TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize)
+{
+ ULONGLONG FreeMpqSpace;
+ ULONGLONG TempPos;
+ TMPQFile * hf;
+
+ // We need to find the position in the MPQ where we save the file data
+ FreeMpqSpace = FindFreeMpqSpace(ha);
+
+ // When format V1, the size of the archive cannot exceed 4 GB
+ if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ {
+ TempPos = FreeMpqSpace +
+ dwFileSize +
+ (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)) +
+ (ha->dwFileTableSize * sizeof(TMPQBlock));
+ if((TempPos >> 32) != 0)
+ {
+ SetLastError(ERROR_DISK_FULL);
+ return NULL;
+ }
+ }
+
+ // Allocate the file handle
+ hf = CreateFileHandle(ha, NULL);
+ if(hf == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ // We need to find the position in the MPQ where we save the file data
+ hf->MpqFilePos = FreeMpqSpace;
+ hf->bIsWriteHandle = true;
+ return hf;
+}
+
+// Loads a table from MPQ.
+// Can be used for hash table, block table, sector offset table or sector checksum table
+void * LoadMpqTable(
+ TMPQArchive * ha,
+ ULONGLONG ByteOffset,
+ DWORD dwCompressedSize,
+ DWORD dwTableSize,
+ DWORD dwKey,
+ bool * pbTableIsCut)
+{
+ ULONGLONG FileSize = 0;
+ LPBYTE pbCompressed = NULL;
+ LPBYTE pbMpqTable;
+ LPBYTE pbToRead;
+ DWORD dwBytesToRead = dwCompressedSize;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate the MPQ table
+ pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize);
+ if(pbMpqTable != NULL)
+ {
+ // Check if the MPQ table is encrypted
+ if(dwCompressedSize < dwTableSize)
+ {
+ // Allocate temporary buffer for holding compressed data
+ pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
+ if(pbCompressed == NULL)
+ {
+ STORM_FREE(pbMpqTable);
+ return NULL;
+ }
+ }
+
+ // Get the file offset from which we will read the table
+ // Note: According to Storm.dll from Warcraft III (version 2002),
+ // if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
+ // and the table is loaded from the current file offset
+ if(ByteOffset == SFILE_INVALID_POS)
+ FileStream_GetPos(ha->pStream, &ByteOffset);
+
+ // On archives v 1.0, hash table and block table can go beyond EOF.
+ // Storm.dll reads as much as possible, then fills the missing part with zeros.
+ // Abused by Spazzler map protector which sets hash table size to 0x00100000
+ if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ {
+ // Cut the table size
+ FileStream_GetSize(ha->pStream, &FileSize);
+ if((ByteOffset + dwBytesToRead) > FileSize)
+ {
+ // Fill the extra data with zeros
+ dwBytesToRead = (DWORD)(FileSize - ByteOffset);
+ memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
+
+ // Give the caller information that the table was cut
+ if(pbTableIsCut != NULL)
+ pbTableIsCut[0] = true;
+ }
+ }
+
+ // If everything succeeded, read the raw table form the MPQ
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
+ {
+ // First of all, decrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ }
+
+ // If the table is compressed, decompress it
+ if(dwCompressedSize < dwTableSize)
+ {
+ int cbOutBuffer = (int)dwTableSize;
+ int cbInBuffer = (int)dwCompressedSize;
+
+ if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer))
+ nError = GetLastError();
+ }
+
+ // Make sure that the table is properly byte-swapped
+ BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ // If read failed, free the table and return
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pbMpqTable);
+ pbMpqTable = NULL;
+ }
+
+ // Free the compression buffer, if any
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+ }
+
+ // Return the MPQ table
+ return pbMpqTable;
+}
+
+unsigned char * AllocateMd5Buffer(
+ DWORD dwRawDataSize,
+ DWORD dwChunkSize,
+ LPDWORD pcbMd5Size)
+{
+ unsigned char * md5_array;
+ DWORD cbMd5Size;
+
+ // Sanity check
+ assert(dwRawDataSize != 0);
+ assert(dwChunkSize != 0);
+
+ // Calculate how many MD5's we will calculate
+ cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE;
+
+ // Allocate space for array or MD5s
+ md5_array = STORM_ALLOC(BYTE, cbMd5Size);
+
+ // Give the size of the MD5 array
+ if(pcbMd5Size != NULL)
+ *pcbMd5Size = cbMd5Size;
+ return md5_array;
+}
+
+// Allocates sector buffer and sector offset table
+int AllocateSectorBuffer(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+
+ // Caller of AllocateSectorBuffer must ensure these
+ assert(hf->pbFileSector == NULL);
+ assert(hf->pFileEntry != NULL);
+ assert(hf->ha != NULL);
+
+ // Don't allocate anything if the file has zero size
+ if(hf->pFileEntry->dwFileSize == 0 || hf->dwDataSize == 0)
+ return ERROR_SUCCESS;
+
+ // Determine the file sector size and allocate buffer for it
+ hf->dwSectorSize = (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) ? hf->dwDataSize : ha->dwSectorSize;
+ hf->pbFileSector = STORM_ALLOC(BYTE, hf->dwSectorSize);
+ hf->dwSectorOffs = SFILE_INVALID_POS;
+
+ // Return result
+ return (hf->pbFileSector != NULL) ? (int)ERROR_SUCCESS : (int)ERROR_NOT_ENOUGH_MEMORY;
+}
+
+// Allocates sector offset table
+int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile)
+{
+ TMPQArchive * ha = hf->ha;
+ DWORD dwLength = sizeof(TPatchInfo);
+
+ // The following conditions must be true
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
+ assert(hf->pPatchInfo == NULL);
+
+__AllocateAndLoadPatchInfo:
+
+ // Allocate space for patch header. Start with default size,
+ // and if its size if bigger, then we reload them
+ hf->pPatchInfo = STORM_ALLOC(TPatchInfo, 1);
+ if(hf->pPatchInfo == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Do we have to load the patch header from the file ?
+ if(bLoadFromFile)
+ {
+ // Load the patch header
+ if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength))
+ {
+ // Free the patch info
+ STORM_FREE(hf->pPatchInfo);
+ hf->pPatchInfo = NULL;
+ return GetLastError();
+ }
+
+ // Perform necessary swapping
+ hf->pPatchInfo->dwLength = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwLength);
+ hf->pPatchInfo->dwFlags = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwFlags);
+ hf->pPatchInfo->dwDataSize = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwDataSize);
+
+ // Verify the size of the patch header
+ // If it's not default size, we have to reload them
+ if(hf->pPatchInfo->dwLength > dwLength)
+ {
+ // Free the patch info
+ dwLength = hf->pPatchInfo->dwLength;
+ STORM_FREE(hf->pPatchInfo);
+ hf->pPatchInfo = NULL;
+
+ // If the length is out of all possible ranges, fail the operation
+ if(dwLength > 0x400)
+ return ERROR_FILE_CORRUPT;
+ goto __AllocateAndLoadPatchInfo;
+ }
+
+ // Patch file data size according to the patch header
+ hf->dwDataSize = hf->pPatchInfo->dwDataSize;
+ }
+ else
+ {
+ memset(hf->pPatchInfo, 0, dwLength);
+ }
+
+ // Save the final length to the patch header
+ hf->pPatchInfo->dwLength = dwLength;
+ hf->pPatchInfo->dwFlags = 0x80000000;
+ return ERROR_SUCCESS;
+}
+
+// Allocates sector offset table
+int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ DWORD dwSectorOffsLen;
+ bool bSectorOffsetTableCorrupt = false;
+
+ // Caller of AllocateSectorOffsets must ensure these
+ assert(hf->SectorOffsets == NULL);
+ assert(hf->pFileEntry != NULL);
+ assert(hf->dwDataSize != 0);
+ assert(hf->ha != NULL);
+
+ // If the file is stored as single unit, just set number of sectors to 1
+ if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
+ {
+ hf->dwSectorCount = 1;
+ return ERROR_SUCCESS;
+ }
+
+ // Calculate the number of data sectors
+ // Note that this doesn't work if the file size is zero
+ hf->dwSectorCount = ((hf->dwDataSize - 1) / hf->dwSectorSize) + 1;
+
+ // Calculate the number of file sectors
+ dwSectorOffsLen = (hf->dwSectorCount + 1) * sizeof(DWORD);
+
+ // If MPQ_FILE_SECTOR_CRC flag is set, there will either be extra DWORD
+ // or an array of MD5's. Either way, we read at least 4 bytes more
+ // in order to save additional read from the file.
+ if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
+ dwSectorOffsLen += sizeof(DWORD);
+
+ // Only allocate and load the table if the file is compressed
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ __LoadSectorOffsets:
+
+ // Allocate the sector offset table
+ hf->SectorOffsets = STORM_ALLOC(DWORD, (dwSectorOffsLen / sizeof(DWORD)));
+ if(hf->SectorOffsets == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Only read from the file if we are supposed to do so
+ if(bLoadFromFile)
+ {
+ ULONGLONG RawFilePos = hf->RawFilePos;
+
+ // Append the length of the patch info, if any
+ if(hf->pPatchInfo != NULL)
+ {
+ if((RawFilePos + hf->pPatchInfo->dwLength) < RawFilePos)
+ return ERROR_FILE_CORRUPT;
+ RawFilePos += hf->pPatchInfo->dwLength;
+ }
+
+ // Load the sector offsets from the file
+ if(!FileStream_Read(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
+ {
+ // Free the sector offsets
+ STORM_FREE(hf->SectorOffsets);
+ hf->SectorOffsets = NULL;
+ return GetLastError();
+ }
+
+ // Swap the sector positions
+ BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
+
+ // Decrypt loaded sector positions if necessary
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ // If we don't know the file key, try to find it.
+ if(hf->dwFileKey == 0)
+ {
+ hf->dwFileKey = DetectFileKeyBySectorSize(hf->SectorOffsets, ha->dwSectorSize, dwSectorOffsLen);
+ if(hf->dwFileKey == 0)
+ {
+ STORM_FREE(hf->SectorOffsets);
+ hf->SectorOffsets = NULL;
+ return ERROR_UNKNOWN_FILE_KEY;
+ }
+ }
+
+ // Decrypt sector positions
+ DecryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
+ }
+
+ //
+ // Validate the sector offset table
+ //
+ // Note: Some MPQ protectors put the actual file data before the sector offset table.
+ // In this case, the sector offsets are negative (> 0x80000000).
+ //
+
+ for(DWORD i = 0; i < hf->dwSectorCount; i++)
+ {
+ DWORD dwSectorOffset1 = hf->SectorOffsets[i+1];
+ DWORD dwSectorOffset0 = hf->SectorOffsets[i];
+
+ // Every following sector offset must be bigger than the previous one
+ if(dwSectorOffset1 <= dwSectorOffset0)
+ {
+ bSectorOffsetTableCorrupt = true;
+ break;
+ }
+
+ // The sector size must not be bigger than compressed file size
+ // Edit: Yes, but apparently, in original Storm.dll, the compressed
+ // size is not checked anywhere. However, we need to do this check
+ // in order to sector offset table malformed by MPQ protectors
+ if((dwSectorOffset1 - dwSectorOffset0) > ha->dwSectorSize)
+ {
+ bSectorOffsetTableCorrupt = true;
+ break;
+ }
+ }
+
+ // If data corruption detected, free the sector offset table
+ if(bSectorOffsetTableCorrupt)
+ {
+ STORM_FREE(hf->SectorOffsets);
+ hf->SectorOffsets = NULL;
+ return ERROR_FILE_CORRUPT;
+ }
+
+ //
+ // There may be various extra DWORDs loaded after the sector offset table.
+ // They are mostly empty on WoW release MPQs, but on MPQs from PTR,
+ // they contain random non-zero data. Their meaning is unknown.
+ //
+ // These extra values are, however, include in the dwCmpSize in the file
+ // table. We cannot ignore them, because compacting archive would fail
+ //
+
+ if(hf->SectorOffsets[0] > dwSectorOffsLen)
+ {
+ // MPQ protectors put some ridiculous values there. We must limit the extra bytes
+ if(hf->SectorOffsets[0] > (dwSectorOffsLen + 0x400))
+ return ERROR_FILE_CORRUPT;
+
+ // Free the old sector offset table
+ dwSectorOffsLen = hf->SectorOffsets[0];
+ STORM_FREE(hf->SectorOffsets);
+ goto __LoadSectorOffsets;
+ }
+ }
+ else
+ {
+ memset(hf->SectorOffsets, 0, dwSectorOffsLen);
+ hf->SectorOffsets[0] = dwSectorOffsLen;
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ ULONGLONG RawFilePos;
+ DWORD dwCompressedSize = 0;
+ DWORD dwExpectedSize;
+ DWORD dwCrcOffset; // Offset of the CRC table, relative to file offset in the MPQ
+ DWORD dwCrcSize;
+
+ // Caller of AllocateSectorChecksums must ensure these
+ assert(hf->SectorChksums == NULL);
+ assert(hf->SectorOffsets != NULL);
+ assert(hf->pFileEntry != NULL);
+ assert(hf->ha != NULL);
+
+ // Single unit files don't have sector checksums
+ if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
+ return ERROR_SUCCESS;
+
+ // Caller must ensure that we are only called when we have sector checksums
+ assert(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
+
+ //
+ // Older MPQs store an array of CRC32's after
+ // the raw file data in the MPQ.
+ //
+ // In newer MPQs, the (since Cataclysm BETA) the (attributes) file
+ // contains additional 32-bit values beyond the sector table.
+ // Their number depends on size of the (attributes), but their
+ // meaning is unknown. They are usually zeroed in retail game files,
+ // but contain some sort of checksum in BETA MPQs
+ //
+
+ // Does the size of the file table match with the CRC32-based checksums?
+ dwExpectedSize = (hf->dwSectorCount + 2) * sizeof(DWORD);
+ if(hf->SectorOffsets[0] != 0 && hf->SectorOffsets[0] == dwExpectedSize)
+ {
+ // If we are not loading from the MPQ file, we just allocate the sector table
+ // In that case, do not check any sizes
+ if(bLoadFromFile == false)
+ {
+ hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount);
+ if(hf->SectorChksums == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the checksum table with zeros
+ memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD));
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ // Is there valid size of the sector checksums?
+ if(hf->SectorOffsets[hf->dwSectorCount + 1] >= hf->SectorOffsets[hf->dwSectorCount])
+ dwCompressedSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
+
+ // Ignore cases when the length is too small or too big.
+ if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize)
+ return ERROR_SUCCESS;
+
+ // Calculate offset of the CRC table
+ dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
+ dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount];
+ RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
+
+ // Now read the table from the MPQ
+ hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0, NULL);
+ if(hf->SectorChksums == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // If the size doesn't match, we ignore sector checksums
+// assert(false);
+ return ERROR_SUCCESS;
+}
+
+int WritePatchInfo(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ TPatchInfo * pPatchInfo = hf->pPatchInfo;
+
+ // The caller must make sure that this function is only called
+ // when the following is true.
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
+ assert(pPatchInfo != NULL);
+
+ BSWAP_ARRAY32_UNSIGNED(pPatchInfo, 3 * sizeof(DWORD));
+ if(!FileStream_Write(ha->pStream, &hf->RawFilePos, pPatchInfo, sizeof(TPatchInfo)))
+ return GetLastError();
+
+ return ERROR_SUCCESS;
+}
+
+int WriteSectorOffsets(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ ULONGLONG RawFilePos = hf->RawFilePos;
+ DWORD dwSectorOffsLen;
+
+ // The caller must make sure that this function is only called
+ // when the following is true.
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
+ assert(hf->SectorOffsets != NULL);
+ dwSectorOffsLen = hf->SectorOffsets[0];
+
+ // If file is encrypted, sector positions are also encrypted
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ EncryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
+ BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
+
+ // Adjust sector offset table position, if we also have patch info
+ if(hf->pPatchInfo != NULL)
+ RawFilePos += hf->pPatchInfo->dwLength;
+
+ // Write sector offsets to the archive
+ if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
+ return GetLastError();
+
+ // Not necessary, as the sector checksums
+ // are going to be freed when this is done.
+// BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
+ return ERROR_SUCCESS;
+}
+
+
+int WriteSectorChecksums(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ ULONGLONG RawFilePos;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ LPBYTE pbCompressed;
+ DWORD dwCompressedSize = 0;
+ DWORD dwCrcSize;
+ int nOutSize;
+ int nError = ERROR_SUCCESS;
+
+ // The caller must make sure that this function is only called
+ // when the following is true.
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
+ assert(hf->SectorOffsets != NULL);
+ assert(hf->SectorChksums != NULL);
+
+ // If the MPQ has MD5 of each raw data chunk,
+ // we leave sector offsets empty
+ if(ha->pHeader->dwRawChunkSize != 0)
+ {
+ hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount];
+ return ERROR_SUCCESS;
+ }
+
+ // Calculate size of the checksum array
+ dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
+
+ // Allocate buffer for compressed sector CRCs.
+ pbCompressed = STORM_ALLOC(BYTE, dwCrcSize);
+ if(pbCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Perform the compression
+ BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
+
+ nOutSize = (int)dwCrcSize;
+ SCompCompress(pbCompressed, &nOutSize, hf->SectorChksums, (int)dwCrcSize, MPQ_COMPRESSION_ZLIB, 0, 0);
+ dwCompressedSize = (DWORD)nOutSize;
+
+ // Write the sector CRCs to the archive
+ RawFilePos = hf->RawFilePos + hf->SectorOffsets[hf->dwSectorCount];
+ if(hf->pPatchInfo != NULL)
+ RawFilePos += hf->pPatchInfo->dwLength;
+ if(!FileStream_Write(ha->pStream, &RawFilePos, pbCompressed, dwCompressedSize))
+ nError = GetLastError();
+
+ // Not necessary, as the sector checksums
+ // are going to be freed when this is done.
+// BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
+
+ // Store the sector CRCs
+ hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount] + dwCompressedSize;
+ pFileEntry->dwCmpSize += dwCompressedSize;
+ STORM_FREE(pbCompressed);
+ return nError;
+}
+
+int WriteMemDataMD5(
+ TFileStream * pStream,
+ ULONGLONG RawDataOffs,
+ void * pvRawData,
+ DWORD dwRawDataSize,
+ DWORD dwChunkSize,
+ LPDWORD pcbTotalSize)
+{
+ unsigned char * md5_array;
+ unsigned char * md5;
+ LPBYTE pbRawData = (LPBYTE)pvRawData;
+ DWORD dwBytesRemaining = dwRawDataSize;
+ DWORD dwMd5ArraySize = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate buffer for array of MD5
+ md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
+ if(md5_array == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // For every file chunk, calculate MD5
+ while(dwBytesRemaining != 0)
+ {
+ // Get the remaining number of bytes to read
+ dwChunkSize = STORMLIB_MIN(dwBytesRemaining, dwChunkSize);
+
+ // Calculate MD5
+ CalculateDataBlockHash(pbRawData, dwChunkSize, md5);
+ md5 += MD5_DIGEST_SIZE;
+
+ // Move offset and size
+ dwBytesRemaining -= dwChunkSize;
+ pbRawData += dwChunkSize;
+ }
+
+ // Write the array od MD5's to the file
+ RawDataOffs += dwRawDataSize;
+ if(!FileStream_Write(pStream, &RawDataOffs, md5_array, dwMd5ArraySize))
+ nError = GetLastError();
+
+ // Give the caller the size of the MD5 array
+ if(pcbTotalSize != NULL)
+ *pcbTotalSize = dwRawDataSize + dwMd5ArraySize;
+
+ // Free buffers and exit
+ STORM_FREE(md5_array);
+ return nError;
+}
+
+
+// Writes the MD5 for each chunk of the raw file data
+int WriteMpqDataMD5(
+ TFileStream * pStream,
+ ULONGLONG RawDataOffs,
+ DWORD dwRawDataSize,
+ DWORD dwChunkSize)
+{
+ unsigned char * md5_array;
+ unsigned char * md5;
+ LPBYTE pbFileChunk;
+ DWORD dwMd5ArraySize = 0;
+ DWORD dwToRead = dwRawDataSize;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate buffer for array of MD5
+ md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
+ if(md5_array == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate space for file chunk
+ pbFileChunk = STORM_ALLOC(BYTE, dwChunkSize);
+ if(pbFileChunk == NULL)
+ {
+ STORM_FREE(md5_array);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // For every file chunk, calculate MD5
+ while(dwRawDataSize != 0)
+ {
+ // Get the remaining number of bytes to read
+ dwToRead = STORMLIB_MIN(dwRawDataSize, dwChunkSize);
+
+ // Read the chunk
+ if(!FileStream_Read(pStream, &RawDataOffs, pbFileChunk, dwToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Calculate MD5
+ CalculateDataBlockHash(pbFileChunk, dwToRead, md5);
+ md5 += MD5_DIGEST_SIZE;
+
+ // Move offset and size
+ RawDataOffs += dwToRead;
+ dwRawDataSize -= dwToRead;
+ }
+
+ // Write the array od MD5's to the file
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize))
+ nError = GetLastError();
+ }
+
+ // Free buffers and exit
+ STORM_FREE(pbFileChunk);
+ STORM_FREE(md5_array);
+ return nError;
+}
+
+// Frees the structure for MPQ file
+void FreeFileHandle(TMPQFile *& hf)
+{
+ if(hf != NULL)
+ {
+ // If we have patch file attached to this one, free it first
+ if(hf->hfPatch != NULL)
+ FreeFileHandle(hf->hfPatch);
+
+ // Then free all buffers allocated in the file structure
+ if(hf->pbFileData != NULL)
+ STORM_FREE(hf->pbFileData);
+ if(hf->pPatchInfo != NULL)
+ STORM_FREE(hf->pPatchInfo);
+ if(hf->SectorOffsets != NULL)
+ STORM_FREE(hf->SectorOffsets);
+ if(hf->SectorChksums != NULL)
+ STORM_FREE(hf->SectorChksums);
+ if(hf->pbFileSector != NULL)
+ STORM_FREE(hf->pbFileSector);
+ if(hf->pStream != NULL)
+ FileStream_Close(hf->pStream);
+ STORM_FREE(hf);
+ hf = NULL;
+ }
+}
+
+// Frees the MPQ archive
+void FreeArchiveHandle(TMPQArchive *& ha)
+{
+ if(ha != NULL)
+ {
+ // First of all, free the patch archive, if any
+ if(ha->haPatch != NULL)
+ FreeArchiveHandle(ha->haPatch);
+
+ // Free the patch prefix, if any
+ if(ha->pPatchPrefix != NULL)
+ STORM_FREE(ha->pPatchPrefix);
+
+ // Close the file stream
+ FileStream_Close(ha->pStream);
+ ha->pStream = NULL;
+
+ // Free the file names from the file table
+ if(ha->pFileTable != NULL)
+ {
+ for(DWORD i = 0; i < ha->dwFileTableSize; i++)
+ {
+ if(ha->pFileTable[i].szFileName != NULL)
+ STORM_FREE(ha->pFileTable[i].szFileName);
+ ha->pFileTable[i].szFileName = NULL;
+ }
+
+ // Then free all buffers allocated in the archive structure
+ STORM_FREE(ha->pFileTable);
+ }
+
+ if(ha->pHashTable != NULL)
+ STORM_FREE(ha->pHashTable);
+ if(ha->pHetTable != NULL)
+ FreeHetTable(ha->pHetTable);
+ STORM_FREE(ha);
+ ha = NULL;
+ }
+}
+
+bool IsInternalMpqFileName(const char * szFileName)
+{
+ if(szFileName != NULL && szFileName[0] == '(')
+ {
+ if(!_stricmp(szFileName, LISTFILE_NAME) ||
+ !_stricmp(szFileName, ATTRIBUTES_NAME) ||
+ !_stricmp(szFileName, SIGNATURE_NAME))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Verifies if the file name is a pseudo-name
+bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex)
+{
+ DWORD dwFileIndex = 0;
+
+ if(szFileName != NULL)
+ {
+ // Must be "File########.ext"
+ if(!_strnicmp(szFileName, "File", 4))
+ {
+ // Check 8 digits
+ for(int i = 4; i < 4+8; i++)
+ {
+ if(szFileName[i] < '0' || szFileName[i] > '9')
+ return false;
+ dwFileIndex = (dwFileIndex * 10) + (szFileName[i] - '0');
+ }
+
+ // An extension must follow
+ if(szFileName[12] == '.')
+ {
+ if(pdwFileIndex != NULL)
+ *pdwFileIndex = dwFileIndex;
+ return true;
+ }
+ }
+ }
+
+ // Not a pseudo-name
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Functions calculating and verifying the MD5 signature
+
+bool IsValidMD5(LPBYTE pbMd5)
+{
+ LPDWORD Md5 = (LPDWORD)pbMd5;
+
+ return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
+}
+
+bool IsValidSignature(LPBYTE pbSignature)
+{
+ LPDWORD Signature = (LPDWORD)pbSignature;
+ DWORD SigValid = 0;
+
+ for(int i = 0; i < MPQ_WEAK_SIGNATURE_SIZE / sizeof(DWORD); i++)
+ SigValid |= Signature[i];
+
+ return (SigValid != 0) ? true : false;
+}
+
+
+bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
+{
+ hash_state md5_state;
+ BYTE md5_digest[MD5_DIGEST_SIZE];
+
+ // Don't verify the block if the MD5 is not valid.
+ if(!IsValidMD5(expected_md5))
+ return true;
+
+ // Calculate the MD5 of the data block
+ md5_init(&md5_state);
+ md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
+ md5_done(&md5_state, md5_digest);
+
+ // Does the MD5's match?
+ return (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
+}
+
+void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
+{
+ hash_state md5_state;
+
+ md5_init(&md5_state);
+ md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
+ md5_done(&md5_state, md5_hash);
+}
+
+
+//-----------------------------------------------------------------------------
+// Swapping functions
+
+#ifndef PLATFORM_LITTLE_ENDIAN
+
+//
+// Note that those functions are implemented for Mac operating system,
+// as this is the only supported platform that uses big endian.
+//
+
+// Swaps a signed 16-bit integer
+int16_t SwapInt16(uint16_t data)
+{
+ return (int16_t)CFSwapInt16(data);
+}
+
+// Swaps an unsigned 16-bit integer
+uint16_t SwapUInt16(uint16_t data)
+{
+ return CFSwapInt16(data);
+}
+
+// Swaps signed 32-bit integer
+int32_t SwapInt32(uint32_t data)
+{
+ return (int32_t)CFSwapInt32(data);
+}
+
+// Swaps an unsigned 32-bit integer
+uint32_t SwapUInt32(uint32_t data)
+{
+ return CFSwapInt32(data);
+}
+
+// Swaps signed 64-bit integer
+int64_t SwapInt64(int64_t data)
+{
+ return (int64_t)CFSwapInt64(data);
+}
+
+// Swaps an unsigned 64-bit integer
+uint64_t SwapUInt64(uint64_t data)
+{
+ return CFSwapInt64(data);
+}
+
+// Swaps array of unsigned 16-bit integers
+void ConvertUInt16Buffer(void * ptr, size_t length)
+{
+ uint16_t * buffer = (uint16_t *)ptr;
+ uint32_t nElements = (uint32_t)(length / sizeof(uint16_t));
+
+ while(nElements-- > 0)
+ {
+ *buffer = SwapUInt16(*buffer);
+ buffer++;
+ }
+}
+
+// Swaps array of unsigned 32-bit integers
+void ConvertUInt32Buffer(void * ptr, size_t length)
+{
+ uint32_t * buffer = (uint32_t *)ptr;
+ uint32_t nElements = (uint32_t)(length / sizeof(uint32_t));
+
+ while(nElements-- > 0)
+ {
+ *buffer = SwapUInt32(*buffer);
+ buffer++;
+ }
+}
+
+// Swaps array of unsigned 64-bit integers
+void ConvertUInt64Buffer(void * ptr, size_t length)
+{
+ uint64_t * buffer = (uint64_t *)ptr;
+ uint32_t nElements = (uint32_t)(length / sizeof(uint64_t));
+
+ while(nElements-- > 0)
+ {
+ *buffer = SwapUInt64(*buffer);
+ buffer++;
+ }
+}
+
+// Swaps the TMPQHeader structure
+void ConvertTMPQHeader(void *header, uint16_t version)
+{
+ TMPQHeader * theHeader = (TMPQHeader *)header;
+
+ // 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)
+ {
+ theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
+ theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
+ theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
+ }
+
+ if(version == MPQ_FORMAT_VERSION_3)
+ {
+ theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
+ theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
+ theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
+ }
+
+ 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