aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2013-12-05 15:59:00 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2013-12-05 15:59:00 +0100
commitc34c37b3418f1e5ab3678ce65d46f81803dec91d (patch)
tree4a9cf4c61634691981f9dc367b53dac4070f8d0d /src
parentff0c25952a28a927c48738ab5207b9bda69e588a (diff)
+ StormLib 9.0 BETA
Diffstat (limited to 'src')
-rw-r--r--src/FileStream.cpp208
-rw-r--r--src/SBaseCommon.cpp357
-rw-r--r--src/SBaseFileTable.cpp1710
-rw-r--r--src/SBaseSubTypes.cpp63
-rw-r--r--src/SCompression.cpp12
-rw-r--r--src/SFileAddFile.cpp194
-rw-r--r--src/SFileAttributes.cpp559
-rw-r--r--src/SFileCompactArchive.cpp159
-rw-r--r--src/SFileCreateArchive.cpp48
-rw-r--r--src/SFileFindFile.cpp62
-rw-r--r--src/SFileGetFileInfo.cpp1698
-rw-r--r--src/SFileListFile.cpp236
-rw-r--r--src/SFileOpenArchive.cpp85
-rw-r--r--src/SFileOpenFileEx.cpp96
-rw-r--r--src/SFilePatchArchives.cpp10
-rw-r--r--src/SFileReadFile.cpp476
-rw-r--r--src/SFileVerify.cpp162
-rw-r--r--src/StormCommon.h97
-rw-r--r--src/StormLib.h545
-rw-r--r--src/StormPort.h14
20 files changed, 3031 insertions, 3760 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp
index 373562a..81933fd 100644
--- a/src/FileStream.cpp
+++ b/src/FileStream.cpp
@@ -2006,7 +2006,7 @@ bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream)
*
* \a pStream Pointer to an open stream
*/
-TCHAR * FileStream_GetFileName(TFileStream * pStream)
+const TCHAR * FileStream_GetFileName(TFileStream * pStream)
{
assert(pStream != NULL);
return pStream->szFileName;
@@ -2095,6 +2095,61 @@ void FileStream_Close(TFileStream * pStream)
}
//-----------------------------------------------------------------------------
+// Utility functions (ANSI)
+
+const char * GetPlainFileName(const char * szFileName)
+{
+ const char * szPlainName = szFileName;
+
+ while(*szFileName != 0)
+ {
+ if(*szFileName == '\\' || *szFileName == '/')
+ szPlainName = szFileName + 1;
+ szFileName++;
+ }
+
+ return szPlainName;
+}
+
+void CopyFileName(char * szTarget, const char * szSource, size_t cchLength)
+{
+ memcpy(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Utility functions (UNICODE) only exist in the ANSI version of the library
+// In ANSI builds, TCHAR = char, so we don't need these functions implemented
+
+#ifdef _UNICODE
+const TCHAR * GetPlainFileName(const TCHAR * szFileName)
+{
+ const TCHAR * szPlainName = szFileName;
+
+ while(*szFileName != 0)
+ {
+ if(*szFileName == '\\' || *szFileName == '/')
+ szPlainName = szFileName + 1;
+ szFileName++;
+ }
+
+ return szPlainName;
+}
+
+void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength)
+{
+ mbstowcs(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
+
+void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength)
+{
+ wcstombs(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
+#endif
+
+//-----------------------------------------------------------------------------
// main - for testing purposes
#ifdef __STORMLIB_TEST__
@@ -2141,154 +2196,3 @@ int FileStream_Test(const TCHAR * szFileName, DWORD dwStreamFlags)
return ERROR_SUCCESS;
}
#endif
-
-/*
-int FileStream_Test()
-{
- TFileStream * pStream;
-
- InitializeMpqCryptography();
-
- //
- // Test 1: Write to a stream
- //
-
- pStream = FileStream_CreateFile("E:\\Stream.bin", 0);
- if(pStream != NULL)
- {
- char szString1[100] = "This is a single line\n\r";
- DWORD dwLength = strlen(szString1);
-
- for(int i = 0; i < 10; i++)
- {
- if(!FileStream_Write(pStream, NULL, szString1, dwLength))
- {
- printf("Failed to write to the stream\n");
- return ERROR_CAN_NOT_COMPLETE;
- }
- }
- FileStream_Close(pStream);
- }
-
- //
- // Test2: Read from the stream
- //
-
- pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
- if(pStream != NULL)
- {
- char szString1[100] = "This is a single line\n\r";
- char szString2[100];
- DWORD dwLength = strlen(szString1);
-
- // This call must end with an error
- if(FileStream_Write(pStream, NULL, "aaa", 3))
- {
- printf("Write succeeded while it should fail\n");
- return -1;
- }
-
- for(int i = 0; i < 10; i++)
- {
- if(!FileStream_Read(pStream, NULL, szString2, dwLength))
- {
- printf("Failed to read from the stream\n");
- return -1;
- }
-
- szString2[dwLength] = 0;
- if(strcmp(szString1, szString2))
- {
- printf("Data read from file are different from data written\n");
- return -1;
- }
- }
- FileStream_Close(pStream);
- }
-
- //
- // Test3: Open the temp stream, write some data and switch it to the original stream
- //
-
- pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
- if(pStream != NULL)
- {
- TFileStream * pTempStream;
- ULONGLONG FileSize;
-
- pTempStream = FileStream_CreateFile("E:\\TempStream.bin", 0);
- if(pTempStream == NULL)
- {
- printf("Failed to create temp stream\n");
- return -1;
- }
-
- // Copy the original stream to the temp
- if(!FileStream_GetSize(pStream, FileSize))
- {
- printf("Failed to get the file size\n");
- return -1;
- }
-
- while(FileSize != 0)
- {
- DWORD dwBytesToRead = (DWORD)FileSize;
- char Buffer[0x80];
-
- if(dwBytesToRead > sizeof(Buffer))
- dwBytesToRead = sizeof(Buffer);
-
- if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead))
- {
- printf("CopyStream: Read source file failed\n");
- return -1;
- }
-
- if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead))
- {
- printf("CopyStream: Write target file failed\n");
- return -1;
- }
-
- FileSize -= dwBytesToRead;
- }
-
- // Switch the streams
- // Note that the pTempStream is closed by the operation
- FileStream_Switch(pStream, pTempStream);
- FileStream_Close(pStream);
- }
-
- //
- // Test4: Read from the stream again
- //
-
- pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
- if(pStream != NULL)
- {
- char szString1[100] = "This is a single line\n\r";
- char szString2[100];
- DWORD dwLength = strlen(szString1);
-
- for(int i = 0; i < 10; i++)
- {
- if(!FileStream_Read(pStream, NULL, szString2, dwLength))
- {
- printf("Failed to read from the stream\n");
- return -1;
- }
-
- szString2[dwLength] = 0;
- if(strcmp(szString1, szString2))
- {
- printf("Data read from file are different from data written\n");
- return -1;
- }
- }
- FileStream_Close(pStream);
- }
-
- return 0;
-}
-*/
-
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index 2e9366f..3bb10de 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -15,7 +15,7 @@
#include "StormLib.h"
#include "StormCommon.h"
-char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2012";
+char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014";
//-----------------------------------------------------------------------------
// Local variables
@@ -97,6 +97,8 @@ unsigned char AsciiToUpperTable_Slash[256] =
#define STORM_BUFFER_SIZE 0x500
+#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;
@@ -204,19 +206,17 @@ DWORD HashStringLower(const char * szFileName, DWORD dwHashType)
DWORD GetHashTableSizeForFileCount(DWORD dwFileCount)
{
- DWORD dwPowerOfTwo;
-
- // Round the hash table size up to the nearest power of two
- for(dwPowerOfTwo = HASH_TABLE_SIZE_MIN; dwPowerOfTwo < HASH_TABLE_SIZE_MAX; dwPowerOfTwo <<= 1)
- {
- if(dwPowerOfTwo >= dwFileCount)
- {
- return dwPowerOfTwo;
- }
- }
+ DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN;
+ // For zero files, there is no hash table needed
+ if(dwFileCount == 0)
+ return 0;
+
+ // Round the hash table size up to the nearest power of two
// Don't allow the hash table size go over allowed maximum
- return HASH_TABLE_SIZE_MAX;
+ while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount)
+ dwPowerOfTwo <<= 1;
+ return dwPowerOfTwo;
}
//-----------------------------------------------------------------------------
@@ -252,224 +252,12 @@ ULONGLONG HashStringJenkins(const char * szFileName)
}
//-----------------------------------------------------------------------------
-// Copies the string from char * to TCHAR * and back
-
-#ifdef _UNICODE
-
-// For UNICODE builds, we need two functions
-void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength)
-{
- mbstowcs(szTarget, szSource, cchLength);
- szTarget[cchLength] = 0;
-}
-
-void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength)
-{
- wcstombs(szTarget, szSource, cchLength);
- szTarget[cchLength] = 0;
-}
-
-#else
-
-// For ANSI build, we only need one
-void CopyFileName(char * szTarget, const char * szSource, size_t cchLength)
-{
- memcpy(szTarget, szSource, cchLength);
- szTarget[cchLength] = 0;
-}
-
-#endif
-
-//-----------------------------------------------------------------------------
-// This function converts the MPQ header so it always looks like version 4
-
-int ConvertMpqHeaderToFormat4(
- TMPQArchive * ha,
- ULONGLONG FileSize,
- DWORD dwFlags)
-{
- TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData;
- ULONGLONG ByteOffset;
- DWORD dwExpectedArchiveSize;
- USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion);
- int nError = ERROR_SUCCESS;
-
- // If version 1.0 is forced, then the format version is forced to be 1.0
- // Reason: Storm.dll in Warcraft III ignores format version value
- if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
- wFormatVersion = MPQ_FORMAT_VERSION_1;
-
- // Format-specific fixes
- switch(wFormatVersion)
- {
- case MPQ_FORMAT_VERSION_1:
-
- // Make sure that the V1 header part is BSWAPPed
- BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1);
-
- // Check for malformed MPQ header version 1.0
- if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1)
- {
- pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
- ha->dwFlags |= MPQ_FLAG_PROTECTED;
- }
-
- //
- // The value of "dwArchiveSize" member in the MPQ header
- // is ignored by Storm.dll and can contain garbage value
- // ("w3xmaster" protector).
- //
-
- dwExpectedArchiveSize = (DWORD)(FileSize - ha->MpqPos);
- if(pHeader->dwArchiveSize != dwExpectedArchiveSize)
- {
- // Note: dwExpectedArchiveSize might be incorrect at this point.
- // MPQs version 1.0 can have strong digital signature appended at the end,
- // or they might just have arbitrary data there.
- // In either case, we recalculate the archive size later when
- // block table is loaded and positions of all files is known.
- pHeader->dwArchiveSize = dwExpectedArchiveSize;
- ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE;
- }
-
- // Zero the fields in 2.0 part of the MPQ header
- pHeader->HiBlockTablePos64 = 0;
- pHeader->wHashTablePosHi = 0;
- pHeader->wBlockTablePosHi = 0;
- // No break here !!!
-
- case MPQ_FORMAT_VERSION_2:
- case MPQ_FORMAT_VERSION_3:
-
- // Make sure that the V2+V3 header part is BSWAPPed
- BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3);
-
- // In MPQ format 3.0, the entire header is optional
- // and the size of the header can actually be identical
- // to size of header 2.0
- if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3)
- {
- ULONGLONG ArchiveSize64 = pHeader->dwArchiveSize;
-
- // In format 2.0, the archive size is obsolete and is calculated
- // as the highest offset of hash table, block table or hi-block table.
- // However, we can still rely on it, if the size of the archive is under 4 GB
- if((FileSize - ha->MpqPos) >> 32)
- {
- ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + (pHeader->dwHashTableSize * sizeof(TMPQHash));
- if(ByteOffset > ArchiveSize64)
- ArchiveSize64 = ByteOffset;
-
- ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + (pHeader->dwBlockTableSize * sizeof(TMPQBlock));
- if(ByteOffset > ArchiveSize64)
- ArchiveSize64 = ByteOffset;
-
- // Only if we actually have a hi-block table
- if(pHeader->HiBlockTablePos64)
- {
- ByteOffset = pHeader->HiBlockTablePos64 + (pHeader->dwBlockTableSize * sizeof(USHORT));
- if(ByteOffset > ArchiveSize64)
- ArchiveSize64 = ByteOffset;
- }
-
- // We need to recalculate archive size later,
- // when block table is loaded and the position of files is known
- ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE;
- }
-
- // Initialize the rest of the 3.0 header
- pHeader->ArchiveSize64 = ArchiveSize64;
- pHeader->HetTablePos64 = 0;
- pHeader->BetTablePos64 = 0;
- }
-
- //
- // Calculate compressed size of each table. We assume the following order:
- // 1) HET table
- // 2) BET table
- // 3) Classic hash table
- // 4) Classic block table
- // 5) Hi-block table
- //
-
- // Set all sizes to zero
- pHeader->HetTableSize64 = 0;
- pHeader->BetTableSize64 = 0;
-
- // Either both HET and BET table exist or none of them does.
- if(pHeader->HetTablePos64)
- {
- // Compressed size of the HET and BET tables
- pHeader->HetTableSize64 = pHeader->BetTablePos64 - pHeader->HetTablePos64;
- pHeader->BetTableSize64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) - pHeader->HetTablePos64;
- }
-
- // Compressed size of hash and block table
- if(wFormatVersion >= MPQ_FORMAT_VERSION_2)
- {
- // Compressed size of the hash table
- pHeader->HashTableSize64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
-
- // Block and hi-block table
- if(pHeader->HiBlockTablePos64)
- {
- pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
- pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64;
- }
- else
- {
- pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
- pHeader->HiBlockTableSize64 = 0;
- }
- }
- else
- {
- // No known MPQ in format 1.0 has any of the tables compressed
- pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash);
- pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
- pHeader->HiBlockTableSize64 = 0;
- }
-
- // Set the data chunk size for MD5 to zero
- pHeader->dwRawChunkSize = 0;
-
- // Fill the MD5's
- memset(pHeader->MD5_BlockTable, 0, MD5_DIGEST_SIZE);
- memset(pHeader->MD5_HashTable, 0, MD5_DIGEST_SIZE);
- memset(pHeader->MD5_HiBlockTable, 0, MD5_DIGEST_SIZE);
- memset(pHeader->MD5_BetTable, 0, MD5_DIGEST_SIZE);
- memset(pHeader->MD5_HetTable, 0, MD5_DIGEST_SIZE);
- memset(pHeader->MD5_MpqHeader, 0, MD5_DIGEST_SIZE);
- // No break here !!!!
-
- case MPQ_FORMAT_VERSION_4:
-
- // Make sure that the V4 header part is BSWAPPed
- BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4);
-
- // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A'
- // signature until the position of header MD5 at offset 0xC0
- if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader))
- nError = ERROR_FILE_CORRUPT;
- break;
-
- default:
-
- // Last resort: Check if it's a War of the Immortal data file (SQP)
- nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags);
- break;
- }
-
- return nError;
-}
-
-//-----------------------------------------------------------------------------
// Default flags for (attributes) and (listfile)
-DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize)
+DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion)
{
// Fixed for format 1.0
- if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ if(wFormatVersion == MPQ_FORMAT_VERSION_1)
return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY;
// Size-dependent for formats 2.0-4.0
@@ -664,7 +452,7 @@ DWORD DecryptFileKey(
DWORD dwMpqPos = (DWORD)MpqPos;
// File key is calculated from plain name
- szFileName = GetPlainFileNameA(szFileName);
+ szFileName = GetPlainFileName(szFileName);
dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY);
// Fix the key, if needed
@@ -678,28 +466,30 @@ DWORD DecryptFileKey(
//-----------------------------------------------------------------------------
// Handle validation functions
-bool IsValidMpqHandle(TMPQArchive * ha)
+TMPQArchive * IsValidMpqHandle(HANDLE hMpq)
{
- if(ha == NULL)
- return false;
- if(ha->pHeader == NULL || ha->pHeader->dwID != ID_MPQ)
- return false;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
- return (bool)(ha->pHeader->dwID == ID_MPQ);
+ return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL;
}
-bool IsValidFileHandle(TMPQFile * hf)
+TMPQFile * IsValidFileHandle(HANDLE hFile)
{
- if(hf == NULL)
- return false;
+ TMPQFile * hf = (TMPQFile *)hFile;
- if(hf->dwMagic != ID_MPQ_FILE)
- return false;
+ // Must not be NULL
+ if(hf != NULL && hf->dwMagic == ID_MPQ_FILE)
+ {
+ // Local file handle?
+ if(hf->pStream != NULL)
+ return hf;
- if(hf->pStream != NULL)
- return true;
+ // Also verify the MPQ handle within the file handle
+ if(IsValidMpqHandle(hf->ha))
+ return hf;
+ }
- return IsValidMpqHandle(hf->ha);
+ return NULL;
}
//-----------------------------------------------------------------------------
@@ -710,10 +500,11 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1
{
TMPQHash * pDeletedEntry = NULL; // If a deleted entry was found in the continuous hash range
TMPQHash * pFreeEntry = NULL; // If a free entry was found in the continuous hash range
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
DWORD dwIndex;
// Set the initial index
- dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask);
+ dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
// Search the hash table and return the found entries in the following priority:
// 1) <MATCHING_ENTRY>
@@ -741,7 +532,7 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1
// Move to the next hash entry.
// If we reached the starting entry, it's failure.
- dwIndex = (dwIndex + 1) & ha->dwHashIndexMask;
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
if(dwIndex == dwStartIndex)
break;
}
@@ -750,18 +541,18 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1
return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry;
}
-
// Retrieves the first hash entry for the given file.
// Every locale version of a file has its own hash entry
TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName)
{
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX);
DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A);
DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B);
DWORD dwIndex;
// Set the initial index
- dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask);
+ dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
// Search the hash table
for(;;)
@@ -778,7 +569,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName)
// Move to the next hash entry. Stop searching
// if we got reached the original hash entry
- dwIndex = (dwIndex + 1) & ha->dwHashIndexMask;
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
if(dwIndex == dwStartIndex)
return NULL;
}
@@ -786,6 +577,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName)
TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
{
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
DWORD dwName1 = pHash->dwName1;
DWORD dwName2 = pHash->dwName2;
@@ -797,7 +589,7 @@ TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash *
{
// Move to the next hash entry. Stop searching
// if we got reached the original hash entry
- dwIndex = (dwIndex + 1) & ha->dwHashIndexMask;
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
if(dwIndex == dwStartIndex)
return NULL;
pHash = ha->pHashTable + dwIndex;
@@ -904,21 +696,23 @@ void * LoadMpqTable(
TMPQArchive * ha,
ULONGLONG ByteOffset,
DWORD dwCompressedSize,
- DWORD dwRealSize,
+ DWORD dwTableSize,
DWORD dwKey)
{
LPBYTE pbCompressed = NULL;
LPBYTE pbMpqTable;
LPBYTE pbToRead;
+ DWORD dwBytesToRead = dwCompressedSize;
+ DWORD dwValidLength = dwTableSize;
int nError = ERROR_SUCCESS;
// Allocate the MPQ table
- pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwRealSize);
+ pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize);
if(pbMpqTable != NULL)
{
// "interface.MPQ.part" in trial version of World of Warcraft
// has block table and hash table compressed.
- if(dwCompressedSize < dwRealSize)
+ if(dwCompressedSize < dwTableSize)
{
// Allocate temporary buffer for holding compressed data
pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
@@ -930,7 +724,7 @@ void * LoadMpqTable(
}
// If everything succeeded, read the raw table form the MPQ
- if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize))
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
{
// First of all, decrypt the table
if(dwKey != 0)
@@ -941,17 +735,21 @@ void * LoadMpqTable(
}
// If the table is compressed, decompress it
- if(dwCompressedSize < dwRealSize)
+ if(dwCompressedSize < dwTableSize)
{
- int cbOutBuffer = (int)dwRealSize;
+ int cbOutBuffer = (int)dwTableSize;
int cbInBuffer = (int)dwCompressedSize;
if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer))
nError = GetLastError();
}
- // Makre sure that the table is properly byte-swapped
- BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwRealSize);
+ // Make sure that the table is properly byte-swapped
+ BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
+
+ // If the table was not fully readed, fill the rest with zeros
+ if(dwValidLength < dwTableSize)
+ memset(pbMpqTable + dwValidLength, 0, (dwTableSize - dwValidLength));
}
else
{
@@ -1563,8 +1361,8 @@ void FreeMPQFile(TMPQFile *& hf)
if(hf != NULL)
{
// If we have patch file attached to this one, free it first
- if(hf->hfPatchFile != NULL)
- FreeMPQFile(hf->hfPatchFile);
+ if(hf->hfPatch != NULL)
+ FreeMPQFile(hf->hfPatch);
// Then free all buffers allocated in the file structure
if(hf->pPatchHeader != NULL)
@@ -1623,34 +1421,6 @@ void FreeMPQArchive(TMPQArchive *& ha)
}
}
-const char * GetPlainFileNameA(const char * szFileName)
-{
- const char * szPlainName = szFileName;
-
- while(*szFileName != 0)
- {
- if(*szFileName == '\\' || *szFileName == '/')
- szPlainName = szFileName + 1;
- szFileName++;
- }
-
- return szPlainName;
-}
-
-const TCHAR * GetPlainFileNameT(const TCHAR * szFileName)
-{
- const TCHAR * szPlainName = szFileName;
-
- while(*szFileName != 0)
- {
- if(*szFileName == '\\' || *szFileName == '/')
- szPlainName = szFileName + 1;
- szFileName++;
- }
-
- return szPlainName;
-}
-
bool IsInternalMpqFileName(const char * szFileName)
{
if(szFileName != NULL && szFileName[0] == '(')
@@ -1824,17 +1594,6 @@ void ConvertUInt64Buffer(void * ptr, size_t length)
}
}
-// Swaps the TMPQUserData structure
-void ConvertTMPQUserData(void *userData)
-{
- TMPQUserData * theData = (TMPQUserData *)userData;
-
- theData->dwID = SwapUInt32(theData->dwID);
- theData->cbUserDataSize = SwapUInt32(theData->cbUserDataSize);
- theData->dwHeaderOffs = SwapUInt32(theData->dwHeaderOffs);
- theData->cbUserDataHeader = SwapUInt32(theData->cbUserDataHeader);
-}
-
// Swaps the TMPQHeader structure
void ConvertTMPQHeader(void *header, uint16_t version)
{
@@ -1854,11 +1613,15 @@ void ConvertTMPQHeader(void *header, uint16_t version)
theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize);
}
- if(version == MPQ_FORMAT_VERSION_2 || version == MPQ_FORMAT_VERSION_3)
+ if(version == MPQ_FORMAT_VERSION_2)
{
theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
+ }
+
+ if(version == MPQ_FORMAT_VERSION_3)
+ {
theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index 51d01e4..6b4eada 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -19,54 +19,11 @@
#define MAX_FLAG_INDEX 512
//-----------------------------------------------------------------------------
-// Local structures
-
-// Structure for HET table header
-typedef struct _HET_TABLE_HEADER
-{
- DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes)
- DWORD dwFileCount; // Number of occupied entries in the hash table
- DWORD dwHashTableSize; // Size of the hash table (in bytes)
- DWORD dwHashEntrySize; // Effective size of the hash entry (in bits)
- DWORD dwIndexSizeTotal; // Total size of file index (in bits)
- DWORD dwIndexSizeExtra; // Extra bits in the file index
- DWORD dwIndexSize; // Effective size of the file index (in bits)
- DWORD dwIndexTableSize; // Size of the block index subtable (in bytes)
-
-} HET_TABLE_HEADER, *PHET_TABLE_HEADER;
-
-// Structure for BET table header
-typedef struct _BET_TABLE_HEADER
-{
- DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes)
- DWORD dwFileCount; // Number of files in the BET table
- DWORD dwUnknown08;
- DWORD dwTableEntrySize; // Size of one table entry (in bits)
- DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record)
- DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record)
- DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record)
- DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record)
- DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record)
- DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record)
- DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record)
- DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record)
- DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record)
- DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record)
- DWORD dwBetHashSizeTotal; // Total size of the BET hash
- DWORD dwBetHashSizeExtra; // Extra bits in the BET hash
- DWORD dwBetHashSize; // Effective size of BET hash (in bits)
- DWORD dwBetHashArraySize; // Size of BET hashes array, in bytes
- DWORD dwFlagCount; // Number of flags in the following array
-
-} BET_TABLE_HEADER, *PBET_TABLE_HEADER;
-
-//-----------------------------------------------------------------------------
// Support for calculating bit sizes
static void InitFileFlagArray(LPDWORD FlagArray)
{
- for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++)
- FlagArray[dwFlagIndex] = INVALID_FLAG_VALUE;
+ memset(FlagArray, 0xCC, MAX_FLAG_INDEX * sizeof(DWORD));
}
static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags)
@@ -99,6 +56,19 @@ static DWORD GetNecessaryBitCount(ULONGLONG MaxValue)
return dwBitCount;
}
+static int CompareFilePositions(const void * p1, const void * p2)
+{
+ TMPQBlock * pBlock1 = *(TMPQBlock **)p1;
+ TMPQBlock * pBlock2 = *(TMPQBlock **)p2;
+
+ if(pBlock1->dwFilePos < pBlock2->dwFilePos)
+ return -1;
+ if(pBlock1->dwFilePos > pBlock2->dwFilePos)
+ return +1;
+
+ return 0;
+}
+
//-----------------------------------------------------------------------------
// Support functions for BIT_ARRAY
@@ -116,6 +86,7 @@ static TBitArray * CreateBitArray(
if(pBitArray != NULL)
{
memset(pBitArray, FillValue, nSize);
+ pBitArray->NumberOfBytes = (NumberOfBits + 7) / 8;
pBitArray->NumberOfBits = NumberOfBits;
}
@@ -255,6 +226,242 @@ void SetBits(
}
}
+//-----------------------------------------------------------------------------
+// Support for MPQ header
+
+static DWORD GetArchiveSize32(TMPQArchive * ha, TMPQBlock * pBlockTable, DWORD dwBlockTableSize)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ ULONGLONG FileSize = 0;
+ DWORD dwArchiveSize = pHeader->dwHeaderSize;
+ DWORD dwByteOffset;
+ DWORD dwBlockIndex;
+
+ // Increment by hash table size
+ dwByteOffset = pHeader->dwHashTablePos + (pHeader->dwHashTableSize * sizeof(TMPQHash));
+ if(dwByteOffset > dwArchiveSize)
+ dwArchiveSize = dwByteOffset;
+
+ // Increment by block table size
+ dwByteOffset = pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock));
+ if(dwByteOffset > dwArchiveSize)
+ dwArchiveSize = dwByteOffset;
+
+ // If any of the MPQ files is beyond the hash table/block table, set the end to the file size
+ for(dwBlockIndex = 0; dwBlockIndex < dwBlockTableSize; dwBlockIndex++)
+ {
+ // Only count files that exists
+ if(pBlockTable[dwBlockIndex].dwFlags & MPQ_FILE_EXISTS)
+ {
+ // If this file begins past the end of tables,
+ // assume that the hash/block table is not at the end of the archive
+ if(pBlockTable[dwBlockIndex].dwFilePos > dwArchiveSize)
+ {
+ FileStream_GetSize(ha->pStream, &FileSize);
+ dwArchiveSize = (DWORD)(FileSize - ha->MpqPos);
+ break;
+ }
+ }
+ }
+
+ // Return what we found
+ return dwArchiveSize;
+}
+
+// This function converts the MPQ header so it always looks like version 4
+static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader)
+{
+ ULONGLONG ArchiveSize = pHeader->dwHeaderSize;
+ ULONGLONG ByteOffset = pHeader->dwHeaderSize;
+
+ // If there is HET table
+ if(pHeader->HetTablePos64 != 0)
+ {
+ ByteOffset = pHeader->HetTablePos64 + pHeader->HetTableSize64;
+ if(ByteOffset > ArchiveSize)
+ ArchiveSize = ByteOffset;
+ }
+
+ // If there is BET table
+ if(pHeader->BetTablePos64 != 0)
+ {
+ ByteOffset = pHeader->BetTablePos64 + pHeader->BetTableSize64;
+ if(ByteOffset > ArchiveSize)
+ ArchiveSize = ByteOffset;
+ }
+
+ // If there is hash table
+ if(pHeader->dwHashTablePos || pHeader->wHashTablePosHi)
+ {
+ ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + pHeader->HashTableSize64;
+ if(ByteOffset > ArchiveSize)
+ ArchiveSize = ByteOffset;
+ }
+
+ // If there is block table
+ if(pHeader->dwBlockTablePos || pHeader->wBlockTablePosHi)
+ {
+ ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + pHeader->BlockTableSize64;
+ if(ByteOffset > ArchiveSize)
+ ArchiveSize = ByteOffset;
+ }
+
+ // If there is hi-block table
+ if(pHeader->HiBlockTablePos64)
+ {
+ ByteOffset = pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64;
+ if(ByteOffset > ArchiveSize)
+ ArchiveSize = ByteOffset;
+ }
+
+ return ArchiveSize;
+}
+
+int ConvertMpqHeaderToFormat4(
+ TMPQArchive * ha,
+ ULONGLONG FileSize,
+ DWORD dwFlags)
+{
+ TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData;
+ ULONGLONG ByteOffset;
+ USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion);
+ int nError = ERROR_SUCCESS;
+
+ // If version 1.0 is forced, then the format version is forced to be 1.0
+ // Reason: Storm.dll in Warcraft III ignores format version value
+ if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
+ wFormatVersion = MPQ_FORMAT_VERSION_1;
+
+ // Format-specific fixes
+ switch(wFormatVersion)
+ {
+ case MPQ_FORMAT_VERSION_1:
+
+ // Check for malformed MPQ header version 1.0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1);
+ if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1)
+ {
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
+ ha->dwFlags |= MPQ_FLAG_PROTECTED;
+ }
+
+ //
+ // Note: The value of "dwArchiveSize" member in the MPQ header
+ // is ignored by Storm.dll and can contain garbage value
+ // ("w3xmaster" protector).
+ //
+
+ // Fill the rest of the header with zeros
+ memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V1, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V1);
+ pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash);
+ pHeader->ArchiveSize64 = pHeader->dwArchiveSize;
+ break;
+
+ case MPQ_FORMAT_VERSION_2:
+
+ // Check for malformed MPQ header version 2.0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_2);
+ if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V2)
+ {
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
+ pHeader->HiBlockTablePos64 = 0;
+ pHeader->wHashTablePosHi = 0;
+ pHeader->wBlockTablePosHi = 0;
+ ha->dwFlags |= MPQ_FLAG_PROTECTED;
+ }
+
+ // Fill the rest of the header with zeros
+ memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2);
+ if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos)
+ pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash);
+ if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos)
+ pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ if(pHeader->HiBlockTablePos64)
+ pHeader->HiBlockTableSize64 = pHeader->dwBlockTableSize * sizeof(USHORT);
+ pHeader->ArchiveSize64 = GetArchiveSize64(pHeader);
+ break;
+
+ case MPQ_FORMAT_VERSION_3:
+
+ // In MPQ format 3.0, the entire header is optional
+ // and the size of the header can actually be identical
+ // to size of header 2.0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3);
+ if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3)
+ {
+ pHeader->ArchiveSize64 = pHeader->dwArchiveSize;
+ pHeader->HetTablePos64 = 0;
+ pHeader->BetTablePos64 = 0;
+ }
+
+ //
+ // We need to calculate the compressed size of each table. We assume the following order:
+ // 1) HET table
+ // 2) BET table
+ // 3) Classic hash table
+ // 4) Classic block table
+ // 5) Hi-block table
+ //
+
+ // Fill the rest of the header with zeros
+ memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3);
+ ByteOffset = pHeader->ArchiveSize64;
+
+ // Size of the hi-block table
+ if(pHeader->HiBlockTablePos64)
+ {
+ pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64;
+ ByteOffset = pHeader->HiBlockTablePos64;
+ }
+
+ // Size of the block table
+ if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos)
+ {
+ pHeader->BlockTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ }
+
+ // Size of the hash table
+ if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos)
+ {
+ pHeader->HashTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
+ ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
+ }
+
+ // Size of the BET table
+ if(pHeader->BetTablePos64)
+ {
+ pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64;
+ ByteOffset = pHeader->BetTablePos64;
+ }
+
+ // Size of the HET table
+ if(pHeader->HetTablePos64)
+ {
+ pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64;
+ ByteOffset = pHeader->HetTablePos64;
+ }
+ break;
+
+ case MPQ_FORMAT_VERSION_4:
+
+ // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A'
+ // signature until the position of header MD5 at offset 0xC0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4);
+ if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader))
+ nError = ERROR_FILE_CORRUPT;
+ break;
+
+ default:
+
+ // Last resort: Check if it's a War of the Immortal data file (SQP)
+ nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags);
+ break;
+ }
+
+ return nError;
+}
//-----------------------------------------------------------------------------
// Support for hash table
@@ -339,14 +546,14 @@ static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, L
return NULL;
}
-static int ConvertMpqBlockTable(
+static int BuildFileTableFromBlockTable(
TMPQArchive * ha,
TFileEntry * pFileTable,
TMPQBlock * pBlockTable)
{
TFileEntry * pFileEntry;
TMPQHeader * pHeader = ha->pHeader;
- TMPQBlock * pMpqBlock;
+ TMPQBlock * pBlock;
TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize;
TMPQHash * pHash;
@@ -355,7 +562,7 @@ static int ConvertMpqBlockTable(
if(pHash->dwBlockIndex < pHeader->dwBlockTableSize)
{
pFileEntry = pFileTable + pHash->dwBlockIndex;
- pMpqBlock = pBlockTable + pHash->dwBlockIndex;
+ pBlock = pBlockTable + pHash->dwBlockIndex;
//
// Yet another silly map protector: For each valid file,
@@ -367,14 +574,17 @@ static int ConvertMpqBlockTable(
// a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid
//
- if(!(pMpqBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pMpqBlock->dwFlags & MPQ_FILE_EXISTS))
+ if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS))
{
- // Fill the entry
- pFileEntry->ByteOffset = pMpqBlock->dwFilePos;
+ // ByteOffset is only valid if file size is not zero
+ pFileEntry->ByteOffset = pBlock->dwFilePos;
+ if(pFileEntry->ByteOffset == 0 && pBlock->dwCSize == 0)
+ pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize;
+
pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
- pFileEntry->dwFileSize = pMpqBlock->dwFSize;
- pFileEntry->dwCmpSize = pMpqBlock->dwCSize;
- pFileEntry->dwFlags = pMpqBlock->dwFlags;
+ pFileEntry->dwFileSize = pBlock->dwFSize;
+ pFileEntry->dwCmpSize = pBlock->dwCSize;
+ pFileEntry->dwFlags = pBlock->dwFlags;
pFileEntry->lcLocale = pHash->lcLocale;
pFileEntry->wPlatform = pHash->wPlatform;
}
@@ -394,6 +604,31 @@ static int ConvertMpqBlockTable(
return ERROR_SUCCESS;
}
+static int UpdateFileTableFromHashTable(
+ TMPQArchive * ha,
+ TFileEntry * pFileTable)
+{
+ TFileEntry * pFileEntry;
+ TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ TMPQHash * pHash;
+
+ for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
+ {
+ if(pHash->dwBlockIndex < ha->dwFileTableSize)
+ {
+ pFileEntry = pFileTable + pHash->dwBlockIndex;
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
+ pFileEntry->lcLocale = pHash->lcLocale;
+ pFileEntry->wPlatform = pHash->wPlatform;
+ }
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
static TMPQHash * TranslateHashTable(
TMPQArchive * ha,
ULONGLONG * pcbTableSize)
@@ -419,7 +654,8 @@ static TMPQHash * TranslateHashTable(
return pHashTable;
}
-static TMPQBlock * TranslateBlockTable(
+// Also used in SFileGetFileInfo
+TMPQBlock * TranslateBlockTable(
TMPQArchive * ha,
ULONGLONG * pcbTableSize,
bool * pbNeedHiBlockTable)
@@ -427,8 +663,8 @@ static TMPQBlock * TranslateBlockTable(
TFileEntry * pFileEntry = ha->pFileTable;
TMPQBlock * pBlockTable;
TMPQBlock * pBlock;
+ size_t NeedHiBlockTable = 0;
size_t BlockTableSize;
- bool bNeedHiBlockTable = false;
// Allocate copy of the hash table
pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, ha->dwFileTableSize);
@@ -438,7 +674,7 @@ static TMPQBlock * TranslateBlockTable(
BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize;
for(DWORD i = 0; i < ha->dwFileTableSize; i++)
{
- bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false;
+ NeedHiBlockTable |= (pFileEntry->ByteOffset >> 32);
pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset;
pBlock->dwFSize = pFileEntry->dwFileSize;
pBlock->dwCSize = pFileEntry->dwCmpSize;
@@ -453,7 +689,7 @@ static TMPQBlock * TranslateBlockTable(
*pcbTableSize = (ULONGLONG)BlockTableSize;
if(pbNeedHiBlockTable != NULL)
- *pbNeedHiBlockTable = bNeedHiBlockTable;
+ *pbNeedHiBlockTable = NeedHiBlockTable ? true : false;
}
return pBlockTable;
@@ -488,21 +724,21 @@ static USHORT * TranslateHiBlockTable(
//-----------------------------------------------------------------------------
// General EXT table functions
-TMPQExtTable * LoadExtTable(
+TMPQExtHeader * LoadExtTable(
TMPQArchive * ha,
ULONGLONG ByteOffset,
size_t Size,
DWORD dwSignature,
DWORD dwKey)
{
- TMPQExtTable * pCompressed = NULL; // Compressed table
- TMPQExtTable * pExtTable = NULL; // Uncompressed table
+ TMPQExtHeader * pCompressed = NULL; // Compressed table
+ TMPQExtHeader * pExtTable = NULL; // Uncompressed table
// Do nothing if the size is zero
if(ByteOffset != 0 && Size != 0)
{
// Allocate size for the compressed table
- pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, Size);
+ pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, Size);
if(pExtTable != NULL)
{
// Load the table from the MPQ
@@ -514,7 +750,7 @@ TMPQExtTable * LoadExtTable(
}
// Swap the ext table header
- BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtTable));
+ BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtHeader));
if(pExtTable->dwSignature != dwSignature)
{
STORM_FREE(pExtTable);
@@ -523,14 +759,14 @@ TMPQExtTable * LoadExtTable(
// Decrypt the block
BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
- DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtTable)), dwKey);
+ DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtHeader)), dwKey);
BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
// If the table is compressed, decompress it
- if((pExtTable->dwDataSize + sizeof(TMPQExtTable)) > Size)
+ if((pExtTable->dwDataSize + sizeof(TMPQExtHeader)) > Size)
{
pCompressed = pExtTable;
- pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + pCompressed->dwDataSize);
+ pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + pCompressed->dwDataSize);
if(pExtTable != NULL)
{
int cbOutBuffer = (int)pCompressed->dwDataSize;
@@ -557,12 +793,6 @@ TMPQExtTable * LoadExtTable(
return pExtTable;
}
-// Used in MPQ Editor
-void FreeMpqBuffer(void * pvBuffer)
-{
- STORM_FREE(pvBuffer);
-}
-
static int SaveMpqTable(
TMPQArchive * ha,
void * pMpqTable,
@@ -630,7 +860,7 @@ static int SaveMpqTable(
static int SaveExtTable(
TMPQArchive * ha,
- TMPQExtTable * pExtTable,
+ TMPQExtHeader * pExtTable,
ULONGLONG ByteOffset,
DWORD dwTableSize,
unsigned char * md5,
@@ -639,7 +869,7 @@ static int SaveExtTable(
LPDWORD pcbTotalSize)
{
ULONGLONG FileOffset;
- TMPQExtTable * pCompressed = NULL;
+ TMPQExtHeader * pCompressed = NULL;
DWORD cbTotalSize = 0;
int nError = ERROR_SUCCESS;
@@ -650,7 +880,7 @@ static int SaveExtTable(
int cbInBuffer = (int)dwTableSize;
// Allocate extra space for compressed table
- pCompressed = (TMPQExtTable *)STORM_ALLOC(BYTE, dwTableSize);
+ pCompressed = (TMPQExtHeader *)STORM_ALLOC(BYTE, dwTableSize);
if(pCompressed == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
@@ -676,7 +906,7 @@ static int SaveExtTable(
if(dwKey != 0)
{
BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
- EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtTable)), dwKey);
+ EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtHeader)), dwKey);
BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
}
@@ -719,125 +949,170 @@ static int SaveExtTable(
static void CreateHetHeader(
TMPQHetTable * pHetTable,
- PHET_TABLE_HEADER pHetHeader)
+ TMPQHetHeader * pHetHeader)
{
- // Fill the BET header
- pHetHeader->dwFileCount = pHetTable->dwFileCount;
- pHetHeader->dwHashTableSize = pHetTable->dwHashTableSize;
- pHetHeader->dwHashEntrySize = pHetTable->dwHashBitSize;
- pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwHashTableSize);
- pHetHeader->dwIndexSizeExtra = 0;
- pHetHeader->dwIndexSize = pHetHeader->dwIndexSizeTotal;
- pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwHashTableSize) + 7) / 8;
+ // Fill the common header
+ pHetHeader->ExtHdr.dwSignature = HET_TABLE_SIGNATURE;
+ pHetHeader->ExtHdr.dwVersion = 1;
+ pHetHeader->ExtHdr.dwDataSize = 0;
+
+ // Fill the HET header
+ pHetHeader->dwEntryCount = pHetTable->dwEntryCount;
+ pHetHeader->dwTotalCount = pHetTable->dwTotalCount;
+ pHetHeader->dwNameHashBitSize = pHetTable->dwNameHashBitSize;
+ pHetHeader->dwIndexSizeTotal = pHetTable->dwIndexSizeTotal;
+ pHetHeader->dwIndexSizeExtra = pHetTable->dwIndexSizeExtra;
+ pHetHeader->dwIndexSize = pHetTable->dwIndexSize;
+ pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwTotalCount) + 7) / 8;
// Calculate the total size needed for holding HET table
- pHetHeader->dwTableSize = sizeof(HET_TABLE_HEADER) +
- pHetHeader->dwHashTableSize +
+ pHetHeader->ExtHdr.dwDataSize =
+ pHetHeader->dwTableSize = sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) +
+ pHetHeader->dwTotalCount +
pHetHeader->dwIndexTableSize;
}
-TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty)
+TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData)
{
TMPQHetTable * pHetTable;
pHetTable = STORM_ALLOC(TMPQHetTable, 1);
if(pHetTable != NULL)
{
- pHetTable->dwIndexSizeTotal = 0;
- pHetTable->dwIndexSizeExtra = 0;
- pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
- pHetTable->dwHashBitSize = dwHashBitSize;
+ // Zero the HET table
+ memset(pHetTable, 0, sizeof(TMPQHetTable));
- // If the hash table size is not entered, calculate an optimal
- // hash table size as 4/3 of the current file count.
- if(dwHashTableSize == 0)
- {
- dwHashTableSize = (dwFileCount * 4 / 3);
- assert(dwFileCount != 0);
- }
+ // Hash sizes less than 0x40 bits are not tested
+ assert(dwNameHashBitSize == 0x40);
- // Store the hash table size and file count
- pHetTable->dwHashTableSize = dwHashTableSize;
- pHetTable->dwFileCount = dwFileCount;
+ // Calculate masks
+ pHetTable->AndMask64 = ((dwNameHashBitSize != 0x40) ? ((ULONGLONG)1 << dwNameHashBitSize) : 0) - 1;
+ pHetTable->OrMask64 = (ULONGLONG)1 << (dwNameHashBitSize - 1);
- // Size of one index is calculated from hash table size
- pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwHashTableSize);
- pHetTable->dwIndexSizeExtra = 0;
- pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
+ // Store the HET table parameters
+ pHetTable->dwEntryCount = dwFileCount;
+ pHetTable->dwTotalCount = (dwFileCount * 4) / 3;
+ pHetTable->dwNameHashBitSize = dwNameHashBitSize;
+ pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwFileCount);
+ pHetTable->dwIndexSizeExtra = 0;
+ pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
- // Allocate hash table
- pHetTable->pHetHashes = STORM_ALLOC(BYTE, pHetTable->dwHashTableSize);
- if(pHetTable->pHetHashes != NULL)
+ // Allocate array of hashes
+ pHetTable->pNameHashes = STORM_ALLOC(BYTE, pHetTable->dwTotalCount);
+ if(pHetTable->pNameHashes != NULL)
{
- // Make sure that the HET hashes are initialized
- memset(pHetTable->pHetHashes, 0, pHetTable->dwHashTableSize);
+ // Make sure the data are initialized
+ memset(pHetTable->pNameHashes, 0, pHetTable->dwTotalCount);
- // If we shall create empty HET table, we have to allocate empty block index table as well
- if(bCreateEmpty)
- pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwHashTableSize * pHetTable->dwIndexSizeTotal, 0xFF);
+ // Allocate the bit array for file indexes
+ pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF);
+ if(pHetTable->pBetIndexes != NULL)
+ {
+ // Initialize the HET table from the source data (if given)
+ if(pbSrcData != NULL)
+ {
+ // Copy the name hashes
+ memcpy(pHetTable->pNameHashes, pbSrcData, pHetTable->dwTotalCount);
- // Calculate masks
- pHetTable->AndMask64 = 0;
- if(dwHashBitSize != 0x40)
- pHetTable->AndMask64 = (ULONGLONG)1 << dwHashBitSize;
- pHetTable->AndMask64--;
+ // Copy the file indexes
+ memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + pHetTable->dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes);
+ }
+
+ // Return the result HET table
+ return pHetTable;
+ }
- pHetTable->OrMask64 = (ULONGLONG)1 << (dwHashBitSize - 1);
+ // Free the name hashes
+ STORM_FREE(pHetTable->pNameHashes);
}
- else
+
+ STORM_FREE(pHetTable);
+ }
+
+ // Failed
+ return NULL;
+}
+
+static int InsertHetEntry(TMPQHetTable * pHetTable, ULONGLONG FileNameHash, DWORD dwFileIndex)
+{
+ DWORD StartIndex;
+ DWORD Index;
+ BYTE NameHash1;
+
+ // Get the start index and the high 8 bits of the name hash
+ StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount);
+ NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8));
+
+ // Find a place where to put it
+ for(;;)
+ {
+ // Did we find a free HET entry?
+ if(pHetTable->pNameHashes[Index] == HET_ENTRY_FREE)
{
- STORM_FREE(pHetTable);
- pHetTable = NULL;
+ // Set the entry in the name hash table
+ pHetTable->pNameHashes[Index] = NameHash1;
+
+ // Set the entry in the file index table
+ SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ 4);
+ return ERROR_SUCCESS;
}
+
+ // Move to the next entry in the HET table
+ // If we came to the start index again, we are done
+ Index = (Index + 1) % pHetTable->dwEntryCount;
+ if(Index == StartIndex)
+ break;
}
- return pHetTable;
+ // No space in the HET table. Should never happen,
+ // because the HET table is created according to the number of files
+ assert(false);
+ return ERROR_DISK_FULL;
}
-static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable)
+static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader)
{
- HET_TABLE_HEADER HetHeader;
TMPQHetTable * pHetTable = NULL;
- LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1);
+ LPBYTE pbSrcData = (LPBYTE)(pHetHeader + 1);
// Sanity check
- assert(pExtTable->dwSignature == HET_TABLE_SIGNATURE);
- assert(pExtTable->dwVersion == 1);
+ assert(pHetHeader->ExtHdr.dwSignature == HET_TABLE_SIGNATURE);
+ assert(pHetHeader->ExtHdr.dwVersion == 1);
// Verify size of the HET table
- if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(HET_TABLE_HEADER))
+ if(pHetHeader->ExtHdr.dwDataSize >= sizeof(TMPQHetHeader))
{
- // Copy the table header in order to have it aligned and swapped
- memcpy(&HetHeader, pbSrcData, sizeof(HET_TABLE_HEADER));
- BSWAP_ARRAY32_UNSIGNED(&HetHeader, sizeof(HET_TABLE_HEADER));
- pbSrcData += sizeof(HET_TABLE_HEADER);
-
// Verify the size of the table in the header
- if(HetHeader.dwTableSize == pExtTable->dwDataSize)
+ if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize)
{
+ // The size of the HET table must be sum of header, hash and index table size
+ assert((sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize) == pHetHeader->dwTableSize);
+
+ // So far, all MPQs with HET Table have had total number of entries equal to 4/3 of file count
+ assert(((pHetHeader->dwEntryCount * 4) / 3) == pHetHeader->dwTotalCount);
+
+ // The size of one index is predictable as well
+ assert(GetNecessaryBitCount(pHetHeader->dwEntryCount) == pHetHeader->dwIndexSizeTotal);
+
// The size of index table (in entries) is expected
// to be the same like the hash table size (in bytes)
- assert(((HetHeader.dwIndexTableSize * 8) / HetHeader.dwIndexSize) == HetHeader.dwHashTableSize);
-
+ assert(((pHetHeader->dwTotalCount * pHetHeader->dwIndexSizeTotal) + 7) / 8 == pHetHeader->dwIndexTableSize);
+
// Create translated table
- pHetTable = CreateHetTable(HetHeader.dwHashTableSize, HetHeader.dwFileCount, HetHeader.dwHashEntrySize, false);
+ pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwNameHashBitSize, pbSrcData);
if(pHetTable != NULL)
{
- // Copy the hash table size, index size and extra bits from the HET header
- pHetTable->dwHashTableSize = HetHeader.dwHashTableSize;
- pHetTable->dwIndexSizeTotal = HetHeader.dwIndexSizeTotal;
- pHetTable->dwIndexSizeExtra = HetHeader.dwIndexSizeExtra;
-
- // Fill the hash table
- if(pHetTable->pHetHashes != NULL)
- memcpy(pHetTable->pHetHashes, pbSrcData, pHetTable->dwHashTableSize);
- pbSrcData += pHetTable->dwHashTableSize;
-
- // Copy the block index table
- pHetTable->pBetIndexes = CreateBitArray(HetHeader.dwIndexTableSize * 8, 0xFF);
- if(pHetTable->pBetIndexes != NULL)
- memcpy(pHetTable->pBetIndexes->Elements, pbSrcData, HetHeader.dwIndexTableSize);
- pbSrcData += HetHeader.dwIndexTableSize;
+ // Now the sizes in the hash table should be already set
+ assert(pHetTable->dwEntryCount == pHetHeader->dwEntryCount);
+ assert(pHetTable->dwTotalCount == pHetHeader->dwTotalCount);
+ assert(pHetTable->dwIndexSizeTotal == pHetHeader->dwIndexSizeTotal);
+
+ // Copy the missing variables
+ pHetTable->dwIndexSizeExtra = pHetHeader->dwIndexSizeExtra;
+ pHetTable->dwIndexSize = pHetHeader->dwIndexSize;
}
}
}
@@ -845,90 +1120,78 @@ static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable)
return pHetTable;
}
-static TMPQExtTable * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable)
+static TMPQExtHeader * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable)
{
- TMPQExtTable * pExtTable = NULL;
- HET_TABLE_HEADER HetHeader;
+ TMPQHetHeader * pHetHeader = NULL;
+ TMPQHetHeader HetHeader;
LPBYTE pbLinearTable = NULL;
LPBYTE pbTrgData;
- size_t HetTableSize;
// Prepare header of the HET table
CreateHetHeader(pHetTable, &HetHeader);
- // Calculate the total size needed for holding the encrypted HET table
- HetTableSize = HetHeader.dwTableSize;
-
// Allocate space for the linear table
- pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + HetTableSize);
+ pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + HetHeader.dwTableSize);
if(pbLinearTable != NULL)
{
- // Create the common ext table header
- pExtTable = (TMPQExtTable *)pbLinearTable;
- pExtTable->dwSignature = HET_TABLE_SIGNATURE;
- pExtTable->dwVersion = 1;
- pExtTable->dwDataSize = (DWORD)HetTableSize;
- pbTrgData = (LPBYTE)(pExtTable + 1);
+ // Copy the table header
+ pHetHeader = (TMPQHetHeader *)pbLinearTable;
+ memcpy(pHetHeader, &HetHeader, sizeof(TMPQHetHeader));
+ pbTrgData = (LPBYTE)(pHetHeader + 1);
- // Copy the HET table header
- memcpy(pbTrgData, &HetHeader, sizeof(HET_TABLE_HEADER));
- BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(HET_TABLE_HEADER));
- pbTrgData += sizeof(HET_TABLE_HEADER);
-
- // Copy the array of HET hashes
- memcpy(pbTrgData, pHetTable->pHetHashes, pHetTable->dwHashTableSize);
- pbTrgData += pHetTable->dwHashTableSize;
+ // Copy the array of name hashes
+ memcpy(pbTrgData, pHetTable->pNameHashes, pHetTable->dwTotalCount);
+ pbTrgData += pHetTable->dwTotalCount;
// Copy the bit array of BET indexes
memcpy(pbTrgData, pHetTable->pBetIndexes->Elements, HetHeader.dwIndexTableSize);
- // Calculate the total size of the table, including the TMPQExtTable
+ // Calculate the total size of the table, including the TMPQExtHeader
if(pcbHetTable != NULL)
{
- *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtTable) + HetTableSize);
+ *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + HetHeader.dwTableSize);
}
}
- return pExtTable;
+ return &pHetHeader->ExtHdr;
}
DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
{
TMPQHetTable * pHetTable = ha->pHetTable;
ULONGLONG FileNameHash;
- ULONGLONG AndMask64;
- ULONGLONG OrMask64;
- ULONGLONG BetHash;
DWORD StartIndex;
DWORD Index;
- BYTE HetHash; // Upper 8 bits of the masked file name hash
+ BYTE NameHash1; // Upper 8 bits of the masked file name hash
+
+ // If there are no entries in the HET table, do nothing
+ if(pHetTable->dwEntryCount == 0)
+ return HASH_ENTRY_FREE;
// Do nothing if the MPQ has no HET table
assert(ha->pHetTable != NULL);
// Calculate 64-bit hash of the file name
- AndMask64 = pHetTable->AndMask64;
- OrMask64 = pHetTable->OrMask64;
- FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64;
+ FileNameHash = (HashStringJenkins(szFileName) & pHetTable->AndMask64) | pHetTable->OrMask64;
// Split the file name hash into two parts:
- // Part 1: The highest 8 bits of the name hash
- // Part 2: The rest of the name hash (without the highest 8 bits)
- HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8));
- BetHash = FileNameHash & (AndMask64 >> 0x08);
+ // NameHash1: The highest 8 bits of the name hash
+ // NameHash2: File name hash limited to hash size
+ // Note: Our file table contains full name hash, no need to cut the high 8 bits before comparison
+ NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8));
// Calculate the starting index to the hash table
- StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize);
+ StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount);
// Go through HET table until we find a terminator
- while(pHetTable->pHetHashes[Index] != HET_ENTRY_FREE)
+ while(pHetTable->pNameHashes[Index] != HET_ENTRY_FREE)
{
// Did we find a match ?
- if(pHetTable->pHetHashes[Index] == HetHash)
+ if(pHetTable->pNameHashes[Index] == NameHash1)
{
DWORD dwFileIndex = 0;
- // Get the index of the BetHash
+ // Get the file index
GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
pHetTable->dwIndexSize,
&dwFileIndex,
@@ -940,14 +1203,14 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
// assert(dwFileIndex <= ha->dwFileTableSize);
//
- // Verify the BetHash against the entry in the table of BET hashes
- if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash)
+ // Verify the FileNameHash against the entry in the table of name hashes
+ if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash)
return dwFileIndex;
}
- // Move to the next entry in the primary search table
+ // Move to the next entry in the HET table
// If we came to the start index again, we are done
- Index = (Index + 1) % pHetTable->dwHashTableSize;
+ Index = (Index + 1) % pHetTable->dwEntryCount;
if(Index == StartIndex)
break;
}
@@ -956,103 +1219,12 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
return HASH_ENTRY_FREE;
}
-DWORD AllocateHetEntry(
- TMPQArchive * ha,
- TFileEntry * pFileEntry)
-{
- TMPQHetTable * pHetTable = ha->pHetTable;
- ULONGLONG FileNameHash;
- ULONGLONG AndMask64;
- ULONGLONG OrMask64;
- ULONGLONG BetHash;
- DWORD FileCountIncrement = 0;
- DWORD FreeHetIndex = HASH_ENTRY_FREE;
- DWORD dwFileIndex;
- DWORD StartIndex;
- DWORD Index;
- BYTE HetHash; // Upper 8 bits of the masked file name hash
-
- // Do nothing if the MPQ has no HET table
- assert(ha->pHetTable != NULL);
-
- // Calculate 64-bit hash of the file name
- AndMask64 = pHetTable->AndMask64;
- OrMask64 = pHetTable->OrMask64;
- FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64;
-
- // Calculate the starting index to the hash table
- StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize);
-
- // Split the file name hash into two parts:
- // Part 1: The highest 8 bits of the name hash
- // Part 2: The rest of the name hash (without the highest 8 bits)
- HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8));
- BetHash = FileNameHash & (AndMask64 >> 0x08);
-
- // Go through HET table until we find a terminator
- for(;;)
- {
- // Did we find a match ?
- if(pHetTable->pHetHashes[Index] == HetHash)
- {
- DWORD dwFileIndex = 0;
-
- // Get the index of the BetHash
- GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
- pHetTable->dwIndexSize,
- &dwFileIndex,
- 4);
- //
- // TODO: This condition only happens when we are opening a MPQ
- // where some files were deleted by StormLib. Perhaps
- // we should not allow shrinking of the file table in MPQs v 4.0?
- // assert(dwFileIndex <= ha->dwFileTableSize);
- //
-
- // Verify the BetHash against the entry in the table of BET hashes
- if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash)
- {
- FreeHetIndex = Index;
- break;
- }
- }
-
- // Check for entries that might have been deleted
- if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED || pHetTable->pHetHashes[Index] == HET_ENTRY_FREE)
- {
- FileCountIncrement++;
- FreeHetIndex = Index;
- break;
- }
-
- // Move to the next entry in the primary search table
- // If we came to the start index again, we are done
- Index = (Index + 1) % pHetTable->dwHashTableSize;
- if(Index == StartIndex)
- return HASH_ENTRY_FREE;
- }
-
- // Fill the HET table entry
- dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable);
- pHetTable->pHetHashes[FreeHetIndex] = HetHash;
- pHetTable->dwFileCount += FileCountIncrement;
- SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * FreeHetIndex,
- pHetTable->dwIndexSize,
- &dwFileIndex,
- 4);
-
- // Fill the file entry
- pFileEntry->BetHash = BetHash;
- pFileEntry->dwHetIndex = FreeHetIndex;
- return FreeHetIndex;
-}
-
void FreeHetTable(TMPQHetTable * pHetTable)
{
if(pHetTable != NULL)
{
- if(pHetTable->pHetHashes != NULL)
- STORM_FREE(pHetTable->pHetHashes);
+ if(pHetTable->pNameHashes != NULL)
+ STORM_FREE(pHetTable->pNameHashes);
if(pHetTable->pBetIndexes != NULL)
STORM_FREE(pHetTable->pBetIndexes);
@@ -1065,7 +1237,7 @@ void FreeHetTable(TMPQHetTable * pHetTable)
static void CreateBetHeader(
TMPQArchive * ha,
- PBET_TABLE_HEADER pBetHeader)
+ TMPQBetHeader * pBetHeader)
{
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
TFileEntry * pFileEntry;
@@ -1079,9 +1251,18 @@ static void CreateBetHeader(
// Initialize array of flag combinations
InitFileFlagArray(FlagArray);
+ // Fill the common header
+ pBetHeader->ExtHdr.dwSignature = BET_TABLE_SIGNATURE;
+ pBetHeader->ExtHdr.dwVersion = 1;
+ pBetHeader->ExtHdr.dwDataSize = 0;
+
// Get the maximum values for the BET table
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
+ //
+ // Note: Deleted files must be counted as well
+ //
+
// Highest file position in the MPQ
if(pFileEntry->ByteOffset > MaxByteOffset)
MaxByteOffset = pFileEntry->ByteOffset;
@@ -1124,24 +1305,25 @@ static void CreateBetHeader(
pBetHeader->dwBitCount_Unknown;
// Save the file count and flag count
- pBetHeader->dwFileCount = ha->dwFileTableSize;
+ pBetHeader->dwEntryCount = ha->dwFileTableSize;
pBetHeader->dwFlagCount = dwMaxFlagIndex + 1;
pBetHeader->dwUnknown08 = 0x10;
// Save the total size of the BET hash
- pBetHeader->dwBetHashSizeTotal = ha->pHetTable->dwHashBitSize - 0x08;
- pBetHeader->dwBetHashSizeExtra = 0;
- pBetHeader->dwBetHashSize = pBetHeader->dwBetHashSizeTotal;
- pBetHeader->dwBetHashArraySize = ((pBetHeader->dwBetHashSizeTotal * pBetHeader->dwFileCount) + 7) / 8;
+ pBetHeader->dwBitTotal_NameHash2 = ha->pHetTable->dwNameHashBitSize - 0x08;
+ pBetHeader->dwBitExtra_NameHash2 = 0;
+ pBetHeader->dwBitCount_NameHash2 = pBetHeader->dwBitTotal_NameHash2;
+ pBetHeader->dwNameHashArraySize = ((pBetHeader->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount) + 7) / 8;
// Save the total table size
- pBetHeader->dwTableSize = sizeof(BET_TABLE_HEADER) +
+ pBetHeader->ExtHdr.dwDataSize =
+ pBetHeader->dwTableSize = sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader) +
pBetHeader->dwFlagCount * sizeof(DWORD) +
- ((pBetHeader->dwTableEntrySize * pBetHeader->dwFileCount) + 7) / 8 +
- pBetHeader->dwBetHashArraySize;
+ ((pBetHeader->dwTableEntrySize * pBetHeader->dwEntryCount) + 7) / 8 +
+ pBetHeader->dwNameHashArraySize;
}
-TMPQBetTable * CreateBetTable(DWORD dwFileCount)
+TMPQBetTable * CreateBetTable(DWORD dwEntryCount)
{
TMPQBetTable * pBetTable;
@@ -1150,7 +1332,7 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount)
if(pBetTable != NULL)
{
memset(pBetTable, 0, sizeof(TMPQBetTable));
- pBetTable->dwFileCount = dwFileCount;
+ pBetTable->dwEntryCount = dwEntryCount;
}
return pBetTable;
@@ -1158,90 +1340,87 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount)
static TMPQBetTable * TranslateBetTable(
TMPQArchive * ha,
- TMPQExtTable * pExtTable)
+ TMPQBetHeader * pBetHeader)
{
- BET_TABLE_HEADER BetHeader;
TMPQBetTable * pBetTable = NULL;
- LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1);
+ LPBYTE pbSrcData = (LPBYTE)(pBetHeader + 1);
DWORD LengthInBytes;
// Sanity check
- assert(pExtTable->dwSignature == BET_TABLE_SIGNATURE);
- assert(pExtTable->dwVersion == 1);
+ assert(pBetHeader->ExtHdr.dwSignature == BET_TABLE_SIGNATURE);
+ assert(pBetHeader->ExtHdr.dwVersion == 1);
assert(ha->pHetTable != NULL);
ha = ha;
// Verify size of the HET table
- if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(BET_TABLE_HEADER))
+ if(pBetHeader->ExtHdr.dwDataSize >= sizeof(TMPQBetHeader))
{
- // Copy the table header in order to have it aligned and swapped
- memcpy(&BetHeader, pbSrcData, sizeof(BET_TABLE_HEADER));
- BSWAP_ARRAY32_UNSIGNED(&BetHeader, sizeof(BET_TABLE_HEADER));
- pbSrcData += sizeof(BET_TABLE_HEADER);
-
- // Some MPQs affected by a bug in StormLib have pBetTable->dwFileCount
- // greater than ha->dwMaxFileCount
- if(BetHeader.dwFileCount > ha->dwMaxFileCount)
- return NULL;
-
// Verify the size of the table in the header
- if(BetHeader.dwTableSize == pExtTable->dwDataSize)
+ if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize)
{
+ // The number of entries in the BET table must be the same like number of entries in the block table
+ assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize);
+ assert(pBetHeader->dwEntryCount <= ha->dwMaxFileCount);
+
+ // The number of entries in the BET table must be the same like number of entries in the HET table
+ // Note that if it's not, it is not a problem
+ //assert(pBetHeader->dwEntryCount == ha->pHetTable->dwEntryCount);
+
// Create translated table
- pBetTable = CreateBetTable(BetHeader.dwFileCount);
+ pBetTable = CreateBetTable(pBetHeader->dwEntryCount);
if(pBetTable != NULL)
{
// Copy the variables from the header to the BetTable
- pBetTable->dwTableEntrySize = BetHeader.dwTableEntrySize;
- pBetTable->dwBitIndex_FilePos = BetHeader.dwBitIndex_FilePos;
- pBetTable->dwBitIndex_FileSize = BetHeader.dwBitIndex_FileSize;
- pBetTable->dwBitIndex_CmpSize = BetHeader.dwBitIndex_CmpSize;
- pBetTable->dwBitIndex_FlagIndex = BetHeader.dwBitIndex_FlagIndex;
- pBetTable->dwBitIndex_Unknown = BetHeader.dwBitIndex_Unknown;
- pBetTable->dwBitCount_FilePos = BetHeader.dwBitCount_FilePos;
- pBetTable->dwBitCount_FileSize = BetHeader.dwBitCount_FileSize;
- pBetTable->dwBitCount_CmpSize = BetHeader.dwBitCount_CmpSize;
- pBetTable->dwBitCount_FlagIndex = BetHeader.dwBitCount_FlagIndex;
- pBetTable->dwBitCount_Unknown = BetHeader.dwBitCount_Unknown;
-
- // Since we don't know what the "unknown" is, we'll assert when it's nonzero
+ pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize;
+ pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos;
+ pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize;
+ pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize;
+ pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex;
+ pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown;
+ pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos;
+ pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize;
+ pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize;
+ pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex;
+ pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown;
+
+ // Since we don't know what the "unknown" is, we'll assert when it's zero
assert(pBetTable->dwBitCount_Unknown == 0);
// Allocate array for flags
- if(BetHeader.dwFlagCount != 0)
+ if(pBetHeader->dwFlagCount != 0)
{
// Allocate array for file flags and load it
- pBetTable->pFileFlags = STORM_ALLOC(DWORD, BetHeader.dwFlagCount);
+ pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount);
if(pBetTable->pFileFlags != NULL)
{
- LengthInBytes = BetHeader.dwFlagCount * sizeof(DWORD);
+ LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD);
memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes);
BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes);
pbSrcData += LengthInBytes;
}
// Save the number of flags
- pBetTable->dwFlagCount = BetHeader.dwFlagCount;
+ pBetTable->dwFlagCount = pBetHeader->dwFlagCount;
}
// Load the bit-based file table
- pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * BetHeader.dwFileCount, 0);
+ pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0);
LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8;
if(pBetTable->pFileTable != NULL)
memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes);
pbSrcData += LengthInBytes;
// Fill the sizes of BET hash
- pBetTable->dwBetHashSizeTotal = BetHeader.dwBetHashSizeTotal;
- pBetTable->dwBetHashSizeExtra = BetHeader.dwBetHashSizeExtra;
- pBetTable->dwBetHashSize = BetHeader.dwBetHashSize;
+ pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2;
+ pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2;
+ pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2;
// Create and load the array of BET hashes
- pBetTable->pBetHashes = CreateBitArray(pBetTable->dwBetHashSizeTotal * BetHeader.dwFileCount, 0);
- LengthInBytes = (pBetTable->pBetHashes->NumberOfBits + 7) / 8;
- if(pBetTable->pBetHashes != NULL)
- memcpy(pBetTable->pBetHashes->Elements, pbSrcData, LengthInBytes);
- pbSrcData += BetHeader.dwBetHashArraySize;
+ pBetTable->pNameHashes = CreateBitArray(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0);
+ LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8;
+ if(pBetTable->pNameHashes != NULL)
+ memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes);
+ pbSrcData += pBetHeader->dwNameHashArraySize;
// Dump both tables
// DumpHetAndBetTable(ha->pHetTable, pBetTable);
@@ -1252,63 +1431,47 @@ static TMPQBetTable * TranslateBetTable(
return pBetTable;
}
-TMPQExtTable * TranslateBetTable(
+TMPQExtHeader * TranslateBetTable(
TMPQArchive * ha,
ULONGLONG * pcbBetTable)
{
- TMPQExtTable * pExtTable = NULL;
- BET_TABLE_HEADER BetHeader;
+ TMPQBetHeader * pBetHeader = NULL;
+ TMPQBetHeader BetHeader;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
TBitArray * pBitArray = NULL;
LPBYTE pbLinearTable = NULL;
LPBYTE pbTrgData;
- size_t BetTableSize;
DWORD LengthInBytes;
DWORD FlagArray[MAX_FLAG_INDEX];
- DWORD i;
// Calculate the bit sizes of various entries
InitFileFlagArray(FlagArray);
CreateBetHeader(ha, &BetHeader);
- // Calculate the size of the BET table
- BetTableSize = sizeof(BET_TABLE_HEADER) +
- BetHeader.dwFlagCount * sizeof(DWORD) +
- ((BetHeader.dwTableEntrySize * BetHeader.dwFileCount) + 7) / 8 +
- BetHeader.dwBetHashArraySize;
-
// Allocate space
- pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + BetTableSize);
+ pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + BetHeader.dwTableSize);
if(pbLinearTable != NULL)
{
- // Create the common ext table header
- pExtTable = (TMPQExtTable *)pbLinearTable;
- pExtTable->dwSignature = BET_TABLE_SIGNATURE;
- pExtTable->dwVersion = 1;
- pExtTable->dwDataSize = (DWORD)BetTableSize;
- pbTrgData = (LPBYTE)(pExtTable + 1);
-
- // Copy the BET table header
- memcpy(pbTrgData, &BetHeader, sizeof(BET_TABLE_HEADER));
- BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(BET_TABLE_HEADER));
- pbTrgData += sizeof(BET_TABLE_HEADER);
+ // Copy the BET header to the linear buffer
+ pBetHeader = (TMPQBetHeader *)pbLinearTable;
+ memcpy(pBetHeader, &BetHeader, sizeof(TMPQBetHeader));
+ pbTrgData = (LPBYTE)(pBetHeader + 1);
// Save the bit-based block table
- pBitArray = CreateBitArray(BetHeader.dwFileCount * BetHeader.dwTableEntrySize, 0);
+ pBitArray = CreateBitArray(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0);
if(pBitArray != NULL)
{
- TFileEntry * pFileEntry = ha->pFileTable;
DWORD dwFlagIndex = 0;
DWORD nBitOffset = 0;
- // Construct the array of flag values and bit-based file table
- for(i = 0; i < BetHeader.dwFileCount; i++, pFileEntry++)
+ // Construct the bit-based file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
//
- // Note: Blizzard MPQs contain valid values even for non-existant files
- // (FilePos, FileSize, CmpSize and FlagIndex)
- // Note: If flags is zero, it must be in the flag table too !!!
+ // Note: Missing files must be included as well
//
-
+
// Save the byte offset
SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FilePos,
BetHeader.dwBitCount_FilePos,
@@ -1349,33 +1512,22 @@ TMPQExtTable * TranslateBetTable(
STORM_FREE(pBitArray);
}
- // Create bit array for BET hashes
- pBitArray = CreateBitArray(BetHeader.dwBetHashSizeTotal * BetHeader.dwFileCount, 0);
+ // Create bit array for name hashes
+ pBitArray = CreateBitArray(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0);
if(pBitArray != NULL)
{
- TFileEntry * pFileEntry = ha->pFileTable;
- ULONGLONG AndMask64 = ha->pHetTable->AndMask64;
- ULONGLONG OrMask64 = ha->pHetTable->OrMask64;
+ DWORD dwFileIndex = 0;
- for(i = 0; i < BetHeader.dwFileCount; i++)
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
- ULONGLONG FileNameHash = 0;
-
- // Calculate 64-bit hash of the file name
- if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL)
- {
- FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64;
- FileNameHash = FileNameHash & (AndMask64 >> 0x08);
- }
-
// Insert the name hash to the bit array
- SetBits(pBitArray, BetHeader.dwBetHashSizeTotal * i,
- BetHeader.dwBetHashSize,
- &FileNameHash,
+ SetBits(pBitArray, BetHeader.dwBitTotal_NameHash2 * dwFileIndex,
+ BetHeader.dwBitCount_NameHash2,
+ &pFileEntry->FileNameHash,
8);
-
- // Move to the next file entry
- pFileEntry++;
+
+ assert(dwFileIndex < BetHeader.dwEntryCount);
+ dwFileIndex++;
}
// Write the array of BET hashes
@@ -1390,11 +1542,11 @@ TMPQExtTable * TranslateBetTable(
// Write the size of the BET table in the MPQ
if(pcbBetTable != NULL)
{
- *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtTable) + BetTableSize);
+ *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + BetHeader.dwTableSize);
}
}
- return pExtTable;
+ return &pBetHeader->ExtHdr;
}
void FreeBetTable(TMPQBetTable * pBetTable)
@@ -1405,8 +1557,8 @@ void FreeBetTable(TMPQBetTable * pBetTable)
STORM_FREE(pBetTable->pFileTable);
if(pBetTable->pFileFlags != NULL)
STORM_FREE(pBetTable->pFileFlags);
- if(pBetTable->pBetHashes != NULL)
- STORM_FREE(pBetTable->pBetHashes);
+ if(pBetTable->pNameHashes != NULL)
+ STORM_FREE(pBetTable->pNameHashes);
STORM_FREE(pBetTable);
}
@@ -1498,7 +1650,7 @@ TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex)
return NULL;
}
-void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName)
+void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName)
{
// Sanity check
assert(pFileEntry != NULL);
@@ -1518,129 +1670,104 @@ void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName)
if(pFileEntry->szFileName != NULL)
strcpy(pFileEntry->szFileName, szFileName);
}
-}
+ // We also need to create the file name hash
+ if(ha->pHetTable != NULL)
+ {
+ ULONGLONG AndMask64 = ha->pHetTable->AndMask64;
+ ULONGLONG OrMask64 = ha->pHetTable->OrMask64;
+
+ pFileEntry->FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64;
+ }
+}
-// Finds a free file entry. Does NOT increment table size.
-TFileEntry * FindFreeFileEntry(TMPQArchive * ha)
+TFileEntry * FindDeletedFileEntry(TMPQArchive * ha)
{
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- TFileEntry * pFreeEntry = NULL;
TFileEntry * pFileEntry;
- // Try to find a free entry
+ // Go through the entire file table and try to find a deleted entry
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
- // If that entry is free, we reuse it
- if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
- {
- pFreeEntry = pFileEntry;
- break;
- }
+ // Return the entry that is within the current file table (i.e. a deleted file)
+ if(!(pFileEntry->dwFlags & MPQ_FILE_EXISTS))
+ return pFileEntry;
//
// Note: Files with "delete marker" are not deleted.
- // Don't consider them free entries
+ // Don't treat them as available
//
}
- // Do we have a deleted entry?
- if(pFreeEntry != NULL)
- {
- ClearFileEntry(ha, pFreeEntry);
- return pFreeEntry;
- }
-
- // If no file entry within the existing file table is free,
- // we try the reserve space after current file table
- if(ha->dwFileTableSize < ha->dwMaxFileCount)
- return ha->pFileTable + ha->dwFileTableSize;
-
- // If we reached maximum file count, we cannot add more files to the MPQ
- assert(ha->dwFileTableSize == ha->dwMaxFileCount);
+ // No deleted entries found
return NULL;
}
-
TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
{
TFileEntry * pFileEntry = NULL;
TMPQHash * pHash;
- DWORD dwHashIndex;
- DWORD dwFileIndex;
- bool bHashEntryExists = false;
- bool bHetEntryExists = false;
- // If the archive has classic hash table, we try to
- // find the file in the hash table
- if(ha->pHashTable != NULL)
+ // The entry in the hash table should not be there.
+ // This case must be handled by the caller
+ assert(ha->pHashTable == NULL || GetHashEntryExact(ha, szFileName, lcLocale) == NULL);
+ assert(ha->pHetTable == NULL || GetFileIndex_Het(ha, szFileName) == HASH_ENTRY_FREE);
+
+ // If we are in the middle of saving listfile, we need to allocate
+ // the file entry at the end of the file table
+ if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0)
{
- // If the hash entry is already there, we reuse the file entry
- pHash = GetHashEntryExact(ha, szFileName, lcLocale);
- if(pHash != NULL)
+ // Attempt to find a deleted file entry in the file table.
+ // If it suceeds, we reuse that entry
+ pFileEntry = FindDeletedFileEntry(ha);
+ if(pFileEntry == NULL)
{
- pFileEntry = ha->pFileTable + pHash->dwBlockIndex;
- bHashEntryExists = true;
- }
- }
+ // If there is no space in the file table, we are sorry
+ if((ha->dwFileTableSize + ha->dwReservedFiles) >= ha->dwMaxFileCount)
+ return NULL;
- // If the archive has HET table, try to use it for
- // finding the file
- if(ha->pHetTable != NULL)
- {
- dwFileIndex = GetFileIndex_Het(ha, szFileName);
- if(dwFileIndex != HASH_ENTRY_FREE)
+ // Invalidate the internal files. Then we need to search for a deleted entry again,
+ // because the previous call to InvalidateInternalFiles might have created some
+ InvalidateInternalFiles(ha);
+
+ // Re-check for deleted entries
+ pFileEntry = FindDeletedFileEntry(ha);
+ if(pFileEntry == NULL)
+ {
+ // If there is still no deleted entry, we allocate an entry at the end of the file table
+ assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount);
+ pFileEntry = ha->pFileTable + ha->dwFileTableSize++;
+ }
+ }
+ else
{
- pFileEntry = ha->pFileTable + dwFileIndex;
- bHetEntryExists = true;
+ // Invalidate the internal files
+ InvalidateInternalFiles(ha);
}
}
-
- // If still haven't found the file entry, we allocate new one
- if(pFileEntry == NULL)
+ else
{
- pFileEntry = FindFreeFileEntry(ha);
- if(pFileEntry == NULL)
- return NULL;
+ // There should be at least one entry for that internal file
+ assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount);
+ pFileEntry = ha->pFileTable + ha->dwFileTableSize++;
}
- // Fill the rest of the file entry
- pFileEntry->ByteOffset = 0;
- pFileEntry->FileTime = 0;
- pFileEntry->dwFileSize = 0;
- pFileEntry->dwCmpSize = 0;
- pFileEntry->dwFlags = 0;
- pFileEntry->lcLocale = (USHORT)lcLocale;
- pFileEntry->wPlatform = 0;
- pFileEntry->dwCrc32 = 0;
- memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE);
-
- // Allocate space for file name, if it's not there yet
- AllocateFileName(pFileEntry, szFileName);
-
- // If the free file entry is at the end of the file table,
- // we have to increment file table size
- if(pFileEntry == ha->pFileTable + ha->dwFileTableSize)
+ // Did we find an usable file entry?
+ if(pFileEntry != NULL)
{
- assert(ha->dwFileTableSize < ha->dwMaxFileCount);
- ha->pHeader->dwBlockTableSize++;
- ha->dwFileTableSize++;
- }
+ // Make sure that the entry is properly initialized
+ memset(pFileEntry, 0, sizeof(TFileEntry));
+ pFileEntry->lcLocale = (USHORT)lcLocale;
+ AllocateFileName(ha, pFileEntry, szFileName);
- // If the MPQ has hash table, we have to insert the new entry into the hash table
- if(ha->pHashTable != NULL && bHashEntryExists == false)
- {
- pHash = AllocateHashEntry(ha, pFileEntry);
- assert(pHash != NULL);
- }
-
- // If the MPQ has HET table, we have to insert it to the HET table as well
- if(ha->pHetTable != NULL && bHetEntryExists == false)
- {
- // TODO: Does HET table even support locales?
- // Most probably, Blizzard gave up that silly idea long ago.
- dwHashIndex = AllocateHetEntry(ha, pFileEntry);
- assert(dwHashIndex != HASH_ENTRY_FREE);
+ // If the MPQ has hash table, we have to insert the new entry into the hash table
+ // We expect it to succeed because there must be a free hash entry if there is a free file entry
+ // Note: Don't bother with the HET table. It will be rebuilt anyway
+ if(ha->pHashTable != NULL)
+ {
+ pHash = AllocateHashEntry(ha, pFileEntry);
+ assert(pHash != NULL);
+ }
}
// Return the file entry
@@ -1653,10 +1780,8 @@ int RenameFileEntry(
const char * szNewFileName)
{
TMPQHash * pHash;
- DWORD dwFileIndex;
- int nError = ERROR_SUCCESS;
- // If the MPQ has classic hash table, clear the entry there
+ // Mark the entry as deleted in the hash table
if(ha->pHashTable != NULL)
{
assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize);
@@ -1666,23 +1791,10 @@ int RenameFileEntry(
pHash->dwBlockIndex = HASH_ENTRY_DELETED;
}
- // If the MPQ has HET table, clear the entry there as well
- if(ha->pHetTable != NULL)
- {
- TMPQHetTable * pHetTable = ha->pHetTable;
- DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1;
-
- assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize);
-
- // Clear the entry in the HET hash array
- pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED;
-
- // Set the BET index to invalid index
- SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex,
- pHetTable->dwIndexSize,
- &dwInvalidFileIndex,
- 4);
- }
+ //
+ // Note: Don't bother with the HET table.
+ // It will be rebuilt from scratch anyway
+ //
// Free the old file name
if(pFileEntry->szFileName != NULL)
@@ -1690,157 +1802,131 @@ int RenameFileEntry(
pFileEntry->szFileName = NULL;
// Allocate new file name
- AllocateFileName(pFileEntry, szNewFileName);
+ AllocateFileName(ha, pFileEntry, szNewFileName);
// Now find a hash entry for the new file name
if(ha->pHashTable != NULL)
{
// Try to find the hash table entry for the new file name
- // Note: If this fails, we leave the MPQ in a corrupt state
+ // Note: Since we deleted one hash entry, this will always succeed
pHash = AllocateHashEntry(ha, pFileEntry);
- if(pHash == NULL)
- nError = ERROR_FILE_CORRUPT;
- }
-
- // If the archive has HET table, we have to allocate HET table for the file as well
- // finding the file
- if(ha->pHetTable != NULL)
- {
- dwFileIndex = AllocateHetEntry(ha, pFileEntry);
- if(dwFileIndex == HASH_ENTRY_FREE)
- nError = ERROR_FILE_CORRUPT;
+ assert(pHash != NULL);
}
// Invalidate the entries for (listfile) and (attributes)
// After we are done with MPQ changes, we need to re-create them
InvalidateInternalFiles(ha);
- return nError;
+ return ERROR_SUCCESS;
}
-void ClearFileEntry(
+void DeleteFileEntry(
TMPQArchive * ha,
TFileEntry * pFileEntry)
{
- TMPQHash * pHash = NULL;
+ TMPQHash * pHash;
// If the MPQ has classic hash table, clear the entry there
if(ha->pHashTable != NULL)
{
- assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize);
-
- pHash = ha->pHashTable + pFileEntry->dwHashIndex;
- memset(pHash, 0xFF, sizeof(TMPQHash));
- pHash->dwBlockIndex = HASH_ENTRY_DELETED;
- }
-
- // If the MPQ has HET table, clear the entry there as well
- if(ha->pHetTable != NULL)
- {
- TMPQHetTable * pHetTable = ha->pHetTable;
- DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1;
-
- assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize);
-
- // Clear the entry in the HET hash array
- pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED;
-
- // Set the BET index to invalid index
- SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex,
- pHetTable->dwIndexSize,
- &dwInvalidFileIndex,
- 4);
+ // Only if the file entry is still an existing one
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // We expect dwHashIndex to be within the hash table
+ pHash = ha->pHashTable + pFileEntry->dwHashIndex;
+ assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize);
+
+ // Set the hash table entry as deleted
+ pHash->dwName1 = 0xFFFFFFFF;
+ pHash->dwName2 = 0xFFFFFFFF;
+ pHash->lcLocale = 0xFFFF;
+ pHash->wPlatform = 0xFFFF;
+ pHash->dwBlockIndex = HASH_ENTRY_DELETED;
+ }
}
// Free the file name, and set the file entry as deleted
if(pFileEntry->szFileName != NULL)
STORM_FREE(pFileEntry->szFileName);
+ pFileEntry->szFileName = NULL;
+
+ //
+ // Don't modify the HET table, because it gets recreated from scratch at every modification operation
+ // Don't decrement the number of entries in the file table
+ // Keep Byte Offset, file size and compressed size in the file table
+ // Also keep the CRC32 and MD5 in the file attributes
+ // Clear the file name hash
+ // Clear the MPQ_FILE_EXISTS bit.
+ //
- // Invalidate the file entry
- memset(pFileEntry, 0, sizeof(TFileEntry));
+ pFileEntry->FileNameHash = 0;
+ pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS;
}
-int FreeFileEntry(
- TMPQArchive * ha,
- TFileEntry * pFileEntry)
+void InvalidateInternalFiles(TMPQArchive * ha)
{
- TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- TFileEntry * pTempEntry;
- int nError = ERROR_SUCCESS;
-
- //
- // If we have HET table, we cannot just get rid of the file
- // Doing so would lead to empty gaps in the HET table
- // We have to keep BET hash, hash index, HET index, locale, platform and file name
- //
+ TFileEntry * pFileEntry;
+ DWORD dwReservedFiles = 0;
- if(ha->pHetTable == NULL)
+ // Do nothing if we are in the middle of saving internal files
+ if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES))
{
- TFileEntry * pLastFileEntry = ha->pFileTable + ha->dwFileTableSize - 1;
- TFileEntry * pLastUsedEntry = pLastFileEntry;
-
- // Zero the file entry
- ClearFileEntry(ha, pFileEntry);
+ //
+ // We clear the file entries of (listfile) and (attributes)
+ // For each internal file cleared, we increment the number
+ // of reserved entries in the file table.
+ //
- // Now there is a chance that we created a chunk of free
- // file entries at the end of the file table. We check this
- // and eventually free all deleted file entries at the end
- for(pTempEntry = ha->pFileTable; pTempEntry < pFileTableEnd; pTempEntry++)
+ // Invalidate the (listfile), if not done yet
+ if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID))
{
- // Is that an occupied file entry?
- if(pTempEntry->dwFlags & MPQ_FILE_EXISTS)
- pLastUsedEntry = pTempEntry;
- }
+ pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ DeleteFileEntry(ha, pFileEntry);
+ dwReservedFiles++;
+ }
- // Can we free some entries at the end?
- if(pLastUsedEntry < pLastFileEntry)
- {
- // Fix the size of the file table entry
- ha->dwFileTableSize = (DWORD)(pLastUsedEntry - ha->pFileTable) + 1;
- ha->pHeader->dwBlockTableSize = ha->dwFileTableSize;
+ ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID;
}
- }
- else
- {
- // Note: Deleted entries in Blizzard MPQs version 4.0
- // normally contain valid byte offset and length
- pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS;
- nError = ERROR_SUCCESS;
- }
- return nError;
-}
+ // Invalidate the (attributes), if not done yet
+ if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID))
+ {
+ pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ DeleteFileEntry(ha, pFileEntry);
+ dwReservedFiles++;
+ }
-void InvalidateInternalFiles(TMPQArchive * ha)
-{
- TFileEntry * pFileEntry;
+ ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID;
+ }
- //
- // Note: We set the size of both (listfile) and (attributes) to zero.
- // This causes allocating space for newly added files straight over
- // (listfile)/(attributes), if these were the last ones in the MPQ
- //
+ // If the internal files are at the end of the file table (they usually are),
+ // we want to free these 2 entries, so when new files are added, they get
+ // added to the freed entries and the internal files get added after that
+ if(ha->dwFileTableSize > 0 && dwReservedFiles != 0)
+ {
+ // Go backwards while there are free entries
+ for(pFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; pFileEntry >= ha->pFileTable; pFileEntry--)
+ {
+ // Stop searching if a file is present
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ pFileEntry++;
+ break;
+ }
+ }
- // Invalidate the (listfile), if not done yet
- if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID))
- {
- pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0;
- ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID;
- }
+ // Calculate the new file table size
+ ha->dwFileTableSize = (DWORD)(pFileEntry - ha->pFileTable);
+ ha->dwReservedFiles = dwReservedFiles;
+ }
- // Invalidate the (attributes), if not done yet
- if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID))
- {
- pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0;
- ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID;
+ // Remember that the MPQ has been changed and it will be necessary
+ // to update the tables
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
}
-
- // Remember that the MPQ has been changed and it will be necessary
- // to update the tables
- ha->dwFlags |= MPQ_FLAG_CHANGED;
}
//-----------------------------------------------------------------------------
@@ -1853,7 +1939,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl
ULONGLONG BitmapOffset;
ULONGLONG EndOfMpq;
DWORD DataBlockCount = 0;
- DWORD BitmapByteSize;
+ DWORD BitmapByteSize = 0;
DWORD WholeByteCount;
DWORD ExtraBitsCount;
@@ -1865,7 +1951,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl
{
// Calculate the number of extra bytes for data bitmap
DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / ha->pHeader->dwRawChunkSize) + 1);
- BitmapByteSize = ((DataBlockCount - 1) / 8) + 1;
+ BitmapByteSize = ((DataBlockCount + 7) / 8);
BitmapOffset = EndOfMpq + BitmapByteSize;
// Try to load the data bitmap from the end of the file
@@ -1876,7 +1962,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl
if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE)
{
// Several sanity checks to ensure integrity of the bitmap
- assert(MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq);
+ assert(ha->MpqPos + MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq);
assert(ha->pHeader->dwRawChunkSize == DataBitmap.dwBlockSize);
// Allocate space for the data bitmap
@@ -1928,6 +2014,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl
*pbFileIsComplete = bFileIsComplete;
}
+ ha->dwBitmapSize = sizeof(TMPQBitmap) + BitmapByteSize;
ha->pBitmap = pBitmap;
return ERROR_SUCCESS;
}
@@ -1943,6 +2030,10 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize)
assert((dwHashTableSize & (dwHashTableSize - 1)) == 0);
assert(ha->pHashTable == NULL);
+ // If the required hash table size is zero, don't create anything
+ if(dwHashTableSize == 0)
+ dwHashTableSize = HASH_TABLE_SIZE_DEFAULT;
+
// Create the hash table
pHashTable = STORM_ALLOC(TMPQHash, dwHashTableSize);
if(pHashTable == NULL)
@@ -1950,11 +2041,8 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize)
// Fill it
memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash));
+ ha->dwMaxFileCount = dwHashTableSize;
ha->pHashTable = pHashTable;
-
- // Set the max file count, if needed
- if(ha->pHetTable == NULL)
- ha->dwMaxFileCount = dwHashTableSize;
return ERROR_SUCCESS;
}
@@ -2004,53 +2092,124 @@ TMPQHash * LoadHashTable(TMPQArchive * ha)
break;
}
- // Calculate mask value that will serve for calculating
- // the hash table index from the MPQ_HASH_TABLE_INDEX hash
- ha->dwHashIndexMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0;
-
// Return the hash table
return pHashTable;
}
+// This function fixes the scenario then dwBlockTableSize
+// is greater and goes into a MPQ file
static void FixBlockTableSize(
TMPQArchive * ha,
- TMPQBlock * pBlockTable,
- DWORD dwClaimedSize)
+ TMPQBlock * pBlockTable)
{
TMPQHeader * pHeader = ha->pHeader;
ULONGLONG BlockTableStart;
ULONGLONG BlockTableEnd;
ULONGLONG FileDataStart;
+ DWORD dwFixedTableSize = pHeader->dwBlockTableSize;
+
+ // Only perform this check on MPQs version 1.0
+ assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1);
+
+ // Calculate claimed block table begin and end
+ BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock));
+
+ for(DWORD i = 0; i < dwFixedTableSize; i++)
+ {
+ // If the block table end goes into that file, fix the block table end
+ FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos;
+ if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart)
+ {
+ dwFixedTableSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock));
+ BlockTableEnd = FileDataStart;
+ ha->dwFlags |= MPQ_FLAG_PROTECTED;
+ }
+ }
+
+ // If we found a mismatch in the block table size
+ if(dwFixedTableSize < pHeader->dwBlockTableSize)
+ {
+ // Fill the additional space with zeros
+ memset(pBlockTable + dwFixedTableSize, 0, (pHeader->dwBlockTableSize - dwFixedTableSize) * sizeof(TMPQBlock));
+
+ // Fix the block table size
+ pHeader->dwBlockTableSize = dwFixedTableSize;
+ pHeader->BlockTableSize64 = dwFixedTableSize * sizeof(TMPQBlock);
+
+ //
+ // Note: We should recalculate the archive size in the header,
+ // (it might be invalid as well) but we don't care about it anyway
+ //
+
+ // In theory, a MPQ could have bigger block table than hash table
+ assert(pHeader->dwBlockTableSize <= ha->dwMaxFileCount);
+ }
+}
+
+// This function fixes the scenario then dwBlockTableSize
+// is greater and goes into a MPQ file
+static void FixCompressedFileSize(
+ TMPQArchive * ha,
+ TMPQBlock * pBlockTable)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock ** SortTable;
+ TMPQBlock * pBlockTableEnd = pBlockTable + pHeader->dwBlockTableSize;
+ TMPQBlock * pBlock;
+ size_t nElements = 0;
+ size_t nIndex;
+ DWORD dwArchiveSize;
// Only perform this check on MPQs version 1.0
- if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1)
+ assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1);
+
+ // Allocate sort table for all entries
+ SortTable = STORM_ALLOC(TMPQBlock*, pHeader->dwBlockTableSize);
+ if(SortTable != NULL)
{
- // Calculate claimed block table begin and end
- BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
- BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock));
+ // Calculate the end of the archive
+ dwArchiveSize = GetArchiveSize32(ha, pBlockTable, pHeader->dwBlockTableSize);
+
+ // Put all blocks to a sort table
+ for(pBlock = pBlockTable; pBlock < pBlockTableEnd; pBlock++)
+ {
+ if(pBlock->dwFlags & MPQ_FILE_EXISTS)
+ SortTable[nElements++] = pBlock;
+ }
- for(DWORD i = 0; i < dwClaimedSize; i++)
+ // Have we found at least one compressed
+ if(nElements > 0)
{
- // If the block table end goes into that file, fix the block table end
- FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos;
- if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart)
+ // Sort the table
+ qsort(SortTable, nElements, sizeof(TMPQBlock *), CompareFilePositions);
+
+ // Walk the table and set all compressed sizes to they
+ // match the difference (dwFilePos1 - dwFilePos0)
+ for(nIndex = 0; nIndex < nElements - 1; nIndex++)
{
- dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock));
- BlockTableEnd = FileDataStart;
+ TMPQBlock * pBlock1 = SortTable[nIndex + 1];
+ TMPQBlock * pBlock0 = SortTable[nIndex];
+
+ pBlock0->dwCSize = (pBlock->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (pBlock1->dwFilePos - pBlock0->dwFilePos) : pBlock0->dwFSize;
}
+
+ // Fix the last entry
+ pBlock = SortTable[nElements - 1];
+ pBlock->dwCSize = dwArchiveSize - pBlock->dwFilePos;
}
- }
- // Fix the block table size
- pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock);
- pHeader->dwBlockTableSize = dwClaimedSize;
+ STORM_FREE(SortTable);
+ }
}
-TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
+
+TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries)
{
TMPQHeader * pHeader = ha->pHeader;
TMPQBlock * pBlockTable = NULL;
ULONGLONG ByteOffset;
+ ULONGLONG FileSize;
DWORD dwTableSize;
DWORD dwCmpSize;
@@ -2067,22 +2226,20 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
{
case MPQ_SUBTYPE_MPQ:
- // Sanity check, enforced by LoadAnyHashTable
- assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize);
-
// Calculate sizes of both tables
ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
dwCmpSize = (DWORD)pHeader->BlockTableSize64;
- // I found a MPQ which claimed 0x200 entries in the block table,
- // but the file was cut and there was only 0x1A0 entries.
- // We will handle this case properly.
+ // Handle case when either the MPQ is cut in the middle of the block table
+ // or the MPQ is malformed so that block table size is greater than should be
+ FileStream_GetSize(ha->pStream, &FileSize);
if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize)
{
- pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock));
- pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
- dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ pHeader->BlockTableSize64 = FileSize - ByteOffset;
+ pHeader->dwBlockTableSize = (DWORD)(pHeader->BlockTableSize64 / sizeof(TMPQBlock));
+ dwTableSize = dwCmpSize = (DWORD)pHeader->BlockTableSize64;
+ ha->dwFlags |= MPQ_FLAG_PROTECTED;
}
//
@@ -2094,9 +2251,17 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
// If failed to load the block table, delete it
pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE);
- // Defense against MPQs that claim block table to be bigger than it really is
- if(pBlockTable != NULL)
- FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize);
+ // De-protecting maps for Warcraft III
+ if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ {
+ // Some protectors set the block table size bigger than really is
+ if(pBlockTable != NULL)
+ FixBlockTableSize(ha, pBlockTable);
+
+ // Defense against protectors that set invalid compressed size
+ if((ha->dwFlags & MPQ_FLAG_PROTECTED) && (bDontFixEntries == false))
+ FixCompressedFileSize(ha, pBlockTable);
+ }
break;
case MPQ_SUBTYPE_SQP:
@@ -2115,8 +2280,8 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
TMPQHetTable * LoadHetTable(TMPQArchive * ha)
{
+ TMPQExtHeader * pExtTable;
TMPQHetTable * pHetTable = NULL;
- TMPQExtTable * pExtTable;
TMPQHeader * pHeader = ha->pHeader;
// If the HET table position is not NULL, we expect
@@ -2128,7 +2293,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha)
if(pExtTable != NULL)
{
// If loading HET table fails, we ignore the result.
- pHetTable = TranslateHetTable(pExtTable);
+ pHetTable = TranslateHetTable((TMPQHetHeader *)pExtTable);
STORM_FREE(pExtTable);
}
}
@@ -2138,7 +2303,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha)
TMPQBetTable * LoadBetTable(TMPQArchive * ha)
{
- TMPQExtTable * pExtTable;
+ TMPQExtHeader * pExtTable;
TMPQBetTable * pBetTable = NULL;
TMPQHeader * pHeader = ha->pHeader;
@@ -2152,7 +2317,7 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha)
{
// If succeeded, we translate the BET table
// to more readable form
- pBetTable = TranslateBetTable(ha, pExtTable);
+ pBetTable = TranslateBetTable(ha, (TMPQBetHeader *)pExtTable);
STORM_FREE(pExtTable);
}
}
@@ -2174,26 +2339,16 @@ int LoadAnyHashTable(TMPQArchive * ha)
if(ha->pHetTable == NULL && ha->pHashTable == NULL)
return ERROR_FILE_CORRUPT;
- // Set the maximum file count
- if(ha->pHetTable != NULL && ha->pHashTable != NULL)
- ha->dwMaxFileCount = STORMLIB_MIN(ha->pHetTable->dwHashTableSize, pHeader->dwHashTableSize);
- else
- ha->dwMaxFileCount = (ha->pHetTable != NULL) ? ha->pHetTable->dwHashTableSize : pHeader->dwHashTableSize;
-
- // In theory, a MPQ could have bigger block table than hash table
- if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount)
- {
- ha->dwMaxFileCount = ha->pHeader->dwBlockTableSize;
- ha->dwFlags |= MPQ_FLAG_READ_ONLY;
- }
-
+ // Set the maximum file count to the size of the hash table.
+ // Note: We don't care about HET table limits, because HET table is rebuilt
+ // after each file add/rename/delete.
+ ha->dwMaxFileCount = (ha->pHashTable != NULL) ? pHeader->dwHashTableSize : HASH_TABLE_SIZE_MAX;
return ERROR_SUCCESS;
}
int BuildFileTable_Classic(
TMPQArchive * ha,
- TFileEntry * pFileTable,
- ULONGLONG FileSize)
+ TFileEntry * pFileTable)
{
TFileEntry * pFileEntry;
TMPQHeader * pHeader = ha->pHeader;
@@ -2203,33 +2358,23 @@ int BuildFileTable_Classic(
// Sanity checks
assert(ha->pHashTable != NULL);
+ // If the MPQ has no block table, do nothing
+ if(ha->pHeader->dwBlockTableSize == 0)
+ return ERROR_SUCCESS;
+
// Load the block table
- pBlockTable = (TMPQBlock *)LoadBlockTable(ha, FileSize);
+ pBlockTable = (TMPQBlock *)LoadBlockTable(ha);
if(pBlockTable != NULL)
{
- TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize;
- TMPQHash * pHash;
-
// If we don't have HET table, we build the file entries from the hash&block tables
+ // If we do, we only update it from the hash table
if(ha->pHetTable == NULL)
{
- nError = ConvertMpqBlockTable(ha, pFileTable, pBlockTable);
+ nError = BuildFileTableFromBlockTable(ha, pFileTable, pBlockTable);
}
else
{
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
- {
- if(pHash->dwBlockIndex < ha->dwFileTableSize)
- {
- pFileEntry = pFileTable + pHash->dwBlockIndex;
- if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
- {
- pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
- pFileEntry->lcLocale = pHash->lcLocale;
- pFileEntry->wPlatform = pHash->wPlatform;
- }
- }
- }
+ nError = UpdateFileTableFromHashTable(ha, pFileTable);
}
// Free the block table
@@ -2260,13 +2405,13 @@ int BuildFileTable_Classic(
// Now merge the hi-block table to the file table
if(nError == ERROR_SUCCESS)
{
+ BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, dwTableSize);
pFileEntry = pFileTable;
// Add the high file offset to the base file offset.
- // We also need to swap it during the process.
for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++)
{
- pFileEntry->ByteOffset |= ((ULONGLONG)BSWAP_INT16_UNSIGNED(pHiBlockTable[i]) << 32);
+ pFileEntry->ByteOffset = MAKE_OFFSET64(pHiBlockTable[i], pFileEntry->ByteOffset);
pFileEntry++;
}
}
@@ -2301,13 +2446,18 @@ int BuildFileTable_HetBet(
pBetTable = LoadBetTable(ha);
if(pBetTable != NULL)
{
- // Step one: Fill the indexes to the HET table
- for(i = 0; i < pHetTable->dwHashTableSize; i++)
+ // Verify the size of NameHash2 in the BET table.
+ // It has to be 8 bits less than the information in HET table
+ if((pBetTable->dwBitCount_NameHash2 + 8) != pHetTable->dwNameHashBitSize)
+ return ERROR_FILE_CORRUPT;
+
+ // Step one: Fill the name indexes
+ for(i = 0; i < pHetTable->dwEntryCount; i++)
{
DWORD dwFileIndex = 0;
// Is the entry in the HET table occupied?
- if(pHetTable->pHetHashes[i] != 0)
+ if(pHetTable->pNameHashes[i] != 0)
{
// Load the index to the BET table
GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i,
@@ -2315,17 +2465,20 @@ int BuildFileTable_HetBet(
&dwFileIndex,
4);
// Overflow test
- if(dwFileIndex < pBetTable->dwFileCount)
+ if(dwFileIndex < pBetTable->dwEntryCount)
{
- // Get the file entry and save HET index
- pFileEntry = pFileTable + dwFileIndex;
- pFileEntry->dwHetIndex = i;
+ ULONGLONG NameHash1 = pHetTable->pNameHashes[i];
+ ULONGLONG NameHash2 = 0;
// Load the BET hash
- GetBits(pBetTable->pBetHashes, pBetTable->dwBetHashSizeTotal * dwFileIndex,
- pBetTable->dwBetHashSize,
- &pFileEntry->BetHash,
- 8);
+ GetBits(pBetTable->pNameHashes, pBetTable->dwBitTotal_NameHash2 * dwFileIndex,
+ pBetTable->dwBitCount_NameHash2,
+ &NameHash2,
+ 8);
+
+ // Combine both part of the name hash and put it to the file table
+ pFileEntry = pFileTable + dwFileIndex;
+ pFileEntry->FileNameHash = (NameHash1 << pBetTable->dwBitCount_NameHash2) | NameHash2;
}
}
}
@@ -2333,7 +2486,7 @@ int BuildFileTable_HetBet(
// Go through the entire BET table and convert it to the file table.
pFileEntry = pFileTable;
pBitArray = pBetTable->pFileTable;
- for(i = 0; i < pBetTable->dwFileCount; i++)
+ for(i = 0; i < pBetTable->dwEntryCount; i++)
{
DWORD dwFlagIndex = 0;
@@ -2363,7 +2516,6 @@ int BuildFileTable_HetBet(
pBetTable->dwBitCount_FlagIndex,
&dwFlagIndex,
4);
-
pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex];
}
@@ -2377,7 +2529,7 @@ int BuildFileTable_HetBet(
}
// Set the current size of the file table
- ha->dwFileTableSize = pBetTable->dwFileCount;
+ ha->dwFileTableSize = pBetTable->dwEntryCount;
FreeBetTable(pBetTable);
nError = ERROR_SUCCESS;
}
@@ -2389,7 +2541,7 @@ int BuildFileTable_HetBet(
return nError;
}
-int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize)
+int BuildFileTable(TMPQArchive * ha)
{
TFileEntry * pFileTable;
bool bFileTableCreated = false;
@@ -2420,7 +2572,7 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize)
// Note: If block table is corrupt or missing, we set the archive as read only
if(ha->pHashTable != NULL)
{
- if(BuildFileTable_Classic(ha, pFileTable, FileSize) != ERROR_SUCCESS)
+ if(BuildFileTable_Classic(ha, pFileTable) != ERROR_SUCCESS)
ha->dwFlags |= MPQ_FLAG_READ_ONLY;
else
bFileTableCreated = true;
@@ -2438,11 +2590,128 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize)
return ERROR_SUCCESS;
}
+// Rebuilds the HET table from scratch based on the file table
+// Used after a modifying operation (add, rename, delete)
+int RebuildHetTable(TMPQArchive * ha)
+{
+ TMPQHetTable * pOldHetTable = ha->pHetTable;
+ ULONGLONG FileNameHash;
+ DWORD i;
+ int nError = ERROR_SUCCESS;
+
+ // Create new HET table based on the total number of entries in the file table
+ // Note that if we fail to create it, we just stop using HET table
+ ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL);
+ if(ha->pHetTable != NULL)
+ {
+ // Go through the file table again and insert all existing files
+ for(i = 0; i < ha->dwFileTableSize; i++)
+ {
+ if(ha->pFileTable[i].dwFlags & MPQ_FILE_EXISTS)
+ {
+ // Get the high
+ FileNameHash = ha->pFileTable[i].FileNameHash;
+ nError = InsertHetEntry(ha->pHetTable, FileNameHash, i);
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+ }
+ }
+
+ // Free the old HET table
+ FreeHetTable(pOldHetTable);
+ return nError;
+}
+
+// Rebuilds the file table, removing all deleted file entries.
+// Used when compacting the archive
+int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount)
+{
+ TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pOldFileTable = ha->pFileTable;
+ TFileEntry * pFileTable;
+ TFileEntry * pFileEntry;
+ TFileEntry * pOldEntry;
+ TMPQHash * pOldHashTable = ha->pHashTable;
+ TMPQHash * pHashTable = NULL;
+ TMPQHash * pHash;
+ int nError = ERROR_SUCCESS;
+
+ // The new hash table size must be greater or equal to the current hash table size
+ assert(dwNewHashTableSize >= ha->pHeader->dwHashTableSize);
+ assert(dwNewMaxFileCount >= ha->dwFileTableSize);
+
+ // The new hash table size must be a power of two
+ assert((dwNewHashTableSize & (dwNewHashTableSize - 1)) == 0);
+
+ // Allocate the new file table
+ pFileTable = pFileEntry = STORM_ALLOC(TFileEntry, dwNewMaxFileCount);
+ if(pFileTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate new hash table
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ {
+ pHashTable = STORM_ALLOC(TMPQHash, dwNewHashTableSize);
+ if(pHashTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // If both succeeded, we need to rebuild the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ // Make sure that the hash table is properly filled
+ memset(pFileTable, 0x00, sizeof(TFileEntry) * dwNewMaxFileCount);
+ memset(pHashTable, 0xFF, sizeof(TMPQHash) * dwNewHashTableSize);
+
+ // Set the new tables to the MPQ archive
+ ha->pFileTable = pFileTable;
+ ha->pHashTable = pHashTable;
+
+ // Set the new limits to the MPQ archive
+ ha->pHeader->dwHashTableSize = dwNewHashTableSize;
+ ha->dwMaxFileCount = dwNewMaxFileCount;
+
+ // Now copy all the file entries
+ for(pOldEntry = pOldFileTable; pOldEntry < pOldFileTableEnd; pOldEntry++)
+ {
+ // If the file entry exists, we copy it to the new table
+ // Otherwise, we skip it
+ if(pOldEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // Copy the file entry
+ *pFileEntry = *pOldEntry;
+
+ // Create hash entry for it
+ if(ha->pHashTable != NULL)
+ {
+ pHash = AllocateHashEntry(ha, pFileEntry);
+ assert(pHash != NULL);
+ }
+
+ // Move the file entry by one
+ *pFileEntry++;
+ }
+ }
+
+ // Update the file table size
+ ha->dwFileTableSize = (DWORD)(pFileEntry - pFileTable);
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+ }
+
+ // Now free the remaining entries
+ if(pOldFileTable != NULL)
+ STORM_FREE(pOldFileTable);
+ if(pOldHashTable != NULL)
+ STORM_FREE(pOldHashTable);
+ return nError;
+}
+
// Saves MPQ header, hash table, block table and hi-block table.
int SaveMPQTables(TMPQArchive * ha)
{
- TMPQExtTable * pHetTable = NULL;
- TMPQExtTable * pBetTable = NULL;
+ TMPQExtHeader * pHetTable = NULL;
+ TMPQExtHeader * pBetTable = NULL;
TMPQHeader * pHeader = ha->pHeader;
TMPQBlock * pBlockTable = NULL;
TMPQHash * pHashTable = NULL;
@@ -2488,7 +2757,7 @@ int SaveMPQTables(TMPQArchive * ha)
}
// Create block table
- if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ if(nError == ERROR_SUCCESS && ha->pFileTable != NULL)
{
pBlockTable = TranslateBlockTable(ha, &BlockTableSize64, &bNeedHiBlockTable);
if(pBlockTable == NULL)
@@ -2581,6 +2850,7 @@ int SaveMPQTables(TMPQArchive * ha)
// Write the MPQ header to the file
memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4);
if(!FileStream_Write(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize))
diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp
index 5c78004..0aae9f5 100644
--- a/src/SBaseSubTypes.cpp
+++ b/src/SBaseSubTypes.cpp
@@ -439,6 +439,42 @@ int ConvertMpkHeaderToFormat4(
return ERROR_FILE_CORRUPT;
}
+// Attempts to search a free hash entry in the hash table being converted.
+// The created hash table must always be of nonzero size,
+// should have no duplicated items and no deleted entries
+TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2)
+{
+ TMPQHash * pHash;
+ DWORD dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & (dwHashTableSize - 1));
+ assert(dwHashTableSize != 0);
+
+ // Search the hash table and return the found entries in the following priority:
+ for(;;)
+ {
+ // We are not expecting to find matching entry in the hash table being built
+ // We are not expecting to find deleted entry either
+ pHash = pHashTable + dwIndex;
+ assert(pHash->dwName1 != dwName1 || pHash->dwName2 != dwName2);
+
+ // If we found a free entry, we need to stop searching
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ return pHash;
+
+ // Move to the next hash entry.
+ // If we reached the starting entry, it's failure.
+ dwIndex = (dwIndex + 1) & (dwHashTableSize - 1);
+ if(dwIndex == dwStartIndex)
+ break;
+ }
+
+ // We haven't found anything
+ assert(false);
+ return NULL;
+}
+
void DecryptMpkTable(void * pvMpkTable, size_t cbSize)
{
LPBYTE pbMpkTable = (LPBYTE)pvMpkTable;
@@ -477,6 +513,7 @@ void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize)
TMPQHash * LoadMpkHashTable(TMPQArchive * ha)
{
TMPQHeader * pHeader = ha->pHeader;
+ TMPQHash * pHashTable = NULL;
TMPKHash * pMpkHash;
TMPQHash * pHash = NULL;
DWORD dwHashTableSize = pHeader->dwHashTableSize;
@@ -484,7 +521,7 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha)
// MPKs use different hash table searching.
// Instead of using MPQ_HASH_TABLE_INDEX hash as index,
// they store the value directly in the hash table.
- // Also, for faster searching, the hash table is sorted ascending by the value
+ // Also for faster searching, the hash table is sorted ascending by the value
// Load and decrypt the MPK hash table.
pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash));
@@ -493,25 +530,21 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha)
// Calculate the hash table size as if it was real MPQ hash table
pHeader->dwHashTableSize = GetHashTableSizeForFileCount(dwHashTableSize);
pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash);
- ha->dwHashIndexMask = pHeader->dwHashTableSize ? (pHeader->dwHashTableSize - 1) : 0;
// Now allocate table that will serve like a true MPQ hash table,
// so we translate the MPK hash table to MPQ hash table
- ha->pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize);
- if(ha->pHashTable != NULL)
+ pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize);
+ if(pHashTable != NULL)
{
// Set the entire hash table to free
- memset(ha->pHashTable, 0xFF, (size_t)pHeader->HashTableSize64);
+ memset(pHashTable, 0xFF, (size_t)pHeader->HashTableSize64);
// Copy the MPK hash table into MPQ hash table
for(DWORD i = 0; i < dwHashTableSize; i++)
{
// Finds the free hash entry in the hash table
- pHash = FindFreeHashEntry(ha, pMpkHash[i].dwName1, pMpkHash[i].dwName2, pMpkHash[i].dwName3, 0);
- if(pHash == NULL)
- break;
-
- // Sanity check
+ // We don;t expect any errors here, because we are putting files to empty hash table
+ pHash = FindFreeHashEntry(pHashTable, pHeader->dwHashTableSize, pMpkHash[i].dwName1, pMpkHash[i].dwName2, pMpkHash[i].dwName3);
assert(pHash->dwBlockIndex == HASH_ENTRY_FREE);
// Copy the MPK hash entry to the hash table
@@ -521,21 +554,13 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha)
pHash->dwName1 = pMpkHash[i].dwName2;
pHash->dwName2 = pMpkHash[i].dwName3;
}
-
- // If an error was found during conversion,
- // free the hash table
- if(pHash == NULL)
- {
- STORM_FREE(ha->pHashTable);
- ha->pHashTable = NULL;
- }
}
// Free the temporary hash table
STORM_FREE(pMpkHash);
}
- return ha->pHashTable;
+ return pHashTable;
}
static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags)
diff --git a/src/SCompression.cpp b/src/SCompression.cpp
index 933dc61..5402771 100644
--- a/src/SCompression.cpp
+++ b/src/SCompression.cpp
@@ -425,7 +425,7 @@ static void LZMA_Callback_Free(void *p, void *address)
// the data compressed by StormLib.
//
-/*static */ void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+static void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
{
ICompressProgress Progress;
CLzmaEncProps props;
@@ -1076,6 +1076,16 @@ int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInB
// is not supported by newer MPQs.
//
+ case (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN):
+ pfnDecompress1 = Decompress_huff;
+ pfnDecompress2 = Decompress_ADPCM_mono;
+ break;
+
+ case (MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN):
+ pfnDecompress1 = Decompress_huff;
+ pfnDecompress2 = Decompress_ADPCM_stereo;
+ break;
+
default:
SetLastError(ERROR_FILE_CORRUPT);
return 0;
diff --git a/src/SFileAddFile.cpp b/src/SFileAddFile.cpp
index 775a969..bb8f4f1 100644
--- a/src/SFileAddFile.cpp
+++ b/src/SFileAddFile.cpp
@@ -85,23 +85,17 @@ static int WriteDataToMpqFile(
{
TFileEntry * pFileEntry = hf->pFileEntry;
ULONGLONG ByteOffset;
- LPBYTE pbCompressed = NULL; // Compressed (target) data
- LPBYTE pbToWrite = NULL; // Data to write to the file
- int nCompressionLevel = -1; // ADPCM compression level (only used for wave files)
+ LPBYTE pbCompressed = NULL; // Compressed (target) data
+ LPBYTE pbToWrite = hf->pbFileSector; // Data to write to the file
+ int nCompressionLevel; // ADPCM compression level (only used for wave files)
int nError = ERROR_SUCCESS;
- // If the caller wants ADPCM compression, we will set wave compression level to 4,
- // which corresponds to medium quality
- if(dwCompression & LOSSY_COMPRESSION_MASK)
- nCompressionLevel = 4;
-
// Make sure that the caller won't overrun the previously initiated file size
assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize);
assert(hf->dwSectorCount != 0);
assert(hf->pbFileSector != NULL);
if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize)
return ERROR_DISK_FULL;
- pbToWrite = hf->pbFileSector;
// Now write all data to the file sector buffer
if(nError == ERROR_SUCCESS)
@@ -159,8 +153,8 @@ static int WriteDataToMpqFile(
}
//
- // Note that both SCompImplode and SCompCompress give original buffer,
- // if they are unable to comperss the data.
+ // Note that both SCompImplode and SCompCompress copy data as-is,
+ // if they are unable to compress the data.
//
if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
@@ -170,6 +164,21 @@ static int WriteDataToMpqFile(
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
{
+ // If this is the first sector, we need to override the given compression
+ // by the first sector compression. This is because the entire sector must
+ // be compressed by the same compression.
+ //
+ // Test case:
+ //
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_PKWARE) // Write 0x10 bytes (sector 0)
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
+ dwCompression = (dwSectorIndex == 0) ? hf->dwCompression0 : dwCompression;
+
+ // If the caller wants ADPCM compression, we will set wave compression level to 4,
+ // which corresponds to medium quality
+ nCompressionLevel = (dwCompression & LOSSY_COMPRESSION_MASK) ? 4 : -1;
SCompCompress(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel);
}
@@ -236,8 +245,8 @@ static int RecryptFileData(
assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED);
// File decryption key is calculated from the plain name
- szNewFileName = GetPlainFileNameA(szNewFileName);
- szFileName = GetPlainFileNameA(szFileName);
+ szNewFileName = GetPlainFileName(szNewFileName);
+ szFileName = GetPlainFileName(szFileName);
// Calculate both file keys
dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags);
@@ -382,7 +391,7 @@ int SFileAddFile_Init(
if(hf == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
- // Find a free space in the MPQ, as well as free block table entry
+ // Find a free space in the MPQ and verify if it's not over 4 GB on MPQs v1
if(nError == ERROR_SUCCESS)
{
// Find the position where the file will be stored
@@ -390,9 +399,6 @@ int SFileAddFile_Init(
hf->RawFilePos = ha->MpqPos + hf->MpqFilePos;
hf->bIsWriteHandle = true;
- // Sanity check: The MPQ must be marked as changed at this point
- assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0);
-
// When format V1, the size of the archive cannot exceed 4 GB
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
{
@@ -418,38 +424,26 @@ int SFileAddFile_Init(
}
else
{
- // Only if the file really exists
- if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
- {
- // If the file exists and "replace existing" is not set, fail it
- if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0)
- nError = ERROR_ALREADY_EXISTS;
-
- // If the file entry already contains a file
- // and it is a pseudo-name, replace it
- if(nError == ERROR_SUCCESS)
- {
- AllocateFileName(pFileEntry, szFileName);
- }
- }
+ // If the caller didn't set MPQ_FILE_REPLACEEXISTING, fail it
+ if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0)
+ nError = ERROR_ALREADY_EXISTS;
}
}
- //
- // At this point, the file name in file entry must be non-NULL
- //
-
- // Create key for file encryption
- if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED))
- {
- hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags);
- }
-
+ // Fill the file entry and TMPQFile structure
if(nError == ERROR_SUCCESS)
{
+ // At this point, the file name in the file entry must be set
+ assert(pFileEntry->szFileName != NULL);
+ assert(_stricmp(pFileEntry->szFileName, szFileName) == 0);
+
// Initialize the hash entry for the file
hf->pFileEntry = pFileEntry;
hf->dwDataSize = dwFileSize;
+
+ // Decrypt the file key
+ if(dwFlags & MPQ_FILE_ENCRYPTED)
+ hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags);
// Initialize the block table entry for the file
pFileEntry->ByteOffset = hf->MpqFilePos;
@@ -467,14 +461,17 @@ int SFileAddFile_Init(
// If the caller gave us a file time, use it.
pFileEntry->FileTime = FileTime;
+ // Mark the archive as modified
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+
// Call the callback, if needed
if(ha->pfnAddFileCB != NULL)
ha->pfnAddFileCB(ha->pvAddFileUserData, 0, hf->dwDataSize, false);
}
- // If an error occured, remember it
- if(nError != ERROR_SUCCESS && hf != NULL)
- hf->bErrorOccured = true;
+ // Store the error code from Add File operation
+ if(hf != NULL)
+ hf->nAddFileError = nError;
*phf = hf;
return nError;
}
@@ -499,12 +496,9 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d
ULONGLONG RawFilePos = hf->RawFilePos;
// Allocate buffer for file sector
- nError = AllocateSectorBuffer(hf);
+ hf->nAddFileError = nError = AllocateSectorBuffer(hf);
if(nError != ERROR_SUCCESS)
- {
- hf->bErrorOccured = true;
return nError;
- }
// Allocate patch info, if the data is patch
if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize))
@@ -513,34 +507,25 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d
hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE;
// Allocate the patch info
- nError = AllocatePatchInfo(hf, false);
+ hf->nAddFileError = nError = AllocatePatchInfo(hf, false);
if(nError != ERROR_SUCCESS)
- {
- hf->bErrorOccured = true;
return nError;
- }
}
// Allocate sector offsets
if(hf->SectorOffsets == NULL)
{
- nError = AllocateSectorOffsets(hf, false);
+ hf->nAddFileError = nError = AllocateSectorOffsets(hf, false);
if(nError != ERROR_SUCCESS)
- {
- hf->bErrorOccured = true;
return nError;
- }
}
// Create array of sector checksums
if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC))
{
- nError = AllocateSectorChecksums(hf, false);
+ hf->nAddFileError = nError = AllocateSectorChecksums(hf, false);
if(nError != ERROR_SUCCESS)
- {
- hf->bErrorOccured = true;
return nError;
- }
}
// Pre-save the patch info, if any
@@ -568,7 +553,16 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d
// Write the MPQ data to the file
if(nError == ERROR_SUCCESS)
+ {
+ // Save the first sector compression to the file structure
+ // Note that the entire first file sector will be compressed
+ // by compression that was passed to the first call of SFileAddFile_Write
+ if(hf->dwFilePos == 0)
+ hf->dwCompression0 = dwCompression;
+
+ // Write the data to the MPQ
nError = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression);
+ }
// If it succeeded and we wrote all the file data,
// we need to re-save sector offset table
@@ -586,8 +580,6 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d
if(hf->SectorChksums != NULL)
{
nError = WriteSectorChecksums(hf);
- if(nError != ERROR_SUCCESS)
- hf->bErrorOccured = true;
}
// Now write patch info
@@ -597,16 +589,12 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d
hf->pPatchInfo->dwDataSize = hf->pFileEntry->dwFileSize;
hf->pFileEntry->dwFileSize = hf->dwPatchedFileSize;
nError = WritePatchInfo(hf);
- if(nError != ERROR_SUCCESS)
- hf->bErrorOccured = true;
}
// Now write sector offsets to the file
if(hf->SectorOffsets != NULL)
{
nError = WriteSectorOffsets(hf);
- if(nError != ERROR_SUCCESS)
- hf->bErrorOccured = true;
}
// Write the MD5 hashes of each file chunk, if required
@@ -616,16 +604,12 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d
ha->MpqPos + hf->pFileEntry->ByteOffset,
hf->pFileEntry->dwCmpSize,
ha->pHeader->dwRawChunkSize);
- if(nError != ERROR_SUCCESS)
- hf->bErrorOccured = true;
}
}
}
- else
- {
- hf->bErrorOccured = true;
- }
+ // Store the error code from the Write File operation
+ hf->nAddFileError = nError;
return nError;
}
@@ -633,45 +617,43 @@ int SFileAddFile_Finish(TMPQFile * hf)
{
TMPQArchive * ha = hf->ha;
TFileEntry * pFileEntry = hf->pFileEntry;
- int nError = ERROR_SUCCESS;
+ int nError = hf->nAddFileError;
// If all previous operations succeeded, we can update the MPQ
- if(!hf->bErrorOccured)
+ if(nError == ERROR_SUCCESS)
{
// Verify if the caller wrote the file properly
if(hf->pPatchInfo == NULL)
{
assert(pFileEntry != NULL);
if(hf->dwFilePos != pFileEntry->dwFileSize)
- {
nError = ERROR_CAN_NOT_COMPLETE;
- hf->bErrorOccured = true;
- }
}
else
{
if(hf->dwFilePos != hf->pPatchInfo->dwDataSize)
- {
nError = ERROR_CAN_NOT_COMPLETE;
- hf->bErrorOccured = true;
- }
}
}
- if(!hf->bErrorOccured)
+ // Now we need to recreate the HET table, if exists
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ {
+ nError = RebuildHetTable(ha);
+ }
+
+ // Update the block table size
+ if(nError == ERROR_SUCCESS)
{
// Call the user callback, if any
if(ha->pfnAddFileCB != NULL)
ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwDataSize, hf->dwDataSize, true);
-
- // Update the size of the block table
- ha->pHeader->dwBlockTableSize = ha->dwFileTableSize;
}
else
{
// Free the file entry in MPQ tables
if(pFileEntry != NULL)
- FreeFileEntry(ha, pFileEntry);
+ DeleteFileEntry(ha, pFileEntry);
}
// Clear the add file callback
@@ -695,7 +677,7 @@ bool WINAPI SFileCreateFile(
int nError = ERROR_SUCCESS;
// Check valid parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(szArchivedName == NULL || *szArchivedName == 0)
nError = ERROR_INVALID_PARAMETER;
@@ -725,16 +707,9 @@ bool WINAPI SFileCreateFile(
nError = ERROR_INVALID_PARAMETER;
}
- // Create the file in MPQ
+ // Initiate the add file operation
if(nError == ERROR_SUCCESS)
- {
- // Invalidate the entries for (listfile) and (attributes)
- // After we are done with MPQ changes, we need to re-create them anyway
- InvalidateInternalFiles(ha);
-
- // Initiate the add file operation
nError = SFileAddFile_Init(ha, szArchivedName, FileTime, dwFileSize, lcLocale, dwFlags, (TMPQFile **)phFile);
- }
// Deal with the errors
if(nError != ERROR_SUCCESS)
@@ -752,7 +727,7 @@ bool WINAPI SFileWriteFile(
int nError = ERROR_SUCCESS;
// Check the proper parameters
- if(!IsValidFileHandle(hf))
+ if(!IsValidFileHandle(hFile))
nError = ERROR_INVALID_HANDLE;
if(hf->bIsWriteHandle == false)
nError = ERROR_INVALID_HANDLE;
@@ -794,7 +769,7 @@ bool WINAPI SFileFinishFile(HANDLE hFile)
int nError = ERROR_SUCCESS;
// Check the proper parameters
- if(!IsValidFileHandle(hf))
+ if(!IsValidFileHandle(hFile))
nError = ERROR_INVALID_HANDLE;
if(hf->bIsWriteHandle == false)
nError = ERROR_INVALID_HANDLE;
@@ -881,6 +856,8 @@ bool WINAPI SFileAddFileEx(
if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO))
dwCompression = MPQ_COMPRESSION_PKWARE;
+ // Remove both flag mono and stereo flags.
+ // They will be re-added according to WAVE type
dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO);
bIsAdpcmCompression = true;
}
@@ -908,7 +885,7 @@ bool WINAPI SFileAddFileEx(
// If the file being added is a WAVE file, we check number of channels
if(bIsFirstSector && bIsAdpcmCompression)
{
- // The file must really be a wave file, otherwise it's data corruption
+ // The file must really be a wave file, otherwise it will corrupt the file
if(!IsWaveFile(pbFileData, dwBytesToRead, &dwChannels))
{
nError = ERROR_BAD_FORMAT;
@@ -1024,7 +1001,7 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch
// Check the parameters
if(nError == ERROR_SUCCESS)
{
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(szFileName == NULL || *szFileName == 0)
nError = ERROR_INVALID_PARAMETER;
@@ -1067,8 +1044,12 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch
// After we are done with MPQ changes, we need to re-create them anyway
InvalidateInternalFiles(ha);
- // Mark the file entry as free
- nError = FreeFileEntry(ha, pFileEntry);
+ // 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);
}
// Resolve error and exit
@@ -1089,7 +1070,7 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s
// Test the valid parameters
if(nError == ERROR_SUCCESS)
{
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0)
nError = ERROR_INVALID_PARAMETER;
@@ -1136,6 +1117,10 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s
nError = RenameFileEntry(ha, pFileEntry, szNewFileName);
}
+ // Now we need to recreate the HET table, if we have one
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ nError = RebuildHetTable(ha);
+
// Now we copy the existing file entry to the new one
if(nError == ERROR_SUCCESS)
{
@@ -1170,9 +1155,8 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s
}
}
- //
// Note: MPQ_FLAG_CHANGED is set by RenameFileEntry
- //
+ // assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0);
// Resolve error and return
if(nError != ERROR_SUCCESS)
@@ -1207,7 +1191,7 @@ bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale)
TMPQFile * hf = (TMPQFile *)hFile;
// Invalid handle => do nothing
- if(!IsValidFileHandle(hf))
+ if(!IsValidFileHandle(hFile))
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
@@ -1274,7 +1258,7 @@ bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileC
{
TMPQArchive * ha = (TMPQArchive *) hMpq;
- if (!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp
index 995e5b1..bca1f66 100644
--- a/src/SFileAttributes.cpp
+++ b/src/SFileAttributes.cpp
@@ -27,343 +27,352 @@ typedef struct _MPQ_ATTRIBUTES_HEADER
} MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER;
//-----------------------------------------------------------------------------
-// Public functions (internal use by StormLib)
+// Local functions
-int SAttrLoadAttributes(TMPQArchive * ha)
+static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwFileTableSize)
{
- MPQ_ATTRIBUTES_HEADER AttrHeader;
- HANDLE hFile = NULL;
+ DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER);
+
+ // Calculate size of the (attributes) file
+ if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
+ cbAttrFile += dwFileTableSize * sizeof(DWORD);
+ if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ cbAttrFile += dwFileTableSize * sizeof(ULONGLONG);
+ if(dwAttrFlags & MPQ_ATTRIBUTE_MD5)
+ cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE;
+
+ // Weird: When there's 1 extra bit in the patch bit array, it's ignored
+ // wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes
+ if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ cbAttrFile += (dwFileTableSize + 6) / 8;
+
+ return cbAttrFile;
+}
+
+static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile)
+{
+ LPBYTE pbAttrFileEnd = pbAttrFile + cbAttrFile;
+ LPBYTE pbAttrPtr = pbAttrFile;
DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize;
- DWORD dwArraySize;
- DWORD dwBytesRead;
DWORD i;
- int nError = ERROR_SUCCESS;
- // File table must be initialized
- assert(ha->pFileTable != NULL);
+ // Load and verify the header
+ if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd)
+ {
+ PMPQ_ATTRIBUTES_HEADER pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr;
+
+ BSWAP_ARRAY32_UNSIGNED(pAttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER));
+ if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1)
+ return ERROR_BAD_FORMAT;
+
+ // Verify the flags and size of the file
+ assert((pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL) == 0);
+ assert(GetSizeOfAttributesFile(pAttrHeader->dwFlags, dwBlockTableSize) == cbAttrFile);
+
+ ha->dwAttrFlags = pAttrHeader->dwFlags;
+ pbAttrPtr = (LPBYTE)(pAttrHeader + 1);
+ }
- // Attempt to open the "(attributes)" file.
- // If it's not there, then the archive doesn't support attributes
- if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile))
+ // Load the CRC32 (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
{
- // Load the content of the attributes file
- SFileReadFile(hFile, &AttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER), &dwBytesRead, NULL);
- if(dwBytesRead != sizeof(MPQ_ATTRIBUTES_HEADER))
- nError = ERROR_FILE_CORRUPT;
+ LPDWORD ArrayCRC32 = (LPDWORD)pbAttrPtr;
+ DWORD cbArraySize = dwBlockTableSize * sizeof(DWORD);
- // Verify the header of the (attributes) file
- if(nError == ERROR_SUCCESS)
- {
- AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(AttrHeader.dwVersion);
- AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED(AttrHeader.dwFlags);
- ha->dwAttrFlags = AttrHeader.dwFlags;
- if(dwBytesRead != sizeof(MPQ_ATTRIBUTES_HEADER))
- nError = ERROR_FILE_CORRUPT;
- }
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
- // Verify format of the attributes
- if(nError == ERROR_SUCCESS)
- {
- if(AttrHeader.dwVersion > MPQ_ATTRIBUTES_V1)
- nError = ERROR_BAD_FORMAT;
- }
+ BSWAP_ARRAY32_UNSIGNED(ArrayCRC32, cbCRC32Size);
+ for(i = 0; i < dwBlockTableSize; i++)
+ ha->pFileTable[i].dwCrc32 = ArrayCRC32[i];
+ pbAttrPtr += cbArraySize;
+ }
- // Load the CRC32 (if any)
- if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_CRC32))
- {
- LPDWORD pArrayCRC32 = STORM_ALLOC(DWORD, dwBlockTableSize);
+ // Load the FILETIME (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ {
+ ULONGLONG * ArrayFileTime = (ULONGLONG *)pbAttrPtr;
+ DWORD cbArraySize = dwBlockTableSize * sizeof(ULONGLONG);
- if(pArrayCRC32 != NULL)
- {
- dwArraySize = dwBlockTableSize * sizeof(DWORD);
- SFileReadFile(hFile, pArrayCRC32, dwArraySize, &dwBytesRead, NULL);
- if(dwBytesRead == dwArraySize)
- {
- for(i = 0; i < dwBlockTableSize; i++)
- ha->pFileTable[i].dwCrc32 = BSWAP_INT32_UNSIGNED(pArrayCRC32[i]);
- }
- else
- nError = ERROR_FILE_CORRUPT;
-
- STORM_FREE(pArrayCRC32);
- }
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
- // Read the array of file times
- if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_FILETIME))
- {
- ULONGLONG * pArrayFileTime = STORM_ALLOC(ULONGLONG, dwBlockTableSize);
+ BSWAP_ARRAY64_UNSIGNED(ArrayFileTime, cbFileTimeSize);
+ for(i = 0; i < dwBlockTableSize; i++)
+ ha->pFileTable[i].FileTime = ArrayFileTime[i];
+ pbAttrPtr += cbArraySize;
+ }
- if(pArrayFileTime != NULL)
- {
- dwArraySize = dwBlockTableSize * sizeof(ULONGLONG);
- SFileReadFile(hFile, pArrayFileTime, dwArraySize, &dwBytesRead, NULL);
- if(dwBytesRead == dwArraySize)
- {
- for(i = 0; i < dwBlockTableSize; i++)
- ha->pFileTable[i].FileTime = BSWAP_INT64_UNSIGNED(pArrayFileTime[i]);
- }
- else
- nError = ERROR_FILE_CORRUPT;
-
- STORM_FREE(pArrayFileTime);
- }
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
+ // Load the MD5 (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)
+ {
+ LPBYTE ArrayMd5 = pbAttrPtr;
+ DWORD cbArraySize = dwBlockTableSize * MD5_DIGEST_SIZE;
- // Read the MD5 (if any)
- // Note: MD5 array can be incomplete, if it's the last array in the (attributes)
- if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_MD5))
- {
- unsigned char * pArrayMD5 = STORM_ALLOC(unsigned char, (dwBlockTableSize * MD5_DIGEST_SIZE));
- unsigned char * md5;
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
- if(pArrayMD5 != NULL)
- {
- dwArraySize = dwBlockTableSize * MD5_DIGEST_SIZE;
- SFileReadFile(hFile, pArrayMD5, dwArraySize, &dwBytesRead, NULL);
- if(dwBytesRead == dwArraySize)
- {
- md5 = pArrayMD5;
- for(i = 0; i < dwBlockTableSize; i++)
- {
- memcpy(ha->pFileTable[i].md5, md5, MD5_DIGEST_SIZE);
- md5 += MD5_DIGEST_SIZE;
- }
- }
- else
- nError = ERROR_FILE_CORRUPT;
-
- STORM_FREE(pArrayMD5);
- }
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ for(i = 0; i < dwBlockTableSize; i++)
+ {
+ memcpy(ha->pFileTable[i].md5, ArrayMd5, MD5_DIGEST_SIZE);
+ ArrayMd5 += MD5_DIGEST_SIZE;
}
+ pbAttrPtr += cbArraySize;
+ }
- // Read the patch bit for each file
- if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_PATCH_BIT))
- {
- LPBYTE pbBitArray;
- DWORD dwByteSize = ((dwBlockTableSize - 1) / 8) + 1;
+ // Read the patch bit for each file (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ {
+ LPBYTE pbBitArray = pbAttrPtr;
+ DWORD cbArraySize = (dwBlockTableSize + 7) / 8;
+ DWORD dwByteIndex = 0;
+ DWORD dwBitMask = 0x80;
- pbBitArray = STORM_ALLOC(BYTE, dwByteSize);
- if(pbBitArray != NULL)
- {
- SFileReadFile(hFile, pbBitArray, dwByteSize, &dwBytesRead, NULL);
- if(dwBytesRead == dwByteSize)
- {
- for(i = 0; i < dwBlockTableSize; i++)
- {
- DWORD dwByteIndex = i / 8;
- DWORD dwBitMask = 0x80 >> (i & 7);
-
- // Is the appropriate bit set?
- if(pbBitArray[dwByteIndex] & dwBitMask)
- {
- // At the moment, we assume that the patch bit is present
- // in both file table and (attributes)
- assert((ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) != 0);
- ha->pFileTable[i].dwFlags |= MPQ_FILE_PATCH_FILE;
- }
- }
- }
- else
- nError = ERROR_FILE_CORRUPT;
-
- STORM_FREE(pbBitArray);
- }
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
+
+ for(i = 0; i < dwBlockTableSize; i++)
+ {
+ ha->pFileTable[i].dwFlags |= (pbBitArray[dwByteIndex] & dwBitMask) ? MPQ_FILE_PATCH_FILE : 0;
+ dwByteIndex += (dwBitMask & 0x01);
+ dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01);
}
- //
- // Note: Version 7.00 of StormLib saved the (attributes) incorrectly.
- // Sometimes, number of entries in the (attributes) was 1 item less
- // than block table size.
- // If we encounter such table, we will zero all three arrays
- //
+ pbAttrPtr += cbArraySize;
+ }
- if(nError != ERROR_SUCCESS)
- ha->dwAttrFlags = 0;
+ //
+ // Note: Version 7.00 of StormLib saved the (attributes) incorrectly.
+ // Sometimes, number of entries in the (attributes) was 1 item less
+ // than block table size.
+ // If we encounter such table, we will zero all three arrays
+ //
- // Cleanup & exit
- SFileCloseFile(hFile);
- }
- return nError;
+ if(pbAttrPtr != pbAttrFileEnd)
+ ha->dwAttrFlags = 0;
+ return ERROR_SUCCESS;
}
-int SAttrFileSaveToMpq(TMPQArchive * ha)
+static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
{
- MPQ_ATTRIBUTES_HEADER AttrHeader;
+ PMPQ_ATTRIBUTES_HEADER pAttrHeader;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
TFileEntry * pFileEntry;
- TMPQFile * hf = NULL;
- DWORD dwFinalBlockTableSize = ha->dwFileTableSize;
- DWORD dwFileSize = 0;
- DWORD dwToWrite;
- DWORD i;
- int nError = ERROR_SUCCESS;
+ LPBYTE pbAttrFile;
+ LPBYTE pbAttrPtr;
+ size_t cbAttrFile;
+ DWORD dwFinalEntries = ha->dwFileTableSize + ha->dwReservedFiles;
- // Now we have to check if we need patch bits in the (attributes)
- if(nError == ERROR_SUCCESS)
+ // Check if we need patch bits in the (attributes) file
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
{
- for(i = 0; i < ha->dwFileTableSize; i++)
+ if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
{
- if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE)
- {
- ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT;
- break;
- }
+ ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT;
+ break;
}
}
- // If the (attributes) is not in the file table yet,
- // we have to increase the final block table size
- pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- {
- // If "(attributes)" file exists, and it's set to 0, then remove it
- if(ha->dwAttrFlags == 0)
- {
- FreeFileEntry(ha, pFileEntry);
- return ERROR_SUCCESS;
- }
- }
- else
+ // Allocate the buffer for holding the entire (attributes)
+ // Allodate 1 byte more (See GetSizeOfAttributesFile for more info)
+ cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, dwFinalEntries);
+ pbAttrFile = pbAttrPtr = STORM_ALLOC(BYTE, cbAttrFile + 1);
+ if(pbAttrFile != NULL)
{
- // If we don't want to create file atributes, do nothing
- if(ha->dwAttrFlags == 0)
- return ERROR_SUCCESS;
-
- // Check where the file entry is going to be allocated.
- // If at the end of the file table, we have to increment
- // the expected size of the (attributes) file.
- pFileEntry = FindFreeFileEntry(ha);
- if(pFileEntry == ha->pFileTable + ha->dwFileTableSize)
- dwFinalBlockTableSize++;
- }
+ // Make sure it's all zeroed
+ memset(pbAttrFile, 0, cbAttrFile + 1);
- // Calculate the size of the attributes file
- if(nError == ERROR_SUCCESS)
- {
- dwFileSize = sizeof(MPQ_ATTRIBUTES_HEADER); // Header
+ // Write the header of the (attributes) file
+ pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr;
+ pAttrHeader->dwVersion = BSWAP_INT32_UNSIGNED(100);
+ pAttrHeader->dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL));
+ pbAttrPtr = (LPBYTE)(pAttrHeader + 1);
+
+ // Write the array of CRC32, if present
if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
- dwFileSize += dwFinalBlockTableSize * sizeof(DWORD);
- if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
- dwFileSize += dwFinalBlockTableSize * sizeof(ULONGLONG);
- if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)
- dwFileSize += dwFinalBlockTableSize * MD5_DIGEST_SIZE;
- if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
- dwFileSize += ((dwFinalBlockTableSize - 1)) / 8 + 1;
- }
+ {
+ LPDWORD pArrayCRC32 = (LPDWORD)pbAttrPtr;
- // Determine the flags for (attributes)
- if(ha->dwFileFlags2 == 0)
- ha->dwFileFlags2 = GetDefaultSpecialFileFlags(ha, dwFileSize);
+ // Copy from file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ *pArrayCRC32++ = BSWAP_INT32_UNSIGNED(pFileEntry->dwCrc32);
- // Create the attributes file in the MPQ
- nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME,
- 0,
- dwFileSize,
- LANG_NEUTRAL,
- ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING,
- &hf);
+ // Skip the reserved entries
+ pbAttrPtr = (LPBYTE)(pArrayCRC32 + ha->dwReservedFiles);
+ }
- // Write all parts of the (attributes) file
- if(nError == ERROR_SUCCESS)
- {
- assert(ha->dwFileTableSize == dwFinalBlockTableSize);
+ // Write the array of file time
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ {
+ ULONGLONG * pArrayFileTime = (ULONGLONG *)pbAttrPtr;
- // Note that we don't know what the new bit (0x08) means.
- AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(100);
- AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL));
- dwToWrite = sizeof(MPQ_ATTRIBUTES_HEADER);
- nError = SFileAddFile_Write(hf, &AttrHeader, dwToWrite, MPQ_COMPRESSION_ZLIB);
- }
+ // Copy from file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ *pArrayFileTime++ = BSWAP_INT64_UNSIGNED(pFileEntry->FileTime);
- // Write the array of CRC32
- if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32))
- {
- LPDWORD pArrayCRC32 = STORM_ALLOC(DWORD, dwFinalBlockTableSize);
+ // Skip the reserved entries
+ pbAttrPtr = (LPBYTE)(pArrayFileTime + ha->dwReservedFiles);
+ }
- if(pArrayCRC32 != NULL)
+ // Write the array of MD5s
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)
{
+ LPBYTE pbArrayMD5 = pbAttrPtr;
+
// Copy from file table
- for(i = 0; i < ha->dwFileTableSize; i++)
- pArrayCRC32[i] = BSWAP_INT32_UNSIGNED(ha->pFileTable[i].dwCrc32);
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ memcpy(pbArrayMD5, pFileEntry->md5, MD5_DIGEST_SIZE);
+ pbArrayMD5 += MD5_DIGEST_SIZE;
+ }
- dwToWrite = ha->dwFileTableSize * sizeof(DWORD);
- nError = SFileAddFile_Write(hf, pArrayCRC32, dwToWrite, MPQ_COMPRESSION_ZLIB);
- STORM_FREE(pArrayCRC32);
+ // Skip the reserved items
+ pbAttrPtr = pbArrayMD5 + (ha->dwReservedFiles * MD5_DIGEST_SIZE);
}
- }
-
- // Write the array of file time
- if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME))
- {
- ULONGLONG * pArrayFileTime = STORM_ALLOC(ULONGLONG, ha->dwFileTableSize);
- if(pArrayFileTime != NULL)
+ // Write the array of patch bits
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
{
+ LPBYTE pbBitArray = pbAttrPtr;
+ DWORD dwByteSize = (dwFinalEntries + 7) / 8;
+ DWORD dwByteIndex = 0;
+ DWORD dwBitMask = 0x80;
+
// Copy from file table
- for(i = 0; i < ha->dwFileTableSize; i++)
- pArrayFileTime[i] = BSWAP_INT64_UNSIGNED(ha->pFileTable[i].FileTime);
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Set the bit, if needed
+ if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
+ pbBitArray[dwByteIndex] |= dwBitMask;
+
+ // Update bit index and bit mask
+ dwByteIndex += (dwBitMask & 0x01);
+ dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01);
+ }
- dwToWrite = ha->dwFileTableSize * sizeof(ULONGLONG);
- nError = SFileAddFile_Write(hf, pArrayFileTime, dwToWrite, MPQ_COMPRESSION_ZLIB);
- STORM_FREE(pArrayFileTime);
+ // Move past the bit array
+ pbAttrPtr = (pbBitArray + dwByteSize);
}
+
+ // Now we expect that current position matches the estimated size
+ // Note that if there is 1 extra bit above the byte size,
+ // the table is actually 1 byte shorted in Blizzard MPQs. See GetSizeOfAttributesFile
+ assert((size_t)(pbAttrPtr - pbAttrFile) == cbAttrFile + ((dwFinalEntries & 0x07) == 1) ? 1 : 0);
}
- // Write the array of MD5s
- if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5))
- {
- char * pArrayMD5 = STORM_ALLOC(char, ha->dwFileTableSize * MD5_DIGEST_SIZE);
+ // Give away the attributes file
+ if(pcbAttrFile != NULL)
+ *pcbAttrFile = (DWORD)cbAttrFile;
+ return pbAttrFile;
+}
- if(pArrayMD5 != NULL)
- {
- // Copy from file table
- for(i = 0; i < ha->dwFileTableSize; i++)
- memcpy(&pArrayMD5[i * MD5_DIGEST_SIZE], ha->pFileTable[i].md5, MD5_DIGEST_SIZE);
+//-----------------------------------------------------------------------------
+// Public functions (internal use by StormLib)
- dwToWrite = ha->dwFileTableSize * MD5_DIGEST_SIZE;
- nError = SFileAddFile_Write(hf, pArrayMD5, dwToWrite, MPQ_COMPRESSION_ZLIB);
- STORM_FREE(pArrayMD5);
- }
- }
+int SAttrLoadAttributes(TMPQArchive * ha)
+{
+ HANDLE hFile = NULL;
+ LPBYTE pbAttrFile;
+ DWORD dwBytesRead;
+ DWORD cbAttrFile = 0;
+ int nError = ERROR_FILE_CORRUPT;
+
+ // File table must be initialized
+ assert(ha->pFileTable != NULL);
- // Write the array of patch bits
- if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT))
+ // Attempt to open the "(attributes)" file.
+ // If it's not there, then the archive doesn't support attributes
+ if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile))
{
- LPBYTE pbBitArray;
- DWORD dwByteSize = ((ha->dwFileTableSize - 1) / 8) + 1;
+ // Retrieve and check size of the (attributes) file
+ cbAttrFile = SFileGetFileSize(hFile, NULL);
- pbBitArray = STORM_ALLOC(BYTE, dwByteSize);
- if(pbBitArray != NULL)
+ // Size of the (attributes) might be 1 byte less than expected
+ // See GetSizeOfAttributesFile for more info
+ pbAttrFile = STORM_ALLOC(BYTE, cbAttrFile + 1);
+ if(pbAttrFile != NULL)
{
- memset(pbBitArray, 0, dwByteSize);
- for(i = 0; i < ha->dwFileTableSize; i++)
- {
- DWORD dwByteIndex = i / 8;
- DWORD dwBitMask = 0x80 >> (i & 7);
+ // Set the last byte to 0 in case the size should be 1 byte greater
+ pbAttrFile[cbAttrFile] = 0;
- if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE)
- pbBitArray[dwByteIndex] |= dwBitMask;
- }
+ // Load the entire file to memory
+ SFileReadFile(hFile, pbAttrFile, cbAttrFile, &dwBytesRead, NULL);
+ if(dwBytesRead == cbAttrFile)
+ nError = LoadAttributesFile(ha, pbAttrFile, cbAttrFile);
- nError = SFileAddFile_Write(hf, pbBitArray, dwByteSize, MPQ_COMPRESSION_ZLIB);
- STORM_FREE(pbBitArray);
+ // Free the buffer
+ STORM_FREE(pbAttrFile);
}
+
+ // Close the attributes file
+ SFileCloseFile(hFile);
}
- // Finalize the file in the archive
- if(hf != NULL)
+ return nError;
+}
+
+// Saves the (attributes) to the MPQ
+int SAttrFileSaveToMpq(TMPQArchive * ha)
+{
+ TMPQFile * hf = NULL;
+ LPBYTE pbAttrFile;
+ DWORD cbAttrFile = 0;
+ int nError = ERROR_SUCCESS;
+
+ // If there are no file flags for (attributes) file, do nothing
+ if(ha->dwFileFlags2 == 0 || ha->dwMaxFileCount == 0)
+ return ERROR_SUCCESS;
+
+ // We expect at least one reserved entry to be there
+ assert(ha->dwReservedFiles >= 1);
+
+ // Create the raw data that is to be written to (attributes)
+ // Note: Blizzard MPQs have entries for (listfile) and (attributes),
+ // but they are filled empty
+ pbAttrFile = CreateAttributesFile(ha, &cbAttrFile);
+
+ // Now we decrement the number of reserved files.
+ // This frees one slot in the file table, so the subsequent file create operation should succeed
+ // This must happen even if CreateAttributesFile failed
+ ha->dwReservedFiles--;
+
+ // If we created something, write the attributes to the MPQ
+ if(pbAttrFile != NULL)
{
- SFileAddFile_Finish(hf);
- }
+ // We expect it to be nonzero size
+ assert(cbAttrFile != 0);
+
+ // Determine the real flags for (attributes)
+ if(ha->dwFileFlags2 == MPQ_FILE_EXISTS)
+ ha->dwFileFlags2 = GetDefaultSpecialFileFlags(cbAttrFile, ha->pHeader->wFormatVersion);
+
+ // Create the attributes file in the MPQ
+ nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME,
+ 0,
+ cbAttrFile,
+ LANG_NEUTRAL,
+ ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Write the attributes file raw data to it
+ if(nError == ERROR_SUCCESS)
+ {
+ // Write the content of the attributes file to the MPQ
+ nError = SFileAddFile_Write(hf, pbAttrFile, cbAttrFile, MPQ_COMPRESSION_ZLIB);
+ SFileAddFile_Finish(hf);
+
+ // Clear the invalidate flag
+ ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID;
+ }
- if(nError == ERROR_SUCCESS)
- ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID;
+ // Free the attributes buffer
+ STORM_FREE(pbAttrFile);
+ }
+
return nError;
}
@@ -375,7 +384,7 @@ DWORD WINAPI SFileGetAttributes(HANDLE hMpq)
TMPQArchive * ha = (TMPQArchive *)hMpq;
// Verify the parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
{
SetLastError(ERROR_INVALID_PARAMETER);
return SFILE_INVALID_ATTRIBUTES;
@@ -389,7 +398,7 @@ bool WINAPI SFileSetAttributes(HANDLE hMpq, DWORD dwFlags)
TMPQArchive * ha = (TMPQArchive *)hMpq;
// Verify the parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
@@ -439,7 +448,7 @@ bool WINAPI SFileUpdateFileAttributes(HANDLE hMpq, const char * szFileName)
// Get the file size
hf = (TMPQFile *)hFile;
- SFileGetFileInfo(hFile, SFILE_INFO_FILE_SIZE, &dwTotalBytes, sizeof(DWORD), NULL);
+ dwTotalBytes = hf->pFileEntry->dwFileSize;
// Initialize the CRC32 and MD5 contexts
md5_init(&md5_state);
diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp
index a726d43..64c599d 100644
--- a/src/SFileCompactArchive.cpp
+++ b/src/SFileCompactArchive.cpp
@@ -441,7 +441,8 @@ bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK pfnCompa
{
TMPQArchive * ha = (TMPQArchive *) hMpq;
- if (!IsValidMpqHandle(ha)) {
+ if (!IsValidMpqHandle(hMpq))
+ {
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
@@ -466,7 +467,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
int nError = ERROR_SUCCESS;
// Test the valid parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
nError = ERROR_ACCESS_DENIED;
@@ -544,6 +545,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
// Write the MPQ header to the file
memcpy(&SaveMpqHeader, ha->pHeader, ha->pHeader->dwHeaderSize);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4);
if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize))
@@ -555,9 +557,21 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
// Now copy all files
if(nError == ERROR_SUCCESS)
- {
nError = CopyMpqFiles(ha, pFileKeys, pTempStream);
- ha->dwFlags |= MPQ_FLAG_CHANGED;
+
+ // Defragment the file table
+ if(nError == ERROR_SUCCESS)
+ nError = RebuildFileTable(ha, ha->pHeader->dwHashTableSize, ha->dwMaxFileCount);
+
+ // We also need to rebuild the HET table, if any
+ if(nError == ERROR_SUCCESS)
+ {
+ // Invalidate (listfile) and (attributes)
+ InvalidateInternalFiles(ha);
+
+ // Rebuild the HET table, if we have any
+ if(ha->pHetTable != NULL)
+ nError = RebuildHetTable(ha);
}
// If succeeded, switch the streams
@@ -608,25 +622,16 @@ DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq)
bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
{
- TMPQHetTable * pOldHetTable = NULL;
TMPQArchive * ha = (TMPQArchive *)hMpq;
- TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- TFileEntry * pOldFileTable = NULL;
- TFileEntry * pOldFileEntry;
- TFileEntry * pFileEntry;
- TMPQHash * pOldHashTable = NULL;
- DWORD dwOldHashTableSize = 0;
- DWORD dwOldFileTableSize = 0;
+ DWORD dwNewHashTableSize = 0;
int nError = ERROR_SUCCESS;
// Test the valid parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
nError = ERROR_ACCESS_DENIED;
-
- // The new limit must be greater than the current file table size
- if(nError == ERROR_SUCCESS && ha->dwFileTableSize > dwMaxFileCount)
+ if(dwMaxFileCount < ha->dwFileTableSize)
nError = ERROR_DISK_FULL;
// ALL file names must be known in order to be able
@@ -637,126 +642,28 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
}
// If the MPQ has a hash table, then we relocate the hash table
- if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
- {
- // Save parameters for the current hash table
- dwOldHashTableSize = ha->pHeader->dwHashTableSize;
- pOldHashTable = ha->pHashTable;
-
- // Allocate new hash table
- ha->pHeader->dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);
- ha->pHashTable = STORM_ALLOC(TMPQHash, ha->pHeader->dwHashTableSize);
- if(ha->pHashTable != NULL)
- memset(ha->pHashTable, 0xFF, ha->pHeader->dwHashTableSize * sizeof(TMPQHash));
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
-
- // If the MPQ has HET table, allocate new one as well
- if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
- {
- // Save the original HET table
- pOldHetTable = ha->pHetTable;
-
- // Create new one
- ha->pHetTable = CreateHetTable(0, dwMaxFileCount, 0x40, true);
- if(ha->pHetTable == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
-
- // Now reallocate the file table
- if(nError == ERROR_SUCCESS)
- {
- // Save the current file table
- dwOldFileTableSize = ha->dwFileTableSize;
- pOldFileTable = ha->pFileTable;
-
- // Create new one
- ha->pFileTable = STORM_ALLOC(TFileEntry, dwMaxFileCount);
- if(ha->pFileTable != NULL)
- memset(ha->pFileTable, 0, dwMaxFileCount * sizeof(TFileEntry));
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
-
- // Now we have to build both classic hash table and HET table.
if(nError == ERROR_SUCCESS)
{
- DWORD dwFileIndex = 0;
- DWORD dwHashIndex = 0;
+ // Calculate the hash table size for the new file limit
+ dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);
- // Create new hash and HET entry for each file
- pFileEntry = ha->pFileTable;
- for(pOldFileEntry = pOldFileTable; pOldFileEntry < pOldFileTableEnd; pOldFileEntry++)
- {
- if(pOldFileEntry->dwFlags & MPQ_FILE_EXISTS)
- {
- // Copy the old file entry to the new one
- memcpy(pFileEntry, pOldFileEntry, sizeof(TFileEntry));
- assert(pFileEntry->szFileName != NULL);
-
- // Create new entry in the hash table
- if(ha->pHashTable != NULL)
- {
- if(AllocateHashEntry(ha, pFileEntry) == NULL)
- {
- nError = ERROR_CAN_NOT_COMPLETE;
- break;
- }
- }
-
- // Create new entry in the HET table, if needed
- if(ha->pHetTable != NULL)
- {
- dwHashIndex = AllocateHetEntry(ha, pFileEntry);
- if(dwHashIndex == HASH_ENTRY_FREE)
- {
- nError = ERROR_CAN_NOT_COMPLETE;
- break;
- }
- }
-
- // Move to the next file entry in the new table
- pFileEntry++;
- dwFileIndex++;
- }
- }
+ // Rebuild both file tables
+ nError = RebuildFileTable(ha, dwNewHashTableSize, dwMaxFileCount);
}
- // Mark the archive as changed
- // Note: We always have to rebuild the (attributes) file due to file table change
+ // We always have to rebuild the (attributes) file due to file table change
if(nError == ERROR_SUCCESS)
{
- ha->dwMaxFileCount = dwMaxFileCount;
+ // Invalidate (listfile) and (attributes)
InvalidateInternalFiles(ha);
- }
- else
- {
- // Revert the hash table
- if(ha->pHashTable != NULL && pOldHashTable != NULL)
- {
- STORM_FREE(ha->pHashTable);
- ha->pHeader->dwHashTableSize = dwOldHashTableSize;
- ha->pHashTable = pOldHashTable;
- }
-
- // Revert the HET table
- if(ha->pHetTable != NULL && pOldHetTable != NULL)
- {
- FreeHetTable(ha->pHetTable);
- ha->pHetTable = pOldHetTable;
- }
- // Revert the file table
- if(pOldFileTable != NULL)
- {
- STORM_FREE(ha->pFileTable);
- ha->pFileTable = pOldFileTable;
- }
-
- SetLastError(nError);
+ // Rebuild the HET table, if we have any
+ if(ha->pHetTable != NULL)
+ nError = RebuildHetTable(ha);
}
- // Return the result
+ // Return the error
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
return (nError == ERROR_SUCCESS);
}
diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp
index 78ddd71..2b51efa 100644
--- a/src/SFileCreateArchive.cpp
+++ b/src/SFileCreateArchive.cpp
@@ -57,6 +57,7 @@ static int WriteNakedMPQHeader(TMPQArchive * ha)
// Write it to the file
BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_2);
BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_3);
BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_4);
if(!FileStream_Write(ha->pStream, &ha->MpqPos, &Header, dwBytesToWrite))
@@ -68,19 +69,27 @@ static int WriteNakedMPQHeader(TMPQArchive * ha)
//-----------------------------------------------------------------------------
// Creates a new MPQ archive.
-bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
+bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
{
SFILE_CREATE_MPQ CreateInfo;
// Fill the create structure
memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ));
CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ);
- CreateInfo.dwMpqVersion = (dwFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT;
+ CreateInfo.dwMpqVersion = (dwCreateFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT;
CreateInfo.dwStreamFlags = STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE;
- CreateInfo.dwAttrFlags = (dwFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0;
+ CreateInfo.dwFileFlags1 = (dwCreateFlags & MPQ_CREATE_LISTFILE) ? MPQ_FILE_EXISTS : 0;
+ CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_EXISTS : 0;
+ CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0;
CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000;
CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0;
CreateInfo.dwMaxFileCount = dwMaxFileCount;
+
+ // Backward compatibility: SFileCreateArchive always used to add (listfile)
+ // We would break loads of applications if we change that
+ CreateInfo.dwFileFlags1 = MPQ_FILE_EXISTS;
+
+ // Let the main function create the archive
return SFileCreateArchive2(szMpqName, &CreateInfo, phMpq);
}
@@ -93,7 +102,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
HANDLE hMpq = NULL;
DWORD dwBlockTableSize = 0; // Initial block table size
DWORD dwHashTableSize = 0;
- DWORD dwMaxFileCount;
+ DWORD dwReservedFiles = 0; // Number of reserved file entries
int nError = ERROR_SUCCESS;
// Check the parameters, if they are valid
@@ -109,8 +118,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
(pCreateInfo->pvUserData != NULL || pCreateInfo->cbUserData != 0) ||
(pCreateInfo->dwAttrFlags & ~MPQ_ATTRIBUTE_ALL) ||
(pCreateInfo->dwSectorSize & (pCreateInfo->dwSectorSize - 1)) ||
- (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)) ||
- (pCreateInfo->dwMaxFileCount < 4))
+ (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)))
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
@@ -142,15 +150,14 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
return false;
}
- // Increment the maximum amount of files to have space
- // for listfile and attributes file
- dwMaxFileCount = pCreateInfo->dwMaxFileCount;
- if(pCreateInfo->dwAttrFlags != 0)
- dwMaxFileCount++;
- dwMaxFileCount++;
+ // Increment the maximum amount of files to have space for (listfile) and (attributes)
+ if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags1)
+ dwReservedFiles++;
+ if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags2 && pCreateInfo->dwAttrFlags)
+ dwReservedFiles++;
// If file count is not zero, initialize the hash table size
- dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);
+ dwHashTableSize = GetHashTableSizeForFileCount(pCreateInfo->dwMaxFileCount + dwReservedFiles);
// Retrieve the file size and round it up to 0x200 bytes
FileStream_GetSize(pStream, &MpqPos);
@@ -180,14 +187,13 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
ha->UserDataPos = MpqPos;
ha->MpqPos = MpqPos;
ha->pHeader = pHeader = (TMPQHeader *)ha->HeaderData;
- ha->dwMaxFileCount = dwMaxFileCount;
+ ha->dwMaxFileCount = dwHashTableSize;
ha->dwFileTableSize = 0;
+ ha->dwReservedFiles = dwReservedFiles;
ha->dwFileFlags1 = pCreateInfo->dwFileFlags1;
ha->dwFileFlags2 = pCreateInfo->dwFileFlags2;
- ha->dwFlags = 0;
-
- // Setup the attributes
ha->dwAttrFlags = pCreateInfo->dwAttrFlags;
+ ha->dwFlags = 0;
pStream = NULL;
// Fill the MPQ header
@@ -215,21 +221,21 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
}
// Create initial HET table, if the caller required an MPQ format 3.0 or newer
- if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3)
+ if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3 && pCreateInfo->dwMaxFileCount != 0)
{
- ha->pHetTable = CreateHetTable(0, ha->dwFileTableSize, 0x40, true);
+ ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL);
if(ha->pHetTable == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
// Create initial hash table
- if(nError == ERROR_SUCCESS)
+ if(nError == ERROR_SUCCESS && dwHashTableSize != 0)
{
nError = CreateHashTable(ha, dwHashTableSize);
}
// Create initial file table
- if(nError == ERROR_SUCCESS)
+ if(nError == ERROR_SUCCESS && ha->dwMaxFileCount != 0)
{
ha->pFileTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount);
if(ha->pFileTable != NULL)
diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp
index af7e6bc..21c9499 100644
--- a/src/SFileFindFile.cpp
+++ b/src/SFileFindFile.cpp
@@ -37,12 +37,14 @@ struct TMPQSearch
//-----------------------------------------------------------------------------
// Local functions
-static bool IsValidSearchHandle(TMPQSearch * hs)
+static TMPQSearch * IsValidSearchHandle(HANDLE hFind)
{
- if(hs == NULL)
- return false;
+ TMPQSearch * hs = (TMPQSearch *)hFind;
- return IsValidMpqHandle(hs->ha);
+ if(hs != NULL && IsValidMpqHandle(hs->ha))
+ return hs;
+
+ return NULL;
}
bool CheckWildCard(const char * szString, const char * szWildCard)
@@ -140,7 +142,7 @@ static DWORD GetSearchTableItems(TMPQArchive * ha)
while(ha != NULL)
{
// Append the number of files
- dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwFileCount
+ dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount
: ha->pHeader->dwBlockTableSize;
// Move to the patched archive
ha = ha->haPatch;
@@ -295,25 +297,29 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
}
}
- // Check the file name against the wildcard
- if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
+ // If the file name is still NULL, we cannot include the file to the search
+ if(szFileName != NULL)
{
- // Fill the found entry
- lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex;
- lpFindFileData->dwBlockIndex = dwBlockIndex;
- lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
- lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
- lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
- lpFindFileData->lcLocale = pPatchEntry->lcLocale;
-
- // Fill the filetime
- lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
- lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
-
- // Fill the file name and plain file name
- strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength);
- lpFindFileData->szPlainName = (char *)GetPlainFileNameA(lpFindFileData->cFileName);
- return ERROR_SUCCESS;
+ // Check the file name against the wildcard
+ if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
+ {
+ // Fill the found entry
+ lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex;
+ lpFindFileData->dwBlockIndex = dwBlockIndex;
+ lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
+ lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
+ lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
+ lpFindFileData->lcLocale = pPatchEntry->lcLocale;
+
+ // Fill the filetime
+ lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
+ lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
+
+ // Fill the file name and plain file name
+ strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength);
+ lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName);
+ return ERROR_SUCCESS;
+ }
}
}
}
@@ -352,7 +358,7 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA
int nError = ERROR_SUCCESS;
// Check for the valid parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(szMask == NULL || lpFindFileData == NULL)
nError = ERROR_INVALID_PARAMETER;
@@ -412,11 +418,11 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA
bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
{
- TMPQSearch * hs = (TMPQSearch *)hFind;
+ TMPQSearch * hs = IsValidSearchHandle(hFind);
int nError = ERROR_SUCCESS;
// Check the parameters
- if(!IsValidSearchHandle(hs))
+ if(hs == NULL)
nError = ERROR_INVALID_HANDLE;
if(lpFindFileData == NULL)
nError = ERROR_INVALID_PARAMETER;
@@ -431,10 +437,10 @@ bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
bool WINAPI SFileFindClose(HANDLE hFind)
{
- TMPQSearch * hs = (TMPQSearch *)hFind;
+ TMPQSearch * hs = IsValidSearchHandle(hFind);
// Check the parameters
- if(!IsValidSearchHandle(hs))
+ if(hs == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp
index 6a85bd9..06c8d6a 100644
--- a/src/SFileGetFileInfo.cpp
+++ b/src/SFileGetFileInfo.cpp
@@ -1,12 +1,11 @@
/*****************************************************************************/
-/* SFileReadFile.cpp Copyright (c) Ladislav Zezula 2003 */
+/* SFileGetFileInfo.cpp Copyright (c) Ladislav Zezula 2013 */
/*---------------------------------------------------------------------------*/
-/* Description : */
+/* Description: */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
-/* xx.xx.99 1.00 Lad The first version of SFileReadFile.cpp */
-/* 24.03.99 1.00 Lad Added the SFileGetFileInfo function */
+/* 30.11.13 1.00 Lad The first version of SFileGetFileInfo.cpp */
/*****************************************************************************/
#define __STORMLIB_SELF__
@@ -14,8 +13,34 @@
#include "StormCommon.h"
//-----------------------------------------------------------------------------
+// Local defines
+
+// Information types for SFileGetFileInfo
+#define SFILE_INFO_TYPE_UNKNOWN 0
+#define SFILE_INFO_TYPE_DIRECT_POINTER 1
+#define SFILE_INFO_TYPE_ALLOCATED 2
+#define SFILE_INFO_TYPE_READ_FROM_FILE 3
+#define SFILE_INFO_TYPE_TABLE_POINTER 4
+#define SFILE_INFO_TYPE_FILE_ENTRY 5
+
+//-----------------------------------------------------------------------------
// Local functions
+static void ConvertFileEntryToSelfRelative(TFileEntry * pFileEntry, TFileEntry * pSrcFileEntry)
+{
+ // Copy the file entry itself
+ memcpy(pFileEntry, pSrcFileEntry, sizeof(TFileEntry));
+
+ // If source is NULL, leave it NULL
+ if(pSrcFileEntry->szFileName != NULL)
+ {
+ // Set the file name pointer after the file entry
+ pFileEntry->szFileName = (char *)(pFileEntry + 1);
+ strcpy(pFileEntry->szFileName, pSrcFileEntry->szFileName);
+ }
+}
+
+
static DWORD GetMpqFileCount(TMPQArchive * ha)
{
TFileEntry * pFileTableEnd;
@@ -43,1245 +68,680 @@ static DWORD GetMpqFileCount(TMPQArchive * ha)
return dwFileCount;
}
-static TCHAR * GetFilePatchChain(TMPQFile * hf, DWORD * pcbChainLength)
+static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, DWORD * pcbLengthNeeded)
{
TMPQFile * hfTemp;
- TCHAR * szPatchChain = NULL;
- TCHAR * szPatchItem = NULL;
- TCHAR * szFileName;
+ TCHAR * szFileInfo = (TCHAR *)pvFileInfo;
size_t cchCharsNeeded = 1;
+ size_t cchFileInfo = (cbFileInfo / sizeof(TCHAR));
size_t nLength;
// Patch chain is only supported on MPQ files.
- if(hf->pStream == NULL)
+ if(hf->pStream != NULL)
{
- // Calculate the necessary length of the multi-string
- for(hfTemp = hf; hfTemp != NULL; hfTemp->hfPatchFile)
- cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1;
-
- // Allocate space for the multi-string
- szPatchChain = szPatchItem = STORM_ALLOC(TCHAR, cchCharsNeeded);
- if(szPatchChain != NULL)
- {
- // Fill-in all the names
- for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile)
- {
- szFileName = FileStream_GetFileName(hfTemp->ha->pStream);
- nLength = _tcslen(szFileName) + 1;
-
- memcpy(szPatchItem, szFileName, nLength * sizeof(TCHAR));
- szPatchItem += nLength;
- }
-
- // Terminate the multi-string
- *szPatchItem++ = 0;
- }
-
- // The length must match
- assert((size_t)(szPatchItem - szPatchChain) == cchCharsNeeded);
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
}
- // Give the length of the patch chain, in bytes
- if(pcbChainLength != NULL)
- pcbChainLength[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
- return szPatchChain;
-}
-
-// hf - MPQ File handle.
-// pbBuffer - Pointer to target buffer to store sectors.
-// dwByteOffset - Position of sector in the file (relative to file begin)
-// dwBytesToRead - Number of bytes to read. Must be multiplier of sector size.
-// pdwBytesRead - Stored number of bytes loaded
-static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead)
-{
- ULONGLONG RawFilePos;
- TMPQArchive * ha = hf->ha;
- TFileEntry * pFileEntry = hf->pFileEntry;
- LPBYTE pbRawSector = NULL;
- LPBYTE pbOutSector = pbBuffer;
- LPBYTE pbInSector = pbBuffer;
- DWORD dwRawBytesToRead;
- DWORD dwRawSectorOffset = dwByteOffset;
- DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize;
- DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize;
- DWORD dwSectorsDone = 0;
- DWORD dwBytesRead = 0;
- int nError = ERROR_SUCCESS;
-
- // Note that dwByteOffset must be aligned to size of one sector
- // Note that dwBytesToRead must be a multiplier of one sector size
- // This is local function, so we won't check if that's true.
- // Note that files stored in single units are processed by a separate function
+ // Calculate the necessary length of the multi-string
+ for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch)
+ cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1;
- // If there is not enough bytes remaining, cut dwBytesToRead
- if((dwByteOffset + dwBytesToRead) > hf->dwDataSize)
- dwBytesToRead = hf->dwDataSize - dwByteOffset;
- dwRawBytesToRead = dwBytesToRead;
+ // Give the caller the needed length
+ if(pcbLengthNeeded != NULL)
+ pcbLengthNeeded[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
- // Perform all necessary work to do with compressed files
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ // If the caller gave both buffer pointer and data length,
+ // try to copy the patch chain
+ if(szFileInfo != NULL && cchFileInfo != 0)
{
- // If the sector positions are not loaded yet, do it
- if(hf->SectorOffsets == NULL)
+ // If there is enough space in the buffer, copy the patch chain
+ if(cchCharsNeeded > cchFileInfo)
{
- nError = AllocateSectorOffsets(hf, true);
- if(nError != ERROR_SUCCESS)
- return nError;
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return false;
}
- // If the sector checksums are not loaded yet, load them now.
- if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->bLoadedSectorCRCs == false)
+ // Copy each patch
+ for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch)
{
- //
- // Sector CRCs is plain crap feature. It is almost never present,
- // often it's empty, or the end offset of sector CRCs is zero.
- // We only try to load sector CRCs once, and regardless if it fails
- // or not, we won't try that again for the given file.
- //
-
- AllocateSectorChecksums(hf, true);
- hf->bLoadedSectorCRCs = true;
+ // Get the file name and its length
+ const TCHAR * szFileName = FileStream_GetFileName(hfTemp->ha->pStream);
+ nLength = _tcslen(szFileName) + 1;
+
+ // Copy the file name
+ memcpy(szFileInfo, szFileName, nLength * sizeof(TCHAR));
+ szFileInfo += nLength;
}
- // TODO: If the raw data MD5s are not loaded yet, load them now
- // Only do it if the MPQ is of format 4.0
-// if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0)
-// {
-// nError = AllocateRawMD5s(hf, true);
-// if(nError != ERROR_SUCCESS)
-// return nError;
-// }
-
- // If the file is compressed, also allocate secondary buffer
- pbInSector = pbRawSector = STORM_ALLOC(BYTE, dwBytesToRead);
- if(pbRawSector == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Assign the temporary buffer as target for read operation
- dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex];
- dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset;
+ // Make it multi-string
+ szFileInfo[0] = 0;
}
- // Calculate raw file offset where the sector(s) are stored.
- CalculateRawSectorOffset(RawFilePos, hf, dwRawSectorOffset);
+ return true;
+}
- // Set file pointer and read all required sectors
- if(!FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead))
- return GetLastError();
- dwBytesRead = 0;
+//-----------------------------------------------------------------------------
+// Retrieves an information about an archive or about a file within the archive
+//
+// hMpqOrFile - Handle to an MPQ archive or to a file
+// InfoClass - Information to obtain
+// pvFileInfo - Pointer to buffer to store the information
+// cbFileInfo - Size of the buffer pointed by pvFileInfo
+// pcbLengthNeeded - Receives number of bytes necessary to store the information
- // Now we have to decrypt and decompress all file sectors that have been loaded
- for(DWORD i = 0; i < dwSectorsToRead; i++)
- {
- DWORD dwRawBytesInThisSector = ha->dwSectorSize;
- DWORD dwBytesInThisSector = ha->dwSectorSize;
- DWORD dwIndex = dwSectorIndex + i;
-
- // If there is not enough bytes in the last sector,
- // cut the number of bytes in this sector
- if(dwRawBytesInThisSector > dwBytesToRead)
- dwRawBytesInThisSector = dwBytesToRead;
- if(dwBytesInThisSector > dwBytesToRead)
- dwBytesInThisSector = dwBytesToRead;
-
- // If the file is compressed, we have to adjust the raw sector size
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
- dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex];
-
- // If the file is encrypted, we have to decrypt the sector
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
- {
- BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector);
+bool WINAPI SFileGetFileInfo(
+ HANDLE hMpqOrFile,
+ SFileInfoClass InfoClass,
+ void * pvFileInfo,
+ DWORD cbFileInfo,
+ LPDWORD pcbLengthNeeded)
+{
+ MPQ_SIGNATURE_INFO SignatureInfo;
+ TMPQArchive * ha = NULL;
+ TFileEntry * pFileEntry = NULL;
+ ULONGLONG Int64Value = 0;
+ ULONGLONG ByteOffset = 0;
+ TMPQFile * hf = NULL;
+ void * pvSrcFileInfo = NULL;
+ DWORD cbSrcFileInfo = 0;
+ DWORD dwInt32Value = 0;
+ int nInfoType = SFILE_INFO_TYPE_UNKNOWN;
+ int nError = ERROR_INVALID_PARAMETER;
- // If we don't know the key, try to detect it by file content
- if(hf->dwFileKey == 0)
+ switch(InfoClass)
+ {
+ case SFileMpqFileName:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector);
- if(hf->dwFileKey == 0)
- {
- nError = ERROR_UNKNOWN_FILE_KEY;
- break;
- }
+ pvSrcFileInfo = (void *)FileStream_GetFileName(ha->pStream);
+ cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
+ break;
- DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex);
- BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector);
- }
-
- // If the file has sector CRC check turned on, perform it
- if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL)
- {
- DWORD dwAdlerExpected = hf->SectorChksums[dwIndex];
- DWORD dwAdlerValue = 0;
-
- // We can only check sector CRC when it's not zero
- // Neither can we check it if it's 0xFFFFFFFF.
- if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF)
+ case SFileMpqUserDataOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && ha->pUserData != NULL)
{
- dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector);
- if(dwAdlerValue != dwAdlerExpected)
- {
- nError = ERROR_CHECKSUM_ERROR;
- break;
- }
+ pvSrcFileInfo = &ha->UserDataPos;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
- }
-
- // If the sector is really compressed, decompress it.
- // WARNING : Some sectors may not be compressed, it can be determined only
- // by comparing uncompressed and compressed size !!!
- if(dwRawBytesInThisSector < dwBytesInThisSector)
- {
- int cbOutSector = dwBytesInThisSector;
- int cbInSector = dwRawBytesInThisSector;
- int nResult = 0;
+ break;
- // Is the file compressed by Blizzard's multiple compression ?
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
+ case SFileMpqUserDataHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && ha->pUserData != NULL)
{
- if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2)
- nResult = SCompDecompress2(pbOutSector, &cbOutSector, pbInSector, cbInSector);
- else
- nResult = SCompDecompress(pbOutSector, &cbOutSector, pbInSector, cbInSector);
+ ByteOffset = ha->UserDataPos;
+ cbSrcFileInfo = sizeof(TMPQUserData);
+ nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
}
+ break;
- // Is the file compressed by PKWARE Data Compression Library ?
- else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
+ case SFileMpqUserData:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && ha->pUserData != NULL)
{
- nResult = SCompExplode(pbOutSector, &cbOutSector, pbInSector, cbInSector);
+ ByteOffset = ha->UserDataPos + sizeof(TMPQUserData);
+ cbSrcFileInfo = ha->pUserData->dwHeaderOffs - sizeof(TMPQUserData);
+ nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
}
+ break;
- // Did the decompression fail ?
- if(nResult == 0)
+ case SFileMpqHeaderOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- nError = ERROR_FILE_CORRUPT;
- break;
+ pvSrcFileInfo = &ha->MpqPos;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
- }
- else
- {
- if(pbOutSector != pbInSector)
- memcpy(pbOutSector, pbInSector, dwBytesInThisSector);
- }
-
- // Move pointers
- dwBytesToRead -= dwBytesInThisSector;
- dwByteOffset += dwBytesInThisSector;
- dwBytesRead += dwBytesInThisSector;
- pbOutSector += dwBytesInThisSector;
- pbInSector += dwRawBytesInThisSector;
- dwSectorsDone++;
- }
-
- // Free all used buffers
- if(pbRawSector != NULL)
- STORM_FREE(pbRawSector);
-
- // Give the caller thenumber of bytes read
- *pdwBytesRead = dwBytesRead;
- return nError;
-}
-
-static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
-{
- ULONGLONG RawFilePos = hf->RawFilePos;
- TMPQArchive * ha = hf->ha;
- TFileEntry * pFileEntry = hf->pFileEntry;
- LPBYTE pbCompressed = NULL;
- LPBYTE pbRawData = NULL;
- int nError = ERROR_SUCCESS;
-
- // If the file buffer is not allocated yet, do it.
- if(hf->pbFileSector == NULL)
- {
- nError = AllocateSectorBuffer(hf);
- if(nError != ERROR_SUCCESS)
- return nError;
- pbRawData = hf->pbFileSector;
- }
-
- // If the file is a patch file, adjust raw data offset
- if(hf->pPatchInfo != NULL)
- RawFilePos += hf->pPatchInfo->dwLength;
-
- // If the file sector is not loaded yet, do it
- if(hf->dwSectorOffs != 0)
- {
- // Is the file compressed?
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
- {
- // Allocate space for compressed data
- pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize);
- if(pbCompressed == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
- pbRawData = pbCompressed;
- }
-
- // Load the raw (compressed, encrypted) data
- if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize))
- {
- STORM_FREE(pbCompressed);
- return GetLastError();
- }
-
- // If the file is encrypted, we have to decrypt the data first
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
- {
- BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize);
- DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey);
- BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize);
- }
+ break;
- // If the file is compressed, we have to decompress it now
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
- {
- int cbOutBuffer = (int)hf->dwDataSize;
- int cbInBuffer = (int)pFileEntry->dwCmpSize;
- int nResult = 0;
-
- //
- // If the file is an incremental patch, the size of compressed data
- // is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo)
- //
- // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA:
- //
- // File CmprSize DcmpSize DataSize Compressed?
- // -------------------------------------- ---------- -------- -------- ---------------
- // esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes
- // deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No
- //
-
- if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
- cbInBuffer = cbInBuffer - sizeof(TPatchInfo);
-
- // Is the file compressed by Blizzard's multiple compression ?
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
+ case SFileMpqHeaderSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2)
- nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer);
- else
- nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer);
+ pvSrcFileInfo = &ha->pHeader->dwHeaderSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
+ break;
- // Is the file compressed by PKWARE Data Compression Library ?
- // Note: Single unit files compressed with IMPLODE are not supported by Blizzard
- else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
- nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer);
-
- nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
- }
- else
- {
- if(pbRawData != hf->pbFileSector)
- memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize);
- }
-
- // Free the decompression buffer.
- if(pbCompressed != NULL)
- STORM_FREE(pbCompressed);
-
- // The file sector is now properly loaded
- hf->dwSectorOffs = 0;
- }
-
- // At this moment, we have the file loaded into the file buffer.
- // Copy as much as the caller wants
- if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0)
- {
- // File position is greater or equal to file size ?
- if(dwFilePos >= hf->dwDataSize)
- {
- *pdwBytesRead = 0;
- return ERROR_SUCCESS;
- }
-
- // If not enough bytes remaining in the file, cut them
- if((hf->dwDataSize - dwFilePos) < dwToRead)
- dwToRead = (hf->dwDataSize - dwFilePos);
-
- // Copy the bytes
- memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead);
-
- // Give the number of bytes read
- *pdwBytesRead = dwToRead;
- return ERROR_SUCCESS;
- }
-
- // An error, sorry
- return ERROR_CAN_NOT_COMPLETE;
-}
-
-static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
-{
- ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C)
- TMPQArchive * ha = hf->ha;
- TFileEntry * pFileEntry = hf->pFileEntry;
- LPBYTE pbCompressed = NULL;
- LPBYTE pbRawData = hf->pbFileSector;
- int nError = ERROR_SUCCESS;
-
- // We do not support patch files in MPK archives
- assert(hf->pPatchInfo == NULL);
-
- // If the file buffer is not allocated yet, do it.
- if(hf->pbFileSector == NULL)
- {
- nError = AllocateSectorBuffer(hf);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Is the file compressed?
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
- {
- // Allocate space for compressed data
- pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize);
- if(pbCompressed == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
- pbRawData = pbCompressed;
- }
-
- // Load the raw (compressed, encrypted) data
- if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize))
- {
- STORM_FREE(pbCompressed);
- return GetLastError();
- }
-
- // If the file is encrypted, we have to decrypt the data first
- if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
- {
- DecryptMpkTable(pbRawData, pFileEntry->dwCmpSize);
- }
-
- // If the file is compressed, we have to decompress it now
- if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
- {
- int cbOutBuffer = (int)hf->dwDataSize;
-
- if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, pbRawData, (int)pFileEntry->dwCmpSize))
- nError = ERROR_FILE_CORRUPT;
- }
- else
- {
- if(pbRawData != hf->pbFileSector)
- memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize);
- }
-
- // Free the decompression buffer.
- if(pbCompressed != NULL)
- STORM_FREE(pbCompressed);
-
- // The file sector is now properly loaded
- hf->dwSectorOffs = 0;
- }
-
- // At this moment, we have the file loaded into the file buffer.
- // Copy as much as the caller wants
- if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0)
- {
- // File position is greater or equal to file size ?
- if(dwFilePos >= hf->dwDataSize)
- {
- *pdwBytesRead = 0;
- return ERROR_SUCCESS;
- }
-
- // If not enough bytes remaining in the file, cut them
- if((hf->dwDataSize - dwFilePos) < dwToRead)
- dwToRead = (hf->dwDataSize - dwFilePos);
-
- // Copy the bytes
- memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead);
-
- // Give the number of bytes read
- *pdwBytesRead = dwToRead;
- return ERROR_SUCCESS;
- }
-
- // An error, sorry
- return ERROR_CAN_NOT_COMPLETE;
-}
-
-
-static int ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead)
-{
- TMPQArchive * ha = hf->ha;
- LPBYTE pbBuffer = (BYTE *)pvBuffer;
- DWORD dwTotalBytesRead = 0; // Total bytes read in all three parts
- DWORD dwSectorSizeMask = ha->dwSectorSize - 1; // Mask for block size, usually 0x0FFF
- DWORD dwFileSectorPos; // File offset of the loaded sector
- DWORD dwBytesRead; // Number of bytes read (temporary variable)
- int nError;
-
- // If the file position is at or beyond end of file, do nothing
- if(dwFilePos >= hf->dwDataSize)
- {
- *pdwBytesRead = 0;
- return ERROR_SUCCESS;
- }
-
- // If not enough bytes in the file remaining, cut them
- if(dwBytesToRead > (hf->dwDataSize - dwFilePos))
- dwBytesToRead = (hf->dwDataSize - dwFilePos);
-
- // Compute sector position in the file
- dwFileSectorPos = dwFilePos & ~dwSectorSizeMask; // Position in the block
-
- // If the file sector buffer is not allocated yet, do it now
- if(hf->pbFileSector == NULL)
- {
- nError = AllocateSectorBuffer(hf);
- if(nError != ERROR_SUCCESS)
- return nError;
- }
-
- // Load the first (incomplete) file sector
- if(dwFilePos & dwSectorSizeMask)
- {
- DWORD dwBytesInSector = ha->dwSectorSize;
- DWORD dwBufferOffs = dwFilePos & dwSectorSizeMask;
- DWORD dwToCopy;
-
- // Is the file sector already loaded ?
- if(hf->dwSectorOffs != dwFileSectorPos)
- {
- // Load one MPQ sector into archive buffer
- nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesInSector);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Remember that the data loaded to the sector have new file offset
- hf->dwSectorOffs = dwFileSectorPos;
- }
- else
- {
- if((dwFileSectorPos + dwBytesInSector) > hf->dwDataSize)
- dwBytesInSector = hf->dwDataSize - dwFileSectorPos;
- }
-
- // Copy the data from the offset in the loaded sector to the end of the sector
- dwToCopy = dwBytesInSector - dwBufferOffs;
- if(dwToCopy > dwBytesToRead)
- dwToCopy = dwBytesToRead;
-
- // Copy data from sector buffer into target buffer
- memcpy(pbBuffer, hf->pbFileSector + dwBufferOffs, dwToCopy);
-
- // Update pointers and byte counts
- dwTotalBytesRead += dwToCopy;
- dwFileSectorPos += dwBytesInSector;
- pbBuffer += dwToCopy;
- dwBytesToRead -= dwToCopy;
- }
-
- // Load the whole ("middle") sectors only if there is at least one full sector to be read
- if(dwBytesToRead >= ha->dwSectorSize)
- {
- DWORD dwBlockBytes = dwBytesToRead & ~dwSectorSizeMask;
-
- // Load all sectors to the output buffer
- nError = ReadMpqSectors(hf, pbBuffer, dwFileSectorPos, dwBlockBytes, &dwBytesRead);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Update pointers
- dwTotalBytesRead += dwBytesRead;
- dwFileSectorPos += dwBytesRead;
- pbBuffer += dwBytesRead;
- dwBytesToRead -= dwBytesRead;
- }
-
- // Read the terminating sector
- if(dwBytesToRead > 0)
- {
- DWORD dwToCopy = ha->dwSectorSize;
-
- // Is the file sector already loaded ?
- if(hf->dwSectorOffs != dwFileSectorPos)
- {
- // Load one MPQ sector into archive buffer
- nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesRead);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Remember that the data loaded to the sector have new file offset
- hf->dwSectorOffs = dwFileSectorPos;
- }
-
- // Check number of bytes read
- if(dwToCopy > dwBytesToRead)
- dwToCopy = dwBytesToRead;
-
- // Copy the data from the cached last sector to the caller's buffer
- memcpy(pbBuffer, hf->pbFileSector, dwToCopy);
-
- // Update pointers
- dwTotalBytesRead += dwToCopy;
- }
-
- // Store total number of bytes read to the caller
- *pdwBytesRead = dwTotalBytesRead;
- return ERROR_SUCCESS;
-}
-
-static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
-{
- DWORD dwBytesToRead = dwToRead;
- DWORD dwBytesRead = 0;
- int nError = ERROR_SUCCESS;
-
- // Make sure that the patch file is loaded completely
- if(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;
-
- // Read the file data
- if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
- nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->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;
-
- // Patch the file data
- if(nError == ERROR_SUCCESS)
- nError = PatchFileData(hf);
-
- // Reset number of bytes read to zero
- dwBytesRead = 0;
- }
-
- // If there is something to read, do it
- if(nError == ERROR_SUCCESS)
- {
- if(dwFilePos < hf->cbFileData)
- {
- // Make sure we don't copy more than file size
- if((dwFilePos + dwToRead) > hf->cbFileData)
- dwToRead = hf->cbFileData - dwFilePos;
-
- // Copy the appropriate amount of the file data to the caller's buffer
- memcpy(pvBuffer, hf->pbFileData + dwFilePos, dwToRead);
- dwBytesRead = dwToRead;
- }
-
- // Set the proper error code
- nError = (dwBytesRead == dwBytesToRead) ? ERROR_SUCCESS : ERROR_HANDLE_EOF;
- }
-
- // Give the result to the caller
- if(pdwBytesRead != NULL)
- *pdwBytesRead = dwBytesRead;
- return nError;
-}
-
-static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
-{
- ULONGLONG FilePosition1 = dwFilePos;
- ULONGLONG FilePosition2;
- DWORD dwBytesRead = 0;
- int nError = ERROR_SUCCESS;
-
- assert(hf->pStream != NULL);
-
- // Because stream I/O functions are designed to read
- // "all or nothing", we compare file position before and after,
- // and if they differ, we assume that number of bytes read
- // is the difference between them
-
- if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead))
- {
- // If not all bytes have been read, then return the number of bytes read
- if((nError = GetLastError()) == ERROR_HANDLE_EOF)
- {
- FileStream_GetPos(hf->pStream, &FilePosition2);
- dwBytesRead = (DWORD)(FilePosition2 - FilePosition1);
- }
- }
- else
- {
- dwBytesRead = dwToRead;
- }
-
- *pdwBytesRead = dwBytesRead;
- return nError;
-}
-
-//-----------------------------------------------------------------------------
-// SFileReadFile
-
-bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped)
-{
- TMPQFile * hf = (TMPQFile *)hFile;
- DWORD dwBytesRead = 0; // Number of bytes read
- int nError = ERROR_SUCCESS;
-
- // Keep compilers happy
- lpOverlapped = lpOverlapped;
-
- // Check valid parameters
- if(!IsValidFileHandle(hFile))
- {
- SetLastError(ERROR_INVALID_HANDLE);
- return false;
- }
-
- if(pvBuffer == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return false;
- }
-
- // If the file is local file, read the data directly from the stream
- if(hf->pStream != NULL)
- {
- nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
- }
-
- // If the file is a patch file, we have to read it special way
- else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
- {
- nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
- }
-
- // If the archive is a MPK archive, we need special way to read the file
- else if(hf->ha->dwSubType == MPQ_SUBTYPE_MPK)
- {
- nError = ReadMpkFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
- }
-
- // If the file is single unit file, redirect it to read file
- else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
- {
- nError = ReadMpqFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
- }
-
- // Otherwise read it as sector based MPQ file
- else
- {
- nError = ReadMpqFileSectorFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
- }
-
- // Increment the file position
- hf->dwFilePos += dwBytesRead;
-
- // Give the caller the number of bytes read
- if(pdwRead != NULL)
- *pdwRead = dwBytesRead;
-
- // If the read operation succeeded, but not full number of bytes was read,
- // set the last error to ERROR_HANDLE_EOF
- if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead))
- nError = ERROR_HANDLE_EOF;
-
- // If something failed, set the last error value
- if(nError != ERROR_SUCCESS)
- SetLastError(nError);
- return (nError == ERROR_SUCCESS);
-}
-
-//-----------------------------------------------------------------------------
-// SFileGetFileSize
-
-DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh)
-{
- ULONGLONG FileSize;
- TMPQFile * hf = (TMPQFile *)hFile;
-
- // Validate the file handle before we go on
- if(IsValidFileHandle(hFile))
- {
- // Make sure that the variable is initialized
- FileSize = 0;
-
- // If the file is patched file, we have to get the size of the last version
- if(hf->hfPatchFile != NULL)
- {
- // Walk through the entire patch chain, take the last version
- while(hf != NULL)
+ case SFileMpqHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- // Get the size of the currently pointed version
- FileSize = hf->pFileEntry->dwFileSize;
-
- // Move to the next patch file in the hierarchy
- hf = hf->hfPatchFile;
+ ByteOffset = ha->MpqPos;
+ cbSrcFileInfo = ha->pHeader->dwHeaderSize;
+ nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
}
- }
- else
- {
- // Is it a local file ?
- if(hf->pStream != NULL)
+ break;
+
+ case SFileMpqHetTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- FileStream_GetSize(hf->pStream, &FileSize);
+ pvSrcFileInfo = &ha->pHeader->HetTablePos64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
- else
+ break;
+
+ case SFileMpqHetTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- FileSize = hf->dwDataSize;
+ pvSrcFileInfo = &ha->pHeader->HetTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
- }
-
- // If opened from archive, return file size
- if(pdwFileSizeHigh != NULL)
- *pdwFileSizeHigh = (DWORD)(FileSize >> 32);
- return (DWORD)FileSize;
- }
-
- SetLastError(ERROR_INVALID_HANDLE);
- return SFILE_INVALID_SIZE;
-}
-
-DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod)
-{
- TMPQFile * hf = (TMPQFile *)hFile;
- ULONGLONG FilePosition;
- ULONGLONG MoveOffset;
- DWORD dwFilePosHi;
-
- // If the hFile is not a valid file handle, return an error.
- if(!IsValidFileHandle(hFile))
- {
- SetLastError(ERROR_INVALID_HANDLE);
- return SFILE_INVALID_POS;
- }
-
- // Get the relative point where to move from
- switch(dwMoveMethod)
- {
- case FILE_BEGIN:
- FilePosition = 0;
break;
- case FILE_CURRENT:
- if(hf->pStream != NULL)
+ case SFileMpqHetHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- FileStream_GetPos(hf->pStream, &FilePosition);
+ pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->HetTablePos64, (size_t)ha->pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
+ cbSrcFileInfo = sizeof(TMPQHetHeader);
+ nInfoType = SFILE_INFO_TYPE_ALLOCATED;
}
- else
+ break;
+
+ case SFileMpqHetTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- FilePosition = hf->dwFilePos;
+ pvSrcFileInfo = LoadHetTable(ha);
+ cbSrcFileInfo = sizeof(void *);
+ nInfoType = SFILE_INFO_TYPE_TABLE_POINTER;
}
break;
- case FILE_END:
- if(hf->pStream != NULL)
+ case SFileMpqBetTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- FileStream_GetSize(hf->pStream, &FilePosition);
+ pvSrcFileInfo = &ha->pHeader->BetTablePos64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
- else
+ break;
+
+ case SFileMpqBetTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- FilePosition = SFileGetFileSize(hFile, NULL);
+ pvSrcFileInfo = &ha->pHeader->BetTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- default:
- SetLastError(ERROR_INVALID_PARAMETER);
- return SFILE_INVALID_POS;
- }
-
- // Now get the move offset. Note that both values form
- // a signed 64-bit value (a file pointer can be moved backwards)
- if(plFilePosHigh != NULL)
- dwFilePosHi = *plFilePosHigh;
- else
- dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0;
- MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos);
-
- // Now calculate the new file pointer
- // Do not allow the file pointer to go before the begin of the file
- FilePosition += MoveOffset;
- if(FilePosition < 0)
- FilePosition = 0;
-
- // Now apply the file pointer to the file
- if(hf->pStream != NULL)
- {
- // Apply the new file position
- if(!FileStream_Read(hf->pStream, &FilePosition, NULL, 0))
- return SFILE_INVALID_POS;
-
- // Return the new file position
- if(plFilePosHigh != NULL)
- *plFilePosHigh = (LONG)(FilePosition >> 32);
- return (DWORD)FilePosition;
- }
- else
- {
- // Files in MPQ can't be bigger than 4 GB.
- // We don't allow to go past 4 GB
- if(FilePosition >> 32)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return SFILE_INVALID_POS;
- }
-
- // Change the file position
- hf->dwFilePos = (DWORD)FilePosition;
-
- // Return the new file position
- if(plFilePosHigh != NULL)
- *plFilePosHigh = 0;
- return (DWORD)FilePosition;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Tries to retrieve the file name
+ case SFileMpqBetHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->BetTablePos64, (size_t)ha->pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE);
+ if(pvSrcFileInfo != NULL)
+ {
+ // It is allowed for the caller to only require BET header.
+ cbSrcFileInfo = sizeof(TMPQBetHeader) + ((TMPQBetHeader *)pvSrcFileInfo)->dwFlagCount * sizeof(DWORD);
+ if(cbFileInfo == sizeof(TMPQBetHeader))
+ cbSrcFileInfo = sizeof(TMPQBetHeader);
+ nInfoType = SFILE_INFO_TYPE_ALLOCATED;
+ }
+ }
+ break;
-struct TFileHeader2Ext
-{
- DWORD dwOffset00Data; // Required data at offset 00 (32-bits)
- DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored
- DWORD dwOffset04Data; // Required data at offset 04 (32-bits)
- DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored
- const char * szExt; // Supplied extension, if the condition is true
-};
+ case SFileMpqBetTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = LoadBetTable(ha);
+ cbSrcFileInfo = sizeof(void *);
+ nInfoType = SFILE_INFO_TYPE_TABLE_POINTER;
+ }
+ break;
-static TFileHeader2Ext data2ext[] =
-{
- {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files
- {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files
- {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A')
- {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF'
- {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2'
- {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new)
- {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I
- {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II
- {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm'
- {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM
- {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files
- {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures
- {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2)
- {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files
- {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps
- {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8'
- {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2
- {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc
- {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders
- {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image
- {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension
- {0, 0, 0, 0, NULL} // Terminator
-};
+ case SFileMpqHashTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ Int64Value = MAKE_OFFSET64(ha->pHeader->wHashTablePosHi, ha->pHeader->dwHashTablePos);
+ pvSrcFileInfo = &Int64Value;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
-static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName)
-{
- TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
- DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file
- DWORD dwBytesRead = 0;
- DWORD dwFilePos; // Saved file position
+ case SFileMpqHashTableSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HashTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // Read the first 2 DWORDs bytes from the file
- dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT);
- SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL);
- SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN);
+ case SFileMpqHashTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwHashTableSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // If we read at least 8 bytes
- if(dwBytesRead == sizeof(FirstBytes))
- {
- // Make sure that the array is properly BSWAP-ed
- BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));
+ case SFileMpqHashTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
+ pvSrcFileInfo = ha->pHashTable;
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // Try to guess file extension from those 2 DWORDs
- for(size_t i = 0; data2ext[i].szExt != NULL; i++)
- {
- if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data &&
- (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data)
+ case SFileMpqBlockTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
{
- char szPseudoName[20] = "";
+ Int64Value = MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos);
+ pvSrcFileInfo = &Int64Value;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // Format the pseudo-name
- sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt);
+ case SFileMpqBlockTableSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->BlockTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // Save the pseudo-name in the file entry as well
- AllocateFileName(hf->ha, pFileEntry, szPseudoName);
+ case SFileMpqBlockTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwBlockTableSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // If the caller wants to copy the file name, do it
- if(szFileName != NULL)
- strcpy(szFileName, szPseudoName);
- return ERROR_SUCCESS;
+ case SFileMpqBlockTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ cbSrcFileInfo = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ if(cbFileInfo >= cbSrcFileInfo)
+ pvSrcFileInfo = LoadBlockTable(ha, true);
+ nInfoType = SFILE_INFO_TYPE_ALLOCATED;
}
- }
- }
+ break;
- return ERROR_NOT_SUPPORTED;
-}
+ case SFileMpqHiBlockTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HiBlockTablePos64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
-bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
-{
- TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
- TCHAR * szFileNameT;
- int nError = ERROR_INVALID_HANDLE;
+ case SFileMpqHiBlockTableSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HiBlockTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // Pre-zero the output buffer
- if(szFileName != NULL)
- *szFileName = 0;
+ case SFileMpqHiBlockTable:
+ assert(false);
+ break;
- // Check valid parameters
- if(IsValidFileHandle(hFile))
- {
- TFileEntry * pFileEntry = hf->pFileEntry;
+ case SFileMpqSignatures:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo))
+ {
+ pvSrcFileInfo = &SignatureInfo.SignatureTypes;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
- // For MPQ files, retrieve the file name from the file entry
- if(hf->pStream == NULL)
- {
- if(pFileEntry != NULL)
+ case SFileMpqStrongSignatureOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo))
{
- // If the file name is not there yet, create a pseudo name
- if(pFileEntry->szFileName == NULL)
+ // Is a strong signature present?
+ if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG)
{
- nError = CreatePseudoFileName(hFile, pFileEntry, szFileName);
+ pvSrcFileInfo = &SignatureInfo.EndMpqData;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
- else
+ }
+ break;
+
+ case SFileMpqStrongSignatureSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo))
+ {
+ // Is a strong signature present?
+ if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG)
{
- if(szFileName != NULL)
- strcpy(szFileName, pFileEntry->szFileName);
- nError = ERROR_SUCCESS;
+ dwInt32Value = MPQ_STRONG_SIGNATURE_SIZE + 4;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
}
- }
+ break;
- // For local files, copy the file name from the stream
- else
- {
- if(szFileName != NULL)
+ case SFileMpqStrongSignature:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo))
{
- szFileNameT = FileStream_GetFileName(hf->pStream);
- CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT));
+ // Is a strong signature present?
+ if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG)
+ {
+ pvSrcFileInfo = SignatureInfo.Signature;
+ cbSrcFileInfo = MPQ_STRONG_SIGNATURE_SIZE + 4;
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
}
- nError = ERROR_SUCCESS;
- }
- }
-
- if(nError != ERROR_SUCCESS)
- SetLastError(nError);
- return (nError == ERROR_SUCCESS);
-}
-
-//-----------------------------------------------------------------------------
-// Retrieves an information about an archive or about a file within the archive
-//
-// hMpqOrFile - Handle to an MPQ archive or to a file
-// dwInfoType - Information to obtain
-
-bool WINAPI SFileGetFileInfo(
- HANDLE hMpqOrFile,
- DWORD dwInfoType,
- void * pvFileInfo,
- DWORD cbFileInfo,
- LPDWORD pcbLengthNeeded)
-{
- TMPQArchive * ha = NULL;
- TMPQBlock * pBlockTable = NULL;
- ULONGLONG Int64Value = 0;
- TMPQFile * hf = NULL;
- TCHAR * szPatchChain = NULL;
- void * pvSrcFileInfo = NULL;
- DWORD cbSrcFileInfo = 0;
- DWORD dwInt32Value = 0;
- int nError = ERROR_INVALID_PARAMETER;
+ break;
- switch(dwInfoType)
- {
- case SFILE_INFO_ARCHIVE_NAME:
+ case SFileMpqBitmapOffset:
ha = IsValidMpqHandle(hMpqOrFile);
- if(ha != NULL)
+ if(ha != NULL && ha->pBitmap != NULL)
{
- pvSrcFileInfo = FileStream_GetFileName(ha->pStream);
- cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR);
+ Int64Value = MAKE_OFFSET64(ha->pBitmap->dwMapOffsetHi, ha->pBitmap->dwMapOffsetLo);
+ pvSrcFileInfo = &Int64Value;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive
+ case SFileMpqBitmapSize:
ha = IsValidMpqHandle(hMpqOrFile);
- if(ha != NULL)
+ if(ha != NULL && ha->pBitmap != NULL)
{
- pvSrcFileInfo = &ha->pHeader->dwArchiveSize;
+ pvSrcFileInfo = &ha->dwBitmapSize;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ
+ case SFileMpqBitmap:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- pvSrcFileInfo = &ha->dwMaxFileCount;
- cbSrcFileInfo = sizeof(DWORD);
+ pvSrcFileInfo = ha->pBitmap;
+ cbSrcFileInfo = ha->dwBitmapSize;
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table
+ case SFileMpqArchiveSize64:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- pvSrcFileInfo = &ha->pHeader->dwHashTableSize;
+ pvSrcFileInfo = &ha->pHeader->ArchiveSize64;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table
+ case SFileMpqArchiveSize:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- pvSrcFileInfo = &ha->pHeader->dwBlockTableSize;
+ pvSrcFileInfo = &ha->pHeader->dwArchiveSize;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_SECTOR_SIZE:
+ case SFileMpqMaxFileCount:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- pvSrcFileInfo = &ha->dwSectorSize;
+ pvSrcFileInfo = &ha->dwMaxFileCount;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_HASH_TABLE:
+ case SFileMpqFileTableSize:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- pvSrcFileInfo = ha->pHashTable;
- cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
+ pvSrcFileInfo = &ha->dwFileTableSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_BLOCK_TABLE:
+ case SFileMpqSectorSize:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
- pvSrcFileInfo = pBlockTable = TranslateBlockTable(ha, &Int64Value, NULL);
- cbSrcFileInfo = (DWORD)(Int64Value / sizeof(TMPQBlock));
+ pvSrcFileInfo = &ha->dwSectorSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_NUM_FILES:
+ case SFileMpqNumberOfFiles:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
dwInt32Value = GetMpqFileCount(ha);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqRawChunkSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwRawChunkSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_STREAM_FLAGS:
+ case SFileMpqStreamFlags:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
FileStream_GetFlags(ha->pStream, &dwInt32Value);
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_IS_READ_ONLY:
+ case SFileMpqIsReadOnly:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
{
+ dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY));
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
- dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY));
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_HASH_INDEX:
+ case SFileInfoPatchChain:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL)
+ return GetFilePatchChain(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+ break;
+
+ case SFileInfoFileEntry:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = pFileEntry = hf->pFileEntry;
+ cbSrcFileInfo = sizeof(TFileEntry);
+ if(pFileEntry->szFileName != NULL)
+ cbSrcFileInfo += (DWORD)strlen(pFileEntry->szFileName) + 1;
+ nInfoType = SFILE_INFO_TYPE_FILE_ENTRY;
+ }
+ break;
+
+ case SFileInfoHashEntry:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL)
+ {
+ pvSrcFileInfo = hf->ha->pHashTable + hf->pFileEntry->dwHashIndex;
+ cbSrcFileInfo = sizeof(TMPQHash);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoHashIndex:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
pvSrcFileInfo = &hf->pFileEntry->dwHashIndex;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_CODENAME1:
+ case SFileInfoNameHash1:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL)
{
pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_CODENAME2:
+ case SFileInfoNameHash2:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL)
{
pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoNameHash3:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->FileNameHash;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_LOCALEID:
+ case SFileInfoLocale:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
+ dwInt32Value = hf->pFileEntry->lcLocale;
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
- dwInt32Value = hf->pFileEntry->lcLocale;
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_BLOCKINDEX:
+ case SFileInfoFileIndex:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL && hf->ha != NULL)
{
+ dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable);
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
- dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoByteOffset:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->ByteOffset;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_FILE_SIZE:
+ case SFileInfoFileTime:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
- pvSrcFileInfo = &hf->pFileEntry->dwFileSize;
- cbSrcFileInfo = sizeof(DWORD);
+ pvSrcFileInfo = &hf->pFileEntry->FileTime;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_COMPRESSED_SIZE:
+ case SFileInfoFileSize:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
- pvSrcFileInfo = &hf->pFileEntry->dwCmpSize;
+ pvSrcFileInfo = &hf->pFileEntry->dwFileSize;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_FLAGS:
+ case SFileInfoCompressedSize:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
- pvSrcFileInfo = &hf->pFileEntry->dwFlags;
+ pvSrcFileInfo = &hf->pFileEntry->dwCmpSize;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_POSITION:
+ case SFileInfoFlags:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
- pvSrcFileInfo = &hf->pFileEntry->ByteOffset;
- cbSrcFileInfo = sizeof(ULONGLONG);
+ pvSrcFileInfo = &hf->pFileEntry->dwFlags;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_KEY:
+ case SFileInfoEncryptionKey:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
pvSrcFileInfo = &hf->dwFileKey;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_KEY_UNFIXED:
+ case SFileInfoEncryptionKeyRaw:
hf = IsValidFileHandle(hMpqOrFile);
if(hf != NULL)
{
@@ -1290,52 +750,222 @@ bool WINAPI SFileGetFileInfo(
dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos;
pvSrcFileInfo = &dwInt32Value;
cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
}
break;
- case SFILE_INFO_FILETIME:
- hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL)
- {
- pvSrcFileInfo = &hf->pFileEntry->FileTime;
- cbSrcFileInfo = sizeof(ULONGLONG);
- }
- break;
-
- case SFILE_INFO_PATCH_CHAIN:
- hf = IsValidFileHandle(hMpqOrFile);
- if(hf != NULL)
- pvSrcFileInfo = szPatchChain = GetFilePatchChain(hf, &cbSrcFileInfo);
- break;
+ default: // Invalid info class
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
}
- // Check if one of the cases case yielded a result
- if(pvSrcFileInfo != NULL && pvFileInfo != NULL)
+ // If we validated the handle and info class, give as much info as possible
+ if(nInfoType != SFILE_INFO_TYPE_UNKNOWN)
{
- // Give the length needed
+ // Give the length needed, if wanted
if(pcbLengthNeeded != NULL)
pcbLengthNeeded[0] = cbSrcFileInfo;
- // Verify if we have enough space in the output buffer
- if(cbSrcFileInfo <= cbFileInfo)
+ // If the caller entered an output buffer, the output size must also be entered
+ if(pvFileInfo != NULL && cbFileInfo != 0)
{
- memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo);
- nError = ERROR_SUCCESS;
+ // Check if there is enough space in the output buffer
+ if(cbSrcFileInfo <= cbFileInfo)
+ {
+ switch(nInfoType)
+ {
+ case SFILE_INFO_TYPE_DIRECT_POINTER:
+ case SFILE_INFO_TYPE_ALLOCATED:
+ memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo);
+ nError = ERROR_SUCCESS;
+ break;
+
+ case SFILE_INFO_TYPE_READ_FROM_FILE:
+ if(FileStream_Read(ha->pStream, &ByteOffset, pvFileInfo, cbSrcFileInfo))
+ nError = ERROR_SUCCESS;
+ break;
+
+ case SFILE_INFO_TYPE_TABLE_POINTER:
+ *(void **)pvFileInfo = pvSrcFileInfo;
+ pvSrcFileInfo = NULL;
+ nError = ERROR_SUCCESS;
+ break;
+
+ case SFILE_INFO_TYPE_FILE_ENTRY:
+ assert(pFileEntry != NULL);
+ ConvertFileEntryToSelfRelative((TFileEntry *)pvFileInfo, pFileEntry);
+ nError = ERROR_SUCCESS;
+ break;
+ }
+ }
+ else
+ {
+ nError = ERROR_INSUFFICIENT_BUFFER;
+ }
}
else
{
- nError = ERROR_INSUFFICIENT_BUFFER;
+ nError = ERROR_SUCCESS;
}
- }
- // Free the allocated buffers, if any
- if(szPatchChain != NULL)
- STORM_FREE(szPatchChain);
- if(pBlockTable != NULL)
- STORM_FREE(pBlockTable);
+ // Free the file info if needed
+ if(nInfoType == SFILE_INFO_TYPE_ALLOCATED && pvSrcFileInfo != NULL)
+ STORM_FREE(pvSrcFileInfo);
+ if(nInfoType == SFILE_INFO_TYPE_TABLE_POINTER && pvSrcFileInfo != NULL)
+ SFileFreeFileInfo(pvSrcFileInfo, InfoClass);
+ }
// Set the last error value, if needed
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return (nError == ERROR_SUCCESS);
}
+
+bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass)
+{
+ switch(InfoClass)
+ {
+ case SFileMpqHetTable:
+ FreeHetTable((TMPQHetTable *)pvFileInfo);
+ return true;
+
+ case SFileMpqBetTable:
+ FreeBetTable((TMPQBetTable *)pvFileInfo);
+ return true;
+ }
+
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Tries to retrieve the file name
+
+struct TFileHeader2Ext
+{
+ DWORD dwOffset00Data; // Required data at offset 00 (32-bits)
+ DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored
+ DWORD dwOffset04Data; // Required data at offset 04 (32-bits)
+ DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored
+ const char * szExt; // Supplied extension, if the condition is true
+};
+
+static TFileHeader2Ext data2ext[] =
+{
+ {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files
+ {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files
+ {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A')
+ {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF'
+ {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2'
+ {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new)
+ {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I
+ {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II
+ {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm'
+ {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM
+ {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files
+ {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures
+ {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2)
+ {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files
+ {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps
+ {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8'
+ {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2
+ {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc
+ {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders
+ {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image
+ {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension
+ {0, 0, 0, 0, NULL} // Terminator
+};
+
+static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName)
+{
+ TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
+ DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file
+ DWORD dwBytesRead = 0;
+ DWORD dwFilePos; // Saved file position
+
+ // Read the first 2 DWORDs bytes from the file
+ dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT);
+ SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL);
+ SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN);
+
+ // If we read at least 8 bytes
+ if(dwBytesRead == sizeof(FirstBytes))
+ {
+ // Make sure that the array is properly BSWAP-ed
+ BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));
+
+ // Try to guess file extension from those 2 DWORDs
+ for(size_t i = 0; data2ext[i].szExt != NULL; i++)
+ {
+ if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data &&
+ (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data)
+ {
+ char szPseudoName[20] = "";
+
+ // Format the pseudo-name
+ sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt);
+
+ // Save the pseudo-name in the file entry as well
+ AllocateFileName(hf->ha, pFileEntry, szPseudoName);
+
+ // If the caller wants to copy the file name, do it
+ if(szFileName != NULL)
+ strcpy(szFileName, szPseudoName);
+ return ERROR_SUCCESS;
+ }
+ }
+ }
+
+ return ERROR_NOT_SUPPORTED;
+}
+
+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))
+ {
+ TFileEntry * pFileEntry = hf->pFileEntry;
+
+ // For MPQ files, retrieve the file name from the file entry
+ if(hf->pStream == NULL)
+ {
+ if(pFileEntry != NULL)
+ {
+ // 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;
+ }
+ }
+ }
+
+ // For local files, copy the file name from the stream
+ else
+ {
+ if(szFileName != NULL)
+ {
+ const TCHAR * szStreamName = FileStream_GetFileName(hf->pStream);
+ CopyFileName(szFileName, szStreamName, _tcslen(szStreamName));
+ }
+ nError = ERROR_SUCCESS;
+ }
+ }
+
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp
index 2044125..f98c92b 100644
--- a/src/SFileListFile.cpp
+++ b/src/SFileListFile.cpp
@@ -35,6 +35,18 @@ struct TListFileCache
//-----------------------------------------------------------------------------
// Local functions (cache)
+static char * CopyListLine(char * szListLine, const char * szFileName)
+{
+ // Copy the string
+ while(szFileName[0] != 0)
+ *szListLine++ = *szFileName++;
+
+ // Append the end-of-line
+ *szListLine++ = 0x0D;
+ *szListLine++ = 0x0A;
+ return szListLine;
+}
+
static bool FreeListFileCache(TListFileCache * pCache)
{
// Valid parameter check
@@ -216,19 +228,91 @@ static int CompareFileNodes(const void * p1, const void * p2)
return _stricmp(szFileName1, szFileName2);
}
-static int WriteListFileLine(
- TMPQFile * hf,
- const char * szLine)
+static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile)
{
- char szNewLine[2] = {0x0D, 0x0A};
- size_t nLength = strlen(szLine);
- int nError;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ char ** SortTable = NULL;
+ char * szListFile = NULL;
+ char * szListLine;
+ size_t nFileNodes = 0;
+ size_t cbListFile = 0;
+ size_t nIndex0;
+ size_t nIndex1;
- nError = SFileAddFile_Write(hf, szLine, (DWORD)nLength, MPQ_COMPRESSION_ZLIB);
- if(nError != ERROR_SUCCESS)
- return nError;
+ // Allocate the table for sorting listfile
+ SortTable = STORM_ALLOC(char*, ha->dwFileTableSize);
+ if(SortTable == NULL)
+ return NULL;
+
+ // Construct the sort table
+ // Note: in MPQs with multiple locale versions of the same file,
+ // this code causes adding multiple listfile entries.
+ // They will get removed after the listfile sorting
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Only take existing items
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL)
+ {
+ // Ignore pseudo-names and internal names
+ if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName))
+ {
+ SortTable[nFileNodes++] = pFileEntry->szFileName;
+ }
+ }
+ }
+
+ // Remove duplicities
+ if(nFileNodes > 0)
+ {
+ // Sort the table
+ qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes);
+
+ // Count the 0-th item
+ cbListFile += strlen(SortTable[0]) + 2;
+
+ // Walk through the items and only use the ones that are not duplicated
+ for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++)
+ {
+ // If the next file node is different, we will include it to the result listfile
+ if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0)
+ {
+ cbListFile += strlen(SortTable[nIndex1]) + 2;
+ nIndex0 = nIndex1;
+ }
+ }
+
+ // Now allocate buffer for the entire listfile
+ szListFile = szListLine = STORM_ALLOC(char, cbListFile + 1);
+ if(szListFile != NULL)
+ {
+ // Copy the 0-th item
+ szListLine = CopyListLine(szListLine, SortTable[0]);
+
+ // Walk through the items and only use the ones that are not duplicated
+ for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++)
+ {
+ // If the next file node is different, we will include it to the result listfile
+ if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0)
+ {
+ // Copy the listfile line
+ szListLine = CopyListLine(szListLine, SortTable[nIndex1]);
+ nIndex0 = nIndex1;
+ }
+ }
+
+ // Sanity check - does the size match?
+ assert((size_t)(szListLine - szListFile) == cbListFile);
+ }
+ }
+
+ // Free the sort table
+ STORM_FREE(SortTable);
- return SFileAddFile_Write(hf, szNewLine, sizeof(szNewLine), MPQ_COMPRESSION_ZLIB);
+ // Give away the listfile
+ if(pcbListFile != NULL)
+ *pcbListFile = (DWORD)cbListFile;
+ return (LPBYTE)szListFile;
}
//-----------------------------------------------------------------------------
@@ -252,7 +336,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil
if(pFileEntry != NULL)
{
// Allocate file name for the file entry
- AllocateFileName(pFileEntry, szFileName);
+ AllocateFileName(ha, pFileEntry, szFileName);
bNameEntryCreated = true;
}
@@ -272,7 +356,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil
if(pHash->dwBlockIndex < pHeader->dwBlockTableSize)
{
// Allocate file name for the file entry
- AllocateFileName(ha->pFileTable + pHash->dwBlockIndex, szFileName);
+ AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName);
bNameEntryCreated = true;
}
@@ -284,123 +368,65 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil
return ERROR_CAN_NOT_COMPLETE;
}
-// Saves the whole listfile into the MPQ.
+// Saves the whole listfile to the MPQ
int SListFileSaveToMpq(TMPQArchive * ha)
{
- TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- TFileEntry * pFileEntry;
TMPQFile * hf = NULL;
- char * szPrevItem;
- char ** SortTable = NULL;
- DWORD dwFileSize = 0;
- size_t nFileNodes = 0;
- size_t i;
+ LPBYTE pbListFile;
+ DWORD cbListFile = 0;
int nError = ERROR_SUCCESS;
- // Allocate the table for sorting listfile
- SortTable = STORM_ALLOC(char*, ha->dwFileTableSize);
- if(SortTable == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
+ // Only save (listfile) if we should do so
+ if(ha->dwFileFlags1 == 0 || ha->dwMaxFileCount == 0)
+ return ERROR_SUCCESS;
- // Construct the sort table
- // Note: in MPQs with multiple locale versions of the same file,
- // this code causes adding multiple listfile entries.
- // Since those MPQs were last time used in Starcraft,
- // we leave it as it is.
- for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
- {
- // Only take existing items
- if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL)
- {
- // Ignore pseudo-names
- if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName))
- {
- SortTable[nFileNodes++] = pFileEntry->szFileName;
- }
- }
- }
+ // At this point, we expect to have at least one reserved entry in the file table
+ assert(ha->dwReservedFiles >= 1);
+
+ // Create the raw data that is to be written to (listfile)
+ // Note: Creating the raw data before the (listfile) has been created in the MPQ
+ // causes that the name of the listfile will not be included in the listfile itself.
+ // That is OK, because (listfile) in Blizzard MPQs does not contain it either.
+ pbListFile = CreateListFile(ha, &cbListFile);
- // Sort the table
- qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes);
+ // Now we decrement the number of reserved files.
+ // This frees one slot in the file table, so the subsequent file create operation should succeed
+ // This must happen even if the listfile cannot be created
+ ha->dwReservedFiles--;
- // Now parse the table of file names again - remove duplicates
- // and count file size.
- if(nFileNodes != 0)
+ // If the listfile create succeeded, we write it to the MPQ
+ if(pbListFile != NULL)
{
- // Count the 0-th item
- dwFileSize += (DWORD)strlen(SortTable[0]) + 2;
- szPrevItem = SortTable[0];
-
- // Count all next items
- for(i = 1; i < nFileNodes; i++)
- {
- // If the item is different from the previous one, include its size to the file size
- if(_stricmp(SortTable[i], szPrevItem))
- {
- dwFileSize += (DWORD)strlen(SortTable[i]) + 2;
- szPrevItem = SortTable[i];
- }
- }
+ // We expect it to be nonzero size
+ assert(cbListFile != 0);
- // Determine the flags for (listfile)
- if(ha->dwFileFlags1 == 0)
- ha->dwFileFlags1 = GetDefaultSpecialFileFlags(ha, dwFileSize);
+ // Determine the real flags for (listfile)
+ if(ha->dwFileFlags1 == MPQ_FILE_EXISTS)
+ ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion);
// Create the listfile in the MPQ
nError = SFileAddFile_Init(ha, LISTFILE_NAME,
0,
- dwFileSize,
+ cbListFile,
LANG_NEUTRAL,
ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING,
&hf);
- // Add all file names
+
+ // Write the listfile raw data to it
if(nError == ERROR_SUCCESS)
{
- // Each name is followed by newline ("\x0D\x0A")
- szPrevItem = SortTable[0];
- nError = WriteListFileLine(hf, SortTable[0]);
+ // Write the content of the listfile to the MPQ
+ nError = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB);
+ SFileAddFile_Finish(hf);
- // Count all next items
- for(i = 1; i < nFileNodes; i++)
- {
- // If the item is the same like the last one, skip it
- if(_stricmp(SortTable[i], szPrevItem))
- {
- WriteListFileLine(hf, SortTable[i]);
- szPrevItem = SortTable[i];
- }
- }
+ // Clear the invalidate flag
+ ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID;
}
- }
- else
- {
- // Create the listfile in the MPQ
- dwFileSize = (DWORD)strlen(LISTFILE_NAME) + 2;
- nError = SFileAddFile_Init(ha, LISTFILE_NAME,
- 0,
- dwFileSize,
- LANG_NEUTRAL,
- MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING,
- &hf);
- // Just add "(listfile)" there
- if(nError == ERROR_SUCCESS)
- {
- WriteListFileLine(hf, LISTFILE_NAME);
- }
+ // Free the listfile buffer
+ STORM_FREE(pbListFile);
}
- // Finalize the file in the MPQ
- if(hf != NULL)
- {
- SFileAddFile_Finish(hf);
- }
-
- // Free buffers
- if(nError == ERROR_SUCCESS)
- ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID;
- if(SortTable != NULL)
- STORM_FREE(SortTable);
return nError;
}
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index bf8d19e..b05cff3 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -32,6 +32,33 @@ static bool IsAviFile(void * pvFileBegin)
return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C);
}
+static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData)
+{
+ TMPQUserData * pUserData;
+
+ // BSWAP the source data and copy them to our buffer
+ BSWAP_ARRAY32_UNSIGNED(&pvUserData, sizeof(TMPQUserData));
+ pUserData = (TMPQUserData *)pvUserData;
+
+ // Check the sizes
+ if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs)
+ {
+ // Move to the position given by the userdata
+ ByteOffset += pUserData->dwHeaderOffs;
+
+ // The MPQ header should be within range of the file size
+ if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize)
+ {
+ // Note: We should verify if there is the MPQ header.
+ // However, the header could be at any position below that
+ // that is multiplier of 0x200
+ return (TMPQUserData *)pvUserData;
+ }
+ }
+
+ return NULL;
+}
+
static TFileBitmap * CreateFileBitmap(TMPQArchive * ha, TMPQBitmap * pMpqBitmap, bool bFileIsComplete)
{
TFileBitmap * pBitmap;
@@ -144,6 +171,7 @@ bool WINAPI SFileOpenArchive(
DWORD dwFlags,
HANDLE * phMpq)
{
+ TMPQUserData * pUserData;
TFileStream * pStream = NULL; // Open file stream
TMPQArchive * ha = NULL; // Archive handle
TFileEntry * pFileEntry;
@@ -166,11 +194,18 @@ bool WINAPI SFileOpenArchive(
if(pStream == NULL)
nError = GetLastError();
}
-
- // Allocate the MPQhandle
+
+ // Check the file size. There must be at least 0x20 bytes
if(nError == ERROR_SUCCESS)
{
FileStream_GetSize(pStream, &FileSize);
+ if(FileSize < MPQ_HEADER_SIZE_V1)
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Allocate the MPQhandle
+ if(nError == ERROR_SUCCESS)
+ {
if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
@@ -219,18 +254,18 @@ bool WINAPI SFileOpenArchive(
// If there is the MPQ user data signature, process it
dwHeaderID = BSWAP_INT32_UNSIGNED(*(LPDWORD)ha->HeaderData);
- if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL)
+ if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
{
- // Ignore the MPQ user data completely if the caller wants to open the MPQ as V1.0
- if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
+ // Verify if this looks like a valid user data
+ pUserData = IsValidMpqUserData(SearchPos, FileSize, ha->HeaderData);
+ if(pUserData != NULL)
{
// Fill the user data header
+ ha->UserDataPos = SearchPos;
ha->pUserData = &ha->UserData;
- memcpy(ha->pUserData, ha->HeaderData, sizeof(TMPQUserData));
- BSWAP_TMPQUSERDATA(ha->pUserData);
+ memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData));
- // Remember the position of the user data and continue search
- ha->UserDataPos = SearchPos;
+ // Continue searching from that position
SearchPos += ha->pUserData->dwHeaderOffs;
continue;
}
@@ -266,9 +301,11 @@ bool WINAPI SFileOpenArchive(
// Did we identify one of the supported headers?
if(nError == ERROR_SUCCESS)
{
- // Set the position of user data, header and file offset of the header
+ // Set the user data position to the MPQ header, if none
if(ha->pUserData == NULL)
ha->UserDataPos = SearchPos;
+
+ // Set the position of the MPQ header
ha->pHeader = (TMPQHeader *)ha->HeaderData;
ha->MpqPos = SearchPos;
@@ -285,7 +322,7 @@ bool WINAPI SFileOpenArchive(
// DumpMpqHeader(ha->pHeader);
// W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data,
- // and probably ignores the MPQ format version as well. The trick is to
+ // and ignores the MPQ format version as well. The trick is to
// fake MPQ format 2, with an improper hi-word position of hash table and block table
// We can overcome such protectors by forcing opening the archive as MPQ v 1.0
if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
@@ -336,13 +373,13 @@ bool WINAPI SFileOpenArchive(
// the block table, BET table, hi-block table, (attributes) and (listfile).
if(nError == ERROR_SUCCESS)
{
- nError = BuildFileTable(ha, FileSize);
+ nError = BuildFileTable(ha);
}
// Verify the file table, if no kind of protection was detected
if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0)
{
- TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
ULONGLONG RawFilePos;
// Parse all file entries
@@ -379,10 +416,11 @@ bool WINAPI SFileOpenArchive(
// Save the flags for (listfile)
pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL);
if(pFileEntry != NULL)
+ {
+ // Ignore result of the operation. (listfile) is optional.
+ SFileAddListFile((HANDLE)ha, NULL);
ha->dwFileFlags1 = pFileEntry->dwFlags;
-
- // Ignore result of the operation. (listfile) is optional.
- SFileAddListFile((HANDLE)ha, NULL);
+ }
}
// Load the "(attributes)" file and merge it to the file table
@@ -391,10 +429,11 @@ bool WINAPI SFileOpenArchive(
// Save the flags for (attributes)
pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
if(pFileEntry != NULL)
+ {
+ // Ignore result of the operation. (attributes) is optional.
+ SAttrLoadAttributes(ha);
ha->dwFileFlags2 = pFileEntry->dwFlags;
-
- // Ignore result of the operation. (attributes) is optional.
- SAttrLoadAttributes(ha);
+ }
}
// Cleanup and exit
@@ -436,12 +475,15 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq)
int nError;
// Do nothing if 'hMpq' is bad parameter
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
+ // Indicate that we are saving MPQ internal structures
+ ha->dwFlags |= MPQ_FLAG_SAVING_TABLES;
+
// If the (listfile) has been invalidated, save it
if(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)
{
@@ -466,6 +508,9 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq)
nResultError = nError;
}
+ // We are no longer saving internal MPQ structures
+ ha->dwFlags &= ~MPQ_FLAG_SAVING_TABLES;
+
// Return the error
if(nResultError != ERROR_SUCCESS)
SetLastError(nResultError);
diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp
index 4180cd7..c2cf5d4 100644
--- a/src/SFileOpenFileEx.cpp
+++ b/src/SFileOpenFileEx.cpp
@@ -16,11 +16,19 @@
/* Local functions */
/*****************************************************************************/
-static const char * GetPrefixedName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
+static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
{
if(ha->cchPatchPrefix != 0)
{
+ // Copy the patch prefix
memcpy(szBuffer, ha->szPatchPrefix, ha->cchPatchPrefix);
+
+ // The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY"
+ // We need to remove the "Oldworld\\" prefix
+ if(!_strnicmp(szFileName, "OldWorld\\", 9))
+ szFileName += 9;
+
+ // Copy the rest of the name
strcpy(szBuffer + ha->cchPatchPrefix, szFileName);
szFileName = szBuffer;
}
@@ -61,10 +69,11 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HANDLE * phFile)
{
+ TMPQArchive * haBase = NULL;
TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TFileEntry * pFileEntry;
TMPQFile * hfPatch; // Pointer to patch file
TMPQFile * hfBase = NULL; // Pointer to base open file
- TMPQFile * hfLast = NULL; // The highest file in the chain that is not patch file
TMPQFile * hf = NULL;
HANDLE hPatchFile;
char szPrefixBuffer[MAX_PATH];
@@ -72,66 +81,52 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN
// Keep this flag here for future updates
dwReserved = dwReserved;
- // First of all, try to open the original version of the file in any of the patch chain
+ // First of all, find the latest archive where the file is in base version
+ // (i.e. where the original, unpatched version of the file exists)
while(ha != NULL)
{
- // Prepare the file name with a correct prefix
- if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
- {
- // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
- if((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
- {
- hf = hfLast = hfBase;
- break;
- }
-
- SFileCloseFile((HANDLE)hfBase);
- }
+ // If the file is there, then we remember the archive
+ pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), 0);
+ if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
+ haBase = ha;
- // Move to the next file in the patch chain
+ // Move to the patch archive
ha = ha->haPatch;
}
- // If we couldn't find the file in any of the patches, it doesn't exist
- if(hf == NULL)
+ // If we couldn't find the base file in any of the patches, it doesn't exist
+ if((ha = haBase) == NULL)
{
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
}
- // Now keep going in the patch chain and open every patch file that is there
- for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch)
+ // Now open the base file
+ if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
{
- // Prepare the file name with a correct prefix
- if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
+ // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
+ assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0);
+ hf = hfBase;
+
+ // Now open all patches and attach them on top of the base file
+ for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch)
{
- // Remember the new version
- hfPatch = (TMPQFile *)hPatchFile;
+ // Prepare the file name with a correct prefix
+ if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
+ {
+ // Remember the new version
+ hfPatch = (TMPQFile *)hPatchFile;
- // If we encountered a full replacement of the file,
- // we have to remember the highest full file
- if((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
- hfLast = hfPatch;
+ // We should not find patch file
+ assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0);
- // Set current patch to base file and move on
- hf->hfPatchFile = hfPatch;
- hf = hfPatch;
+ // Attach the patch to the base file
+ hf->hfPatch = hfPatch;
+ hf = hfPatch;
+ }
}
}
- // Now we need to free all files that are below the highest unpatched version
- while(hfBase != hfLast)
- {
- TMPQFile * hfNext = hfBase->hfPatchFile;
-
- // Free the file below
- hfBase->hfPatchFile = NULL;
- FreeMPQFile(hfBase);
-
- // Move the base to the next file
- hfBase = hfNext;
- }
-
// Give the updated base MPQ
if(phFile != NULL)
*phFile = (HANDLE)hfBase;
@@ -163,7 +158,7 @@ int WINAPI SFileEnumLocales(
DWORD dwLocales = 0;
// Test the parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
return ERROR_INVALID_HANDLE;
if(szFileName == NULL || *szFileName == 0)
return ERROR_INVALID_PARAMETER;
@@ -236,7 +231,7 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
bool bIsPseudoName;
int nError = ERROR_SUCCESS;
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(szFileName == NULL || *szFileName == 0)
nError = ERROR_INVALID_PARAMETER;
@@ -251,7 +246,7 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
while(ha != NULL)
{
// Verify presence of the file
- pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), lcFileLocale)
+ 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)
@@ -301,7 +296,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
case SFILE_OPEN_FROM_MPQ:
case SFILE_OPEN_BASE_FILE:
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
{
nError = ERROR_INVALID_HANDLE;
break;
@@ -369,6 +364,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
if(nError != ERROR_SUCCESS)
{
SetLastError(nError);
+ *phFile = NULL;
return false;
}
}
@@ -409,7 +405,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
if(bOpenByIndex == false)
{
// If there is no file name yet, allocate it
- AllocateFileName(pFileEntry, szFileName);
+ AllocateFileName(ha, pFileEntry, szFileName);
// If the file is encrypted, we should detect the file key
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
@@ -454,7 +450,7 @@ bool WINAPI SFileCloseFile(HANDLE hFile)
{
TMPQFile * hf = (TMPQFile *)hFile;
- if(!IsValidFileHandle(hf))
+ if(!IsValidFileHandle(hFile))
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp
index bfb4100..7f67749 100644
--- a/src/SFilePatchArchives.cpp
+++ b/src/SFilePatchArchives.cpp
@@ -34,7 +34,7 @@ static bool GetDefaultPatchPrefix(
const TCHAR * szDash;
// Ensure that both names are plain names
- szBaseMpqName = GetPlainFileNameT(szBaseMpqName);
+ szBaseMpqName = GetPlainFileName(szBaseMpqName);
// Patch prefix is for the Cataclysm MPQs, whose names
// are like "locale-enGB.MPQ" or "speech-enGB.MPQ"
@@ -431,7 +431,7 @@ int PatchFileData(TMPQFile * hf)
int nError = ERROR_SUCCESS;
// Move to the first patch
- hf = hf->hfPatchFile;
+ hf = hf->hfPatch;
// Now go through all patches and patch the original data
while(hf != NULL)
@@ -450,7 +450,7 @@ int PatchFileData(TMPQFile * hf)
break;
// Move to the next patch
- hf = hf->hfPatchFile;
+ hf = hf->hfPatch;
}
return nError;
@@ -493,7 +493,7 @@ bool WINAPI SFileOpenPatchArchive(
dwFlags = dwFlags;
// Verify input parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
nError = ERROR_INVALID_HANDLE;
if(szPatchMpqName == NULL || *szPatchMpqName == 0)
nError = ERROR_INVALID_PARAMETER;
@@ -580,7 +580,7 @@ bool WINAPI SFileIsPatchedArchive(HANDLE hMpq)
TMPQArchive * ha = (TMPQArchive *)hMpq;
// Verify input parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
return false;
return (ha->haPatch != NULL);
diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp
index 6eb0d10..cfc8d2a 100644
--- a/src/SFileReadFile.cpp
+++ b/src/SFileReadFile.cpp
@@ -14,103 +14,8 @@
#include "StormCommon.h"
//-----------------------------------------------------------------------------
-// Local structures
-
-struct TFileHeader2Ext
-{
- DWORD dwOffset00Data; // Required data at offset 00 (32-bits)
- DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored
- DWORD dwOffset04Data; // Required data at offset 04 (32-bits)
- DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored
- const char * szExt; // Supplied extension, if the condition is true
-};
-
-//-----------------------------------------------------------------------------
// Local functions
-static DWORD GetMpqFileCount(TMPQArchive * ha)
-{
- TFileEntry * pFileTableEnd;
- TFileEntry * pFileEntry;
- DWORD dwFileCount = 0;
-
- // Go through all open MPQs, including patches
- while(ha != NULL)
- {
- // Only count files that are not patch files
- pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
- for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
- {
- // If the file is patch file and this is not primary archive, skip it
- // BUGBUG: This errorneously counts non-patch files that are in both
- // base MPQ and in patches, and increases the number of files by cca 50%
- if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS)
- dwFileCount++;
- }
-
- // Move to the next patch archive
- ha = ha->haPatch;
- }
-
- return dwFileCount;
-}
-
-static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded)
-{
- TMPQFile * hfTemp;
- TCHAR * szPatchChain = (TCHAR *)pvFileInfo;
- TCHAR * szFileName;
- size_t cchCharsNeeded = 1;
- size_t nLength;
- DWORD cbLengthNeeded;
-
- // Check if the "hf" is a MPQ file
- if(hf->pStream != NULL)
- {
- // Calculate the length needed
- szFileName = FileStream_GetFileName(hf->pStream);
- cchCharsNeeded += _tcslen(szFileName) + 1;
- cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
-
- // If we have enough space, copy the file name
- if(cbFileInfo >= cbLengthNeeded)
- {
- nLength = _tcslen(szFileName) + 1;
- memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR));
- szPatchChain += nLength;
-
- // Terminate the multi-string
- *szPatchChain = 0;
- }
- }
- else
- {
- // Calculate number of characters needed
- for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile)
- cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1;
- cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
-
- // If we have enough space, the copy the patch chain
- if(cbFileInfo >= cbLengthNeeded)
- {
- for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile)
- {
- szFileName = FileStream_GetFileName(hfTemp->ha->pStream);
- nLength = _tcslen(szFileName) + 1;
- memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR));
- szPatchChain += nLength;
- }
-
- // Terminate the multi-string
- *szPatchChain = 0;
- }
- }
-
- // Give result length, terminate multi-string and return
- *pcbLengthNeeded = cbLengthNeeded;
- return true;
-}
-
// hf - MPQ File handle.
// pbBuffer - Pointer to target buffer to store sectors.
// dwByteOffset - Position of sector in the file (relative to file begin)
@@ -453,7 +358,12 @@ static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos
nError = AllocateSectorBuffer(hf);
if(nError != ERROR_SUCCESS)
return nError;
+ pbRawData = hf->pbFileSector;
+ }
+ // If the file sector is not loaded yet, do it
+ if(hf->dwSectorOffs != 0)
+ {
// Is the file compressed?
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
{
@@ -750,7 +660,7 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD
lpOverlapped = lpOverlapped;
// Check valid parameters
- if(!IsValidFileHandle(hf))
+ if(!IsValidFileHandle(hFile))
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
@@ -769,7 +679,7 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD
}
// If the file is a patch file, we have to read it special way
- else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
+ else if(hf->hfPatch != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
{
nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
}
@@ -819,13 +729,13 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh)
TMPQFile * hf = (TMPQFile *)hFile;
// Validate the file handle before we go on
- if(IsValidFileHandle(hf))
+ if(IsValidFileHandle(hFile))
{
// Make sure that the variable is initialized
FileSize = 0;
// If the file is patched file, we have to get the size of the last version
- if(hf->hfPatchFile != NULL)
+ if(hf->hfPatch != NULL)
{
// Walk through the entire patch chain, take the last version
while(hf != NULL)
@@ -834,7 +744,7 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh)
FileSize = hf->pFileEntry->dwFileSize;
// Move to the next patch file in the hierarchy
- hf = hf->hfPatchFile;
+ hf = hf->hfPatch;
}
}
else
@@ -868,7 +778,7 @@ DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHi
DWORD dwFilePosHi;
// If the hFile is not a valid file handle, return an error.
- if(!IsValidFileHandle(hf))
+ if(!IsValidFileHandle(hFile))
{
SetLastError(ERROR_INVALID_HANDLE);
return SFILE_INVALID_POS;
@@ -954,367 +864,3 @@ DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHi
}
}
-//-----------------------------------------------------------------------------
-// Tries to retrieve the file name
-
-static TFileHeader2Ext data2ext[] =
-{
- {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files
- {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files
- {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A')
- {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF'
- {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2'
- {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new)
- {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I
- {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II
- {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm'
- {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM
- {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files
- {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures
- {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2)
- {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files
- {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps
- {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8'
- {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2
- {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc
- {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders
- {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image
- {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension
- {0, 0, 0, 0, NULL} // Terminator
-};
-
-static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName)
-{
- TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
- DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file
- DWORD dwBytesRead = 0;
- DWORD dwFilePos; // Saved file position
-
- // Read the first 2 DWORDs bytes from the file
- dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT);
- SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL);
- SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN);
-
- // If we read at least 8 bytes
- if(dwBytesRead == sizeof(FirstBytes))
- {
- // Make sure that the array is properly BSWAP-ed
- BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));
-
- // Try to guess file extension from those 2 DWORDs
- for(size_t i = 0; data2ext[i].szExt != NULL; i++)
- {
- if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data &&
- (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data)
- {
- char szPseudoName[20] = "";
-
- // Format the pseudo-name
- sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt);
-
- // Save the pseudo-name in the file entry as well
- AllocateFileName(pFileEntry, szPseudoName);
-
- // If the caller wants to copy the file name, do it
- if(szFileName != NULL)
- strcpy(szFileName, szPseudoName);
- return ERROR_SUCCESS;
- }
- }
- }
-
- return ERROR_NOT_SUPPORTED;
-}
-
-bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
-{
- TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
- TCHAR * szFileNameT;
- int nError = ERROR_INVALID_HANDLE;
-
- // Pre-zero the output buffer
- if(szFileName != NULL)
- *szFileName = 0;
-
- // Check valid parameters
- if(IsValidFileHandle(hf))
- {
- TFileEntry * pFileEntry = hf->pFileEntry;
-
- // For MPQ files, retrieve the file name from the file entry
- if(hf->pStream == NULL)
- {
- if(pFileEntry != NULL)
- {
- // 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;
- }
- }
- }
-
- // For local files, copy the file name from the stream
- else
- {
- if(szFileName != NULL)
- {
- szFileNameT = FileStream_GetFileName(hf->pStream);
- CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT));
- }
- nError = ERROR_SUCCESS;
- }
- }
-
- if(nError != ERROR_SUCCESS)
- SetLastError(nError);
- return (nError == ERROR_SUCCESS);
-}
-
-//-----------------------------------------------------------------------------
-// Retrieves an information about an archive or about a file within the archive
-//
-// hMpqOrFile - Handle to an MPQ archive or to a file
-// dwInfoType - Information to obtain
-
-#define VERIFY_MPQ_HANDLE(h) \
- if(!IsValidMpqHandle(h)) \
- { \
- nError = ERROR_INVALID_HANDLE; \
- break; \
- }
-
-#define VERIFY_FILE_HANDLE(h) \
- if(!IsValidFileHandle(h)) \
- { \
- nError = ERROR_INVALID_HANDLE; \
- break; \
- }
-
-bool WINAPI SFileGetFileInfo(
- HANDLE hMpqOrFile,
- DWORD dwInfoType,
- void * pvFileInfo,
- DWORD cbFileInfo,
- LPDWORD pcbLengthNeeded)
-{
- TMPQArchive * ha = (TMPQArchive *)hMpqOrFile;
- TMPQBlock * pBlock;
- TMPQFile * hf = (TMPQFile *)hMpqOrFile;
- void * pvSrcFileInfo = NULL;
- DWORD cbLengthNeeded = 0;
- DWORD dwIsReadOnly;
- DWORD dwFileCount = 0;
- DWORD dwFileIndex;
- DWORD dwFileKey;
- DWORD i;
- int nError = ERROR_SUCCESS;
-
- switch(dwInfoType)
- {
- case SFILE_INFO_ARCHIVE_NAME:
- VERIFY_MPQ_HANDLE(ha);
-
- // pvFileInfo receives the name of the archive, terminated by 0
- pvSrcFileInfo = FileStream_GetFileName(ha->pStream);
- cbLengthNeeded = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR);
- break;
-
- case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &ha->pHeader->dwArchiveSize;
- break;
-
- case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &ha->dwMaxFileCount;
- break;
-
- case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &ha->pHeader->dwHashTableSize;
- break;
-
- case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &ha->pHeader->dwBlockTableSize;
- break;
-
- case SFILE_INFO_SECTOR_SIZE:
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &ha->dwSectorSize;
- break;
-
- case SFILE_INFO_HASH_TABLE:
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
- pvSrcFileInfo = ha->pHashTable;
- break;
-
- case SFILE_INFO_BLOCK_TABLE:
- VERIFY_MPQ_HANDLE(ha);
- cbLengthNeeded = ha->dwFileTableSize * sizeof(TMPQBlock);
- if(cbFileInfo < cbLengthNeeded)
- {
- nError = ERROR_INSUFFICIENT_BUFFER;
- break;
- }
-
- // Construct block table from file table size
- pBlock = (TMPQBlock *)pvFileInfo;
- for(i = 0; i < ha->dwFileTableSize; i++)
- {
- pBlock->dwFilePos = (DWORD)ha->pFileTable[i].ByteOffset;
- pBlock->dwFSize = ha->pFileTable[i].dwFileSize;
- pBlock->dwCSize = ha->pFileTable[i].dwCmpSize;
- pBlock->dwFlags = ha->pFileTable[i].dwFlags;
- pBlock++;
- }
- break;
-
- case SFILE_INFO_NUM_FILES:
- VERIFY_MPQ_HANDLE(ha);
- dwFileCount = GetMpqFileCount(ha);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &dwFileCount;
- break;
-
- case SFILE_INFO_STREAM_FLAGS:
- VERIFY_MPQ_HANDLE(ha);
- FileStream_GetFlags(ha->pStream, &dwFileKey);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &dwFileKey;
- break;
-
- case SFILE_INFO_IS_READ_ONLY:
- VERIFY_MPQ_HANDLE(ha);
- dwIsReadOnly = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY));
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &dwIsReadOnly;
- break;
-
- case SFILE_INFO_HASH_INDEX:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->pFileEntry->dwHashIndex;
- break;
-
- case SFILE_INFO_CODENAME1:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->pFileEntry->dwHashIndex;
- if(ha->pHashTable != NULL)
- pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1;
- break;
-
- case SFILE_INFO_CODENAME2:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- if(ha->pHashTable != NULL)
- pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2;
- break;
-
- case SFILE_INFO_LOCALEID:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->pFileEntry->lcLocale;
- break;
-
- case SFILE_INFO_BLOCKINDEX:
- VERIFY_FILE_HANDLE(hf);
- dwFileIndex = (DWORD)(hf->pFileEntry - hf->ha->pFileTable);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &dwFileIndex;
- break;
-
- case SFILE_INFO_FILE_SIZE:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->pFileEntry->dwFileSize;
- break;
-
- case SFILE_INFO_COMPRESSED_SIZE:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->pFileEntry->dwCmpSize;
- break;
-
- case SFILE_INFO_FLAGS:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->pFileEntry->dwFlags;
- break;
-
- case SFILE_INFO_POSITION:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(ULONGLONG);
- pvSrcFileInfo = &hf->pFileEntry->ByteOffset;
- break;
-
- case SFILE_INFO_KEY:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &hf->dwFileKey;
- break;
-
- case SFILE_INFO_KEY_UNFIXED:
- VERIFY_FILE_HANDLE(hf);
- dwFileKey = hf->dwFileKey;
- if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY)
- dwFileKey = (dwFileKey ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos;
- cbLengthNeeded = sizeof(DWORD);
- pvSrcFileInfo = &dwFileKey;
- break;
-
- case SFILE_INFO_FILETIME:
- VERIFY_FILE_HANDLE(hf);
- cbLengthNeeded = sizeof(ULONGLONG);
- pvSrcFileInfo = &hf->pFileEntry->FileTime;
- break;
-
- case SFILE_INFO_PATCH_CHAIN:
- VERIFY_FILE_HANDLE(hf);
- GetFilePatchChain(hf, pvFileInfo, cbFileInfo, &cbLengthNeeded);
- break;
-
- default:
- nError = ERROR_INVALID_PARAMETER;
- break;
- }
-
- // If everything is OK so far, copy the information
- if(nError == ERROR_SUCCESS)
- {
- // Is the output buffer large enough?
- if(cbFileInfo >= cbLengthNeeded)
- {
- // Copy the data
- if(pvSrcFileInfo != NULL)
- memcpy(pvFileInfo, pvSrcFileInfo, cbLengthNeeded);
- }
- else
- {
- nError = ERROR_INSUFFICIENT_BUFFER;
- }
-
- // Give the size to the caller
- if(pcbLengthNeeded != NULL)
- *pcbLengthNeeded = cbLengthNeeded;
- }
-
- // Set the last error value, if needed
- if(nError != ERROR_SUCCESS)
- SetLastError(nError);
- return (nError == ERROR_SUCCESS);
-}
diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp
index 4509388..219e187 100644
--- a/src/SFileVerify.cpp
+++ b/src/SFileVerify.cpp
@@ -20,25 +20,8 @@
//-----------------------------------------------------------------------------
// Local defines
-#define SIGNATURE_TYPE_NONE 0
-#define SIGNATURE_TYPE_WEAK 1
-#define SIGNATURE_TYPE_STRONG 2
-
#define MPQ_DIGEST_UNIT_SIZE 0x10000
-typedef struct _MPQ_SIGNATURE_INFO
-{
- ULONGLONG BeginMpqData; // File offset where the hashing starts
- ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file)
- ULONGLONG EndExclude; // End of the excluded area (used for (signature) file)
- ULONGLONG EndMpqData; // File offset where the hashing ends
- ULONGLONG EndOfFile; // Size of the entire file
- BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10];
- DWORD cbSignatureSize; // Length of the signature
- int nSignatureType; // See SIGNATURE_TYPE_XXX
-
-} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO;
-
//-----------------------------------------------------------------------------
// Known Blizzard public keys
// Created by Jean-Francois Roy using OpenSSL
@@ -156,7 +139,7 @@ static void GetPlainAnsiFileName(
const TCHAR * szFileName,
char * szPlainName)
{
- const TCHAR * szPlainNameT = GetPlainFileNameT(szFileName);
+ const TCHAR * szPlainNameT = GetPlainFileName(szFileName);
// Convert the plain name to ANSI
while(*szPlainNameT != 0)
@@ -186,67 +169,13 @@ static void CalculateArchiveRange(
}
}
- // Get the MPQ data end. This is stored in our MPQ header,
- // and it's been already prepared by SFileOpenArchive,
+ // Get the MPQ data end. This is stored in the MPQ header
pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64;
// Get the size of the entire file
FileStream_GetSize(ha->pStream, &pSI->EndOfFile);
}
-static bool QueryMpqSignatureInfo(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI)
-{
- ULONGLONG ExtraBytes;
- TMPQFile * hf;
- HANDLE hFile;
- DWORD dwFileSize;
-
- // Calculate the range of the MPQ
- CalculateArchiveRange(ha, pSI);
-
- // If there is "(signature)" file in the MPQ, it has a weak signature
- if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile))
- {
- // Get the content of the signature
- SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL);
-
- // Verify the size of the signature
- hf = (TMPQFile *)hFile;
-
- // We have to exclude the signature file from the digest
- pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset;
- pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize;
- dwFileSize = hf->dwDataSize;
-
- // Close the file
- SFileCloseFile(hFile);
- pSI->nSignatureType = SIGNATURE_TYPE_WEAK;
- return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false;
- }
-
- // If there is extra bytes beyond the end of the archive,
- // it's the strong signature
- ExtraBytes = pSI->EndOfFile - pSI->EndMpqData;
- if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4))
- {
- // Read the strong signature
- if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4)))
- return false;
-
- // Check the signature header "NGIS"
- if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S')
- return false;
-
- pSI->nSignatureType = SIGNATURE_TYPE_STRONG;
- return true;
- }
-
- // Succeeded, but no known signature found
- return true;
-}
-
static bool CalculateMpqHashMd5(
TMPQArchive * ha,
PMPQ_SIGNATURE_INFO pSI,
@@ -723,7 +652,7 @@ static DWORD VerifyFile(
if(dwTotalBytes == 0)
{
// Check CRC32 and MD5 only if there is no patches
- if(hf->hfPatchFile == NULL)
+ if(hf->hfPatch == NULL)
{
// Check if the CRC32 matches.
if(dwFlags & SFILE_VERIFY_FILE_CRC)
@@ -781,6 +710,63 @@ static DWORD VerifyFile(
return dwVerifyResult;
}
+// Used in SFileGetFileInfo
+bool QueryMpqSignatureInfo(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ ULONGLONG ExtraBytes;
+ TMPQFile * hf;
+ HANDLE hFile;
+ DWORD dwFileSize;
+
+ // Make sure it's all zeroed
+ memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO));
+
+ // Calculate the range of the MPQ
+ CalculateArchiveRange(ha, pSI);
+
+ // If there is "(signature)" file in the MPQ, it has a weak signature
+ if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile))
+ {
+ // Get the content of the signature
+ SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL);
+
+ // Verify the size of the signature
+ hf = (TMPQFile *)hFile;
+
+ // We have to exclude the signature file from the digest
+ pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset;
+ pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize;
+ dwFileSize = hf->dwDataSize;
+
+ // Close the file
+ SFileCloseFile(hFile);
+ pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK;
+ return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false;
+ }
+
+ // If there is extra bytes beyond the end of the archive,
+ // it's the strong signature
+ ExtraBytes = pSI->EndOfFile - pSI->EndMpqData;
+ if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4))
+ {
+ // Read the strong signature
+ if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4)))
+ return false;
+
+ // Check the signature header "NGIS"
+ if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S')
+ return false;
+
+ pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG;
+ return true;
+ }
+
+ // Succeeded, but no known signature found
+ return true;
+}
+
//-----------------------------------------------------------------------------
// Public (exported) functions
@@ -828,7 +814,7 @@ int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * sz
TMPQHeader * pHeader;
// Verify input parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
return ERROR_INVALID_PARAMETER;
pHeader = ha->pHeader;
@@ -900,7 +886,7 @@ DWORD WINAPI SFileVerifyArchive(HANDLE hMpq)
TMPQArchive * ha = (TMPQArchive *)hMpq;
// Verify input parameters
- if(!IsValidMpqHandle(ha))
+ if(!IsValidMpqHandle(hMpq))
return ERROR_VERIFY_FAILED;
// Get the MPQ signature and signature type
@@ -908,18 +894,20 @@ DWORD WINAPI SFileVerifyArchive(HANDLE hMpq)
if(!QueryMpqSignatureInfo(ha, &si))
return ERROR_VERIFY_FAILED;
- // Verify the signature
- switch(si.nSignatureType)
- {
- case SIGNATURE_TYPE_NONE:
- return ERROR_NO_SIGNATURE;
+ // If there is no signature
+ if(si.SignatureTypes == 0)
+ return ERROR_NO_SIGNATURE;
- case SIGNATURE_TYPE_WEAK:
- return VerifyWeakSignature(ha, &si);
+ // We haven't seen a MPQ with both signatures
+ assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG);
- case SIGNATURE_TYPE_STRONG:
- return VerifyStrongSignature(ha, &si);
- }
+ // Verify the strong signature, if present
+ if(si.SignatureTypes & SIGNATURE_TYPE_STRONG)
+ return VerifyStrongSignature(ha, &si);
+
+ // Verify the weak signature, if present
+ if(si.SignatureTypes & SIGNATURE_TYPE_WEAK)
+ return VerifyWeakSignature(ha, &si);
- return ERROR_VERIFY_FAILED;
+ return ERROR_NO_SIGNATURE;
}
diff --git a/src/StormCommon.h b/src/StormCommon.h
index dcc9c7f..bfe99f7 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -61,9 +61,6 @@
#define ID_MPQ_FILE 0x46494c45 // Used internally for checking TMPQFile ('FILE')
-#define MPQ_WEAK_SIGNATURE_SIZE 64
-#define MPQ_STRONG_SIGNATURE_SIZE 256
-
// Prevent problems with CRT "min" and "max" functions,
// as they are not defined on all platforms
#define STORMLIB_MIN(a, b) ((a < b) ? a : b)
@@ -71,39 +68,50 @@
#define STORMLIB_UNUSED(p) ((void)(p))
// Macro for building 64-bit file offset from two 32-bit
-#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | lo)
+#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo)
+
+//-----------------------------------------------------------------------------
+// MPQ signature information
+
+// Size of each signature type
+#define MPQ_WEAK_SIGNATURE_SIZE 64
+#define MPQ_STRONG_SIGNATURE_SIZE 256
+
+// MPQ signature info
+typedef struct _MPQ_SIGNATURE_INFO
+{
+ ULONGLONG BeginMpqData; // File offset where the hashing starts
+ ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file)
+ ULONGLONG EndExclude; // End of the excluded area (used for (signature) file)
+ ULONGLONG EndMpqData; // File offset where the hashing ends
+ ULONGLONG EndOfFile; // Size of the entire file
+ BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10];
+ DWORD cbSignatureSize; // Length of the signature
+ DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX
+
+} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO;
//-----------------------------------------------------------------------------
// Memory management
//
// We use our own macros for allocating/freeing memory. If you want
-// to redefine them, please keep the following rules
+// to redefine them, please keep the following rules:
//
// - The memory allocation must return NULL if not enough memory
// (i.e not to throw exception)
-// - It is not necessary to fill the allocated buffer with zeros
-// - Memory freeing function doesn't have to test the pointer to NULL.
+// - The allocating function does not need to fill the allocated buffer with zeros
+// - Memory freeing function doesn't have to test the pointer to NULL
//
#if defined(_MSC_VER) && defined(_DEBUG)
-__inline void * DebugMalloc(char * /* szFile */, int /* nLine */, size_t nSize)
-{
-// return new BYTE[nSize];
- return HeapAlloc(GetProcessHeap(), 0, nSize);
-}
-__inline void DebugFree(void * ptr)
-{
-// delete [] ptr;
- HeapFree(GetProcessHeap(), 0, ptr);
-}
+#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
+#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
-#define STORM_ALLOC(type, nitems) (type *)DebugMalloc(__FILE__, __LINE__, (nitems) * sizeof(type))
-#define STORM_FREE(ptr) DebugFree(ptr)
#else
-#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type))
-#define STORM_FREE(ptr) free(ptr)
+#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type))
+#define STORM_FREE(ptr) free(ptr)
#endif
@@ -137,12 +145,7 @@ DWORD GetHashTableSizeForFileCount(DWORD dwFileCount);
bool IsPseudoFileName(const char * szFileName, LPDWORD pdwFileIndex);
ULONGLONG HashStringJenkins(const char * szFileName);
-void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength);
-void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength);
-
-int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags);
-
-DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize);
+DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion);
void EncryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey);
void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey);
@@ -158,17 +161,26 @@ void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_ha
//-----------------------------------------------------------------------------
// Handle validation functions
-bool IsValidMpqHandle(TMPQArchive * ha);
-bool IsValidFileHandle(TMPQFile * hf);
+TMPQArchive * IsValidMpqHandle(HANDLE hMpq);
+TMPQFile * IsValidFileHandle(HANDLE hFile);
//-----------------------------------------------------------------------------
-// Hash table and block table manipulation
+// Support for MPQ file tables
+
+int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags);
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);
-DWORD AllocateHetEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
+
+TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey);
+TMPQHetTable * LoadHetTable(TMPQArchive * ha);
+TMPQBetTable * LoadBetTable(TMPQArchive * ha);
+
+TMPQHash * LoadHashTable(TMPQArchive * ha);
+TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries = false);
+TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable);
ULONGLONG FindFreeMpqSpace(TMPQArchive * ha);
@@ -178,10 +190,12 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComp
// Functions that load the HET and BET tables
int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize);
int LoadAnyHashTable(TMPQArchive * ha);
-int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize);
+int BuildFileTable(TMPQArchive * ha);
+int RebuildHetTable(TMPQArchive * ha);
+int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount);
int SaveMPQTables(TMPQArchive * ha);
-TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty);
+TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwHashBitSize, LPBYTE pbSrcData);
void FreeHetTable(TMPQHetTable * pHetTable);
TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount);
@@ -194,18 +208,19 @@ TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID l
TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex);
// Allocates file name in the file entry
-void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName);
+void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName);
// Allocates new file entry in the MPQ tables. Reuses existing, if possible
-TFileEntry * FindFreeFileEntry(TMPQArchive * ha);
TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale);
int RenameFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szNewFileName);
-void ClearFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
-int FreeFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
+void DeleteFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
// Invalidates entries for (listfile) and (attributes)
void InvalidateInternalFiles(TMPQArchive * ha);
+// Retrieves information about the strong signature
+bool QueryMpqSignatureInfo(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSignatureInfo);
+
//-----------------------------------------------------------------------------
// Support for alternate file formats (SBaseSubTypes.cpp)
@@ -245,10 +260,14 @@ void FreeMPQArchive(TMPQArchive *& ha);
// Utility functions
bool CheckWildCard(const char * szString, const char * szWildCard);
-const char * GetPlainFileNameA(const char * szFileName);
-const TCHAR * GetPlainFileNameT(const TCHAR * szFileName);
bool IsInternalMpqFileName(const char * szFileName);
+const TCHAR * GetPlainFileName(const TCHAR * szFileName);
+const char * GetPlainFileName(const char * szFileName);
+
+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
diff --git a/src/StormLib.h b/src/StormLib.h
index 3f91478..ce65f7f 100644
--- a/src/StormLib.h
+++ b/src/StormLib.h
@@ -67,6 +67,7 @@
/* 26.04.12 8.10 Lad Support for data map, added SFileGetArchiveBitmap */
/* 29.05.12 8.20 Lad C-only interface */
/* 14.01.13 8.21 Lad ADPCM and Huffmann (de)compression refactored */
+/* 04.12.13 9.00 Lad Unit tests, bug fixes */
/*****************************************************************************/
#ifndef __STORMLIB_H__
@@ -131,8 +132,8 @@ extern "C" {
//-----------------------------------------------------------------------------
// Defines
-#define STORMLIB_VERSION 0x0817 // Current version of StormLib (8.23)
-#define STORMLIB_VERSION_STRING "8.23" // String version of StormLib version
+#define STORMLIB_VERSION 0x0900 // Current version of StormLib (9.0)
+#define STORMLIB_VERSION_STRING "9.00" // String version of StormLib version
#define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A')
#define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B')
@@ -146,15 +147,15 @@ extern "C" {
#define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ
// Values for SFileCreateArchive
-#define HASH_TABLE_SIZE_MIN 0x00000004 // Minimum acceptable hash table size
+#define HASH_TABLE_SIZE_MIN 0x00000004 // Verified: If there is 1 file, hash table size is 4
#define HASH_TABLE_SIZE_DEFAULT 0x00001000 // Default hash table size for empty MPQs
#define HASH_TABLE_SIZE_MAX 0x00080000 // Maximum acceptable hash table size
#define HASH_ENTRY_DELETED 0xFFFFFFFE // Block index for deleted entry in the hash table
#define HASH_ENTRY_FREE 0xFFFFFFFF // Block index for free entry in the hash table
-#define HET_ENTRY_DELETED 0x80 // HET hash value for a deleted entry
-#define HET_ENTRY_FREE 0x00 // HET hash value for free entry
+#define HET_ENTRY_DELETED 0x80 // NameHash1 value for a deleted entry
+#define HET_ENTRY_FREE 0x00 // NameHash1 value for free entry
#define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure
@@ -173,11 +174,11 @@ extern "C" {
// Flags for TMPQArchive::dwFlags
#define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access
#define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed
-#define MPQ_FLAG_PROTECTED 0x00000004 // Set on protected MPQs (like W3M maps)
+#define MPQ_FLAG_PROTECTED 0x00000004 // Some kind of protector detected (W3M maps)
#define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000008 // Checking sector CRC when reading files
-#define MPQ_FLAG_NEED_FIX_SIZE 0x00000010 // Used during opening the archive
#define MPQ_FLAG_LISTFILE_INVALID 0x00000020 // If set, it means that the (listfile) has been invalidated
#define MPQ_FLAG_ATTRIBUTES_INVALID 0x00000040 // If set, it means that the (attributes) has been invalidated
+#define MPQ_FLAG_SAVING_TABLES 0x00000080 // If set, we are saving MPQ internal files and MPQ tables
// Values for TMPQArchive::dwSubType
#define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games)
@@ -214,9 +215,6 @@ extern "C" {
MPQ_FILE_SECTOR_CRC | \
MPQ_FILE_EXISTS)
-// A notification that people should stop using this flag
-const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK") unsigned int MPQ_FILE_COMPRESSED = 0x0000FF00;
-
// Compression types for multiple compressions
#define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only)
#define MPQ_COMPRESSION_ZLIB 0x02 // ZLIB compression
@@ -244,35 +242,6 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK
// Block map defines
#define MPQ_DATA_BITMAP_SIGNATURE 0x33767470 // Signature of the MPQ data bitmap ('ptv3')
-// Constants for SFileGetFileInfo
-#define SFILE_INFO_ARCHIVE_NAME 1 // MPQ size (value from header)
-#define SFILE_INFO_ARCHIVE_SIZE 2 // MPQ size (value from header)
-#define SFILE_INFO_MAX_FILE_COUNT 3 // Max number of files in the MPQ
-#define SFILE_INFO_HASH_TABLE_SIZE 4 // Size of hash table, in entries
-#define SFILE_INFO_BLOCK_TABLE_SIZE 5 // Number of entries in the block table
-#define SFILE_INFO_SECTOR_SIZE 6 // Size of file sector (in bytes)
-#define SFILE_INFO_HASH_TABLE 7 // Pointer to Hash table (TMPQHash *)
-#define SFILE_INFO_BLOCK_TABLE 8 // Pointer to Block Table (TMPQBlock *)
-#define SFILE_INFO_NUM_FILES 9 // Real number of files within archive
-#define SFILE_INFO_STREAM_FLAGS 10 // Stream flags for the MPQ. See STREAM_FLAG_XXX
-#define SFILE_INFO_IS_READ_ONLY 11 // TRUE of the MPQ was open as read only
-//------
-#define SFILE_INFO_HASH_INDEX 100 // Hash index of file in MPQ
-#define SFILE_INFO_CODENAME1 101 // The first codename of the file
-#define SFILE_INFO_CODENAME2 102 // The second codename of the file
-#define SFILE_INFO_LOCALEID 103 // Locale ID of file in MPQ
-#define SFILE_INFO_BLOCKINDEX 104 // Index to Block Table
-#define SFILE_INFO_FILE_SIZE 105 // Original file size (from the block table)
-#define SFILE_INFO_COMPRESSED_SIZE 106 // Compressed file size (from the block table)
-#define SFILE_INFO_FLAGS 107 // File flags
-#define SFILE_INFO_POSITION 108 // File position within archive
- // Note: for current pointer in open MPQ file,
- // use SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT);
-#define SFILE_INFO_KEY 109 // File decryption key
-#define SFILE_INFO_KEY_UNFIXED 110 // Decryption key not fixed to file pos and size
-#define SFILE_INFO_FILETIME 111 // TMPQFileTime
-#define SFILE_INFO_PATCH_CHAIN 112 // Chain of patches
-
#define LISTFILE_NAME "(listfile)" // Name of internal listfile
#define SIGNATURE_NAME "(signature)" // Name of internal signature
#define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file
@@ -319,7 +288,8 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK
#define MPQ_OPEN_ENCRYPTED STREAM_PROVIDER_ENCRYPTED
// Flags for SFileCreateArchive
-#define MPQ_CREATE_ATTRIBUTES 0x00100000 // Also add the (attributes) file
+#define MPQ_CREATE_LISTFILE 0x00100000 // Also add the (listfile) file
+#define MPQ_CREATE_ATTRIBUTES 0x00200000 // Also add the (attributes) file
#define MPQ_CREATE_ARCHIVE_V1 0x00000000 // Creates archive of version 1 (size up to 4GB)
#define MPQ_CREATE_ARCHIVE_V2 0x01000000 // Creates archive of version 2 (larger than 4 GB)
#define MPQ_CREATE_ARCHIVE_V3 0x02000000 // Creates archive of version 3
@@ -357,6 +327,11 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK
#define SFILE_VERIFY_HIBLOCK_TABLE 0x0006 // Verify raw data of the hi-block table
#define SFILE_VERIFY_FILE 0x0007 // Verify raw data of a file
+// Signature types
+#define SIGNATURE_TYPE_NONE 0x0000 // The archive has no signature in it
+#define SIGNATURE_TYPE_WEAK 0x0001 // The archive has weak signature
+#define SIGNATURE_TYPE_STRONG 0x0002 // The archive has strong signature
+
// Return values for SFileVerifyArchive
#define ERROR_NO_SIGNATURE 0 // There is no signature in the MPQ
#define ERROR_VERIFY_FAILED 1 // There was an error during verifying signature (like no memory)
@@ -381,14 +356,114 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK
typedef DWORD (*HASH_STRING)(const char * szFileName, DWORD dwHashType);
//-----------------------------------------------------------------------------
+// File information classes for SFileGetFileInfo and SFileFreeFileInfo
+
+typedef enum _SFileInfoClass
+{
+ // Info classes for archives
+ SFileMpqFileName, // Name of the archive file (TCHAR [])
+ SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG)
+ SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData)
+ SFileMpqUserData, // MPQ USer data, without the header (BYTE [])
+ SFileMpqHeaderOffset, // Offset of the MPQ header (ULONGLONG)
+ SFileMpqHeaderSize, // Fixed size of the MPQ header
+ SFileMpqHeader, // Raw (unfixed) archive header (TMPQHeader)
+ SFileMpqHetTableOffset, // Offset of the HET table, relative to MPQ header (ULONGLONG)
+ SFileMpqHetTableSize, // Compressed size of the HET table (ULONGLONG)
+ SFileMpqHetHeader, // HET table header (TMPQHetHeader)
+ SFileMpqHetTable, // HET table as pointer. Must be freed using SFileFreeFileInfo
+ SFileMpqBetTableOffset, // Offset of the BET table, relative to MPQ header (ULONGLONG)
+ SFileMpqBetTableSize, // Compressed size of the BET table (ULONGLONG)
+ SFileMpqBetHeader, // BET table header, followed by the flags (TMPQBetHeader + DWORD[])
+ SFileMpqBetTable, // BET table as pointer. Must be freed using SFileFreeFileInfo
+ SFileMpqHashTableOffset, // Hash table offset, relative to MPQ header (ULONGLONG)
+ SFileMpqHashTableSize64, // Compressed size of the hash table (ULONGLONG)
+ SFileMpqHashTableSize, // Size of the hash table, in entries (DWORD)
+ SFileMpqHashTable, // Raw (unfixed) hash table (TMPQBlock [])
+ SFileMpqBlockTableOffset, // Block table offset, relative to MPQ header (ULONGLONG)
+ SFileMpqBlockTableSize64, // Compressed size of the block table (ULONGLONG)
+ SFileMpqBlockTableSize, // Size of the block table, in entries (DWORD)
+ SFileMpqBlockTable, // Raw (unfixed) block table (TMPQBlock [])
+ SFileMpqHiBlockTableOffset, // Hi-block table offset, relative to MPQ header (ULONGLONG)
+ SFileMpqHiBlockTableSize64, // Compressed size of the hi-block table (ULONGLONG)
+ SFileMpqHiBlockTable, // The hi-block table (USHORT [])
+ SFileMpqSignatures, // Signatures present in the MPQ (DWORD)
+ SFileMpqStrongSignatureOffset, // Byte offset of the strong signature, relative to begin of the file (ULONGLONG)
+ SFileMpqStrongSignatureSize, // Size of the strong signature (DWORD)
+ SFileMpqStrongSignature, // The strong signature (BYTE [])
+ SFileMpqBitmapOffset, // Byte offset of the MPQ bitmap, relative to begin of the file (ULONGLONG)
+ SFileMpqBitmapSize, // Size of the MPQ bitmap (DWORD)
+ SFileMpqBitmap, // The MPQ Bitmap (BYTE [])
+ SFileMpqArchiveSize64, // Archive size from the header (ULONGLONG)
+ SFileMpqArchiveSize, // Archive size from the header (DWORD)
+ SFileMpqMaxFileCount, // Max number of files in the archive (DWORD)
+ SFileMpqFileTableSize, // Number of entries in the file table (DWORD)
+ SFileMpqSectorSize, // Sector size (DWORD)
+ SFileMpqNumberOfFiles, // Number of files (DWORD)
+ SFileMpqRawChunkSize, // Size of the raw data chunk for MD5
+ SFileMpqStreamFlags, // Stream flags (DWORD)
+ SFileMpqIsReadOnly, // Nonzero if the MPQ is read only (DWORD)
+
+ // Info classes for files
+ SFileInfoPatchChain, // Chain of patches where the file is (TCHAR [])
+ SFileInfoFileEntry, // The file entry for the file (TFileEntry)
+ SFileInfoHashEntry, // Hash table entry for the file (TMPQHash)
+ SFileInfoHashIndex, // Index of the hash table entry (DWORD)
+ SFileInfoNameHash1, // The first name hash in the hash table (DWORD)
+ SFileInfoNameHash2, // The second name hash in the hash table (DWORD)
+ SFileInfoNameHash3, // 64-bit file name hash for the HET/BET tables (ULONGLONG)
+ SFileInfoLocale, // File locale (DWORD)
+ SFileInfoFileIndex, // Block index (DWORD)
+ SFileInfoByteOffset, // File position in the archive (ULONGLONG)
+ SFileInfoFileTime, // File time (ULONGLONG)
+ SFileInfoFileSize, // Size of the file (DWORD)
+ SFileInfoCompressedSize, // Compressed file size (DWORD)
+ SFileInfoFlags, // File flags from (DWORD)
+ SFileInfoEncryptionKey, // File encryption key
+ SFileInfoEncryptionKeyRaw, // Unfixed value of the file key
+} SFileInfoClass;
+
+//-----------------------------------------------------------------------------
+// Deprecated flags. These are going to be removed in next releases.
+
+// MPQ_FILE_COMPRESSED is deprecated. Do not use.
+STORMLIB_DEPRECATED_FLAG(DWORD, MPQ_FILE_COMPRESSED, MPQ_FILE_COMPRESS_MASK);
+
+// Legacy values for file info classes. Included for backward compatibility, do not use.
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_ARCHIVE_NAME, SFileMpqFileName);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_ARCHIVE_SIZE, SFileMpqArchiveSize);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_MAX_FILE_COUNT, SFileMpqMaxFileCount);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_TABLE_SIZE, SFileMpqHashTableSize);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCK_TABLE_SIZE, SFileMpqBlockTableSize);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_SECTOR_SIZE, SFileMpqSectorSize);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_TABLE, SFileMpqHashTable);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCK_TABLE, SFileMpqBlockTable);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_NUM_FILES, SFileMpqNumberOfFiles);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_STREAM_FLAGS, SFileMpqStreamFlags);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_IS_READ_ONLY, SFileMpqIsReadOnly);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_INDEX, SFileInfoHashIndex);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_CODENAME1, SFileInfoNameHash1);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_CODENAME2, SFileInfoNameHash2);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_LOCALEID, SFileInfoLocale);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCKINDEX, SFileInfoFileIndex);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FILE_SIZE, SFileInfoFileSize);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_COMPRESSED_SIZE, SFileInfoCompressedSize);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FLAGS, SFileInfoFlags);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_POSITION, SFileInfoByteOffset);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_KEY, SFileInfoEncryptionKey);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_KEY_UNFIXED, SFileInfoEncryptionKeyRaw);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FILETIME, SFileInfoFileTime);
+STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_PATCH_CHAIN, SFileInfoPatchChain);
+
+//-----------------------------------------------------------------------------
// Callback functions
// Values for compact callback
-#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total)
-#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total)
-#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used
-#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total)
-#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used
+#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total)
+#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total)
+#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used
+#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total)
+#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used
typedef void (WINAPI * SFILE_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall);
typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes);
@@ -400,6 +475,7 @@ typedef struct TFileStream TFileStream;
typedef struct _TBitArray
{
+ DWORD NumberOfBytes; // Total number of bytes in "Elements"
DWORD NumberOfBits; // Total number of bits that are available
BYTE Elements[1]; // Array of elements (variable length)
} TBitArray;
@@ -602,10 +678,10 @@ typedef struct _TMPQBlock
// Patch file information, preceding the sector offset table
typedef struct _TPatchInfo
{
- DWORD dwLength; // Length of patch info header, in bytes
- DWORD dwFlags; // Flags. 0x80000000 = MD5 (?)
- DWORD dwDataSize; // Uncompressed size of the patch file
- BYTE md5[0x10]; // MD5 of the entire patch file after decompression
+ DWORD dwLength; // Length of patch info header, in bytes
+ DWORD dwFlags; // Flags. 0x80000000 = MD5 (?)
+ DWORD dwDataSize; // Uncompressed size of the patch file
+ BYTE md5[0x10]; // MD5 of the entire patch file after decompression
// Followed by the sector table (variable length)
} TPatchInfo;
@@ -614,21 +690,21 @@ typedef struct _TPatchInfo
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
+ 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
+ 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')
+ 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;
@@ -640,32 +716,74 @@ typedef struct _TPatchHeader
// (attributes) file and from (listfile).
typedef struct _TFileEntry
{
- 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.
- ULONGLONG BetHash; // Lower part of the file name hash. Only used when the MPQ has BET table.
- DWORD dwHashIndex; // Index to the hash table. Only used when the MPQ has classic hash table
- DWORD dwHetIndex; // Index to the HET table. Only used when the MPQ has HET 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.
- char * szFileName; // File name. NULL if not known.
+ 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.
+ char * szFileName; // File name. NULL if not known.
} TFileEntry;
// Common header for HET and BET tables
-typedef struct _TMPQExtTable
+typedef struct _TMPQExtHeader
{
- DWORD dwSignature; // 'HET\x1A' or 'BET\x1A'
- DWORD dwVersion; // Version. Seems to be always 1
- DWORD dwDataSize; // Size of the contained table
+ DWORD dwSignature; // 'HET\x1A' or 'BET\x1A'
+ DWORD dwVersion; // Version. Seems to be always 1
+ DWORD dwDataSize; // Size of the contained table
// Followed by the table header
// Followed by the table data
-} TMPQExtTable;
+} TMPQExtHeader;
+
+// Structure for HET table header
+typedef struct _TMPQHetHeader
+{
+ TMPQExtHeader ExtHdr;
+
+ DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes)
+ DWORD dwEntryCount; // Number of occupied entries in the HET table
+ DWORD dwTotalCount; // Total number of entries in the HET table
+ DWORD dwNameHashBitSize; // Size of the name hash entry (in bits)
+ DWORD dwIndexSizeTotal; // Total size of file index (in bits)
+ DWORD dwIndexSizeExtra; // Extra bits in the file index
+ DWORD dwIndexSize; // Effective size of the file index (in bits)
+ DWORD dwIndexTableSize; // Size of the block index subtable (in bytes)
+
+} TMPQHetHeader;
+
+// Structure for BET table header
+typedef struct _TMPQBetHeader
+{
+ TMPQExtHeader ExtHdr;
+
+ DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes)
+ DWORD dwEntryCount; // Number of entries in the BET table. Must match HET_TABLE_HEADER::dwEntryCount
+ DWORD dwUnknown08;
+ DWORD dwTableEntrySize; // Size of one table entry (in bits)
+ DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record)
+ DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record)
+ DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record)
+ DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record)
+ DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record)
+ DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record)
+ DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record)
+ DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record)
+ DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record)
+ DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record)
+ DWORD dwBitTotal_NameHash2; // Total bit size of the NameHash2
+ DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2
+ DWORD dwBitCount_NameHash2; // Effective size of NameHash2 (in bits)
+ DWORD dwNameHashArraySize; // Size of NameHash2 table, in bytes
+ DWORD dwFlagCount; // Number of flags in the following array
+
+} TMPQBetHeader;
//
// MPQ data bitmap, can be found at (FileSize - sizeof(TMPQBlockMap))
@@ -675,175 +793,185 @@ typedef struct _TMPQExtTable
//
typedef struct _TMPQBitmap
{
- DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE)
- DWORD dwAlways3; // Unknown, seems to always have value of 3
- DWORD dwBuildNumber; // Game build number for that MPQ
- DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map
- DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map
- DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes)
+ DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE)
+ DWORD dwAlways3; // Unknown, seems to always have value of 3
+ DWORD dwBuildNumber; // Game build number for that MPQ
+ DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map
+ DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map
+ DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes)
} TMPQBitmap;
// Structure for parsed HET table
typedef struct _TMPQHetTable
{
- TBitArray * pBetIndexes; // Bit array of indexes to BET tables
- LPBYTE pHetHashes; // Array of HET hashes. Each entry has size of 1 byte
- ULONGLONG AndMask64; // AND mask used for calculating file name hash
- ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash
-
- DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits)
- DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes
- DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits)
- DWORD dwFileCount; // Number of occupied entries in the HET table
- DWORD dwHashTableSize; // Number of entries in pBetHashes
- DWORD dwHashBitSize; // Effective number of bits in the hash
+ TBitArray * pBetIndexes; // Bit array of FileIndex values
+ LPBYTE pNameHashes; // Array of NameHash1 values (NameHash1 = upper 8 bits of FileName hashe)
+ ULONGLONG AndMask64; // AND mask used for calculating file name hash
+ ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash
+
+ DWORD dwEntryCount; // Number of occupied entries in the HET table
+ DWORD dwTotalCount; // Number of entries in both NameHash and FileIndex table
+ DWORD dwNameHashBitSize; // Size of the name hash entry (in bits)
+ DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits)
+ DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes
+ DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits)
} TMPQHetTable;
// Structure for parsed BET table
typedef struct _TMPQBetTable
{
- TBitArray * pBetHashes; // Array of BET hashes
- TBitArray * pFileTable; // Bit-based file table
- LPDWORD pFileFlags; // Array of file flags
-
- DWORD dwTableEntrySize; // Size of one table entry, in bits
- DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry
- DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry
- DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry
- DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry
- DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry
- DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry
- DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry
- DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry
- DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry
- DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry
- DWORD dwBetHashSizeTotal; // Total size of bet hash
- DWORD dwBetHashSizeExtra; // Extra bits in the bet hash
- DWORD dwBetHashSize; // Effective size of the bet hash
- DWORD dwFileCount; // Number of files (usually equal to maximum number of files)
- DWORD dwFlagCount; // Number of entries in pFileFlags
+ TBitArray * pNameHashes; // Array of NameHash2 entries (lower 24 bits of FileName hash)
+ TBitArray * pFileTable; // Bit-based file table
+ LPDWORD pFileFlags; // Array of file flags
+
+ DWORD dwTableEntrySize; // Size of one table entry, in bits
+ DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry
+ DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry
+ DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry
+ DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry
+ DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry
+ DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry
+ DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry
+ DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry
+ DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry
+ DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry
+ DWORD dwBitTotal_NameHash2; // Total size of the NameHash2
+ DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2
+ DWORD dwBitCount_NameHash2; // Effective size of the NameHash2
+ DWORD dwEntryCount; // Number of entries
+ DWORD dwFlagCount; // Number of fil flags in pFileFlags
} TMPQBetTable;
// Archive handle structure
typedef struct _TMPQArchive
{
- TFileStream * pStream; // Open stream for the MPQ
-
- 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)
-
- struct _TMPQArchive * haPatch; // Pointer to patch archive, if any
- struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any
- char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs
- size_t cchPatchPrefix; // Length of the patch prefix, in characters
-
- TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file)
- TMPQHeader * pHeader; // MPQ file header
- TMPQBitmap * pBitmap; // MPQ bitmap
- TMPQHash * pHashTable; // Hash table
- TMPQHetTable * pHetTable; // Het table
- TFileEntry * pFileTable; // File table
- HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash
+ TFileStream * pStream; // Open stream for the MPQ
+
+ 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)
+
+ struct _TMPQArchive * haPatch; // Pointer to patch archive, if any
+ struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any
+ char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs
+ size_t cchPatchPrefix; // Length of the patch prefix, in characters
+
+ TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file)
+ TMPQHeader * pHeader; // MPQ file header
+ TMPQBitmap * pBitmap; // MPQ bitmap
+ TMPQHash * pHashTable; // Hash table
+ TMPQHetTable * pHetTable; // HET table
+ TFileEntry * pFileTable; // File table
+ HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash
- TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found
+ TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found
BYTE HeaderData[MPQ_HEADER_SIZE_V4]; // Storage for MPQ header
DWORD dwHETBlockSize;
DWORD dwBETBlockSize;
- DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one
- DWORD dwMaxFileCount; // Maximum number of files in the MPQ
- DWORD dwHashIndexMask; // Mask for converting MPQ_HASH_TABLE_INDEX into real index
- DWORD dwSectorSize; // Default size of one file sector
- DWORD dwFileFlags1; // Flags for (listfile)
- DWORD dwFileFlags2; // Flags for (attributes)
- DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX
- DWORD dwFlags; // See MPQ_FLAG_XXXXX
- DWORD dwSubType; // See MPQ_SUBTYPE_XXX
-
- SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files
- void * pvAddFileUserData; // User data thats passed to the callback
-
- SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive
- ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call
- ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted
- void * pvCompactUserData; // User data thats passed to the callback
+ DWORD dwBitmapSize; // sizeof(TMPQBitmap) + size of the bit array
+ DWORD dwMaxFileCount; // Maximum number of files in the MPQ. Also total size of the file table.
+ 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
+ DWORD dwFileFlags1; // Flags for (listfile)
+ DWORD dwFileFlags2; // Flags for (attributes)
+ DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX
+ DWORD dwFlags; // See MPQ_FLAG_XXXXX
+ DWORD dwSubType; // See MPQ_SUBTYPE_XXX
+
+ SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files
+ void * pvAddFileUserData; // User data thats passed to the callback
+
+ SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive
+ ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call
+ ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted
+ void * pvCompactUserData; // User data thats passed to the callback
} TMPQArchive;
// File handle structure
typedef struct _TMPQFile
{
- TFileStream * pStream; // File stream. Only used on local files
- TMPQArchive * ha; // Archive handle
- 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 dwMagic; // 'FILE'
-
- struct _TMPQFile * hfPatchFile; // Pointer to opened patch file
- TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file
- LPBYTE pbFileData; // Loaded and patched file data. Only used if the file is a patch file
- DWORD cbFileData; // Size of loaded patched data
-
- 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
- DWORD dwSectorCount; // Number of sectors in the file
- DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ
- DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry)
-
- LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content
- DWORD dwSectorOffs; // File position of currently loaded file sector
- DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size
-
- unsigned char hctx[HASH_STATE_SIZE];// Hash state for MD5. Used when saving file to MPQ
- DWORD dwCrc32; // CRC32 value, used when saving file to MPQ
-
- bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs
- bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file
- bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile
- bool bErrorOccured; // If true, then at least one error occured during saving the file to the archive
+ TFileStream * pStream; // File stream. Only used on local files
+ TMPQArchive * ha; // Archive handle
+ 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 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; // Loaded and patched file data. Only used if the file is a patch file
+ DWORD cbFileData; // Size of loaded patched data
+
+ 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
+ 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
+ DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry)
+
+ LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content
+ DWORD dwSectorOffs; // File position of currently loaded file sector
+ DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size
+
+ unsigned char hctx[HASH_STATE_SIZE]; // Hash state for MD5. Used when saving file to MPQ
+ DWORD dwCrc32; // CRC32 value, used when saving file to MPQ
+
+ int nAddFileError; // Result of the "Add File" operations
+
+ bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs
+ bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file
+ bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile
} TMPQFile;
// Structure for SFileFindFirstFile and SFileFindNextFile
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
- DWORD dwCompSize; // Compressed file size
- DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present)
- DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present)
- LCID lcLocale; // Locale version
+ 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
+ DWORD dwCompSize; // Compressed file size
+ DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present)
+ DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present)
+ LCID lcLocale; // Locale version
} SFILE_FIND_DATA, *PSFILE_FIND_DATA;
typedef struct _SFILE_CREATE_MPQ
{
- DWORD cbSize; // Size of this structure, in bytes
- DWORD dwMpqVersion; // Version of the MPQ to be created
- void *pvUserData; // Reserved, must be NULL
- DWORD cbUserData; // Reserved, must be 0
- DWORD dwStreamFlags; // Stream flags for creating the MPQ
- DWORD dwFileFlags1; // File flags for (listfile). 0 = default
- DWORD dwFileFlags2; // File flags for (attributes). 0 = default
- DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created
- DWORD dwSectorSize; // Sector size for compressed files
- DWORD dwRawChunkSize; // Size of raw data chunk
- DWORD dwMaxFileCount; // File limit for the MPQ
+ DWORD cbSize; // Size of this structure, in bytes
+ DWORD dwMpqVersion; // Version of the MPQ to be created
+ void *pvUserData; // Reserved, must be NULL
+ DWORD cbUserData; // Reserved, must be 0
+ DWORD dwStreamFlags; // Stream flags for creating the MPQ
+ DWORD dwFileFlags1; // File flags for (listfile). 0 = default
+ DWORD dwFileFlags2; // File flags for (attributes). 0 = default
+ DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created
+ DWORD dwSectorSize; // Sector size for compressed files
+ DWORD dwRawChunkSize; // Size of raw data chunk
+ DWORD dwMaxFileCount; // File limit for the MPQ
} SFILE_CREATE_MPQ, *PSFILE_CREATE_MPQ;
//-----------------------------------------------------------------------------
// Stream support - functions
+// UNICODE versions of the file access functions
TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags);
TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags);
-TCHAR * FileStream_GetFileName(TFileStream * pStream);
+const TCHAR * FileStream_GetFileName(TFileStream * pStream);
+
+//#ifdef _UNICODE
+//TFileStream * FileStream_CreateFile(const char * szFileName, DWORD dwStreamFlags);
+//TFileStream * FileStream_OpenFile(const char * szFileName, DWORD dwStreamFlags);
+//#endif
+
bool FileStream_IsReadOnly(TFileStream * pStream);
bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead);
bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite);
@@ -881,7 +1009,7 @@ LCID WINAPI SFileSetLocale(LCID lcNewLocale);
// Functions for archive manipulation
bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq);
-bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq);
+bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq);
bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq);
bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded);
@@ -916,16 +1044,17 @@ bool WINAPI SFileIsPatchedArchive(HANDLE hMpq);
// Functions for file manipulation
// Reading from MPQ file
+bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName);
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile);
DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh);
DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod);
bool WINAPI SFileReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped);
bool WINAPI SFileCloseFile(HANDLE hFile);
-// Retrieving info about the file
-bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName);
+// Retrieving info about a file in the archive
+bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, SFileInfoClass InfoClass, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded);
bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName);
-bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, DWORD dwInfoType, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded);
+bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass);
// High-level extract function
bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR * szExtracted, DWORD dwSearchScope);
diff --git a/src/StormPort.h b/src/StormPort.h
index 4e34280..83d8624 100644
--- a/src/StormPort.h
+++ b/src/StormPort.h
@@ -169,15 +169,17 @@
#define _tcscpy strcpy
#define _tcscat strcat
#define _tcsrchr strrchr
+ #define _tcsstr strstr
#define _tprintf printf
#define _stprintf sprintf
#define _tremove remove
#define _stricmp strcasecmp
#define _strnicmp strncasecmp
+ #define _tcsicmp strcasecmp
#define _tcsnicmp strncasecmp
-#endif // !WIN32
+#endif // !PLATFORM_WINDOWS
// 64-bit calls are supplied by "normal" calls on Mac
#if defined(PLATFORM_MAC)
@@ -221,7 +223,6 @@
#define BSWAP_ARRAY32_UNSIGNED(a,b) {}
#define BSWAP_ARRAY64_UNSIGNED(a,b) {}
#define BSWAP_PART_HEADER(a) {}
- #define BSWAP_TMPQUSERDATA(a) {}
#define BSWAP_TMPQHEADER(a,b) {}
#define BSWAP_TMPKHEADER(a) {}
#else
@@ -255,7 +256,6 @@
#define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b))
#define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b))
#define BSWAP_PART_HEADER(a) ConvertPartHeader(a)
- #define BSWAP_TMPQUSERDATA(a) ConvertTMPQUserData((a))
#define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b))
#define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a))
#endif
@@ -273,4 +273,12 @@
#define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated(_Text)))
#endif
+// When a flag is deprecated, use this macro
+#ifndef _STORMLIB_NO_DEPRECATE
+ #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) \
+ const STORMLIB_DEPRECATED(#oldflag " is deprecated. Use " #newflag ". To supress this warning, define _STORMLIB_NO_DEPRECATE") type oldflag = (type)newflag;
+#else
+ #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) const type oldflag = (type)newflag;
+#endif
+
#endif // __STORMPORT_H__