aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorunknown <E:\Ladik\Mail>2015-05-01 07:06:29 +0200
committerunknown <E:\Ladik\Mail>2015-05-01 07:06:29 +0200
commit46930855f500c1b494e3b16bb7a3323c07d4d5fb (patch)
tree6220cc643761137a930841d8ec828db9f3db53cf /src
parenta205159d004871efbedd7cbfb686b8fe82bfb532 (diff)
+ Removed back reference of FileTable -> HashTable, as it is logically incorrect
+ Optimized patching process so it consimes less memory + Added hash table and block table defragmenting for malformed War3 maps
Diffstat (limited to 'src')
-rw-r--r--src/SBaseCommon.cpp16
-rw-r--r--src/SBaseDumpData.cpp52
-rw-r--r--src/SBaseFileTable.cpp1032
-rw-r--r--src/SFileAddFile.cpp245
-rw-r--r--src/SFileAttributes.cpp63
-rw-r--r--src/SFileCompactArchive.cpp33
-rw-r--r--src/SFileCreateArchive.cpp12
-rw-r--r--src/SFileFindFile.cpp16
-rw-r--r--src/SFileGetFileInfo.cpp43
-rw-r--r--src/SFileListFile.cpp24
-rw-r--r--src/SFileOpenArchive.cpp61
-rw-r--r--src/SFileOpenFileEx.cpp316
-rw-r--r--src/SFilePatchArchives.cpp747
-rw-r--r--src/SFileReadFile.cpp30
-rw-r--r--src/SFileVerify.cpp11
-rw-r--r--src/StormCommon.h62
-rw-r--r--src/StormLib.h65
-rw-r--r--src/huffman/huff.cpp3
18 files changed, 1353 insertions, 1478 deletions
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index 062a737..b4d62e0 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -97,7 +97,7 @@ unsigned char AsciiToUpperTable_Slash[256] =
#define STORM_BUFFER_SIZE 0x500
-#define HASH_INDEX_MASK(ha) (ha->dwHashTableSize ? (ha->dwHashTableSize - 1) : 0)
+#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0)
static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine
static bool bMpqCryptographyInitialized = false;
@@ -624,7 +624,8 @@ TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash *
// Allocates an entry in the hash table
TMPQHash * AllocateHashEntry(
TMPQArchive * ha,
- TFileEntry * pFileEntry)
+ TFileEntry * pFileEntry,
+ LCID lcLocale)
{
TMPQHash * pHash;
DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX);
@@ -632,18 +633,15 @@ TMPQHash * AllocateHashEntry(
DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B);
// Attempt to find a free hash entry
- pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, pFileEntry->lcLocale);
+ pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, lcLocale);
if(pHash != NULL)
{
// Fill the free hash entry
pHash->dwName1 = dwName1;
pHash->dwName2 = dwName2;
- pHash->lcLocale = pFileEntry->lcLocale;
- pHash->wPlatform = pFileEntry->wPlatform;
+ pHash->lcLocale = (USHORT)lcLocale;
+ pHash->wPlatform = 0;
pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
-
- // Fill the hash index in the file entry
- pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
}
return pHash;
@@ -1405,8 +1403,6 @@ void FreeFileHandle(TMPQFile *& hf)
// Then free all buffers allocated in the file structure
if(hf->pbFileData != NULL)
STORM_FREE(hf->pbFileData);
- if(hf->pPatchHeader != NULL)
- STORM_FREE(hf->pPatchHeader);
if(hf->pPatchInfo != NULL)
STORM_FREE(hf->pPatchInfo);
if(hf->SectorOffsets != NULL)
diff --git a/src/SBaseDumpData.cpp b/src/SBaseDumpData.cpp
index 7056d8b..d2b1206 100644
--- a/src/SBaseDumpData.cpp
+++ b/src/SBaseDumpData.cpp
@@ -41,6 +41,26 @@ void DumpMpqHeader(TMPQHeader * pHeader)
printf("-----------------------------------------------\n\n");
}
+void DumpHashTable(TMPQHash * pHashTable, DWORD dwHashTableSize)
+{
+ DWORD i;
+
+ if(pHashTable == NULL || dwHashTableSize == 0)
+ return;
+
+ printf("== Hash Table =================================\n");
+ for(i = 0; i < dwHashTableSize; i++)
+ {
+ printf("[%08x] %08X %08X %04X %04X %08X\n", i,
+ pHashTable[i].dwName1,
+ pHashTable[i].dwName2,
+ pHashTable[i].lcLocale,
+ pHashTable[i].wPlatform,
+ pHashTable[i].dwBlockIndex);
+ }
+ printf("-----------------------------------------------\n\n");
+}
+
void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable)
{
DWORD i;
@@ -49,14 +69,14 @@ void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable)
return;
printf("== HET Header =================================\n");
- printf("ULONGLONG AndMask64 = %016llX\n", pHetTable->AndMask64);
- printf("ULONGLONG OrMask64 = %016llX\n", pHetTable->OrMask64);
+ printf("ULONGLONG AndMask64 = %016llX\n", pHetTable->AndMask64);
+ printf("ULONGLONG OrMask64 = %016llX\n", pHetTable->OrMask64);
+ printf("DWORD dwEntryCount = %08X\n", pHetTable->dwEntryCount);
+ printf("DWORD dwTotalCount = %08X\n", pHetTable->dwTotalCount);
+ printf("DWORD dwNameHashBitSize = %08X\n", pHetTable->dwNameHashBitSize);
printf("DWORD dwIndexSizeTotal = %08X\n", pHetTable->dwIndexSizeTotal);
printf("DWORD dwIndexSizeExtra = %08X\n", pHetTable->dwIndexSizeExtra);
printf("DWORD dwIndexSize = %08X\n", pHetTable->dwIndexSize);
- printf("DWORD dwMaxFileCount = %08X\n", pHetTable->dwMaxFileCount);
- printf("DWORD dwHashTableSize = %08X\n", pHetTable->dwHashTableSize);
- printf("DWORD dwHashBitSize = %08X\n", pHetTable->dwHashBitSize);
printf("-----------------------------------------------\n\n");
printf("== BET Header =================================\n");
@@ -71,17 +91,17 @@ void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable)
printf("DWORD dwBitCount_CmpSize = %08X\n", pBetTable->dwBitCount_CmpSize);
printf("DWORD dwBitCount_FlagIndex = %08X\n", pBetTable->dwBitCount_FlagIndex);
printf("DWORD dwBitCount_Unknown = %08X\n", pBetTable->dwBitCount_Unknown);
- printf("DWORD dwBetHashSizeTotal = %08X\n", pBetTable->dwBetHashSizeTotal);
- printf("DWORD dwBetHashSizeExtra = %08X\n", pBetTable->dwBetHashSizeExtra);
- printf("DWORD dwBetHashSize = %08X\n", pBetTable->dwBetHashSize);
- printf("DWORD dwMaxFileCount = %08X\n", pBetTable->dwMaxFileCount);
+ printf("DWORD dwBitTotal_NameHash2 = %08X\n", pBetTable->dwBitTotal_NameHash2);
+ printf("DWORD dwBitExtra_NameHash2 = %08X\n", pBetTable->dwBitExtra_NameHash2);
+ printf("DWORD dwBitCount_NameHash2 = %08X\n", pBetTable->dwBitCount_NameHash2);
+ printf("DWORD dwEntryCount = %08X\n", pBetTable->dwEntryCount);
printf("DWORD dwFlagCount = %08X\n", pBetTable->dwFlagCount);
printf("-----------------------------------------------\n\n");
printf("== HET & Bet Table ======================================================================\n\n");
printf("HetIdx HetHash BetIdx BetHash ByteOffset FileSize CmpSize FlgIdx Flags \n");
printf("------ ------- ------ ---------------- ---------------- -------- -------- ------ --------\n");
- for(i = 0; i < pHetTable->dwHashTableSize; i++)
+ for(i = 0; i < pHetTable->dwTotalCount; i++)
{
ULONGLONG ByteOffset = 0;
ULONGLONG BetHash = 0;
@@ -96,14 +116,14 @@ void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable)
&dwBetIndex,
4);
- if(dwBetIndex < pHetTable->dwMaxFileCount)
+ if(dwBetIndex < pHetTable->dwTotalCount)
{
DWORD dwEntryIndex = pBetTable->dwTableEntrySize * dwBetIndex;
- GetBits(pBetTable->pBetHashes, dwBetIndex * pBetTable->dwBetHashSizeTotal,
- pBetTable->dwBetHashSize,
- &BetHash,
- 8);
+ GetBits(pBetTable->pNameHashes, dwBetIndex * pBetTable->dwBitTotal_NameHash2,
+ pBetTable->dwBitCount_NameHash2,
+ &BetHash,
+ 8);
GetBits(pBetTable->pFileTable, dwEntryIndex + pBetTable->dwBitIndex_FilePos,
pBetTable->dwBitCount_FilePos,
@@ -129,7 +149,7 @@ void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable)
}
printf(" %04X %02lX %04X %016llX %016llX %08X %08X %04X %08X\n", i,
- pHetTable->pHetHashes[i],
+ pHetTable->pNameHashes[i],
dwBetIndex,
BetHash,
ByteOffset,
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index 97ecb53..9e6d4b6 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -372,9 +372,9 @@ int ConvertMpqHeaderToFormat4(
//
Label_ArchiveVersion1:
- if(pHeader->dwHashTablePos <= pHeader->dwHeaderSize)
+ if(pHeader->dwHashTablePos <= pHeader->dwHeaderSize || (pHeader->dwHashTablePos & 0x80000000))
ha->dwFlags |= MPQ_FLAG_MALFORMED;
- if(pHeader->dwBlockTablePos <= pHeader->dwHeaderSize)
+ if(pHeader->dwBlockTablePos <= pHeader->dwHeaderSize || (pHeader->dwBlockTablePos & 0x80000000))
ha->dwFlags |= MPQ_FLAG_MALFORMED;
// Fill the rest of the header
@@ -568,42 +568,35 @@ int ConvertMpqHeaderToFormat4(
//-----------------------------------------------------------------------------
// Support for hash table
-static bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash)
+// Hash entry verification when the file table does not exist yet
+static bool IsValidHashEntry1(TMPQArchive * ha, TMPQHash * pHash, TMPQBlock * pBlockTable)
{
- // The block table index must be smaller than file table size
- // The block table entry's flags must have MPQ_FILE_EXISTS set
- return (
- (pHash->dwBlockIndex < ha->dwFileTableSize) &&
- (ha->pFileTable[pHash->dwBlockIndex].dwFlags & MPQ_FILE_EXISTS_MASK) == MPQ_FILE_EXISTS
- );
-}
-
-// 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;
+ ULONGLONG ByteOffset;
+ TMPQBlock * pBlock = pBlockTable + pHash->dwBlockIndex;
- // Parse the found hashes
- while(pHash != NULL)
+ // Storm.dll does not perform this check. However, if there will
+ // be an entry with (dwBlockIndex > dwBlockTableSize), the game would crash
+ // Hence we assume that dwBlockIndex must be less than dwBlockTableSize
+ if(pHash->dwBlockIndex < ha->pHeader->dwBlockTableSize)
{
- // 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);
+ // Check whether this is an existing file
+ // Also we do not allow to be file size greater than 2GB
+ if((pBlock->dwFlags & MPQ_FILE_EXISTS) && (pBlock->dwFSize & 0x8000000) == 0)
+ {
+ // The begin of the file must be within the archive
+ ByteOffset = FileOffsetFromMpqOffset(ha, pBlock->dwFilePos);
+ return (ByteOffset < ha->FileSize);
+ }
}
- // At the end, return neutral hash (if found), otherwise NULL
- return (pHashNeutral != NULL) ? pHashNeutral : pHashAny;
+ return false;
+}
+
+// Hash entry verification when the file table does not exist yet
+static bool IsValidHashEntry2(TMPQArchive * ha, TMPQHash * pHash)
+{
+ TFileEntry * pFileEntry = ha->pFileTable + pHash->dwBlockIndex;
+ return ((pHash->dwBlockIndex < ha->dwFileTableSize) && (pFileEntry->dwFlags & MPQ_FILE_EXISTS)) ? true : false;
}
// Returns a hash table entry in the following order:
@@ -620,7 +613,7 @@ static TMPQHash * GetHashEntryLocale(TMPQArchive * ha, const char * szFileName,
while(pHash != NULL)
{
// If the locales match, return it
- if(pHash->lcLocale == lcLocale)
+ if(lcLocale == 0 && lcLocale == pHash->lcLocale)
return pHash;
// If we found neutral hash, remember it
@@ -658,78 +651,172 @@ static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, L
return NULL;
}
-static int BuildFileTableFromBlockTable(
+// Defragment the file table so it does not contain any gaps
+// Note: As long as all values of all TMPQHash::dwBlockIndex
+// are not HASH_ENTRY_FREE, the startup search index does not matter.
+// Hash table is circular, so as long as there is no terminator,
+// all entries will be found.
+static TMPQHash * DefragmentHashTable(
TMPQArchive * ha,
- TFileEntry * pFileTable,
+ TMPQHash * pHashTable,
TMPQBlock * pBlockTable)
{
- TFileEntry * pFileEntry;
TMPQHeader * pHeader = ha->pHeader;
- TMPQBlock * pBlock;
- TMPQHash * pHashEnd = ha->pHashTable + ha->dwHashTableSize;
- TMPQHash * pHash;
+ TMPQHash * pHashTableEnd = pHashTable + pHeader->dwHashTableSize;
+ TMPQHash * pSource = pHashTable;
+ TMPQHash * pTarget = pHashTable;
+ DWORD dwFirstFreeEntry;
+ DWORD dwNewTableSize;
- // Parse the entire hash table
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
- {
- //
- // Only if the block index is less than block table size.
- // An MPQ protector can place multiple invalid entries
- // into the hash table:
- //
- // a6d79af0 e61a0932 001e0000 0000770b <== Fake valid
- // a6d79af0 e61a0932 0000d761 0000dacb <== Fake valid
- // a6d79af0 e61a0932 00000000 0000002f <== Real file entry (block index is valid)
- // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid
- //
+ // Sanity checks
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1);
+ assert(pHeader->HiBlockTablePos64 == 0);
- if(pHash->dwBlockIndex < pHeader->dwBlockTableSize)
+ // Parse the hash table and move the entries to the begin of it
+ for(pSource = pHashTable; pSource < pHashTableEnd; pSource++)
+ {
+ // Check whether this is a valid hash table entry
+ if(IsValidHashEntry1(ha, pSource, pBlockTable))
{
- // Get the pointer to the file entry and the block entry
- pFileEntry = pFileTable + pHash->dwBlockIndex;
- pBlock = pBlockTable + pHash->dwBlockIndex;
+ // Copy the hash table entry back
+ if(pSource > pTarget)
+ pTarget[0] = pSource[0];
- // Only if the block table entry contains valid file
- if((pBlock->dwFlags & MPQ_FILE_EXISTS) && !(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS))
- {
- // ByteOffset is only valid if file size is not zero
- pFileEntry->ByteOffset = pBlock->dwFilePos;
- if(pFileEntry->ByteOffset == 0 && pBlock->dwFSize == 0)
- pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize;
-
- 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;
- }
+ // Move the target
+ pTarget++;
}
}
- return ERROR_SUCCESS;
+ // Calculate how many entries in the hash table we really need
+ dwFirstFreeEntry = (DWORD)(pTarget - pHashTable);
+ dwNewTableSize = GetHashTableSizeForFileCount(dwFirstFreeEntry);
+
+ // Fill the rest with entries that look like deleted
+ pHashTableEnd = pHashTable + dwNewTableSize;
+ pSource = pHashTable + dwFirstFreeEntry;
+ memset(pSource, 0xFF, (dwNewTableSize - dwFirstFreeEntry) * sizeof(TMPQHash));
+
+ // Mark the block indexes as deleted
+ for(; pSource < pHashTableEnd; pSource++)
+ pSource->dwBlockIndex = HASH_ENTRY_DELETED;
+
+ // Free some of the space occupied by the hash table
+ if(dwNewTableSize < pHeader->dwHashTableSize)
+ {
+ pHashTable = STORM_REALLOC(TMPQHash, pHashTable, dwNewTableSize);
+ ha->pHeader->dwHashTableSize = dwNewTableSize;
+ }
+
+ return pHashTable;
}
-static int UpdateFileTableFromHashTable(
+static int BuildFileTableFromBlockTable(
TMPQArchive * ha,
- TFileEntry * pFileTable)
+ TMPQBlock * pBlockTable)
{
TFileEntry * pFileEntry;
- TMPQHash * pHashEnd = ha->pHashTable + ha->dwHashTableSize;
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlock;
+ TMPQHash * pHashTableEnd;
TMPQHash * pHash;
+ LPDWORD DefragmentTable = NULL;
+ DWORD dwItemCount = 0;
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
+ // Sanity checks
+ assert(ha->pFileTable != NULL);
+ assert(ha->dwFileTableSize >= ha->dwMaxFileCount);
+
+ // Defragment the hash table, if needed
+ if(ha->dwFlags & MPQ_FLAG_HASH_TABLE_CUT)
{
- if(pHash->dwBlockIndex < ha->dwFileTableSize)
+ ha->pHashTable = DefragmentHashTable(ha, ha->pHashTable, pBlockTable);
+ ha->dwMaxFileCount = pHeader->dwHashTableSize;
+ }
+
+ // If the hash table or block table is cut,
+ // we will defragment the block table
+ if(ha->dwFlags & (MPQ_FLAG_HASH_TABLE_CUT | MPQ_FLAG_BLOCK_TABLE_CUT))
+ {
+ // Sanity checks
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1);
+ assert(pHeader->HiBlockTablePos64 == 0);
+
+ // Allocate the translation table
+ DefragmentTable = STORM_ALLOC(DWORD, pHeader->dwBlockTableSize);
+ if(DefragmentTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the translation table
+ memset(DefragmentTable, 0xFF, pHeader->dwBlockTableSize * sizeof(DWORD));
+ }
+
+ // Parse the entire hash table
+ pHashTableEnd = ha->pHashTable + pHeader->dwHashTableSize;
+ for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
+ {
+ DWORD dwBlockIndex = pHash->dwBlockIndex;
+ DWORD dwNewIndex = pHash->dwBlockIndex;
+
+ //
+ // We need to properly handle these cases:
+ // - Multiple hash entries (same file name) point to the same block entry
+ // - Multiple hash entries (different file name) point to the same block entry
+ //
+ // Ignore all hash table entries where:
+ // - dwBlockIndex >= BlockTableSize
+ // - Flags of the appropriate block table entry
+
+ if(IsValidHashEntry1(ha, pHash, pBlockTable))
{
- pFileEntry = pFileTable + pHash->dwBlockIndex;
- if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ // Determine the new block index
+ if(DefragmentTable != NULL)
{
- pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
- pFileEntry->lcLocale = pHash->lcLocale;
- pFileEntry->wPlatform = pHash->wPlatform;
+ // Need to handle case when multile hash
+ // entries point to the same block entry
+ if(DefragmentTable[dwBlockIndex] == HASH_ENTRY_FREE)
+ {
+ DefragmentTable[dwBlockIndex] = dwItemCount;
+ dwNewIndex = dwItemCount++;
+ }
+ else
+ {
+ dwNewIndex = DefragmentTable[dwBlockIndex];
+ }
+
+ // Fix the pointer in the hash entry
+ pHash->dwBlockIndex = dwNewIndex;
}
+
+ // Get the pointer to the file entry and the block entry
+ pFileEntry = ha->pFileTable + dwNewIndex;
+ pBlock = pBlockTable + dwNewIndex;
+
+ // ByteOffset is only valid if file size is not zero
+ pFileEntry->ByteOffset = pBlock->dwFilePos;
+ if(pFileEntry->ByteOffset == 0 && pBlock->dwFSize == 0)
+ pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize;
+
+ // Fill the rest of the file entry
+ pFileEntry->dwFileSize = pBlock->dwFSize;
+ pFileEntry->dwCmpSize = pBlock->dwCSize;
+ pFileEntry->dwFlags = pBlock->dwFlags;
+ }
+ }
+
+ // Free the translation table
+ if(DefragmentTable != NULL)
+ {
+ // If we defragmented the block table in the process,
+ // free some memory by shrinking the file table
+ if(ha->dwFileTableSize > ha->dwMaxFileCount)
+ {
+ ha->pFileTable = STORM_REALLOC(TFileEntry, ha->pFileTable, ha->dwMaxFileCount);
+ ha->pHeader->dwBlockTableSize = ha->dwMaxFileCount;
+ ha->dwFileTableSize = ha->dwMaxFileCount;
}
+
+ // Free the translation table
+ STORM_FREE(DefragmentTable);
}
return ERROR_SUCCESS;
@@ -743,11 +830,11 @@ static TMPQHash * TranslateHashTable(
size_t HashTableSize;
// Allocate copy of the hash table
- pHashTable = STORM_ALLOC(TMPQHash, ha->dwHashTableSize);
+ pHashTable = STORM_ALLOC(TMPQHash, ha->pHeader->dwHashTableSize);
if(pHashTable != NULL)
{
// Copy the hash table
- HashTableSize = sizeof(TMPQHash) * ha->dwHashTableSize;
+ HashTableSize = sizeof(TMPQHash) * ha->pHeader->dwHashTableSize;
memcpy(pHashTable, ha->pHashTable, HashTableSize);
// Give the size to the caller
@@ -769,18 +856,17 @@ TMPQBlock * TranslateBlockTable(
TFileEntry * pFileEntry = ha->pFileTable;
TMPQBlock * pBlockTable;
TMPQBlock * pBlock;
- size_t NeedHiBlockTable = 0;
- size_t BlockTableSize;
+ DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize;
+ DWORD NeedHiBlockTable = 0;
// Allocate copy of the hash table
- pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, ha->dwFileTableSize);
+ pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, dwBlockTableSize);
if(pBlockTable != NULL)
{
- // Copy the block table
- BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize;
- for(DWORD i = 0; i < ha->dwFileTableSize; i++)
+ // Convert the block table
+ for(DWORD i = 0; i < dwBlockTableSize; i++)
{
- NeedHiBlockTable |= (pFileEntry->ByteOffset >> 32);
+ NeedHiBlockTable |= (DWORD)(pFileEntry->ByteOffset >> 32);
pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset;
pBlock->dwFSize = pFileEntry->dwFileSize;
pBlock->dwCSize = pFileEntry->dwCmpSize;
@@ -792,7 +878,7 @@ TMPQBlock * TranslateBlockTable(
// Give the size to the caller
if(pcbTableSize != NULL)
- *pcbTableSize = (ULONGLONG)BlockTableSize;
+ *pcbTableSize = (ULONGLONG)dwBlockTableSize * sizeof(TMPQBlock);
if(pbNeedHiBlockTable != NULL)
*pbNeedHiBlockTable = NeedHiBlockTable ? true : false;
@@ -808,20 +894,19 @@ static USHORT * TranslateHiBlockTable(
TFileEntry * pFileEntry = ha->pFileTable;
USHORT * pHiBlockTable;
USHORT * pHiBlock;
- size_t HiBlockTableSize;
+ DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize;
// Allocate copy of the hash table
- pHiBlockTable = pHiBlock = STORM_ALLOC(USHORT, ha->dwFileTableSize);
+ pHiBlockTable = pHiBlock = STORM_ALLOC(USHORT, dwBlockTableSize);
if(pHiBlockTable != NULL)
{
// Copy the block table
- HiBlockTableSize = sizeof(USHORT) * ha->dwFileTableSize;
- for(DWORD i = 0; i < ha->dwFileTableSize; i++)
+ for(DWORD i = 0; i < dwBlockTableSize; i++)
pHiBlock[i] = (USHORT)(pFileEntry[i].ByteOffset >> 0x20);
// Give the size to the caller
if(pcbTableSize != NULL)
- *pcbTableSize = (ULONGLONG)HiBlockTableSize;
+ *pcbTableSize = (ULONGLONG)dwBlockTableSize * sizeof(USHORT);
}
return pHiBlockTable;
@@ -1267,7 +1352,7 @@ static TMPQExtHeader * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * p
return &pHetHeader->ExtHdr;
}
-DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
+static DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
{
TMPQHetTable * pHetTable = ha->pHetTable;
ULONGLONG FileNameHash;
@@ -1306,17 +1391,13 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
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);
- //
+ sizeof(DWORD));
// 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 HET table
@@ -1368,6 +1449,7 @@ static void CreateBetHeader(
pBetHeader->ExtHdr.dwDataSize = 0;
// Get the maximum values for the BET table
+ pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize;
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
//
@@ -1416,7 +1498,7 @@ static void CreateBetHeader(
pBetHeader->dwBitCount_Unknown;
// Save the file count and flag count
- pBetHeader->dwEntryCount = ha->dwFileTableSize;
+ pBetHeader->dwEntryCount = ha->pHeader->dwBlockTableSize;
pBetHeader->dwFlagCount = dwMaxFlagIndex + 1;
pBetHeader->dwUnknown08 = 0x10;
@@ -1577,6 +1659,7 @@ TMPQExtHeader * TranslateBetTable(
DWORD nBitOffset = 0;
// Construct the bit-based file table
+ pFileTableEnd = ha->pFileTable + BetHeader.dwEntryCount;
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
//
@@ -1678,11 +1761,25 @@ void FreeBetTable(TMPQBetTable * pBetTable)
//-----------------------------------------------------------------------------
// Support for file table
-TFileEntry * GetFileEntryAny(TMPQArchive * ha, const char * szFileName)
+TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex)
{
TMPQHash * pHash;
DWORD dwFileIndex;
+ // First, we have to search the classic hash table
+ // This is because on renaming, deleting, or changing locale,
+ // we will need the pointer to hash table entry
+ if(ha->pHashTable != NULL)
+ {
+ pHash = GetHashEntryLocale(ha, szFileName, lcLocale);
+ if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize)
+ {
+ if(PtrHashIndex != NULL)
+ PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable);
+ return ha->pFileTable + pHash->dwBlockIndex;
+ }
+ }
+
// If we have HET table in the MPQ, try to find the file in HET table
if(ha->pHetTable != NULL)
{
@@ -1691,76 +1788,48 @@ TFileEntry * GetFileEntryAny(TMPQArchive * ha, const char * szFileName)
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)
{
+ return GetFileEntryLocale2(ha, szFileName, lcLocale, NULL);
+}
+
+TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex)
+{
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 the hash table is present, find the entry from hash table
if(ha->pHashTable != NULL)
{
- pHash = GetHashEntryLocale(ha, szFileName, lcLocale);
+ pHash = GetHashEntryExact(ha, szFileName, lcLocale);
if(pHash != NULL && pHash->dwBlockIndex < ha->dwFileTableSize)
+ {
+ if(PtrHashIndex != NULL)
+ PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable);
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)
+ {
+ if(PtrHashIndex != NULL)
+ PtrHashIndex[0] = 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(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName)
{
// Sanity check
@@ -1792,99 +1861,103 @@ void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * sz
}
}
-TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex)
{
- TFileEntry * pFileTableEnd;
- TFileEntry * pFileTablePtr;
- TFileEntry * pFileTable = ha->pFileTable;
- TFileEntry * pLastEntry = ha->pFileTable;
- TFileEntry * pFileEntry = NULL;
- TMPQHash * pHash;
- DWORD dwFreeNeeded = ha->dwReservedFiles;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFreeEntry = NULL;
+ TFileEntry * pFileEntry;
+ TMPQHash * pHash = NULL;
+ DWORD dwReservedFiles = ha->dwReservedFiles;
DWORD dwFreeCount = 0;
- // 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);
+ // Sanity check: File table size must be greater or equal to max file count
+ assert(ha->dwFileTableSize >= ha->dwMaxFileCount);
- // Determine the end of the file table
- pFileTableEnd = ha->pFileTable + STORMLIB_MAX(ha->dwFileTableSize, ha->dwMaxFileCount);
+ // If we are saving MPQ tables, we don't tale number of reserved files into account
+ dwReservedFiles = (ha->dwFlags & MPQ_FLAG_SAVING_TABLES) ? 0 : ha->dwReservedFiles;
- // Find the first free file entry
- for(pFileTablePtr = pFileTable; pFileTablePtr < pFileTableEnd; pFileTablePtr++)
+ // Now find a free entry in the file table.
+ // Note that in the case when free entries are in the middle,
+ // we need to use these
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
- // Is that entry free?
- // Note: Files with "delete marker" are not deleted.
- // Don't treat them as available
- if((pFileTablePtr->dwFlags & MPQ_FILE_EXISTS) == 0)
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
{
- if(pFileEntry == NULL)
- pFileEntry = pFileTablePtr;
+ // Remember the first free entry
+ if(pFreeEntry == NULL)
+ pFreeEntry = pFileEntry;
dwFreeCount++;
- }
- else
- {
- // Update the last used entry
- if(pFileTablePtr > pLastEntry)
- pLastEntry = pFileTablePtr;
+
+ // If the number of free items is greater than number
+ // of reserved items, We can add the file
+ if(dwFreeCount > dwReservedFiles)
+ break;
}
}
- // Did we find an usable file entry?
- if(pFileEntry != NULL)
+ // If the total number of free entries is less than number of reserved files,
+ // we cannot add the file to the archive
+ if(pFreeEntry == NULL || dwFreeCount <= dwReservedFiles)
+ return NULL;
+
+ // Initialize the file entry and set its file name
+ memset(pFreeEntry, 0, sizeof(TFileEntry));
+ AllocateFileName(ha, pFreeEntry, szFileName);
+
+ // If the archive has a hash table, we need to first free entry there
+ if(ha->pHashTable != NULL)
{
- // Is there enough free entries?
- if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES))
- dwFreeNeeded++;
- if(dwFreeCount < dwFreeNeeded)
- return NULL;
+ // Make sure that the entry is not there yet
+ assert(GetHashEntryExact(ha, szFileName, lcLocale) == NULL);
- // Make sure that the entry is properly initialized
- memset(pFileEntry, 0, sizeof(TFileEntry));
- pFileEntry->lcLocale = (USHORT)lcLocale;
- AllocateFileName(ha, pFileEntry, szFileName);
+ // Find a free hash table entry for the name
+ pHash = AllocateHashEntry(ha, pFreeEntry, lcLocale);
+ if(pHash == NULL)
+ return NULL;
- // 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);
- }
+ // Set the file index to the hash table
+ pHash->dwBlockIndex = (DWORD)(pFreeEntry - ha->pFileTable);
+ PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable);
+ }
- // Update the file table size
- if(pFileEntry >= pLastEntry)
- pLastEntry = pFileEntry;
- ha->dwFileTableSize = (DWORD)(pLastEntry - pFileTable) + 1;
+ // If the archive has a HET table, just do some checks
+ // Note: Don't bother modifying the HET table. It will be rebuilt from scratch after, anyway
+ if(ha->pHetTable != NULL)
+ {
+ assert(GetFileIndex_Het(ha, szFileName) == HASH_ENTRY_FREE);
}
- // Return the file entry
- return pFileEntry;
+ // Return the free table entry
+ return pFreeEntry;
}
int RenameFileEntry(
TMPQArchive * ha,
- TFileEntry * pFileEntry,
+ TMPQFile * hf,
const char * szNewFileName)
{
- TMPQHash * pHash;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ TMPQHash * pHashEntry = hf->pHashEntry;
+ LCID lcLocale = 0;
- // Mark the entry as deleted in the hash table
+ // If the archive hash hash table, we need to free the hash table entry
if(ha->pHashTable != NULL)
{
- assert(pFileEntry->dwHashIndex < ha->dwHashTableSize);
+ // The file must have hash table entry assigned
+ // Will exit if there are multiple HASH entries pointing to the same file entry
+ if(pHashEntry == NULL)
+ return ERROR_NOT_SUPPORTED;
- pHash = ha->pHashTable + pFileEntry->dwHashIndex;
- memset(pHash, 0xFF, sizeof(TMPQHash));
- pHash->dwBlockIndex = HASH_ENTRY_DELETED;
- }
+ // Save the locale
+ lcLocale = pHashEntry->lcLocale;
- //
- // Note: Don't bother with the HET table.
- // It will be rebuilt from scratch anyway
- //
+ // Mark the hash table entry as deleted
+ pHashEntry->dwName1 = 0xFFFFFFFF;
+ pHashEntry->dwName2 = 0xFFFFFFFF;
+ pHashEntry->lcLocale = 0xFFFF;
+ pHashEntry->wPlatform = 0xFFFF;
+ pHashEntry->dwBlockIndex = HASH_ENTRY_DELETED;
+ }
// Free the old file name
if(pFileEntry->szFileName != NULL)
@@ -1894,44 +1967,36 @@ int RenameFileEntry(
// Allocate new file name
AllocateFileName(ha, pFileEntry, szNewFileName);
- // Now find a hash entry for the new file name
+ // Allocate new hash entry
if(ha->pHashTable != NULL)
{
- // Try to find the hash table entry for the new file name
- // Note: Since we deleted one hash entry, this will always succeed
- pHash = AllocateHashEntry(ha, pFileEntry);
- assert(pHash != NULL);
+ // Since we freed one hash entry before, this must succeed
+ hf->pHashEntry = AllocateHashEntry(ha, pFileEntry, lcLocale);
+ assert(hf->pHashEntry != NULL);
}
- // Invalidate the entries for (listfile) and (attributes)
- // After we are done with MPQ changes, we need to re-create them
- InvalidateInternalFiles(ha);
return ERROR_SUCCESS;
}
-void DeleteFileEntry(
- TMPQArchive * ha,
- TFileEntry * pFileEntry)
+int DeleteFileEntry(TMPQArchive * ha, TMPQFile * hf)
{
- TMPQHash * pHash;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ TMPQHash * pHashEntry = hf->pHashEntry;
- // If the MPQ has classic hash table, clear the entry there
+ // If the archive hash hash table, we need to free the hash table entry
if(ha->pHashTable != NULL)
{
- // 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->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;
- }
+ // The file must have hash table entry assigned
+ // Will exit if there are multiple HASH entries pointing to the same file entry
+ if(pHashEntry == NULL)
+ return ERROR_NOT_SUPPORTED;
+
+ // Mark the hash table entry as deleted
+ pHashEntry->dwName1 = 0xFFFFFFFF;
+ pHashEntry->dwName2 = 0xFFFFFFFF;
+ pHashEntry->lcLocale = 0xFFFF;
+ pHashEntry->wPlatform = 0xFFFF;
+ pHashEntry->dwBlockIndex = HASH_ENTRY_DELETED;
}
// Free the file name, and set the file entry as deleted
@@ -1940,67 +2005,73 @@ void DeleteFileEntry(
pFileEntry->szFileName = NULL;
//
- // Don't modify the HET table, because it gets recreated from scratch at every modification operation
+ // Don't modify the HET table, because it gets recreated by the caller
// 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.
+ // Keep Byte Offset, file size, compressed size, CRC32 and MD5
+ // Clear the file name hash and the MPQ_FILE_EXISTS bit
//
- pFileEntry->FileNameHash = 0;
pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS;
+ pFileEntry->FileNameHash = 0;
+ return ERROR_SUCCESS;
}
-void InvalidateInternalFiles(TMPQArchive * ha)
+DWORD InvalidateInternalFile(TMPQArchive * ha, const char * szFileName, DWORD dwFlagNone, DWORD dwFlagNew)
{
- TFileEntry * pFileEntry1 = NULL;
- TFileEntry * pFileEntry2 = NULL;
- TFileEntry * pFileEntry3 = NULL;
+ TMPQFile * hf = NULL;
+ DWORD dwFileFlags = 0;
+ int nError = ERROR_FILE_NOT_FOUND;
+
+ // Open the file from the MPQ
+ if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
+ {
+ // Remember the file flags
+ dwFileFlags = hf->pFileEntry->dwFlags;
+
+ // Delete the file entry
+ nError = DeleteFileEntry(ha, hf);
+ if(nError == ERROR_SUCCESS)
+ {
+ ha->dwFlags |= dwFlagNew;
+ ha->dwReservedFiles++;
+ }
+ // Free the file entry
+ FreeFileHandle(hf);
+ }
+
+ // If the deletion failed, set the "none" flag
+ ha->dwFlags |= (nError != ERROR_SUCCESS) ? dwFlagNone : 0;
+ return dwFileFlags;
+}
+
+void InvalidateInternalFiles(TMPQArchive * ha)
+{
// Do nothing if we are in the middle of saving internal files
if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES))
{
//
- // We clear the file entries of (listfile) and (attributes)
+ // We clear the file entries for (listfile), (attributes) and (signature)
// For each internal file cleared, we increment the number
// of reserved entries in the file table.
//
// Invalidate the (listfile), if not done yet
- if((ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID) == 0)
+ if((ha->dwFlags & (MPQ_FLAG_LISTFILE_NONE | MPQ_FLAG_LISTFILE_NEW)) == 0)
{
- // If the (listfile) is not there but will be, reserve one entry
- pFileEntry1 = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL);
- if(pFileEntry1 == NULL && ha->dwFileFlags1)
- ha->dwReservedFiles++;
-
- // Mark the (listfile) as invalid
- ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID;
+ ha->dwFileFlags1 = InvalidateInternalFile(ha, LISTFILE_NAME, MPQ_FLAG_LISTFILE_NONE, MPQ_FLAG_LISTFILE_NEW);
}
// Invalidate the (attributes), if not done yet
- if((ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID) == 0)
+ if((ha->dwFlags & (MPQ_FLAG_ATTRIBUTES_NONE | MPQ_FLAG_ATTRIBUTES_NEW)) == 0)
{
- // If the (attributes) is not there but will be, reserve one entry
- pFileEntry2 = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
- if(pFileEntry2 == NULL && ha->dwFileFlags2)
- ha->dwReservedFiles++;
-
- // Mark (attributes) as invalid
- ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID;
+ ha->dwFileFlags2 = InvalidateInternalFile(ha, ATTRIBUTES_NAME, MPQ_FLAG_ATTRIBUTES_NONE, MPQ_FLAG_ATTRIBUTES_NEW);
}
// Invalidate the (signature), if not done yet
- if((ha->dwFlags & MPQ_FLAG_SIGNATURE_INVALID) == 0)
+ if((ha->dwFlags & (MPQ_FLAG_SIGNATURE_NONE | MPQ_FLAG_SIGNATURE_NEW)) == 0)
{
- // If the (signature) is not there but will be, reserve one entry
- pFileEntry3 = GetFileEntryExact(ha, SIGNATURE_NAME, LANG_NEUTRAL);
- if(pFileEntry3 == NULL && ha->dwFileFlags3)
- ha->dwReservedFiles++;
-
- // Mark (signature) as invalid
- ha->dwFlags |= MPQ_FLAG_SIGNATURE_INVALID;
+ ha->dwFileFlags3 = InvalidateInternalFile(ha, SIGNATURE_NAME, MPQ_FLAG_SIGNATURE_NONE, MPQ_FLAG_SIGNATURE_NEW);
}
// Remember that the MPQ has been changed
@@ -2030,7 +2101,7 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize)
// Fill it
memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash));
- ha->dwHashTableSize = dwHashTableSize;
+ ha->pHeader->dwHashTableSize = dwHashTableSize;
ha->dwMaxFileCount = dwHashTableSize;
ha->pHashTable = pHashTable;
return ERROR_SUCCESS;
@@ -2065,8 +2136,9 @@ static TMPQHash * LoadHashTable(TMPQArchive * ha)
// Read, decrypt and uncompress the hash table
pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE, &bHashTableIsCut);
+// DumpHashTable(pHashTable, pHeader->dwHashTableSize);
- // If the hash table was cut, we need to remember it
+ // If the hash table was cut, we can/have to defragment it
if(pHashTable != NULL && bHashTableIsCut)
ha->dwFlags |= (MPQ_FLAG_MALFORMED | MPQ_FLAG_HASH_TABLE_CUT);
break;
@@ -2084,6 +2156,17 @@ static TMPQHash * LoadHashTable(TMPQArchive * ha)
return pHashTable;
}
+int CreateFileTable(TMPQArchive * ha, DWORD dwFileTableSize)
+{
+ ha->pFileTable = STORM_ALLOC(TFileEntry, dwFileTableSize);
+ if(ha->pFileTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ memset(ha->pFileTable, 0x00, sizeof(TFileEntry) * dwFileTableSize);
+ ha->dwFileTableSize = dwFileTableSize;
+ return ERROR_SUCCESS;
+}
+
TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool /* bDontFixEntries */)
{
TMPQHeader * pHeader = ha->pHeader;
@@ -2202,42 +2285,30 @@ int LoadAnyHashTable(TMPQArchive * ha)
// 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;
- ha->dwHashTableSize = pHeader->dwHashTableSize;
return ERROR_SUCCESS;
}
-static int BuildFileTable_Classic(
- TMPQArchive * ha,
- TFileEntry * pFileTable)
+static int BuildFileTable_Classic(TMPQArchive * ha)
{
- TFileEntry * pFileEntry;
TMPQHeader * pHeader = ha->pHeader;
TMPQBlock * pBlockTable;
int nError = ERROR_SUCCESS;
// Sanity checks
assert(ha->pHashTable != NULL);
+ assert(ha->pFileTable != NULL);
// If the MPQ has no block table, do nothing
- if(ha->pHeader->dwBlockTableSize == 0)
+ if(pHeader->dwBlockTableSize == 0)
return ERROR_SUCCESS;
+ assert(ha->dwFileTableSize >= pHeader->dwBlockTableSize);
// Load the block table
+ // WARNING! ha->pFileTable can change in the process!!
pBlockTable = (TMPQBlock *)LoadBlockTable(ha);
if(pBlockTable != NULL)
{
- // 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 = BuildFileTableFromBlockTable(ha, pFileTable, pBlockTable);
- }
- else
- {
- nError = UpdateFileTableFromHashTable(ha, pFileTable);
- }
-
- // Free the block table
+ nError = BuildFileTableFromBlockTable(ha, pBlockTable);
STORM_FREE(pBlockTable);
}
else
@@ -2265,15 +2336,14 @@ static int BuildFileTable_Classic(
// Now merge the hi-block table to the file table
if(nError == ERROR_SUCCESS)
{
+ TFileEntry * pFileEntry = ha->pFileTable;
+
+ // Swap the hi-block table
BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, dwTableSize);
- pFileEntry = pFileTable;
// Add the high file offset to the base file offset.
- for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++)
- {
+ for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++, pFileEntry++)
pFileEntry->ByteOffset = MAKE_OFFSET64(pHiBlockTable[i], pFileEntry->ByteOffset);
- pFileEntry++;
- }
}
// Free the hi-block table
@@ -2285,18 +2355,14 @@ static int BuildFileTable_Classic(
}
}
- // Set the current size of the file table
- ha->dwFileTableSize = pHeader->dwBlockTableSize;
return nError;
}
-static int BuildFileTable_HetBet(
- TMPQArchive * ha,
- TFileEntry * pFileTable)
+static int BuildFileTable_HetBet(TMPQArchive * ha)
{
TMPQHetTable * pHetTable = ha->pHetTable;
TMPQBetTable * pBetTable;
- TFileEntry * pFileEntry = pFileTable;
+ TFileEntry * pFileEntry = ha->pFileTable;
TBitArray * pBitArray;
DWORD dwBitPosition = 0;
DWORD i;
@@ -2317,7 +2383,7 @@ static int BuildFileTable_HetBet(
DWORD dwFileIndex = 0;
// Is the entry in the HET table occupied?
- if(pHetTable->pNameHashes[i] != 0)
+ if(pHetTable->pNameHashes[i] != HET_ENTRY_FREE)
{
// Load the index to the BET table
GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i,
@@ -2337,14 +2403,14 @@ static int BuildFileTable_HetBet(
8);
// Combine both part of the name hash and put it to the file table
- pFileEntry = pFileTable + dwFileIndex;
+ pFileEntry = ha->pFileTable + dwFileIndex;
pFileEntry->FileNameHash = (NameHash1 << pBetTable->dwBitCount_NameHash2) | NameHash2;
}
}
}
// Go through the entire BET table and convert it to the file table.
- pFileEntry = pFileTable;
+ pFileEntry = ha->pFileTable;
pBitArray = pBetTable->pFileTable;
for(i = 0; i < pBetTable->dwEntryCount; i++)
{
@@ -2389,7 +2455,6 @@ static int BuildFileTable_HetBet(
}
// Set the current size of the file table
- ha->dwFileTableSize = pBetTable->dwEntryCount;
FreeBetTable(pBetTable);
nError = ERROR_SUCCESS;
}
@@ -2403,11 +2468,11 @@ static int BuildFileTable_HetBet(
int BuildFileTable(TMPQArchive * ha)
{
- TFileEntry * pFileTable;
DWORD dwFileTableSize;
bool bFileTableCreated = false;
// Sanity checks
+ assert(ha->pFileTable == NULL);
assert(ha->dwFileTableSize == 0);
assert(ha->dwMaxFileCount != 0);
@@ -2415,18 +2480,19 @@ int BuildFileTable(TMPQArchive * ha)
dwFileTableSize = STORMLIB_MAX(ha->pHeader->dwBlockTableSize, ha->dwMaxFileCount);
// Allocate the file table with size determined before
- pFileTable = STORM_ALLOC(TFileEntry, dwFileTableSize);
- if(pFileTable == NULL)
+ ha->pFileTable = STORM_ALLOC(TFileEntry, dwFileTableSize);
+ if(ha->pFileTable == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill the table with zeros
- memset(pFileTable, 0, dwFileTableSize * sizeof(TFileEntry));
+ memset(ha->pFileTable, 0, dwFileTableSize * sizeof(TFileEntry));
+ ha->dwFileTableSize = dwFileTableSize;
// 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)
+ if(BuildFileTable_HetBet(ha) != ERROR_SUCCESS)
ha->dwFlags |= MPQ_FLAG_READ_ONLY;
else
bFileTableCreated = true;
@@ -2436,209 +2502,105 @@ int BuildFileTable(TMPQArchive * ha)
// Note: If block table is corrupt or missing, we set the archive as read only
if(ha->pHashTable != NULL)
{
- if(BuildFileTable_Classic(ha, pFileTable) != ERROR_SUCCESS)
+ if(BuildFileTable_Classic(ha) != ERROR_SUCCESS)
ha->dwFlags |= MPQ_FLAG_READ_ONLY;
else
bFileTableCreated = true;
}
-
- // If something failed, we free the file table entry
- if(bFileTableCreated == false)
+
+ // Return result
+ return bFileTableCreated ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
+}
+
+/*
+void UpdateBlockTableSize(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ DWORD dwBlockTableSize = 0;
+
+ // Calculate the number of files
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
- STORM_FREE(pFileTable);
- return ERROR_FILE_CORRUPT;
+ // If the source table entry is valid,
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ dwBlockTableSize = (DWORD)(pFileEntry - ha->pFileTable) + 1;
}
- // Assign it to the archive structure
- ha->pFileTable = pFileTable;
- return ERROR_SUCCESS;
+ // Save the block table size to the MPQ header
+ ha->pHeader->dwBlockTableSize = ha->dwReservedFiles + dwBlockTableSize;
}
+*/
// Defragment the file table so it does not contain any gaps
int DefragmentFileTable(TMPQArchive * ha)
{
- TFileEntry * pNewTable;
- TMPQHash * pHashEnd = ha->pHashTable + ha->dwHashTableSize;
- TMPQHash * pHash;
- PDWORD NewIndexes;
- DWORD dwNewTableSize = 0;
- DWORD dwNewBlockIndex;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pSource = ha->pFileTable;
+ TFileEntry * pTarget = ha->pFileTable;
+ LPDWORD DefragmentTable;
+ DWORD dwBlockTableSize = 0;
+ DWORD dwSrcIndex;
+ DWORD dwTrgIndex;
// Allocate brand new file table
- NewIndexes = STORM_ALLOC(DWORD, ha->dwFileTableSize);
- if(NewIndexes != NULL)
+ DefragmentTable = STORM_ALLOC(DWORD, ha->dwFileTableSize);
+ if(DefragmentTable != NULL)
{
// Clear the file table
- memset(NewIndexes, 0xFF, sizeof(DWORD) * ha->dwFileTableSize);
+ memset(DefragmentTable, 0xFF, sizeof(DWORD) * ha->dwFileTableSize);
- // Create map of OldBlockIndex => NewBlockIndex
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
+ // Parse the entire file table and defragment it
+ for(; pSource < pFileTableEnd; pSource++)
{
- // If the entry is valid, move it
- if(IsValidHashEntry(ha, pHash))
+ // If the source table entry is valid,
+ if(pSource->dwFlags & MPQ_FILE_EXISTS)
{
- // Was the new index already resolved?
- // Note: Multiple hash table entries can point to the same block table entry
- if(NewIndexes[pHash->dwBlockIndex] == 0xFFFFFFFF)
- NewIndexes[pHash->dwBlockIndex] = dwNewTableSize++;
+ // Remember the index conversion
+ dwSrcIndex = (DWORD)(pSource - ha->pFileTable);
+ dwTrgIndex = (DWORD)(pTarget - ha->pFileTable);
+ DefragmentTable[dwSrcIndex] = dwTrgIndex;
+
+ // Move the entry, if needed
+ if(pTarget != pSource)
+ pTarget[0] = pSource[0];
+ pTarget++;
+
+ // Update the block table size
+ dwBlockTableSize = (DWORD)(pSource - ha->pFileTable) + 1;
}
}
- // We have to append reserved files
- dwNewTableSize += ha->dwReservedFiles;
-
- // Allocate new file table
- pNewTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount);
- if(pNewTable != NULL)
+ // Did we defragment something?
+ if(pTarget < pFileTableEnd)
{
- // Fill the new table with zeros
- memset(pNewTable, 0, sizeof(TFileEntry) * ha->dwMaxFileCount);
+ // Clear the remaining file entries
+ memset(pTarget, 0, (pFileTableEnd - pTarget) * sizeof(TFileEntry));
- // Create map of OldBlockIndex => NewBlockIndex
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
+ // Go through the hash table and relocate the block indexes
+ if(ha->pHashTable != NULL)
{
- // If the entry is valid, move it
- if(IsValidHashEntry(ha, pHash))
- {
- // The new index should be resolved by now
- assert(NewIndexes[pHash->dwBlockIndex] != 0xFFFFFFFF);
- dwNewBlockIndex = NewIndexes[pHash->dwBlockIndex];
+ TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ TMPQHash * pHash;
- // Remap the old entry to the new entry
- pNewTable[dwNewBlockIndex] = ha->pFileTable[pHash->dwBlockIndex];
- pHash->dwBlockIndex = dwNewBlockIndex;
- }
- }
-
- // Replace the old file table with the new table
- STORM_FREE(ha->pFileTable);
- ha->pFileTable = pNewTable;
- ha->dwFileTableSize = dwNewTableSize;
- }
-
- // Free the conversion table
- STORM_FREE(NewIndexes);
- }
-
- return ERROR_SUCCESS;
-}
-
-// Defragment the file table so it does not contain any gaps
-// Note: As long as all values of all TMPQHash::dwBlockIndex
-// are not HASH_ENTRY_FREE, the startup search index does not matter.
-// Hash table is circular, so as long as there is no terminator,
-// all entries will be found.
-int DefragmentHashTable(TMPQArchive * ha)
-{
- TMPQHash * pNewTable;
- TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
- TMPQHash * pNewHash;
- TMPQHash * pHash;
- DWORD dwNewTableSize = 0;
- DWORD dwNewIndex = 0;
-
- // Calculate how many entries in the hash table we really need
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
- dwNewTableSize += IsValidHashEntry(ha, pHash) ? 1 : 0;
- dwNewTableSize = GetHashTableSizeForFileCount(dwNewTableSize);
-
- // Could the hash table be shrinked?
- if(dwNewTableSize < ha->dwHashTableSize)
- {
- pNewTable = STORM_ALLOC(TMPQHash, dwNewTableSize);
- if(pNewTable != NULL)
- {
- // Initialize the new table
- memset(pNewTable, 0xFF, dwNewTableSize * sizeof(TMPQHash));
- pNewHash = pNewTable;
-
- // Copy the entries from the current hash table to new hash table
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
- {
- if(IsValidHashEntry(ha, pHash))
+ for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
{
- assert(dwNewIndex < dwNewTableSize);
- pNewHash[dwNewIndex++] = pHash[0];
+ if(pHash->dwBlockIndex < ha->dwFileTableSize)
+ {
+ assert(DefragmentTable[pHash->dwBlockIndex] != HASH_ENTRY_FREE);
+ pHash->dwBlockIndex = DefragmentTable[pHash->dwBlockIndex];
+ }
}
}
-
- // Fill the remaining entries so they look like deleted
- while(dwNewIndex < dwNewTableSize)
- pNewHash[dwNewIndex++].dwBlockIndex = HASH_ENTRY_DELETED;
-
- // Swap the hash tables
- STORM_FREE(ha->pHashTable);
- ha->pHashTable = pNewTable;
- ha->dwHashTableSize = dwNewTableSize;
}
- }
- return ERROR_SUCCESS;
-}
-// Performs final validation (and possible defragmentation)
-// of malformed hash+block tables
-int ShrinkMalformedMpqTables(TMPQArchive * ha)
-{
- // Sanity checks
- assert(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1);
- assert(ha->pHashTable != NULL);
- assert(ha->pFileTable != NULL);
+ // Save the block table size
+ ha->pHeader->dwBlockTableSize = ha->dwReservedFiles + dwBlockTableSize;
- // Now perform the defragmentation of the block table
- if(ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT)
- {
- assert(ha->dwFileTableSize == ha->pHeader->dwBlockTableSize);
- DefragmentFileTable(ha);
- }
-
- // If the hash table has been cut, we need to defragment it
- // This will greatly improve performance on searching and loading listfile
- if(ha->dwFlags & MPQ_FLAG_HASH_TABLE_CUT)
- {
- assert(ha->dwHashTableSize == ha->pHeader->dwHashTableSize);
- DefragmentHashTable(ha);
+ // Free the defragment table
+ STORM_FREE(DefragmentTable);
}
-/*
- TMPQHash * pSrcEntry = ha->pHashTable;
- TMPQHash * pTrgEntry = ha->pHashTable;
- DWORD dwNewEntryCount;
- DWORD dwNewTableSize;
-
- assert(ha->dwHashTableSize == ha->pHeader->dwHashTableSize);
-
- // Defragment the hash table
- for(i = 0; i < ha->dwHashTableSize; i++, pSrcEntry++)
- {
- // If the entry is valid, move it
- if(pSrcEntry->dwBlockIndex < ha->dwFileTableSize)
- {
- // Fix the hash table index in the file table and move the entry
- if(pTrgEntry != pSrcEntry)
- {
- pFileTable[pSrcEntry->dwBlockIndex].dwHashIndex = (DWORD)(pTrgEntry - pHashTable);
- *pTrgEntry = *pSrcEntry;
- }
-
- // Increment the number of used entries
- pTrgEntry++;
- }
- }
-
- // Calculate the new hash table size
- dwNewEntryCount = (DWORD)(pTrgEntry - pHashTable);
- dwNewTableSize = GetHashTableSizeForFileCount(dwNewEntryCount);
-
- // Mark the extra entries in the hash table as deleted
- // This causes every hash search to go over the entire hash table,
- // but if the hash table is cut, it would do it anyway
- memset(pHashTable + dwNewEntryCount, 0xFF, (dwNewTableSize - dwNewEntryCount) * sizeof(TMPQHash));
- for(i = dwNewEntryCount; i < dwNewTableSize; i++)
- pHashTable[i].dwBlockIndex = HASH_ENTRY_DELETED;
- // Change the hash table size in the header
- ha->dwHashTableSize = dwNewTableSize;
- }
-*/
return ERROR_SUCCESS;
}
@@ -2647,23 +2609,32 @@ int ShrinkMalformedMpqTables(TMPQArchive * ha)
int RebuildHetTable(TMPQArchive * ha)
{
TMPQHetTable * pOldHetTable = ha->pHetTable;
- ULONGLONG FileNameHash;
- DWORD i;
+ TFileEntry * pFileTableEnd;
+ TFileEntry * pFileEntry;
+ DWORD dwBlockTableSize = ha->dwFileTableSize;
int nError = ERROR_SUCCESS;
+ // If we are in the state of saving MPQ tables, the real size of block table
+ // must already have been calculated. Use that value instead
+ if(ha->dwFlags & MPQ_FLAG_SAVING_TABLES)
+ {
+ assert(ha->pHeader->dwBlockTableSize != 0);
+ dwBlockTableSize = ha->pHeader->dwBlockTableSize;
+ }
+
// 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, 0, 0x40, NULL);
+ ha->pHetTable = CreateHetTable(dwBlockTableSize, 0, 0x40, NULL);
if(ha->pHetTable != NULL)
{
// Go through the file table again and insert all existing files
- for(i = 0; i < ha->dwFileTableSize; i++)
+ pFileTableEnd = ha->pFileTable + dwBlockTableSize;
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
- if(ha->pFileTable[i].dwFlags & MPQ_FILE_EXISTS)
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
{
// Get the high
- FileNameHash = ha->pFileTable[i].FileNameHash;
- nError = InsertHetEntry(ha->pHetTable, FileNameHash, i);
+ nError = InsertHetEntry(ha->pHetTable, pFileEntry->FileNameHash, (DWORD)(pFileEntry - ha->pFileTable));
if(nError != ERROR_SUCCESS)
break;
}
@@ -2677,31 +2648,33 @@ int RebuildHetTable(TMPQArchive * ha)
// Rebuilds the file table, removing all deleted file entries.
// Used when compacting the archive
-int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount)
+int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize)
{
- TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- TFileEntry * pOldFileTable = ha->pFileTable;
- TFileEntry * pFileTable;
TFileEntry * pFileEntry;
- TFileEntry * pOldEntry;
+ TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
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->dwHashTableSize);
-
- // The new hash table size must be a power of two
+ assert(dwNewHashTableSize >= ha->pHeader->dwHashTableSize);
+ assert(dwNewHashTableSize >= ha->dwMaxFileCount);
assert((dwNewHashTableSize & (dwNewHashTableSize - 1)) == 0);
+ assert(ha->pHashTable != NULL);
- // Allocate the new file table
- pFileTable = pFileEntry = STORM_ALLOC(TFileEntry, dwNewMaxFileCount);
- if(pFileTable == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ // Reallocate the new file table, if needed
+ if(dwNewHashTableSize > ha->dwFileTableSize)
+ {
+ ha->pFileTable = STORM_REALLOC(TFileEntry, ha->pFileTable, dwNewHashTableSize);
+ if(ha->pFileTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ memset(ha->pFileTable + ha->dwFileTableSize, 0, (dwNewHashTableSize - ha->dwFileTableSize) * sizeof(TFileEntry));
+ }
// Allocate new hash table
- if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ if(nError == ERROR_SUCCESS)
{
pHashTable = STORM_ALLOC(TMPQHash, dwNewHashTableSize);
if(pHashTable == NULL)
@@ -2712,56 +2685,31 @@ int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxF
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->dwHashTableSize = dwNewHashTableSize;
- ha->dwMaxFileCount = dwNewMaxFileCount;
+ ha->pHeader->dwHashTableSize = dwNewHashTableSize;
- // Now copy all the file entries
- for(pOldEntry = pOldFileTable; pOldEntry < pOldFileTableEnd; pOldEntry++)
+ // Parse the old hash table and copy all entries to the new table
+ for(pHash = pOldHashTable; pHash < pHashTableEnd; pHash++)
{
- // If the file entry exists, we copy it to the new table
- // Otherwise, we skip it
- if(pOldEntry->dwFlags & MPQ_FILE_EXISTS)
+ if(IsValidHashEntry2(ha, pHash))
{
- // Copy the file entry
- *pFileEntry = *pOldEntry;
-
- // Create hash entry for it
- if(ha->pHashTable != NULL)
- {
- pHash = AllocateHashEntry(ha, pFileEntry);
- if(pHash == NULL)
- {
- nError = ERROR_DISK_FULL;
- break;
- }
- }
-
- // Move the file entry by one
- pFileEntry++;
+ pFileEntry = ha->pFileTable + pHash->dwBlockIndex;
+ AllocateHashEntry(ha, pFileEntry, pHash->lcLocale);
}
}
- // Update the file table size
- ha->dwFileTableSize = (DWORD)(pFileEntry - pFileTable);
+ // Increment the max file count for the file
+ ha->dwFileTableSize = dwNewHashTableSize;
+ ha->dwMaxFileCount = dwNewHashTableSize;
ha->dwFlags |= MPQ_FLAG_CHANGED;
- pFileTable = NULL;
}
// Now free the remaining entries
- if(pOldFileTable != NULL)
- STORM_FREE(pOldFileTable);
if(pOldHashTable != NULL)
STORM_FREE(pOldHashTable);
- if(pFileTable != NULL)
- STORM_FREE(pFileTable);
return nError;
}
diff --git a/src/SFileAddFile.cpp b/src/SFileAddFile.cpp
index aa47aa7..70a857b 100644
--- a/src/SFileAddFile.cpp
+++ b/src/SFileAddFile.cpp
@@ -354,7 +354,7 @@ static int RecryptFileData(
}
//-----------------------------------------------------------------------------
-// Support functions for adding files to the MPQ
+// Internal support for MPQ modifications
int SFileAddFile_Init(
TMPQArchive * ha,
@@ -368,6 +368,7 @@ int SFileAddFile_Init(
TFileEntry * pFileEntry = NULL;
ULONGLONG TempPos; // For various file offset calculations
TMPQFile * hf = NULL; // File structure for newly added file
+ DWORD dwHashIndex = HASH_ENTRY_FREE;
int nError = ERROR_SUCCESS;
//
@@ -410,7 +411,7 @@ int SFileAddFile_Init(
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
{
TempPos = hf->MpqFilePos + dwFileSize;
- TempPos += ha->dwHashTableSize * sizeof(TMPQHash);
+ TempPos += ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
TempPos += ha->dwFileTableSize * sizeof(TMPQBlock);
if((TempPos >> 32) != 0)
nError = ERROR_DISK_FULL;
@@ -421,25 +422,24 @@ int SFileAddFile_Init(
if(nError == ERROR_SUCCESS)
{
// Check if the file already exists in the archive
- pFileEntry = GetFileEntryExact(ha, szFileName, lcLocale);
- if(pFileEntry == NULL)
- {
- // First, free the internal files
- InvalidateInternalFiles(ha);
-
- // Find a free entry in the file table
- pFileEntry = AllocateFileEntry(ha, szFileName, lcLocale);
- if(pFileEntry == NULL)
- nError = ERROR_DISK_FULL;
- }
- else
+ pFileEntry = GetFileEntryExact(ha, szFileName, lcLocale, &dwHashIndex);
+ if(pFileEntry != NULL)
{
- // If the caller didn't set MPQ_FILE_REPLACEEXISTING, fail it
if(dwFlags & MPQ_FILE_REPLACEEXISTING)
InvalidateInternalFiles(ha);
else
nError = ERROR_ALREADY_EXISTS;
}
+ else
+ {
+ // Free all internal files - (listfile), (attributes), (signature)
+ InvalidateInternalFiles(ha);
+
+ // Attempt to allocate new file entry
+ pFileEntry = AllocateFileEntry(ha, szFileName, lcLocale, &dwHashIndex);
+ if(pFileEntry == NULL)
+ nError = ERROR_DISK_FULL;
+ }
}
// Fill the file entry and TMPQFile structure
@@ -452,7 +452,14 @@ int SFileAddFile_Init(
// Initialize the hash entry for the file
hf->pFileEntry = pFileEntry;
hf->dwDataSize = dwFileSize;
-
+
+ // Set the hash table entry
+ if(ha->pHashTable != NULL && dwHashIndex < ha->pHeader->dwHashTableSize)
+ {
+ hf->pHashEntry = ha->pHashTable + dwHashIndex;
+ hf->pHashEntry->lcLocale = (USHORT)lcLocale;
+ }
+
// Decrypt the file key
if(dwFlags & MPQ_FILE_ENCRYPTED)
hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags);
@@ -462,7 +469,6 @@ int SFileAddFile_Init(
pFileEntry->dwFileSize = dwFileSize;
pFileEntry->dwCmpSize = 0;
pFileEntry->dwFlags = dwFlags | MPQ_FILE_EXISTS;
- pFileEntry->lcLocale = (USHORT)lcLocale;
// Initialize the file time, CRC32 and MD5
assert(sizeof(hf->hctx) >= sizeof(hash_state));
@@ -668,7 +674,7 @@ int SFileAddFile_Finish(TMPQFile * hf)
{
// Free the file entry in MPQ tables
if(pFileEntry != NULL)
- DeleteFileEntry(ha, pFileEntry);
+ DeleteFileEntry(ha, hf);
}
// Clear the add file callback
@@ -1007,72 +1013,59 @@ bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szA
//-----------------------------------------------------------------------------
// bool SFileRemoveFile(HANDLE hMpq, char * szFileName)
//
-// This function removes a file from the archive. The file content
-// remains there, only the entries in the hash table and in the block
-// table are updated.
+// This function removes a file from the archive.
+//
bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope)
{
- TMPQArchive * ha = (TMPQArchive *)hMpq;
- TFileEntry * pFileEntry = NULL; // File entry of the file to be deleted
- DWORD dwFileIndex = 0;
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
+ TMPQFile * hf = NULL;
int nError = ERROR_SUCCESS;
// Keep compiler happy
dwSearchScope = dwSearchScope;
// Check the parameters
- if(nError == ERROR_SUCCESS)
- {
- if(!IsValidMpqHandle(hMpq))
- nError = ERROR_INVALID_HANDLE;
- if(szFileName == NULL || *szFileName == 0)
- nError = ERROR_INVALID_PARAMETER;
- if(IsInternalMpqFileName(szFileName))
- nError = ERROR_INTERNAL_FILE;
- }
+ if(ha == NULL)
+ nError = ERROR_INVALID_HANDLE;
+ if(szFileName == NULL || *szFileName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+ if(IsInternalMpqFileName(szFileName))
+ nError = ERROR_INTERNAL_FILE;
+ // Do not allow to remove files from read-only or patched MPQs
if(nError == ERROR_SUCCESS)
{
- // Do not allow to remove files from MPQ open for read only
- if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ if((ha->dwFlags & MPQ_FLAG_READ_ONLY) || (ha->haPatch != NULL))
nError = ERROR_ACCESS_DENIED;
}
- // Get hash entry belonging to this file
+ // If all checks have passed, we can delete the file from the MPQ
if(nError == ERROR_SUCCESS)
{
- if(!IsPseudoFileName(szFileName, &dwFileIndex))
+ // Open the file from the MPQ
+ if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
{
- if((pFileEntry = GetFileEntryExact(ha, (char *)szFileName, lcFileLocale)) == NULL)
- nError = ERROR_FILE_NOT_FOUND;
+ // Delete the file entry
+ nError = DeleteFileEntry(ha, hf);
+ FreeFileHandle(hf);
}
else
- {
- if((pFileEntry = GetFileEntryByIndex(ha, dwFileIndex)) == NULL)
- nError = ERROR_FILE_NOT_FOUND;
- }
- }
-
- // Test if the file is not already deleted
- if(nError == ERROR_SUCCESS)
- {
- if(!(pFileEntry->dwFlags & MPQ_FILE_EXISTS))
- nError = ERROR_FILE_NOT_FOUND;
+ nError = GetLastError();
}
+ // If the file has been deleted, we need to invalidate
+ // the internal files and recreate HET table
if(nError == ERROR_SUCCESS)
{
- // Invalidate the entries for (listfile) and (attributes)
+ // Invalidate the entries for internal files
// After we are done with MPQ changes, we need to re-create them anyway
InvalidateInternalFiles(ha);
- // Delete the file entry in the file table and defragment the file table
- DeleteFileEntry(ha, pFileEntry);
-
- // We also need to rebuild the HET table, if present
- if(ha->pHetTable != NULL)
- nError = RebuildHetTable(ha);
+ //
+ // Don't rebuild HET table now; the file's flags indicate
+ // that it's been deleted, which is enough
+ //
}
// Resolve error and exit
@@ -1084,81 +1077,56 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch
// Renames the file within the archive.
bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName)
{
- TMPQArchive * ha = (TMPQArchive *)hMpq;
- TFileEntry * pFileEntry = NULL;
- ULONGLONG RawDataOffs;
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
TMPQFile * hf;
int nError = ERROR_SUCCESS;
// Test the valid parameters
- if(nError == ERROR_SUCCESS)
- {
- if(!IsValidMpqHandle(hMpq))
- nError = ERROR_INVALID_HANDLE;
- if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0)
- nError = ERROR_INVALID_PARAMETER;
- }
+ if(ha == NULL)
+ nError = ERROR_INVALID_HANDLE;
+ if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+ if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName))
+ nError = ERROR_INTERNAL_FILE;
+ // Do not allow to rename files in MPQ open for read only
if(nError == ERROR_SUCCESS)
{
- // Do not allow to rename files in MPQ open for read only
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
nError = ERROR_ACCESS_DENIED;
-
- // Do not allow renaming anything to a pseudo-file name
- if(IsPseudoFileName(szFileName, NULL) || IsPseudoFileName(szNewFileName, NULL))
- nError = ERROR_INVALID_PARAMETER;
-
- // Do not allow to rename any of the internal files
- // Also do not allow to rename any of files to an internal file
- if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName))
- nError = ERROR_INTERNAL_FILE;
}
- // Find the current file entry.
+ // Open the new file. If exists, we don't allow rename operation
if(nError == ERROR_SUCCESS)
{
- // Get the file entry
- pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
- if(pFileEntry == NULL)
- nError = ERROR_FILE_NOT_FOUND;
- }
-
- // Also try to find file entry for the new file.
- // This verifies if we are not overwriting an existing file
- // (whose name we perhaps don't know)
- if(nError == ERROR_SUCCESS)
- {
- if(GetFileEntryLocale(ha, szNewFileName, pFileEntry->lcLocale) != NULL)
+ if(GetFileEntryLocale(ha, szNewFileName, lcFileLocale) != NULL)
nError = ERROR_ALREADY_EXISTS;
}
- // Now we rename the existing file entry.
+ // Open the file from the MPQ
if(nError == ERROR_SUCCESS)
{
- // Rename the file entry
- nError = RenameFileEntry(ha, pFileEntry, szNewFileName);
- }
+ // Attempt to open the file
+ if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
+ {
+ ULONGLONG RawDataOffs;
+ TFileEntry * pFileEntry = hf->pFileEntry;
- // Now we need to recreate the HET table, if we have one
- if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
- nError = RebuildHetTable(ha);
+ // Invalidate the entries for internal files
+ InvalidateInternalFiles(ha);
- // Now we copy the existing file entry to the new one
- if(nError == ERROR_SUCCESS)
- {
- // If the file is encrypted, we have to re-crypt the file content
- // with the new decryption key
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
- {
- hf = CreateFileHandle(ha, pFileEntry);
- if(hf != NULL)
+ // Rename the file entry in the table
+ nError = RenameFileEntry(ha, hf, szNewFileName);
+
+ // If the file is encrypted, we have to re-crypt the file content
+ // with the new decryption key
+ if((nError == ERROR_SUCCESS) && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED))
{
// Recrypt the file data in the MPQ
nError = RecryptFileData(ha, hf, szFileName, szNewFileName);
- // Update the MD5
- if(ha->pHeader->dwRawChunkSize != 0)
+ // Update the MD5 of the raw block
+ if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0)
{
RawDataOffs = ha->MpqPos + pFileEntry->ByteOffset;
WriteMpqDataMD5(ha->pStream,
@@ -1166,20 +1134,22 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s
pFileEntry->dwCmpSize,
ha->pHeader->dwRawChunkSize);
}
-
- FreeFileHandle(hf);
- }
- else
- {
- nError = ERROR_NOT_ENOUGH_MEMORY;
}
+
+ // Free the file handle
+ FreeFileHandle(hf);
+ }
+ else
+ {
+ nError = GetLastError();
}
}
- // Note: MPQ_FLAG_CHANGED is set by RenameFileEntry
- // assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0);
+ // We also need to rebuild the HET table, if present
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ nError = RebuildHetTable(ha);
- // Resolve error and return
+ // Resolve error and exit
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return (nError == ERROR_SUCCESS);
@@ -1209,15 +1179,23 @@ bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale)
{
TMPQArchive * ha;
TFileEntry * pFileEntry;
- TMPQFile * hf = (TMPQFile *)hFile;
+ TMPQFile * hf = IsValidFileHandle(hFile);
// Invalid handle => do nothing
- if(!IsValidFileHandle(hFile))
+ if(hf == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
+ // Do not allow to rename files in MPQ open for read only
+ ha = hf->ha;
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
// Do not allow unnamed access
if(hf->pFileEntry->szFileName == NULL)
{
@@ -1232,42 +1210,23 @@ bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale)
return false;
}
- // Do not allow changing file locales in MPQs version 3 or higher
- ha = hf->ha;
- if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3)
+ // Do not allow changing file locales if there is no hash table
+ if(hf->pHashEntry == NULL)
{
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
- // Do not allow to rename files in MPQ open for read only
- if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
- {
- SetLastError(ERROR_ACCESS_DENIED);
- return false;
- }
-
- // If the file already has that locale, return OK
- if(hf->pFileEntry->lcLocale == lcNewLocale)
- return true;
-
// We have to check if the file+locale is not already there
- pFileEntry = GetFileEntryExact(ha, hf->pFileEntry->szFileName, lcNewLocale);
+ pFileEntry = GetFileEntryExact(ha, hf->pFileEntry->szFileName, lcNewLocale, NULL);
if(pFileEntry != NULL)
{
SetLastError(ERROR_ALREADY_EXISTS);
return false;
}
- // Set the locale and return success
- pFileEntry = hf->pFileEntry;
- pFileEntry->lcLocale = (USHORT)lcNewLocale;
-
- // Save the new locale to the hash table, if any
- if(ha->pHashTable != NULL)
- ha->pHashTable[pFileEntry->dwHashIndex].lcLocale = (USHORT)lcNewLocale;
-
- // Remember that the MPQ tables have been changed
+ // Update the locale in the hash table entry
+ hf->pHashEntry->lcLocale = (USHORT)lcNewLocale;
ha->dwFlags |= MPQ_FLAG_CHANGED;
return true;
}
diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp
index 81bf1cf..dacfd3b 100644
--- a/src/SFileAttributes.cpp
+++ b/src/SFileAttributes.cpp
@@ -34,29 +34,29 @@ typedef struct _MPQ_ATTRIBUTES_HEADER
//-----------------------------------------------------------------------------
// Local functions
-static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwFileTableSize)
+static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwBlockTableSize)
{
DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER);
// Calculate size of the (attributes) file
if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
- cbAttrFile += dwFileTableSize * sizeof(DWORD);
+ cbAttrFile += dwBlockTableSize * sizeof(DWORD);
if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
- cbAttrFile += dwFileTableSize * sizeof(ULONGLONG);
+ cbAttrFile += dwBlockTableSize * sizeof(ULONGLONG);
if(dwAttrFlags & MPQ_ATTRIBUTE_MD5)
- cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE;
+ cbAttrFile += dwBlockTableSize * MD5_DIGEST_SIZE;
// The bit array has been created without the last bit belonging to (attributes)
// When the number of files is a multiplier of 8 plus one, then the size of (attributes)
// if 1 byte less than expected.
// Example: wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes
if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
- cbAttrFile += (dwFileTableSize + 6) / 8;
+ cbAttrFile += (dwBlockTableSize + 6) / 8;
return cbAttrFile;
}
-static DWORD CheckSizeOfAttributesFile(DWORD cbAttrFile, DWORD dwAttrFlags, DWORD dwFileTableSize)
+static DWORD CheckSizeOfAttributesFile(DWORD cbAttrFile, DWORD dwAttrFlags, DWORD dwBlockTableSize)
{
DWORD cbHeaderSize = sizeof(MPQ_ATTRIBUTES_HEADER);
DWORD cbChecksumSize1 = 0;
@@ -87,21 +87,21 @@ static DWORD CheckSizeOfAttributesFile(DWORD cbAttrFile, DWORD dwAttrFlags, DWOR
// Get the expected size of CRC32 array
if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
{
- cbChecksumSize1 += dwFileTableSize * sizeof(DWORD);
+ cbChecksumSize1 += dwBlockTableSize * sizeof(DWORD);
cbChecksumSize2 += cbChecksumSize1 - sizeof(DWORD);
}
// Get the expected size of FILETIME array
if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
{
- cbFileTimeSize1 += dwFileTableSize * sizeof(ULONGLONG);
+ cbFileTimeSize1 += dwBlockTableSize * sizeof(ULONGLONG);
cbFileTimeSize2 += cbFileTimeSize1 - sizeof(ULONGLONG);
}
// Get the expected size of MD5 array
if(dwAttrFlags & MPQ_ATTRIBUTE_MD5)
{
- cbFileHashSize1 += dwFileTableSize * MD5_DIGEST_SIZE;
+ cbFileHashSize1 += dwBlockTableSize * MD5_DIGEST_SIZE;
cbFileHashSize2 += cbFileHashSize1 - MD5_DIGEST_SIZE;
}
@@ -109,26 +109,26 @@ static DWORD CheckSizeOfAttributesFile(DWORD cbAttrFile, DWORD dwAttrFlags, DWOR
if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
{
cbPatchBitSize1 =
- cbPatchBitSize2 = ((dwFileTableSize + 6) / 8);
- cbPatchBitSize3 = dwFileTableSize * sizeof(DWORD);
+ cbPatchBitSize2 = ((dwBlockTableSize + 6) / 8);
+ cbPatchBitSize3 = dwBlockTableSize * sizeof(DWORD);
}
// Check if the (attributes) file entry count is equal to our file table size
if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1 + cbPatchBitSize1))
- return dwFileTableSize;
+ return dwBlockTableSize;
// Check if the (attributes) file entry count is equal to our file table size minus one
if(cbAttrFile == (cbHeaderSize + cbChecksumSize2 + cbFileTimeSize2 + cbFileHashSize2 + cbPatchBitSize2))
- return dwFileTableSize - 1;
+ return dwBlockTableSize - 1;
// Zenith.SC2MAP has the MPQ_ATTRIBUTE_PATCH_BIT set, but the bit array is missing
if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1))
- return dwFileTableSize;
+ return dwBlockTableSize;
// interface.MPQ.part (WoW build 10958) has the MPQ_ATTRIBUTE_PATCH_BIT set
// but there's an array of DWORDs (filled with zeros) instead of array of bits
if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1 + cbPatchBitSize3))
- return dwFileTableSize;
+ return dwBlockTableSize;
#ifdef __STORMLIB_TEST__
// Invalid size of the (attributes) file
@@ -246,12 +246,11 @@ static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrF
static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
{
PMPQ_ATTRIBUTES_HEADER pAttrHeader;
- TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize;
TFileEntry * pFileEntry;
LPBYTE pbAttrFile;
LPBYTE pbAttrPtr;
size_t cbAttrFile;
- DWORD dwFinalEntries = ha->dwFileTableSize + ha->dwReservedFiles;
// Check if we need patch bits in the (attributes) file
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
@@ -265,7 +264,7 @@ static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
// Allocate the buffer for holding the entire (attributes)
// Allocate 1 byte more (See GetSizeOfAttributesFile for more info)
- cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, dwFinalEntries);
+ cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, ha->pHeader->dwBlockTableSize);
pbAttrFile = pbAttrPtr = STORM_ALLOC(BYTE, cbAttrFile + 1);
if(pbAttrFile != NULL)
{
@@ -287,8 +286,8 @@ static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
*pArrayCRC32++ = BSWAP_INT32_UNSIGNED(pFileEntry->dwCrc32);
- // Skip the reserved entries
- pbAttrPtr = (LPBYTE)(pArrayCRC32 + ha->dwReservedFiles);
+ // Update pointer
+ pbAttrPtr = (LPBYTE)pArrayCRC32;
}
// Write the array of file time
@@ -300,8 +299,8 @@ static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
*pArrayFileTime++ = BSWAP_INT64_UNSIGNED(pFileEntry->FileTime);
- // Skip the reserved entries
- pbAttrPtr = (LPBYTE)(pArrayFileTime + ha->dwReservedFiles);
+ // Update pointer
+ pbAttrPtr = (LPBYTE)pArrayFileTime;
}
// Write the array of MD5s
@@ -316,8 +315,8 @@ static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
pbArrayMD5 += MD5_DIGEST_SIZE;
}
- // Skip the reserved items
- pbAttrPtr = pbArrayMD5 + (ha->dwReservedFiles * MD5_DIGEST_SIZE);
+ // Update pointer
+ pbAttrPtr = pbArrayMD5;
}
// Write the array of patch bits
@@ -339,12 +338,8 @@ static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01);
}
- // Having incremented the bit array just by the number of items in the file table
- // will create the bit array one byte less of the number of files is a multiplier of 8).
- // Blizzard MPQs have the same feature.
-
// Move past the bit array
- pbAttrPtr = (pbBitArray + dwByteIndex) + ((dwBitMask & 0x7F) ? 1 : 0);
+ pbAttrPtr += (ha->pHeader->dwBlockTableSize + 6) / 8;
}
// Now we expect that current position matches the estimated size
@@ -419,7 +414,7 @@ int SAttrFileSaveToMpq(TMPQArchive * ha)
if(ha->dwFileFlags2 != 0)
{
// At this point, we expect to have at least one reserved entry in the file table
- assert(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID);
+ assert(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW);
assert(ha->dwReservedFiles > 0);
// Create the raw data that is to be written to (attributes)
@@ -451,6 +446,10 @@ int SAttrFileSaveToMpq(TMPQArchive * ha)
SFileAddFile_Finish(hf);
}
+ // Clear the number of reserved files
+ ha->dwFlags &= ~(MPQ_FLAG_ATTRIBUTES_NEW | MPQ_FLAG_ATTRIBUTES_NONE);
+ ha->dwReservedFiles--;
+
// Free the attributes buffer
STORM_FREE(pbAttrFile);
}
@@ -459,10 +458,6 @@ int SAttrFileSaveToMpq(TMPQArchive * ha)
// If the (attributes) file would be empty, its OK
nError = (cbAttrFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
}
-
- // Clear the number of reserved files
- ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID;
- ha->dwReservedFiles--;
}
return nError;
diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp
index a70fa0a..2c0ef5a 100644
--- a/src/SFileCompactArchive.cpp
+++ b/src/SFileCompactArchive.cpp
@@ -598,18 +598,10 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
if(nError == ERROR_SUCCESS)
nError = CopyMpqFiles(ha, pFileKeys, pTempStream);
- // We also need to rebuild the HET table, if any
- if(nError == ERROR_SUCCESS)
- {
- // Rebuild the HET table, if we have any
- if(ha->pHetTable != NULL)
- nError = RebuildHetTable(ha);
- ha->dwFlags |= MPQ_FLAG_CHANGED;
- }
-
// If succeeded, switch the streams
if(nError == ERROR_SUCCESS)
{
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
if(FileStream_Replace(ha->pStream, pTempStream))
pTempStream = NULL;
else
@@ -619,7 +611,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
// Final user notification
if(nError == ERROR_SUCCESS && ha->pfnCompactCB != NULL)
{
- ha->CompactBytesProcessed += (ha->dwHashTableSize * sizeof(TMPQHash));
+ ha->CompactBytesProcessed += (ha->pHeader->dwHashTableSize * sizeof(TMPQHash));
ha->CompactBytesProcessed += (ha->dwFileTableSize * sizeof(TMPQBlock));
ha->pfnCompactCB(ha->pvCompactUserData, CCB_CLOSING_ARCHIVE, ha->CompactBytesProcessed, ha->CompactTotalBytes);
}
@@ -658,21 +650,18 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
if(dwMaxFileCount < ha->dwFileTableSize)
nError = ERROR_DISK_FULL;
- // ALL file names must be known in order to be able
- // to rebuild hash table
- if(nError == ERROR_SUCCESS)
+ // ALL file names must be known in order to be able to rebuild hash table
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
{
nError = CheckIfAllFilesKnown(ha);
- }
-
- // If the MPQ has a hash table, then we relocate the hash table
- if(nError == ERROR_SUCCESS)
- {
- // Calculate the hash table size for the new file limit
- dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Calculate the hash table size for the new file limit
+ dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);
- // Rebuild both file tables
- nError = RebuildFileTable(ha, dwNewHashTableSize, dwMaxFileCount);
+ // Rebuild both file tables
+ nError = RebuildFileTable(ha, dwNewHashTableSize);
+ }
}
// We always have to rebuild the (attributes) file due to file table change
diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp
index 206baff..fb9ed60 100644
--- a/src/SFileCreateArchive.cpp
+++ b/src/SFileCreateArchive.cpp
@@ -159,21 +159,21 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
// Increment the maximum amount of files to have space for (listfile)
if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags1)
{
- dwMpqFlags |= MPQ_FLAG_LISTFILE_INVALID;
+ dwMpqFlags |= MPQ_FLAG_LISTFILE_NEW;
dwReservedFiles++;
}
// Increment the maximum amount of files to have space for (attributes)
if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags2 && pCreateInfo->dwAttrFlags)
{
- dwMpqFlags |= MPQ_FLAG_ATTRIBUTES_INVALID;
+ dwMpqFlags |= MPQ_FLAG_ATTRIBUTES_NEW;
dwReservedFiles++;
}
// Increment the maximum amount of files to have space for (signature)
if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags3)
{
- dwMpqFlags |= MPQ_FLAG_SIGNATURE_INVALID;
+ dwMpqFlags |= MPQ_FLAG_SIGNATURE_NEW;
dwReservedFiles++;
}
@@ -256,11 +256,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
// Create initial file table
if(nError == ERROR_SUCCESS && ha->dwMaxFileCount != 0)
{
- ha->pFileTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount);
- if(ha->pFileTable != NULL)
- memset(ha->pFileTable, 0x00, sizeof(TFileEntry) * ha->dwMaxFileCount);
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ nError = CreateFileTable(ha, ha->dwMaxFileCount);
}
// Cleanup : If an error, delete all buffers and return
diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp
index fb71871..198f960 100644
--- a/src/SFileFindFile.cpp
+++ b/src/SFileFindFile.cpp
@@ -185,7 +185,6 @@ static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
TFileEntry * pPatchEntry = NULL;
TFileEntry * pTempEntry;
char szFileName[MAX_PATH];
- LCID lcLocale = pFileEntry->lcLocale;
// Go while there are patches
while(ha->haPatch != NULL)
@@ -200,7 +199,7 @@ static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
strcat(szFileName, pFileEntry->szFileName);
// Try to find the file there
- pTempEntry = GetFileEntryExact(ha, szFileName, lcLocale);
+ pTempEntry = GetFileEntryExact(ha, szFileName, 0, NULL);
if(pTempEntry != NULL)
pPatchEntry = pTempEntry;
}
@@ -218,7 +217,7 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
TFileEntry * pFileEntry;
const char * szFileName;
HANDLE hFile;
- char szPseudoName[20];
+ char szNameBuff[MAX_PATH];
DWORD dwBlockIndex;
size_t nPrefixLength;
@@ -260,11 +259,11 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
if(szFileName == NULL)
{
// Open the file by its pseudo-name.
- // This also generates the file name with a proper extension
- sprintf(szPseudoName, "File%08u.xxx", (unsigned int)dwBlockIndex);
- if(SFileOpenFileEx((HANDLE)hs->ha, szPseudoName, SFILE_OPEN_BASE_FILE, &hFile))
+ sprintf(szNameBuff, "File%08u.xxx", (unsigned int)dwBlockIndex);
+ if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile))
{
- szFileName = (pFileEntry->szFileName != NULL) ? pFileEntry->szFileName : szPseudoName;
+ SFileGetFileName(hFile, szNameBuff);
+ szFileName = szNameBuff;
SFileCloseFile(hFile);
}
}
@@ -276,12 +275,11 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
{
// Fill the found entry. hash entry and block index are taken from the base MPQ
- lpFindFileData->dwHashIndex = pFileEntry->dwHashIndex;
lpFindFileData->dwBlockIndex = dwBlockIndex;
lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
- lpFindFileData->lcLocale = pPatchEntry->lcLocale;
+ lpFindFileData->lcLocale = 0; // pPatchEntry->lcLocale;
// Fill the filetime
lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp
index f7faea0..40b2720 100644
--- a/src/SFileGetFileInfo.cpp
+++ b/src/SFileGetFileInfo.cpp
@@ -143,7 +143,6 @@ bool WINAPI SFileGetFileInfo(
TFileEntry * pFileEntry = NULL;
ULONGLONG Int64Value = 0;
ULONGLONG ByteOffset = 0;
- TMPQHash * pHash;
TMPQFile * hf = NULL;
void * pvSrcFileInfo = NULL;
DWORD cbSrcFileInfo = 0;
@@ -376,7 +375,7 @@ bool WINAPI SFileGetFileInfo(
if(ha != NULL && ha->pHashTable != NULL)
{
pvSrcFileInfo = ha->pHashTable;
- cbSrcFileInfo = ha->dwHashTableSize * sizeof(TMPQHash);
+ cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
@@ -629,9 +628,9 @@ bool WINAPI SFileGetFileInfo(
case SFileInfoHashEntry:
hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL)
+ if(hf != NULL && hf->pHashEntry != NULL)
{
- pvSrcFileInfo = hf->ha->pHashTable + hf->pFileEntry->dwHashIndex;
+ pvSrcFileInfo = hf->pHashEntry;
cbSrcFileInfo = sizeof(TMPQHash);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
@@ -639,9 +638,9 @@ bool WINAPI SFileGetFileInfo(
case SFileInfoHashIndex:
hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL && hf->pFileEntry != NULL)
+ if(hf != NULL && hf->pHashEntry != NULL)
{
- pvSrcFileInfo = &hf->pFileEntry->dwHashIndex;
+ pvSrcFileInfo = &hf->dwHashIndex;
cbSrcFileInfo = sizeof(DWORD);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
@@ -649,10 +648,10 @@ bool WINAPI SFileGetFileInfo(
case SFileInfoNameHash1:
hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL)
+ if(hf != NULL && hf->pHashEntry != NULL)
{
- pHash = hf->ha->pHashTable + hf->pFileEntry->dwHashIndex;
- pvSrcFileInfo = &pHash->dwName1;
+ dwInt32Value = hf->pHashEntry->dwName1;
+ pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
@@ -660,10 +659,10 @@ bool WINAPI SFileGetFileInfo(
case SFileInfoNameHash2:
hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL)
+ if(hf != NULL && hf->pHashEntry != NULL)
{
- pHash = hf->ha->pHashTable + hf->pFileEntry->dwHashIndex;
- pvSrcFileInfo = &pHash->dwName2;
+ dwInt32Value = hf->pHashEntry->dwName2;
+ pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
@@ -681,9 +680,9 @@ bool WINAPI SFileGetFileInfo(
case SFileInfoLocale:
hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL && hf->pFileEntry != NULL)
+ if(hf != NULL && hf->pHashEntry != NULL)
{
- dwInt32Value = hf->pFileEntry->lcLocale;
+ dwInt32Value = hf->pHashEntry->lcLocale;
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
@@ -950,10 +949,6 @@ bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
int nError = ERROR_INVALID_HANDLE;
- // Pre-zero the output buffer
- if(szFileName != NULL)
- *szFileName = 0;
-
// Check valid parameters
if(IsValidFileHandle(hFile))
{
@@ -966,15 +961,11 @@ bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
{
// If the file name is not there yet, create a pseudo name
if(pFileEntry->szFileName == NULL)
- {
nError = CreatePseudoFileName(hFile, pFileEntry, szFileName);
- }
- else
- {
- if(szFileName != NULL)
- strcpy(szFileName, pFileEntry->szFileName);
- nError = ERROR_SUCCESS;
- }
+
+ // Copy the file name to the output buffer, if any
+ if(pFileEntry->szFileName && szFileName)
+ strcpy(szFileName, pFileEntry->szFileName);
}
}
diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp
index 5f8a9df..385098e 100644
--- a/src/SFileListFile.cpp
+++ b/src/SFileListFile.cpp
@@ -389,7 +389,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil
// If we have HET table, use that one
if(ha->pHetTable != NULL)
{
- pFileEntry = GetFileEntryAny(ha, szFileName);
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
if(pFileEntry != NULL)
{
// Allocate file name for the file entry
@@ -402,18 +402,12 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil
// If we have hash table, we use it
if(ha->pHashTable != NULL)
{
- // Look for the first hash table entry for the file
- pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
-
// Go while we found something
+ pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
while(pHash != NULL)
{
- // Is it a valid file table index ?
- if(pHash->dwBlockIndex < ha->dwFileTableSize)
- {
- // Allocate file name for the file entry
- AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName);
- }
+ // Allocate file name for the file entry
+ AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName);
// Now find the next language version of the file
pHash = GetNextHashEntry(ha, pFirstHash, pHash);
@@ -437,7 +431,7 @@ int SListFileSaveToMpq(TMPQArchive * ha)
if(ha->dwFileFlags1 != 0)
{
// At this point, we expect to have at least one reserved entry in the file table
- assert(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID);
+ assert(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW);
assert(ha->dwReservedFiles > 0);
// Create the raw data that is to be written to (listfile)
@@ -470,6 +464,10 @@ int SListFileSaveToMpq(TMPQArchive * ha)
SFileAddFile_Finish(hf);
}
+ // Clear the listfile flags
+ ha->dwFlags &= ~(MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_NONE);
+ ha->dwReservedFiles--;
+
// Free the listfile buffer
STORM_FREE(pbListFile);
}
@@ -478,10 +476,6 @@ int SListFileSaveToMpq(TMPQArchive * ha)
// If the (listfile) file would be empty, its OK
nError = (cbListFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
}
-
- // Clear the listfile flags
- ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID;
- ha->dwReservedFiles--;
}
return nError;
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index ffd5bfe..5ce4290 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -339,8 +339,9 @@ bool WINAPI SFileOpenArchive(
ha->UserDataPos = SearchOffset;
// Set the position of the MPQ header
- ha->pHeader = (TMPQHeader *)ha->HeaderData;
- ha->MpqPos = SearchOffset;
+ ha->pHeader = (TMPQHeader *)ha->HeaderData;
+ ha->MpqPos = SearchOffset;
+ ha->FileSize = FileSize;
// Sector size must be nonzero.
if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0)
@@ -395,14 +396,6 @@ bool WINAPI SFileOpenArchive(
nError = BuildFileTable(ha);
}
- // We now have both hash table and block table loaded.
- // If any malformation was detected in the MPQ,
- // we perform hash table and block table validation (and eventual defragmentation)
- if(nError == ERROR_SUCCESS && ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1 && (ha->dwFlags & MPQ_FLAG_MALFORMED))
- {
- nError = ShrinkMalformedMpqTables(ha);
- }
-
// Load the internal listfile and include it to the file table
if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0)
{
@@ -492,7 +485,6 @@ bool WINAPI SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK Downlo
bool WINAPI SFileFlushArchive(HANDLE hMpq)
{
- TFileEntry * pFileEntry;
TMPQArchive * ha;
int nResultError = ERROR_SUCCESS;
int nError;
@@ -510,44 +502,7 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq)
// Indicate that we are saving MPQ internal structures
ha->dwFlags |= MPQ_FLAG_SAVING_TABLES;
- //
- // Invalidate entries for each internal file
- //
-
- if(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)
- {
- pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- {
- DeleteFileEntry(ha, pFileEntry);
- ha->dwReservedFiles++;
- }
- }
-
- if(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)
- {
- pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- {
- DeleteFileEntry(ha, pFileEntry);
- ha->dwReservedFiles++;
- }
- }
-
- if(ha->dwFlags & MPQ_FLAG_SIGNATURE_INVALID)
- {
- pFileEntry = GetFileEntryExact(ha, SIGNATURE_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- {
- DeleteFileEntry(ha, pFileEntry);
- ha->dwReservedFiles++;
- }
- }
-
- //
// Defragment the file table. This will allow us to put the internal files to the end
- //
-
DefragmentFileTable(ha);
//
@@ -555,21 +510,21 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq)
// Note that the (signature) file is usually before (listfile) in the file table
//
- if(ha->dwFlags & MPQ_FLAG_SIGNATURE_INVALID)
+ if(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW)
{
nError = SSignFileCreate(ha);
if(nError != ERROR_SUCCESS)
nResultError = nError;
}
- if(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)
+ if(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW)
{
nError = SListFileSaveToMpq(ha);
if(nError != ERROR_SUCCESS)
nResultError = nError;
}
- if(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)
+ if(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW)
{
nError = SAttrFileSaveToMpq(ha);
if(nError != ERROR_SUCCESS)
@@ -579,6 +534,10 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq)
// Save HET table, BET table, hash table, block table, hi-block table
if(ha->dwFlags & MPQ_FLAG_CHANGED)
{
+ // Rebuild the HET table
+ if(ha->pHetTable != NULL)
+ RebuildHetTable(ha);
+
// Save all MPQ tables first
nError = SaveMPQTables(ha);
if(nError != ERROR_SUCCESS)
diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp
index 3a9976e..3794680 100644
--- a/src/SFileOpenFileEx.cpp
+++ b/src/SFileOpenFileEx.cpp
@@ -16,6 +16,33 @@
/* Local functions */
/*****************************************************************************/
+static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex)
+{
+ TMPQHash * pHashTableEnd;
+ TMPQHash * pHash;
+ DWORD dwFirstIndex = HASH_ENTRY_FREE;
+
+ // Should only be called if the archive has hash table
+ assert(ha->pHashTable != NULL);
+
+ // Multiple hash table entries can point to the file table entry.
+ // We need to search all of them
+ pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
+ {
+ if(pHash->dwBlockIndex == dwFileIndex)
+ {
+ // Duplicate hash entry found
+ if(dwFirstIndex != HASH_ENTRY_FREE)
+ return HASH_ENTRY_FREE;
+ dwFirstIndex = (DWORD)(pHash - ha->pHashTable);
+ }
+ }
+
+ // Return the hash table entry index
+ return dwFirstIndex;
+}
+
static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
{
TMPQNamePrefix * pPrefix;
@@ -41,7 +68,7 @@ static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName,
return szFileName;
}
-static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
+static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile)
{
TFileStream * pStream;
TMPQFile * hf = NULL;
@@ -59,7 +86,7 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
if(hf != NULL)
{
hf->pStream = pStream;
- *phFile = hf;
+ *PtrFile = hf;
return true;
}
else
@@ -68,11 +95,11 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
}
}
- *phFile = NULL;
+ *PtrFile = NULL;
return false;
}
-bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * phFile)
+bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile)
{
TMPQArchive * haBase = NULL;
TMPQArchive * ha = (TMPQArchive *)hMpq;
@@ -88,7 +115,7 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * phFile)
while(ha != NULL)
{
// If the file is there, then we remember the archive
- pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0);
+ pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL);
if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
haBase = ha;
@@ -130,8 +157,8 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * phFile)
}
// Give the updated base MPQ
- if(phFile != NULL)
- *phFile = (HANDLE)hfBase;
+ if(PtrFile != NULL)
+ *PtrFile = (HANDLE)hfBase;
return (hfBase != NULL);
}
@@ -148,15 +175,15 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * phFile)
int WINAPI SFileEnumLocales(
HANDLE hMpq,
const char * szFileName,
- LCID * plcLocales,
- LPDWORD pdwMaxLocales,
+ LCID * PtrLocales,
+ LPDWORD PtrMaxLocales,
DWORD dwSearchScope)
{
TMPQArchive * ha = (TMPQArchive *)hMpq;
- TFileEntry * pFileEntry;
TMPQHash * pFirstHash;
TMPQHash * pHash;
DWORD dwFileIndex = 0;
+ DWORD dwMaxLocales;
DWORD dwLocales = 0;
// Test the parameters
@@ -164,130 +191,63 @@ int WINAPI SFileEnumLocales(
return ERROR_INVALID_HANDLE;
if(szFileName == NULL || *szFileName == 0)
return ERROR_INVALID_PARAMETER;
- if(pdwMaxLocales == NULL)
+ if(ha->pHashTable == NULL)
+ return ERROR_NOT_SUPPORTED;
+ if(PtrMaxLocales == NULL)
+ return ERROR_INVALID_PARAMETER;
+ if(IsPseudoFileName(szFileName, &dwFileIndex))
return ERROR_INVALID_PARAMETER;
// Keep compiler happy
+ dwMaxLocales = PtrMaxLocales[0];
dwSearchScope = dwSearchScope;
- // Parse hash table entries for all locales
- if(!IsPseudoFileName(szFileName, &dwFileIndex))
- {
- // Calculate the number of locales
- pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
- while(pHash != NULL)
- {
- dwLocales++;
- pHash = GetNextHashEntry(ha, pFirstHash, pHash);
- }
-
- // Test if there is enough space to copy the locales
- if(*pdwMaxLocales < dwLocales)
- {
- *pdwMaxLocales = dwLocales;
- return ERROR_INSUFFICIENT_BUFFER;
- }
-
- // Enum the locales
- pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
- while(pHash != NULL)
- {
- *plcLocales++ = pHash->lcLocale;
- pHash = GetNextHashEntry(ha, pFirstHash, pHash);
- }
- }
- else
- {
- // There must be space for 1 locale
- if(*pdwMaxLocales < 1)
- {
- *pdwMaxLocales = 1;
- return ERROR_INSUFFICIENT_BUFFER;
- }
-
- // For nameless access, always return 1 locale
- pFileEntry = GetFileEntryByIndex(ha, dwFileIndex);
- pHash = ha->pHashTable + pFileEntry->dwHashIndex;
- *plcLocales = pHash->lcLocale;
- dwLocales = 1;
- }
-
- // Give the caller the total number of found locales
- *pdwMaxLocales = dwLocales;
- return ERROR_SUCCESS;
-}
-
-//-----------------------------------------------------------------------------
-// SFileHasFile
-//
-// hMpq - Handle of opened MPQ archive
-// szFileName - Name of file to look for
-
-bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
-{
- TMPQArchive * ha = (TMPQArchive *)hMpq;
- TFileEntry * pFileEntry;
- DWORD dwFlagsToCheck = MPQ_FILE_EXISTS;
- DWORD dwFileIndex = 0;
- char szPrefixBuffer[MAX_PATH];
- bool bIsPseudoName;
- int nError = ERROR_SUCCESS;
-
- if(!IsValidMpqHandle(hMpq))
- nError = ERROR_INVALID_HANDLE;
- if(szFileName == NULL || *szFileName == 0)
- nError = ERROR_INVALID_PARAMETER;
-
- // Prepare the file opening
- if(nError == ERROR_SUCCESS)
+ // Parse all files with that name
+ pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
+ while(pHash != NULL)
{
- // Different processing for pseudo-names
- bIsPseudoName = IsPseudoFileName(szFileName, &dwFileIndex);
-
- // Walk through the MPQ and all patches
- while(ha != NULL)
- {
- // Verify presence of the file
- pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), lcFileLocale)
- : GetFileEntryByIndex(ha, dwFileIndex);
- // Verify the file flags
- if(pFileEntry != NULL && (pFileEntry->dwFlags & dwFlagsToCheck) == MPQ_FILE_EXISTS)
- return true;
-
- // If this is patched archive, go to the patch
- dwFlagsToCheck = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE;
- ha = ha->haPatch;
- }
+ // Put the locales to the buffer
+ if(PtrLocales != NULL && dwLocales < dwMaxLocales)
+ *PtrLocales++ = pHash->lcLocale;
+ dwLocales++;
- // Not found, sorry
- nError = ERROR_FILE_NOT_FOUND;
+ // Get the next locale
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
}
- // Cleanup
- SetLastError(nError);
- return false;
+ // Give the caller the number of locales and return
+ PtrMaxLocales[0] = dwLocales;
+ return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER;
}
-
//-----------------------------------------------------------------------------
// SFileOpenFileEx
//
// hMpq - Handle of opened MPQ archive
// szFileName - Name of file to open
// dwSearchScope - Where to search
-// phFile - Pointer to store opened file handle
+// PtrFile - Pointer to store opened file handle
-bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile)
+bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile)
{
- TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
TFileEntry * pFileEntry = NULL;
TMPQFile * hf = NULL;
+ DWORD dwHashIndex = HASH_ENTRY_FREE;
DWORD dwFileIndex = 0;
bool bOpenByIndex = false;
int nError = ERROR_SUCCESS;
// Don't accept NULL pointer to file handle
- if(phFile == NULL)
+ if(szFileName == NULL || *szFileName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // When opening a file from MPQ, the handle must be valid
+ if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL)
+ nError = ERROR_INVALID_HANDLE;
+
+ // When not checking for existence, the pointer to file handle must be valid
+ if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL)
nError = ERROR_INVALID_PARAMETER;
// Prepare the file opening
@@ -297,40 +257,28 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
{
case SFILE_OPEN_FROM_MPQ:
case SFILE_OPEN_BASE_FILE:
+ case SFILE_OPEN_CHECK_EXISTS:
- if(!IsValidMpqHandle(hMpq))
- {
- nError = ERROR_INVALID_HANDLE;
- break;
- }
-
- if(szFileName == NULL || *szFileName == 0)
- {
- nError = ERROR_INVALID_PARAMETER;
- break;
- }
-
// Check the pseudo-file name
- if(IsPseudoFileName(szFileName, &dwFileIndex))
+ if((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true)
{
- pFileEntry = GetFileEntryByIndex(ha, dwFileIndex);
- bOpenByIndex = true;
- if(pFileEntry == NULL)
- nError = ERROR_FILE_NOT_FOUND;
+ // Get the file entry for the file
+ if(dwFileIndex > ha->dwFileTableSize)
+ break;
+ pFileEntry = ha->pFileTable + dwFileIndex;
}
else
{
- // If this MPQ is a patched archive, open the file as patched
+ // If this MPQ has no patches, open the file from this MPQ directly
if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE)
{
- // Otherwise, open the file from *this* MPQ
- pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
- if(pFileEntry == NULL)
- nError = ERROR_FILE_NOT_FOUND;
+ pFileEntry = GetFileEntryLocale2(ha, szFileName, lcFileLocale, &dwHashIndex);
}
+
+ // If this MPQ is a patched archive, open the file as patched
else
{
- return OpenPatchedFile(hMpq, szFileName, phFile);
+ return OpenPatchedFile(hMpq, szFileName, PtrFile);
}
}
break;
@@ -340,20 +288,13 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
// This open option is reserved for opening MPQ internal listfile.
// No argument validation. Tries to open file with neutral locale first,
// then any other available.
- pFileEntry = GetFileEntryAny(ha, szFileName);
- if(pFileEntry == NULL)
- nError = ERROR_FILE_NOT_FOUND;
+ pFileEntry = GetFileEntryLocale2(ha, szFileName, 0, &dwHashIndex);
break;
case SFILE_OPEN_LOCAL_FILE:
- if(szFileName == NULL || *szFileName == 0)
- {
- nError = ERROR_INVALID_PARAMETER;
- break;
- }
-
- return OpenLocalFile(szFileName, phFile);
+ // Open a local file
+ return OpenLocalFile(szFileName, PtrFile);
default:
@@ -361,73 +302,76 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
nError = ERROR_INVALID_PARAMETER;
break;
}
-
- // Quick return if something failed
- if(nError != ERROR_SUCCESS)
- {
- SetLastError(nError);
- *phFile = NULL;
- return false;
- }
}
- // Test if the file was not already deleted.
+ // Check whether the file really exists in the MPQ
if(nError == ERROR_SUCCESS)
{
- if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
+ if(pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
nError = ERROR_FILE_NOT_FOUND;
- if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS)
+ if(pFileEntry != NULL && pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS)
nError = ERROR_NOT_SUPPORTED;
}
- // Allocate file handle
- if(nError == ERROR_SUCCESS)
+ // Did the caller just wanted to know if the file exists?
+ if(nError == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS)
{
+ // Allocate file handle
hf = CreateFileHandle(ha, pFileEntry);
- if(hf == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
-
- // Initialize file handle
- if(nError == ERROR_SUCCESS)
- {
- // If the MPQ has sector CRC enabled, enable if for the file
- if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC)
- hf->bCheckSectorCRCs = true;
-
- // If we know the real file name, copy it to the file entry
- if(bOpenByIndex == false)
+ if(hf != NULL)
{
- // If there is no file name yet, allocate it
- AllocateFileName(ha, pFileEntry, szFileName);
-
- // If the file is encrypted, we should detect the file key
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ // Get the hash index for the file
+ if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE)
+ dwHashIndex = FindHashIndex(ha, dwFileIndex);
+ if(dwHashIndex != HASH_ENTRY_FREE)
+ hf->pHashEntry = ha->pHashTable + dwHashIndex;
+ hf->dwHashIndex = dwHashIndex;
+
+ // If the MPQ has sector CRC enabled, enable if for the file
+ if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC)
+ hf->bCheckSectorCRCs = true;
+
+ // If we know the real file name, copy it to the file entry
+ if(bOpenByIndex == false)
{
- hf->dwFileKey = DecryptFileKey(szFileName,
- pFileEntry->ByteOffset,
- pFileEntry->dwFileSize,
- pFileEntry->dwFlags);
+ // If there is no file name yet, allocate it
+ AllocateFileName(ha, pFileEntry, szFileName);
+
+ // If the file is encrypted, we should detect the file key
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ hf->dwFileKey = DecryptFileKey(szFileName,
+ pFileEntry->ByteOffset,
+ pFileEntry->dwFileSize,
+ pFileEntry->dwFlags);
+ }
}
}
else
{
- // Try to auto-detect the file name
- if(!SFileGetFileName(hf, NULL))
- nError = GetLastError();
+ nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
- // Cleanup and exit
+ // Give the file entry
+ if(PtrFile != NULL)
+ PtrFile[0] = hf;
+
+ // Return error code
if(nError != ERROR_SUCCESS)
- {
SetLastError(nError);
- FreeFileHandle(hf);
- return false;
- }
+ return (nError == ERROR_SUCCESS);
+}
- *phFile = hf;
- return true;
+//-----------------------------------------------------------------------------
+// SFileHasFile
+//
+// hMpq - Handle of opened MPQ archive
+// szFileName - Name of file to look for
+
+bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
+{
+ return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL);
}
//-----------------------------------------------------------------------------
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp
index 21446d8..f4867b0 100644
--- a/src/SFilePatchArchives.cpp
+++ b/src/SFilePatchArchives.cpp
@@ -19,6 +19,31 @@
#define PATCH_SIGNATURE_MD5 0x5f35444d
#define PATCH_SIGNATURE_XFRM 0x4d524658
+#define SIZE_OF_XFRM_HEADER 0x0C
+
+// Header for incremental patch files
+typedef struct _MPQ_PATCH_HEADER
+{
+ //-- PATCH header -----------------------------------
+ DWORD dwSignature; // 'PTCH'
+ DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed)
+ DWORD dwSizeBeforePatch; // Size of the file before patch
+ DWORD dwSizeAfterPatch; // Size of file after patch
+
+ //-- MD5 block --------------------------------------
+ DWORD dwMD5; // 'MD5_'
+ DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself
+ BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file
+ BYTE md5_after_patch[0x10]; // MD5 of the patched file
+
+ //-- XFRM block -------------------------------------
+ DWORD dwXFRM; // 'XFRM'
+ DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data
+ DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY')
+
+ // Followed by the patch data
+} MPQ_PATCH_HEADER, *PMPQ_PATCH_HEADER;
+
typedef struct _BLIZZARD_BSDIFF40_FILE
{
ULONGLONG Signature;
@@ -27,31 +52,61 @@ typedef struct _BLIZZARD_BSDIFF40_FILE
ULONGLONG NewFileSize;
} BLIZZARD_BSDIFF40_FILE, *PBLIZZARD_BSDIFF40_FILE;
+typedef struct _BSDIFF_CTRL_BLOCK
+{
+ DWORD dwAddDataLength;
+ DWORD dwMovDataLength;
+ DWORD dwOldMoveLength;
+
+} BSDIFF_CTRL_BLOCK, *PBSDIFF_CTRL_BLOCK;
+
+typedef struct _LOCALIZED_MPQ_INFO
+{
+ const char * szNameTemplate; // Name template
+ size_t nLangOffset; // Offset of the language
+ size_t nLength; // Length of the name template
+} LOCALIZED_MPQ_INFO, *PLOCALIZED_MPQ_INFO;
+
//-----------------------------------------------------------------------------
// Local variables
-static const char * LanguageList[] =
+// 4-byte groups for all languages
+static const char * LanguageList = "baseteenenUSenGBenCNenTWdeDEesESesMXfrFRitITkoKRptBRptPTruRUzhCNzhTW";
+
+// List of localized MPQs for World of Warcraft
+static LOCALIZED_MPQ_INFO LocaleMpqs_WoW[] =
{
- "deDE",
- "enCN",
- "enGB",
- "enTW",
- "enUS",
- "esES",
- "esMX",
- "frFR",
- "koKR",
- "ptBR",
- "ptPT",
- "ruRU",
- "zhCN",
- "zhTW",
- NULL
+ {"expansion1-locale-####", 18, 22},
+ {"expansion1-speech-####", 18, 22},
+ {"expansion2-locale-####", 18, 22},
+ {"expansion2-speech-####", 18, 22},
+ {"expansion3-locale-####", 18, 22},
+ {"expansion3-speech-####", 18, 22},
+ {"locale-####", 7, 11},
+ {"speech-####", 7, 11},
+ {NULL, 0, 0}
};
//-----------------------------------------------------------------------------
// Local functions
+static inline bool IsPatchMetadataFile(TFileEntry * pFileEntry)
+{
+ // The file must ave a name
+ if(pFileEntry->szFileName != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
+ {
+ // The file must be small
+ if(0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40)
+ {
+ // Compare the plain name
+ return (_stricmp(GetPlainFileName(pFileEntry->szFileName), PATCH_METADATA_NAME) == 0);
+ }
+ }
+
+ // Not a patch_metadata
+ return false;
+}
+
static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE pbCompressed, DWORD cbCompressed)
{
LPBYTE pbDecompressedEnd = pbDecompressed + cbDecompressed;
@@ -89,122 +144,94 @@ static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE p
}
}
-static int LoadFilePatch_COPY(TMPQFile * hf, TPatchHeader * pPatchHeader)
+static int LoadFilePatch_COPY(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch)
{
- int nError = ERROR_SUCCESS;
-
- // Allocate space for patch header and compressed data
- hf->pPatchHeader = (TPatchHeader *)STORM_ALLOC(BYTE, pPatchHeader->dwSizeOfPatchData);
- if(hf->pPatchHeader == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ DWORD cbBytesToRead = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER);
+ DWORD cbBytesRead = 0;
- // Load the patch data and decide if they are compressed or not
- if(nError == ERROR_SUCCESS)
- {
- LPBYTE pbPatchFile = (LPBYTE)hf->pPatchHeader;
-
- // Copy the patch header itself
- memcpy(pbPatchFile, pPatchHeader, sizeof(TPatchHeader));
- pbPatchFile += sizeof(TPatchHeader);
-
- // Load the rest of the patch
- if(!SFileReadFile((HANDLE)hf, pbPatchFile, pPatchHeader->dwSizeOfPatchData - sizeof(TPatchHeader), NULL, NULL))
- nError = GetLastError();
- }
-
- return nError;
+ // Simply load the rest of the patch
+ SFileReadFile((HANDLE)hf, (pFullPatch + 1), cbBytesToRead, &cbBytesRead, NULL);
+ return (cbBytesRead == cbBytesToRead) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
}
-static int LoadFilePatch_BSD0(TMPQFile * hf, TPatchHeader * pPatchHeader)
+static int LoadFilePatch_BSD0(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch)
{
- LPBYTE pbDecompressed = NULL;
+ LPBYTE pbDecompressed = (LPBYTE)(pFullPatch + 1);
LPBYTE pbCompressed = NULL;
DWORD cbDecompressed = 0;
DWORD cbCompressed = 0;
DWORD dwBytesRead = 0;
int nError = ERROR_SUCCESS;
- // Allocate space for compressed data
- cbCompressed = pPatchHeader->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER;
- pbCompressed = STORM_ALLOC(BYTE, cbCompressed);
- if(pbCompressed == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ // Calculate the size of compressed data
+ cbDecompressed = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER);
+ cbCompressed = pFullPatch->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER;
- // Read the compressed patch data
- if(nError == ERROR_SUCCESS)
- {
- // Load the rest of the header
- SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL);
- if(dwBytesRead != cbCompressed)
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Get the uncompressed size of the patch
- if(nError == ERROR_SUCCESS)
+ // Is that file compressed?
+ if(cbCompressed < cbDecompressed)
{
- cbDecompressed = pPatchHeader->dwSizeOfPatchData - sizeof(TPatchHeader);
- hf->pPatchHeader = (TPatchHeader *)STORM_ALLOC(BYTE, pPatchHeader->dwSizeOfPatchData);
- if(hf->pPatchHeader == NULL)
+ pbCompressed = STORM_ALLOC(BYTE, cbCompressed);
+ if(pbCompressed == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
- }
-
- // Now decompress the patch data
- if(nError == ERROR_SUCCESS)
- {
- // Copy the patch header
- memcpy(hf->pPatchHeader, pPatchHeader, sizeof(TPatchHeader));
- pbDecompressed = (LPBYTE)hf->pPatchHeader + sizeof(TPatchHeader);
- // Uncompress or copy the patch data
- if(cbCompressed < cbDecompressed)
+ // Read the compressed patch data
+ if(nError == ERROR_SUCCESS)
{
- Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed);
- }
- else
- {
- assert(cbCompressed == cbDecompressed);
- memcpy(pbDecompressed, pbCompressed, cbCompressed);
+ SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL);
+ if(dwBytesRead != cbCompressed)
+ nError = ERROR_FILE_CORRUPT;
}
+
+ // Decompress the data
+ if(nError == ERROR_SUCCESS)
+ Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed);
+
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+ }
+ else
+ {
+ SFileReadFile((HANDLE)hf, pbDecompressed, cbDecompressed, &dwBytesRead, NULL);
+ if(dwBytesRead != cbDecompressed)
+ nError = ERROR_FILE_CORRUPT;
}
- // Free buffers and exit
- if(pbCompressed != NULL)
- STORM_FREE(pbCompressed);
return nError;
}
static int ApplyFilePatch_COPY(
- TMPQFile * hfFrom,
- TMPQFile * hf,
- TPatchHeader * pPatchHeader)
+ TMPQPatcher * pPatcher,
+ PMPQ_PATCH_HEADER pFullPatch,
+ LPBYTE pbTarget,
+ LPBYTE pbSource)
{
// Sanity checks
- assert(hf->cbFileData == (pPatchHeader->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER));
- assert(hf->pbFileData != NULL);
- hfFrom = hfFrom;
+ assert(pPatcher->cbMaxFileData >= pPatcher->cbFileData);
+ pFullPatch = pFullPatch;
// Copy the patch data as-is
- memcpy(hf->pbFileData, (LPBYTE)pPatchHeader + sizeof(TPatchHeader), hf->cbFileData);
+ memcpy(pbTarget, pbSource, pPatcher->cbFileData);
return ERROR_SUCCESS;
}
static int ApplyFilePatch_BSD0(
- TMPQFile * hfFrom,
- TMPQFile * hf,
- TPatchHeader * pPatchHeader)
+ TMPQPatcher * pPatcher,
+ PMPQ_PATCH_HEADER pFullPatch,
+ LPBYTE pbTarget,
+ LPBYTE pbSource)
{
PBLIZZARD_BSDIFF40_FILE pBsdiff;
- LPDWORD pCtrlBlock;
- LPBYTE pbPatchData = (LPBYTE)pPatchHeader + sizeof(TPatchHeader);
+ PBSDIFF_CTRL_BLOCK pCtrlBlock;
+ LPBYTE pbPatchData = (LPBYTE)(pFullPatch + 1);
LPBYTE pDataBlock;
LPBYTE pExtraBlock;
- LPBYTE pbOldData = hfFrom->pbFileData;
- LPBYTE pbNewData = hf->pbFileData;
+ LPBYTE pbOldData = pbSource;
+ LPBYTE pbNewData = pbTarget;
DWORD dwCombineSize;
DWORD dwNewOffset = 0; // Current position to patch
DWORD dwOldOffset = 0; // Current source position
DWORD dwNewSize; // Patched file size
- DWORD dwOldSize = hfFrom->cbFileData; // File size before patch
+ DWORD dwOldSize = pPatcher->cbFileData; // File size before patch
// Get pointer to the patch header
// Format of BSDIFF header corresponds to original BSDIFF, which is:
@@ -221,7 +248,7 @@ static int ApplyFilePatch_BSD0(
// 0000 4 bytes Length to copy from the BSDIFF data block the new file
// 0004 4 bytes Length to copy from the BSDIFF extra block
// 0008 4 bytes Size to increment source file offset
- pCtrlBlock = (LPDWORD)pbPatchData;
+ pCtrlBlock = (PBSDIFF_CTRL_BLOCK)pbPatchData;
pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->CtrlBlockSize);
// Get the pointer to the data block
@@ -235,9 +262,9 @@ static int ApplyFilePatch_BSD0(
// Now patch the file
while(dwNewOffset < dwNewSize)
{
- DWORD dwAddDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock[0]);
- DWORD dwMovDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock[1]);
- DWORD dwOldMoveLength = BSWAP_INT32_UNSIGNED(pCtrlBlock[2]);
+ DWORD dwAddDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwAddDataLength);
+ DWORD dwMovDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwMovDataLength);
+ DWORD dwOldMoveLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwOldMoveLength);
DWORD i;
// Sanity check
@@ -272,175 +299,122 @@ static int ApplyFilePatch_BSD0(
if(dwOldMoveLength & 0x80000000)
dwOldMoveLength = 0x80000000 - dwOldMoveLength;
dwOldOffset += dwOldMoveLength;
- pCtrlBlock += 3;
+ pCtrlBlock++;
}
- // Success
+ // The size after patch must match
+ if(dwNewOffset != pFullPatch->dwSizeAfterPatch)
+ return ERROR_FILE_CORRUPT;
+
+ // Update the new data size
+ pPatcher->cbFileData = dwNewOffset;
return ERROR_SUCCESS;
}
-
-static int LoadFilePatch(TMPQFile * hf)
+static PMPQ_PATCH_HEADER LoadFullFilePatch(TMPQFile * hf, MPQ_PATCH_HEADER & PatchHeader)
{
- TPatchHeader PatchHeader;
- DWORD dwBytesRead;
+ PMPQ_PATCH_HEADER pFullPatch;
int nError = ERROR_SUCCESS;
- // Read the patch header
- SFileReadFile((HANDLE)hf, &PatchHeader, sizeof(TPatchHeader), &dwBytesRead, NULL);
- if(dwBytesRead != sizeof(TPatchHeader))
- nError = ERROR_FILE_CORRUPT;
+ // BSWAP the entire header, if needed
+ BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6);
+ BSWAP_ARRAY32_UNSIGNED(&PatchHeader.dwXFRM, sizeof(DWORD) * 3);
// Verify the signatures in the patch header
- if(nError == ERROR_SUCCESS)
- {
- // BSWAP the entire header, if needed
- BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6);
- PatchHeader.dwXFRM = BSWAP_INT32_UNSIGNED(PatchHeader.dwXFRM);
- PatchHeader.dwXfrmBlockSize = BSWAP_INT32_UNSIGNED(PatchHeader.dwXfrmBlockSize);
- PatchHeader.dwPatchType = BSWAP_INT32_UNSIGNED(PatchHeader.dwPatchType);
-
- if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM)
- nError = ERROR_FILE_CORRUPT;
- }
+ if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM)
+ return NULL;
- // Read the patch, depending on patch type
- if(nError == ERROR_SUCCESS)
+ // Allocate space for patch header and compressed data
+ pFullPatch = (PMPQ_PATCH_HEADER)STORM_ALLOC(BYTE, PatchHeader.dwSizeOfPatchData);
+ if(pFullPatch != NULL)
{
- switch(PatchHeader.dwPatchType)
+ // Copy the patch header
+ memcpy(pFullPatch, &PatchHeader, sizeof(MPQ_PATCH_HEADER));
+
+ // Read the patch, depending on patch type
+ if(nError == ERROR_SUCCESS)
{
- case 0x59504f43: // 'COPY'
- nError = LoadFilePatch_COPY(hf, &PatchHeader);
- break;
+ switch(PatchHeader.dwPatchType)
+ {
+ case 0x59504f43: // 'COPY'
+ nError = LoadFilePatch_COPY(hf, pFullPatch);
+ break;
- case 0x30445342: // 'BSD0'
- nError = LoadFilePatch_BSD0(hf, &PatchHeader);
- break;
+ case 0x30445342: // 'BSD0'
+ nError = LoadFilePatch_BSD0(hf, pFullPatch);
+ break;
- default:
- nError = ERROR_FILE_CORRUPT;
- break;
+ default:
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+ }
+
+ // If something failed, free the patch buffer
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pFullPatch);
+ pFullPatch = NULL;
}
}
- return nError;
+ // Give the result to the caller
+ return pFullPatch;
}
static int ApplyFilePatch(
- TMPQFile * hfBase, // The file in the base MPQ
- TMPQFile * hfPrev, // The file in the previous MPQ
- TMPQFile * hf)
+ TMPQPatcher * pPatcher,
+ PMPQ_PATCH_HEADER pFullPatch)
{
- TPatchHeader * pPatchHeader = hf->pPatchHeader;
- TMPQFile * hfFrom = NULL;
- int nError = ERROR_SUCCESS;
+ LPBYTE pbSource = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData2 : pPatcher->pbFileData1;
+ LPBYTE pbTarget = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData1 : pPatcher->pbFileData2;
+ int nError;
// Sanity checks
- assert(hf->pbFileData == NULL);
-
- // Either take the base version or the previous version
- if(!memcmp(hfBase->FileDataMD5, pPatchHeader->md5_before_patch, MD5_DIGEST_SIZE))
- hfFrom = hfBase;
- if(!memcmp(hfPrev->FileDataMD5, pPatchHeader->md5_before_patch, MD5_DIGEST_SIZE))
- hfFrom = hfPrev;
- if(hfFrom == NULL)
- return ERROR_FILE_CORRUPT;
-
- // Allocate the buffer for patched file content
- hf->pbFileData = STORM_ALLOC(BYTE, pPatchHeader->dwSizeAfterPatch);
- hf->cbFileData = pPatchHeader->dwSizeAfterPatch;
- if(hf->pbFileData == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
+ assert(pFullPatch->dwSizeAfterPatch <= pPatcher->cbMaxFileData);
- // Apply the patch
- if(nError == ERROR_SUCCESS)
+ // Apply the patch according to the type
+ switch(pFullPatch->dwPatchType)
{
- switch(pPatchHeader->dwPatchType)
- {
- case 0x59504f43: // 'COPY'
- nError = ApplyFilePatch_COPY(hfFrom, hf, pPatchHeader);
- break;
+ case 0x59504f43: // 'COPY'
+ nError = ApplyFilePatch_COPY(pPatcher, pFullPatch, pbTarget, pbSource);
+ break;
- case 0x30445342: // 'BSD0'
- nError = ApplyFilePatch_BSD0(hfFrom, hf, pPatchHeader);
- break;
+ case 0x30445342: // 'BSD0'
+ nError = ApplyFilePatch_BSD0(pPatcher, pFullPatch, pbTarget, pbSource);
+ break;
- default:
- nError = ERROR_FILE_CORRUPT;
- break;
- }
+ default:
+ nError = ERROR_FILE_CORRUPT;
+ break;
}
// Verify MD5 after patch
- if(nError == ERROR_SUCCESS && pPatchHeader->dwSizeAfterPatch != 0)
+ if(nError == ERROR_SUCCESS && pFullPatch->dwSizeAfterPatch != 0)
{
// Verify the patched file
- if(!VerifyDataBlockHash(hf->pbFileData, hf->cbFileData, pPatchHeader->md5_after_patch))
+ if(!VerifyDataBlockHash(pbTarget, pFullPatch->dwSizeAfterPatch, pFullPatch->md5_after_patch))
nError = ERROR_FILE_CORRUPT;
// Copy the MD5 of the new block
- memcpy(hf->FileDataMD5, pPatchHeader->md5_after_patch, MD5_DIGEST_SIZE);
+ memcpy(pPatcher->this_md5, pFullPatch->md5_after_patch, MD5_DIGEST_SIZE);
}
return nError;
}
-static void FreePatchData(TMPQFile * hf)
-{
- STORM_FREE(hf->pbFileData);
- hf->pbFileData = NULL;
- hf->cbFileData = 0;
-
- STORM_FREE(hf->pPatchHeader);
- hf->pPatchHeader = NULL;
-}
-
//-----------------------------------------------------------------------------
// Local functions (patch prefix matching)
-static TFileEntry * FindMd5ListFile(TMPQArchive * ha)
-{
- TFileEntry * pFileEntry = ha->pFileTable + ha->dwFileTableSize;
- char * szLstName;
- size_t nTryCount = 0;
- size_t nLength;
-
- // Check every file entry for "*-md5.lst".
- // Go backwards, as the entry is usually at the end of the file table
- while(pFileEntry > ha->pFileTable && nTryCount < 10)
- {
- // The file name must be valid
- if(pFileEntry->szFileName != NULL)
- {
- // Get the name and length
- szLstName = pFileEntry->szFileName;
- nLength = strlen(szLstName);
-
- // Check for the tail name
- if(!_stricmp(szLstName + nLength - 8, "-md5.lst"))
- return pFileEntry;
- }
-
- // Move back
- pFileEntry--;
- nTryCount++;
- }
-
- // Not found, sorry
- return NULL;
-}
-
-static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, const char * szPrefixEnd)
+static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, size_t nLength)
{
TMPQNamePrefix * pNewPrefix;
- size_t nLength;
// If the end of the patch prefix was not entered, find it
- if(szFileName != NULL && szPrefixEnd == NULL)
- szPrefixEnd = szFileName + strlen(szFileName);
+ if(szFileName != NULL && nLength == 0)
+ nLength = strlen(szFileName);
// Create the patch prefix
- nLength = (szPrefixEnd - szFileName);
pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength);
if(pNewPrefix != NULL)
{
@@ -465,7 +439,7 @@ static bool IsMatchingPatchFile(
const char * szFileName,
LPBYTE pbFileMd5)
{
- TPatchHeader PatchHeader = {0};
+ MPQ_PATCH_HEADER PatchHeader = {0};
HANDLE hFile = NULL;
DWORD dwTransferred = 0;
bool bResult = false;
@@ -474,12 +448,12 @@ static bool IsMatchingPatchFile(
if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
{
// Load the patch header
- SFileReadFile(hFile, &PatchHeader, sizeof(TPatchHeader), &dwTransferred, NULL);
+ SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL);
BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6);
// If the file contains an incremental patch,
// compare the "MD5 before patching" with the base file MD5
- if(dwTransferred == sizeof(TPatchHeader) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER)
+ if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER)
bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE));
// Close the file
@@ -489,51 +463,87 @@ static bool IsMatchingPatchFile(
return bResult;
}
-static const char * GetLstFileLanguage(const char * szFileName)
+static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pMpqInfo)
{
- char szLstSuffix[0x80];
- size_t nLength;
- size_t nSuffixLength;
-
- // Each language-dependent file ends with "xxXX-md5.lst"
- nLength = strlen(szFileName);
- if(nLength < 12)
- return NULL;
+ TFileEntry * pFileEntry;
+ const char * szLanguage = LanguageList;
+ char szFileName[0x40];
- // Try each and every possibility
- for(size_t i = 0; LanguageList[i] != NULL; i++)
+ // Iterate through all localized languages
+ while(pMpqInfo->szNameTemplate != NULL)
{
- nSuffixLength = sprintf(szLstSuffix, "%s-md5.lst", LanguageList[i]);
- assert(nSuffixLength == 12);
+ // Iterate through all languages
+ for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage += 4)
+ {
+ // Construct the file name
+ memcpy(szFileName, pMpqInfo->szNameTemplate, pMpqInfo->nLength);
+ szFileName[pMpqInfo->nLangOffset + 0] = szLanguage[0];
+ szFileName[pMpqInfo->nLangOffset + 1] = szLanguage[1];
+ szFileName[pMpqInfo->nLangOffset + 2] = szLanguage[2];
+ szFileName[pMpqInfo->nLangOffset + 3] = szLanguage[3];
+
+ // Append the suffix
+ memcpy(szFileName + pMpqInfo->nLength, "-md5.lst", 9);
+
+ // Check whether the name exists
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
+ if(pFileEntry != NULL)
+ return szLanguage;
+ }
- if(!_stricmp(szFileName + nLength - nSuffixLength, szLstSuffix))
- return LanguageList[i];
+ // Move to the next language name
+ pMpqInfo++;
}
+ // Not found
return NULL;
}
-static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch)
+static TFileEntry * FindBaseLstFile(TMPQArchive * ha)
{
TFileEntry * pFileEntry;
- const char * szFilePrefix = "Base";
const char * szLanguage;
- char szNamePrefix[0x10];
- int nLength;
+ char szFileName[0x40];
- // Find a *-md5.lst file in the base archive
- pFileEntry = FindMd5ListFile(haBase);
- if(pFileEntry == NULL)
- return false;
+ // Prepare the file name tenplate
+ memcpy(szFileName, "####-md5.lst", 13);
- // Language-specific MPQs have the language identifier right before extension
- szLanguage = GetLstFileLanguage(pFileEntry->szFileName);
- if(szLanguage != NULL)
- szFilePrefix = szLanguage;
+ // Try all languages
+ for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage++)
+ {
+ // Copy the language name
+ szFileName[0] = szLanguage[0];
+ szFileName[1] = szLanguage[1];
+ szFileName[2] = szLanguage[2];
+ szFileName[3] = szLanguage[3];
+
+ // Check whether this file exists
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
+ if(pFileEntry != NULL)
+ return pFileEntry;
+ }
- // Format the name prefix
- nLength = sprintf(szNamePrefix, "%s\\", szFilePrefix);
- return CreatePatchPrefix(haPatch, szNamePrefix, &szNamePrefix[nLength]);
+ return NULL;
+}
+
+static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch)
+{
+ const char * szPatchPrefix;
+ char szNamePrefix[0x08];
+
+ // Try to find the language of the MPQ archive
+ szPatchPrefix = FindArchiveLanguage(haBase, LocaleMpqs_WoW);
+ if(szPatchPrefix == NULL)
+ szPatchPrefix = "Base";
+
+ // Format the patch prefix
+ szNamePrefix[0] = szPatchPrefix[0];
+ szNamePrefix[1] = szPatchPrefix[1];
+ szNamePrefix[2] = szPatchPrefix[2];
+ szNamePrefix[3] = szPatchPrefix[3];
+ szNamePrefix[4] = '\\';
+ szNamePrefix[5] = 0;
+ return CreatePatchPrefix(haPatch, szNamePrefix, 5);
}
//
@@ -555,60 +565,68 @@ static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive *
// We need to match the file by its MD5
//
+
static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch)
{
- TFileEntry * pFileTableEnd;
- TFileEntry * pFileEntry;
+ TMPQNamePrefix * pPatchPrefix;
TFileEntry * pBaseEntry;
- const char * szPlainName;
char * szLstFileName;
+ char * szPlainName;
size_t cchWorkBuffer = 0x400;
- size_t cchBaseName;
- size_t cchDirName;
bool bResult = false;
- // Find a *-md5.lst file in the base archive
- pBaseEntry = FindMd5ListFile(haBase);
- if(pBaseEntry == NULL)
- return false;
- cchBaseName = strlen(pBaseEntry->szFileName) + 1;
-
- // Allocate working buffer for merging LST file
- szLstFileName = STORM_ALLOC(char, cchWorkBuffer);
- if(szLstFileName != NULL)
+ // First-level patches: Find the same file within the patch archive
+ // and verify by MD5-before-patch
+ if(haBase->haPatch == NULL)
{
- // Find that file in the patch MPQ
- pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
- for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
+ TFileEntry * pFileEntry;
+
+ // Allocate working buffer for merging LST file
+ szLstFileName = STORM_ALLOC(char, cchWorkBuffer);
+ if(szLstFileName != NULL)
{
- // Find the "(patch_metadata)" file within that folder
- // Note that the file is always relatively small and contains the patch prefix
- // Checking for file size greatly speeds up the search process
- if(pFileEntry->szFileName && !(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40))
- {
- // If the plain file name matches, we need to check its MD5
- szPlainName = GetPlainFileName(pFileEntry->szFileName);
- cchDirName = (size_t)(szPlainName - pFileEntry->szFileName);
+ // Find a *-md5.lst file in the base archive
+ pBaseEntry = FindBaseLstFile(haBase);
+ if(pBaseEntry == NULL)
+ return false;
- // The file name must not too long and must be PATCH_METADATA_NAME
- if((cchDirName + cchBaseName) < cchWorkBuffer && _stricmp(szPlainName, PATCH_METADATA_NAME) == 0)
+ // Parse the entire file table
+ for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Look for "patch_metadata" file
+ if(IsPatchMetadataFile(pFileEntry))
{
- // Construct the name of the eventuall LST file
- memcpy(szLstFileName, pFileEntry->szFileName, cchDirName);
- memcpy(szLstFileName + cchDirName, pBaseEntry->szFileName, cchBaseName);
+ // Construct the name of the MD5 file
+ strcpy(szLstFileName, pFileEntry->szFileName);
+ szPlainName = (char *)GetPlainFileName(szLstFileName);
+ strcpy(szPlainName, pBaseEntry->szFileName);
- // If there is the "*-md5.lst" file in that directory, we check its MD5
+ // Check for matching MD5 file
if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5))
{
- bResult = CreatePatchPrefix(haPatch, pFileEntry->szFileName, szPlainName);
+ bResult = CreatePatchPrefix(haPatch, szLstFileName, (size_t)(szPlainName - szLstFileName));
break;
}
}
}
+
+ // Delete the merge buffer
+ STORM_FREE(szLstFileName);
}
+ }
- // Free the work buffer
- STORM_FREE(szLstFileName);
+ // For second-level patches, just take the patch prefix from the lower level patch
+ else
+ {
+ // There must be at least two patches in the chain
+ assert(haBase->haPatch->pPatchPrefix != NULL);
+ pPatchPrefix = haBase->haPatch->pPatchPrefix;
+
+ // Copy the patch prefix
+ bResult = CreatePatchPrefix(haPatch,
+ pPatchPrefix->szPatchPrefix,
+ pPatchPrefix->nLength);
}
return bResult;
@@ -618,23 +636,22 @@ static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const c
{
// If the patch prefix was explicitly entered, we use that one
if(szPatchPathPrefix != NULL)
- return CreatePatchPrefix(haPatch, szPatchPathPrefix, szPatchPathPrefix + strlen(szPatchPathPrefix));
+ return CreatePatchPrefix(haPatch, szPatchPathPrefix, 0);
- // Patches for World of Warcraft - mostly the do not use prefix.
- // Those who do, they have the (patch_metadata) file present in the "base" subdirectory.
+ // Patches for World of Warcraft - they mostly do not use prefix.
// All patches that use patch prefix have the "base\\(patch_metadata) file present
- if(GetFileEntryAny(haPatch, "base\\" PATCH_METADATA_NAME))
+ if(GetFileEntryLocale(haPatch, "base\\" PATCH_METADATA_NAME, 0))
return FindPatchPrefix_WoW_13164_13623(haBase, haPatch);
// Updates for Starcraft II
// Match: LocalizedData\GameHotkeys.txt <==> Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt
// All Starcraft II base archives seem to have the file "StreamingBuckets.txt" present
- if(GetFileEntryAny(haBase, "StreamingBuckets.txt"))
+ if(GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0))
return FindPatchPrefix_SC2(haBase, haPatch);
// Diablo III patch MPQs don't use patch prefix
// Hearthstone MPQs don't use patch prefix
- CreatePatchPrefix(haPatch, NULL, NULL);
+ CreatePatchPrefix(haPatch, NULL, 0);
return true;
}
@@ -643,11 +660,11 @@ static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const c
bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize)
{
- TPatchHeader * pPatchHeader = (TPatchHeader *)pvData;
+ PMPQ_PATCH_HEADER pPatchHeader = (PMPQ_PATCH_HEADER)pvData;
BLIZZARD_BSDIFF40_FILE DiffFile;
DWORD dwPatchType;
- if(cbData >= sizeof(TPatchHeader) + sizeof(BLIZZARD_BSDIFF40_FILE))
+ if(cbData >= sizeof(MPQ_PATCH_HEADER) + sizeof(BLIZZARD_BSDIFF40_FILE))
{
dwPatchType = BSWAP_INT32_UNSIGNED(pPatchHeader->dwPatchType);
if(dwPatchType == 0x30445342)
@@ -666,6 +683,40 @@ bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatche
return false;
}
+int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf)
+{
+ DWORD cbMaxFileData = 0;
+
+ // Overflow check
+ if((sizeof(MPQ_PATCH_HEADER) + cbMaxFileData) < cbMaxFileData)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ if(hf->hfPatch == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ // Initialize the entire structure with zeros
+ memset(pPatcher, 0, sizeof(TMPQPatcher));
+
+ // Copy the MD5 of the current file
+ memcpy(pPatcher->this_md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE);
+
+ // Find out the biggest data size needed during the patching process
+ while(hf != NULL)
+ {
+ if(hf->pFileEntry->dwFileSize > cbMaxFileData)
+ cbMaxFileData = hf->pFileEntry->dwFileSize;
+ hf = hf->hfPatch;
+ }
+
+ // Allocate primary and secondary buffer
+ pPatcher->pbFileData1 = STORM_ALLOC(BYTE, cbMaxFileData);
+ pPatcher->pbFileData2 = STORM_ALLOC(BYTE, cbMaxFileData);
+ if(!pPatcher->pbFileData1 || !pPatcher->pbFileData2)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ pPatcher->cbMaxFileData = cbMaxFileData;
+ return ERROR_SUCCESS;
+}
+
//
// Note: The patch may either be applied to the base file or to the previous version
// In Starcraft II, Mods\Core.SC2Mod\Base.SC2Data, file StreamingBuckets.txt:
@@ -685,59 +736,101 @@ bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatche
// 9 patches in a row, each requiring 70 MB memory (35 MB patch data + 35 MB work buffer)
//
-int PatchFileData(TMPQFile * hf)
+int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf)
{
+ PMPQ_PATCH_HEADER pFullPatch;
+ MPQ_PATCH_HEADER PatchHeader1;
+ MPQ_PATCH_HEADER PatchHeader2 = {0};
TMPQFile * hfBase = hf;
- TMPQFile * hfPrev = hf;
+ DWORD cbBytesRead = 0;
int nError = ERROR_SUCCESS;
- // We need to calculate the MD5 of the entire file
- assert(hf->pbFileData != NULL);
- assert(hf->cbFileData != 0);
- CalculateDataBlockHash(hf->pbFileData, hf->cbFileData, hf->FileDataMD5);
+ // Move to the first patch
+ assert(hfBase->pbFileData == NULL);
+ assert(hfBase->cbFileData == 0);
+ hf = hf->hfPatch;
+
+ // Read the header of the current patch
+ SFileReadFile((HANDLE)hf, &PatchHeader1, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL);
+ if(cbBytesRead != sizeof(MPQ_PATCH_HEADER))
+ return ERROR_FILE_CORRUPT;
- // Apply all patches
- for(hf = hf->hfPatch; hf != NULL; hf = hf->hfPatch)
+ // Perform the patching process
+ while(nError == ERROR_SUCCESS && hf != NULL)
{
- // This must be true
- assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
+ // Try to read the next patch header. If the md5_before_patch
+ // still matches we go directly to the next one and repeat
+ while(hf->hfPatch != NULL)
+ {
+ // Attempt to read the patch header
+ SFileReadFile((HANDLE)hf->hfPatch, &PatchHeader2, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL);
+ if(cbBytesRead != sizeof(MPQ_PATCH_HEADER))
+ return ERROR_FILE_CORRUPT;
- // Make sure that the patch data is loaded
- nError = LoadFilePatch(hf);
- if(nError != ERROR_SUCCESS)
- break;
+ // Compare the md5_before_patch
+ if(memcmp(PatchHeader2.md5_before_patch, pPatcher->this_md5, MD5_DIGEST_SIZE))
+ break;
- // Apply the patch
- nError = ApplyFilePatch(hfBase, hfPrev, hf);
- if(nError != ERROR_SUCCESS)
- break;
+ // Move one patch fuhrter
+ PatchHeader1 = PatchHeader2;
+ hf = hf->hfPatch;
+ }
- // Only keep base file version and previous version
- if(hfPrev != hfBase)
- FreePatchData(hfPrev);
+ // Allocate memory for the patch data
+ pFullPatch = LoadFullFilePatch(hf, PatchHeader1);
+ if(pFullPatch != NULL)
+ {
+ // Apply the patch
+ nError = ApplyFilePatch(pPatcher, pFullPatch);
+ STORM_FREE(pFullPatch);
+ }
+ else
+ {
+ nError = ERROR_FILE_CORRUPT;
+ }
- // Is this the last patch in the chain?
- if(hf->hfPatch == NULL)
- break;
- hfPrev = hf;
+ // Move to the next patch
+ PatchHeader1 = PatchHeader2;
+ pPatcher->nCounter++;
+ hf = hf->hfPatch;
}
- // When done, we need to rewrite the base file data
- // with the last of the patch chain
+ // Put the result data to the file structure
if(nError == ERROR_SUCCESS)
{
- // Free the base file data
- STORM_FREE(hfBase->pbFileData);
-
- // Switch the latest patched data to the base file
- hfBase->pbFileData = hf->pbFileData;
- hfBase->cbFileData = hf->cbFileData;
- hf->pbFileData = NULL;
- hf->cbFileData = 0;
+ // Swap the pointer to the file data structure
+ if(pPatcher->nCounter & 0x01)
+ {
+ hfBase->pbFileData = pPatcher->pbFileData2;
+ pPatcher->pbFileData2 = NULL;
+ }
+ else
+ {
+ hfBase->pbFileData = pPatcher->pbFileData1;
+ pPatcher->pbFileData1 = NULL;
+ }
+
+ // Also supply the data size
+ hfBase->cbFileData = pPatcher->cbFileData;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+void Patch_Finalize(TMPQPatcher * pPatcher)
+{
+ if(pPatcher != NULL)
+ {
+ if(pPatcher->pbFileData1 != NULL)
+ STORM_FREE(pPatcher->pbFileData1);
+ if(pPatcher->pbFileData2 != NULL)
+ STORM_FREE(pPatcher->pbFileData2);
+
+ memset(pPatcher, 0, sizeof(TMPQPatcher));
}
- return nError;
}
+
//-----------------------------------------------------------------------------
// Public functions
diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp
index 2451865..3293cf6 100644
--- a/src/SFileReadFile.cpp
+++ b/src/SFileReadFile.cpp
@@ -563,6 +563,7 @@ static int ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos
static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
{
+ TMPQPatcher Patcher;
DWORD dwBytesToRead = dwToRead;
DWORD dwBytesRead = 0;
int nError = ERROR_SUCCESS;
@@ -570,27 +571,26 @@ static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos,
// Make sure that the patch file is loaded completely
if(nError == ERROR_SUCCESS && hf->pbFileData == NULL)
{
- // Load the original file and store its content to "pbOldData"
- hf->pbFileData = STORM_ALLOC(BYTE, hf->pFileEntry->dwFileSize);
- hf->cbFileData = hf->pFileEntry->dwFileSize;
- if(hf->pbFileData == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
+ // Initialize patching process and allocate data
+ nError = Patch_InitPatcher(&Patcher, hf);
+ if(nError != ERROR_SUCCESS)
+ return nError;
- // Read the file data
+ // Set the current data size
+ Patcher.cbFileData = hf->pFileEntry->dwFileSize;
+
+ // Initialize the patcher object with initial file data
if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
- nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead);
+ nError = ReadMpqFileSingleUnit(hf, Patcher.pbFileData1, 0, Patcher.cbFileData, &dwBytesRead);
else
- nError = ReadMpqFileSectorFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead);
-
- // Fix error code
- if(nError == ERROR_SUCCESS && dwBytesRead != hf->cbFileData)
- nError = ERROR_FILE_CORRUPT;
+ nError = ReadMpqFileSectorFile(hf, Patcher.pbFileData1, 0, Patcher.cbFileData, &dwBytesRead);
- // Patch the file data
+ // Perform the patching process
if(nError == ERROR_SUCCESS)
- nError = PatchFileData(hf);
+ nError = Patch_Process(&Patcher, hf);
- // Reset number of bytes read to zero
+ // Finalize the patcher structure
+ Patch_Finalize(&Patcher);
dwBytesRead = 0;
}
diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp
index 65bad00..45ff58e 100644
--- a/src/SFileVerify.cpp
+++ b/src/SFileVerify.cpp
@@ -790,6 +790,7 @@ int SSignFileCreate(TMPQArchive * ha)
if(ha->dwFileFlags3 != 0)
{
// The (signature) file must be non-encrypted and non-compressed
+ assert(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW);
assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
assert(ha->dwReservedFiles > 0);
@@ -809,11 +810,11 @@ int SSignFileCreate(TMPQArchive * ha)
memset(EmptySignature, 0, sizeof(EmptySignature));
nError = SFileAddFile_Write(hf, EmptySignature, (DWORD)sizeof(EmptySignature), 0);
SFileAddFile_Finish(hf);
- }
- // Clear the invalid mark
- ha->dwFlags &= ~MPQ_FLAG_SIGNATURE_INVALID;
- ha->dwReservedFiles--;
+ // Clear the invalid mark
+ ha->dwFlags &= ~(MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_SIGNATURE_NONE);
+ ha->dwReservedFiles--;
+ }
}
return nError;
@@ -1043,7 +1044,7 @@ bool WINAPI SFileSignArchive(HANDLE hMpq, DWORD dwSignatureType)
{
// Turn the signature on. The signature will
// be applied when the archive is closed
- ha->dwFlags |= MPQ_FLAG_SIGNATURE_INVALID | MPQ_FLAG_CHANGED;
+ ha->dwFlags |= MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_CHANGED;
ha->dwFileFlags3 = MPQ_FILE_EXISTS;
ha->dwReservedFiles++;
}
diff --git a/src/StormCommon.h b/src/StormCommon.h
index 25f148e..3363a70 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -105,19 +105,19 @@ typedef struct _MPQ_SIGNATURE_INFO
// - Memory freeing function doesn't have to test the pointer to NULL
//
-#if defined(_MSC_VER) && defined(_DEBUG)
-
-#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
-#define STORM_REALLOC(type, ptr, nitems) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((nitems) * sizeof(type)))
-#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
-
-#else
+//#if defined(_MSC_VER) && defined(_DEBUG)
+//
+//#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
+//#define STORM_REALLOC(type, ptr, nitems) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((nitems) * sizeof(type)))
+//#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
+//
+//#else
#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type))
#define STORM_REALLOC(type, ptr, nitems) (type *)realloc(ptr, ((nitems) * sizeof(type)))
#define STORM_FREE(ptr) free(ptr)
-#endif
+//#endif
//-----------------------------------------------------------------------------
// StormLib internal global variables
@@ -181,7 +181,7 @@ int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG F
TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale);
TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName);
TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash);
-TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
+TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry, LCID lcLocale);
TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey);
TMPQHetTable * LoadHetTable(TMPQArchive * ha);
@@ -197,10 +197,10 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize);
int LoadAnyHashTable(TMPQArchive * ha);
int BuildFileTable(TMPQArchive * ha);
int DefragmentFileTable(TMPQArchive * ha);
-int ShrinkMalformedMpqTables(TMPQArchive * ha);
+int CreateFileTable(TMPQArchive * ha, DWORD dwFileTableSize);
int RebuildHetTable(TMPQArchive * ha);
-int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount);
+int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize);
int SaveMPQTables(TMPQArchive * ha);
TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwHashBitSize, LPBYTE pbSrcData);
@@ -210,18 +210,17 @@ TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount);
void FreeBetTable(TMPQBetTable * pBetTable);
// Functions for finding files in the file table
-TFileEntry * GetFileEntryAny(TMPQArchive * ha, const char * szFileName);
+TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex);
TFileEntry * GetFileEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale);
-TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale);
-TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex);
+TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex);
// Allocates file name in the file entry
void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName);
// Allocates new file entry in the MPQ tables. Reuses existing, if possible
-TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale);
-int RenameFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szNewFileName);
-void DeleteFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
+TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex);
+int RenameFileEntry(TMPQArchive * ha, TMPQFile * hf, const char * szNewFileName);
+int DeleteFileEntry(TMPQArchive * ha, TMPQFile * hf);
// Invalidates entries for (listfile) and (attributes)
void InvalidateInternalFiles(TMPQArchive * ha);
@@ -257,11 +256,27 @@ int WriteSectorChecksums(TMPQFile * hf);
int WriteMemDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, void * pvRawData, DWORD dwRawDataSize, DWORD dwChunkSize, LPDWORD pcbTotalSize);
int WriteMpqDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, DWORD dwRawDataSize, DWORD dwChunkSize);
void FreeFileHandle(TMPQFile *& hf);
+void FreeArchiveHandle(TMPQArchive *& ha);
-bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize);
-int PatchFileData(TMPQFile * hf);
+//-----------------------------------------------------------------------------
+// Patch functions
-void FreeArchiveHandle(TMPQArchive *& ha);
+// Structure used for the patching process
+typedef struct _TMPQPatcher
+{
+ BYTE this_md5[MD5_DIGEST_SIZE]; // MD5 of the current file state
+ LPBYTE pbFileData1; // Primary working buffer
+ LPBYTE pbFileData2; // Secondary working buffer
+ DWORD cbMaxFileData; // Maximum allowed size of the patch data
+ DWORD cbFileData; // Current size of the result data
+ DWORD nCounter; // Counter of the patch process
+
+} TMPQPatcher;
+
+bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize);
+int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf);
+int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf);
+void Patch_Finalize(TMPQPatcher * pPatcher);
//-----------------------------------------------------------------------------
// Utility functions
@@ -276,7 +291,7 @@ void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength);
void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength);
//-----------------------------------------------------------------------------
-// Support for adding files to the MPQ
+// Internal support for MPQ modifications
int SFileAddFile_Init(
TMPQArchive * ha,
@@ -320,12 +335,17 @@ int SSignFileFinish(TMPQArchive * ha);
// Dump data support
#ifdef __STORMLIB_DUMP_DATA__
+
void DumpMpqHeader(TMPQHeader * pHeader);
+void DumpHashTable(TMPQHash * pHashTable, DWORD dwHashTableSize);
void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable);
#else
+
#define DumpMpqHeader(h) /* */
+#define DumpHashTable(h, s) /* */
#define DumpHetAndBetTable(h, b) /* */
+
#endif
#endif // __STORMCOMMON_H__
diff --git a/src/StormLib.h b/src/StormLib.h
index 3d4fddf..a94d485 100644
--- a/src/StormLib.h
+++ b/src/StormLib.h
@@ -169,6 +169,7 @@ extern "C" {
// Values for SFileOpenFile
#define SFILE_OPEN_FROM_MPQ 0x00000000 // Open the file from the MPQ archive
+#define SFILE_OPEN_CHECK_EXISTS 0xFFFFFFFC // Only check whether the file exists
#define SFILE_OPEN_BASE_FILE 0xFFFFFFFD // Reserved for StormLib internal use
#define SFILE_OPEN_ANY_LOCALE 0xFFFFFFFE // Reserved for StormLib internal use
#define SFILE_OPEN_LOCAL_FILE 0xFFFFFFFF // Open a local file
@@ -180,12 +181,15 @@ extern "C" {
#define MPQ_FLAG_HASH_TABLE_CUT 0x00000008 // The hash table goes beyond EOF
#define MPQ_FLAG_BLOCK_TABLE_CUT 0x00000010 // The hash table goes beyond EOF
#define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000020 // Checking sector CRC when reading files
-#define MPQ_FLAG_LISTFILE_INVALID 0x00000040 // If set, it means that the (listfile) has been invalidated
-#define MPQ_FLAG_ATTRIBUTES_INVALID 0x00000080 // If set, it means that the (attributes) has been invalidated
-#define MPQ_FLAG_SIGNATURE_INVALID 0x00000100 // If set, it means that the (signature) has been invalidated
-#define MPQ_FLAG_SAVING_TABLES 0x00000200 // If set, we are saving MPQ internal files and MPQ tables
-#define MPQ_FLAG_PATCH 0x00000400 // If set, this MPQ is a patch archive
-#define MPQ_FLAG_WAR3_MAP 0x00000800 // If set, this MPQ is a map for Warcraft III
+#define MPQ_FLAG_SAVING_TABLES 0x00000040 // If set, we are saving MPQ internal files and MPQ tables
+#define MPQ_FLAG_PATCH 0x00000080 // If set, this MPQ is a patch archive
+#define MPQ_FLAG_WAR3_MAP 0x00000100 // If set, this MPQ is a map for Warcraft III
+#define MPQ_FLAG_LISTFILE_NONE 0x00000200 // Set when no (listfile) was found in InvalidateInternalFiles
+#define MPQ_FLAG_LISTFILE_NEW 0x00000400 // Set when (listfile) invalidated by InvalidateInternalFiles
+#define MPQ_FLAG_ATTRIBUTES_NONE 0x00000800 // Set when no (attributes) was found in InvalidateInternalFiles
+#define MPQ_FLAG_ATTRIBUTES_NEW 0x00001000 // Set when (attributes) invalidated by InvalidateInternalFiles
+#define MPQ_FLAG_SIGNATURE_NONE 0x00002000 // Set when no (signature) was found in InvalidateInternalFiles
+#define MPQ_FLAG_SIGNATURE_NEW 0x00004000 // Set when (signature) invalidated by InvalidateInternalFiles
// Values for TMPQArchive::dwSubType
#define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games)
@@ -650,31 +654,6 @@ typedef struct _TPatchInfo
// Followed by the sector table (variable length)
} TPatchInfo;
-// Header for PTCH files
-typedef struct _TPatchHeader
-{
- //-- PATCH header -----------------------------------
- DWORD dwSignature; // 'PTCH'
- DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed)
- DWORD dwSizeBeforePatch; // Size of the file before patch
- DWORD dwSizeAfterPatch; // Size of file after patch
-
- //-- MD5 block --------------------------------------
- DWORD dwMD5; // 'MD5_'
- DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself
- BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file
- BYTE md5_after_patch[0x10]; // MD5 of the patched file
-
- //-- XFRM block -------------------------------------
- DWORD dwXFRM; // 'XFRM'
- DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data
- DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY')
-
- // Followed by the patch data
-} TPatchHeader;
-
-#define SIZE_OF_XFRM_HEADER 0x0C
-
// This is the combined file entry for maintaining file list in the MPQ.
// This structure is combined from block table, hi-block table,
// (attributes) file and from (listfile).
@@ -683,14 +662,11 @@ typedef struct _TFileEntry
ULONGLONG FileNameHash; // Jenkins hash of the file name. Only used when the MPQ has BET table.
ULONGLONG ByteOffset; // Position of the file content in the MPQ, relative to the MPQ header
ULONGLONG FileTime; // FileTime from the (attributes) file. 0 if not present.
- DWORD dwHashIndex; // Index to the hash table. Only used when the MPQ has classic hash table
DWORD dwFileSize; // Decompressed size of the file
DWORD dwCmpSize; // Compressed size of the file (i.e., size of the file data in the MPQ)
DWORD dwFlags; // File flags (from block table)
- USHORT lcLocale; // Locale ID for the file
- USHORT wPlatform; // Platform ID for the file
DWORD dwCrc32; // CRC32 from (attributes) file. 0 if not present.
- unsigned char md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present.
+ BYTE md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present.
char * szFileName; // File name. NULL if not known.
} TFileEntry;
@@ -817,6 +793,7 @@ typedef struct _TMPQArchive
ULONGLONG UserDataPos; // Position of user data (relative to the begin of the file)
ULONGLONG MpqPos; // MPQ header offset (relative to the begin of the file)
+ ULONGLONG FileSize; // Size of the file at the moment of file open
struct _TMPQArchive * haPatch; // Pointer to patch archive, if any
struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any
@@ -835,7 +812,6 @@ typedef struct _TMPQArchive
DWORD dwHETBlockSize;
DWORD dwBETBlockSize;
DWORD dwMaxFileCount; // Maximum number of files in the MPQ. Also total size of the file table.
- DWORD dwHashTableSize; // Size of the hash table. Different from hash table size in the header if the hash table was shrunk
DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one
DWORD dwReservedFiles; // Number of entries reserved for internal MPQ files (listfile, attributes)
DWORD dwSectorSize; // Default size of one file sector
@@ -860,22 +836,22 @@ typedef struct _TMPQFile
{
TFileStream * pStream; // File stream. Only used on local files
TMPQArchive * ha; // Archive handle
+ TMPQHash * pHashEntry; // Pointer to hash table entry, if the file was open using hash table
TFileEntry * pFileEntry; // File entry for the file
- DWORD dwFileKey; // Decryption key
- DWORD dwFilePos; // Current file position
ULONGLONG RawFilePos; // Offset in MPQ archive (relative to file begin)
ULONGLONG MpqFilePos; // Offset in MPQ archive (relative to MPQ header)
+ DWORD dwHashIndex; // Hash table index (0xFFFFFFFF if not used)
+ DWORD dwFileKey; // Decryption key
+ DWORD dwFilePos; // Current file position
DWORD dwMagic; // 'FILE'
struct _TMPQFile * hfPatch; // Pointer to opened patch file
- TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file
- LPBYTE pbFileData; // Data of the file (single unit files, patched files)
- DWORD cbFileData; // Size of file data
- BYTE FileDataMD5[MD5_DIGEST_SIZE];// MD5 hash of the loaded file data. Used during patch process
TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table
- DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files.
- DWORD * SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector
+ LPDWORD SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files.
+ LPDWORD SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector
+ LPBYTE pbFileData; // Data of the file (single unit files, patched files)
+ DWORD cbFileData; // Size of file data
DWORD dwCompression0; // Compression that will be used on the first file sector
DWORD dwSectorCount; // Number of sectors in the file
DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ
@@ -900,7 +876,6 @@ typedef struct _SFILE_FIND_DATA
{
char cFileName[MAX_PATH]; // Full name of the found file
char * szPlainName; // Plain name of the found file
- DWORD dwHashIndex; // Hash table index for the file
DWORD dwBlockIndex; // Block table index for the file
DWORD dwFileSize; // File size in bytes
DWORD dwFileFlags; // MPQ file flags
diff --git a/src/huffman/huff.cpp b/src/huffman/huff.cpp
index cf5ae05..c973580 100644
--- a/src/huffman/huff.cpp
+++ b/src/huffman/huff.cpp
@@ -406,10 +406,7 @@ void THTreeItem::RemoveItem()
THuffmannTree::THuffmannTree(bool bCompression)
{
- // TODO: Obsolete, delete this!!
-// InitializeHTListHead(&ItemLinks);
pFirst = pLast = LIST_HEAD();
-
MinValidValue = 1;
ItemsUsed = 0;