diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SBaseCommon.cpp | 41 | ||||
-rw-r--r-- | src/SBaseFileTable.cpp | 90 | ||||
-rw-r--r-- | src/SFileOpenArchive.cpp | 46 | ||||
-rw-r--r-- | src/StormCommon.h | 3 |
4 files changed, 112 insertions, 68 deletions
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 28dc701..9003960 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -907,6 +907,7 @@ TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize) void * LoadMpqTable(
TMPQArchive * ha,
ULONGLONG ByteOffset,
+ LPBYTE pbTableHash,
DWORD dwCompressedSize,
DWORD dwTableSize,
DWORD dwKey,
@@ -965,6 +966,19 @@ void * LoadMpqTable( // If everything succeeded, read the raw table from the MPQ
if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
{
+ // Verify the MD5 of the table, if present
+ if(!VerifyDataBlockHash(pbToRead, dwBytesToRead, pbTableHash))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ if(nError == ERROR_SUCCESS)
+ {
// First of all, decrypt the table
if(dwKey != 0)
{
@@ -986,10 +1000,6 @@ void * LoadMpqTable( // Make sure that the table is properly byte-swapped
BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
}
- else
- {
- nError = GetLastError();
- }
// If read failed, free the table and return
if(nError != ERROR_SUCCESS)
@@ -1341,7 +1351,7 @@ int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile) RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
// Now read the table from the MPQ
- hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0, NULL);
+ hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, NULL, dwCompressedSize, dwCrcSize, 0, NULL);
if(hf->SectorChksums == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
}
@@ -1692,7 +1702,7 @@ bool IsValidMD5(LPBYTE pbMd5) {
LPDWORD Md5 = (LPDWORD)pbMd5;
- return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
+ return ((Md5 != NULL) && (Md5[0] | Md5[1] | Md5[2] | Md5[3])) ? true : false;
}
bool IsValidSignature(LPBYTE pbSignature)
@@ -1711,18 +1721,21 @@ bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_ {
hash_state md5_state;
BYTE md5_digest[MD5_DIGEST_SIZE];
+ bool bResult = true;
// Don't verify the block if the MD5 is not valid.
- if(!IsValidMD5(expected_md5))
- return true;
+ if(IsValidMD5(expected_md5))
+ {
+ // Calculate the MD5 of the data block
+ md5_init(&md5_state);
+ md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
+ md5_done(&md5_state, md5_digest);
- // Calculate the MD5 of the data block
- md5_init(&md5_state);
- md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
- md5_done(&md5_state, md5_digest);
+ // Does the MD5's match?
+ bResult = (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
+ }
- // Does the MD5's match?
- return (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
+ return bResult;
}
void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 98d8e44..f9a86a0 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -258,6 +258,18 @@ static bool VerifyTablePosition64( return true;
}
+static bool VerifyTableTandemPositions(
+ ULONGLONG MpqOffset, // Position of the MPQ header
+ ULONGLONG TableOffset1, // 1st table: Position, relative to MPQ header
+ ULONGLONG TableSize1, // 1st table: Size in bytes
+ ULONGLONG TableOffset2, // 2nd table: Position, relative to MPQ header
+ ULONGLONG TableSize2, // 2nd table: Size in bytes
+ ULONGLONG FileSize) // Size of the entire file, in bytes
+{
+ return VerifyTablePosition64(MpqOffset, TableOffset1, TableSize1, FileSize) &&
+ VerifyTablePosition64(MpqOffset, TableOffset2, TableSize2, FileSize);
+}
+
static ULONGLONG DetermineArchiveSize_V1(
TMPQArchive * ha,
TMPQHeader * pHeader,
@@ -417,7 +429,7 @@ ULONGLONG CalculateRawSectorOffset( // This function converts the MPQ header so it always looks like version 4
int ConvertMpqHeaderToFormat4(
TMPQArchive * ha,
- ULONGLONG MpqOffset,
+ ULONGLONG ByteOffset,
ULONGLONG FileSize,
DWORD dwFlags,
MTYPE MapType)
@@ -426,8 +438,10 @@ int ConvertMpqHeaderToFormat4( ULONGLONG BlockTablePos64 = 0;
ULONGLONG HashTablePos64 = 0;
ULONGLONG BlockTableMask = (ULONGLONG)-1;
- ULONGLONG ByteOffset;
+ ULONGLONG MaxOffset;
USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion);
+ bool bHashBlockOffsetOK = false;
+ bool bHetBetOffsetOK = false;
int nError = ERROR_SUCCESS;
// If version 1.0 is forced, then the format version is forced to be 1.0
@@ -479,14 +493,14 @@ int ConvertMpqHeaderToFormat4( // Block table position must be calculated as 32-bit value
// Note: BOBA protector puts block table before the MPQ header, so it is negative
- BlockTablePos64 = (ULONGLONG)((DWORD)MpqOffset + pHeader->dwBlockTablePos);
+ BlockTablePos64 = (ULONGLONG)((DWORD)ByteOffset + pHeader->dwBlockTablePos);
BlockTableMask = 0xFFFFFFF0;
// Determine the archive size on malformed MPQs
if(ha->dwFlags & MPQ_FLAG_MALFORMED)
{
// Calculate the archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V1(ha, pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V1(ha, pHeader, ByteOffset, FileSize);
pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
}
@@ -538,7 +552,7 @@ int ConvertMpqHeaderToFormat4( assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
// Determine real archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, ByteOffset, FileSize);
// Calculate the size of the hi-block table
pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64;
@@ -547,7 +561,7 @@ int ConvertMpqHeaderToFormat4( else
{
// Determine real archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, ByteOffset, FileSize);
// Calculate size of the block table
pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - BlockTablePos64;
@@ -561,7 +575,7 @@ int ConvertMpqHeaderToFormat4( }
// Add the MPQ Offset
- BlockTablePos64 += MpqOffset;
+ BlockTablePos64 += ByteOffset;
break;
case MPQ_FORMAT_VERSION_3:
@@ -590,45 +604,45 @@ int ConvertMpqHeaderToFormat4( memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3);
BlockTablePos64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
HashTablePos64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
- ByteOffset = pHeader->ArchiveSize64;
+ MaxOffset = pHeader->ArchiveSize64;
// Size of the hi-block table
if(pHeader->HiBlockTablePos64)
{
- pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64;
- ByteOffset = pHeader->HiBlockTablePos64;
+ pHeader->HiBlockTableSize64 = MaxOffset - pHeader->HiBlockTablePos64;
+ MaxOffset = pHeader->HiBlockTablePos64;
}
// Size of the block table
if(BlockTablePos64)
{
- pHeader->BlockTableSize64 = ByteOffset - BlockTablePos64;
- ByteOffset = BlockTablePos64;
+ pHeader->BlockTableSize64 = MaxOffset - BlockTablePos64;
+ MaxOffset = BlockTablePos64;
}
// Size of the hash table
if(HashTablePos64)
{
- pHeader->HashTableSize64 = ByteOffset - HashTablePos64;
- ByteOffset = HashTablePos64;
+ pHeader->HashTableSize64 = MaxOffset - HashTablePos64;
+ MaxOffset = HashTablePos64;
}
// Size of the BET table
if(pHeader->BetTablePos64)
{
- pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64;
- ByteOffset = pHeader->BetTablePos64;
+ pHeader->BetTableSize64 = MaxOffset - pHeader->BetTablePos64;
+ MaxOffset = pHeader->BetTablePos64;
}
// Size of the HET table
if(pHeader->HetTablePos64)
{
- pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64;
-// ByteOffset = pHeader->HetTablePos64;
+ pHeader->HetTableSize64 = MaxOffset - pHeader->HetTablePos64;
+// MaxOffset = pHeader->HetTablePos64;
}
// Add the MPQ Offset
- BlockTablePos64 += MpqOffset;
+ BlockTablePos64 += ByteOffset;
break;
case MPQ_FORMAT_VERSION_4:
@@ -638,16 +652,34 @@ int ConvertMpqHeaderToFormat4( BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4);
// Apparently, Starcraft II only accepts MPQ headers where the MPQ header hash matches
- // If MD5 doesn't match, we ignore this offset
+ // If MD5 doesn't match, we ignore this offset. We also ignore it if there's no MD5 at all
+ if(!IsValidMD5(pHeader->MD5_MpqHeader))
+ return ERROR_FAKE_MPQ_HEADER;
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))
+
+ // HiBlockTable must be 0 for archives under 4GB
+ if((pHeader->ArchiveSize64 >> 0x20) == 0 && pHeader->HiBlockTablePos64 != 0)
return ERROR_FAKE_MPQ_HEADER;
- if(!VerifyTablePosition64(MpqOffset, pHeader->HetTablePos64, pHeader->HetTableSize64, FileSize))
+
+ // Is the "HET&BET" table tandem OK?
+ bHetBetOffsetOK = VerifyTableTandemPositions(ByteOffset,
+ pHeader->HetTablePos64, pHeader->HetTableSize64,
+ pHeader->BetTablePos64, pHeader->BetTableSize64,
+ FileSize);
+
+ // Is the "Hash&Block" table tandem OK?
+ bHashBlockOffsetOK = VerifyTableTandemPositions(ByteOffset,
+ pHeader->dwHashTablePos, pHeader->HashTableSize64,
+ pHeader->dwBlockTablePos, pHeader->BlockTableSize64,
+ FileSize);
+
+ // At least one pair must be OK
+ if(bHetBetOffsetOK == false && bHashBlockOffsetOK == false)
return ERROR_FAKE_MPQ_HEADER;
// Check for malformed MPQs
- if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_4 || (MpqOffset + pHeader->ArchiveSize64) != FileSize || (MpqOffset + pHeader->HiBlockTablePos64) >= FileSize)
+ if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_4 || (ByteOffset + pHeader->ArchiveSize64) != FileSize || (ByteOffset + pHeader->HiBlockTablePos64) >= FileSize)
{
pHeader->wFormatVersion = MPQ_FORMAT_VERSION_4;
pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V4;
@@ -658,12 +690,12 @@ int ConvertMpqHeaderToFormat4( if(ha->dwFlags & MPQ_FLAG_MALFORMED)
{
// Calculate the archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V4(pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V4(pHeader, ByteOffset, FileSize);
pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
}
// Calculate the block table position
- BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ BlockTablePos64 = ByteOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
break;
default:
@@ -679,13 +711,13 @@ int ConvertMpqHeaderToFormat4( }
// Calculate the block table position
- BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ BlockTablePos64 = ByteOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
break;
}
// Handle case when block table is placed before the MPQ header
// Used by BOBA protector
- if(BlockTablePos64 < MpqOffset)
+ if(BlockTablePos64 < ByteOffset)
ha->dwFlags |= MPQ_FLAG_MALFORMED;
return nError;
}
@@ -2304,7 +2336,7 @@ static TMPQHash * LoadHashTable(TMPQArchive * ha) dwCmpSize = (DWORD)pHeader->HashTableSize64;
// Read, decrypt and uncompress the hash table
- pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE, &bHashTableIsCut);
+ pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, pHeader->MD5_HashTable, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE, &bHashTableIsCut);
// DumpHashTable(pHashTable, pHeader->dwHashTableSize);
// If the hash table was cut, we can/have to defragment it
@@ -2365,7 +2397,7 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool /* bDontFixEntries */) dwCmpSize = (DWORD)pHeader->BlockTableSize64;
// Read, decrypt and uncompress the block table
- pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE, &bBlockTableIsCut);
+ pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, NULL, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE, &bBlockTableIsCut);
// If the block table was cut, we need to remember it
if(pBlockTable != NULL && bBlockTableIsCut)
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index 7dfb851..500e041 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -135,12 +135,12 @@ static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize) } // Check the begin of hi-block table - if(pHeader->HiBlockTablePos64 != 0) - { - ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64; - if(ByteOffset > FileSize) - return ERROR_BAD_FORMAT; - } + //if(pHeader->HiBlockTablePos64 != 0) + //{ + // ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64; + // if(ByteOffset > FileSize) + // return ERROR_BAD_FORMAT; + //} // All OK. return ERROR_SUCCESS; @@ -186,7 +186,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); - MTYPE MapType = MapTypeNotRecognized; + MTYPE MapType = MapTypeNotChecked; int nError = ERROR_SUCCESS; // Verify the parameters @@ -234,7 +234,7 @@ bool WINAPI SFileOpenArchive( // Find the position of MPQ header if(nError == ERROR_SUCCESS) { - ULONGLONG SearchOffset = 0; + ULONGLONG ByteOffset = 0; ULONGLONG EndOfSearch = FileSize; DWORD dwStrmFlags = 0; DWORD dwHeaderSize; @@ -261,26 +261,25 @@ bool WINAPI SFileOpenArchive( EndOfSearch = 0x08000000; // Find the offset of MPQ header within the file - while(bSearchComplete == false && SearchOffset < EndOfSearch) + while(bSearchComplete == false && ByteOffset < EndOfSearch) { // Always read at least 0x1000 bytes for performance. // This is what Storm.dll (2002) does. DWORD dwBytesAvailable = HEADER_SEARCH_BUFFER_SIZE; - DWORD dwInBufferOffset = 0; // Cut the bytes available, if needed - if((FileSize - SearchOffset) < HEADER_SEARCH_BUFFER_SIZE) - dwBytesAvailable = (DWORD)(FileSize - SearchOffset); + if((FileSize - ByteOffset) < HEADER_SEARCH_BUFFER_SIZE) + dwBytesAvailable = (DWORD)(FileSize - ByteOffset); // Read the eventual MPQ header - if(!FileStream_Read(ha->pStream, &SearchOffset, pbHeaderBuffer, dwBytesAvailable)) + if(!FileStream_Read(ha->pStream, &ByteOffset, pbHeaderBuffer, dwBytesAvailable)) { nError = GetLastError(); break; } // Check whether the file is AVI file or a Warcraft III/Starcraft II map - if(SearchOffset == 0) + if(MapType == MapTypeNotChecked) { // Do nothing if the file is an AVI file if((MapType = CheckMapType(szMpqName, pbHeaderBuffer, dwBytesAvailable)) == MapTypeAviFile) @@ -291,7 +290,7 @@ bool WINAPI SFileOpenArchive( } // Search the header buffer - while(dwInBufferOffset < dwBytesAvailable) + for(DWORD dwInBufferOffset = 0; dwInBufferOffset < dwBytesAvailable; dwInBufferOffset += 0x200) { // Copy the data from the potential header buffer to the MPQ header memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData)); @@ -304,16 +303,16 @@ bool WINAPI SFileOpenArchive( if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA) { // Verify if this looks like a valid user data - pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); + pUserData = IsValidMpqUserData(ByteOffset, FileSize, ha->HeaderData); if(pUserData != NULL) { // Fill the user data header - ha->UserDataPos = SearchOffset; + ha->UserDataPos = ByteOffset; ha->pUserData = &ha->UserData; memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); // Continue searching from that position - SearchOffset += ha->pUserData->dwHeaderOffs; + ByteOffset += ha->pUserData->dwHeaderOffs; break; } } @@ -327,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, MapType); + nError = ConvertMpqHeaderToFormat4(ha, ByteOffset, FileSize, dwFlags, MapType); if(nError != ERROR_FAKE_MPQ_HEADER) { bSearchComplete = true; @@ -353,8 +352,7 @@ bool WINAPI SFileOpenArchive( } // Move the pointers - SearchOffset += 0x200; - dwInBufferOffset += 0x200; + ByteOffset += 0x200; } } @@ -363,15 +361,15 @@ bool WINAPI SFileOpenArchive( { // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) - ha->UserDataPos = SearchOffset; + ha->UserDataPos = ByteOffset; // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; - ha->MpqPos = SearchOffset; + ha->MpqPos = ByteOffset; ha->FileSize = FileSize; // Sector size must be nonzero. - if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0) + if(ByteOffset >= FileSize || ha->pHeader->wSectorSize == 0) nError = ERROR_BAD_FORMAT; } } diff --git a/src/StormCommon.h b/src/StormCommon.h index 1167d29..9f9715f 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -75,6 +75,7 @@ typedef enum _MTYPE
{
+ MapTypeNotChecked, // The map type was not checked yet
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
@@ -310,7 +311,7 @@ int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry);
TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize);
-void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey, bool * pbTableIsCut);
+void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, LPBYTE pbTableHash, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey, bool * pbTableIsCut);
int AllocateSectorBuffer(TMPQFile * hf);
int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile);
int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile);
|