diff options
author | LordJZ <a553r7fa1l3d@gmail.com> | 2012-06-05 04:17:10 +0400 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2012-07-03 17:16:30 +0200 |
commit | 459257b0563f3c085e5e1f3cb988309309377f46 (patch) | |
tree | e7d9c2bec0615172518039f0df19ac4b48115532 /dep/StormLib/src/SBaseFileTable.cpp | |
parent | 6fe1657fe98035d9a762d2e92f275826bec6d1fe (diff) |
Update StormLib
Diffstat (limited to 'dep/StormLib/src/SBaseFileTable.cpp')
-rw-r--r-- | dep/StormLib/src/SBaseFileTable.cpp | 611 |
1 files changed, 342 insertions, 269 deletions
diff --git a/dep/StormLib/src/SBaseFileTable.cpp b/dep/StormLib/src/SBaseFileTable.cpp index 83d31bbac94..09379ca5da7 100644 --- a/dep/StormLib/src/SBaseFileTable.cpp +++ b/dep/StormLib/src/SBaseFileTable.cpp @@ -15,7 +15,8 @@ //----------------------------------------------------------------------------- // Local defines -#define MAX_FLAG_INDEX 256 +#define INVALID_FLAG_VALUE 0xCCCCCCCC +#define MAX_FLAG_INDEX 512 //----------------------------------------------------------------------------- // Local structures @@ -62,63 +63,10 @@ typedef struct _BET_TABLE_HEADER //----------------------------------------------------------------------------- // Support for calculating bit sizes -static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) -{ - DWORD dwBitCount = 0; - - while(MaxValue > 0) - { - MaxValue >>= 1; - dwBitCount++; - } - - return dwBitCount; -} - -static BYTE GetFlagsComboIndex(DWORD dwFlags) +static void InitFileFlagArray(LPDWORD FlagArray) { - BYTE FlagComboIndex = 0; - - // - // We only use 8 different bits. This allows us to - // construct 256 unique values for every possible combination of MPQ file flags. - // - - if(dwFlags & MPQ_FILE_IMPLODE) - FlagComboIndex |= 0x01; - dwFlags &= ~MPQ_FILE_IMPLODE; - - if(dwFlags & MPQ_FILE_COMPRESS) - FlagComboIndex |= 0x02; - dwFlags &= ~MPQ_FILE_COMPRESS; - - if(dwFlags & MPQ_FILE_ENCRYPTED) - FlagComboIndex |= 0x04; - dwFlags &= ~MPQ_FILE_ENCRYPTED; - - if(dwFlags & MPQ_FILE_FIX_KEY) - FlagComboIndex |= 0x08; - dwFlags &= ~MPQ_FILE_FIX_KEY; - - if(dwFlags & MPQ_FILE_PATCH_FILE) - FlagComboIndex |= 0x10; - dwFlags &= ~MPQ_FILE_PATCH_FILE; - - if(dwFlags & MPQ_FILE_SINGLE_UNIT) - FlagComboIndex |= 0x20; - dwFlags &= ~MPQ_FILE_SINGLE_UNIT; - - if(dwFlags & MPQ_FILE_DELETE_MARKER) - FlagComboIndex |= 0x40; - dwFlags &= ~MPQ_FILE_DELETE_MARKER; - - if(dwFlags & MPQ_FILE_SECTOR_CRC) - FlagComboIndex |= 0x80; - dwFlags &= ~MPQ_FILE_SECTOR_CRC; - - // Sanity check - the flags must now be zero - assert((dwFlags & 0x7FFFFFFF) == 0); - return FlagComboIndex; + for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) + FlagArray[dwFlagIndex] = INVALID_FLAG_VALUE; } static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) @@ -126,7 +74,7 @@ static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) // Find free or equal entry in the flag array for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) { - if(FlagArray[dwFlagIndex] == 0 || FlagArray[dwFlagIndex] == dwFlags) + if(FlagArray[dwFlagIndex] == INVALID_FLAG_VALUE || FlagArray[dwFlagIndex] == dwFlags) { FlagArray[dwFlagIndex] = dwFlags; return dwFlagIndex; @@ -138,6 +86,19 @@ static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) return 0xFFFFFFFF; } +static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) +{ + DWORD dwBitCount = 0; + + while(MaxValue > 0) + { + MaxValue >>= 1; + dwBitCount++; + } + + return dwBitCount; +} + //----------------------------------------------------------------------------- // Support functions for BIT_ARRAY @@ -420,8 +381,7 @@ static TMPQBlock * TranslateBlockTable( BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize; for(DWORD i = 0; i < ha->dwFileTableSize; i++) { - if(pFileEntry->ByteOffset >> 32) - bNeedHiBlockTable = true; + bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false; pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset; pBlock->dwFSize = pFileEntry->dwFileSize; pBlock->dwCSize = pFileEntry->dwCmpSize; @@ -519,11 +479,15 @@ TMPQExtTable * LoadExtTable( int cbOutBuffer = (int)pCompressed->dwDataSize; int cbInBuffer = (int)Size; - // Decompress the XXX block + // Decompress the extended table pExtTable->dwSignature = pCompressed->dwSignature; pExtTable->dwVersion = pCompressed->dwVersion; pExtTable->dwDataSize = pCompressed->dwDataSize; - SCompDecompress((char *)(pExtTable + 1), &cbOutBuffer, (char *)(pCompressed + 1), cbInBuffer); + if(!SCompDecompress2((char *)(pExtTable + 1), &cbOutBuffer, (char *)(pCompressed + 1), cbInBuffer)) + { + STORM_FREE(pExtTable); + pExtTable = NULL; + } } // Free the compressed block @@ -537,9 +501,9 @@ TMPQExtTable * LoadExtTable( } // Used in MPQ Editor -void FreeExtTable(TMPQExtTable * pExtTable) +void FreeMpqBuffer(void * pvBuffer) { - STORM_FREE(pExtTable); + STORM_FREE(pvBuffer); } static int SaveMpqTable( @@ -1018,26 +982,18 @@ static void CreateBetHeader( TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; ULONGLONG MaxByteOffset = 0; + DWORD FlagArray[MAX_FLAG_INDEX]; + DWORD dwMaxFlagIndex = 0; DWORD dwMaxFileSize = 0; DWORD dwMaxCmpSize = 0; - DWORD dwFlagCount = 0; - BYTE FlagComboArray[MAX_FLAG_INDEX]; - BYTE FlagComboIndex; + DWORD dwFlagIndex; // Initialize array of flag combinations - memset(FlagComboArray, 0, sizeof(FlagComboArray)); + InitFileFlagArray(FlagArray); // Get the maximum values for the BET table for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - // We don't allow the file table to have free entries in the middle - // This must be a bug in the library somewhere. - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - pFileEntry->dwFlags = MPQ_FILE_EXISTS | MPQ_FILE_DELETE_MARKER; - assert(false); - } - // Highest file position in the MPQ if(pFileEntry->ByteOffset > MaxByteOffset) MaxByteOffset = pFileEntry->ByteOffset; @@ -1051,10 +1007,9 @@ static void CreateBetHeader( dwMaxCmpSize = pFileEntry->dwCmpSize; // Check if this flag was there before - FlagComboIndex = GetFlagsComboIndex(pFileEntry->dwFlags); - if(FlagComboArray[FlagComboIndex] == 0) - dwFlagCount++; - FlagComboArray[FlagComboIndex] = 1; + dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags); + if(dwFlagIndex > dwMaxFlagIndex) + dwMaxFlagIndex = dwFlagIndex; } // Now save bit count for every piece of file information @@ -1068,7 +1023,7 @@ static void CreateBetHeader( pBetHeader->dwBitCount_CmpSize = GetNecessaryBitCount(dwMaxCmpSize); pBetHeader->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_CmpSize + pBetHeader->dwBitCount_CmpSize; - pBetHeader->dwBitCount_FlagIndex = GetNecessaryBitCount(dwFlagCount); + pBetHeader->dwBitCount_FlagIndex = GetNecessaryBitCount(dwMaxFlagIndex + 1); pBetHeader->dwBitIndex_Unknown = pBetHeader->dwBitIndex_FlagIndex + pBetHeader->dwBitCount_FlagIndex; pBetHeader->dwBitCount_Unknown = 0; @@ -1082,7 +1037,7 @@ static void CreateBetHeader( // Save the file count and flag count pBetHeader->dwFileCount = ha->dwFileTableSize; - pBetHeader->dwFlagCount = dwFlagCount; + pBetHeader->dwFlagCount = dwMaxFlagIndex + 1; pBetHeader->dwUnknown08 = 0x10; // Save the total size of the BET hash @@ -1224,8 +1179,8 @@ TMPQExtTable * TranslateBetTable( DWORD i; // Calculate the bit sizes of various entries + InitFileFlagArray(FlagArray); CreateBetHeader(ha, &BetHeader); - memset(FlagArray, 0, sizeof(FlagArray)); // Calculate the size of the BET table BetTableSize = sizeof(BET_TABLE_HEADER) + @@ -1260,30 +1215,32 @@ TMPQExtTable * TranslateBetTable( // Construct the array of flag values and bit-based file table for(i = 0; i < BetHeader.dwFileCount; i++, pFileEntry++) { - // Only count files that exist - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // Save the byte offset - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FilePos, - BetHeader.dwBitCount_FilePos, - &pFileEntry->ByteOffset, - 8); - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FileSize, - BetHeader.dwBitCount_FileSize, - &pFileEntry->dwFileSize, - 4); - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_CmpSize, - BetHeader.dwBitCount_CmpSize, - &pFileEntry->dwCmpSize, - 4); - - // Get the flag array for the file - dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags); - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FlagIndex, - BetHeader.dwBitCount_FlagIndex, - &dwFlagIndex, - 4); - } + // + // Note: Blizzard MPQs contain valid values even for non-existant files + // (FilePos, FileSize, CmpSize and FlagIndex) + // Note: If flags is zero, it must be in the flag table too !!! + // + + // Save the byte offset + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FilePos, + BetHeader.dwBitCount_FilePos, + &pFileEntry->ByteOffset, + 8); + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FileSize, + BetHeader.dwBitCount_FileSize, + &pFileEntry->dwFileSize, + 4); + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_CmpSize, + BetHeader.dwBitCount_CmpSize, + &pFileEntry->dwCmpSize, + 4); + + // Save the flag index + dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags); + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FlagIndex, + BetHeader.dwBitCount_FlagIndex, + &dwFlagIndex, + 4); // Move the bit offset nBitOffset += BetHeader.dwTableEntrySize; @@ -1480,7 +1437,7 @@ void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) TFileEntry * FindFreeFileEntry(TMPQArchive * ha) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pDeletedEntry = NULL; + TFileEntry * pFreeEntry = NULL; TFileEntry * pFileEntry; // Try to find a free entry @@ -1488,18 +1445,22 @@ TFileEntry * FindFreeFileEntry(TMPQArchive * ha) { // If that entry is free, we reuse it if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - return pFileEntry; + { + pFreeEntry = pFileEntry; + break; + } - // If that entry is deleted, remember it - if(pFileEntry->dwFlags & MPQ_FILE_DELETE_MARKER) - pDeletedEntry = pFileEntry; + // + // Note: Files with "delete marker" are not deleted. + // Don't consider them free entries + // } // Do we have a deleted entry? - if(pDeletedEntry != NULL) + if(pFreeEntry != NULL) { - ClearFileEntry(ha, pDeletedEntry); - return pDeletedEntry; + ClearFileEntry(ha, pFreeEntry); + return pFreeEntry; } // If no file entry within the existing file table is free, @@ -1702,9 +1663,11 @@ void ClearFileEntry( 4); } - // Free the file name, and zero the entire entry + // Free the file name, and set the file entry as deleted if(pFileEntry->szFileName != NULL) STORM_FREE(pFileEntry->szFileName); + + // Invalidate the file entry memset(pFileEntry, 0, sizeof(TFileEntry)); } @@ -1718,7 +1681,7 @@ int FreeFileEntry( // // If we have HET table, we cannot just get rid of the file - // Doing so would lead to empty gaps in the HET and BET tables + // Doing so would lead to empty gaps in the HET table // We have to keep BET hash, hash index, HET index, locale, platform and file name // @@ -1750,14 +1713,10 @@ int FreeFileEntry( } else { - memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE); - pFileEntry->ByteOffset = 0; - pFileEntry->FileTime = 0; - pFileEntry->dwFileSize = 0; - pFileEntry->dwCmpSize = 0; - pFileEntry->dwFlags = MPQ_FILE_EXISTS | MPQ_FILE_DELETE_MARKER; - pFileEntry->dwCrc32 = 0; - nError = ERROR_MARKED_FOR_DELETE; + // Note: Deleted entries in Blizzard MPQs version 4.0 + // normally contain valid byte offset and length + pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; + nError = ERROR_SUCCESS; } return nError; @@ -1785,49 +1744,104 @@ void InvalidateInternalFiles(TMPQArchive * ha) ha->dwFlags |= MPQ_FLAG_INV_ATTRIBUTES; } - // Remember that the MPQ has been changed and it will ne necessary + // Remember that the MPQ has been changed and it will be necessary // to update the tables ha->dwFlags |= MPQ_FLAG_CHANGED; } //----------------------------------------------------------------------------- -// Support for file tables - hash table, block table, hi-block table, -// (attributes) and (listfile) +// Functions that loads and verify MPQ data bitmap -static void FixBlockTableSize( - TMPQArchive * ha, - TMPQBlock * pBlockTable, - DWORD dwClaimedSize) +int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComplete) { - TMPQHeader * pHeader = ha->pHeader; - ULONGLONG BlockTableStart; - ULONGLONG BlockTableEnd; - ULONGLONG FileDataStart; + TMPQBitmap * pBitmap = NULL; + TMPQBitmap DataBitmap; + ULONGLONG BitmapOffset; + ULONGLONG EndOfMpq; + DWORD DataBlockCount = 0; + DWORD BitmapByteSize; + DWORD WholeByteCount; + DWORD ExtraBitsCount; + + // Is there enough space for a MPQ bitmap? + EndOfMpq = ha->MpqPos + ha->pHeader->ArchiveSize64; + FileSize = FileSize - sizeof(TMPQBitmap); + if(FileSize > EndOfMpq) + { + // Try to load the data bitmap from the end of the file + if(FileStream_Read(ha->pStream, &FileSize, &DataBitmap, sizeof(TMPQBitmap))) + { + // Is it a valid data bitmap? + BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&DataBitmap), sizeof(TMPQBitmap)); + if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE) + { + // We assume that MPQs with data bitmap begin at position 0 + assert(ha->MpqPos == 0); - // Only perform this check on MPQs version 1.0 - if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + // Calculate the number of extra bytes for data bitmap + DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / DataBitmap.dwBlockSize) + 1); + BitmapByteSize = ((DataBlockCount - 1) / 8) + 1; + + // Verify the data block size + BitmapOffset = ((ULONGLONG)DataBitmap.dwMapOffsetHi << 32) | DataBitmap.dwMapOffsetLo; + assert((DWORD)(FileSize - BitmapOffset) == BitmapByteSize); + + // Allocate space for the data bitmap + pBitmap = (TMPQBitmap *)STORM_ALLOC(BYTE, sizeof(TMPQBitmap) + BitmapByteSize); + if(pBitmap != NULL) + { + // Copy the bitmap header + memcpy(pBitmap, &DataBitmap, sizeof(TMPQBitmap)); + + // Read the remaining part + if(!FileStream_Read(ha->pStream, &BitmapOffset, (pBitmap + 1), BitmapByteSize)) + { + STORM_FREE(pBitmap); + pBitmap = NULL; + } + } + } + } + } + + // If the caller asks for file completeness, check it + if(pBitmap != NULL && pbFileIsComplete != NULL) { - // Calculate claimed block table begin and end - BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); + DWORD i; + bool bFileIsComplete = true; - for(DWORD i = 0; i < dwClaimedSize; i++) + // Calculate the number of whole bytes and extra bits of the bitmap + WholeByteCount = (DataBlockCount / 8); + ExtraBitsCount = (DataBlockCount & 7); + + // Verify the whole bytes - their value must be 0xFF + for(i = 0; i < WholeByteCount; i++) { - // If the block table end goes into that file, fix the block table end - FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; - if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) - { - dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); - BlockTableEnd = FileDataStart; - } + if(pbBitmap[i] != 0xFF) + bFileIsComplete = false; + } + + // If there are extra bits, calculate the mask + if(ExtraBitsCount != 0) + { + BYTE ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); + + if(pbBitmap[i] != ExpectedValue) + bFileIsComplete = false; } + + // Give the result to the caller + *pbFileIsComplete = bFileIsComplete; } - // Fix the block table size - pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); - pHeader->dwBlockTableSize = dwClaimedSize; + ha->pBitmap = pBitmap; + return ERROR_SUCCESS; } +//----------------------------------------------------------------------------- +// Support for file tables - hash table, block table, hi-block table + int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) { TMPQHash * pHashTable; @@ -1851,7 +1865,7 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) return ERROR_SUCCESS; } -int LoadHashTable(TMPQArchive * ha) +TMPQHash * LoadHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG ByteOffset; @@ -1862,17 +1876,17 @@ int LoadHashTable(TMPQArchive * ha) // If the MPQ has no hash table, do nothing if(pHeader->dwHashTablePos == 0 && pHeader->wHashTablePosHi == 0) - return ERROR_SUCCESS; + return NULL; // If the hash table size is zero, do nothing if(pHeader->dwHashTableSize == 0) - return ERROR_SUCCESS; + return NULL; // Allocate buffer for the hash table dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash); pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); if(pHashTable == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + return NULL; // Compressed size of the hash table dwCmpSize = (DWORD)pHeader->HashTableSize64; @@ -1891,17 +1905,108 @@ int LoadHashTable(TMPQArchive * ha) { STORM_FREE(pHashTable); pHashTable = NULL; - return nError; } - // Set the maximum file count to the size of the hash table - // In case there is HET table, we have to keep the file limit - if(ha->pHetTable == NULL) - ha->dwMaxFileCount = pHeader->dwHashTableSize; + // Return the hash table + return pHashTable; +} - // Store the hash table to the MPQ - ha->pHashTable = pHashTable; - return ERROR_SUCCESS; +static void FixBlockTableSize( + TMPQArchive * ha, + TMPQBlock * pBlockTable, + DWORD dwClaimedSize) +{ + TMPQHeader * pHeader = ha->pHeader; + ULONGLONG BlockTableStart; + ULONGLONG BlockTableEnd; + ULONGLONG FileDataStart; + + // Only perform this check on MPQs version 1.0 + if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + { + // Calculate claimed block table begin and end + BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + + for(DWORD i = 0; i < dwClaimedSize; i++) + { + // If the block table end goes into that file, fix the block table end + FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; + if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) + { + dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); + BlockTableEnd = FileDataStart; + } + } + } + + // Fix the block table size + pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); + pHeader->dwBlockTableSize = dwClaimedSize; +} + +TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPQBlock * pBlockTable; + ULONGLONG ByteOffset; + DWORD dwTableSize; + DWORD dwCmpSize; + int nError; + + // Do nothing if the block table position is zero + if(pHeader->dwBlockTablePos == 0 && pHeader->wBlockTablePosHi == 0) + return NULL; + + // Do nothing if the block table size is zero + if(pHeader->dwBlockTableSize == 0) + return NULL; + + // Sanity check, enforced by LoadAnyHashTable + assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); + + // Calculate sizes of both tables + ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + dwCmpSize = (DWORD)pHeader->BlockTableSize64; + + // Allocate space for the block table + // Note: pHeader->dwBlockTableSize can be zero !!! + pBlockTable = STORM_ALLOC(TMPQBlock, ha->dwMaxFileCount); + if(pBlockTable == NULL) + return NULL; + + // Fill the block table with zeros + memset(pBlockTable, 0, dwTableSize); + + // I found a MPQ which claimed 0x200 entries in the block table, + // but the file was cut and there was only 0x1A0 entries. + // We will handle this case properly. + if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize) + { + pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock)); + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + } + + // + // One of the first cracked versions of Diablo I had block table unencrypted + // StormLib does NOT support such MPQs anymore, as they are incompatible + // with compressed block table feature + // + + // Load the block table + nError = LoadMpqTable(ha, ByteOffset, pBlockTable, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); + if(nError != ERROR_SUCCESS) + { + // Failed, sorry + STORM_FREE(pBlockTable); + return NULL; + } + + // Defense against MPQs that claim block table to be bigger than it really is + FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); + return pBlockTable; } int LoadHetTable(TMPQArchive * ha) @@ -1963,19 +2068,25 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha) int LoadAnyHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; - bool bHashTableLoaded = false; // If the MPQ archive is empty, don't bother trying to load anything if(pHeader->dwHashTableSize == 0 && pHeader->HetTableSize64 == 0) return CreateHashTable(ha, HASH_TABLE_SIZE_DEFAULT); - // Try to load HET table - if(LoadHetTable(ha) == ERROR_SUCCESS) - bHashTableLoaded = true; + // Try to load HET and/or classic hash table + LoadHetTable(ha); - // Try to load the classic hash table - if(LoadHashTable(ha) == ERROR_SUCCESS) - bHashTableLoaded = true; + // Load the HASH table + ha->pHashTable = LoadHashTable(ha); + + // Set the maximum file count to the size of the hash table + // In case there is HET table, we have to keep the file limit + if(ha->pHetTable == NULL) + ha->dwMaxFileCount = pHeader->dwHashTableSize; + + // Did at least one succeed? + if(ha->pHetTable == NULL && ha->pHashTable == NULL) + return ERROR_FILE_CORRUPT; // In theory, a MPQ could have bigger block table than hash table if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount) @@ -1984,7 +2095,7 @@ int LoadAnyHashTable(TMPQArchive * ha) ha->dwFlags |= MPQ_FLAG_READ_ONLY; } - return bHashTableLoaded ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; + return ERROR_SUCCESS; } int BuildFileTable_Classic( @@ -2001,118 +2112,80 @@ int BuildFileTable_Classic( // Sanity checks assert(ha->pHashTable != NULL); - // Do nothing if the size of the block table is zero - if(pHeader->dwBlockTablePos != 0 && pHeader->dwBlockTableSize != 0) + // Load the block table + pBlockTable = LoadBlockTable(ha, FileSize); + if(pBlockTable != NULL) { - // Sanity check, enforced by LoadAnyHashTable - assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); + TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; + TMPQHash * pHash; - // Allocate space for the block table - // Note: pHeader->dwBlockTableSize can be zero !!! - pBlockTable = STORM_ALLOC(TMPQBlock, ha->dwMaxFileCount); - if(pBlockTable != NULL) + // If we don't have HET table, we build the file entries from the hash&block tables + if(ha->pHetTable == NULL) { - ULONGLONG ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; - TMPQHash * pHash; - DWORD dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - DWORD dwCmpSize = (DWORD)pHeader->BlockTableSize64; - - // Fill the block table with zeros - memset(pBlockTable, 0, dwTableSize); - - // I have found a MPQ which claimed 0x200 entries in the block table, - // but the file was cut and there was only 0x1A0 entries. - // We will handle this case properly. - if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize) + for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) { - pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock)); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - } - - // - // One of the first cracked versions of Diablo I had block table unencrypted - // StormLib does NOT support such MPQs anymore, as they are incompatible - // with compressed block table feature - // - - // Load the block table - nError = LoadMpqTable(ha, ByteOffset, pBlockTable, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - if(nError == ERROR_SUCCESS) - { - // Defense against MPQs that claim block table to be bigger than it really is - FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); - - // If we don't have HET table, we build the file entries from the hash&block tables - if(ha->pHetTable == NULL) + if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + pFileEntry = pFileTable + pHash->dwBlockIndex; + pBlock = pBlockTable + pHash->dwBlockIndex; + + // + // Yet another silly map protector: For each valid file, + // there are 4 items in the hash table, that appears to be valid: + // + // a6d79af0 e61a0932 001e0000 0000770b <== Fake valid + // a6d79af0 e61a0932 0000d761 0000dacb <== Fake valid + // a6d79af0 e61a0932 00000000 0000002f <== Real file entry + // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid + // + + if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) { - if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - pBlock = pBlockTable + pHash->dwBlockIndex; - - // - // Yet another silly map protector: For each valid file, - // there are 4 items in the hash table, that appears to be valid: - // - // a6d79af0 e61a0932 001e0000 0000770b <== Fake valid - // a6d79af0 e61a0932 0000d761 0000dacb <== Fake valid - // a6d79af0 e61a0932 00000000 0000002f <== Real file entry - // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid - // - - if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) - { - // Fill the entry - pFileEntry->ByteOffset = pBlock->dwFilePos; - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->dwFileSize = pBlock->dwFSize; - pFileEntry->dwCmpSize = pBlock->dwCSize; - pFileEntry->dwFlags = pBlock->dwFlags; - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - else - { - // If the hash table entry doesn't point to the valid file item, - // we invalidate the entire hash table entry - pHash->dwName1 = 0xFFFFFFFF; - pHash->dwName2 = 0xFFFFFFFF; - pHash->lcLocale = 0xFFFF; - pHash->wPlatform = 0xFFFF; - pHash->dwBlockIndex = HASH_ENTRY_DELETED; - } - } + // Fill the entry + pFileEntry->ByteOffset = pBlock->dwFilePos; + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); + pFileEntry->dwFileSize = pBlock->dwFSize; + pFileEntry->dwCmpSize = pBlock->dwCSize; + pFileEntry->dwFlags = pBlock->dwFlags; + pFileEntry->lcLocale = pHash->lcLocale; + pFileEntry->wPlatform = pHash->wPlatform; } - } - else - { - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + else { - if(pHash->dwBlockIndex < ha->dwFileTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - } + // If the hash table entry doesn't point to the valid file item, + // we invalidate the entire hash table entry + pHash->dwName1 = 0xFFFFFFFF; + pHash->dwName2 = 0xFFFFFFFF; + pHash->lcLocale = 0xFFFF; + pHash->wPlatform = 0xFFFF; + pHash->dwBlockIndex = HASH_ENTRY_DELETED; } } } - - // Free the block table - STORM_FREE(pBlockTable); } else { - nError = ERROR_NOT_ENOUGH_MEMORY; + for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + { + if(pHash->dwBlockIndex < ha->dwFileTableSize) + { + pFileEntry = pFileTable + pHash->dwBlockIndex; + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); + pFileEntry->lcLocale = pHash->lcLocale; + pFileEntry->wPlatform = pHash->wPlatform; + } + } + } } + + // Free the block table + STORM_FREE(pBlockTable); + } + else + { + nError = ERROR_NOT_ENOUGH_MEMORY; } // Load the hi-block table |