diff options
Diffstat (limited to 'dep/StormLib/src/SBaseCommon.cpp')
-rw-r--r-- | dep/StormLib/src/SBaseCommon.cpp | 1700 |
1 files changed, 0 insertions, 1700 deletions
diff --git a/dep/StormLib/src/SBaseCommon.cpp b/dep/StormLib/src/SBaseCommon.cpp deleted file mode 100644 index 65818784521..00000000000 --- a/dep/StormLib/src/SBaseCommon.cpp +++ /dev/null @@ -1,1700 +0,0 @@ -/*****************************************************************************/ -/* 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-2012"; - -//----------------------------------------------------------------------------- -// The buffer for decryption engine. - -LCID lcFileLocale = LANG_NEUTRAL; // File locale -USHORT wPlatform = 0; // File platform - -//----------------------------------------------------------------------------- -// Storm buffer functions - -#define STORM_BUFFER_SIZE 0x500 - -static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine -static bool bMpqCryptographyInitialized = false; - -DWORD HashString(const char * szFileName, DWORD dwHashType) -{ - LPBYTE pbKey = (BYTE *)szFileName; - DWORD dwSeed1 = 0x7FED7FED; - DWORD dwSeed2 = 0xEEEEEEEE; - DWORD ch; - - while(*pbKey != 0) - { - ch = toupper(*pbKey++); - - dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); - dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; - } - - return dwSeed1; -} - -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; - } -} - -//----------------------------------------------------------------------------- -// Calculates the hash table size for a given amount of files - -DWORD GetHashTableSizeForFileCount(DWORD dwFileCount) -{ - DWORD dwPowerOfTwo; - - // Round the hash table size up to the nearest power of two - for(dwPowerOfTwo = HASH_TABLE_SIZE_MIN; dwPowerOfTwo < HASH_TABLE_SIZE_MAX; dwPowerOfTwo <<= 1) - { - if(dwPowerOfTwo >= dwFileCount) - { - return dwPowerOfTwo; - } - } - - // Don't allow the hash table size go over allowed maximum - return HASH_TABLE_SIZE_MAX; -} - -//----------------------------------------------------------------------------- -// Calculates a Jenkin's Encrypting and decrypting MPQ file data - -ULONGLONG HashStringJenkins(const char * szFileName) -{ - char * szTemp; - char szLocFileName[0x108]; - char chOneChar; - size_t nLength = 0; - unsigned int primary_hash = 1; - unsigned int secondary_hash = 2; - - // This is required to produce defined results - assert(szFileName != NULL); - - // Normalize the file name - convert to uppercase, and convert "/" to "\\". - if(szFileName != NULL) - { - szTemp = szLocFileName; - while(*szFileName != 0) - { - chOneChar = (char)tolower(*szFileName++); - if(chOneChar == '/') - chOneChar = '\\'; - - *szTemp++ = chOneChar; - } - - nLength = szTemp - szLocFileName; - *szTemp = 0; - } - - // Thanks Quantam for finding out what the algorithm is. - // I am really getting old for reversing large chunks of assembly - // that does hashing :-) - hashlittle2(szLocFileName, nLength, &secondary_hash, &primary_hash); - - // Combine those 2 together - return (ULONGLONG)primary_hash * (ULONGLONG)0x100000000ULL + (ULONGLONG)secondary_hash; -} - -//----------------------------------------------------------------------------- -// This function converts the MPQ header so it always looks like version 4 - -int ConvertMpqHeaderToFormat4( - TMPQArchive * ha, - ULONGLONG FileSize, - DWORD dwFlags) -{ - ULONGLONG ByteOffset; - TMPQHeader * pHeader = ha->pHeader; - DWORD dwExpectedArchiveSize; - USHORT wFormatVersion = pHeader->wFormatVersion; - int nError = ERROR_SUCCESS; - - // If version 1.0 is forced, then the format version is forced to be 1.0 - // Reason: Storm.dll in Warcraft III ignores format version value - if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) - wFormatVersion = MPQ_FORMAT_VERSION_1; - - // Format-specific fixes - switch(wFormatVersion) - { - case MPQ_FORMAT_VERSION_1: - - // Check for malformed MPQ header version 1.0 - if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) - { - pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; - ha->dwFlags |= MPQ_FLAG_PROTECTED; - } - - // - // The value of "dwArchiveSize" member in the MPQ header - // is ignored by Storm.dll and can contain garbage value - // ("w3xmaster" protector). - // - - dwExpectedArchiveSize = (DWORD)(FileSize - ha->MpqPos); - if(pHeader->dwArchiveSize != dwExpectedArchiveSize) - { - // Note: dwExpectedArchiveSize might be incorrect at this point. - // MPQs version 1.0 can have strong digital signature appended at the end, - // or they might just have arbitrary data there. - // In either case, we recalculate the archive size later when - // block table is loaded and positions of all files is known. - pHeader->dwArchiveSize = dwExpectedArchiveSize; - ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE; - } - - // Zero the fields in 2.0 part of the MPQ header - pHeader->HiBlockTablePos64 = 0; - pHeader->wHashTablePosHi = 0; - pHeader->wBlockTablePosHi = 0; - // No break here !!! - - case MPQ_FORMAT_VERSION_2: - case MPQ_FORMAT_VERSION_3: - - // In MPQ format 3.0, the entire header is optional - // and the size of the header can actually be identical - // to size of header 2.0 - if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3) - { - ULONGLONG ArchiveSize64 = pHeader->dwArchiveSize; - - // In format 2.0, the archive size is obsolete and is calculated - // as the highest offset of hash table, block table or hi-block table. - // However, we can still rely on it, if the size of the archive is under 4 GB - if((FileSize - ha->MpqPos) >> 32) - { - ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + (pHeader->dwHashTableSize * sizeof(TMPQHash)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - - ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - - // Only if we actually have a hi-block table - if(pHeader->HiBlockTablePos64) - { - ByteOffset = pHeader->HiBlockTablePos64 + (pHeader->dwBlockTableSize * sizeof(USHORT)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - } - - // We need to recalculate archive size later, - // when block table is loaded and the position of files is known - ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE; - } - - // Initialize the rest of the 3.0 header - pHeader->ArchiveSize64 = ArchiveSize64; - pHeader->HetTablePos64 = 0; - pHeader->BetTablePos64 = 0; - } - - // - // Calculate compressed size of each table. We assume the following order: - // 1) HET table - // 2) BET table - // 3) Classic hash table - // 4) Classic block table - // 5) Hi-block table - // - - // Set all sizes to zero - pHeader->HetTableSize64 = 0; - pHeader->BetTableSize64 = 0; - - // Either both HET and BET table exist or none of them does. - if(pHeader->HetTablePos64) - { - // Compressed size of the HET and BET tables - pHeader->HetTableSize64 = pHeader->BetTablePos64 - pHeader->HetTablePos64; - pHeader->BetTableSize64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) - pHeader->HetTablePos64; - } - - // Compressed size of hash and block table - if(wFormatVersion >= MPQ_FORMAT_VERSION_2) - { - // Compressed size of the hash table - pHeader->HashTableSize64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); - - // Block and hi-block table - if(pHeader->HiBlockTablePos64) - { - pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64; - } - else - { - pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - pHeader->HiBlockTableSize64 = 0; - } - } - else - { - // No known MPQ in format 1.0 has any of the tables compressed - pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - pHeader->HiBlockTableSize64 = 0; - } - - // Set the data chunk size for MD5 to zero - pHeader->dwRawChunkSize = 0; - - // Fill the MD5's - memset(pHeader->MD5_BlockTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HashTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HiBlockTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_BetTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HetTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_MpqHeader, 0, MD5_DIGEST_SIZE); - // No break here !!!! - - case MPQ_FORMAT_VERSION_4: - - // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A' - // signature until the position of header MD5 at offset 0xC0 - if(!VerifyDataBlockHash(ha->pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, ha->pHeader->MD5_MpqHeader)) - nError = ERROR_FILE_CORRUPT; - break; - } - - return nError; -} - -//----------------------------------------------------------------------------- -// Default flags for (attributes) and (listfile) - -DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize) -{ - // Fixed for format 1.0 - if(ha->pHeader->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 and decrypting MPQ file data - -void EncryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwSeed1) -{ - LPDWORD block = (LPDWORD)pvFileBlock; - DWORD dwSeed2 = 0xEEEEEEEE; - DWORD ch; - - // Round to DWORDs - dwLength >>= 2; - - while(dwLength-- > 0) - { - dwSeed2 += StormBuffer[0x400 + (dwSeed1 & 0xFF)]; - ch = *block; - *block++ = ch ^ (dwSeed1 + dwSeed2); - - dwSeed1 = ((~dwSeed1 << 0x15) + 0x11111111) | (dwSeed1 >> 0x0B); - dwSeed2 = ch + dwSeed2 + (dwSeed2 << 5) + 3; - } -} - -void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwSeed1) -{ - LPDWORD block = (LPDWORD)pvFileBlock; - DWORD dwSeed2 = 0xEEEEEEEE; - DWORD ch; - - // Round to DWORDs - dwLength >>= 2; - - while(dwLength-- > 0) - { - dwSeed2 += StormBuffer[0x400 + (dwSeed1 & 0xFF)]; - ch = *block ^ (dwSeed1 + dwSeed2); - - dwSeed1 = ((~dwSeed1 << 0x15) + 0x11111111) | (dwSeed1 >> 0x0B); - dwSeed2 = ch + dwSeed2 + (dwSeed2 << 5) + 3; - *block++ = ch; - } -} - -/* -void EncryptMpqTable(void * pvMpqTable, DWORD dwLength, const char * szKey) -{ - EncryptMpqBlock(pvMpqTable, dwLength, HashString(szKey, MPQ_HASH_FILE_KEY)); -} - -void DecryptMpqTable(void * pvMpqTable, DWORD dwLength, const char * szKey) -{ - DecryptMpqBlock(pvMpqTable, dwLength, HashString(szKey, MPQ_HASH_FILE_KEY)); -} -*/ - -/** - * Functions tries to get file decryption key. The trick comes from sector - * positions which are stored at the begin of each compressed file. We know the - * file size, that means we know number of sectors that means we know the first - * DWORD value in sector position. And if we know encrypted and decrypted value, - * we can find the decryption key !!! - * - * hf - MPQ file handle - * SectorOffsets - DWORD array of sector positions - * ch - Decrypted value of the first sector pos - */ - -DWORD DetectFileKeyBySectorSize(LPDWORD SectorOffsets, DWORD decrypted) -{ - DWORD saveKey1; - DWORD temp = *SectorOffsets ^ decrypted; // temp = seed1 + seed2 - temp -= 0xEEEEEEEE; // temp = seed1 + StormBuffer[0x400 + (seed1 & 0xFF)] - - for(int i = 0; i < 0x100; i++) // Try all 255 possibilities - { - DWORD seed1; - DWORD seed2 = 0xEEEEEEEE; - DWORD ch; - - // Try the first DWORD (We exactly know the value) - seed1 = temp - StormBuffer[0x400 + i]; - seed2 += StormBuffer[0x400 + (seed1 & 0xFF)]; - ch = SectorOffsets[0] ^ (seed1 + seed2); - - if(ch != decrypted) - continue; - - // Add 1 because we are decrypting sector positions - saveKey1 = seed1 + 1; - - // If OK, continue and test the second value. We don't know exactly the value, - // but we know that the second one has lower 16 bits set to zero - // (no compressed sector is larger than 0xFFFF bytes) - seed1 = ((~seed1 << 0x15) + 0x11111111) | (seed1 >> 0x0B); - seed2 = ch + seed2 + (seed2 << 5) + 3; - - seed2 += StormBuffer[0x400 + (seed1 & 0xFF)]; - ch = SectorOffsets[1] ^ (seed1 + seed2); - - if((ch & 0xFFFF0000) == 0) - return saveKey1; - } - return 0; -} - -// Function tries to detect file encryption key. It expectes at least two uncompressed bytes -DWORD DetectFileKeyByKnownContent(void * pvFileContent, DWORD nDwords, ...) -{ - LPDWORD pdwContent = (LPDWORD)pvFileContent; - va_list argList; - DWORD dwDecrypted[0x10]; - DWORD saveKey1; - DWORD dwTemp; - DWORD i, j; - - // We need at least two DWORDS to detect the file key - if(nDwords < 0x02 || nDwords > 0x10) - return 0; - - va_start(argList, nDwords); - for(i = 0; i < nDwords; i++) - dwDecrypted[i] = va_arg(argList, DWORD); - va_end(argList); - - dwTemp = (*pdwContent ^ dwDecrypted[0]) - 0xEEEEEEEE; - for(i = 0; i < 0x100; i++) // Try all 256 possibilities - { - DWORD seed1; - DWORD seed2 = 0xEEEEEEEE; - DWORD ch; - - // Try the first DWORD - seed1 = dwTemp - StormBuffer[0x400 + i]; - seed2 += StormBuffer[0x400 + (seed1 & 0xFF)]; - ch = pdwContent[0] ^ (seed1 + seed2); - - if(ch != dwDecrypted[0]) - continue; - - saveKey1 = seed1; - - // If OK, continue and test all bytes. - for(j = 1; j < nDwords; j++) - { - seed1 = ((~seed1 << 0x15) + 0x11111111) | (seed1 >> 0x0B); - seed2 = ch + seed2 + (seed2 << 5) + 3; - - seed2 += StormBuffer[0x400 + (seed1 & 0xFF)]; - ch = pdwContent[j] ^ (seed1 + seed2); - - if(ch == dwDecrypted[j] && j == nDwords - 1) - return saveKey1; - } - } - return 0; -} - -DWORD DetectFileKeyByContent(void * pvFileContent, DWORD dwFileSize) -{ - DWORD dwFileKey; - - // Try to break the file encryption key as if it was a WAVE file - if(dwFileSize >= 0x0C) - { - dwFileKey = DetectFileKeyByKnownContent(pvFileContent, 3, 0x46464952, dwFileSize - 8, 0x45564157); - if(dwFileKey != 0) - return dwFileKey; - } - - // Try to break the encryption key as if it was an EXE file - if(dwFileSize > 0x40) - { - dwFileKey = DetectFileKeyByKnownContent(pvFileContent, 2, 0x00905A4D, 0x00000003); - if(dwFileKey != 0) - return dwFileKey; - } - - // Try to break the encryption key as if it was a XML file - if(dwFileSize > 0x04) - { - dwFileKey = DetectFileKeyByKnownContent(pvFileContent, 2, 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 = GetPlainFileNameA(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 - -bool IsValidMpqHandle(TMPQArchive * ha) -{ - if(ha == NULL) - return false; - if(ha->pHeader == NULL || ha->pHeader->dwID != ID_MPQ) - return false; - - return (bool)(ha->pHeader->dwID == ID_MPQ); -} - -bool IsValidFileHandle(TMPQFile * hf) -{ - if(hf == NULL) - return false; - - if(hf->dwMagic != ID_MPQ_FILE) - return false; - - if(hf->pStream != NULL) - return true; - - return IsValidMpqHandle(hf->ha); -} - -//----------------------------------------------------------------------------- -// Hash table and block table manipulation - -// Retrieves the first hash entry for the given file. -// Every locale version of a file has its own hash entry -TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) -{ - TMPQHash * pStartHash; // File hash entry (start) - TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash; // File hash entry (current) - DWORD dwHashTableSizeMask; - DWORD dwIndex = HashString(szFileName, MPQ_HASH_TABLE_INDEX); - DWORD dwName1 = HashString(szFileName, MPQ_HASH_NAME_A); - DWORD dwName2 = HashString(szFileName, MPQ_HASH_NAME_B); - - // Get the first possible has entry that might be the one - dwHashTableSizeMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - pStartHash = pHash = ha->pHashTable + (dwIndex & dwHashTableSizeMask); - - // There might be deleted entries in the hash table prior to our desired entry. - while(pHash->dwBlockIndex != HASH_ENTRY_FREE) - { - // If the entry agrees, we found it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize) - return pHash; - - // Move to the next hash entry. Stop searching - // if we got reached the original hash entry - if(++pHash >= pHashEnd) - pHash = ha->pHashTable; - if(pHash == pStartHash) - break; - } - - // The apropriate hash entry was not found - return NULL; -} - -TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash) -{ - TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash = pPrevHash; - DWORD dwName1 = pPrevHash->dwName1; - DWORD dwName2 = pPrevHash->dwName2; - - // Now go for any next entry that follows the pPrevHash, - // until either free hash entry was found, or the start entry was reached - for(;;) - { - // Move to the next hash entry. Stop searching - // if we got reached the original hash entry - if(++pHash >= pHashEnd) - pHash = ha->pHashTable; - if(pHash == pFirstHash) - break; - - // If the entry is a free entry, stop search - if(pHash->dwBlockIndex == HASH_ENTRY_FREE) - break; - - // If the entry is not free and the name agrees, we found it - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->pHeader->dwBlockTableSize) - return pHash; - } - - // No next entry - return NULL; -} - -// Allocates an entry in the hash table -DWORD AllocateHashEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) -{ - TMPQHash * pStartHash; // File hash entry (start) - TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash; // File hash entry (current) - DWORD dwHashTableSizeMask; - DWORD dwIndex = HashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX); - DWORD dwName1 = HashString(pFileEntry->szFileName, MPQ_HASH_NAME_A); - DWORD dwName2 = HashString(pFileEntry->szFileName, MPQ_HASH_NAME_B); - - // Get the first possible has entry that might be the one - dwHashTableSizeMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - pStartHash = pHash = ha->pHashTable + (dwIndex & dwHashTableSizeMask); - - // There might be deleted entries in the hash table prior to our desired entry. - while(pHash->dwBlockIndex < HASH_ENTRY_DELETED) - { - // If there already is an existing entry, reuse it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == pFileEntry->lcLocale) - break; - - // Move to the next hash entry. - // If we reached the starting entry, it's failure. - if(++pHash >= pHashEnd) - pHash = ha->pHashTable; - if(pHash == pStartHash) - return HASH_ENTRY_FREE; - } - - // Fill the free hash entry - pHash->dwName1 = dwName1; - pHash->dwName2 = dwName2; - pHash->lcLocale = pFileEntry->lcLocale; - pHash->wPlatform = pFileEntry->wPlatform; - pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); - - // Fill the hash index in the file entry - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - return pFileEntry->dwHashIndex; -} - -// 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. -void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos) -{ - TMPQHeader * pHeader = ha->pHeader; - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry = ha->pFileTable; - ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize; - DWORD dwChunkCount; - - // Parse the entire block table - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Only take existing files - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // 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 - if(pFreeSpacePos != NULL) - *pFreeSpacePos = FreeSpacePos; -} - -//----------------------------------------------------------------------------- -// Common functions - MPQ File - -TMPQFile * CreateMpqFile(TMPQArchive * ha) -{ - TMPQFile * hf; - - // Allocate space for TMPQFile - hf = STORM_ALLOC(TMPQFile, 1); - if(hf != NULL) - { - // Fill the file structure - memset(hf, 0, sizeof(TMPQFile)); - hf->ha = ha; - hf->pStream = NULL; - hf->dwMagic = ID_MPQ_FILE; - } - - return hf; -} - -// Loads a table from MPQ. -// Can be used for hash table, block table, sector offset table or sector checksum table -int LoadMpqTable( - TMPQArchive * ha, - ULONGLONG ByteOffset, - void * pvTable, - DWORD dwCompressedSize, - DWORD dwRealSize, - DWORD dwKey) -{ - LPBYTE pbCompressed = NULL; - LPBYTE pbToRead = (LPBYTE)pvTable; - int nError = ERROR_SUCCESS; - - // "interface.MPQ.part" in trial version of World of Warcraft - // has block table and hash table compressed. - if(dwCompressedSize < dwRealSize) - { - // Allocate temporary buffer for holding compressed data - pbCompressed = STORM_ALLOC(BYTE, dwCompressedSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Assign the temporary buffer as target for read operation - pbToRead = pbCompressed; - } - - // Read the table - if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize)) - { - // First of all, decrypt the table - if(dwKey != 0) - { - BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); - DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey); - BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); - } - - // If the table is compressed, decompress it - if(dwCompressedSize < dwRealSize) - { - int cbOutBuffer = (int)dwRealSize; - int cbInBuffer = (int)dwCompressedSize; - - if(!SCompDecompress2((char *)pvTable, &cbOutBuffer, (char *)pbCompressed, cbInBuffer)) - nError = GetLastError(); - - // Free the temporary buffer - STORM_FREE(pbCompressed); - } - } - else - { - nError = GetLastError(); - } - - BSWAP_ARRAY32_UNSIGNED(pvTable, dwRealSize); - return nError; -} - -void CalculateRawSectorOffset( - ULONGLONG & RawFilePos, - TMPQFile * hf, - DWORD dwSectorOffset) -{ - // - // Some MPQ protectors place the sector offset table after the actual file data. - // Sector offsets in the sector offset table are negative. When added - // to MPQ file offset from the block table entry, the result is a correct - // position of the file data in the MPQ. - // - // The position of sector table must be always within the MPQ, however. - // When a negative sector offset is found, we make sure that we make the addition - // just in 32-bits, and then add the MPQ offset. - // - - if(dwSectorOffset & 0x80000000) - { - RawFilePos = hf->ha->MpqPos + ((DWORD)hf->pFileEntry->ByteOffset + dwSectorOffset); - } - else - { - RawFilePos = hf->RawFilePos + dwSectorOffset; - } - - // We also have to add patch header size, if patch header is present - if(hf->pPatchInfo != NULL) - RawFilePos += hf->pPatchInfo->dwLength; -} - -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 = (TPatchInfo *)STORM_ALLOC(BYTE, dwLength); - 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_COMPRESSED) - { - __LoadSectorOffsets: - - // Allocate the sector offset table - hf->SectorOffsets = (DWORD *)STORM_ALLOC(BYTE, dwSectorOffsLen); - 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; - - if(hf->pPatchInfo != NULL) - 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, 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 - if((dwSectorOffset1 - dwSectorOffset0) > pFileEntry->dwCmpSize) - { - 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) - { - dwSectorOffsLen = hf->SectorOffsets[0]; - STORM_FREE(hf->SectorOffsets); - hf->SectorOffsets = NULL; - 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] == dwExpectedSize) - { - // 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; - - // Allocate the array for the sector checksums - hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount); - if(hf->SectorChksums == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // If we are not supposed to load it from the file, allocate empty buffer - if(bLoadFromFile == false) - { - memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD)); - return ERROR_SUCCESS; - } - - // Calculate offset of the CRC table - dwCrcSize = hf->dwSectorCount * sizeof(DWORD); - dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount]; - CalculateRawSectorOffset(RawFilePos, hf, dwCrcOffset); - - // Now read the table from the MPQ - return LoadMpqTable(ha, RawFilePos, hf->SectorChksums, dwCompressedSize, dwCrcSize, 0); - } - - // 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_COMPRESSED); - 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((char *)pbCompressed, &nOutSize, (char *)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 FreeMPQFile(TMPQFile *& hf) -{ - if(hf != NULL) - { - // If we have patch file attached to this one, free it first - if(hf->hfPatchFile != NULL) - FreeMPQFile(hf->hfPatchFile); - - // Then free all buffers allocated in the file structure - if(hf->pPatchHeader != NULL) - STORM_FREE(hf->pPatchHeader); - 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); - FileStream_Close(hf->pStream); - STORM_FREE(hf); - hf = NULL; - } -} - -// Frees the MPQ archive -void FreeMPQArchive(TMPQArchive *& ha) -{ - if(ha != NULL) - { - // First of all, free the patch archive, if any - if(ha->haPatch != NULL) - FreeMPQArchive(ha->haPatch); - - // 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->pBitmap != NULL) - STORM_FREE(ha->pBitmap); - if(ha->pHashTable != NULL) - STORM_FREE(ha->pHashTable); - if(ha->pHetTable != NULL) - FreeHetTable(ha->pHetTable); - STORM_FREE(ha); - ha = NULL; - } -} - -const char * GetPlainFileNameA(const char * szFileName) -{ - const char * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - -const TCHAR * GetPlainFileNameT(const TCHAR * szFileName) -{ - const TCHAR * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - -bool IsInternalMpqFileName(const char * szFileName) -{ - if(szFileName != NULL && szFileName[0] == '(') - { - 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) -{ - BYTE BitSummary = 0; - - // The MD5 is considered invalid of it is zeroed - BitSummary |= pbMd5[0x00] | pbMd5[0x01] | pbMd5[0x02] | pbMd5[0x03] | pbMd5[0x04] | pbMd5[0x05] | pbMd5[0x06] | pbMd5[0x07]; - BitSummary |= pbMd5[0x08] | pbMd5[0x09] | pbMd5[0x0A] | pbMd5[0x0B] | pbMd5[0x0C] | pbMd5[0x0D] | pbMd5[0x0E] | pbMd5[0x0F]; - return (BitSummary != 0); -} - -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 TMPQUserData structure -void ConvertTMPQUserData(void *userData) -{ - TMPQUserData * theData = (TMPQUserData *)userData; - - theData->dwID = SwapUInt32(theData->dwID); - theData->cbUserDataSize = SwapUInt32(theData->cbUserDataSize); - theData->dwHeaderOffs = SwapUInt32(theData->dwHeaderOffs); - theData->cbUserDataHeader = SwapUInt32(theData->cbUserDataHeader); -} - -// Swaps the TMPQHeader structure -void ConvertTMPQHeader(void *header) -{ - TMPQHeader * theHeader = (TMPQHeader *)header; - - theHeader->dwID = SwapUInt32(theHeader->dwID); - theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize); - theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize); - theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion); - theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize); - theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos); - theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos); - theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize); - theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize); - - if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) - { - // Swap the hi-block table position - theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64); - - theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi); - theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi); - - if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3) - { - theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64); - theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64); - theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64); - - if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4) - { - theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64); - theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64); - theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64); - theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64); - theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64); - } - } - } -} - -#endif // PLATFORM_LITTLE_ENDIAN |