aboutsummaryrefslogtreecommitdiff
path: root/src/SBaseFileTable.cpp
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2013-12-05 15:59:00 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2013-12-05 15:59:00 +0100
commitc34c37b3418f1e5ab3678ce65d46f81803dec91d (patch)
tree4a9cf4c61634691981f9dc367b53dac4070f8d0d /src/SBaseFileTable.cpp
parentff0c25952a28a927c48738ab5207b9bda69e588a (diff)
+ StormLib 9.0 BETA
Diffstat (limited to 'src/SBaseFileTable.cpp')
-rw-r--r--src/SBaseFileTable.cpp1710
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))