diff options
| author | Nicholas Estelami <NEstelami@users.noreply.github.com> | 2022-06-01 16:05:05 -0400 |
|---|---|---|
| committer | Nicholas Estelami <NEstelami@users.noreply.github.com> | 2022-06-01 16:05:05 -0400 |
| commit | fc7da35054a8e7a12fb6dd5dfa01082c2b049c63 (patch) | |
| tree | 578be5f8a8608e45f550e6e7ba9f7128d8d88890 /StormLib/src/SBaseCommon.cpp | |
| parent | 6f5ce7d7157d60c22ca7cd43ff576fc54dc50dbc (diff) | |
Replaced StormLib .lib files with source code.
Some modifications to handle backslashes and forward slashes, along with some optimizations to speed up OTR generation.
Diffstat (limited to 'StormLib/src/SBaseCommon.cpp')
| -rw-r--r-- | StormLib/src/SBaseCommon.cpp | 1967 |
1 files changed, 1967 insertions, 0 deletions
diff --git a/StormLib/src/SBaseCommon.cpp b/StormLib/src/SBaseCommon.cpp new file mode 100644 index 000000000..fcb50a927 --- /dev/null +++ b/StormLib/src/SBaseCommon.cpp @@ -0,0 +1,1967 @@ +/*****************************************************************************/ +/* 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 + +DWORD g_dwMpqSignature = ID_MPQ; // Marker for MPQ header +DWORD g_dwHashTableKey = MPQ_KEY_HASH_TABLE; // Key for hash table +DWORD g_dwBlockTableKey = MPQ_KEY_BLOCK_TABLE; // Key for block table +LCID g_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 backslash (0x5C) to backslash (0x2F) +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 (for ANSI builds) + +char * StringCopy(char * szTarget, size_t cchTarget, const char * szSource) +{ + size_t cchSource = 0; + + if(cchTarget > 0) + { + cchSource = strlen(szSource); + + if(cchSource >= cchTarget) + cchSource = cchTarget - 1; + + memcpy(szTarget, szSource, cchSource); + szTarget[cchSource] = 0; + } + + return szTarget + cchSource; +} + +void StringCat(char * szTarget, size_t cchTargetMax, const char * szSource) +{ + // Get the current length of the target + size_t cchTarget = strlen(szTarget); + + // Copy the string to the target + if(cchTarget < cchTargetMax) + { + StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource); + } +} + +void StringCreatePseudoFileName(char * szBuffer, size_t cchMaxChars, unsigned int nIndex, const char * szExtension) +{ + char * szBufferEnd = szBuffer + cchMaxChars; + + // "File" + szBuffer = StringCopy(szBuffer, (szBufferEnd - szBuffer), "File"); + + // Number + szBuffer = IntToString(szBuffer, szBufferEnd - szBuffer + 1, nIndex, 8); + + // Dot + if(szBuffer < szBufferEnd) + *szBuffer++ = '.'; + + // Extension + while(szExtension[0] == '.') + szExtension++; + StringCopy(szBuffer, (szBufferEnd - szBuffer), szExtension); +} + +//----------------------------------------------------------------------------- +// Utility functions (UNICODE) only exist in the ANSI version of the library +// In ANSI builds, TCHAR = char, so we don't need these functions implemented + +#ifdef _UNICODE +void StringCopy(TCHAR * szTarget, size_t cchTarget, const char * szSource) +{ + if(cchTarget > 0) + { + size_t cchSource = strlen(szSource); + + if(cchSource >= cchTarget) + cchSource = cchTarget - 1; + + mbstowcs(szTarget, szSource, cchSource); + szTarget[cchSource] = 0; + } +} + +void StringCopy(char * szTarget, size_t cchTarget, const TCHAR * szSource) +{ + if(cchTarget > 0) + { + size_t cchSource = _tcslen(szSource); + + if(cchSource >= cchTarget) + cchSource = cchTarget - 1; + + wcstombs(szTarget, szSource, cchSource); + szTarget[cchSource] = 0; + } +} + +void StringCopy(TCHAR * szTarget, size_t cchTarget, const TCHAR * szSource) +{ + if(cchTarget > 0) + { + size_t cchSource = _tcslen(szSource); + + if(cchSource >= cchTarget) + cchSource = cchTarget - 1; + + memcpy(szTarget, szSource, cchSource * sizeof(TCHAR)); + szTarget[cchSource] = 0; + } +} + +void StringCat(TCHAR * szTarget, size_t cchTargetMax, const TCHAR * szSource) +{ + // Get the current length of the target + size_t cchTarget = _tcslen(szTarget); + + // Copy the string to the target + if(cchTarget < cchTargetMax) + { + StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource); + } +} +#endif + +//----------------------------------------------------------------------------- +// 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; + } +} + +// +// Note: Implementation of this function in WorldEdit.exe and storm.dll +// incorrectly treats the character as signed, which leads to the +// a buffer underflow if the character in the file name >= 0x80: +// The following steps happen when *pbKey == 0xBF and dwHashType == 0x0000 +// (calculating hash index) +// +// 1) Result of AsciiToUpperTable_Slash[*pbKey++] is sign-extended to 0xffffffbf +// 2) The "ch" is added to dwHashType (0xffffffbf + 0x0000 => 0xffffffbf) +// 3) The result is used as index to the StormBuffer table, +// thus dereferences a random value BEFORE the begin of StormBuffer. +// +// As result, MPQs containing files with non-ANSI characters will not work between +// various game versions and localizations. Even WorldEdit, after importing a file +// with Korean characters in the name, cannot open the file back. +// +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++]; + ch = AsciiToUpperTable[*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 + +// Returns the nearest higher power of two. +// If the value is already a power of two, returns the same value +DWORD GetNearestPowerOfTwo(DWORD dwFileCount) +{ + dwFileCount --; + + dwFileCount |= dwFileCount >> 1; + dwFileCount |= dwFileCount >> 2; + dwFileCount |= dwFileCount >> 4; + dwFileCount |= dwFileCount >> 8; + dwFileCount |= dwFileCount >> 16; + + return dwFileCount + 1; +} +/* +DWORD GetNearestPowerOfTwo(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 + +static DWORD EncryptUInt32Unaligned(LPDWORD DataPointer, DWORD i, DWORD dwXorKey) +{ + LPBYTE pbDataPointer = (LPBYTE)(DataPointer + i); + LPBYTE pbXorKey = (LPBYTE)(&dwXorKey); + DWORD dwValue32; + + // Retrieve the value + dwValue32 = ((DWORD)pbDataPointer[0] << 0x00) | + ((DWORD)pbDataPointer[1] << 0x08) | + ((DWORD)pbDataPointer[2] << 0x10) | + ((DWORD)pbDataPointer[3] << 0x18); + + // Perform unaligned XOR + pbDataPointer[0] = (pbDataPointer[0] ^ pbXorKey[0]); + pbDataPointer[1] = (pbDataPointer[1] ^ pbXorKey[1]); + pbDataPointer[2] = (pbDataPointer[2] ^ pbXorKey[2]); + pbDataPointer[3] = (pbDataPointer[3] ^ pbXorKey[3]); + return dwValue32; +} + +void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1) +{ + LPDWORD DataPointer = (LPDWORD)pvDataBlock; + DWORD dwValue32; + DWORD dwKey2 = 0xEEEEEEEE; + + // Round to DWORDs + dwLength >>= 2; + + // We need different approach on non-aligned buffers + if(STORMLIB_DWORD_ALIGNED(DataPointer)) + { + for(DWORD i = 0; i < dwLength; i++) + { + // Modify the second key + dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; + + // We can use 32-bit approach, when the buffer is aligned + DataPointer[i] = (dwValue32 = DataPointer[i]) ^ (dwKey1 + dwKey2); + + dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); + dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3; + } + } + else + { + for(DWORD i = 0; i < dwLength; i++) + { + // Modify the second key + dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; + + // The data are unaligned. Make sure we don't cause data misalignment error + dwValue32 = EncryptUInt32Unaligned(DataPointer, i, (dwKey1 + dwKey2)); + + dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); + dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3; + } + } +} + +static DWORD DecryptUInt32Unaligned(LPDWORD DataPointer, DWORD i, DWORD dwXorKey) +{ + LPBYTE pbDataPointer = (LPBYTE)(DataPointer + i); + LPBYTE pbXorKey = (LPBYTE)(&dwXorKey); + + // Perform unaligned XOR + pbDataPointer[0] = (pbDataPointer[0] ^ pbXorKey[0]); + pbDataPointer[1] = (pbDataPointer[1] ^ pbXorKey[1]); + pbDataPointer[2] = (pbDataPointer[2] ^ pbXorKey[2]); + pbDataPointer[3] = (pbDataPointer[3] ^ pbXorKey[3]); + + // Retrieve the value + return ((DWORD)pbDataPointer[0] << 0x00) | + ((DWORD)pbDataPointer[1] << 0x08) | + ((DWORD)pbDataPointer[2] << 0x10) | + ((DWORD)pbDataPointer[3] << 0x18); +} + +void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1) +{ + LPDWORD DataPointer = (LPDWORD)pvDataBlock; + DWORD dwValue32; + DWORD dwKey2 = 0xEEEEEEEE; + + // Round to DWORDs + dwLength >>= 2; + + // We need different approach on non-aligned buffers + if(STORMLIB_DWORD_ALIGNED(DataPointer)) + { + for(DWORD i = 0; i < dwLength; i++) + { + // Modify the second key + dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; + + // We can use 32-bit approach, when the buffer is aligned + DataPointer[i] = dwValue32 = DataPointer[i] ^ (dwKey1 + dwKey2); + + dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); + dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3; + } + } + else + { + for(DWORD i = 0; i < dwLength; i++) + { + // Modify the second key + dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; + + // The data are unaligned. Make sure we don't cause data misalignment error + dwValue32 = DecryptUInt32Unaligned(DataPointer, i, (dwKey1 + dwKey2)); + + 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 == g_dwMpqSignature) ? 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 && MPQ_BLOCK_INDEX(pHash) < 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 && MPQ_BLOCK_INDEX(pHash) < 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->Platform = 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; + + //static TFileEntry* furthestFile = nullptr; + //TFileEntry* startEntry = furthestFile == nullptr ? ha->pFileTable : furthestFile; + + TFileEntry* startEntry = (ha->useFreeSpaceOptimization && ha->lastFreeSpaceEntry != nullptr) ? ha->lastFreeSpaceEntry : ha->pFileTable; + + // Parse the entire block table + for(pFileEntry = startEntry; 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; + } + + ha->lastFreeSpaceEntry = pFileEntry; + + //if (ha->useFreeSpaceOptimization) + //break; + } + } + } + + // 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, + LPBYTE pbTableHash, + DWORD dwCompressedSize, + DWORD dwTableSize, + DWORD dwKey, + bool * pbTableIsCut) +{ + ULONGLONG FileSize = 0; + LPBYTE pbCompressed = NULL; + LPBYTE pbMpqTable; + LPBYTE pbToRead; + DWORD dwBytesToRead = dwCompressedSize; + DWORD dwErrCode = 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 + // Abused by NP_Protect in MPQs v4 as well + 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 from the MPQ + if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead)) + { + // Verify the MD5 of the table, if present + if(!VerifyDataBlockHash(pbToRead, dwBytesToRead, pbTableHash)) + { + dwErrCode = ERROR_FILE_CORRUPT; + } + } + else + { + dwErrCode = GetLastError(); + } + + if(dwErrCode == ERROR_SUCCESS) + { + // 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)) + dwErrCode = GetLastError(); + } + + // Make sure that the table is properly byte-swapped + BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize); + } + + // If read failed, free the table and return + if(dwErrCode != 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 +DWORD 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) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; +} + +// Allocates sector offset table +DWORD 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 +DWORD 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; +} + +DWORD 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, NULL, 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; +} + +DWORD 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; +} + +DWORD 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; +} + +DWORD WriteSectorChecksums(TMPQFile * hf) +{ + TMPQArchive * ha = hf->ha; + ULONGLONG RawFilePos; + TFileEntry * pFileEntry = hf->pFileEntry; + LPBYTE pbCompressed; + DWORD dwCompressedSize = 0; + DWORD dwErrCode = ERROR_SUCCESS; + DWORD dwCrcSize; + int nOutSize; + + // 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)) + dwErrCode = 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 dwErrCode; +} + +DWORD 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; + DWORD dwErrCode = 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)) + dwErrCode = 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 dwErrCode; +} + + +// Writes the MD5 for each chunk of the raw file data +DWORD WriteMpqDataMD5( + TFileStream * pStream, + ULONGLONG RawDataOffs, + DWORD dwRawDataSize, + DWORD dwChunkSize) +{ + unsigned char * md5_array; + unsigned char * md5; + LPBYTE pbFileChunk; + DWORD dwMd5ArraySize = 0; + DWORD dwToRead = dwRawDataSize; + DWORD dwErrCode = 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)) + { + dwErrCode = 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(dwErrCode == ERROR_SUCCESS) + { + if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize)) + dwErrCode = GetLastError(); + } + + // Free buffers and exit + STORM_FREE(pbFileChunk); + STORM_FREE(md5_array); + return dwErrCode; +} + +// 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 != NULL) && (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]; + bool bResult = true; + + // Don't verify the block if the MD5 is not valid. + if(IsValidMD5(expected_md5)) + { + // 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? + bResult = (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0); + } + + return bResult; +} + +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 STORMLIB_LITTLE_ENDIAN + +// Swaps a signed 16-bit integer +int16_t SwapInt16(uint16_t val) +{ + return (val << 8) | ((val >> 8) & 0xFF); +} + +// Swaps an unsigned 16-bit integer +uint16_t SwapUInt16(uint16_t val) +{ + return (val << 8) | (val >> 8 ); +} + +// Swaps a signed 32-bit integer +int32_t SwapInt32(uint32_t val) +{ + val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF ); + return (val << 16) | ((val >> 16) & 0xFFFF); +} + +// Swaps an unsigned 32-bit integer +uint32_t SwapUInt32(uint32_t val) +{ + val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF ); + return (val << 16) | (val >> 16); +} + +// Swaps a signed 64-bit integer +int64_t SwapInt64(uint64_t val) +{ + val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL ); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL ); + return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL); +} + +// Swaps an unsigned 64-bit integer +uint64_t SwapUInt64(uint64_t val) +{ + val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL ); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL ); + return (val << 32) | (val >> 32); +} + +// 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 // STORMLIB_LITTLE_ENDIAN |
