summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2013-11-13 07:56:31 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2013-11-13 07:56:31 +0100
commita70863499e5e9e6560477b8f4a0a594d6e62650e (patch)
tree21118705bf1d412ed9aea0b51daf25f98b4fabad
parent66b71713c2a60e6f6ccc55245b067dad34fd6154 (diff)
+ Support for SQP and MPK archives
+ Makefiles fixed
-rw-r--r--CMakeLists.txt2
-rw-r--r--Makefile.linux2
-rw-r--r--StormLib.vcproj198
-rw-r--r--StormLib_dll.vcproj4
-rw-r--r--StormLib_test.vcproj4
-rw-r--r--makefile.w322
-rw-r--r--src/SBaseCommon.cpp513
-rw-r--r--src/SBaseFileTable.cpp249
-rw-r--r--src/SBaseSubTypes.cpp598
-rw-r--r--src/SCompression.cpp53
-rw-r--r--src/SFileCompactArchive.cpp17
-rw-r--r--src/SFileCreateArchive.cpp5
-rw-r--r--src/SFileFindFile.cpp1
-rw-r--r--src/SFileOpenArchive.cpp39
-rw-r--r--src/SFileOpenFileEx.cpp16
-rw-r--r--src/SFileReadFile.cpp306
-rw-r--r--src/StormCommon.h23
-rw-r--r--src/StormLib.h89
-rw-r--r--src/StormPort.h9
-rw-r--r--test/Test.cpp23
20 files changed, 1506 insertions, 647 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e5b869..3fb206a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -223,7 +223,7 @@ set(ZLIB_BZIP2_FILES
src/bzip2/huffman.c
src/bzip2/randtable.c
src/zlib/adler32.c
- src/zlib/compress2.c
+ src/zlib/compress.c
src/zlib/crc32.c
src/zlib/deflate.c
src/zlib/inffast.c
diff --git a/Makefile.linux b/Makefile.linux
index e9d89c7..34440cf 100644
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -231,7 +231,7 @@ COBJS = src/libtomcrypt/src/hashes/sha1.o \
src/pklib/explode.o \
src/zlib/crc32.o \
src/zlib/trees.o \
- src/zlib/compress2.o \
+ src/zlib/compress.o \
src/zlib/adler32.o \
src/zlib/inftrees.o \
src/zlib/inffast.o \
diff --git a/StormLib.vcproj b/StormLib.vcproj
index 8475695..8fc4f4e 100644
--- a/StormLib.vcproj
+++ b/StormLib.vcproj
@@ -1559,6 +1559,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath=".\src\SBaseSubTypes.cpp"
+ >
+ </File>
+ <File
RelativePath=".\src\SCompression.cpp"
>
<FileConfiguration
@@ -3690,50 +3694,156 @@
/>
</FileConfiguration>
</File>
- <Filter Name="adpcm">
- <File RelativePath=".\src\adpcm\adpcm.cpp" />
- <File RelativePath=".\src\adpcm\adpcm.h" />
+ <Filter
+ Name="adpcm"
+ >
+ <File
+ RelativePath=".\src\adpcm\adpcm.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\adpcm\adpcm.h"
+ >
+ </File>
</Filter>
- <Filter Name="bzip2">
- <File RelativePath=".\src\bzip2\blocksort.c" />
- <File RelativePath=".\src\bzip2\bzlib.c" />
- <File RelativePath=".\src\bzip2\compress.c" />
- <File RelativePath=".\src\bzip2\crctable.c" />
- <File RelativePath=".\src\bzip2\decompress.c"/>
- <File RelativePath=".\src\bzip2\huffman.c" />
- <File RelativePath=".\src\bzip2\randtable.c" />
+ <Filter
+ Name="bzip2"
+ >
+ <File
+ RelativePath=".\src\bzip2\blocksort.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\bzip2\bzlib.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\bzip2\compress.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\bzip2\crctable.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\bzip2\decompress.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\bzip2\huffman.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\bzip2\randtable.c"
+ >
+ </File>
</Filter>
- <Filter Name="huffman">
- <File RelativePath=".\src\huffman\huff.cpp" />
- <File RelativePath=".\src\huffman\huff.h" />
+ <Filter
+ Name="huffman"
+ >
+ <File
+ RelativePath=".\src\huffman\huff.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\huffman\huff.h"
+ >
+ </File>
</Filter>
- <Filter Name="libtomcrypt">
- <Filter Name="hashes">
- <File RelativePath=".\src\libtomcrypt\src\hashes\hash_memory.c" />
- <File RelativePath=".\src\libtomcrypt\src\hashes\md5.c" />
- <File RelativePath=".\src\libtomcrypt\src\hashes\sha1.c" />
+ <Filter
+ Name="libtomcrypt"
+ >
+ <Filter
+ Name="hashes"
+ >
+ <File
+ RelativePath=".\src\libtomcrypt\src\hashes\hash_memory.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\hashes\md5.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\hashes\sha1.c"
+ >
+ </File>
</Filter>
- <Filter Name="math">
- <File RelativePath=".\src\libtomcrypt\src\math\ltm_desc.c" />
- <File RelativePath=".\src\libtomcrypt\src\math\multi.c" />
- <File RelativePath=".\src\libtomcrypt\src\math\rand_prime.c" />
+ <Filter
+ Name="math"
+ >
+ <File
+ RelativePath=".\src\libtomcrypt\src\math\ltm_desc.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\math\multi.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\math\rand_prime.c"
+ >
+ </File>
</Filter>
- <Filter Name="misc">
- <File RelativePath=".\src\libtomcrypt\src\misc\base64_decode.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_argchk.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_find_hash.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_find_prng.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_descriptor.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_is_valid.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_libc.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_ltc_mp_descriptor.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_descriptor.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_is_valid.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_register_hash.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\crypt_register_prng.c" />
- <File RelativePath=".\src\libtomcrypt\src\misc\zeromem.c" />
+ <Filter
+ Name="misc"
+ >
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\base64_decode.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_argchk.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_find_hash.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_find_prng.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_descriptor.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_hash_is_valid.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_libc.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_ltc_mp_descriptor.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_descriptor.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_prng_is_valid.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_register_hash.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\crypt_register_prng.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtomcrypt\src\misc\zeromem.c"
+ >
+ </File>
</Filter>
- <Filter Name="pk">
+ <Filter
+ Name="pk"
+ >
<Filter
Name="asn1"
>
@@ -3923,9 +4033,15 @@
<Filter
Name="libtommath"
>
- <File RelativePath=".\src\libtommath\bn_fast_mp_invmod.c" />
- <File RelativePath=".\src\libtommath\bn_fast_mp_montgomery_reduce.c" />
- <File
+ <File
+ RelativePath=".\src\libtommath\bn_fast_mp_invmod.c"
+ >
+ </File>
+ <File
+ RelativePath=".\src\libtommath\bn_fast_mp_montgomery_reduce.c"
+ >
+ </File>
+ <File
RelativePath=".\src\libtommath\bn_fast_s_mp_mul_digs.c"
>
</File>
diff --git a/StormLib_dll.vcproj b/StormLib_dll.vcproj
index 92b5ef0..4991465 100644
--- a/StormLib_dll.vcproj
+++ b/StormLib_dll.vcproj
@@ -553,6 +553,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath=".\src\SBaseSubTypes.cpp"
+ >
+ </File>
+ <File
RelativePath=".\src\SCompression.cpp"
>
<FileConfiguration
diff --git a/StormLib_test.vcproj b/StormLib_test.vcproj
index d928bbe..61e04ea 100644
--- a/StormLib_test.vcproj
+++ b/StormLib_test.vcproj
@@ -523,6 +523,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath=".\src\SBaseSubTypes.cpp"
+ >
+ </File>
+ <File
RelativePath=".\src\SCompression.cpp"
>
<FileConfiguration
diff --git a/makefile.w32 b/makefile.w32
index 8826ccd..2e3b743 100644
--- a/makefile.w32
+++ b/makefile.w32
@@ -235,7 +235,7 @@ OBJS_C = src/bzip2/blocksort.o \
src/pklib/explode.o \
src/pklib/implode.o \
src/zlib/adler32.o \
- src/zlib/compress2.o \
+ src/zlib/compress.o \
src/zlib/crc32.o \
src/zlib/deflate.o \
src/zlib/inffast.o \
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index 58b2fd5..78537cf 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -26,7 +26,8 @@ USHORT wPlatform = 0; // File platform
//-----------------------------------------------------------------------------
// Conversion to uppercase/lowercase
-// This table converts ASCII characters to lowercase and slash to backslash
+// Converts ASCII characters to lowercase
+// Converts slash (0x2F) to backslash (0x5C)
unsigned char AsciiToLowerTable[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
@@ -47,13 +48,34 @@ unsigned char AsciiToLowerTable[256] =
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
-// This table converts ASCII characters to uppercase and slash to backslash
-// BUGBUG: Reverted conversion of normal slash to backslash
-// Will we have issues on Linux/Mac, when adding files like /home/Ladik/Files/File.ext ?
+// Converts ASCII characters to uppercase
+// Converts slash (0x2F) to backslash (0x5C)
unsigned char AsciiToUpperTable[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+// Converts ASCII characters to uppercase
+// Does NOT convert slash (0x2F) to backslash (0x5C)
+unsigned char AsciiToUpperTable_Slash[256] =
+{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
@@ -126,9 +148,8 @@ DWORD HashString(const char * szFileName, DWORD dwHashType)
while(*pbKey != 0)
{
- // Note: AsciiToUpperTable conversion table must not convert '/' to '\',
- // due to SQP data files for War of the Immortal,
- // who commonly use file names like "../Data/Task/1315.str"
+ // Convert the input character to uppercase
+ // Convert slash (0x2F) to backslash (0x5C)
ch = AsciiToUpperTable[*pbKey++];
dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
@@ -138,6 +159,46 @@ DWORD HashString(const char * szFileName, DWORD dwHashType)
return dwSeed1;
}
+DWORD HashStringSlash(const char * szFileName, DWORD dwHashType)
+{
+ LPBYTE pbKey = (BYTE *)szFileName;
+ DWORD dwSeed1 = 0x7FED7FED;
+ DWORD dwSeed2 = 0xEEEEEEEE;
+ DWORD ch;
+
+ while(*pbKey != 0)
+ {
+ // Convert the input character to uppercase
+ // DON'T convert slash (0x2F) to backslash (0x5C)
+ ch = AsciiToUpperTable_Slash[*pbKey++];
+
+ dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
+ dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
+ }
+
+ return dwSeed1;
+}
+
+DWORD HashStringLower(const char * szFileName, DWORD dwHashType)
+{
+ LPBYTE pbKey = (BYTE *)szFileName;
+ DWORD dwSeed1 = 0x7FED7FED;
+ DWORD dwSeed2 = 0xEEEEEEEE;
+ DWORD ch;
+
+ while(*pbKey != 0)
+ {
+ // Convert the input character to lower
+ // DON'T convert slash (0x2F) to backslash (0x5C)
+ ch = AsciiToLowerTable[*pbKey++];
+
+ dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
+ dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
+ }
+
+ return dwSeed1;
+}
+
//-----------------------------------------------------------------------------
// Calculates the hash table size for a given amount of files
@@ -191,71 +252,34 @@ ULONGLONG HashStringJenkins(const char * szFileName)
}
//-----------------------------------------------------------------------------
-// Processes War of the Immortals data files (SQP)
-//
+// Copies the string from char * to TCHAR * and back
-int ConvertSqpHeaderToFormat4(
- TMPQArchive * ha,
- ULONGLONG FileSize,
- DWORD dwFlags)
-{
- TMPQHeader * pHeader = ha->pHeader;
- TSQPHeader SqpHeader;
+#ifdef _UNICODE
- // SQP files from War of the Immortal use MPQ file format with slightly
- // modified structure. These fields have different position:
- //
- // Offset TMPQHeader TSQPHeader
- // ------ ---------- -----------
- // 000C wFormatVersion dwHashTablePos (lo)
- // 000E wSectorSize dwHashTablePos (hi)
- // 001C dwBlockTableSize (lo) wBlockSize
- // 001E dwHashTableSize (hi) wFormatVersion
-
- // Can't open the archive with ceraint flags
- if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
- return ERROR_FILE_CORRUPT;
-
- // The file must not be greater than 4 GB
- if((FileSize >> 0x20) != 0)
- return ERROR_FILE_CORRUPT;
+// For UNICODE builds, we need two functions
+void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength)
+{
+ mbstowcs(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
- // Copy the MPQ header into the SQP header
- memcpy(&SqpHeader, ha->pHeader, sizeof(TSQPHeader));
- assert(SqpHeader.dwHeaderSize == sizeof(TSQPHeader));
+void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength)
+{
+ wcstombs(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
- // SQP format uses header size of 0x20
- if(SqpHeader.dwID == ID_MPQ && SqpHeader.dwHeaderSize == MPQ_HEADER_SIZE_V1 && SqpHeader.dwArchiveSize == FileSize)
- {
- // Check for fixed value in the SQP files
- if(SqpHeader.wFormatVersion == MPQ_FORMAT_VERSION_1 && SqpHeader.wSectorSize == 3)
- {
- // Translate the SQP header into MPQ Header
- pHeader->wFormatVersion = SqpHeader.wFormatVersion;
- pHeader->wSectorSize = SqpHeader.wSectorSize;
- pHeader->dwHashTablePos = SqpHeader.dwHashTablePos;
- pHeader->dwBlockTablePos = SqpHeader.dwBlockTablePos;
- pHeader->dwHashTableSize = SqpHeader.dwHashTableSize;
- pHeader->dwBlockTableSize = SqpHeader.dwBlockTableSize;
-
- // Initialize the fields of header 2.0+
- memset(&pHeader->HiBlockTablePos64, 0, MPQ_HEADER_SIZE_V4 - MPQ_HEADER_SIZE_V1);
-
- // Initialize the fields of 3.0 header
- pHeader->ArchiveSize64 = SqpHeader.dwArchiveSize;
- pHeader->HashTableSize64 = SqpHeader.dwHashTableSize * sizeof(TMPQHash);
- pHeader->BlockTableSize64 = SqpHeader.dwBlockTableSize * sizeof(TMPQBlock);
-
- // Mark this file as SQP file
- ha->dwFlags |= MPQ_FLAG_READ_ONLY;
- ha->dwSubType = MPQ_SUBTYPE_SQP;
- return ERROR_SUCCESS;
- }
- }
+#else
- return ERROR_FILE_CORRUPT;
+// 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
@@ -264,10 +288,10 @@ int ConvertMpqHeaderToFormat4(
ULONGLONG FileSize,
DWORD dwFlags)
{
+ TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData;
ULONGLONG ByteOffset;
- TMPQHeader * pHeader = ha->pHeader;
DWORD dwExpectedArchiveSize;
- USHORT wFormatVersion = pHeader->wFormatVersion;
+ 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
@@ -280,6 +304,9 @@ int ConvertMpqHeaderToFormat4(
{
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)
{
@@ -314,6 +341,9 @@ int ConvertMpqHeaderToFormat4(
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
@@ -414,9 +444,12 @@ int ConvertMpqHeaderToFormat4(
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(ha->pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, ha->pHeader->MD5_MpqHeader))
+ if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader))
nError = ERROR_FILE_CORRUPT;
break;
@@ -487,18 +520,6 @@ void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwSeed1)
}
}
-/*
-void EncryptMpqTable(void * pvMpqTable, DWORD dwLength, const char * szKey)
-{
- EncryptMpqBlock(pvMpqTable, dwLength, HashString(szKey, MPQ_HASH_FILE_KEY));
-}
-
-void DecryptMpqTable(void * pvMpqTable, DWORD dwLength, const char * szKey)
-{
- DecryptMpqBlock(pvMpqTable, dwLength, HashString(szKey, MPQ_HASH_FILE_KEY));
-}
-*/
-
/**
* Functions tries to get file decryption key. The trick comes from sector
* positions which are stored at the begin of each compressed file. We know the
@@ -684,114 +705,139 @@ bool IsValidFileHandle(TMPQFile * hf)
//-----------------------------------------------------------------------------
// Hash table and block table manipulation
+// Attempts to search a free hash entry, or an entry whose names and locale matches
+TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale)
+{
+ 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 dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask);
+
+ // Search the hash table and return the found entries in the following priority:
+ // 1) <MATCHING_ENTRY>
+ // 2) <DELETED-ENTRY>
+ // 3) <FREE-ENTRY>
+ // 4) NULL
+ for(;;)
+ {
+ TMPQHash * pHash = ha->pHashTable + dwIndex;
+
+ // If we found a matching entry, return that one
+ if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == lcLocale)
+ return pHash;
+
+ // If we found a deleted entry, remember it but keep searching
+ if(pHash->dwBlockIndex == HASH_ENTRY_DELETED && pDeletedEntry == NULL)
+ pDeletedEntry = pHash;
+
+ // If we found a free entry, we need to stop searching
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ {
+ pFreeEntry = pHash;
+ break;
+ }
+
+ // Move to the next hash entry.
+ // If we reached the starting entry, it's failure.
+ dwIndex = (dwIndex + 1) & ha->dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ break;
+ }
+
+ // If we found a deleted entry, return that one preferentially
+ 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)
{
- TMPQHash * pStartHash; // File hash entry (start)
- TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
- TMPQHash * pHash; // File hash entry (current)
- DWORD dwHashTableSizeMask;
- DWORD dwIndex = HashString(szFileName, MPQ_HASH_TABLE_INDEX);
- DWORD dwName1 = HashString(szFileName, MPQ_HASH_NAME_A);
- DWORD dwName2 = HashString(szFileName, MPQ_HASH_NAME_B);
-
- // Get the first possible has entry that might be the one
- dwHashTableSizeMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0;
- pStartHash = pHash = ha->pHashTable + (dwIndex & dwHashTableSizeMask);
-
- // There might be deleted entries in the hash table prior to our desired entry.
- while(pHash->dwBlockIndex != HASH_ENTRY_FREE)
+ 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);
+
+ // Search the hash table
+ for(;;)
{
- // If the entry agrees, we found it.
+ TMPQHash * pHash = ha->pHashTable + dwIndex;
+
+ // If the entry matches, we found it.
if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize)
return pHash;
+ // If that hash entry is a free entry, it means we haven't found the file
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ return NULL;
+
// Move to the next hash entry. Stop searching
// if we got reached the original hash entry
- if(++pHash >= pHashEnd)
- pHash = ha->pHashTable;
- if(pHash == pStartHash)
- break;
+ dwIndex = (dwIndex + 1) & ha->dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ return NULL;
}
-
- // The apropriate hash entry was not found
- return NULL;
}
-TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash)
+TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
{
- TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
- TMPQHash * pHash = pPrevHash;
- DWORD dwName1 = pPrevHash->dwName1;
- DWORD dwName2 = pPrevHash->dwName2;
+ DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
+ DWORD dwName1 = pHash->dwName1;
+ DWORD dwName2 = pHash->dwName2;
+ DWORD dwIndex = (DWORD)(pHash - ha->pHashTable);
- // Now go for any next entry that follows the pPrevHash,
+ // Now go for any next entry that follows the pHash,
// until either free hash entry was found, or the start entry was reached
for(;;)
{
// Move to the next hash entry. Stop searching
// if we got reached the original hash entry
- if(++pHash >= pHashEnd)
- pHash = ha->pHashTable;
- if(pHash == pFirstHash)
- break;
-
- // If the entry is a free entry, stop search
- if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
- break;
+ dwIndex = (dwIndex + 1) & ha->dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ return NULL;
+ pHash = ha->pHashTable + dwIndex;
- // If the entry is not free and the name agrees, we found it
+ // If the entry matches, we found it.
if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->pHeader->dwBlockTableSize)
return pHash;
- }
- // No next entry
- return NULL;
+ // If that hash entry is a free entry, it means we haven't found the file
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ return NULL;
+ }
}
// Allocates an entry in the hash table
-DWORD AllocateHashEntry(
+TMPQHash * AllocateHashEntry(
TMPQArchive * ha,
TFileEntry * pFileEntry)
{
- TMPQHash * pStartHash; // File hash entry (start)
- TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
- TMPQHash * pHash; // File hash entry (current)
- DWORD dwHashTableSizeMask;
- DWORD dwIndex = HashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX);
- DWORD dwName1 = HashString(pFileEntry->szFileName, MPQ_HASH_NAME_A);
- DWORD dwName2 = HashString(pFileEntry->szFileName, MPQ_HASH_NAME_B);
-
- // Get the first possible has entry that might be the one
- dwHashTableSizeMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0;
- pStartHash = pHash = ha->pHashTable + (dwIndex & dwHashTableSizeMask);
-
- // There might be deleted entries in the hash table prior to our desired entry.
- while(pHash->dwBlockIndex < HASH_ENTRY_DELETED)
+ TMPQHash * pHash;
+ DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX);
+ DWORD dwName1 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_A);
+ DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B);
+
+ // Attempt to find a free hash entry
+ pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, pFileEntry->lcLocale);
+ if(pHash != NULL)
{
- // If there already is an existing entry, reuse it.
- if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == pFileEntry->lcLocale)
- break;
-
- // Move to the next hash entry.
- // If we reached the starting entry, it's failure.
- if(++pHash >= pHashEnd)
- pHash = ha->pHashTable;
- if(pHash == pStartHash)
- return HASH_ENTRY_FREE;
+ // Fill the free hash entry
+ pHash->dwName1 = dwName1;
+ pHash->dwName2 = dwName2;
+ pHash->lcLocale = pFileEntry->lcLocale;
+ pHash->wPlatform = pFileEntry->wPlatform;
+ pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
+
+ // Fill the hash index in the file entry
+ pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
}
- // Fill the free hash entry
- pHash->dwName1 = dwName1;
- pHash->dwName2 = dwName2;
- pHash->lcLocale = pFileEntry->lcLocale;
- pHash->wPlatform = pFileEntry->wPlatform;
- pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
-
- // Fill the hash index in the file entry
- pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
- return pFileEntry->dwHashIndex;
+ return pHash;
}
// Finds a free space in the MPQ where to store next data
@@ -855,62 +901,78 @@ TMPQFile * CreateMpqFile(TMPQArchive * ha)
// Loads a table from MPQ.
// Can be used for hash table, block table, sector offset table or sector checksum table
-int LoadMpqTable(
+void * LoadMpqTable(
TMPQArchive * ha,
ULONGLONG ByteOffset,
- void * pvTable,
DWORD dwCompressedSize,
DWORD dwRealSize,
DWORD dwKey)
{
LPBYTE pbCompressed = NULL;
- LPBYTE pbToRead = (LPBYTE)pvTable;
+ LPBYTE pbMpqTable;
+ LPBYTE pbToRead;
int nError = ERROR_SUCCESS;
- // "interface.MPQ.part" in trial version of World of Warcraft
- // has block table and hash table compressed.
- if(dwCompressedSize < dwRealSize)
+ // Allocate the MPQ table
+ pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwRealSize);
+ if(pbMpqTable != NULL)
{
- // Allocate temporary buffer for holding compressed data
- pbCompressed = STORM_ALLOC(BYTE, dwCompressedSize);
- if(pbCompressed == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
+ // "interface.MPQ.part" in trial version of World of Warcraft
+ // has block table and hash table compressed.
+ if(dwCompressedSize < dwRealSize)
+ {
+ // Allocate temporary buffer for holding compressed data
+ pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
+ if(pbCompressed == NULL)
+ {
+ STORM_FREE(pbMpqTable);
+ return NULL;
+ }
+ }
- // Assign the temporary buffer as target for read operation
- pbToRead = pbCompressed;
- }
+ // If everything succeeded, read the raw table form the MPQ
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize))
+ {
+ // First of all, decrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ }
- // Read the table
- if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize))
- {
- // First of all, decrypt the table
- if(dwKey != 0)
+ // If the table is compressed, decompress it
+ if(dwCompressedSize < dwRealSize)
+ {
+ int cbOutBuffer = (int)dwRealSize;
+ 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);
+ }
+ else
{
- BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
- DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
- BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ nError = GetLastError();
}
- // If the table is compressed, decompress it
- if(dwCompressedSize < dwRealSize)
+ // If read failed, free the table and return
+ if(nError != ERROR_SUCCESS)
{
- int cbOutBuffer = (int)dwRealSize;
- int cbInBuffer = (int)dwCompressedSize;
-
- if(!SCompDecompress2(pvTable, &cbOutBuffer, pbCompressed, cbInBuffer))
- nError = GetLastError();
+ STORM_FREE(pbMpqTable);
+ pbMpqTable = NULL;
+ }
- // Free the temporary buffer
+ // Free the compression buffer, if any
+ if(pbCompressed != NULL)
STORM_FREE(pbCompressed);
- }
- }
- else
- {
- nError = GetLastError();
}
- BSWAP_ARRAY32_UNSIGNED(pvTable, dwRealSize);
- return nError;
+ // Return the MPQ table
+ return pbMpqTable;
}
void CalculateRawSectorOffset(
@@ -1259,18 +1321,15 @@ int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize)
return ERROR_SUCCESS;
- // Allocate the array for the sector checksums
- hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount);
- if(hf->SectorChksums == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
// Calculate offset of the CRC table
dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount];
CalculateRawSectorOffset(RawFilePos, hf, dwCrcOffset);
// Now read the table from the MPQ
- return LoadMpqTable(ha, RawFilePos, hf->SectorChksums, dwCompressedSize, dwCrcSize, 0);
+ hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0);
+ if(hf->SectorChksums == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
}
}
@@ -1778,44 +1837,42 @@ void ConvertTMPQUserData(void *userData)
}
// Swaps the TMPQHeader structure
-void ConvertTMPQHeader(void *header)
+void ConvertTMPQHeader(void *header, uint16_t version)
{
TMPQHeader * theHeader = (TMPQHeader *)header;
-
- theHeader->dwID = SwapUInt32(theHeader->dwID);
- theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize);
- theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize);
- theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion);
- theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize);
- theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos);
- theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos);
- theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize);
- theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize);
-
- if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2)
+
+ // Swap header part version 1
+ if(version == MPQ_FORMAT_VERSION_1)
+ {
+ theHeader->dwID = SwapUInt32(theHeader->dwID);
+ theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize);
+ theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize);
+ theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion);
+ theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize);
+ theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos);
+ theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos);
+ theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize);
+ theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize);
+ }
+
+ if(version == MPQ_FORMAT_VERSION_2 || version == MPQ_FORMAT_VERSION_3)
{
- // Swap the hi-block table position
theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
-
theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
+ theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
+ theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
+ theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
+ }
- if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3)
- {
- theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
- theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
- theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
-
- if(theHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4)
- {
- theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64);
- theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64);
- theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64);
- theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64);
- theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64);
- }
- }
- }
+ if(version == MPQ_FORMAT_VERSION_4)
+ {
+ theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64);
+ theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64);
+ theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64);
+ theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64);
+ theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64);
+ }
}
#endif // PLATFORM_LITTLE_ENDIAN
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index 7b6d2a6..011badf 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -394,52 +394,6 @@ static int ConvertMpqBlockTable(
return ERROR_SUCCESS;
}
-static int ConvertSqpBlockTable(
- TMPQArchive * ha,
- TFileEntry * pFileTable,
- TSQPBlock * pBlockTable)
-{
- TFileEntry * pFileEntry;
- TMPQHeader * pHeader = ha->pHeader;
- TSQPBlock * pSqpBlock;
- TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize;
- TMPQHash * pHash;
-
- for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
- {
- if(pHash->dwBlockIndex < pHeader->dwBlockTableSize)
- {
- pFileEntry = pFileTable + pHash->dwBlockIndex;
- pSqpBlock = pBlockTable + pHash->dwBlockIndex;
-
- if(!(pSqpBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pSqpBlock->dwFlags & MPQ_FILE_EXISTS))
- {
- // Convert SQP block table entry to the file entry
- pFileEntry->ByteOffset = pSqpBlock->dwFilePos;
- pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable);
- pFileEntry->dwFileSize = pSqpBlock->dwFSize;
- pFileEntry->dwCmpSize = pSqpBlock->dwCSize;
- pFileEntry->dwFlags = pSqpBlock->dwFlags;
- pFileEntry->lcLocale = pHash->lcLocale;
- pFileEntry->wPlatform = pHash->wPlatform;
- }
- else
- {
- // If the hash table entry doesn't point to the valid file item,
- // we invalidate the hash table entry
- pHash->dwName1 = 0xFFFFFFFF;
- pHash->dwName2 = 0xFFFFFFFF;
- pHash->lcLocale = 0xFFFF;
- pHash->wPlatform = 0xFFFF;
- pHash->dwBlockIndex = HASH_ENTRY_DELETED;
- }
- }
- }
-
- return ERROR_SUCCESS;
-}
-
-
static TMPQHash * TranslateHashTable(
TMPQArchive * ha,
ULONGLONG * pcbTableSize)
@@ -1654,8 +1608,8 @@ TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID l
// If the MPQ has hash table, we have to insert the new entry into the hash table
if(ha->pHashTable != NULL && bHashEntryExists == false)
{
- dwHashIndex = AllocateHashEntry(ha, pFileEntry);
- assert(dwHashIndex != HASH_ENTRY_FREE);
+ pHash = AllocateHashEntry(ha, pFileEntry);
+ assert(pHash != NULL);
}
// If the MPQ has HET table, we have to insert it to the HET table as well
@@ -1721,8 +1675,8 @@ int RenameFileEntry(
{
// Try to find the hash table entry for the new file name
// Note: If this fails, we leave the MPQ in a corrupt state
- dwFileIndex = AllocateHashEntry(ha, pFileEntry);
- if(dwFileIndex == HASH_ENTRY_FREE)
+ pHash = AllocateHashEntry(ha, pFileEntry);
+ if(pHash == NULL)
nError = ERROR_FILE_CORRUPT;
}
@@ -1976,52 +1930,13 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize)
return ERROR_SUCCESS;
}
-int VerifyAndConvertSqpHashTable(TMPQArchive * ha, TMPQHash * pHashTable, DWORD dwTableSize)
-{
- TMPQHash * pMpqHashEnd = pHashTable + dwTableSize;
- TSQPHash * pSqpHash = (TSQPHash *)pHashTable;
- TMPQHash * pMpqHash;
- DWORD dwBlockIndex;
-
- // Parse all SQP hash table entries
- for(pMpqHash = pHashTable; pMpqHash < pMpqHashEnd; pMpqHash++, pSqpHash++)
- {
- // Ignore free entries
- if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE)
- {
- // Check block index against the size of the block table
- dwBlockIndex = pSqpHash->dwBlockIndex;
- if(ha->pHeader->dwBlockTableSize <= dwBlockIndex && dwBlockIndex < HASH_ENTRY_DELETED)
- return ERROR_FILE_CORRUPT;
-
- // We do not support nonzero locale and platform ID
- if(pSqpHash->dwAlwaysZero != 0 && pSqpHash->dwAlwaysZero != HASH_ENTRY_FREE)
- return ERROR_FILE_CORRUPT;
-
- // Store the file name hash
- pMpqHash->dwName1 = pSqpHash->dwName1;
- pMpqHash->dwName2 = pSqpHash->dwName2;
-
- // Store the rest. Note that this must be done last,
- // because pSqpHash->dwBlockIndex corresponds to pMpqHash->dwName2
- pMpqHash->dwBlockIndex = dwBlockIndex;
- pMpqHash->wPlatform = 0;
- pMpqHash->lcLocale = 0;
- }
- }
-
- // The conversion went OK
- return ERROR_SUCCESS;
-}
-
TMPQHash * LoadHashTable(TMPQArchive * ha)
{
TMPQHeader * pHeader = ha->pHeader;
ULONGLONG ByteOffset;
- TMPQHash * pHashTable;
+ TMPQHash * pHashTable = NULL;
DWORD dwTableSize;
DWORD dwCmpSize;
- int nError;
// If the MPQ has no hash table, do nothing
if(pHeader->dwHashTablePos == 0 && pHeader->wHashTablePosHi == 0)
@@ -2031,37 +1946,40 @@ TMPQHash * LoadHashTable(TMPQArchive * ha)
if(pHeader->dwHashTableSize == 0)
return NULL;
- // Allocate buffer for the hash table
- dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash);
- pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize);
- if(pHashTable == NULL)
- return NULL;
+ // Load the hash table for MPQ variations
+ switch(ha->dwSubType)
+ {
+ case MPQ_SUBTYPE_MPQ:
- // Compressed size of the hash table
- dwCmpSize = (DWORD)pHeader->HashTableSize64;
+ // Get the compressed and uncompressed hash table size
+ dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash);
+ dwCmpSize = (DWORD)pHeader->HashTableSize64;
- //
- // Load the table from the MPQ, with decompression
- //
- // Note: We will NOT check if the hash table is properly decrypted.
- // Some MPQ protectors corrupt the hash table by rewriting part of it.
- // Hash table, the way how it works, allows arbitrary values for unused entries.
- //
+ //
+ // Load the table from the MPQ, with decompression
+ //
+ // Note: We will NOT check if the hash table is properly decrypted.
+ // Some MPQ protectors corrupt the hash table by rewriting part of it.
+ // Hash table, the way how it works, allows arbitrary values for unused entries.
+ //
- ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
- nError = LoadMpqTable(ha, ByteOffset, pHashTable, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE);
-
- // If the file is a SQP file, we need to verify the SQP hash table and convert it to MPQ hash table
- if(ha->dwSubType == MPQ_SUBTYPE_SQP)
- nError = VerifyAndConvertSqpHashTable(ha, pHashTable, pHeader->dwHashTableSize);
-
- // If anything failed, free the hash table
- if(nError != ERROR_SUCCESS)
- {
- STORM_FREE(pHashTable);
- pHashTable = NULL;
+ ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
+ pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE);
+ break;
+
+ case MPQ_SUBTYPE_SQP:
+ pHashTable = LoadSqpHashTable(ha);
+ break;
+
+ case MPQ_SUBTYPE_MPK:
+ pHashTable = LoadMpkHashTable(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;
}
@@ -2103,11 +2021,10 @@ static void FixBlockTableSize(
TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
{
TMPQHeader * pHeader = ha->pHeader;
- TMPQBlock * pBlockTable;
+ TMPQBlock * pBlockTable = NULL;
ULONGLONG ByteOffset;
DWORD dwTableSize;
DWORD dwCmpSize;
- int nError;
// Do nothing if the block table position is zero
if(pHeader->dwBlockTablePos == 0 && pHeader->wBlockTablePosHi == 0)
@@ -2117,50 +2034,54 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
if(pHeader->dwBlockTableSize == 0)
return NULL;
- // Sanity check, enforced by LoadAnyHashTable
- assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize);
+ // Load the block table for MPQ variations
+ switch(ha->dwSubType)
+ {
+ 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;
+ // Calculate sizes of both tables
+ ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ dwCmpSize = (DWORD)pHeader->BlockTableSize64;
- // Allocate space for the block table
- // Note: pHeader->dwBlockTableSize can be zero !!!
- pBlockTable = STORM_ALLOC(TMPQBlock, ha->dwMaxFileCount);
- if(pBlockTable == NULL)
- return NULL;
+ // I found a MPQ which claimed 0x200 entries in the block table,
+ // but the file was cut and there was only 0x1A0 entries.
+ // We will handle this case properly.
+ if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize)
+ {
+ pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock));
+ pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ }
- // Fill the block table with zeros
- memset(pBlockTable, 0, dwTableSize);
+ //
+ // One of the first cracked versions of Diablo I had block table unencrypted
+ // StormLib does NOT support such MPQs anymore, as they are incompatible
+ // with compressed block table feature
+ //
- // I found a MPQ which claimed 0x200 entries in the block table,
- // but the file was cut and there was only 0x1A0 entries.
- // We will handle this case properly.
- if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize)
- {
- pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock));
- pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
- dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
- }
+ // If failed to load the block table, delete it
+ pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE);
- //
- // One of the first cracked versions of Diablo I had block table unencrypted
- // StormLib does NOT support such MPQs anymore, as they are incompatible
- // with compressed block table feature
- //
+ // Defense against MPQs that claim block table to be bigger than it really is
+ if(pBlockTable != NULL)
+ FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize);
+ break;
- // Load the block table
- nError = LoadMpqTable(ha, ByteOffset, pBlockTable, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE);
- if(nError != ERROR_SUCCESS)
- {
- // Failed, sorry
- STORM_FREE(pBlockTable);
- return NULL;
+ case MPQ_SUBTYPE_SQP:
+
+ pBlockTable = LoadSqpBlockTable(ha);
+ break;
+
+ case MPQ_SUBTYPE_MPK:
+
+ pBlockTable = LoadMpkBlockTable(ha);
+ break;
}
- // Defense against MPQs that claim block table to be bigger than it really is
- FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize);
return pBlockTable;
}
@@ -2260,14 +2181,14 @@ int BuildFileTable_Classic(
{
TFileEntry * pFileEntry;
TMPQHeader * pHeader = ha->pHeader;
- void * pBlockTable;
+ TMPQBlock * pBlockTable;
int nError = ERROR_SUCCESS;
// Sanity checks
assert(ha->pHashTable != NULL);
// Load the block table
- pBlockTable = LoadBlockTable(ha, FileSize);
+ pBlockTable = (TMPQBlock *)LoadBlockTable(ha, FileSize);
if(pBlockTable != NULL)
{
TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize;
@@ -2276,11 +2197,7 @@ int BuildFileTable_Classic(
// If we don't have HET table, we build the file entries from the hash&block tables
if(ha->pHetTable == NULL)
{
- // For MPQ files, treat the block table as MPQ Block Table
- if(ha->dwSubType == MPQ_SUBTYPE_MPQ)
- nError = ConvertMpqBlockTable(ha, pFileTable, (TMPQBlock *)pBlockTable);
- else
- nError = ConvertSqpBlockTable(ha, pFileTable, (TSQPBlock *)pBlockTable);
+ nError = ConvertMpqBlockTable(ha, pFileTable, pBlockTable);
}
else
{
@@ -2636,6 +2553,8 @@ int SaveMPQTables(TMPQArchive * ha)
// Write the MPQ header
if(nError == ERROR_SUCCESS)
{
+ TMPQHeader SaveMpqHeader;
+
// Update the size of the archive
pHeader->ArchiveSize64 = TablePos;
pHeader->dwArchiveSize = (DWORD)TablePos;
@@ -2644,10 +2563,12 @@ int SaveMPQTables(TMPQArchive * ha)
CalculateDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader);
// Write the MPQ header to the file
- BSWAP_TMPQHEADER(pHeader);
- if(!FileStream_Write(ha->pStream, &ha->MpqPos, pHeader, pHeader->dwHeaderSize))
+ memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4);
+ if(!FileStream_Write(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize))
nError = GetLastError();
- BSWAP_TMPQHEADER(pHeader);
}
// Clear the changed flag
diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp
new file mode 100644
index 0000000..5c78004
--- /dev/null
+++ b/src/SBaseSubTypes.cpp
@@ -0,0 +1,598 @@
+/*****************************************************************************/
+/* SBaseSubTypes.cpp Copyright (c) Ladislav Zezula 2013 */
+/*---------------------------------------------------------------------------*/
+/* Conversion routines for archive formats that are similar to MPQ format */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 02.11.11 1.00 Lad The first version of SBaseSubTypes.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+/*****************************************************************************/
+/* */
+/* Support for SQP file format (War of the Immortals) */
+/* */
+/*****************************************************************************/
+
+typedef struct _TSQPHeader
+{
+ // The ID_MPQ ('MPQ\x1A') signature
+ DWORD dwID;
+
+ // Size of the archive header
+ DWORD dwHeaderSize;
+
+ // 32-bit size of MPQ archive
+ DWORD dwArchiveSize;
+
+ // Offset to the beginning of the hash table, relative to the beginning of the archive.
+ DWORD dwHashTablePos;
+
+ // Offset to the beginning of the block table, relative to the beginning of the archive.
+ DWORD dwBlockTablePos;
+
+ // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for
+ // the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
+ DWORD dwHashTableSize;
+
+ // Number of entries in the block table
+ DWORD dwBlockTableSize;
+
+ // Must be zero for SQP files
+ USHORT wFormatVersion;
+
+ // Power of two exponent specifying the number of 512-byte disk sectors in each file sector
+ // in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize.
+ USHORT wSectorSize;
+
+} TSQPHeader;
+
+typedef struct _TSQPHash
+{
+ // Most likely the lcLocale+wPlatform.
+ DWORD dwAlwaysZero;
+
+ // If the hash table entry is valid, this is the index into the block table of the file.
+ // Otherwise, one of the following two values:
+ // - FFFFFFFFh: Hash table entry is empty, and has always been empty.
+ // Terminates searches for a given file.
+ // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
+ // Does not terminate searches for a given file.
+ DWORD dwBlockIndex;
+
+ // The hash of the file path, using method A.
+ DWORD dwName1;
+
+ // The hash of the file path, using method B.
+ DWORD dwName2;
+
+} TSQPHash;
+
+typedef struct _TSQPBlock
+{
+ // Offset of the beginning of the file, relative to the beginning of the archive.
+ DWORD dwFilePos;
+
+ // Flags for the file. See MPQ_FILE_XXXX constants
+ DWORD dwFlags;
+
+ // Compressed file size
+ DWORD dwCSize;
+
+ // Uncompressed file size
+ DWORD dwFSize;
+
+} TSQPBlock;
+
+//-----------------------------------------------------------------------------
+// Functions - SQP file format
+
+// This function converts SQP file header into MPQ file header
+int ConvertSqpHeaderToFormat4(
+ TMPQArchive * ha,
+ ULONGLONG FileSize,
+ DWORD dwFlags)
+{
+ TSQPHeader * pSqpHeader = (TSQPHeader *)ha->HeaderData;
+ TMPQHeader Header;
+
+ // SQP files from War of the Immortal use MPQ file format with slightly
+ // modified structure. These fields have different position:
+ //
+ // Offset TMPQHeader TSQPHeader
+ // ------ ---------- -----------
+ // 000C wFormatVersion dwHashTablePos (lo)
+ // 000E wSectorSize dwHashTablePos (hi)
+ // 001C dwBlockTableSize (lo) wBlockSize
+ // 001E dwHashTableSize (hi) wFormatVersion
+
+ // Can't open the archive with certain flags
+ if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
+ return ERROR_FILE_CORRUPT;
+
+ // The file must not be greater than 4 GB
+ if((FileSize >> 0x20) != 0)
+ return ERROR_FILE_CORRUPT;
+
+ // Translate the SQP header into a MPQ header
+ memset(&Header, 0, sizeof(TMPQHeader));
+ Header.dwID = BSWAP_INT32_UNSIGNED(pSqpHeader->dwID);
+ Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHeaderSize);
+ Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwArchiveSize);
+ Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTablePos);
+ Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTablePos);
+ Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTableSize);
+ Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTableSize);
+ Header.wFormatVersion = BSWAP_INT16_UNSIGNED(pSqpHeader->wFormatVersion);
+ Header.wSectorSize = BSWAP_INT16_UNSIGNED(pSqpHeader->wSectorSize);
+ assert(Header.dwHeaderSize == sizeof(TSQPHeader));
+
+ // Verify the SQP header
+ if(Header.dwID == ID_MPQ && Header.dwHeaderSize == sizeof(TSQPHeader) && Header.dwArchiveSize == FileSize)
+ {
+ // Check for fixed values of version and sector size
+ if(Header.wFormatVersion == MPQ_FORMAT_VERSION_1 && Header.wSectorSize == 3)
+ {
+ // Initialize the fields of 3.0 header
+ Header.ArchiveSize64 = Header.dwArchiveSize;
+ Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash);
+ Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock);
+
+ // Copy the converted MPQ header back
+ memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader));
+
+ // Mark this file as SQP file
+ ha->pfnHashString = HashStringSlash;
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ ha->dwSubType = MPQ_SUBTYPE_SQP;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ return ERROR_FILE_CORRUPT;
+}
+
+void * LoadSqpTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize, DWORD dwKey)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ ULONGLONG ByteOffset;
+ LPBYTE pbSqpTable;
+
+ // Allocate buffer for the table
+ pbSqpTable = STORM_ALLOC(BYTE, cbTableSize);
+ if(pbSqpTable != NULL)
+ {
+ // Load the table
+ ByteOffset = ha->MpqPos + dwByteOffset;
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbSqpTable, cbTableSize))
+ {
+ // Decrypt the SQP table
+ DecryptMpqBlock(pbSqpTable, cbTableSize, dwKey);
+ return pbSqpTable;
+ }
+
+ // Free the table
+ STORM_FREE(pbSqpTable);
+ }
+
+ return NULL;
+}
+
+TMPQHash * LoadSqpHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TSQPHash * pSqpHashTable;
+ TSQPHash * pSqpHashEnd;
+ TSQPHash * pSqpHash;
+ TMPQHash * pMpqHash;
+ DWORD dwBlockIndex;
+ int nError = ERROR_SUCCESS;
+
+ // Load the hash table
+ pSqpHashTable = (TSQPHash *)LoadSqpTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TSQPHash), MPQ_KEY_HASH_TABLE);
+ if(pSqpHashTable != NULL)
+ {
+ // Parse the entire hash table and convert it to MPQ hash table
+ pSqpHashEnd = pSqpHashTable + pHeader->dwHashTableSize;
+ pMpqHash = (TMPQHash *)pSqpHashTable;
+ for(pSqpHash = pSqpHashTable; pSqpHash < pSqpHashEnd; pSqpHash++, pMpqHash++)
+ {
+ // Ignore free entries
+ if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE)
+ {
+ // Check block index against the size of the block table
+ dwBlockIndex = pSqpHash->dwBlockIndex;
+ if(pHeader->dwBlockTableSize <= dwBlockIndex && dwBlockIndex < HASH_ENTRY_DELETED)
+ nError = ERROR_FILE_CORRUPT;
+
+ // We do not support nonzero locale and platform ID
+ if(pSqpHash->dwAlwaysZero != 0 && pSqpHash->dwAlwaysZero != HASH_ENTRY_FREE)
+ nError = ERROR_FILE_CORRUPT;
+
+ // Store the file name hash
+ pMpqHash->dwName1 = pSqpHash->dwName1;
+ pMpqHash->dwName2 = pSqpHash->dwName2;
+
+ // Store the rest. Note that this must be done last,
+ // because pSqpHash->dwBlockIndex corresponds to pMpqHash->dwName2
+ pMpqHash->dwBlockIndex = dwBlockIndex;
+ pMpqHash->wPlatform = 0;
+ pMpqHash->lcLocale = 0;
+ }
+ }
+
+ // If an error occured, we need to free the hash table
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pSqpHashTable);
+ pSqpHashTable = NULL;
+ }
+ }
+
+ // Return the converted hash table (or NULL on failure)
+ return (TMPQHash *)pSqpHashTable;
+}
+
+// Loads the SQP Block table and converts it to a MPQ block table
+TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TSQPBlock * pSqpBlockTable;
+ TSQPBlock * pSqpBlockEnd;
+ TSQPBlock * pSqpBlock;
+ TMPQBlock * pMpqBlock;
+ DWORD dwFlags;
+ int nError = ERROR_SUCCESS;
+
+ // Load the hash table
+ pSqpBlockTable = (TSQPBlock *)LoadSqpTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TSQPBlock), MPQ_KEY_BLOCK_TABLE);
+ if(pSqpBlockTable != NULL)
+ {
+ // Parse the entire hash table and convert it to MPQ hash table
+ pSqpBlockEnd = pSqpBlockTable + pHeader->dwBlockTableSize;
+ pMpqBlock = (TMPQBlock *)pSqpBlockTable;
+ for(pSqpBlock = pSqpBlockTable; pSqpBlock < pSqpBlockEnd; pSqpBlock++, pMpqBlock++)
+ {
+ // Check for valid flags
+ if(pSqpBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS)
+ nError = ERROR_FILE_CORRUPT;
+
+ // Convert SQP block table entry to MPQ block table entry
+ dwFlags = pSqpBlock->dwFlags;
+ pMpqBlock->dwCSize = pSqpBlock->dwCSize;
+ pMpqBlock->dwFSize = pSqpBlock->dwFSize;
+ pMpqBlock->dwFlags = dwFlags;
+ }
+
+ // If an error occured, we need to free the hash table
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pSqpBlockTable);
+ pSqpBlockTable = NULL;
+ }
+ }
+
+ // Return the converted hash table (or NULL on failure)
+ return (TMPQBlock *)pSqpBlockTable;
+}
+
+/*****************************************************************************/
+/* */
+/* Support for MPK file format (Longwu Online) */
+/* */
+/*****************************************************************************/
+
+#define MPK_FILE_UNKNOWN_0001 0x00000001 // Seems to be always present
+#define MPK_FILE_UNKNOWN_0010 0x00000010 // Seems to be always present
+#define MPK_FILE_COMPRESSED 0x00000100 // Indicates a compressed file
+#define MPK_FILE_UNKNOWN_2000 0x00002000 // Seems to be always present
+#define MPK_FILE_EXISTS 0x01000000 // Seems to be always present
+
+typedef struct _TMPKHeader
+{
+ // The ID_MPK ('MPK\x1A') signature
+ DWORD dwID;
+
+ // Contains '2000'
+ DWORD dwVersion;
+
+ // 32-bit size of the archive
+ DWORD dwArchiveSize;
+
+ // Size of the archive header
+ DWORD dwHeaderSize;
+
+ DWORD dwHashTablePos;
+ DWORD dwHashTableSize;
+ DWORD dwBlockTablePos;
+ DWORD dwBlockTableSize;
+ DWORD dwUnknownPos;
+ DWORD dwUnknownSize;
+} TMPKHeader;
+
+
+typedef struct _TMPKHash
+{
+ // The hash of the file path, using method A.
+ DWORD dwName1;
+
+ // The hash of the file path, using method B.
+ DWORD dwName2;
+
+ // The hash of the file path, using method C.
+ DWORD dwName3;
+
+ // If the hash table entry is valid, this is the index into the block table of the file.
+ // Otherwise, one of the following two values:
+ // - FFFFFFFFh: Hash table entry is empty, and has always been empty.
+ // Terminates searches for a given file.
+ // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
+ // Does not terminate searches for a given file.
+ DWORD dwBlockIndex;
+
+} TMPKHash;
+
+typedef struct _TMPKBlock
+{
+ DWORD dwFlags; // 0x1121 - Compressed , 0x1120 - Not compressed
+ DWORD dwFilePos; // Offset of the beginning of the file, relative to the beginning of the archive.
+ DWORD dwFSize; // Uncompressed file size
+ DWORD dwCSize; // Compressed file size
+ DWORD dwUnknown; // 0x86364E6D
+} TMPKBlock;
+
+//-----------------------------------------------------------------------------
+// Local variables - MPK file format
+
+static const unsigned char MpkDecryptionKey[512] =
+{
+ 0x60, 0x20, 0x29, 0xE1, 0x01, 0xCE, 0xAA, 0xFE, 0xA3, 0xAB, 0x8E, 0x30, 0xAF, 0x02, 0xD1, 0x7D,
+ 0x41, 0x24, 0x06, 0xBD, 0xAE, 0xBE, 0x43, 0xC3, 0xBA, 0xB7, 0x08, 0x13, 0x51, 0xCF, 0xF8, 0xF7,
+ 0x25, 0x42, 0xA5, 0x4A, 0xDA, 0x0F, 0x52, 0x1C, 0x90, 0x3B, 0x63, 0x49, 0x36, 0xF6, 0xDD, 0x1B,
+ 0xEA, 0x58, 0xD4, 0x40, 0x70, 0x61, 0x55, 0x09, 0xCD, 0x0B, 0xA2, 0x4B, 0x68, 0x2C, 0x8A, 0xF1,
+ 0x3C, 0x3A, 0x65, 0xBB, 0xA1, 0xA8, 0x23, 0x97, 0xFD, 0x15, 0x00, 0x94, 0x88, 0x33, 0x59, 0xE9,
+ 0xFB, 0x69, 0x21, 0xEF, 0x85, 0x5B, 0x57, 0x6C, 0xFA, 0xB5, 0xEE, 0xB8, 0x71, 0xDC, 0xB1, 0x38,
+ 0x0C, 0x0A, 0x5C, 0x56, 0xC9, 0xB4, 0x84, 0x17, 0x1E, 0xE5, 0xD3, 0x5A, 0xCC, 0xFC, 0x11, 0x86,
+ 0x7F, 0x45, 0x4F, 0x54, 0xC8, 0x8D, 0x73, 0x89, 0x79, 0x5D, 0xB3, 0xBF, 0xB9, 0xE3, 0x93, 0xE4,
+ 0x6F, 0x35, 0x2D, 0x46, 0xF2, 0x76, 0xC5, 0x7E, 0xE2, 0xA4, 0xE6, 0xD9, 0x6E, 0x48, 0x34, 0x2B,
+ 0xC6, 0x5F, 0xBC, 0xA0, 0x6D, 0x0D, 0x47, 0x6B, 0x95, 0x96, 0x92, 0x91, 0xB2, 0x27, 0xEB, 0x9E,
+ 0xEC, 0x8F, 0xDF, 0x9C, 0x74, 0x99, 0x64, 0xF5, 0xFF, 0x28, 0xB6, 0x37, 0xF3, 0x7C, 0x81, 0x03,
+ 0x44, 0x62, 0x1F, 0xDB, 0x04, 0x7B, 0xB0, 0x9B, 0x31, 0xA7, 0xDE, 0x78, 0x9F, 0xAD, 0x0E, 0x3F,
+ 0x3E, 0x4D, 0xC7, 0xD7, 0x39, 0x19, 0x5E, 0xC2, 0xD0, 0xAC, 0xE8, 0x1A, 0x87, 0x8B, 0x07, 0x05,
+ 0x22, 0xED, 0x72, 0x2E, 0x1D, 0xC1, 0xA9, 0xD6, 0xE0, 0x83, 0xD5, 0xD8, 0xCB, 0x80, 0xF0, 0x66,
+ 0x7A, 0x9D, 0x50, 0xF9, 0x10, 0x4E, 0x16, 0x14, 0x77, 0x75, 0x6A, 0x67, 0xD2, 0xC0, 0xA6, 0xC4,
+ 0x53, 0x8C, 0x32, 0xCA, 0x82, 0x2A, 0x18, 0x9A, 0xF4, 0x4C, 0x3D, 0x26, 0x12, 0xE7, 0x98, 0x2F,
+ 0x4A, 0x04, 0x0D, 0xAF, 0xB4, 0xCF, 0x12, 0xCE, 0x1A, 0x37, 0x61, 0x39, 0x60, 0x95, 0xBE, 0x25,
+ 0xE4, 0x6E, 0xFC, 0x1B, 0xE7, 0x49, 0xE6, 0x67, 0xF6, 0xC5, 0xCB, 0x2F, 0x27, 0xD4, 0x68, 0xB2,
+ 0x01, 0x52, 0xD0, 0x46, 0x11, 0x20, 0xFB, 0x9D, 0xA9, 0x02, 0xF5, 0x8F, 0x3D, 0x82, 0xD3, 0xFF,
+ 0x0B, 0xB8, 0xF2, 0x4D, 0x8E, 0x81, 0x2C, 0xAB, 0x5F, 0xC4, 0x41, 0x29, 0x40, 0xFA, 0xC0, 0xBF,
+ 0x33, 0x10, 0x21, 0x16, 0xB0, 0x71, 0x83, 0x96, 0x8D, 0x2B, 0x23, 0x3B, 0xF9, 0xC1, 0xE5, 0x72,
+ 0xE2, 0x1C, 0x26, 0xF0, 0x73, 0x36, 0x63, 0x56, 0x31, 0x4E, 0x6B, 0x55, 0x62, 0x79, 0xC6, 0x91,
+ 0x00, 0x35, 0xB1, 0x2A, 0xA6, 0x42, 0xDF, 0xEB, 0x3C, 0x51, 0xEA, 0x97, 0x57, 0x94, 0x8C, 0x80,
+ 0x34, 0x5C, 0xD2, 0x76, 0xA4, 0xE9, 0x85, 0xE8, 0xBB, 0x78, 0xE0, 0xB5, 0xAD, 0x0F, 0x87, 0x70,
+ 0xDD, 0xAE, 0xF4, 0xD9, 0x66, 0x54, 0x6F, 0xCC, 0x4C, 0x77, 0x3E, 0xCD, 0xF1, 0x75, 0x0A, 0xA1,
+ 0x28, 0x9B, 0x9A, 0x7E, 0x4B, 0x98, 0x99, 0x47, 0xFE, 0xA5, 0xF7, 0xB7, 0xA3, 0xE1, 0x9F, 0xBC,
+ 0x93, 0x44, 0x3A, 0x08, 0x89, 0x22, 0xEE, 0xB9, 0x45, 0xD6, 0x06, 0x09, 0xC9, 0xBD, 0x14, 0x0C,
+ 0xB6, 0x5E, 0x9C, 0x7A, 0x65, 0x59, 0xAA, 0x19, 0x5B, 0x7C, 0x18, 0x43, 0x92, 0x13, 0x15, 0x7B,
+ 0xED, 0xD5, 0xC7, 0x17, 0xEF, 0x86, 0x90, 0xC2, 0x74, 0x64, 0xF3, 0xDC, 0x6C, 0x38, 0x05, 0x1D,
+ 0xC8, 0x0E, 0xEC, 0x6A, 0x32, 0xDA, 0xD7, 0xC3, 0xDB, 0x8B, 0x24, 0xB3, 0x5D, 0x2E, 0xBA, 0xA2,
+ 0xD8, 0x03, 0x88, 0x7D, 0x7F, 0x69, 0x8A, 0xFD, 0xCA, 0x4F, 0x30, 0x9E, 0xA0, 0xD1, 0x5A, 0x53,
+ 0xDE, 0x3F, 0x84, 0xAC, 0xF8, 0xA7, 0x2D, 0x1F, 0x1E, 0xE3, 0x58, 0x50, 0x6D, 0x48, 0x07, 0xA8
+};
+
+//-----------------------------------------------------------------------------
+// Functions - MPK file format
+
+// This function converts MPK file header into MPQ file header
+int ConvertMpkHeaderToFormat4(
+ TMPQArchive * ha,
+ ULONGLONG FileSize,
+ DWORD dwFlags)
+{
+ TMPKHeader * pMpkHeader = (TMPKHeader *)ha->HeaderData;
+ TMPQHeader Header;
+
+ // Can't open the archive with certain flags
+ if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
+ return ERROR_FILE_CORRUPT;
+
+ // Translate the MPK header into a MPQ header
+ // Note: Hash table size and block table size are in bytes, not in entries
+ memset(&Header, 0, sizeof(TMPQHeader));
+ Header.dwID = BSWAP_INT32_UNSIGNED(pMpkHeader->dwID);
+ Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwArchiveSize);
+ Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHeaderSize);
+ Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTablePos);
+ Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTableSize) / sizeof(TMPKHash);
+ Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTablePos);
+ Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTableSize) / sizeof(TMPKBlock);
+// Header.dwUnknownPos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownPos);
+// Header.dwUnknownSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownSize);
+ assert(Header.dwHeaderSize == sizeof(TMPKHeader));
+
+ // Verify the MPK header
+ if(Header.dwID == ID_MPK && Header.dwHeaderSize == sizeof(TMPKHeader) && Header.dwArchiveSize == (DWORD)FileSize)
+ {
+ // The header ID must be ID_MPQ
+ Header.dwID = ID_MPQ;
+ Header.wFormatVersion = MPQ_FORMAT_VERSION_1;
+ Header.wSectorSize = 3;
+
+ // Initialize the fields of 3.0 header
+ Header.ArchiveSize64 = Header.dwArchiveSize;
+ Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash);
+ Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock);
+
+ // Copy the converted MPQ header back
+ memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader));
+
+ // Mark this file as MPK file
+ ha->pfnHashString = HashStringLower;
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ ha->dwSubType = MPQ_SUBTYPE_MPK;
+ return ERROR_SUCCESS;
+ }
+ return ERROR_FILE_CORRUPT;
+}
+
+void DecryptMpkTable(void * pvMpkTable, size_t cbSize)
+{
+ LPBYTE pbMpkTable = (LPBYTE)pvMpkTable;
+
+ for(size_t i = 0; i < cbSize; i++)
+ pbMpkTable[i] = MpkDecryptionKey[pbMpkTable[i]];
+}
+
+void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize)
+{
+ ULONGLONG ByteOffset;
+ LPBYTE pbMpkTable = NULL;
+
+ // Allocate space for the table
+ pbMpkTable = STORM_ALLOC(BYTE, cbTableSize);
+ if(pbMpkTable != NULL)
+ {
+ // Load and the MPK hash table
+ ByteOffset = ha->MpqPos + dwByteOffset;
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbMpkTable, cbTableSize))
+ {
+ // Decrypt the table
+ DecryptMpkTable(pbMpkTable, cbTableSize);
+ return pbMpkTable;
+ }
+
+ // Free the MPK table
+ STORM_FREE(pbMpkTable);
+ pbMpkTable = NULL;
+ }
+
+ // Return the table
+ return pbMpkTable;
+}
+
+TMPQHash * LoadMpkHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPKHash * pMpkHash;
+ TMPQHash * pHash = NULL;
+ DWORD dwHashTableSize = pHeader->dwHashTableSize;
+
+ // 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
+
+ // Load and decrypt the MPK hash table.
+ pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash));
+ if(pMpkHash != NULL)
+ {
+ // 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)
+ {
+ // Set the entire hash table to free
+ memset(ha->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
+ assert(pHash->dwBlockIndex == HASH_ENTRY_FREE);
+
+ // Copy the MPK hash entry to the hash table
+ pHash->dwBlockIndex = pMpkHash[i].dwBlockIndex;
+ pHash->wPlatform = 0;
+ pHash->lcLocale = 0;
+ 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;
+}
+
+static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags)
+{
+ DWORD dwMpqFlags = MPQ_FILE_EXISTS;
+
+ // Check for flags that are always present
+ assert((dwMpkFlags & MPK_FILE_UNKNOWN_0001) != 0);
+ assert((dwMpkFlags & MPK_FILE_UNKNOWN_0010) != 0);
+ assert((dwMpkFlags & MPK_FILE_UNKNOWN_2000) != 0);
+ assert((dwMpkFlags & MPK_FILE_EXISTS) != 0);
+
+ // Append the compressed flag
+ dwMpqFlags |= (dwMpkFlags & MPK_FILE_COMPRESSED) ? MPQ_FILE_COMPRESS : 0;
+
+ // All files in the MPQ seem to be single unit files
+ dwMpqFlags |= MPQ_FILE_ENCRYPTED | MPQ_FILE_SINGLE_UNIT;
+
+ return dwMpqFlags;
+}
+
+TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPKBlock * pMpkBlockTable;
+ TMPKBlock * pMpkBlockEnd;
+ TMPQBlock * pBlockTable = NULL;
+ TMPKBlock * pMpkBlock;
+ TMPQBlock * pMpqBlock;
+
+ // Load and decrypt the MPK block table
+ pMpkBlockTable = pMpkBlock = (TMPKBlock *)LoadMpkTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TMPKBlock));
+ if(pMpkBlockTable != NULL)
+ {
+ // Allocate buffer for MPQ-like block table
+ pBlockTable = pMpqBlock = STORM_ALLOC(TMPQBlock, pHeader->dwBlockTableSize);
+ if(pBlockTable != NULL)
+ {
+ // Convert the MPK block table to MPQ block table
+ pMpkBlockEnd = pMpkBlockTable + pHeader->dwBlockTableSize;
+ while(pMpkBlock < pMpkBlockEnd)
+ {
+ // Translate the MPK block table entry to MPQ block table entry
+ pMpqBlock->dwFilePos = pMpkBlock->dwFilePos;
+ pMpqBlock->dwCSize = pMpkBlock->dwCSize;
+ pMpqBlock->dwFSize = pMpkBlock->dwFSize;
+ pMpqBlock->dwFlags = ConvertMpkFlagsToMpqFlags(pMpkBlock->dwFlags);
+
+ // Move both
+ pMpkBlock++;
+ pMpqBlock++;
+ }
+ }
+
+ // Free the MPK block table
+ STORM_FREE(pMpkBlockTable);
+ }
+
+ return pBlockTable;
+}
diff --git a/src/SCompression.cpp b/src/SCompression.cpp
index 8e4fcac..933dc61 100644
--- a/src/SCompression.cpp
+++ b/src/SCompression.cpp
@@ -532,6 +532,47 @@ static int Decompress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBu
return 1;
}
+static int Decompress_LZMA_MPK(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ ELzmaStatus LzmaStatus;
+ ISzAlloc SzAlloc;
+ Byte * destBuffer = (Byte *)pvOutBuffer;
+ Byte * srcBuffer = (Byte *)pvInBuffer;
+ SizeT destLen = *pcbOutBuffer;
+ SizeT srcLen = cbInBuffer;
+ SRes nResult;
+ BYTE LZMA_Props[] = {0x5D, 0x00, 0x00, 0x00, 0x01};
+
+ // There must be at least 0x0E bytes in the buffer
+ if(srcLen <= sizeof(LZMA_Props))
+ return 0;
+
+ // Verify the props header
+ if(memcmp(pvInBuffer, LZMA_Props, sizeof(LZMA_Props)))
+ return 0;
+
+ // Fill the callbacks in structures
+ SzAlloc.Alloc = LZMA_Callback_Alloc;
+ SzAlloc.Free = LZMA_Callback_Free;
+
+ // Perform compression
+ srcLen = cbInBuffer - sizeof(LZMA_Props);
+ nResult = LzmaDecode(destBuffer,
+ &destLen,
+ srcBuffer + sizeof(LZMA_Props),
+ &srcLen,
+ srcBuffer,
+ sizeof(LZMA_Props),
+ LZMA_FINISH_END,
+ &LzmaStatus,
+ &SzAlloc);
+ if(nResult != SZ_OK)
+ return 0;
+
+ *pcbOutBuffer = (unsigned int)destLen;
+ return 1;
+}
+
/******************************************************************************/
/* */
/* Support functions for SPARSE compression (0x20) */
@@ -1073,3 +1114,15 @@ int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInB
SetLastError(ERROR_FILE_CORRUPT);
return nResult;
}
+
+/*****************************************************************************/
+/* */
+/* File decompression for MPK archives */
+/* */
+/*****************************************************************************/
+
+int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ return Decompress_LZMA_MPK(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer);
+}
+
diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp
index 3362a20..a7706d0 100644
--- a/src/SFileCompactArchive.cpp
+++ b/src/SFileCompactArchive.cpp
@@ -539,13 +539,15 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
// Write the MPQ header
if(nError == ERROR_SUCCESS)
{
- // Remember the header size before swapping
- DWORD dwBytesToWrite = ha->pHeader->dwHeaderSize;
-
- BSWAP_TMPQHEADER(ha->pHeader);
- if(!FileStream_Write(pTempStream, NULL, ha->pHeader, dwBytesToWrite))
+ TMPQHeader SaveMpqHeader;
+
+ // 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_3);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4);
+ if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize))
nError = GetLastError();
- BSWAP_TMPQHEADER(ha->pHeader);
// Update the progress
ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize;
@@ -696,8 +698,7 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
// Create new entry in the hash table
if(ha->pHashTable != NULL)
{
- dwHashIndex = AllocateHashEntry(ha, pFileEntry);
- if(dwHashIndex == HASH_ENTRY_FREE)
+ if(AllocateHashEntry(ha, pFileEntry) == NULL)
{
nError = ERROR_CAN_NOT_COMPLETE;
break;
diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp
index 3a6a0d8..39332a6 100644
--- a/src/SFileCreateArchive.cpp
+++ b/src/SFileCreateArchive.cpp
@@ -56,7 +56,9 @@ static int WriteNakedMPQHeader(TMPQArchive * ha)
Header.wSectorSize = pHeader->wSectorSize;
// Write it to the file
- BSWAP_TMPQHEADER(&Header);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_3);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_4);
if(!FileStream_Write(ha->pStream, &ha->MpqPos, &Header, dwBytesToWrite))
nError = GetLastError();
@@ -172,6 +174,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
if(nError == ERROR_SUCCESS)
{
memset(ha, 0, sizeof(TMPQArchive));
+ ha->pfnHashString = HashString;
ha->pStream = pStream;
ha->dwSectorSize = pCreateInfo->dwSectorSize;
ha->UserDataPos = MpqPos;
diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp
index 80aa6e1..17c9e24 100644
--- a/src/SFileFindFile.cpp
+++ b/src/SFileFindFile.cpp
@@ -315,7 +315,6 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
lpFindFileData->szPlainName = (char *)GetPlainFileNameA(lpFindFileData->cFileName);
return ERROR_SUCCESS;
}
-
}
}
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index 97b2b05..a058136 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -182,6 +182,7 @@ bool WINAPI SFileOpenArchive(
DWORD dwHeaderID;
memset(ha, 0, sizeof(TMPQArchive));
+ ha->pfnHashString = HashString;
ha->pStream = pStream;
pStream = NULL;
@@ -238,25 +239,43 @@ bool WINAPI SFileOpenArchive(
// There must be MPQ header signature
if(dwHeaderID == ID_MPQ)
{
- // Save the position where the MPQ header has been found
- if(ha->pUserData == NULL)
- ha->UserDataPos = SearchPos;
- ha->pHeader = (TMPQHeader *)ha->HeaderData;
- ha->MpqPos = SearchPos;
-
// Now convert the header to version 4
- BSWAP_TMPQHEADER(ha->pHeader);
nError = ConvertMpqHeaderToFormat4(ha, FileSize, dwFlags);
break;
}
+ // Check for MPK archives (Longwu Online - MPQ fork)
+ if(dwHeaderID == ID_MPK)
+ {
+ // Now convert the MPK header to MPQ Header version 4
+ nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags);
+ break;
+ }
+
+ // If searching for the MPQ header is disabled, return an error
+ if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH)
+ {
+ nError = ERROR_NOT_SUPPORTED;
+ break;
+ }
+
// Move to the next possible offset
SearchPos += 0x200;
}
- // If we haven't found MPQ header in the file, it's an error
- if(ha->pHeader == NULL || ha->pHeader->wSectorSize == 0)
- nError = ERROR_BAD_FORMAT;
+ // 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
+ if(ha->pUserData == NULL)
+ ha->UserDataPos = SearchPos;
+ ha->pHeader = (TMPQHeader *)ha->HeaderData;
+ ha->MpqPos = SearchPos;
+
+ // Sector size must be nonzero.
+ if(ha->pHeader->wSectorSize == 0)
+ nError = ERROR_BAD_FORMAT;
+ }
}
// Fix table positions according to format
diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp
index a3aa901..4180cd7 100644
--- a/src/SFileOpenFileEx.cpp
+++ b/src/SFileOpenFileEx.cpp
@@ -32,21 +32,13 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
{
TFileStream * pStream;
TMPQFile * hf = NULL;
-
- // We have to convert the local file name to UNICODE, if needed
-#ifdef _UNICODE
TCHAR szFileNameT[MAX_PATH];
- int i;
-
- for(i = 0; szFileName[i] != 0; i++)
- szFileNameT[i] = szFileName[i];
- szFileNameT[i] = 0;
- pStream = FileStream_OpenFile(szFileNameT, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
-#else
- pStream = FileStream_OpenFile(szFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
-#endif
+ // Convert the file name to UNICODE (if needed)
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ // Open the file and create the TMPQFile structure
+ pStream = FileStream_OpenFile(szFileNameT, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY);
if(pStream != NULL)
{
// Allocate and initialize file handle
diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp
index 164b646..5a034b3 100644
--- a/src/SFileReadFile.cpp
+++ b/src/SFileReadFile.cpp
@@ -28,13 +28,6 @@ struct TFileHeader2Ext
//-----------------------------------------------------------------------------
// Local functions
-static void CopyFileName(char * szTarget, const TCHAR * szSource)
-{
- while(*szSource != 0)
- *szTarget++ = (char)*szSource++;
- *szTarget = 0;
-}
-
static DWORD GetMpqFileCount(TMPQArchive * ha)
{
TFileEntry * pFileTableEnd;
@@ -442,7 +435,99 @@ static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos
return ERROR_CAN_NOT_COMPLETE;
}
-static int ReadMpqFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead)
+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_COMPRESSED)
+ {
+ // 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_COMPRESSED)
+ {
+ 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;
@@ -582,7 +667,7 @@ static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos,
if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead);
else
- nError = ReadMpqFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead);
+ nError = ReadMpqFileSectorFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead);
// Fix error code
if(nError == ERROR_SUCCESS && dwBytesRead != hf->cbFileData)
@@ -620,6 +705,38 @@ static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos,
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
@@ -648,58 +765,36 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD
// If the file is local file, read the data directly from the stream
if(hf->pStream != NULL)
{
- ULONGLONG FilePosition1;
- ULONGLONG FilePosition2;
-
- // 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
-
- FileStream_GetPos(hf->pStream, &FilePosition1);
- if(!FileStream_Read(hf->pStream, NULL, 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
- {
- nError = GetLastError();
- }
- }
- else
- {
- dwBytesRead = dwToRead;
- }
+ nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
}
- else
+
+ // 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)
{
- // If the file is a patch file, we have to read it special way
- if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
- {
- nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
- }
+ nError = ReadMpqFilePatchFile(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);
- }
+ // 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);
+ }
- // Otherwise read it as sector based MPQ file
- else
- {
- nError = ReadMpqFile(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);
+ }
- // Increment the file position
- hf->dwFilePos += 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;
@@ -888,58 +983,97 @@ static TFileHeader2Ext data2ext[] =
{0, 0, 0, 0, NULL} // Terminator
};
-bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
+static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName)
{
- TFileEntry * pFileEntry;
TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
- char szPseudoName[20];
- DWORD FirstBytes[2]; // The first 4 bytes of the file
+ DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file
+ DWORD dwBytesRead = 0;
DWORD dwFilePos; // Saved file position
- int nError = ERROR_SUCCESS;
- int i;
- // Pre-zero the output buffer
- if(szFileName != NULL)
- *szFileName = 0;
+ // 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);
- // Check valid parameters
- if(!IsValidFileHandle(hf))
- nError = ERROR_INVALID_HANDLE;
- pFileEntry = hf->pFileEntry;
-
- // Only do something if the file name is not filled
- if(nError == ERROR_SUCCESS && pFileEntry != NULL && pFileEntry->szFileName == NULL)
+ // If we read at least 8 bytes
+ if(dwBytesRead == sizeof(FirstBytes))
{
- // Read the first 2 DWORDs bytes from the file
- FirstBytes[0] = FirstBytes[1] = 0;
- dwFilePos = SFileSetFilePointer(hf, 0, NULL, FILE_CURRENT);
- SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), NULL, NULL);
+ // Make sure that the array is properly BSWAP-ed
BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));
- SFileSetFilePointer(hf, dwFilePos, NULL, FILE_BEGIN);
// Try to guess file extension from those 2 DWORDs
- for(i = 0; data2ext[i].szExt != NULL; i++)
+ 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);
- break;
+
+ // 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;
}
}
+ }
- // Put the file name to the file table
- AllocateFileName(pFileEntry, szPseudoName);
- }
+ 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;
- // Now put the file name to the file structure
- if(nError == ERROR_SUCCESS && szFileName != NULL)
+ // Check valid parameters
+ if(IsValidFileHandle(hf))
{
- if(pFileEntry != NULL && pFileEntry->szFileName != NULL)
- strcpy(szFileName, pFileEntry->szFileName);
- else if(hf->pStream != NULL)
- CopyFileName(szFileName, FileStream_GetFileName(hf->pStream));
+ 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);
}
diff --git a/src/StormCommon.h b/src/StormCommon.h
index 2d5c2ab..766b692 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -127,6 +127,8 @@ extern unsigned char AsciiToUpperTable[256];
#define MPQ_HASH_FILE_KEY 0x300
DWORD HashString(const char * szFileName, DWORD dwHashType);
+DWORD HashStringSlash(const char * szFileName, DWORD dwHashType);
+DWORD HashStringLower(const char * szFileName, DWORD dwHashType);
void InitializeMpqCryptography();
@@ -135,6 +137,9 @@ 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);
@@ -159,9 +164,10 @@ bool IsValidFileHandle(TMPQFile * hf);
//-----------------------------------------------------------------------------
// Hash table and block table manipulation
+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);
-DWORD AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
+TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
DWORD AllocateHetEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos);
@@ -201,10 +207,23 @@ int FreeFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry);
void InvalidateInternalFiles(TMPQArchive * ha);
//-----------------------------------------------------------------------------
+// Support for alternate file formats (SBaseSubTypes.cpp)
+
+int ConvertSqpHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags);
+TMPQHash * LoadSqpHashTable(TMPQArchive * ha);
+TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha);
+
+int ConvertMpkHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags);
+void DecryptMpkTable(void * pvMpkTable, size_t cbSize);
+TMPQHash * LoadMpkHashTable(TMPQArchive * ha);
+TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha);
+int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+
+//-----------------------------------------------------------------------------
// Common functions - MPQ File
TMPQFile * CreateMpqFile(TMPQArchive * ha);
-int LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, void * pvTable, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey);
+void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey);
int AllocateSectorBuffer(TMPQFile * hf);
int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile);
int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile);
diff --git a/src/StormLib.h b/src/StormLib.h
index 85aabab..972e22b 100644
--- a/src/StormLib.h
+++ b/src/StormLib.h
@@ -136,6 +136,7 @@ extern "C" {
#define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A')
#define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B')
+#define ID_MPK 0x1A4B504D // MPK archive header ID ('MPK\x1A')
#define ERROR_AVI_FILE 10000 // No MPQ file, but AVI file.
#define ERROR_UNKNOWN_FILE_KEY 10001 // Returned by SFileReadFile when can't find file key
@@ -181,6 +182,7 @@ extern "C" {
// Values for TMPQArchive::dwSubType
#define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games)
#define MPQ_SUBTYPE_SQP 0x00000001 // The file is a SQP file (War of the Immortals)
+#define MPQ_SUBTYPE_MPK 0x00000002 // The file is a MPK file (Longwu Online)
// Return value for SFileGetFileSize and SFileSetFilePointer
#define SFILE_INVALID_SIZE 0xFFFFFFFF
@@ -304,8 +306,9 @@ extern "C" {
#define MPQ_OPEN_NO_LISTFILE 0x00010000 // Don't load the internal listfile
#define MPQ_OPEN_NO_ATTRIBUTES 0x00020000 // Don't open the attributes
-#define MPQ_OPEN_FORCE_MPQ_V1 0x00040000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header
-#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00080000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file
+#define MPQ_OPEN_NO_HEADER_SEARCH 0x00040000 // Don't search for the MPQ header past the begin of the file
+#define MPQ_OPEN_FORCE_MPQ_V1 0x00080000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header
+#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00100000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file
// Deprecated
#define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY
@@ -370,6 +373,9 @@ extern "C" {
#define LANG_NEUTRAL 0x00 // Neutral locale
#endif
+// Pointer to hashing function
+typedef DWORD (*HASH_STRING)(const char * szFileName, DWORD dwHashType);
+
//-----------------------------------------------------------------------------
// Callback functions
@@ -449,10 +455,10 @@ typedef struct _TMPQUserData
typedef struct _TMPQHeader
{
// The ID_MPQ ('MPQ\x1A') signature
- DWORD dwID;
+ DWORD dwID;
// Size of the archive header
- DWORD dwHeaderSize;
+ DWORD dwHeaderSize;
// 32-bit size of MPQ archive
// This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive
@@ -534,43 +540,8 @@ typedef struct _TMPQHeader
unsigned char MD5_HetTable[MD5_DIGEST_SIZE]; // MD5 of the HET table before decryption
unsigned char MD5_MpqHeader[MD5_DIGEST_SIZE]; // MD5 of the MPQ header from signature to (including) MD5_HetTable
} TMPQHeader;
-
-// MPQ Header for SQP data files
-typedef struct _TSQPHeader
-{
- // The ID_MPQ ('MPQ\x1A') signature
- DWORD dwID;
-
- // Size of the archive header
- DWORD dwHeaderSize;
-
- // 32-bit size of MPQ archive
- DWORD dwArchiveSize;
-
- // Offset to the beginning of the hash table, relative to the beginning of the archive.
- DWORD dwHashTablePos;
-
- // Offset to the beginning of the block table, relative to the beginning of the archive.
- DWORD dwBlockTablePos;
-
- // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for
- // the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
- DWORD dwHashTableSize;
-
- // Number of entries in the block table
- DWORD dwBlockTableSize;
-
- // Must be zero for SQP files
- USHORT wFormatVersion;
-
- // Power of two exponent specifying the number of 512-byte disk sectors in each file sector
- // in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize.
- USHORT wSectorSize;
-
-} TSQPHeader;
#pragma pack(pop)
-
// Hash table entry. All files in the archive are searched by their hashes.
typedef struct _TMPQHash
{
@@ -607,27 +578,6 @@ typedef struct _TMPQHash
DWORD dwBlockIndex;
} TMPQHash;
-typedef struct _TSQPHash
-{
- // Most likely the lcLocale+wPlatform.
- DWORD dwAlwaysZero;
-
- // If the hash table entry is valid, this is the index into the block table of the file.
- // Otherwise, one of the following two values:
- // - FFFFFFFFh: Hash table entry is empty, and has always been empty.
- // Terminates searches for a given file.
- // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
- // Does not terminate searches for a given file.
- DWORD dwBlockIndex;
-
- // The hash of the file path, using method A.
- DWORD dwName1;
-
- // The hash of the file path, using method B.
- DWORD dwName2;
-
-} TSQPHash;
-
// File description block contains informations about the file
typedef struct _TMPQBlock
{
@@ -645,23 +595,6 @@ typedef struct _TMPQBlock
DWORD dwFlags;
} TMPQBlock;
-// File description block for SQP files
-typedef struct _TSQPBlock
-{
- // Offset of the beginning of the file, relative to the beginning of the archive.
- DWORD dwFilePos;
-
- // Flags for the file. See MPQ_FILE_XXXX constants
- DWORD dwFlags;
-
- // Compressed file size
- DWORD dwCSize;
-
- // Uncompressed file size
- DWORD dwFSize;
-
-} TSQPBlock;
-
// Patch file information, preceding the sector offset table
typedef struct _TPatchInfo
{
@@ -806,6 +739,7 @@ typedef struct _TMPQArchive
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
BYTE HeaderData[MPQ_HEADER_SIZE_V4]; // Storage for MPQ header
@@ -814,6 +748,7 @@ typedef struct _TMPQArchive
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)
diff --git a/src/StormPort.h b/src/StormPort.h
index 65fbb21..b2d3158 100644
--- a/src/StormPort.h
+++ b/src/StormPort.h
@@ -211,7 +211,8 @@
#define BSWAP_ARRAY64_UNSIGNED(a,b) {}
#define BSWAP_PART_HEADER(a) {}
#define BSWAP_TMPQUSERDATA(a) {}
- #define BSWAP_TMPQHEADER(a) {}
+ #define BSWAP_TMPQHEADER(a,b) {}
+ #define BSWAP_TMPKHEADER(a) {}
#else
#ifdef __cplusplus
@@ -228,7 +229,8 @@
void ConvertUInt64Buffer(void * ptr, size_t length);
void ConvertPartHeader(void * partHeader);
void ConvertTMPQUserData(void *userData);
- void ConvertTMPQHeader(void *header);
+ void ConvertTMPQHeader(void *header, uint16_t wPart);
+ void ConvertTMPKHeader(void *header);
#ifdef __cplusplus
}
#endif
@@ -243,7 +245,8 @@
#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) ConvertTMPQHeader((a))
+ #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b))
+ #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a))
#endif
#endif // __STORMPORT_H__
diff --git a/test/Test.cpp b/test/Test.cpp
index d40f080..3e4b1fb 100644
--- a/test/Test.cpp
+++ b/test/Test.cpp
@@ -1161,8 +1161,9 @@ __TryAgain:
static int TestArchiveOpenAndClose(const TCHAR * szMpqName)
{
- const char * szFileName1 = "../Data/Task/1315.str";
-// const char * szFileName2 = "items\\map\\mapz_deleted.cel";
+// const char * szFileName = "../Bin/Config/Setting/ErroeString.str";
+ const char * szFileName = "Scp\\LifeSkills.csv";
+// const char * szFileName = "File00000000.xxx";
TMPQArchive * ha = NULL;
HANDLE hFile1 = NULL;
// HANDLE hFile2 = NULL;
@@ -1185,14 +1186,11 @@ static int TestArchiveOpenAndClose(const TCHAR * szMpqName)
// Verify the raw data in the archive
if(nError == ERROR_SUCCESS)
{
- // Verify the archive
- SFileVerifyRawData(hMpq, SFILE_VERIFY_FILE, szFileName1);
-
// Try to open a file
- if(!SFileOpenFileEx(hMpq, szFileName1, SFILE_OPEN_FROM_MPQ, &hFile1))
+ if(!SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile1))
{
nError = GetLastError();
- printf("%s - file not found in the MPQ\n", szFileName1);
+ printf("%s - file not found in the MPQ\n", szFileName);
}
}
@@ -1201,7 +1199,9 @@ static int TestArchiveOpenAndClose(const TCHAR * szMpqName)
{
DWORD dwBytesRead = 0;
BYTE Buffer[0x1000];
+ char szNameBuff[MAX_PATH];
+ SFileGetFileName(hFile1, szNameBuff);
SFileSetFilePointer(hFile1, 0x1000, NULL, FILE_BEGIN);
SFileReadFile(hFile1, Buffer, sizeof(Buffer), &dwBytesRead, NULL);
}
@@ -1210,7 +1210,7 @@ static int TestArchiveOpenAndClose(const TCHAR * szMpqName)
#ifdef _MSC_VER
if(nError == ERROR_SUCCESS)
{
- SFileExtractFile(hMpq, szFileName1, _T("E:\\extracted.wav"), 0);
+ SFileExtractFile(hMpq, szFileName, _T("E:\\extracted.wav"), 0);
PlaySound(_T("E:\\extracted.wav"), NULL, SND_FILENAME);
}
#endif
@@ -2189,7 +2189,7 @@ int main(void)
// nError = TestStructureSizes();
// if(nError == ERROR_SUCCESS)
-// nError = TestOpenLocalFile("C:\\autoexec.bat");
+// nError = TestOpenLocalFile("C:\\Windows\\System32\\kernel32.dll");
// Test reading partial file
// if(nError == ERROR_SUCCESS)
@@ -2213,9 +2213,10 @@ int main(void)
// nError = TestSectorCompress(MPQ_SECTOR_SIZE);
// Test the archive open and close
- if(nError == ERROR_SUCCESS)
- nError = TestArchiveOpenAndClose(MAKE_PATH("2013 - War of the Immortals\\1\\Other.sqp"));
+ if(nError == ERROR_SUCCESS)
+ nError = TestArchiveOpenAndClose(MAKE_PATH("2012 - Longwu Online\\Data\\Scp.mpk"));
// nError = TestArchiveOpenAndClose(MAKE_PATH("1997 - Diablo I\\DIABDAT.MPQ"));
+// nError = TestArchiveOpenAndClose(MAKE_PATH("2012 - War of the Immortals\\1\\Other.sqp"));
// if(nError == ERROR_SUCCESS)
// nError = TestFindFiles(MAKE_PATH("2002 - Warcraft III/HumanEd.mpq"));