aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SBaseCommon.cpp1
-rw-r--r--src/SBaseFileTable.cpp85
-rw-r--r--src/SFileOpenArchive.cpp82
-rw-r--r--src/SFileReadFile.cpp3
-rw-r--r--src/StormCommon.h13
5 files changed, 140 insertions, 44 deletions
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index fc80dee..a116625 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -945,6 +945,7 @@ void * LoadMpqTable(
// On archives v 1.0, hash table and block table can go beyond EOF.
// Storm.dll reads as much as possible, then fills the missing part with zeros.
// Abused by Spazzler map protector which sets hash table size to 0x00100000
+ // Abused by NP_Protect in MPQs v4 as well
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
{
// Cut the table size
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index 7c9a857..c93920c 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -216,6 +216,28 @@ void SetBits(
//-----------------------------------------------------------------------------
// Support for MPQ header
+static bool VerifyTablePosition64(
+ ULONGLONG MpqOffset, // Position of the MPQ header
+ ULONGLONG TableOffset, // Position of the MPQ table, relative to MPQ header
+ ULONGLONG TableSize, // Size of the MPQ table, in bytes
+ ULONGLONG FileSize) // Size of the entire file, in bytes
+{
+ // Verify overflows
+ if((MpqOffset + TableOffset) < MpqOffset)
+ return false;
+ if((MpqOffset + TableOffset + TableSize) < MpqOffset)
+ return false;
+
+ // Verify sizes
+ if(TableOffset >= FileSize || TableSize >= FileSize)
+ return false;
+ if((MpqOffset + TableOffset) >= FileSize)
+ return false;
+ if((MpqOffset + TableOffset + TableSize) >= FileSize)
+ return false;
+ return true;
+}
+
static ULONGLONG DetermineArchiveSize_V1(
TMPQArchive * ha,
TMPQHeader * pHeader,
@@ -288,6 +310,38 @@ static ULONGLONG DetermineArchiveSize_V2(
return (EndOfMpq - MpqOffset);
}
+static ULONGLONG DetermineArchiveSize_V4(
+ TMPQHeader * pHeader,
+ ULONGLONG /* MpqOffset */,
+ ULONGLONG /* FileSize */)
+{
+ ULONGLONG ArchiveSize = 0;
+ ULONGLONG EndOfTable;
+
+ // This could only be called for MPQs version 4
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_4);
+
+ // Determine the archive size as the greatest of all valid values
+ EndOfTable = pHeader->BetTablePos64 + pHeader->BetTableSize64;
+ if(EndOfTable > ArchiveSize)
+ ArchiveSize = EndOfTable;
+
+ EndOfTable = pHeader->dwHashTablePos + pHeader->dwHashTableSize * sizeof(TMPQHash);
+ if(EndOfTable > ArchiveSize)
+ ArchiveSize = EndOfTable;
+
+ EndOfTable = pHeader->dwBlockTablePos + pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ if(EndOfTable > ArchiveSize)
+ ArchiveSize = EndOfTable;
+
+ EndOfTable = pHeader->HetTablePos64 + pHeader->HetTableSize64;
+ if(EndOfTable > ArchiveSize)
+ ArchiveSize = EndOfTable;
+
+ // Return the calculated archive size
+ return ArchiveSize;
+}
+
ULONGLONG FileOffsetFromMpqOffset(TMPQArchive * ha, ULONGLONG MpqOffset)
{
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
@@ -339,7 +393,7 @@ int ConvertMpqHeaderToFormat4(
ULONGLONG MpqOffset,
ULONGLONG FileSize,
DWORD dwFlags,
- bool bIsWarcraft3Map)
+ MTYPE MapType)
{
TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData;
ULONGLONG BlockTablePos64 = 0;
@@ -351,8 +405,10 @@ int ConvertMpqHeaderToFormat4(
// If version 1.0 is forced, then the format version is forced to be 1.0
// Reason: Storm.dll in Warcraft III ignores format version value
- if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) || bIsWarcraft3Map)
+ if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) || (MapType == MapTypeWarcraft3))
wFormatVersion = MPQ_FORMAT_VERSION_1;
+ if(MapType == MapTypeStarcraft2)
+ wFormatVersion = MPQ_FORMAT_VERSION_4;
// Format-specific fixes
switch(wFormatVersion)
@@ -558,6 +614,24 @@ int ConvertMpqHeaderToFormat4(
// If MD5 doesn't match, we ignore this offset
if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader))
return ERROR_FAKE_MPQ_HEADER;
+ if(!VerifyTablePosition64(MpqOffset, pHeader->HiBlockTablePos64, pHeader->HiBlockTableSize64, FileSize))
+ return ERROR_FAKE_MPQ_HEADER;
+
+ // Check for malformed MPQs
+ if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_4 || (ha->MpqPos + pHeader->ArchiveSize64) != FileSize || (ha->MpqPos + pHeader->HiBlockTablePos64) >= FileSize)
+ {
+ pHeader->wFormatVersion = MPQ_FORMAT_VERSION_4;
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V4;
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ }
+
+ // Recalculate archive size
+ if(ha->dwFlags & MPQ_FLAG_MALFORMED)
+ {
+ // Calculate the archive size
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V4(pHeader, MpqOffset, FileSize);
+ pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
+ }
// Calculate the block table position
BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
@@ -1327,7 +1401,7 @@ static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader)
if(pHetHeader->ExtHdr.dwDataSize >= (sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader)))
{
// Verify the size of the table in the header
- if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize)
+ if(pHetHeader->ExtHdr.dwDataSize >= pHetHeader->dwTableSize)
{
// The size of the HET table must be sum of header, hash and index table size
assert((sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize) == pHetHeader->dwTableSize);
@@ -1597,10 +1671,11 @@ static TMPQBetTable * TranslateBetTable(
if(pBetHeader->ExtHdr.dwDataSize >= (sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader)))
{
// Verify the size of the table in the header
- if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize)
+ if(pBetHeader->ExtHdr.dwDataSize >= pBetHeader->dwTableSize)
{
// The number of entries in the BET table must be the same like number of entries in the block table
- assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize);
+ // Note: Ignored if there is no block table
+ //assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize);
assert(pBetHeader->dwEntryCount <= ha->dwMaxFileCount);
// The number of entries in the BET table must be the same like number of entries in the HET table
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index 7755eaf..2020e56 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -19,39 +19,53 @@
#define HEADER_SEARCH_BUFFER_SIZE 0x1000
-/*****************************************************************************/
-/* Local functions */
-/*****************************************************************************/
+//-----------------------------------------------------------------------------
+// Local functions
-static bool IsAviFile(DWORD * HeaderData)
+static MTYPE CheckMapType(LPCTSTR szFileName, LPBYTE pbHeaderBuffer, size_t cbHeaderBuffer)
{
- DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderData[0]);
- DWORD DwordValue2 = BSWAP_INT32_UNSIGNED(HeaderData[2]);
- DWORD DwordValue3 = BSWAP_INT32_UNSIGNED(HeaderData[3]);
+ LPDWORD HeaderInt32 = (LPDWORD)pbHeaderBuffer;
+ LPCTSTR szExtension;
- // Test for 'RIFF', 'AVI ' or 'LIST'
- return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C);
-}
+ // Don't do any checks if there is not at least 16 bytes
+ if(cbHeaderBuffer > 0x10)
+ {
+ DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderInt32[0]);
+ DWORD DwordValue1 = BSWAP_INT32_UNSIGNED(HeaderInt32[1]);
+ DWORD DwordValue2 = BSWAP_INT32_UNSIGNED(HeaderInt32[2]);
+ DWORD DwordValue3 = BSWAP_INT32_UNSIGNED(HeaderInt32[3]);
-static bool IsWarcraft3Map(DWORD * HeaderData)
-{
- DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderData[0]);
- DWORD DwordValue1 = BSWAP_INT32_UNSIGNED(HeaderData[1]);
+ // Test for AVI files (Warcraft III cinematics) - 'RIFF', 'AVI ' or 'LIST'
+ if(DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C)
+ return MapTypeAviFile;
- return (DwordValue0 == 0x57334D48 && DwordValue1 == 0x00000000);
-}
+ // Check for Starcraft II maps
+ if((szExtension = _tcsrchr(szFileName, _T('.'))) != NULL)
+ {
+ // The "NP_Protect" protector places fake Warcraft III header
+ // into the Starcraft II maps, whilst SC2 maps have no other header but MPQ v4
+ if(!_tcsicmp(szExtension, _T(".s2ma")) || !_tcsicmp(szExtension, _T(".SC2Map")) || !_tcsicmp(szExtension, _T(".SC2Mod")))
+ {
+ return MapTypeStarcraft2;
+ }
+ }
+
+ // Check for Warcraft III maps
+ if(DwordValue0 == 0x57334D48 && DwordValue1 == 0x00000000)
+ return MapTypeWarcraft3;
+ }
-static bool IsDllFile(LPBYTE pbHeaderBuffer, size_t cbBytesAvailable)
-{
// MIX files are DLL files that contain MPQ in overlay.
- // Only Warcraft III is able to load them, so we consider them MPQs v 1.0
- if(cbBytesAvailable > 0x200 && pbHeaderBuffer[0] == 'M' && pbHeaderBuffer[1] == 'Z')
+ // Only Warcraft III is able to load them, so we consider them Warcraft III maps
+ if(cbHeaderBuffer > 0x200 && pbHeaderBuffer[0] == 'M' && pbHeaderBuffer[1] == 'Z')
{
- DWORD e_lfanew = *(LPDWORD)(pbHeaderBuffer + 0x3C);
- if(0 < e_lfanew && e_lfanew < 0x10000)
- return true;
+ // Check the value of IMAGE_DOS_HEADER::e_lfanew at offset 0x3C
+ if(0 < HeaderInt32[0x0F] && HeaderInt32[0x0F] < 0x10000)
+ return MapTypeWarcraft3;
}
- return false;
+
+ // No special map type recognized
+ return MapTypeNotRecognized;
}
static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData)
@@ -171,7 +185,7 @@ bool WINAPI SFileOpenArchive(
ULONGLONG FileSize = 0; // Size of the file
LPBYTE pbHeaderBuffer = NULL; // Buffer for searching MPQ header
DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK);
- bool bIsWarcraft3Map = false;
+ MTYPE MapType = MapTypeNotRecognized;
int nError = ERROR_SUCCESS;
// Verify the parameters
@@ -264,19 +278,15 @@ bool WINAPI SFileOpenArchive(
break;
}
- // There are AVI files from Warcraft III with 'MPQ' extension.
+ // Check whether the file is AVI file or a Warcraft III/Starcraft II map
if(SearchOffset == 0)
{
- if(IsAviFile((DWORD *)pbHeaderBuffer))
+ // Do nothing if the file is an AVI file
+ if((MapType = CheckMapType(szMpqName, pbHeaderBuffer, dwBytesAvailable)) == MapTypeAviFile)
{
nError = ERROR_AVI_FILE;
break;
}
-
- if (IsWarcraft3Map((DWORD *)pbHeaderBuffer))
- bIsWarcraft3Map = true;
- if (IsDllFile(pbHeaderBuffer, dwBytesAvailable))
- bIsWarcraft3Map = true;
}
// Search the header buffer
@@ -288,7 +298,7 @@ bool WINAPI SFileOpenArchive(
// If there is the MPQ user data, process it
// Note that Warcraft III does not check for user data, which is abused by many map protectors
dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]);
- if(bIsWarcraft3Map == false && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
+ if(MapType == MapTypeNotRecognized && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
{
if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA)
{
@@ -316,7 +326,7 @@ bool WINAPI SFileOpenArchive(
if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1)
{
// Now convert the header to version 4
- nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags, bIsWarcraft3Map);
+ nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags, MapType);
if(nError != ERROR_FAKE_MPQ_HEADER)
{
bSearchComplete = true;
@@ -325,7 +335,7 @@ bool WINAPI SFileOpenArchive(
}
// Check for MPK archives (Longwu Online - MPQ fork)
- if(bIsWarcraft3Map == false && dwHeaderID == ID_MPK)
+ if(MapType == MapTypeNotRecognized && dwHeaderID == ID_MPK)
{
// Now convert the MPK header to MPQ Header version 4
nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags);
@@ -399,7 +409,7 @@ bool WINAPI SFileOpenArchive(
ha->dwFlags |= MPQ_FLAG_LISTFILE_FORCE;
// Remember whether whis is a map for Warcraft III
- if(bIsWarcraft3Map)
+ if(MapType == MapTypeWarcraft3)
ha->dwFlags |= MPQ_FLAG_WAR3_MAP;
// Set the size of file sector
diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp
index a6b3bc4..a693e70 100644
--- a/src/SFileReadFile.cpp
+++ b/src/SFileReadFile.cpp
@@ -344,11 +344,10 @@ static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos
// Give the number of bytes read
*pdwBytesRead = dwToRead;
- return ERROR_SUCCESS;
}
// An error, sorry
- return ERROR_CAN_NOT_COMPLETE;
+ return nError;
}
static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
diff --git a/src/StormCommon.h b/src/StormCommon.h
index 95b3320..2d51d92 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -71,6 +71,17 @@
#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo)
//-----------------------------------------------------------------------------
+// MTYPE definition - specifies what kind of MPQ is the map type
+
+typedef enum _MTYPE
+{
+ MapTypeNotRecognized, // The file does not seems to be a map
+ MapTypeAviFile, // The file is actually an AVI file (Warcraft III cinematics)
+ MapTypeWarcraft3, // The file is a Warcraft III map
+ MapTypeStarcraft2 // The file is a Starcraft II map
+} MTYPE, *PMTYPE;
+
+//-----------------------------------------------------------------------------
// MPQ signature information
// Size of each signature type
@@ -227,7 +238,7 @@ TMPQFile * IsValidFileHandle(HANDLE hFile);
ULONGLONG FileOffsetFromMpqOffset(TMPQArchive * ha, ULONGLONG MpqOffset);
ULONGLONG CalculateRawSectorOffset(TMPQFile * hf, DWORD dwSectorOffset);
-int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags, bool bIsWarcraft3Map);
+int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags, MTYPE MapType);
bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash);