diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:59:00 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:59:00 +0100 |
commit | c34c37b3418f1e5ab3678ce65d46f81803dec91d (patch) | |
tree | 4a9cf4c61634691981f9dc367b53dac4070f8d0d /src/SBaseFileTable.cpp | |
parent | ff0c25952a28a927c48738ab5207b9bda69e588a (diff) |
+ StormLib 9.0 BETA
Diffstat (limited to 'src/SBaseFileTable.cpp')
-rw-r--r-- | src/SBaseFileTable.cpp | 1710 |
1 files changed, 990 insertions, 720 deletions
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 51d01e4..6b4eada 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -19,54 +19,11 @@ #define MAX_FLAG_INDEX 512 //----------------------------------------------------------------------------- -// Local structures - -// Structure for HET table header -typedef struct _HET_TABLE_HEADER -{ - DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes) - DWORD dwFileCount; // Number of occupied entries in the hash table - DWORD dwHashTableSize; // Size of the hash table (in bytes) - DWORD dwHashEntrySize; // Effective size of the hash entry (in bits) - DWORD dwIndexSizeTotal; // Total size of file index (in bits) - DWORD dwIndexSizeExtra; // Extra bits in the file index - DWORD dwIndexSize; // Effective size of the file index (in bits) - DWORD dwIndexTableSize; // Size of the block index subtable (in bytes) - -} HET_TABLE_HEADER, *PHET_TABLE_HEADER; - -// Structure for BET table header -typedef struct _BET_TABLE_HEADER -{ - DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes) - DWORD dwFileCount; // Number of files in the BET table - DWORD dwUnknown08; - DWORD dwTableEntrySize; // Size of one table entry (in bits) - DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record) - DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record) - DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record) - DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record) - DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record) - DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record) - DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record) - DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record) - DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record) - DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record) - DWORD dwBetHashSizeTotal; // Total size of the BET hash - DWORD dwBetHashSizeExtra; // Extra bits in the BET hash - DWORD dwBetHashSize; // Effective size of BET hash (in bits) - DWORD dwBetHashArraySize; // Size of BET hashes array, in bytes - DWORD dwFlagCount; // Number of flags in the following array - -} BET_TABLE_HEADER, *PBET_TABLE_HEADER; - -//----------------------------------------------------------------------------- // Support for calculating bit sizes static void InitFileFlagArray(LPDWORD FlagArray) { - for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) - FlagArray[dwFlagIndex] = INVALID_FLAG_VALUE; + memset(FlagArray, 0xCC, MAX_FLAG_INDEX * sizeof(DWORD)); } static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) @@ -99,6 +56,19 @@ static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) return dwBitCount; } +static int CompareFilePositions(const void * p1, const void * p2) +{ + TMPQBlock * pBlock1 = *(TMPQBlock **)p1; + TMPQBlock * pBlock2 = *(TMPQBlock **)p2; + + if(pBlock1->dwFilePos < pBlock2->dwFilePos) + return -1; + if(pBlock1->dwFilePos > pBlock2->dwFilePos) + return +1; + + return 0; +} + //----------------------------------------------------------------------------- // Support functions for BIT_ARRAY @@ -116,6 +86,7 @@ static TBitArray * CreateBitArray( if(pBitArray != NULL) { memset(pBitArray, FillValue, nSize); + pBitArray->NumberOfBytes = (NumberOfBits + 7) / 8; pBitArray->NumberOfBits = NumberOfBits; } @@ -255,6 +226,242 @@ void SetBits( } } +//----------------------------------------------------------------------------- +// Support for MPQ header + +static DWORD GetArchiveSize32(TMPQArchive * ha, TMPQBlock * pBlockTable, DWORD dwBlockTableSize) +{ + TMPQHeader * pHeader = ha->pHeader; + ULONGLONG FileSize = 0; + DWORD dwArchiveSize = pHeader->dwHeaderSize; + DWORD dwByteOffset; + DWORD dwBlockIndex; + + // Increment by hash table size + dwByteOffset = pHeader->dwHashTablePos + (pHeader->dwHashTableSize * sizeof(TMPQHash)); + if(dwByteOffset > dwArchiveSize) + dwArchiveSize = dwByteOffset; + + // Increment by block table size + dwByteOffset = pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + if(dwByteOffset > dwArchiveSize) + dwArchiveSize = dwByteOffset; + + // If any of the MPQ files is beyond the hash table/block table, set the end to the file size + for(dwBlockIndex = 0; dwBlockIndex < dwBlockTableSize; dwBlockIndex++) + { + // Only count files that exists + if(pBlockTable[dwBlockIndex].dwFlags & MPQ_FILE_EXISTS) + { + // If this file begins past the end of tables, + // assume that the hash/block table is not at the end of the archive + if(pBlockTable[dwBlockIndex].dwFilePos > dwArchiveSize) + { + FileStream_GetSize(ha->pStream, &FileSize); + dwArchiveSize = (DWORD)(FileSize - ha->MpqPos); + break; + } + } + } + + // Return what we found + return dwArchiveSize; +} + +// This function converts the MPQ header so it always looks like version 4 +static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader) +{ + ULONGLONG ArchiveSize = pHeader->dwHeaderSize; + ULONGLONG ByteOffset = pHeader->dwHeaderSize; + + // If there is HET table + if(pHeader->HetTablePos64 != 0) + { + ByteOffset = pHeader->HetTablePos64 + pHeader->HetTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is BET table + if(pHeader->BetTablePos64 != 0) + { + ByteOffset = pHeader->BetTablePos64 + pHeader->BetTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is hash table + if(pHeader->dwHashTablePos || pHeader->wHashTablePosHi) + { + ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + pHeader->HashTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is block table + if(pHeader->dwBlockTablePos || pHeader->wBlockTablePosHi) + { + ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + pHeader->BlockTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is hi-block table + if(pHeader->HiBlockTablePos64) + { + ByteOffset = pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + return ArchiveSize; +} + +int ConvertMpqHeaderToFormat4( + TMPQArchive * ha, + ULONGLONG FileSize, + DWORD dwFlags) +{ + TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; + ULONGLONG ByteOffset; + USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion); + int nError = ERROR_SUCCESS; + + // If version 1.0 is forced, then the format version is forced to be 1.0 + // 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 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1); + if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) + { + pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + + // + // Note: The value of "dwArchiveSize" member in the MPQ header + // is ignored by Storm.dll and can contain garbage value + // ("w3xmaster" protector). + // + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V1, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V1); + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + break; + + case MPQ_FORMAT_VERSION_2: + + // Check for malformed MPQ header version 2.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_2); + if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V2) + { + pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; + pHeader->HiBlockTablePos64 = 0; + pHeader->wHashTablePosHi = 0; + pHeader->wBlockTablePosHi = 0; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2); + if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) + pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); + if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + if(pHeader->HiBlockTablePos64) + pHeader->HiBlockTableSize64 = pHeader->dwBlockTableSize * sizeof(USHORT); + pHeader->ArchiveSize64 = GetArchiveSize64(pHeader); + break; + + 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 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3); + if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3) + { + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + pHeader->HetTablePos64 = 0; + pHeader->BetTablePos64 = 0; + } + + // + // We need to calculate the 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 + // + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3); + ByteOffset = pHeader->ArchiveSize64; + + // Size of the hi-block table + if(pHeader->HiBlockTablePos64) + { + pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64; + ByteOffset = pHeader->HiBlockTablePos64; + } + + // Size of the block table + if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) + { + pHeader->BlockTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + } + + // Size of the hash table + if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) + { + pHeader->HashTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + } + + // Size of the BET table + if(pHeader->BetTablePos64) + { + pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64; + ByteOffset = pHeader->BetTablePos64; + } + + // Size of the HET table + if(pHeader->HetTablePos64) + { + pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64; + ByteOffset = pHeader->HetTablePos64; + } + break; + + 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 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4); + if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader)) + nError = ERROR_FILE_CORRUPT; + break; + + default: + + // Last resort: Check if it's a War of the Immortal data file (SQP) + nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags); + break; + } + + return nError; +} //----------------------------------------------------------------------------- // Support for hash table @@ -339,14 +546,14 @@ static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, L return NULL; } -static int ConvertMpqBlockTable( +static int BuildFileTableFromBlockTable( TMPQArchive * ha, TFileEntry * pFileTable, TMPQBlock * pBlockTable) { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; - TMPQBlock * pMpqBlock; + TMPQBlock * pBlock; TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; TMPQHash * pHash; @@ -355,7 +562,7 @@ static int ConvertMpqBlockTable( if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { pFileEntry = pFileTable + pHash->dwBlockIndex; - pMpqBlock = pBlockTable + pHash->dwBlockIndex; + pBlock = pBlockTable + pHash->dwBlockIndex; // // Yet another silly map protector: For each valid file, @@ -367,14 +574,17 @@ static int ConvertMpqBlockTable( // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid // - if(!(pMpqBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pMpqBlock->dwFlags & MPQ_FILE_EXISTS)) + if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) { - // Fill the entry - pFileEntry->ByteOffset = pMpqBlock->dwFilePos; + // ByteOffset is only valid if file size is not zero + pFileEntry->ByteOffset = pBlock->dwFilePos; + if(pFileEntry->ByteOffset == 0 && pBlock->dwCSize == 0) + pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize; + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->dwFileSize = pMpqBlock->dwFSize; - pFileEntry->dwCmpSize = pMpqBlock->dwCSize; - pFileEntry->dwFlags = pMpqBlock->dwFlags; + pFileEntry->dwFileSize = pBlock->dwFSize; + pFileEntry->dwCmpSize = pBlock->dwCSize; + pFileEntry->dwFlags = pBlock->dwFlags; pFileEntry->lcLocale = pHash->lcLocale; pFileEntry->wPlatform = pHash->wPlatform; } @@ -394,6 +604,31 @@ static int ConvertMpqBlockTable( return ERROR_SUCCESS; } +static int UpdateFileTableFromHashTable( + TMPQArchive * ha, + TFileEntry * pFileTable) +{ + TFileEntry * pFileEntry; + TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; + TMPQHash * pHash; + + 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; + } + } + } + + return ERROR_SUCCESS; +} + static TMPQHash * TranslateHashTable( TMPQArchive * ha, ULONGLONG * pcbTableSize) @@ -419,7 +654,8 @@ static TMPQHash * TranslateHashTable( return pHashTable; } -static TMPQBlock * TranslateBlockTable( +// Also used in SFileGetFileInfo +TMPQBlock * TranslateBlockTable( TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable) @@ -427,8 +663,8 @@ static TMPQBlock * TranslateBlockTable( TFileEntry * pFileEntry = ha->pFileTable; TMPQBlock * pBlockTable; TMPQBlock * pBlock; + size_t NeedHiBlockTable = 0; size_t BlockTableSize; - bool bNeedHiBlockTable = false; // Allocate copy of the hash table pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, ha->dwFileTableSize); @@ -438,7 +674,7 @@ static TMPQBlock * TranslateBlockTable( BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize; for(DWORD i = 0; i < ha->dwFileTableSize; i++) { - bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false; + NeedHiBlockTable |= (pFileEntry->ByteOffset >> 32); pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset; pBlock->dwFSize = pFileEntry->dwFileSize; pBlock->dwCSize = pFileEntry->dwCmpSize; @@ -453,7 +689,7 @@ static TMPQBlock * TranslateBlockTable( *pcbTableSize = (ULONGLONG)BlockTableSize; if(pbNeedHiBlockTable != NULL) - *pbNeedHiBlockTable = bNeedHiBlockTable; + *pbNeedHiBlockTable = NeedHiBlockTable ? true : false; } return pBlockTable; @@ -488,21 +724,21 @@ static USHORT * TranslateHiBlockTable( //----------------------------------------------------------------------------- // General EXT table functions -TMPQExtTable * LoadExtTable( +TMPQExtHeader * LoadExtTable( TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey) { - TMPQExtTable * pCompressed = NULL; // Compressed table - TMPQExtTable * pExtTable = NULL; // Uncompressed table + TMPQExtHeader * pCompressed = NULL; // Compressed table + TMPQExtHeader * pExtTable = NULL; // Uncompressed table // Do nothing if the size is zero if(ByteOffset != 0 && Size != 0) { // Allocate size for the compressed table - pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, Size); + pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, Size); if(pExtTable != NULL) { // Load the table from the MPQ @@ -514,7 +750,7 @@ TMPQExtTable * LoadExtTable( } // Swap the ext table header - BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtTable)); + BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtHeader)); if(pExtTable->dwSignature != dwSignature) { STORM_FREE(pExtTable); @@ -523,14 +759,14 @@ TMPQExtTable * LoadExtTable( // Decrypt the block BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); - DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtTable)), dwKey); + DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtHeader)), dwKey); BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); // If the table is compressed, decompress it - if((pExtTable->dwDataSize + sizeof(TMPQExtTable)) > Size) + if((pExtTable->dwDataSize + sizeof(TMPQExtHeader)) > Size) { pCompressed = pExtTable; - pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + pCompressed->dwDataSize); + pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + pCompressed->dwDataSize); if(pExtTable != NULL) { int cbOutBuffer = (int)pCompressed->dwDataSize; @@ -557,12 +793,6 @@ TMPQExtTable * LoadExtTable( return pExtTable; } -// Used in MPQ Editor -void FreeMpqBuffer(void * pvBuffer) -{ - STORM_FREE(pvBuffer); -} - static int SaveMpqTable( TMPQArchive * ha, void * pMpqTable, @@ -630,7 +860,7 @@ static int SaveMpqTable( static int SaveExtTable( TMPQArchive * ha, - TMPQExtTable * pExtTable, + TMPQExtHeader * pExtTable, ULONGLONG ByteOffset, DWORD dwTableSize, unsigned char * md5, @@ -639,7 +869,7 @@ static int SaveExtTable( LPDWORD pcbTotalSize) { ULONGLONG FileOffset; - TMPQExtTable * pCompressed = NULL; + TMPQExtHeader * pCompressed = NULL; DWORD cbTotalSize = 0; int nError = ERROR_SUCCESS; @@ -650,7 +880,7 @@ static int SaveExtTable( int cbInBuffer = (int)dwTableSize; // Allocate extra space for compressed table - pCompressed = (TMPQExtTable *)STORM_ALLOC(BYTE, dwTableSize); + pCompressed = (TMPQExtHeader *)STORM_ALLOC(BYTE, dwTableSize); if(pCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; @@ -676,7 +906,7 @@ static int SaveExtTable( if(dwKey != 0) { BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); - EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtTable)), dwKey); + EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtHeader)), dwKey); BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); } @@ -719,125 +949,170 @@ static int SaveExtTable( static void CreateHetHeader( TMPQHetTable * pHetTable, - PHET_TABLE_HEADER pHetHeader) + TMPQHetHeader * pHetHeader) { - // Fill the BET header - pHetHeader->dwFileCount = pHetTable->dwFileCount; - pHetHeader->dwHashTableSize = pHetTable->dwHashTableSize; - pHetHeader->dwHashEntrySize = pHetTable->dwHashBitSize; - pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwHashTableSize); - pHetHeader->dwIndexSizeExtra = 0; - pHetHeader->dwIndexSize = pHetHeader->dwIndexSizeTotal; - pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwHashTableSize) + 7) / 8; + // Fill the common header + pHetHeader->ExtHdr.dwSignature = HET_TABLE_SIGNATURE; + pHetHeader->ExtHdr.dwVersion = 1; + pHetHeader->ExtHdr.dwDataSize = 0; + + // Fill the HET header + pHetHeader->dwEntryCount = pHetTable->dwEntryCount; + pHetHeader->dwTotalCount = pHetTable->dwTotalCount; + pHetHeader->dwNameHashBitSize = pHetTable->dwNameHashBitSize; + pHetHeader->dwIndexSizeTotal = pHetTable->dwIndexSizeTotal; + pHetHeader->dwIndexSizeExtra = pHetTable->dwIndexSizeExtra; + pHetHeader->dwIndexSize = pHetTable->dwIndexSize; + pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwTotalCount) + 7) / 8; // Calculate the total size needed for holding HET table - pHetHeader->dwTableSize = sizeof(HET_TABLE_HEADER) + - pHetHeader->dwHashTableSize + + pHetHeader->ExtHdr.dwDataSize = + pHetHeader->dwTableSize = sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize; } -TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty) +TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData) { TMPQHetTable * pHetTable; pHetTable = STORM_ALLOC(TMPQHetTable, 1); if(pHetTable != NULL) { - pHetTable->dwIndexSizeTotal = 0; - pHetTable->dwIndexSizeExtra = 0; - pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; - pHetTable->dwHashBitSize = dwHashBitSize; + // Zero the HET table + memset(pHetTable, 0, sizeof(TMPQHetTable)); - // If the hash table size is not entered, calculate an optimal - // hash table size as 4/3 of the current file count. - if(dwHashTableSize == 0) - { - dwHashTableSize = (dwFileCount * 4 / 3); - assert(dwFileCount != 0); - } + // Hash sizes less than 0x40 bits are not tested + assert(dwNameHashBitSize == 0x40); - // Store the hash table size and file count - pHetTable->dwHashTableSize = dwHashTableSize; - pHetTable->dwFileCount = dwFileCount; + // Calculate masks + pHetTable->AndMask64 = ((dwNameHashBitSize != 0x40) ? ((ULONGLONG)1 << dwNameHashBitSize) : 0) - 1; + pHetTable->OrMask64 = (ULONGLONG)1 << (dwNameHashBitSize - 1); - // Size of one index is calculated from hash table size - pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwHashTableSize); - pHetTable->dwIndexSizeExtra = 0; - pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; + // Store the HET table parameters + pHetTable->dwEntryCount = dwFileCount; + pHetTable->dwTotalCount = (dwFileCount * 4) / 3; + pHetTable->dwNameHashBitSize = dwNameHashBitSize; + pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwFileCount); + pHetTable->dwIndexSizeExtra = 0; + pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; - // Allocate hash table - pHetTable->pHetHashes = STORM_ALLOC(BYTE, pHetTable->dwHashTableSize); - if(pHetTable->pHetHashes != NULL) + // Allocate array of hashes + pHetTable->pNameHashes = STORM_ALLOC(BYTE, pHetTable->dwTotalCount); + if(pHetTable->pNameHashes != NULL) { - // Make sure that the HET hashes are initialized - memset(pHetTable->pHetHashes, 0, pHetTable->dwHashTableSize); + // Make sure the data are initialized + memset(pHetTable->pNameHashes, 0, pHetTable->dwTotalCount); - // If we shall create empty HET table, we have to allocate empty block index table as well - if(bCreateEmpty) - pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwHashTableSize * pHetTable->dwIndexSizeTotal, 0xFF); + // Allocate the bit array for file indexes + pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF); + if(pHetTable->pBetIndexes != NULL) + { + // Initialize the HET table from the source data (if given) + if(pbSrcData != NULL) + { + // Copy the name hashes + memcpy(pHetTable->pNameHashes, pbSrcData, pHetTable->dwTotalCount); - // Calculate masks - pHetTable->AndMask64 = 0; - if(dwHashBitSize != 0x40) - pHetTable->AndMask64 = (ULONGLONG)1 << dwHashBitSize; - pHetTable->AndMask64--; + // Copy the file indexes + memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + pHetTable->dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes); + } + + // Return the result HET table + return pHetTable; + } - pHetTable->OrMask64 = (ULONGLONG)1 << (dwHashBitSize - 1); + // Free the name hashes + STORM_FREE(pHetTable->pNameHashes); } - else + + STORM_FREE(pHetTable); + } + + // Failed + return NULL; +} + +static int InsertHetEntry(TMPQHetTable * pHetTable, ULONGLONG FileNameHash, DWORD dwFileIndex) +{ + DWORD StartIndex; + DWORD Index; + BYTE NameHash1; + + // Get the start index and the high 8 bits of the name hash + StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount); + NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8)); + + // Find a place where to put it + for(;;) + { + // Did we find a free HET entry? + if(pHetTable->pNameHashes[Index] == HET_ENTRY_FREE) { - STORM_FREE(pHetTable); - pHetTable = NULL; + // Set the entry in the name hash table + pHetTable->pNameHashes[Index] = NameHash1; + + // Set the entry in the file index table + SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, + pHetTable->dwIndexSize, + &dwFileIndex, + 4); + return ERROR_SUCCESS; } + + // Move to the next entry in the HET table + // If we came to the start index again, we are done + Index = (Index + 1) % pHetTable->dwEntryCount; + if(Index == StartIndex) + break; } - return pHetTable; + // No space in the HET table. Should never happen, + // because the HET table is created according to the number of files + assert(false); + return ERROR_DISK_FULL; } -static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable) +static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader) { - HET_TABLE_HEADER HetHeader; TMPQHetTable * pHetTable = NULL; - LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1); + LPBYTE pbSrcData = (LPBYTE)(pHetHeader + 1); // Sanity check - assert(pExtTable->dwSignature == HET_TABLE_SIGNATURE); - assert(pExtTable->dwVersion == 1); + assert(pHetHeader->ExtHdr.dwSignature == HET_TABLE_SIGNATURE); + assert(pHetHeader->ExtHdr.dwVersion == 1); // Verify size of the HET table - if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(HET_TABLE_HEADER)) + if(pHetHeader->ExtHdr.dwDataSize >= sizeof(TMPQHetHeader)) { - // Copy the table header in order to have it aligned and swapped - memcpy(&HetHeader, pbSrcData, sizeof(HET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(&HetHeader, sizeof(HET_TABLE_HEADER)); - pbSrcData += sizeof(HET_TABLE_HEADER); - // Verify the size of the table in the header - if(HetHeader.dwTableSize == pExtTable->dwDataSize) + if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize) { + // The size of the HET table must be sum of header, hash and index table size + assert((sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize) == pHetHeader->dwTableSize); + + // So far, all MPQs with HET Table have had total number of entries equal to 4/3 of file count + assert(((pHetHeader->dwEntryCount * 4) / 3) == pHetHeader->dwTotalCount); + + // The size of one index is predictable as well + assert(GetNecessaryBitCount(pHetHeader->dwEntryCount) == pHetHeader->dwIndexSizeTotal); + // The size of index table (in entries) is expected // to be the same like the hash table size (in bytes) - assert(((HetHeader.dwIndexTableSize * 8) / HetHeader.dwIndexSize) == HetHeader.dwHashTableSize); - + assert(((pHetHeader->dwTotalCount * pHetHeader->dwIndexSizeTotal) + 7) / 8 == pHetHeader->dwIndexTableSize); + // Create translated table - pHetTable = CreateHetTable(HetHeader.dwHashTableSize, HetHeader.dwFileCount, HetHeader.dwHashEntrySize, false); + pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwNameHashBitSize, pbSrcData); if(pHetTable != NULL) { - // Copy the hash table size, index size and extra bits from the HET header - pHetTable->dwHashTableSize = HetHeader.dwHashTableSize; - pHetTable->dwIndexSizeTotal = HetHeader.dwIndexSizeTotal; - pHetTable->dwIndexSizeExtra = HetHeader.dwIndexSizeExtra; - - // Fill the hash table - if(pHetTable->pHetHashes != NULL) - memcpy(pHetTable->pHetHashes, pbSrcData, pHetTable->dwHashTableSize); - pbSrcData += pHetTable->dwHashTableSize; - - // Copy the block index table - pHetTable->pBetIndexes = CreateBitArray(HetHeader.dwIndexTableSize * 8, 0xFF); - if(pHetTable->pBetIndexes != NULL) - memcpy(pHetTable->pBetIndexes->Elements, pbSrcData, HetHeader.dwIndexTableSize); - pbSrcData += HetHeader.dwIndexTableSize; + // Now the sizes in the hash table should be already set + assert(pHetTable->dwEntryCount == pHetHeader->dwEntryCount); + assert(pHetTable->dwTotalCount == pHetHeader->dwTotalCount); + assert(pHetTable->dwIndexSizeTotal == pHetHeader->dwIndexSizeTotal); + + // Copy the missing variables + pHetTable->dwIndexSizeExtra = pHetHeader->dwIndexSizeExtra; + pHetTable->dwIndexSize = pHetHeader->dwIndexSize; } } } @@ -845,90 +1120,78 @@ static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable) return pHetTable; } -static TMPQExtTable * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable) +static TMPQExtHeader * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable) { - TMPQExtTable * pExtTable = NULL; - HET_TABLE_HEADER HetHeader; + TMPQHetHeader * pHetHeader = NULL; + TMPQHetHeader HetHeader; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; - size_t HetTableSize; // Prepare header of the HET table CreateHetHeader(pHetTable, &HetHeader); - // Calculate the total size needed for holding the encrypted HET table - HetTableSize = HetHeader.dwTableSize; - // Allocate space for the linear table - pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + HetTableSize); + pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + HetHeader.dwTableSize); if(pbLinearTable != NULL) { - // Create the common ext table header - pExtTable = (TMPQExtTable *)pbLinearTable; - pExtTable->dwSignature = HET_TABLE_SIGNATURE; - pExtTable->dwVersion = 1; - pExtTable->dwDataSize = (DWORD)HetTableSize; - pbTrgData = (LPBYTE)(pExtTable + 1); + // Copy the table header + pHetHeader = (TMPQHetHeader *)pbLinearTable; + memcpy(pHetHeader, &HetHeader, sizeof(TMPQHetHeader)); + pbTrgData = (LPBYTE)(pHetHeader + 1); - // Copy the HET table header - memcpy(pbTrgData, &HetHeader, sizeof(HET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(HET_TABLE_HEADER)); - pbTrgData += sizeof(HET_TABLE_HEADER); - - // Copy the array of HET hashes - memcpy(pbTrgData, pHetTable->pHetHashes, pHetTable->dwHashTableSize); - pbTrgData += pHetTable->dwHashTableSize; + // Copy the array of name hashes + memcpy(pbTrgData, pHetTable->pNameHashes, pHetTable->dwTotalCount); + pbTrgData += pHetTable->dwTotalCount; // Copy the bit array of BET indexes memcpy(pbTrgData, pHetTable->pBetIndexes->Elements, HetHeader.dwIndexTableSize); - // Calculate the total size of the table, including the TMPQExtTable + // Calculate the total size of the table, including the TMPQExtHeader if(pcbHetTable != NULL) { - *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtTable) + HetTableSize); + *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + HetHeader.dwTableSize); } } - return pExtTable; + return &pHetHeader->ExtHdr; } DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) { TMPQHetTable * pHetTable = ha->pHetTable; ULONGLONG FileNameHash; - ULONGLONG AndMask64; - ULONGLONG OrMask64; - ULONGLONG BetHash; DWORD StartIndex; DWORD Index; - BYTE HetHash; // Upper 8 bits of the masked file name hash + BYTE NameHash1; // Upper 8 bits of the masked file name hash + + // If there are no entries in the HET table, do nothing + if(pHetTable->dwEntryCount == 0) + return HASH_ENTRY_FREE; // Do nothing if the MPQ has no HET table assert(ha->pHetTable != NULL); // Calculate 64-bit hash of the file name - AndMask64 = pHetTable->AndMask64; - OrMask64 = pHetTable->OrMask64; - FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64; + FileNameHash = (HashStringJenkins(szFileName) & pHetTable->AndMask64) | pHetTable->OrMask64; // Split the file name hash into two parts: - // Part 1: The highest 8 bits of the name hash - // Part 2: The rest of the name hash (without the highest 8 bits) - HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8)); - BetHash = FileNameHash & (AndMask64 >> 0x08); + // NameHash1: The highest 8 bits of the name hash + // NameHash2: File name hash limited to hash size + // Note: Our file table contains full name hash, no need to cut the high 8 bits before comparison + NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8)); // Calculate the starting index to the hash table - StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize); + StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount); // Go through HET table until we find a terminator - while(pHetTable->pHetHashes[Index] != HET_ENTRY_FREE) + while(pHetTable->pNameHashes[Index] != HET_ENTRY_FREE) { // Did we find a match ? - if(pHetTable->pHetHashes[Index] == HetHash) + if(pHetTable->pNameHashes[Index] == NameHash1) { DWORD dwFileIndex = 0; - // Get the index of the BetHash + // Get the file index GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, pHetTable->dwIndexSize, &dwFileIndex, @@ -940,14 +1203,14 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) // assert(dwFileIndex <= ha->dwFileTableSize); // - // Verify the BetHash against the entry in the table of BET hashes - if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash) + // Verify the FileNameHash against the entry in the table of name hashes + if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash) return dwFileIndex; } - // Move to the next entry in the primary search table + // Move to the next entry in the HET table // If we came to the start index again, we are done - Index = (Index + 1) % pHetTable->dwHashTableSize; + Index = (Index + 1) % pHetTable->dwEntryCount; if(Index == StartIndex) break; } @@ -956,103 +1219,12 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) return HASH_ENTRY_FREE; } -DWORD AllocateHetEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) -{ - TMPQHetTable * pHetTable = ha->pHetTable; - ULONGLONG FileNameHash; - ULONGLONG AndMask64; - ULONGLONG OrMask64; - ULONGLONG BetHash; - DWORD FileCountIncrement = 0; - DWORD FreeHetIndex = HASH_ENTRY_FREE; - DWORD dwFileIndex; - DWORD StartIndex; - DWORD Index; - BYTE HetHash; // Upper 8 bits of the masked file name hash - - // Do nothing if the MPQ has no HET table - assert(ha->pHetTable != NULL); - - // Calculate 64-bit hash of the file name - AndMask64 = pHetTable->AndMask64; - OrMask64 = pHetTable->OrMask64; - FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64; - - // Calculate the starting index to the hash table - StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize); - - // Split the file name hash into two parts: - // Part 1: The highest 8 bits of the name hash - // Part 2: The rest of the name hash (without the highest 8 bits) - HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8)); - BetHash = FileNameHash & (AndMask64 >> 0x08); - - // Go through HET table until we find a terminator - for(;;) - { - // Did we find a match ? - if(pHetTable->pHetHashes[Index] == HetHash) - { - DWORD dwFileIndex = 0; - - // Get the index of the BetHash - GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, - pHetTable->dwIndexSize, - &dwFileIndex, - 4); - // - // TODO: This condition only happens when we are opening a MPQ - // where some files were deleted by StormLib. Perhaps - // we should not allow shrinking of the file table in MPQs v 4.0? - // assert(dwFileIndex <= ha->dwFileTableSize); - // - - // Verify the BetHash against the entry in the table of BET hashes - if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash) - { - FreeHetIndex = Index; - break; - } - } - - // Check for entries that might have been deleted - if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED || pHetTable->pHetHashes[Index] == HET_ENTRY_FREE) - { - FileCountIncrement++; - FreeHetIndex = Index; - break; - } - - // Move to the next entry in the primary search table - // If we came to the start index again, we are done - Index = (Index + 1) % pHetTable->dwHashTableSize; - if(Index == StartIndex) - return HASH_ENTRY_FREE; - } - - // Fill the HET table entry - dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable); - pHetTable->pHetHashes[FreeHetIndex] = HetHash; - pHetTable->dwFileCount += FileCountIncrement; - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * FreeHetIndex, - pHetTable->dwIndexSize, - &dwFileIndex, - 4); - - // Fill the file entry - pFileEntry->BetHash = BetHash; - pFileEntry->dwHetIndex = FreeHetIndex; - return FreeHetIndex; -} - void FreeHetTable(TMPQHetTable * pHetTable) { if(pHetTable != NULL) { - if(pHetTable->pHetHashes != NULL) - STORM_FREE(pHetTable->pHetHashes); + if(pHetTable->pNameHashes != NULL) + STORM_FREE(pHetTable->pNameHashes); if(pHetTable->pBetIndexes != NULL) STORM_FREE(pHetTable->pBetIndexes); @@ -1065,7 +1237,7 @@ void FreeHetTable(TMPQHetTable * pHetTable) static void CreateBetHeader( TMPQArchive * ha, - PBET_TABLE_HEADER pBetHeader) + TMPQBetHeader * pBetHeader) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; @@ -1079,9 +1251,18 @@ static void CreateBetHeader( // Initialize array of flag combinations InitFileFlagArray(FlagArray); + // Fill the common header + pBetHeader->ExtHdr.dwSignature = BET_TABLE_SIGNATURE; + pBetHeader->ExtHdr.dwVersion = 1; + pBetHeader->ExtHdr.dwDataSize = 0; + // Get the maximum values for the BET table for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { + // + // Note: Deleted files must be counted as well + // + // Highest file position in the MPQ if(pFileEntry->ByteOffset > MaxByteOffset) MaxByteOffset = pFileEntry->ByteOffset; @@ -1124,24 +1305,25 @@ static void CreateBetHeader( pBetHeader->dwBitCount_Unknown; // Save the file count and flag count - pBetHeader->dwFileCount = ha->dwFileTableSize; + pBetHeader->dwEntryCount = ha->dwFileTableSize; pBetHeader->dwFlagCount = dwMaxFlagIndex + 1; pBetHeader->dwUnknown08 = 0x10; // Save the total size of the BET hash - pBetHeader->dwBetHashSizeTotal = ha->pHetTable->dwHashBitSize - 0x08; - pBetHeader->dwBetHashSizeExtra = 0; - pBetHeader->dwBetHashSize = pBetHeader->dwBetHashSizeTotal; - pBetHeader->dwBetHashArraySize = ((pBetHeader->dwBetHashSizeTotal * pBetHeader->dwFileCount) + 7) / 8; + pBetHeader->dwBitTotal_NameHash2 = ha->pHetTable->dwNameHashBitSize - 0x08; + pBetHeader->dwBitExtra_NameHash2 = 0; + pBetHeader->dwBitCount_NameHash2 = pBetHeader->dwBitTotal_NameHash2; + pBetHeader->dwNameHashArraySize = ((pBetHeader->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount) + 7) / 8; // Save the total table size - pBetHeader->dwTableSize = sizeof(BET_TABLE_HEADER) + + pBetHeader->ExtHdr.dwDataSize = + pBetHeader->dwTableSize = sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader) + pBetHeader->dwFlagCount * sizeof(DWORD) + - ((pBetHeader->dwTableEntrySize * pBetHeader->dwFileCount) + 7) / 8 + - pBetHeader->dwBetHashArraySize; + ((pBetHeader->dwTableEntrySize * pBetHeader->dwEntryCount) + 7) / 8 + + pBetHeader->dwNameHashArraySize; } -TMPQBetTable * CreateBetTable(DWORD dwFileCount) +TMPQBetTable * CreateBetTable(DWORD dwEntryCount) { TMPQBetTable * pBetTable; @@ -1150,7 +1332,7 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount) if(pBetTable != NULL) { memset(pBetTable, 0, sizeof(TMPQBetTable)); - pBetTable->dwFileCount = dwFileCount; + pBetTable->dwEntryCount = dwEntryCount; } return pBetTable; @@ -1158,90 +1340,87 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount) static TMPQBetTable * TranslateBetTable( TMPQArchive * ha, - TMPQExtTable * pExtTable) + TMPQBetHeader * pBetHeader) { - BET_TABLE_HEADER BetHeader; TMPQBetTable * pBetTable = NULL; - LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1); + LPBYTE pbSrcData = (LPBYTE)(pBetHeader + 1); DWORD LengthInBytes; // Sanity check - assert(pExtTable->dwSignature == BET_TABLE_SIGNATURE); - assert(pExtTable->dwVersion == 1); + assert(pBetHeader->ExtHdr.dwSignature == BET_TABLE_SIGNATURE); + assert(pBetHeader->ExtHdr.dwVersion == 1); assert(ha->pHetTable != NULL); ha = ha; // Verify size of the HET table - if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(BET_TABLE_HEADER)) + if(pBetHeader->ExtHdr.dwDataSize >= sizeof(TMPQBetHeader)) { - // Copy the table header in order to have it aligned and swapped - memcpy(&BetHeader, pbSrcData, sizeof(BET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(&BetHeader, sizeof(BET_TABLE_HEADER)); - pbSrcData += sizeof(BET_TABLE_HEADER); - - // Some MPQs affected by a bug in StormLib have pBetTable->dwFileCount - // greater than ha->dwMaxFileCount - if(BetHeader.dwFileCount > ha->dwMaxFileCount) - return NULL; - // Verify the size of the table in the header - if(BetHeader.dwTableSize == pExtTable->dwDataSize) + if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize) { + // The number of entries in the BET table must be the same like number of entries in the block table + assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize); + assert(pBetHeader->dwEntryCount <= ha->dwMaxFileCount); + + // The number of entries in the BET table must be the same like number of entries in the HET table + // Note that if it's not, it is not a problem + //assert(pBetHeader->dwEntryCount == ha->pHetTable->dwEntryCount); + // Create translated table - pBetTable = CreateBetTable(BetHeader.dwFileCount); + pBetTable = CreateBetTable(pBetHeader->dwEntryCount); if(pBetTable != NULL) { // Copy the variables from the header to the BetTable - pBetTable->dwTableEntrySize = BetHeader.dwTableEntrySize; - pBetTable->dwBitIndex_FilePos = BetHeader.dwBitIndex_FilePos; - pBetTable->dwBitIndex_FileSize = BetHeader.dwBitIndex_FileSize; - pBetTable->dwBitIndex_CmpSize = BetHeader.dwBitIndex_CmpSize; - pBetTable->dwBitIndex_FlagIndex = BetHeader.dwBitIndex_FlagIndex; - pBetTable->dwBitIndex_Unknown = BetHeader.dwBitIndex_Unknown; - pBetTable->dwBitCount_FilePos = BetHeader.dwBitCount_FilePos; - pBetTable->dwBitCount_FileSize = BetHeader.dwBitCount_FileSize; - pBetTable->dwBitCount_CmpSize = BetHeader.dwBitCount_CmpSize; - pBetTable->dwBitCount_FlagIndex = BetHeader.dwBitCount_FlagIndex; - pBetTable->dwBitCount_Unknown = BetHeader.dwBitCount_Unknown; - - // Since we don't know what the "unknown" is, we'll assert when it's nonzero + pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize; + pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos; + pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize; + pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize; + pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex; + pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown; + pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos; + pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize; + pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize; + pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex; + pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown; + + // Since we don't know what the "unknown" is, we'll assert when it's zero assert(pBetTable->dwBitCount_Unknown == 0); // Allocate array for flags - if(BetHeader.dwFlagCount != 0) + if(pBetHeader->dwFlagCount != 0) { // Allocate array for file flags and load it - pBetTable->pFileFlags = STORM_ALLOC(DWORD, BetHeader.dwFlagCount); + pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount); if(pBetTable->pFileFlags != NULL) { - LengthInBytes = BetHeader.dwFlagCount * sizeof(DWORD); + LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD); memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes); BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes); pbSrcData += LengthInBytes; } // Save the number of flags - pBetTable->dwFlagCount = BetHeader.dwFlagCount; + pBetTable->dwFlagCount = pBetHeader->dwFlagCount; } // Load the bit-based file table - pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * BetHeader.dwFileCount, 0); + pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0); LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8; if(pBetTable->pFileTable != NULL) memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes); pbSrcData += LengthInBytes; // Fill the sizes of BET hash - pBetTable->dwBetHashSizeTotal = BetHeader.dwBetHashSizeTotal; - pBetTable->dwBetHashSizeExtra = BetHeader.dwBetHashSizeExtra; - pBetTable->dwBetHashSize = BetHeader.dwBetHashSize; + pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2; + pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2; + pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2; // Create and load the array of BET hashes - pBetTable->pBetHashes = CreateBitArray(pBetTable->dwBetHashSizeTotal * BetHeader.dwFileCount, 0); - LengthInBytes = (pBetTable->pBetHashes->NumberOfBits + 7) / 8; - if(pBetTable->pBetHashes != NULL) - memcpy(pBetTable->pBetHashes->Elements, pbSrcData, LengthInBytes); - pbSrcData += BetHeader.dwBetHashArraySize; + pBetTable->pNameHashes = CreateBitArray(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0); + LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8; + if(pBetTable->pNameHashes != NULL) + memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes); + pbSrcData += pBetHeader->dwNameHashArraySize; // Dump both tables // DumpHetAndBetTable(ha->pHetTable, pBetTable); @@ -1252,63 +1431,47 @@ static TMPQBetTable * TranslateBetTable( return pBetTable; } -TMPQExtTable * TranslateBetTable( +TMPQExtHeader * TranslateBetTable( TMPQArchive * ha, ULONGLONG * pcbBetTable) { - TMPQExtTable * pExtTable = NULL; - BET_TABLE_HEADER BetHeader; + TMPQBetHeader * pBetHeader = NULL; + TMPQBetHeader BetHeader; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pFileEntry; TBitArray * pBitArray = NULL; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; - size_t BetTableSize; DWORD LengthInBytes; DWORD FlagArray[MAX_FLAG_INDEX]; - DWORD i; // Calculate the bit sizes of various entries InitFileFlagArray(FlagArray); CreateBetHeader(ha, &BetHeader); - // Calculate the size of the BET table - BetTableSize = sizeof(BET_TABLE_HEADER) + - BetHeader.dwFlagCount * sizeof(DWORD) + - ((BetHeader.dwTableEntrySize * BetHeader.dwFileCount) + 7) / 8 + - BetHeader.dwBetHashArraySize; - // Allocate space - pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + BetTableSize); + pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + BetHeader.dwTableSize); if(pbLinearTable != NULL) { - // Create the common ext table header - pExtTable = (TMPQExtTable *)pbLinearTable; - pExtTable->dwSignature = BET_TABLE_SIGNATURE; - pExtTable->dwVersion = 1; - pExtTable->dwDataSize = (DWORD)BetTableSize; - pbTrgData = (LPBYTE)(pExtTable + 1); - - // Copy the BET table header - memcpy(pbTrgData, &BetHeader, sizeof(BET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(BET_TABLE_HEADER)); - pbTrgData += sizeof(BET_TABLE_HEADER); + // Copy the BET header to the linear buffer + pBetHeader = (TMPQBetHeader *)pbLinearTable; + memcpy(pBetHeader, &BetHeader, sizeof(TMPQBetHeader)); + pbTrgData = (LPBYTE)(pBetHeader + 1); // Save the bit-based block table - pBitArray = CreateBitArray(BetHeader.dwFileCount * BetHeader.dwTableEntrySize, 0); + pBitArray = CreateBitArray(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0); if(pBitArray != NULL) { - TFileEntry * pFileEntry = ha->pFileTable; DWORD dwFlagIndex = 0; DWORD nBitOffset = 0; - // Construct the array of flag values and bit-based file table - for(i = 0; i < BetHeader.dwFileCount; i++, pFileEntry++) + // Construct the bit-based file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // - // 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 !!! + // Note: Missing files must be included as well // - + // Save the byte offset SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FilePos, BetHeader.dwBitCount_FilePos, @@ -1349,33 +1512,22 @@ TMPQExtTable * TranslateBetTable( STORM_FREE(pBitArray); } - // Create bit array for BET hashes - pBitArray = CreateBitArray(BetHeader.dwBetHashSizeTotal * BetHeader.dwFileCount, 0); + // Create bit array for name hashes + pBitArray = CreateBitArray(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0); if(pBitArray != NULL) { - TFileEntry * pFileEntry = ha->pFileTable; - ULONGLONG AndMask64 = ha->pHetTable->AndMask64; - ULONGLONG OrMask64 = ha->pHetTable->OrMask64; + DWORD dwFileIndex = 0; - for(i = 0; i < BetHeader.dwFileCount; i++) + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - ULONGLONG FileNameHash = 0; - - // Calculate 64-bit hash of the file name - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) - { - FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64; - FileNameHash = FileNameHash & (AndMask64 >> 0x08); - } - // Insert the name hash to the bit array - SetBits(pBitArray, BetHeader.dwBetHashSizeTotal * i, - BetHeader.dwBetHashSize, - &FileNameHash, + SetBits(pBitArray, BetHeader.dwBitTotal_NameHash2 * dwFileIndex, + BetHeader.dwBitCount_NameHash2, + &pFileEntry->FileNameHash, 8); - - // Move to the next file entry - pFileEntry++; + + assert(dwFileIndex < BetHeader.dwEntryCount); + dwFileIndex++; } // Write the array of BET hashes @@ -1390,11 +1542,11 @@ TMPQExtTable * TranslateBetTable( // Write the size of the BET table in the MPQ if(pcbBetTable != NULL) { - *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtTable) + BetTableSize); + *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + BetHeader.dwTableSize); } } - return pExtTable; + return &pBetHeader->ExtHdr; } void FreeBetTable(TMPQBetTable * pBetTable) @@ -1405,8 +1557,8 @@ void FreeBetTable(TMPQBetTable * pBetTable) STORM_FREE(pBetTable->pFileTable); if(pBetTable->pFileFlags != NULL) STORM_FREE(pBetTable->pFileFlags); - if(pBetTable->pBetHashes != NULL) - STORM_FREE(pBetTable->pBetHashes); + if(pBetTable->pNameHashes != NULL) + STORM_FREE(pBetTable->pNameHashes); STORM_FREE(pBetTable); } @@ -1498,7 +1650,7 @@ TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex) return NULL; } -void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) +void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName) { // Sanity check assert(pFileEntry != NULL); @@ -1518,129 +1670,104 @@ void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) if(pFileEntry->szFileName != NULL) strcpy(pFileEntry->szFileName, szFileName); } -} + // We also need to create the file name hash + if(ha->pHetTable != NULL) + { + ULONGLONG AndMask64 = ha->pHetTable->AndMask64; + ULONGLONG OrMask64 = ha->pHetTable->OrMask64; + + pFileEntry->FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64; + } +} -// Finds a free file entry. Does NOT increment table size. -TFileEntry * FindFreeFileEntry(TMPQArchive * ha) +TFileEntry * FindDeletedFileEntry(TMPQArchive * ha) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFreeEntry = NULL; TFileEntry * pFileEntry; - // Try to find a free entry + // Go through the entire file table and try to find a deleted entry for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - // If that entry is free, we reuse it - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - pFreeEntry = pFileEntry; - break; - } + // Return the entry that is within the current file table (i.e. a deleted file) + if(!(pFileEntry->dwFlags & MPQ_FILE_EXISTS)) + return pFileEntry; // // Note: Files with "delete marker" are not deleted. - // Don't consider them free entries + // Don't treat them as available // } - // Do we have a deleted entry? - if(pFreeEntry != NULL) - { - ClearFileEntry(ha, pFreeEntry); - return pFreeEntry; - } - - // If no file entry within the existing file table is free, - // we try the reserve space after current file table - if(ha->dwFileTableSize < ha->dwMaxFileCount) - return ha->pFileTable + ha->dwFileTableSize; - - // If we reached maximum file count, we cannot add more files to the MPQ - assert(ha->dwFileTableSize == ha->dwMaxFileCount); + // No deleted entries found return NULL; } - TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale) { TFileEntry * pFileEntry = NULL; TMPQHash * pHash; - DWORD dwHashIndex; - DWORD dwFileIndex; - bool bHashEntryExists = false; - bool bHetEntryExists = false; - // If the archive has classic hash table, we try to - // find the file in the hash table - if(ha->pHashTable != NULL) + // The entry in the hash table should not be there. + // This case must be handled by the caller + assert(ha->pHashTable == NULL || GetHashEntryExact(ha, szFileName, lcLocale) == NULL); + assert(ha->pHetTable == NULL || GetFileIndex_Het(ha, szFileName) == HASH_ENTRY_FREE); + + // If we are in the middle of saving listfile, we need to allocate + // the file entry at the end of the file table + if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0) { - // If the hash entry is already there, we reuse the file entry - pHash = GetHashEntryExact(ha, szFileName, lcLocale); - if(pHash != NULL) + // Attempt to find a deleted file entry in the file table. + // If it suceeds, we reuse that entry + pFileEntry = FindDeletedFileEntry(ha); + if(pFileEntry == NULL) { - pFileEntry = ha->pFileTable + pHash->dwBlockIndex; - bHashEntryExists = true; - } - } + // If there is no space in the file table, we are sorry + if((ha->dwFileTableSize + ha->dwReservedFiles) >= ha->dwMaxFileCount) + return NULL; - // If the archive has HET table, try to use it for - // finding the file - if(ha->pHetTable != NULL) - { - dwFileIndex = GetFileIndex_Het(ha, szFileName); - if(dwFileIndex != HASH_ENTRY_FREE) + // Invalidate the internal files. Then we need to search for a deleted entry again, + // because the previous call to InvalidateInternalFiles might have created some + InvalidateInternalFiles(ha); + + // Re-check for deleted entries + pFileEntry = FindDeletedFileEntry(ha); + if(pFileEntry == NULL) + { + // If there is still no deleted entry, we allocate an entry at the end of the file table + assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount); + pFileEntry = ha->pFileTable + ha->dwFileTableSize++; + } + } + else { - pFileEntry = ha->pFileTable + dwFileIndex; - bHetEntryExists = true; + // Invalidate the internal files + InvalidateInternalFiles(ha); } } - - // If still haven't found the file entry, we allocate new one - if(pFileEntry == NULL) + else { - pFileEntry = FindFreeFileEntry(ha); - if(pFileEntry == NULL) - return NULL; + // There should be at least one entry for that internal file + assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount); + pFileEntry = ha->pFileTable + ha->dwFileTableSize++; } - // Fill the rest of the file entry - pFileEntry->ByteOffset = 0; - pFileEntry->FileTime = 0; - pFileEntry->dwFileSize = 0; - pFileEntry->dwCmpSize = 0; - pFileEntry->dwFlags = 0; - pFileEntry->lcLocale = (USHORT)lcLocale; - pFileEntry->wPlatform = 0; - pFileEntry->dwCrc32 = 0; - memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE); - - // Allocate space for file name, if it's not there yet - AllocateFileName(pFileEntry, szFileName); - - // If the free file entry is at the end of the file table, - // we have to increment file table size - if(pFileEntry == ha->pFileTable + ha->dwFileTableSize) + // Did we find an usable file entry? + if(pFileEntry != NULL) { - assert(ha->dwFileTableSize < ha->dwMaxFileCount); - ha->pHeader->dwBlockTableSize++; - ha->dwFileTableSize++; - } + // Make sure that the entry is properly initialized + memset(pFileEntry, 0, sizeof(TFileEntry)); + pFileEntry->lcLocale = (USHORT)lcLocale; + AllocateFileName(ha, pFileEntry, szFileName); - // If the MPQ has hash table, we have to insert the new entry into the hash table - if(ha->pHashTable != NULL && bHashEntryExists == false) - { - pHash = AllocateHashEntry(ha, pFileEntry); - assert(pHash != NULL); - } - - // If the MPQ has HET table, we have to insert it to the HET table as well - if(ha->pHetTable != NULL && bHetEntryExists == false) - { - // TODO: Does HET table even support locales? - // Most probably, Blizzard gave up that silly idea long ago. - dwHashIndex = AllocateHetEntry(ha, pFileEntry); - assert(dwHashIndex != HASH_ENTRY_FREE); + // If the MPQ has hash table, we have to insert the new entry into the hash table + // We expect it to succeed because there must be a free hash entry if there is a free file entry + // Note: Don't bother with the HET table. It will be rebuilt anyway + if(ha->pHashTable != NULL) + { + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); + } } // Return the file entry @@ -1653,10 +1780,8 @@ int RenameFileEntry( const char * szNewFileName) { TMPQHash * pHash; - DWORD dwFileIndex; - int nError = ERROR_SUCCESS; - // If the MPQ has classic hash table, clear the entry there + // Mark the entry as deleted in the hash table if(ha->pHashTable != NULL) { assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); @@ -1666,23 +1791,10 @@ int RenameFileEntry( pHash->dwBlockIndex = HASH_ENTRY_DELETED; } - // If the MPQ has HET table, clear the entry there as well - if(ha->pHetTable != NULL) - { - TMPQHetTable * pHetTable = ha->pHetTable; - DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1; - - assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize); - - // Clear the entry in the HET hash array - pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED; - - // Set the BET index to invalid index - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex, - pHetTable->dwIndexSize, - &dwInvalidFileIndex, - 4); - } + // + // Note: Don't bother with the HET table. + // It will be rebuilt from scratch anyway + // // Free the old file name if(pFileEntry->szFileName != NULL) @@ -1690,157 +1802,131 @@ int RenameFileEntry( pFileEntry->szFileName = NULL; // Allocate new file name - AllocateFileName(pFileEntry, szNewFileName); + AllocateFileName(ha, pFileEntry, szNewFileName); // Now find a hash entry for the new file name if(ha->pHashTable != NULL) { // Try to find the hash table entry for the new file name - // Note: If this fails, we leave the MPQ in a corrupt state + // Note: Since we deleted one hash entry, this will always succeed pHash = AllocateHashEntry(ha, pFileEntry); - if(pHash == NULL) - nError = ERROR_FILE_CORRUPT; - } - - // If the archive has HET table, we have to allocate HET table for the file as well - // finding the file - if(ha->pHetTable != NULL) - { - dwFileIndex = AllocateHetEntry(ha, pFileEntry); - if(dwFileIndex == HASH_ENTRY_FREE) - nError = ERROR_FILE_CORRUPT; + assert(pHash != NULL); } // Invalidate the entries for (listfile) and (attributes) // After we are done with MPQ changes, we need to re-create them InvalidateInternalFiles(ha); - return nError; + return ERROR_SUCCESS; } -void ClearFileEntry( +void DeleteFileEntry( TMPQArchive * ha, TFileEntry * pFileEntry) { - TMPQHash * pHash = NULL; + TMPQHash * pHash; // If the MPQ has classic hash table, clear the entry there if(ha->pHashTable != NULL) { - assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); - - pHash = ha->pHashTable + pFileEntry->dwHashIndex; - memset(pHash, 0xFF, sizeof(TMPQHash)); - pHash->dwBlockIndex = HASH_ENTRY_DELETED; - } - - // If the MPQ has HET table, clear the entry there as well - if(ha->pHetTable != NULL) - { - TMPQHetTable * pHetTable = ha->pHetTable; - DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1; - - assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize); - - // Clear the entry in the HET hash array - pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED; - - // Set the BET index to invalid index - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex, - pHetTable->dwIndexSize, - &dwInvalidFileIndex, - 4); + // Only if the file entry is still an existing one + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + // We expect dwHashIndex to be within the hash table + pHash = ha->pHashTable + pFileEntry->dwHashIndex; + assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); + + // Set the hash table entry as deleted + pHash->dwName1 = 0xFFFFFFFF; + pHash->dwName2 = 0xFFFFFFFF; + pHash->lcLocale = 0xFFFF; + pHash->wPlatform = 0xFFFF; + pHash->dwBlockIndex = HASH_ENTRY_DELETED; + } } // Free the file name, and set the file entry as deleted if(pFileEntry->szFileName != NULL) STORM_FREE(pFileEntry->szFileName); + pFileEntry->szFileName = NULL; + + // + // Don't modify the HET table, because it gets recreated from scratch at every modification operation + // Don't decrement the number of entries in the file table + // Keep Byte Offset, file size and compressed size in the file table + // Also keep the CRC32 and MD5 in the file attributes + // Clear the file name hash + // Clear the MPQ_FILE_EXISTS bit. + // - // Invalidate the file entry - memset(pFileEntry, 0, sizeof(TFileEntry)); + pFileEntry->FileNameHash = 0; + pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; } -int FreeFileEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) +void InvalidateInternalFiles(TMPQArchive * ha) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pTempEntry; - int nError = ERROR_SUCCESS; - - // - // If we have HET table, we cannot just get rid of the file - // 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 - // + TFileEntry * pFileEntry; + DWORD dwReservedFiles = 0; - if(ha->pHetTable == NULL) + // Do nothing if we are in the middle of saving internal files + if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES)) { - TFileEntry * pLastFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; - TFileEntry * pLastUsedEntry = pLastFileEntry; - - // Zero the file entry - ClearFileEntry(ha, pFileEntry); + // + // We clear the file entries of (listfile) and (attributes) + // For each internal file cleared, we increment the number + // of reserved entries in the file table. + // - // Now there is a chance that we created a chunk of free - // file entries at the end of the file table. We check this - // and eventually free all deleted file entries at the end - for(pTempEntry = ha->pFileTable; pTempEntry < pFileTableEnd; pTempEntry++) + // Invalidate the (listfile), if not done yet + if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)) { - // Is that an occupied file entry? - if(pTempEntry->dwFlags & MPQ_FILE_EXISTS) - pLastUsedEntry = pTempEntry; - } + pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL); + if(pFileEntry != NULL) + { + DeleteFileEntry(ha, pFileEntry); + dwReservedFiles++; + } - // Can we free some entries at the end? - if(pLastUsedEntry < pLastFileEntry) - { - // Fix the size of the file table entry - ha->dwFileTableSize = (DWORD)(pLastUsedEntry - ha->pFileTable) + 1; - ha->pHeader->dwBlockTableSize = ha->dwFileTableSize; + ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID; } - } - else - { - // 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; -} + // Invalidate the (attributes), if not done yet + if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)) + { + pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); + if(pFileEntry != NULL) + { + DeleteFileEntry(ha, pFileEntry); + dwReservedFiles++; + } -void InvalidateInternalFiles(TMPQArchive * ha) -{ - TFileEntry * pFileEntry; + ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID; + } - // - // Note: We set the size of both (listfile) and (attributes) to zero. - // This causes allocating space for newly added files straight over - // (listfile)/(attributes), if these were the last ones in the MPQ - // + // If the internal files are at the end of the file table (they usually are), + // we want to free these 2 entries, so when new files are added, they get + // added to the freed entries and the internal files get added after that + if(ha->dwFileTableSize > 0 && dwReservedFiles != 0) + { + // Go backwards while there are free entries + for(pFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; pFileEntry >= ha->pFileTable; pFileEntry--) + { + // Stop searching if a file is present + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry++; + break; + } + } - // Invalidate the (listfile), if not done yet - if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)) - { - pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0; - ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID; - } + // Calculate the new file table size + ha->dwFileTableSize = (DWORD)(pFileEntry - ha->pFileTable); + ha->dwReservedFiles = dwReservedFiles; + } - // Invalidate the (attributes), if not done yet - if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)) - { - pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0; - ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID; + // Remember that the MPQ has been changed and it will be necessary + // to update the tables + ha->dwFlags |= MPQ_FLAG_CHANGED; } - - // Remember that the MPQ has been changed and it will be necessary - // to update the tables - ha->dwFlags |= MPQ_FLAG_CHANGED; } //----------------------------------------------------------------------------- @@ -1853,7 +1939,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl ULONGLONG BitmapOffset; ULONGLONG EndOfMpq; DWORD DataBlockCount = 0; - DWORD BitmapByteSize; + DWORD BitmapByteSize = 0; DWORD WholeByteCount; DWORD ExtraBitsCount; @@ -1865,7 +1951,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl { // Calculate the number of extra bytes for data bitmap DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / ha->pHeader->dwRawChunkSize) + 1); - BitmapByteSize = ((DataBlockCount - 1) / 8) + 1; + BitmapByteSize = ((DataBlockCount + 7) / 8); BitmapOffset = EndOfMpq + BitmapByteSize; // Try to load the data bitmap from the end of the file @@ -1876,7 +1962,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE) { // Several sanity checks to ensure integrity of the bitmap - assert(MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); + assert(ha->MpqPos + MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); assert(ha->pHeader->dwRawChunkSize == DataBitmap.dwBlockSize); // Allocate space for the data bitmap @@ -1928,6 +2014,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl *pbFileIsComplete = bFileIsComplete; } + ha->dwBitmapSize = sizeof(TMPQBitmap) + BitmapByteSize; ha->pBitmap = pBitmap; return ERROR_SUCCESS; } @@ -1943,6 +2030,10 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) assert((dwHashTableSize & (dwHashTableSize - 1)) == 0); assert(ha->pHashTable == NULL); + // If the required hash table size is zero, don't create anything + if(dwHashTableSize == 0) + dwHashTableSize = HASH_TABLE_SIZE_DEFAULT; + // Create the hash table pHashTable = STORM_ALLOC(TMPQHash, dwHashTableSize); if(pHashTable == NULL) @@ -1950,11 +2041,8 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) // Fill it memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash)); + ha->dwMaxFileCount = dwHashTableSize; ha->pHashTable = pHashTable; - - // Set the max file count, if needed - if(ha->pHetTable == NULL) - ha->dwMaxFileCount = dwHashTableSize; return ERROR_SUCCESS; } @@ -2004,53 +2092,124 @@ TMPQHash * LoadHashTable(TMPQArchive * ha) break; } - // Calculate mask value that will serve for calculating - // the hash table index from the MPQ_HASH_TABLE_INDEX hash - ha->dwHashIndexMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - // Return the hash table return pHashTable; } +// This function fixes the scenario then dwBlockTableSize +// is greater and goes into a MPQ file static void FixBlockTableSize( TMPQArchive * ha, - TMPQBlock * pBlockTable, - DWORD dwClaimedSize) + TMPQBlock * pBlockTable) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG BlockTableStart; ULONGLONG BlockTableEnd; ULONGLONG FileDataStart; + DWORD dwFixedTableSize = pHeader->dwBlockTableSize; + + // Only perform this check on MPQs version 1.0 + assert(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 < dwFixedTableSize; 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) + { + dwFixedTableSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); + BlockTableEnd = FileDataStart; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + } + + // If we found a mismatch in the block table size + if(dwFixedTableSize < pHeader->dwBlockTableSize) + { + // Fill the additional space with zeros + memset(pBlockTable + dwFixedTableSize, 0, (pHeader->dwBlockTableSize - dwFixedTableSize) * sizeof(TMPQBlock)); + + // Fix the block table size + pHeader->dwBlockTableSize = dwFixedTableSize; + pHeader->BlockTableSize64 = dwFixedTableSize * sizeof(TMPQBlock); + + // + // Note: We should recalculate the archive size in the header, + // (it might be invalid as well) but we don't care about it anyway + // + + // In theory, a MPQ could have bigger block table than hash table + assert(pHeader->dwBlockTableSize <= ha->dwMaxFileCount); + } +} + +// This function fixes the scenario then dwBlockTableSize +// is greater and goes into a MPQ file +static void FixCompressedFileSize( + TMPQArchive * ha, + TMPQBlock * pBlockTable) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPQBlock ** SortTable; + TMPQBlock * pBlockTableEnd = pBlockTable + pHeader->dwBlockTableSize; + TMPQBlock * pBlock; + size_t nElements = 0; + size_t nIndex; + DWORD dwArchiveSize; // Only perform this check on MPQs version 1.0 - if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1); + + // Allocate sort table for all entries + SortTable = STORM_ALLOC(TMPQBlock*, pHeader->dwBlockTableSize); + if(SortTable != NULL) { - // Calculate claimed block table begin and end - BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + // Calculate the end of the archive + dwArchiveSize = GetArchiveSize32(ha, pBlockTable, pHeader->dwBlockTableSize); + + // Put all blocks to a sort table + for(pBlock = pBlockTable; pBlock < pBlockTableEnd; pBlock++) + { + if(pBlock->dwFlags & MPQ_FILE_EXISTS) + SortTable[nElements++] = pBlock; + } - for(DWORD i = 0; i < dwClaimedSize; i++) + // Have we found at least one compressed + if(nElements > 0) { - // 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) + // Sort the table + qsort(SortTable, nElements, sizeof(TMPQBlock *), CompareFilePositions); + + // Walk the table and set all compressed sizes to they + // match the difference (dwFilePos1 - dwFilePos0) + for(nIndex = 0; nIndex < nElements - 1; nIndex++) { - dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); - BlockTableEnd = FileDataStart; + TMPQBlock * pBlock1 = SortTable[nIndex + 1]; + TMPQBlock * pBlock0 = SortTable[nIndex]; + + pBlock0->dwCSize = (pBlock->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (pBlock1->dwFilePos - pBlock0->dwFilePos) : pBlock0->dwFSize; } + + // Fix the last entry + pBlock = SortTable[nElements - 1]; + pBlock->dwCSize = dwArchiveSize - pBlock->dwFilePos; } - } - // Fix the block table size - pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); - pHeader->dwBlockTableSize = dwClaimedSize; + STORM_FREE(SortTable); + } } -TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) + +TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries) { TMPQHeader * pHeader = ha->pHeader; TMPQBlock * pBlockTable = NULL; ULONGLONG ByteOffset; + ULONGLONG FileSize; DWORD dwTableSize; DWORD dwCmpSize; @@ -2067,22 +2226,20 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) { case MPQ_SUBTYPE_MPQ: - // Sanity check, enforced by LoadAnyHashTable - assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); - // Calculate sizes of both tables ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); dwCmpSize = (DWORD)pHeader->BlockTableSize64; - // 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. + // Handle case when either the MPQ is cut in the middle of the block table + // or the MPQ is malformed so that block table size is greater than should be + FileStream_GetSize(ha->pStream, &FileSize); 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); + pHeader->BlockTableSize64 = FileSize - ByteOffset; + pHeader->dwBlockTableSize = (DWORD)(pHeader->BlockTableSize64 / sizeof(TMPQBlock)); + dwTableSize = dwCmpSize = (DWORD)pHeader->BlockTableSize64; + ha->dwFlags |= MPQ_FLAG_PROTECTED; } // @@ -2094,9 +2251,17 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) // If failed to load the block table, delete it pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - // Defense against MPQs that claim block table to be bigger than it really is - if(pBlockTable != NULL) - FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); + // De-protecting maps for Warcraft III + if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) + { + // Some protectors set the block table size bigger than really is + if(pBlockTable != NULL) + FixBlockTableSize(ha, pBlockTable); + + // Defense against protectors that set invalid compressed size + if((ha->dwFlags & MPQ_FLAG_PROTECTED) && (bDontFixEntries == false)) + FixCompressedFileSize(ha, pBlockTable); + } break; case MPQ_SUBTYPE_SQP: @@ -2115,8 +2280,8 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) TMPQHetTable * LoadHetTable(TMPQArchive * ha) { + TMPQExtHeader * pExtTable; TMPQHetTable * pHetTable = NULL; - TMPQExtTable * pExtTable; TMPQHeader * pHeader = ha->pHeader; // If the HET table position is not NULL, we expect @@ -2128,7 +2293,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) if(pExtTable != NULL) { // If loading HET table fails, we ignore the result. - pHetTable = TranslateHetTable(pExtTable); + pHetTable = TranslateHetTable((TMPQHetHeader *)pExtTable); STORM_FREE(pExtTable); } } @@ -2138,7 +2303,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) TMPQBetTable * LoadBetTable(TMPQArchive * ha) { - TMPQExtTable * pExtTable; + TMPQExtHeader * pExtTable; TMPQBetTable * pBetTable = NULL; TMPQHeader * pHeader = ha->pHeader; @@ -2152,7 +2317,7 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha) { // If succeeded, we translate the BET table // to more readable form - pBetTable = TranslateBetTable(ha, pExtTable); + pBetTable = TranslateBetTable(ha, (TMPQBetHeader *)pExtTable); STORM_FREE(pExtTable); } } @@ -2174,26 +2339,16 @@ int LoadAnyHashTable(TMPQArchive * ha) if(ha->pHetTable == NULL && ha->pHashTable == NULL) return ERROR_FILE_CORRUPT; - // Set the maximum file count - if(ha->pHetTable != NULL && ha->pHashTable != NULL) - ha->dwMaxFileCount = STORMLIB_MIN(ha->pHetTable->dwHashTableSize, pHeader->dwHashTableSize); - else - ha->dwMaxFileCount = (ha->pHetTable != NULL) ? ha->pHetTable->dwHashTableSize : pHeader->dwHashTableSize; - - // In theory, a MPQ could have bigger block table than hash table - if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount) - { - ha->dwMaxFileCount = ha->pHeader->dwBlockTableSize; - ha->dwFlags |= MPQ_FLAG_READ_ONLY; - } - + // Set the maximum file count to the size of the hash table. + // Note: We don't care about HET table limits, because HET table is rebuilt + // after each file add/rename/delete. + ha->dwMaxFileCount = (ha->pHashTable != NULL) ? pHeader->dwHashTableSize : HASH_TABLE_SIZE_MAX; return ERROR_SUCCESS; } int BuildFileTable_Classic( TMPQArchive * ha, - TFileEntry * pFileTable, - ULONGLONG FileSize) + TFileEntry * pFileTable) { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; @@ -2203,33 +2358,23 @@ int BuildFileTable_Classic( // Sanity checks assert(ha->pHashTable != NULL); + // If the MPQ has no block table, do nothing + if(ha->pHeader->dwBlockTableSize == 0) + return ERROR_SUCCESS; + // Load the block table - pBlockTable = (TMPQBlock *)LoadBlockTable(ha, FileSize); + pBlockTable = (TMPQBlock *)LoadBlockTable(ha); if(pBlockTable != NULL) { - TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; - TMPQHash * pHash; - // If we don't have HET table, we build the file entries from the hash&block tables + // If we do, we only update it from the hash table if(ha->pHetTable == NULL) { - nError = ConvertMpqBlockTable(ha, pFileTable, pBlockTable); + nError = BuildFileTableFromBlockTable(ha, pFileTable, pBlockTable); } else { - 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; - } - } - } + nError = UpdateFileTableFromHashTable(ha, pFileTable); } // Free the block table @@ -2260,13 +2405,13 @@ int BuildFileTable_Classic( // Now merge the hi-block table to the file table if(nError == ERROR_SUCCESS) { + BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, dwTableSize); pFileEntry = pFileTable; // Add the high file offset to the base file offset. - // We also need to swap it during the process. for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++) { - pFileEntry->ByteOffset |= ((ULONGLONG)BSWAP_INT16_UNSIGNED(pHiBlockTable[i]) << 32); + pFileEntry->ByteOffset = MAKE_OFFSET64(pHiBlockTable[i], pFileEntry->ByteOffset); pFileEntry++; } } @@ -2301,13 +2446,18 @@ int BuildFileTable_HetBet( pBetTable = LoadBetTable(ha); if(pBetTable != NULL) { - // Step one: Fill the indexes to the HET table - for(i = 0; i < pHetTable->dwHashTableSize; i++) + // Verify the size of NameHash2 in the BET table. + // It has to be 8 bits less than the information in HET table + if((pBetTable->dwBitCount_NameHash2 + 8) != pHetTable->dwNameHashBitSize) + return ERROR_FILE_CORRUPT; + + // Step one: Fill the name indexes + for(i = 0; i < pHetTable->dwEntryCount; i++) { DWORD dwFileIndex = 0; // Is the entry in the HET table occupied? - if(pHetTable->pHetHashes[i] != 0) + if(pHetTable->pNameHashes[i] != 0) { // Load the index to the BET table GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i, @@ -2315,17 +2465,20 @@ int BuildFileTable_HetBet( &dwFileIndex, 4); // Overflow test - if(dwFileIndex < pBetTable->dwFileCount) + if(dwFileIndex < pBetTable->dwEntryCount) { - // Get the file entry and save HET index - pFileEntry = pFileTable + dwFileIndex; - pFileEntry->dwHetIndex = i; + ULONGLONG NameHash1 = pHetTable->pNameHashes[i]; + ULONGLONG NameHash2 = 0; // Load the BET hash - GetBits(pBetTable->pBetHashes, pBetTable->dwBetHashSizeTotal * dwFileIndex, - pBetTable->dwBetHashSize, - &pFileEntry->BetHash, - 8); + GetBits(pBetTable->pNameHashes, pBetTable->dwBitTotal_NameHash2 * dwFileIndex, + pBetTable->dwBitCount_NameHash2, + &NameHash2, + 8); + + // Combine both part of the name hash and put it to the file table + pFileEntry = pFileTable + dwFileIndex; + pFileEntry->FileNameHash = (NameHash1 << pBetTable->dwBitCount_NameHash2) | NameHash2; } } } @@ -2333,7 +2486,7 @@ int BuildFileTable_HetBet( // Go through the entire BET table and convert it to the file table. pFileEntry = pFileTable; pBitArray = pBetTable->pFileTable; - for(i = 0; i < pBetTable->dwFileCount; i++) + for(i = 0; i < pBetTable->dwEntryCount; i++) { DWORD dwFlagIndex = 0; @@ -2363,7 +2516,6 @@ int BuildFileTable_HetBet( pBetTable->dwBitCount_FlagIndex, &dwFlagIndex, 4); - pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex]; } @@ -2377,7 +2529,7 @@ int BuildFileTable_HetBet( } // Set the current size of the file table - ha->dwFileTableSize = pBetTable->dwFileCount; + ha->dwFileTableSize = pBetTable->dwEntryCount; FreeBetTable(pBetTable); nError = ERROR_SUCCESS; } @@ -2389,7 +2541,7 @@ int BuildFileTable_HetBet( return nError; } -int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) +int BuildFileTable(TMPQArchive * ha) { TFileEntry * pFileTable; bool bFileTableCreated = false; @@ -2420,7 +2572,7 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) // Note: If block table is corrupt or missing, we set the archive as read only if(ha->pHashTable != NULL) { - if(BuildFileTable_Classic(ha, pFileTable, FileSize) != ERROR_SUCCESS) + if(BuildFileTable_Classic(ha, pFileTable) != ERROR_SUCCESS) ha->dwFlags |= MPQ_FLAG_READ_ONLY; else bFileTableCreated = true; @@ -2438,11 +2590,128 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) return ERROR_SUCCESS; } +// Rebuilds the HET table from scratch based on the file table +// Used after a modifying operation (add, rename, delete) +int RebuildHetTable(TMPQArchive * ha) +{ + TMPQHetTable * pOldHetTable = ha->pHetTable; + ULONGLONG FileNameHash; + DWORD i; + int nError = ERROR_SUCCESS; + + // Create new HET table based on the total number of entries in the file table + // Note that if we fail to create it, we just stop using HET table + ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL); + if(ha->pHetTable != NULL) + { + // Go through the file table again and insert all existing files + for(i = 0; i < ha->dwFileTableSize; i++) + { + if(ha->pFileTable[i].dwFlags & MPQ_FILE_EXISTS) + { + // Get the high + FileNameHash = ha->pFileTable[i].FileNameHash; + nError = InsertHetEntry(ha->pHetTable, FileNameHash, i); + if(nError != ERROR_SUCCESS) + break; + } + } + } + + // Free the old HET table + FreeHetTable(pOldHetTable); + return nError; +} + +// Rebuilds the file table, removing all deleted file entries. +// Used when compacting the archive +int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount) +{ + TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pOldFileTable = ha->pFileTable; + TFileEntry * pFileTable; + TFileEntry * pFileEntry; + TFileEntry * pOldEntry; + TMPQHash * pOldHashTable = ha->pHashTable; + TMPQHash * pHashTable = NULL; + TMPQHash * pHash; + int nError = ERROR_SUCCESS; + + // The new hash table size must be greater or equal to the current hash table size + assert(dwNewHashTableSize >= ha->pHeader->dwHashTableSize); + assert(dwNewMaxFileCount >= ha->dwFileTableSize); + + // The new hash table size must be a power of two + assert((dwNewHashTableSize & (dwNewHashTableSize - 1)) == 0); + + // Allocate the new file table + pFileTable = pFileEntry = STORM_ALLOC(TFileEntry, dwNewMaxFileCount); + if(pFileTable == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; + + // Allocate new hash table + if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) + { + pHashTable = STORM_ALLOC(TMPQHash, dwNewHashTableSize); + if(pHashTable == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; + } + + // If both succeeded, we need to rebuild the file table + if(nError == ERROR_SUCCESS) + { + // Make sure that the hash table is properly filled + memset(pFileTable, 0x00, sizeof(TFileEntry) * dwNewMaxFileCount); + memset(pHashTable, 0xFF, sizeof(TMPQHash) * dwNewHashTableSize); + + // Set the new tables to the MPQ archive + ha->pFileTable = pFileTable; + ha->pHashTable = pHashTable; + + // Set the new limits to the MPQ archive + ha->pHeader->dwHashTableSize = dwNewHashTableSize; + ha->dwMaxFileCount = dwNewMaxFileCount; + + // Now copy all the file entries + for(pOldEntry = pOldFileTable; pOldEntry < pOldFileTableEnd; pOldEntry++) + { + // If the file entry exists, we copy it to the new table + // Otherwise, we skip it + if(pOldEntry->dwFlags & MPQ_FILE_EXISTS) + { + // Copy the file entry + *pFileEntry = *pOldEntry; + + // Create hash entry for it + if(ha->pHashTable != NULL) + { + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); + } + + // Move the file entry by one + *pFileEntry++; + } + } + + // Update the file table size + ha->dwFileTableSize = (DWORD)(pFileEntry - pFileTable); + ha->dwFlags |= MPQ_FLAG_CHANGED; + } + + // Now free the remaining entries + if(pOldFileTable != NULL) + STORM_FREE(pOldFileTable); + if(pOldHashTable != NULL) + STORM_FREE(pOldHashTable); + return nError; +} + // Saves MPQ header, hash table, block table and hi-block table. int SaveMPQTables(TMPQArchive * ha) { - TMPQExtTable * pHetTable = NULL; - TMPQExtTable * pBetTable = NULL; + TMPQExtHeader * pHetTable = NULL; + TMPQExtHeader * pBetTable = NULL; TMPQHeader * pHeader = ha->pHeader; TMPQBlock * pBlockTable = NULL; TMPQHash * pHashTable = NULL; @@ -2488,7 +2757,7 @@ int SaveMPQTables(TMPQArchive * ha) } // Create block table - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) + if(nError == ERROR_SUCCESS && ha->pFileTable != NULL) { pBlockTable = TranslateBlockTable(ha, &BlockTableSize64, &bNeedHiBlockTable); if(pBlockTable == NULL) @@ -2581,6 +2850,7 @@ int SaveMPQTables(TMPQArchive * ha) // Write the MPQ header to the file memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize)) |