summaryrefslogtreecommitdiff
path: root/src/SBaseFileTable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/SBaseFileTable.cpp')
-rw-r--r--src/SBaseFileTable.cpp2552
1 files changed, 2552 insertions, 0 deletions
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
new file mode 100644
index 0000000..c77e71d
--- /dev/null
+++ b/src/SBaseFileTable.cpp
@@ -0,0 +1,2552 @@
+/*****************************************************************************/
+/* SBaseFileTable.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* Description: Common handler for classic and new hash&block tables */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 06.09.10 1.00 Lad The first version of SBaseFileTable.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local defines
+
+#define INVALID_FLAG_VALUE 0xCCCCCCCC
+#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 dwMaxFileCount; // Maximum number of files in the MPQ
+ 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;
+}
+
+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] == INVALID_FLAG_VALUE || FlagArray[dwFlagIndex] == dwFlags)
+ {
+ FlagArray[dwFlagIndex] = dwFlags;
+ return dwFlagIndex;
+ }
+ }
+
+ // This should never happen
+ assert(false);
+ return 0xFFFFFFFF;
+}
+
+static DWORD GetNecessaryBitCount(ULONGLONG MaxValue)
+{
+ DWORD dwBitCount = 0;
+
+ while(MaxValue > 0)
+ {
+ MaxValue >>= 1;
+ dwBitCount++;
+ }
+
+ return dwBitCount;
+}
+
+//-----------------------------------------------------------------------------
+// Support functions for BIT_ARRAY
+
+static USHORT SetBitsMask[] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};
+
+static TBitArray * CreateBitArray(
+ DWORD NumberOfBits,
+ BYTE FillValue)
+{
+ TBitArray * pBitArray;
+ size_t nSize = sizeof(TBitArray) + (NumberOfBits + 7) / 8;
+
+ // Allocate the bit array
+ pBitArray = (TBitArray *)STORM_ALLOC(BYTE, nSize);
+ if(pBitArray != NULL)
+ {
+ memset(pBitArray, FillValue, nSize);
+ pBitArray->NumberOfBits = NumberOfBits;
+ }
+
+ return pBitArray;
+}
+
+void GetBits(
+ TBitArray * pArray,
+ unsigned int nBitPosition,
+ unsigned int nBitLength,
+ void * pvBuffer,
+ int nResultByteSize)
+{
+ unsigned char * pbBuffer = (unsigned char *)pvBuffer;
+ unsigned int nBytePosition0 = (nBitPosition / 8);
+ unsigned int nBytePosition1 = nBytePosition0 + 1;
+ unsigned int nByteLength = (nBitLength / 8);
+ unsigned int nBitOffset = (nBitPosition & 0x07);
+ unsigned char BitBuffer;
+
+ // Keep compiler happy for platforms where nResultByteSize is not used
+ nResultByteSize = nResultByteSize;
+
+#ifdef _DEBUG
+ // Check if the target is properly zeroed
+ for(int i = 0; i < nResultByteSize; i++)
+ assert(pbBuffer[i] == 0);
+#endif
+
+#ifndef PLATFORM_LITTLE_ENDIAN
+ // Adjust the buffer pointer for big endian platforms
+ pbBuffer += (nResultByteSize - 1);
+#endif
+
+ // Copy whole bytes, if any
+ while(nByteLength > 0)
+ {
+ // Is the current position in the Elements byte-aligned?
+ if(nBitOffset != 0)
+ {
+ BitBuffer = (unsigned char)((pArray->Elements[nBytePosition0] >> nBitOffset) | (pArray->Elements[nBytePosition1] << (0x08 - nBitOffset)));
+ }
+ else
+ {
+ BitBuffer = pArray->Elements[nBytePosition0];
+ }
+
+#ifdef PLATFORM_LITTLE_ENDIAN
+ *pbBuffer++ = BitBuffer;
+#else
+ *pbBuffer-- = BitBuffer;
+#endif
+
+ // Move byte positions and lengths
+ nBytePosition1++;
+ nBytePosition0++;
+ nByteLength--;
+ }
+
+ // Get the rest of the bits
+ nBitLength = (nBitLength & 0x07);
+ if(nBitLength != 0)
+ {
+ *pbBuffer = (unsigned char)(pArray->Elements[nBytePosition0] >> nBitOffset);
+
+ if(nBitLength > (8 - nBitOffset))
+ *pbBuffer = (unsigned char)((pArray->Elements[nBytePosition1] << (8 - nBitOffset)) | (pArray->Elements[nBytePosition0] >> nBitOffset));
+
+ *pbBuffer &= (0x01 << nBitLength) - 1;
+ }
+}
+
+void SetBits(
+ TBitArray * pArray,
+ unsigned int nBitPosition,
+ unsigned int nBitLength,
+ void * pvBuffer,
+ int nResultByteSize)
+{
+ unsigned char * pbBuffer = (unsigned char *)pvBuffer;
+ unsigned int nBytePosition = (nBitPosition / 8);
+ unsigned int nBitOffset = (nBitPosition & 0x07);
+ unsigned short BitBuffer = 0;
+ unsigned short AndMask = 0;
+ unsigned short OneByte = 0;
+
+ // Keep compiler happy for platforms where nResultByteSize is not used
+ nResultByteSize = nResultByteSize;
+
+#ifndef PLATFORM_LITTLE_ENDIAN
+ // Adjust the buffer pointer for big endian platforms
+ pbBuffer += (nResultByteSize - 1);
+#endif
+
+ // Copy whole bytes, if any
+ while(nBitLength > 8)
+ {
+ // Reload the bit buffer
+#ifdef PLATFORM_LITTLE_ENDIAN
+ OneByte = *pbBuffer++;
+#else
+ OneByte = *pbBuffer--;
+#endif
+ // Update the BitBuffer and AndMask for the bit array
+ BitBuffer = (BitBuffer >> 0x08) | (OneByte << nBitOffset);
+ AndMask = (AndMask >> 0x08) | (0x00FF << nBitOffset);
+
+ // Update the byte in the array
+ pArray->Elements[nBytePosition] = (BYTE)((pArray->Elements[nBytePosition] & ~AndMask) | BitBuffer);
+
+ // Move byte positions and lengths
+ nBytePosition++;
+ nBitLength -= 0x08;
+ }
+
+ if(nBitLength != 0)
+ {
+ // Reload the bit buffer
+ OneByte = *pbBuffer;
+
+ // Update the AND mask for the last bit
+ BitBuffer = (BitBuffer >> 0x08) | (OneByte << nBitOffset);
+ AndMask = (AndMask >> 0x08) | (SetBitsMask[nBitLength] << nBitOffset);
+
+ // Update the byte in the array
+ pArray->Elements[nBytePosition] = (BYTE)((pArray->Elements[nBytePosition] & ~AndMask) | BitBuffer);
+
+ // Update the next byte, if needed
+ if(AndMask & 0xFF00)
+ {
+ nBytePosition++;
+ BitBuffer >>= 0x08;
+ AndMask >>= 0x08;
+
+ pArray->Elements[nBytePosition] = (BYTE)((pArray->Elements[nBytePosition] & ~AndMask) | BitBuffer);
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Support for hash table
+
+// Returns a hash table entry in the following order:
+// 1) A hash table entry with the neutral locale
+// 2) A hash table entry with any other locale
+// 3) NULL
+static TMPQHash * GetHashEntryAny(TMPQArchive * ha, const char * szFileName)
+{
+ TMPQHash * pHashNeutral = NULL;
+ TMPQHash * pFirstHash = GetFirstHashEntry(ha, szFileName);
+ TMPQHash * pHashAny = NULL;
+ TMPQHash * pHash = pFirstHash;
+
+ // Parse the found hashes
+ while(pHash != NULL)
+ {
+ // If we found neutral hash, remember it
+ if(pHash->lcLocale == 0)
+ pHashNeutral = pHash;
+ if(pHashAny == NULL)
+ pHashAny = pHash;
+
+ // Get the next hash entry for that file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // At the end, return neutral hash (if found), otherwise NULL
+ return (pHashNeutral != NULL) ? pHashNeutral : pHashAny;
+}
+
+// Returns a hash table entry in the following order:
+// 1) A hash table entry with the preferred locale
+// 2) A hash table entry with the neutral locale
+// 3) NULL
+static TMPQHash * GetHashEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+{
+ TMPQHash * pHashNeutral = NULL;
+ TMPQHash * pFirstHash = GetFirstHashEntry(ha, szFileName);
+ TMPQHash * pHash = pFirstHash;
+
+ // Parse the found hashes
+ while(pHash != NULL)
+ {
+ // If the locales match, return it
+ if(pHash->lcLocale == lcLocale)
+ return pHash;
+
+ // If we found neutral hash, remember it
+ if(pHash->lcLocale == 0)
+ pHashNeutral = pHash;
+
+ // Get the next hash entry for that file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // At the end, return neutral hash (if found), otherwise NULL
+ return pHashNeutral;
+}
+
+// Returns a hash table entry in the following order:
+// 1) A hash table entry with the preferred locale
+// 2) NULL
+static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+{
+ TMPQHash * pFirstHash = GetFirstHashEntry(ha, szFileName);
+ TMPQHash * pHash = pFirstHash;
+
+ // Parse the found hashes
+ while(pHash != NULL)
+ {
+ // If the locales match, return it
+ if(pHash->lcLocale == lcLocale)
+ return pHash;
+
+ // Get the next hash entry for that file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // Not found
+ return NULL;
+}
+
+static TMPQHash * TranslateHashTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbTableSize)
+{
+ TMPQHash * pHashTable;
+ size_t HashTableSize;
+
+ // Allocate copy of the hash table
+ pHashTable = STORM_ALLOC(TMPQHash, ha->pHeader->dwHashTableSize);
+ if(pHashTable != NULL)
+ {
+ // Copy the hash table
+ HashTableSize = sizeof(TMPQHash) * ha->pHeader->dwHashTableSize;
+ memcpy(pHashTable, ha->pHashTable, HashTableSize);
+
+ // Give the size to the caller
+ if(pcbTableSize != NULL)
+ {
+ *pcbTableSize = (ULONGLONG)HashTableSize;
+ }
+ }
+
+ return pHashTable;
+}
+
+static TMPQBlock * TranslateBlockTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbTableSize,
+ bool * pbNeedHiBlockTable)
+{
+ TFileEntry * pFileEntry = ha->pFileTable;
+ TMPQBlock * pBlockTable;
+ TMPQBlock * pBlock;
+ size_t BlockTableSize;
+ bool bNeedHiBlockTable = false;
+
+ // Allocate copy of the hash table
+ pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, ha->dwFileTableSize);
+ if(pBlockTable != NULL)
+ {
+ // Copy the block table
+ BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize;
+ for(DWORD i = 0; i < ha->dwFileTableSize; i++)
+ {
+ bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false;
+ pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset;
+ pBlock->dwFSize = pFileEntry->dwFileSize;
+ pBlock->dwCSize = pFileEntry->dwCmpSize;
+ pBlock->dwFlags = pFileEntry->dwFlags;
+
+ pFileEntry++;
+ pBlock++;
+ }
+
+ // Give the size to the caller
+ if(pcbTableSize != NULL)
+ *pcbTableSize = (ULONGLONG)BlockTableSize;
+
+ if(pbNeedHiBlockTable != NULL)
+ *pbNeedHiBlockTable = bNeedHiBlockTable;
+ }
+
+ return pBlockTable;
+}
+
+static USHORT * TranslateHiBlockTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbTableSize)
+{
+ TFileEntry * pFileEntry = ha->pFileTable;
+ USHORT * pHiBlockTable;
+ USHORT * pHiBlock;
+ size_t HiBlockTableSize;
+
+ // Allocate copy of the hash table
+ pHiBlockTable = pHiBlock = STORM_ALLOC(USHORT, ha->dwFileTableSize);
+ if(pHiBlockTable != NULL)
+ {
+ // Copy the block table
+ HiBlockTableSize = sizeof(USHORT) * ha->dwFileTableSize;
+ for(DWORD i = 0; i < ha->dwFileTableSize; i++)
+ pHiBlock[i] = (USHORT)(pFileEntry[i].ByteOffset >> 0x20);
+
+ // Give the size to the caller
+ if(pcbTableSize != NULL)
+ *pcbTableSize = (ULONGLONG)HiBlockTableSize;
+ }
+
+ return pHiBlockTable;
+}
+
+//-----------------------------------------------------------------------------
+// General EXT table functions
+
+TMPQExtTable * LoadExtTable(
+ TMPQArchive * ha,
+ ULONGLONG ByteOffset,
+ size_t Size,
+ DWORD dwSignature,
+ DWORD dwKey)
+{
+ TMPQExtTable * pCompressed = NULL; // Compressed table
+ TMPQExtTable * 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);
+ if(pExtTable != NULL)
+ {
+ // Load the table from the MPQ
+ ByteOffset += ha->MpqPos;
+ if(!FileStream_Read(ha->pStream, &ByteOffset, pExtTable, (DWORD)Size))
+ {
+ STORM_FREE(pExtTable);
+ return NULL;
+ }
+
+ // Swap the ext table header
+ BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtTable));
+ if(pExtTable->dwSignature != dwSignature)
+ {
+ STORM_FREE(pExtTable);
+ return NULL;
+ }
+
+ // Decrypt the block
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+ DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtTable)), dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+
+ // If the table is compressed, decompress it
+ if((pExtTable->dwDataSize + sizeof(TMPQExtTable)) > Size)
+ {
+ pCompressed = pExtTable;
+ pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + pCompressed->dwDataSize);
+ if(pExtTable != NULL)
+ {
+ int cbOutBuffer = (int)pCompressed->dwDataSize;
+ int cbInBuffer = (int)Size;
+
+ // Decompress the extended table
+ pExtTable->dwSignature = pCompressed->dwSignature;
+ pExtTable->dwVersion = pCompressed->dwVersion;
+ pExtTable->dwDataSize = pCompressed->dwDataSize;
+ if(!SCompDecompress2(pExtTable + 1, &cbOutBuffer, pCompressed + 1, cbInBuffer))
+ {
+ STORM_FREE(pExtTable);
+ pExtTable = NULL;
+ }
+ }
+
+ // Free the compressed block
+ STORM_FREE(pCompressed);
+ }
+ }
+ }
+
+ // Return the decompressed table to the caller
+ return pExtTable;
+}
+
+// Used in MPQ Editor
+void FreeMpqBuffer(void * pvBuffer)
+{
+ STORM_FREE(pvBuffer);
+}
+
+static int SaveMpqTable(
+ TMPQArchive * ha,
+ void * pMpqTable,
+ ULONGLONG ByteOffset,
+ size_t Size,
+ unsigned char * md5,
+ DWORD dwKey,
+ bool bCompress)
+{
+ ULONGLONG FileOffset;
+ void * pCompressed = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Do we have to compress the table?
+ if(bCompress)
+ {
+ int cbOutBuffer = (int)Size;
+ int cbInBuffer = (int)Size;
+
+ // Allocate extra space for compressed table
+ pCompressed = STORM_ALLOC(BYTE, Size);
+ if(pCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Compress the table
+ SCompCompress(pCompressed, &cbOutBuffer, pMpqTable, cbInBuffer, MPQ_COMPRESSION_ZLIB, 0, 0);
+
+ // If the compression failed, revert it. Otherwise, swap the tables
+ if(cbOutBuffer >= cbInBuffer)
+ {
+ STORM_FREE(pCompressed);
+ pCompressed = NULL;
+ }
+ else
+ {
+ pMpqTable = pCompressed;
+ }
+ }
+
+ // Encrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pMpqTable, Size);
+ EncryptMpqBlock(pMpqTable, (DWORD)Size, dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pMpqTable, Size);
+ }
+
+ // Calculate the MD5
+ if(md5 != NULL)
+ {
+ CalculateDataBlockHash(pMpqTable, (DWORD)Size, md5);
+ }
+
+ // Save the table to the MPQ
+ BSWAP_ARRAY32_UNSIGNED(pMpqTable, Size);
+ FileOffset = ha->MpqPos + ByteOffset;
+ if(!FileStream_Write(ha->pStream, &FileOffset, pMpqTable, (DWORD)Size))
+ nError = GetLastError();
+
+ // Free the compressed table, if any
+ if(pCompressed != NULL)
+ STORM_FREE(pCompressed);
+ return nError;
+}
+
+static int SaveExtTable(
+ TMPQArchive * ha,
+ TMPQExtTable * pExtTable,
+ ULONGLONG ByteOffset,
+ DWORD dwTableSize,
+ unsigned char * md5,
+ DWORD dwKey,
+ bool bCompress,
+ LPDWORD pcbTotalSize)
+{
+ ULONGLONG FileOffset;
+ TMPQExtTable * pCompressed = NULL;
+ DWORD cbTotalSize = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Do we have to compress the table?
+ if(bCompress)
+ {
+ int cbOutBuffer = (int)dwTableSize;
+ int cbInBuffer = (int)dwTableSize;
+
+ // Allocate extra space for compressed table
+ pCompressed = (TMPQExtTable *)STORM_ALLOC(BYTE, dwTableSize);
+ if(pCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Compress the table
+ pCompressed->dwSignature = pExtTable->dwSignature;
+ pCompressed->dwVersion = pExtTable->dwVersion;
+ pCompressed->dwDataSize = pExtTable->dwDataSize;
+ SCompCompress((pCompressed + 1), &cbOutBuffer, (pExtTable + 1), cbInBuffer, MPQ_COMPRESSION_ZLIB, 0, 0);
+
+ // If the compression failed, revert it. Otherwise, swap the tables
+ if(cbOutBuffer >= cbInBuffer)
+ {
+ STORM_FREE(pCompressed);
+ pCompressed = NULL;
+ }
+ else
+ {
+ pExtTable = pCompressed;
+ }
+ }
+
+ // Encrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+ EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtTable)), dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+ }
+
+ // Calculate the MD5 of the table after
+ if(md5 != NULL)
+ {
+ CalculateDataBlockHash(pExtTable, dwTableSize, md5);
+ }
+
+ // Save the table to the MPQ
+ FileOffset = ha->MpqPos + ByteOffset;
+ if(FileStream_Write(ha->pStream, &FileOffset, pExtTable, dwTableSize))
+ cbTotalSize += dwTableSize;
+ else
+ nError = GetLastError();
+
+ // We have to write raw data MD5
+ if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0)
+ {
+ nError = WriteMemDataMD5(ha->pStream,
+ FileOffset,
+ pExtTable,
+ dwTableSize,
+ ha->pHeader->dwRawChunkSize,
+ &cbTotalSize);
+ }
+
+ // Give the total written size, if needed
+ if(pcbTotalSize != NULL)
+ *pcbTotalSize = cbTotalSize;
+
+ // Free the compressed table, if any
+ if(pCompressed != NULL)
+ STORM_FREE(pCompressed);
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Support for HET table
+
+static void CreateHetHeader(
+ TMPQHetTable * pHetTable,
+ PHET_TABLE_HEADER pHetHeader)
+{
+ // Fill the BET header
+ pHetHeader->dwMaxFileCount = pHetTable->dwMaxFileCount;
+ pHetHeader->dwHashTableSize = pHetTable->dwHashTableSize;
+ pHetHeader->dwHashEntrySize = pHetTable->dwHashBitSize;
+ pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwMaxFileCount);
+ pHetHeader->dwIndexSizeExtra = 0;
+ pHetHeader->dwIndexSize = pHetHeader->dwIndexSizeTotal;
+ pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwHashTableSize) + 7) / 8;
+
+ // Calculate the total size needed for holding HET table
+ pHetHeader->dwTableSize = sizeof(HET_TABLE_HEADER) +
+ pHetHeader->dwHashTableSize +
+ pHetHeader->dwIndexTableSize;
+}
+
+TMPQHetTable * CreateHetTable(DWORD dwMaxFileCount, DWORD dwHashBitSize, bool bCreateEmpty)
+{
+ TMPQHetTable * pHetTable;
+
+ pHetTable = STORM_ALLOC(TMPQHetTable, 1);
+ if(pHetTable != NULL)
+ {
+ pHetTable->dwIndexSizeTotal = 0;
+ pHetTable->dwIndexSizeExtra = 0;
+ pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
+ pHetTable->dwMaxFileCount = dwMaxFileCount;
+ pHetTable->dwHashTableSize = (dwMaxFileCount * 4 / 3);
+ pHetTable->dwHashBitSize = dwHashBitSize;
+
+ // Size of one index is calculated from max file count
+ pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwMaxFileCount);
+ pHetTable->dwIndexSizeExtra = 0;
+ pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
+
+ // Allocate hash table
+ pHetTable->pHetHashes = STORM_ALLOC(BYTE, pHetTable->dwHashTableSize);
+ memset(pHetTable->pHetHashes, 0, pHetTable->dwHashTableSize);
+
+ // 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);
+
+ // Calculate masks
+ pHetTable->AndMask64 = 0;
+ if(dwHashBitSize != 0x40)
+ pHetTable->AndMask64 = (ULONGLONG)1 << dwHashBitSize;
+ pHetTable->AndMask64--;
+
+ pHetTable->OrMask64 = (ULONGLONG)1 << (dwHashBitSize - 1);
+ }
+
+ return pHetTable;
+}
+
+static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable)
+{
+ HET_TABLE_HEADER HetHeader;
+ TMPQHetTable * pHetTable = NULL;
+ LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1);
+
+ // Sanity check
+ assert(pExtTable->dwSignature == HET_TABLE_SIGNATURE);
+ assert(pExtTable->dwVersion == 1);
+
+ // Verify size of the HET table
+ if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(HET_TABLE_HEADER))
+ {
+ // 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)
+ {
+ // Create translated table
+ pHetTable = CreateHetTable(HetHeader.dwMaxFileCount, HetHeader.dwHashEntrySize, false);
+ 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;
+ }
+ }
+ }
+
+ return pHetTable;
+}
+
+static TMPQExtTable * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable)
+{
+ TMPQExtTable * pExtTable = NULL;
+ HET_TABLE_HEADER 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);
+ 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 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 bit array of BET indexes
+ memcpy(pbTrgData, pHetTable->pBetIndexes->Elements, HetHeader.dwIndexTableSize);
+
+ // Calculate the total size of the table, including the TMPQExtTable
+ if(pcbHetTable != NULL)
+ {
+ *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtTable) + HetTableSize);
+ }
+ }
+
+ return pExtTable;
+}
+
+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
+
+ // 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;
+
+ // 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);
+
+ // Calculate the starting index to the hash table
+ StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize);
+
+ // Go through HET table until we find a terminator
+ while(pHetTable->pHetHashes[Index] != HET_ENTRY_FREE)
+ {
+ // Did we find 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)
+ return dwFileIndex;
+ }
+
+ // 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)
+ break;
+ }
+
+ // File not found
+ return HASH_ENTRY_FREE;
+}
+
+DWORD AllocateHetEntry(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry)
+{
+ TMPQHetTable * pHetTable = ha->pHetTable;
+ ULONGLONG FileNameHash;
+ ULONGLONG AndMask64;
+ ULONGLONG OrMask64;
+ ULONGLONG BetHash;
+ 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(;;)
+ {
+ // Check for entries that might have been deleted
+ if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED)
+ {
+ DWORD dwInvalidBetIndex = (1 << pHetTable->dwIndexSizeTotal) - 1;
+ DWORD dwBetIndex = 0;
+
+ // Verify the BET index. If it's really free, we can use it
+ dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable);
+ GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
+ pHetTable->dwIndexSize,
+ &dwBetIndex,
+ 4);
+
+ if(dwBetIndex == dwInvalidBetIndex)
+ {
+ FreeHetIndex = Index;
+ break;
+ }
+ }
+
+ // Is that entry free ?
+ if(pHetTable->pHetHashes[Index] == HET_ENTRY_FREE)
+ {
+ 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;
+ 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->pBetIndexes != NULL)
+ STORM_FREE(pHetTable->pBetIndexes);
+
+ STORM_FREE(pHetTable);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Support for BET table
+
+static void CreateBetHeader(
+ TMPQArchive * ha,
+ PBET_TABLE_HEADER pBetHeader)
+{
+ 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 dwFlagIndex;
+
+ // Initialize array of flag combinations
+ InitFileFlagArray(FlagArray);
+
+ // Get the maximum values for the BET table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Highest file position in the MPQ
+ if(pFileEntry->ByteOffset > MaxByteOffset)
+ MaxByteOffset = pFileEntry->ByteOffset;
+
+ // Biggest file size
+ if(pFileEntry->dwFileSize > dwMaxFileSize)
+ dwMaxFileSize = pFileEntry->dwFileSize;
+
+ // Biggest compressed size
+ if(pFileEntry->dwCmpSize > dwMaxCmpSize)
+ dwMaxCmpSize = pFileEntry->dwCmpSize;
+
+ // Check if this flag was there before
+ dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags);
+ if(dwFlagIndex > dwMaxFlagIndex)
+ dwMaxFlagIndex = dwFlagIndex;
+ }
+
+ // Now save bit count for every piece of file information
+ pBetHeader->dwBitIndex_FilePos = 0;
+ pBetHeader->dwBitCount_FilePos = GetNecessaryBitCount(MaxByteOffset);
+
+ pBetHeader->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FilePos + pBetHeader->dwBitCount_FilePos;
+ pBetHeader->dwBitCount_FileSize = GetNecessaryBitCount(dwMaxFileSize);
+
+ pBetHeader->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_FileSize + pBetHeader->dwBitCount_FileSize;
+ pBetHeader->dwBitCount_CmpSize = GetNecessaryBitCount(dwMaxCmpSize);
+
+ pBetHeader->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_CmpSize + pBetHeader->dwBitCount_CmpSize;
+ pBetHeader->dwBitCount_FlagIndex = GetNecessaryBitCount(dwMaxFlagIndex + 1);
+
+ pBetHeader->dwBitIndex_Unknown = pBetHeader->dwBitIndex_FlagIndex + pBetHeader->dwBitCount_FlagIndex;
+ pBetHeader->dwBitCount_Unknown = 0;
+
+ // Calculate the total size of one entry
+ pBetHeader->dwTableEntrySize = pBetHeader->dwBitCount_FilePos +
+ pBetHeader->dwBitCount_FileSize +
+ pBetHeader->dwBitCount_CmpSize +
+ pBetHeader->dwBitCount_FlagIndex +
+ pBetHeader->dwBitCount_Unknown;
+
+ // Save the file count and flag count
+ pBetHeader->dwFileCount = 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;
+
+ // Save the total table size
+ pBetHeader->dwTableSize = sizeof(BET_TABLE_HEADER) +
+ pBetHeader->dwFlagCount * sizeof(DWORD) +
+ ((pBetHeader->dwTableEntrySize * pBetHeader->dwFileCount) + 7) / 8 +
+ pBetHeader->dwBetHashArraySize;
+}
+
+TMPQBetTable * CreateBetTable(DWORD dwFileCount)
+{
+ TMPQBetTable * pBetTable;
+
+ // Allocate BET table
+ pBetTable = STORM_ALLOC(TMPQBetTable, 1);
+ if(pBetTable != NULL)
+ {
+ memset(pBetTable, 0, sizeof(TMPQBetTable));
+ pBetTable->dwFileCount = dwFileCount;
+ }
+
+ return pBetTable;
+}
+
+static TMPQBetTable * TranslateBetTable(
+ TMPQArchive * ha,
+ TMPQExtTable * pExtTable)
+{
+ BET_TABLE_HEADER BetHeader;
+ TMPQBetTable * pBetTable = NULL;
+ LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1);
+ DWORD LengthInBytes;
+
+ // Sanity check
+ assert(pExtTable->dwSignature == BET_TABLE_SIGNATURE);
+ assert(pExtTable->dwVersion == 1);
+ assert(ha->pHetTable != NULL);
+ ha = ha;
+
+ // Verify size of the HET table
+ if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(BET_TABLE_HEADER))
+ {
+ // 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)
+ {
+ // Create translated table
+ pBetTable = CreateBetTable(BetHeader.dwFileCount);
+ 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
+ assert(pBetTable->dwBitCount_Unknown == 0);
+
+ // Allocate array for flags
+ if(BetHeader.dwFlagCount != 0)
+ {
+ // Allocate array for file flags and load it
+ pBetTable->pFileFlags = STORM_ALLOC(DWORD, BetHeader.dwFlagCount);
+ if(pBetTable->pFileFlags != NULL)
+ {
+ LengthInBytes = BetHeader.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;
+ }
+
+ // Load the bit-based file table
+ pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * BetHeader.dwFileCount, 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;
+
+ // 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;
+
+ // Dump both tables
+// DumpHetAndBetTable(ha->pHetTable, pBetTable);
+ }
+ }
+ }
+
+ return pBetTable;
+}
+
+TMPQExtTable * TranslateBetTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbBetTable)
+{
+ TMPQExtTable * pExtTable = NULL;
+ BET_TABLE_HEADER BetHeader;
+ 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);
+ 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);
+
+ // Save the bit-based block table
+ pBitArray = CreateBitArray(BetHeader.dwFileCount * 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++)
+ {
+ //
+ // 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
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FilePos,
+ BetHeader.dwBitCount_FilePos,
+ &pFileEntry->ByteOffset,
+ 8);
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FileSize,
+ BetHeader.dwBitCount_FileSize,
+ &pFileEntry->dwFileSize,
+ 4);
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_CmpSize,
+ BetHeader.dwBitCount_CmpSize,
+ &pFileEntry->dwCmpSize,
+ 4);
+
+ // Save the flag index
+ dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags);
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FlagIndex,
+ BetHeader.dwBitCount_FlagIndex,
+ &dwFlagIndex,
+ 4);
+
+ // Move the bit offset
+ nBitOffset += BetHeader.dwTableEntrySize;
+ }
+
+ // Write the array of flags
+ LengthInBytes = BetHeader.dwFlagCount * sizeof(DWORD);
+ memcpy(pbTrgData, FlagArray, LengthInBytes);
+ BSWAP_ARRAY32_UNSIGNED(pbTrgData, LengthInBytes);
+ pbTrgData += LengthInBytes;
+
+ // Write the bit-based block table
+ LengthInBytes = (pBitArray->NumberOfBits + 7) / 8;
+ memcpy(pbTrgData, pBitArray->Elements, LengthInBytes);
+ pbTrgData += LengthInBytes;
+
+ // Free the bit array
+ STORM_FREE(pBitArray);
+ }
+
+ // Create bit array for BET hashes
+ pBitArray = CreateBitArray(BetHeader.dwBetHashSizeTotal * BetHeader.dwFileCount, 0);
+ if(pBitArray != NULL)
+ {
+ TFileEntry * pFileEntry = ha->pFileTable;
+ ULONGLONG AndMask64 = ha->pHetTable->AndMask64;
+ ULONGLONG OrMask64 = ha->pHetTable->OrMask64;
+
+ for(i = 0; i < BetHeader.dwFileCount; i++)
+ {
+ 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,
+ 8);
+
+ // Move to the next file entry
+ pFileEntry++;
+ }
+
+ // Write the array of BET hashes
+ LengthInBytes = (pBitArray->NumberOfBits + 7) / 8;
+ memcpy(pbTrgData, pBitArray->Elements, LengthInBytes);
+ pbTrgData += LengthInBytes;
+
+ // Free the bit array
+ STORM_FREE(pBitArray);
+ }
+
+ // Write the size of the BET table in the MPQ
+ if(pcbBetTable != NULL)
+ {
+ *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtTable) + BetTableSize);
+ }
+ }
+
+ return pExtTable;
+}
+
+void FreeBetTable(TMPQBetTable * pBetTable)
+{
+ if(pBetTable != NULL)
+ {
+ if(pBetTable->pFileTable != NULL)
+ STORM_FREE(pBetTable->pFileTable);
+ if(pBetTable->pFileFlags != NULL)
+ STORM_FREE(pBetTable->pFileFlags);
+ if(pBetTable->pBetHashes != NULL)
+ STORM_FREE(pBetTable->pBetHashes);
+
+ STORM_FREE(pBetTable);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Support for file table
+
+TFileEntry * GetFileEntryAny(TMPQArchive * ha, const char * szFileName)
+{
+ TMPQHash * pHash;
+ DWORD dwFileIndex;
+
+ // If we have HET table in the MPQ, try to find the file in HET table
+ if(ha->pHetTable != NULL)
+ {
+ dwFileIndex = GetFileIndex_Het(ha, szFileName);
+ if(dwFileIndex != HASH_ENTRY_FREE)
+ return ha->pFileTable + dwFileIndex;
+ }
+
+ // Otherwise, perform the file search in the classic hash table
+ if(ha->pHashTable != NULL)
+ {
+ pHash = GetHashEntryAny(ha, szFileName);
+ if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize)
+ return ha->pFileTable + pHash->dwBlockIndex;
+ }
+
+ // Not found
+ return NULL;
+}
+
+TFileEntry * GetFileEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+{
+ TMPQHash * pHash;
+ DWORD dwFileIndex;
+
+ // If we have HET table in the MPQ, try to find the file in HET table
+ if(ha->pHetTable != NULL)
+ {
+ dwFileIndex = GetFileIndex_Het(ha, szFileName);
+ if(dwFileIndex != HASH_ENTRY_FREE)
+ return ha->pFileTable + dwFileIndex;
+ }
+
+ // Otherwise, perform the file search in the classic hash table
+ if(ha->pHashTable != NULL)
+ {
+ pHash = GetHashEntryLocale(ha, szFileName, lcLocale);
+ if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize)
+ return ha->pFileTable + pHash->dwBlockIndex;
+ }
+
+ // Not found
+ return NULL;
+}
+
+TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+{
+ TMPQHash * pHash;
+ DWORD dwFileIndex;
+
+ // If we have HET table in the MPQ, try to find the file in HET table
+ if(ha->pHetTable != NULL)
+ {
+ dwFileIndex = GetFileIndex_Het(ha, szFileName);
+ if(dwFileIndex != HASH_ENTRY_FREE)
+ return ha->pFileTable + dwFileIndex;
+ }
+
+ // Otherwise, perform the file search in the classic hash table
+ if(ha->pHashTable != NULL)
+ {
+ pHash = GetHashEntryExact(ha, szFileName, lcLocale);
+ if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize)
+ return ha->pFileTable + pHash->dwBlockIndex;
+ }
+
+ // Not found
+ return NULL;
+}
+
+TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex)
+{
+ // For MPQs with classic hash table
+ if(dwIndex < ha->dwFileTableSize)
+ return ha->pFileTable + dwIndex;
+ return NULL;
+}
+
+void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName)
+{
+ // Sanity check
+ assert(pFileEntry != NULL);
+
+ // If the file name is pseudo file name, free it at this point
+ if(IsPseudoFileName(pFileEntry->szFileName, NULL))
+ {
+ if(pFileEntry->szFileName != NULL)
+ STORM_FREE(pFileEntry->szFileName);
+ pFileEntry->szFileName = NULL;
+ }
+
+ // Only allocate new file name if it's not there yet
+ if(pFileEntry->szFileName == NULL)
+ {
+ pFileEntry->szFileName = STORM_ALLOC(char, strlen(szFileName) + 1);
+ if(pFileEntry->szFileName != NULL)
+ strcpy(pFileEntry->szFileName, szFileName);
+ }
+}
+
+
+// Finds a free file entry. Does NOT increment table size.
+TFileEntry * FindFreeFileEntry(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFreeEntry = NULL;
+ TFileEntry * pFileEntry;
+
+ // Try to find a free 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;
+ }
+
+ //
+ // Note: Files with "delete marker" are not deleted.
+ // Don't consider them free entries
+ //
+ }
+
+ // 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);
+ 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)
+ {
+ // If the hash entry is already there, we reuse the file entry
+ pHash = GetHashEntryExact(ha, szFileName, lcLocale);
+ if(pHash != NULL)
+ {
+ pFileEntry = ha->pFileTable + pHash->dwBlockIndex;
+ bHashEntryExists = true;
+ }
+ }
+
+ // 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)
+ {
+ pFileEntry = ha->pFileTable + dwFileIndex;
+ bHetEntryExists = true;
+ }
+ }
+
+ // If still haven't found the file entry, we allocate new one
+ if(pFileEntry == NULL)
+ {
+ pFileEntry = FindFreeFileEntry(ha);
+ if(pFileEntry == NULL)
+ return NULL;
+ }
+
+ // 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)
+ {
+ assert(ha->dwFileTableSize < ha->dwMaxFileCount);
+ ha->pHeader->dwBlockTableSize++;
+ ha->dwFileTableSize++;
+ }
+
+ // If the MPQ has hash table, we have to insert the new entry into the hash table
+ if(ha->pHashTable != NULL && bHashEntryExists == false)
+ {
+ dwHashIndex = AllocateHashEntry(ha, pFileEntry);
+ assert(dwHashIndex != HASH_ENTRY_FREE);
+ }
+
+ // 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);
+ }
+
+ // Return the file entry
+ return pFileEntry;
+}
+
+int RenameFileEntry(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry,
+ const char * szNewFileName)
+{
+ TMPQHash * pHash;
+ DWORD dwFileIndex;
+ int nError = ERROR_SUCCESS;
+
+ // 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);
+ }
+
+ // Free the old file name
+ if(pFileEntry->szFileName != NULL)
+ STORM_FREE(pFileEntry->szFileName);
+ pFileEntry->szFileName = NULL;
+
+ // Allocate new file name
+ AllocateFileName(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
+ dwFileIndex = AllocateHashEntry(ha, pFileEntry);
+ if(dwFileIndex == HASH_ENTRY_FREE)
+ 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;
+ }
+
+ // Invalidate the entries for (listfile) and (attributes)
+ // After we are done with MPQ changes, we need to re-create them
+ InvalidateInternalFiles(ha);
+ return nError;
+}
+
+void ClearFileEntry(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry)
+{
+ TMPQHash * pHash = NULL;
+
+ // 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);
+ }
+
+ // 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));
+}
+
+int FreeFileEntry(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry)
+{
+ 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
+ //
+
+ if(ha->pHetTable == NULL)
+ {
+ TFileEntry * pLastFileEntry = ha->pFileTable + ha->dwFileTableSize - 1;
+ TFileEntry * pLastUsedEntry = pLastFileEntry;
+
+ // Zero the file entry
+ ClearFileEntry(ha, pFileEntry);
+
+ // 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++)
+ {
+ // Is that an occupied file entry?
+ if(pTempEntry->dwFlags & MPQ_FILE_EXISTS)
+ pLastUsedEntry = pTempEntry;
+ }
+
+ // 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;
+ }
+ }
+ 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;
+}
+
+void InvalidateInternalFiles(TMPQArchive * ha)
+{
+ TFileEntry * pFileEntry;
+
+ // Invalidate the (listfile), if not done yet
+ if(!(ha->dwFlags & MPQ_FLAG_INV_LISTFILE))
+ {
+ pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ FreeFileEntry(ha, pFileEntry);
+ ha->dwFlags |= MPQ_FLAG_INV_LISTFILE;
+ }
+
+ // Invalidate the (attributes), if not done yet
+ if(!(ha->dwFlags & MPQ_FLAG_INV_ATTRIBUTES))
+ {
+ pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ FreeFileEntry(ha, pFileEntry);
+ ha->dwFlags |= MPQ_FLAG_INV_ATTRIBUTES;
+ }
+
+ // Remember that the MPQ has been changed and it will be necessary
+ // to update the tables
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+}
+
+//-----------------------------------------------------------------------------
+// Functions that loads and verify MPQ data bitmap
+
+int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComplete)
+{
+ 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);
+
+ // 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)
+ {
+ LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1);
+ DWORD i;
+ bool bFileIsComplete = true;
+
+ // 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(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;
+ }
+
+ 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;
+
+ // Sanity checks
+ assert((dwHashTableSize & (dwHashTableSize - 1)) == 0);
+ assert(ha->pHashTable == NULL);
+
+ // Create the hash table
+ pHashTable = STORM_ALLOC(TMPQHash, dwHashTableSize);
+ if(pHashTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill it
+ memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash));
+ ha->pHashTable = pHashTable;
+
+ // Set the max file count, if needed
+ if(ha->pHetTable == NULL)
+ ha->dwMaxFileCount = dwHashTableSize;
+ return ERROR_SUCCESS;
+}
+
+TMPQHash * LoadHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ ULONGLONG ByteOffset;
+ TMPQHash * pHashTable;
+ DWORD dwTableSize;
+ DWORD dwCmpSize;
+ int nError;
+
+ // If the MPQ has no hash table, do nothing
+ if(pHeader->dwHashTablePos == 0 && pHeader->wHashTablePosHi == 0)
+ return NULL;
+
+ // If the hash table size is zero, do nothing
+ if(pHeader->dwHashTableSize == 0)
+ return NULL;
+
+ // Allocate buffer for the hash table
+ dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash);
+ pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize);
+ if(pHashTable == NULL)
+ return NULL;
+
+ // Compressed size of the hash table
+ dwCmpSize = (DWORD)pHeader->HashTableSize64;
+
+ //
+ // Load the table from the MPQ, with decompression
+ //
+ // Note: We will NOT check if the hash table is properly decrypted.
+ // Some MPQ protectors corrupt the hash table by rewriting part of it.
+ // Hash table, the way how it works, allows arbitrary values for unused entries.
+ //
+
+ ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
+ nError = LoadMpqTable(ha, ByteOffset, pHashTable, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE);
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pHashTable);
+ pHashTable = NULL;
+ }
+
+ // Return the hash table
+ return pHashTable;
+}
+
+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)
+{
+ TMPQExtTable * pExtTable;
+ TMPQHeader * pHeader = ha->pHeader;
+ int nError = ERROR_SUCCESS;
+
+ // If the HET table position is not NULL, we expect
+ // both HET and BET tables to be present.
+ if(pHeader->HetTablePos64 != 0)
+ {
+ // Attempt to load the HET table (Hash Extended Table)
+ pExtTable = LoadExtTable(ha, pHeader->HetTablePos64, (size_t)pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
+ if(pExtTable != NULL)
+ {
+ // If succeeded, we have to limit the maximum file count
+ // to the values saved in the HET table
+ // If loading HET table fails, we ignore the result.
+ ha->pHetTable = TranslateHetTable(pExtTable);
+ if(ha->pHetTable != NULL)
+ ha->dwMaxFileCount = ha->pHetTable->dwMaxFileCount;
+
+ STORM_FREE(pExtTable);
+ }
+
+ // If the HET hable failed to load, it's corrupt.
+ if(ha->pHetTable == NULL)
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ return nError;
+}
+
+TMPQBetTable * LoadBetTable(TMPQArchive * ha)
+{
+ TMPQExtTable * pExtTable;
+ TMPQBetTable * pBetTable = NULL;
+ TMPQHeader * pHeader = ha->pHeader;
+
+ // If the HET table position is not NULL, we expect
+ // both HET and BET tables to be present.
+ if(pHeader->BetTablePos64 != 0)
+ {
+ // Attempt to load the HET table (Hash Extended Table)
+ pExtTable = LoadExtTable(ha, pHeader->BetTablePos64, (size_t)pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE);
+ if(pExtTable != NULL)
+ {
+ // If succeeded, we translate the BET table
+ // to more readable form
+ pBetTable = TranslateBetTable(ha, pExtTable);
+ STORM_FREE(pExtTable);
+ }
+ }
+
+ return pBetTable;
+}
+
+int LoadAnyHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+
+ // 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 and/or classic hash table
+ LoadHetTable(ha);
+
+ // 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)
+ {
+ ha->dwMaxFileCount = ha->pHeader->dwBlockTableSize;
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+int BuildFileTable_Classic(
+ TMPQArchive * ha,
+ TFileEntry * pFileTable,
+ ULONGLONG FileSize)
+{
+ TFileEntry * pFileEntry;
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlockTable;
+ TMPQBlock * pBlock;
+ int nError = ERROR_SUCCESS;
+
+ // Sanity checks
+ assert(ha->pHashTable != NULL);
+
+ // Load the block table
+ pBlockTable = LoadBlockTable(ha, FileSize);
+ 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(ha->pHetTable == NULL)
+ {
+ for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
+ {
+ 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;
+ }
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ }
+
+ // Free the block table
+ STORM_FREE(pBlockTable);
+ }
+ else
+ {
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Load the hi-block table
+ if(nError == ERROR_SUCCESS && pHeader->HiBlockTablePos64 != 0)
+ {
+ ULONGLONG ByteOffset;
+ USHORT * pHiBlockTable = NULL;
+ DWORD dwTableSize = pHeader->dwBlockTableSize * sizeof(USHORT);
+
+ // Allocate space for the hi-block table
+ // Note: pHeader->dwBlockTableSize can be zero !!!
+ pHiBlockTable = STORM_ALLOC(USHORT, pHeader->dwBlockTableSize + 1);
+ if(pHiBlockTable != NULL)
+ {
+ // Load the hi-block table. It is not encrypted, nor compressed
+ ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64;
+ if(!FileStream_Read(ha->pStream, &ByteOffset, pHiBlockTable, dwTableSize))
+ nError = GetLastError();
+
+ // Now merge the hi-block table to the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ 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++;
+ }
+ }
+
+ // Free the hi-block table
+ STORM_FREE(pHiBlockTable);
+ }
+ else
+ {
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Set the current size of the file table
+ ha->dwFileTableSize = pHeader->dwBlockTableSize;
+ return nError;
+}
+
+int BuildFileTable_HetBet(
+ TMPQArchive * ha,
+ TFileEntry * pFileTable)
+{
+ TMPQHetTable * pHetTable = ha->pHetTable;
+ TMPQBetTable * pBetTable;
+ TFileEntry * pFileEntry = pFileTable;
+ TBitArray * pBitArray;
+ DWORD dwBitPosition = 0;
+ DWORD i;
+ int nError = ERROR_FILE_CORRUPT;
+
+ // Load the BET table from the MPQ
+ pBetTable = LoadBetTable(ha);
+ if(pBetTable != NULL)
+ {
+ // Step one: Fill the indexes to the HET table
+ for(i = 0; i < pHetTable->dwHashTableSize; i++)
+ {
+ DWORD dwFileIndex = 0;
+
+ // Is the entry in the HET table occupied?
+ if(pHetTable->pHetHashes[i] != 0)
+ {
+ // Load the index to the BET table
+ GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ 4);
+ // Overflow test
+ if(dwFileIndex < pBetTable->dwFileCount)
+ {
+ // Get the file entry and save HET index
+ pFileEntry = pFileTable + dwFileIndex;
+ pFileEntry->dwHetIndex = i;
+
+ // Load the BET hash
+ GetBits(pBetTable->pBetHashes, pBetTable->dwBetHashSizeTotal * dwFileIndex,
+ pBetTable->dwBetHashSize,
+ &pFileEntry->BetHash,
+ 8);
+ }
+ }
+ }
+
+ // 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++)
+ {
+ DWORD dwFlagIndex = 0;
+
+ // Read the file position
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_FilePos,
+ pBetTable->dwBitCount_FilePos,
+ &pFileEntry->ByteOffset,
+ 8);
+
+ // Read the file size
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_FileSize,
+ pBetTable->dwBitCount_FileSize,
+ &pFileEntry->dwFileSize,
+ 4);
+
+ // Read the compressed size
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_CmpSize,
+ pBetTable->dwBitCount_CmpSize,
+ &pFileEntry->dwCmpSize,
+ 4);
+
+
+ // Read the flag index
+ if(pBetTable->dwFlagCount != 0)
+ {
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_FlagIndex,
+ pBetTable->dwBitCount_FlagIndex,
+ &dwFlagIndex,
+ 4);
+
+ pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex];
+ }
+
+ //
+ // TODO: Locale (?)
+ //
+
+ // Move the current bit position
+ dwBitPosition += pBetTable->dwTableEntrySize;
+ pFileEntry++;
+ }
+
+ // Set the current size of the file table
+ ha->dwFileTableSize = pBetTable->dwFileCount;
+ FreeBetTable(pBetTable);
+ nError = ERROR_SUCCESS;
+ }
+ else
+ {
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ return nError;
+}
+
+int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize)
+{
+ TFileEntry * pFileTable;
+ bool bFileTableCreated = false;
+
+ // Sanity checks
+ assert(ha->dwFileTableSize == 0);
+ assert(ha->dwMaxFileCount != 0);
+
+ // Allocate the file table with size determined before
+ pFileTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount);
+ if(pFileTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the table with zeros
+ memset(pFileTable, 0, ha->dwMaxFileCount * sizeof(TFileEntry));
+
+ // If we have HET table, we load file table from the BET table
+ // Note: If BET table is corrupt or missing, we set the archive as read only
+ if(ha->pHetTable != NULL)
+ {
+ if(BuildFileTable_HetBet(ha, pFileTable) != ERROR_SUCCESS)
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ else
+ bFileTableCreated = true;
+ }
+
+ // If we have hash table, we load the file table from the block table
+ // 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)
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ else
+ bFileTableCreated = true;
+ }
+
+ // If something failed, we free the file table entry
+ if(bFileTableCreated == false)
+ {
+ STORM_FREE(pFileTable);
+ return ERROR_FILE_CORRUPT;
+ }
+
+ // Assign it to the archive structure
+ ha->pFileTable = pFileTable;
+ return ERROR_SUCCESS;
+}
+
+// Saves MPQ header, hash table, block table and hi-block table.
+int SaveMPQTables(TMPQArchive * ha)
+{
+ TMPQExtTable * pHetTable = NULL;
+ TMPQExtTable * pBetTable = NULL;
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlockTable = NULL;
+ TMPQHash * pHashTable = NULL;
+ ULONGLONG HetTableSize64 = 0;
+ ULONGLONG BetTableSize64 = 0;
+ ULONGLONG HashTableSize64 = 0;
+ ULONGLONG BlockTableSize64 = 0;
+ ULONGLONG HiBlockTableSize64 = 0;
+ ULONGLONG TablePos = 0; // A table position, relative to the begin of the MPQ
+ USHORT * pHiBlockTable = NULL;
+ DWORD cbTotalSize;
+ bool bNeedHiBlockTable = false;
+ int nError = ERROR_SUCCESS;
+
+ // We expect this function to be called only when tables have been changed
+ assert(ha->dwFlags & MPQ_FLAG_CHANGED);
+
+ // Find the space where the MPQ tables will be saved
+ FindFreeMpqSpace(ha, &TablePos);
+
+ // If the MPQ has HET table, we prepare a ready-to-save version
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ {
+ pHetTable = TranslateHetTable(ha->pHetTable, &HetTableSize64);
+ if(pHetTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // If the MPQ has HET table, we also must create BET table to be saved
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ {
+ pBetTable = TranslateBetTable(ha, &BetTableSize64);
+ if(pBetTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Now create hash table
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ {
+ pHashTable = TranslateHashTable(ha, &HashTableSize64);
+ if(pHashTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Create block table
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ {
+ pBlockTable = TranslateBlockTable(ha, &BlockTableSize64, &bNeedHiBlockTable);
+ if(pBlockTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Create hi-block table, if needed
+ if(nError == ERROR_SUCCESS && bNeedHiBlockTable)
+ {
+ pHiBlockTable = TranslateHiBlockTable(ha, &HiBlockTableSize64);
+ if(pHiBlockTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Write the HET table, if any
+ if(nError == ERROR_SUCCESS && pHetTable != NULL)
+ {
+ pHeader->HetTableSize64 = HetTableSize64;
+ pHeader->HetTablePos64 = TablePos;
+ nError = SaveExtTable(ha, pHetTable, TablePos, (DWORD)HetTableSize64, pHeader->MD5_HetTable, MPQ_KEY_HASH_TABLE, false, &cbTotalSize);
+ TablePos += cbTotalSize;
+ }
+
+ // Write the BET table, if any
+ if(nError == ERROR_SUCCESS && pBetTable != NULL)
+ {
+ pHeader->BetTableSize64 = BetTableSize64;
+ pHeader->BetTablePos64 = TablePos;
+ nError = SaveExtTable(ha, pBetTable, TablePos, (DWORD)BetTableSize64, pHeader->MD5_BetTable, MPQ_KEY_BLOCK_TABLE, false, &cbTotalSize);
+ TablePos += cbTotalSize;
+ }
+
+ // Write the hash table, if we have any
+ if(nError == ERROR_SUCCESS && pHashTable != NULL)
+ {
+ pHeader->HashTableSize64 = HashTableSize64;
+ pHeader->wHashTablePosHi = (USHORT)(TablePos >> 32);
+ pHeader->dwHashTableSize = (DWORD)(HashTableSize64 / sizeof(TMPQHash));
+ pHeader->dwHashTablePos = (DWORD)TablePos;
+ nError = SaveMpqTable(ha, pHashTable, TablePos, (size_t)HashTableSize64, pHeader->MD5_HashTable, MPQ_KEY_HASH_TABLE, false);
+ TablePos += HashTableSize64;
+ }
+
+ // Write the block table, if we have any
+ if(nError == ERROR_SUCCESS && pBlockTable != NULL)
+ {
+ pHeader->BlockTableSize64 = BlockTableSize64;
+ pHeader->wBlockTablePosHi = (USHORT)(TablePos >> 32);
+ pHeader->dwBlockTableSize = (DWORD)(BlockTableSize64 / sizeof(TMPQBlock));
+ pHeader->dwBlockTablePos = (DWORD)TablePos;
+ nError = SaveMpqTable(ha, pBlockTable, TablePos, (size_t)BlockTableSize64, pHeader->MD5_BlockTable, MPQ_KEY_BLOCK_TABLE, false);
+ TablePos += BlockTableSize64;
+ }
+
+ // Write the hi-block table, if we have any
+ if(nError == ERROR_SUCCESS && pHiBlockTable != NULL)
+ {
+ ULONGLONG ByteOffset = ha->MpqPos + TablePos;
+
+ pHeader->HiBlockTableSize64 = HiBlockTableSize64;
+ pHeader->HiBlockTablePos64 = TablePos;
+ BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, HiBlockTableSize64);
+
+ if(!FileStream_Write(ha->pStream, &ByteOffset, pHiBlockTable, (DWORD)HiBlockTableSize64))
+ nError = GetLastError();
+ TablePos += HiBlockTableSize64;
+ }
+
+ // Cut the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ ULONGLONG FileSize = ha->MpqPos + TablePos;
+
+ if(!FileStream_SetSize(ha->pStream, FileSize))
+ nError = GetLastError();
+ }
+
+ // Write the MPQ header
+ if(nError == ERROR_SUCCESS)
+ {
+ // Update the size of the archive
+ pHeader->ArchiveSize64 = TablePos;
+ pHeader->dwArchiveSize = (DWORD)TablePos;
+
+ // Update the MD5 of the archive header
+ CalculateDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader);
+
+ // Write the MPQ header to the file
+ BSWAP_TMPQHEADER(pHeader);
+ if(!FileStream_Write(ha->pStream, &ha->MpqPos, pHeader, pHeader->dwHeaderSize))
+ nError = GetLastError();
+ BSWAP_TMPQHEADER(pHeader);
+ }
+
+ // Clear the changed flag
+ if(nError == ERROR_SUCCESS)
+ ha->dwFlags &= ~MPQ_FLAG_CHANGED;
+
+ // Cleanup and exit
+ if(pHetTable != NULL)
+ STORM_FREE(pHetTable);
+ if(pBetTable != NULL)
+ STORM_FREE(pBetTable);
+ if(pHashTable != NULL)
+ STORM_FREE(pHashTable);
+ if(pBlockTable != NULL)
+ STORM_FREE(pBlockTable);
+ if(pHiBlockTable != NULL)
+ STORM_FREE(pHiBlockTable);
+ return nError;
+}