From b13aaed6d0a940934dcb26aa3cb28ffc0dd06c48 Mon Sep 17 00:00:00 2001 From: Ladislav Zezula Date: Wed, 18 Nov 2020 19:12:53 +0100 Subject: Fixed some variants of NP_Protect-ed maps --- src/SBaseCommon.cpp | 2 +- src/SBaseFileTable.cpp | 97 ++++++----- src/SFileOpenArchive.cpp | 5 +- src/StormLib.h | 20 ++- test/StormTest.cpp | 412 ++++++++++++++++++++++++----------------------- 5 files changed, 285 insertions(+), 251 deletions(-) diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 34a7a25..97ebc59 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -962,7 +962,7 @@ void * LoadMpqTable( } } - // If everything succeeded, read the raw table form the MPQ + // If everything succeeded, read the raw table from the MPQ if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead)) { // First of all, decrypt the table diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index e08daef..af8d290 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -57,11 +57,11 @@ static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) } //----------------------------------------------------------------------------- -// Implementation of the TStormBits struc +// Implementation of the TMPQBits struct -struct TStormBits +struct TMPQBits { - static TStormBits * Create(DWORD NumberOfBits, BYTE FillValue); + static TMPQBits * Create(DWORD NumberOfBits, BYTE FillValue); void GetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize); void SetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize); @@ -73,17 +73,17 @@ struct TStormBits BYTE Elements[1]; // Array of elements (variable length) }; -const USHORT TStormBits::SetBitsMask[] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF}; +const USHORT TMPQBits::SetBitsMask[] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF}; -TStormBits * TStormBits::Create( +TMPQBits * TMPQBits::Create( DWORD NumberOfBits, BYTE FillValue) { - TStormBits * pBitArray; - size_t nSize = sizeof(TStormBits) + (NumberOfBits + 7) / 8; + TMPQBits * pBitArray; + size_t nSize = sizeof(TMPQBits) + (NumberOfBits + 7) / 8; // Allocate the bit array - pBitArray = (TStormBits *)STORM_ALLOC(BYTE, nSize); + pBitArray = (TMPQBits *)STORM_ALLOC(BYTE, nSize); if(pBitArray != NULL) { memset(pBitArray, FillValue, nSize); @@ -94,7 +94,7 @@ TStormBits * TStormBits::Create( return pBitArray; } -void TStormBits::GetBits( +void TMPQBits::GetBits( unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, @@ -159,7 +159,7 @@ void TStormBits::GetBits( } } -void TStormBits::SetBits( +void TMPQBits::SetBits( unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, @@ -225,6 +225,11 @@ void TStormBits::SetBits( } } +void GetMPQBits(TMPQBits * pBits, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultByteSize) +{ + pBits->GetBits(nBitPosition, nBitLength, pvBuffer, nResultByteSize); +} + //----------------------------------------------------------------------------- // Support for MPQ header @@ -234,19 +239,22 @@ static bool VerifyTablePosition64( 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; + if(TableOffset != 0) + { + // 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; } @@ -333,10 +341,21 @@ static ULONGLONG DetermineArchiveSize_V4( // 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; + // Check position of BET table, if correct + if((pHeader->BetTablePos64 >> 0x20) == 0 && (pHeader->BetTableSize64 >> 0x20) == 0) + { + EndOfTable = pHeader->BetTablePos64 + pHeader->BetTableSize64; + if(EndOfTable > ArchiveSize) + ArchiveSize = EndOfTable; + } + + // Check position of HET table, if correct + if((pHeader->HetTablePos64 >> 0x20) == 0 && (pHeader->HetTableSize64 >> 0x20) == 0) + { + EndOfTable = pHeader->HetTablePos64 + pHeader->HetTableSize64; + if(EndOfTable > ArchiveSize) + ArchiveSize = EndOfTable; + } EndOfTable = pHeader->dwHashTablePos + pHeader->dwHashTableSize * sizeof(TMPQHash); if(EndOfTable > ArchiveSize) @@ -346,10 +365,6 @@ static ULONGLONG DetermineArchiveSize_V4( if(EndOfTable > ArchiveSize) ArchiveSize = EndOfTable; - EndOfTable = pHeader->HetTablePos64 + pHeader->HetTableSize64; - if(EndOfTable > ArchiveSize) - ArchiveSize = EndOfTable; - // Return the calculated archive size return ArchiveSize; } @@ -628,6 +643,8 @@ int ConvertMpqHeaderToFormat4( return ERROR_FAKE_MPQ_HEADER; if(!VerifyTablePosition64(MpqOffset, pHeader->HiBlockTablePos64, pHeader->HiBlockTableSize64, FileSize)) return ERROR_FAKE_MPQ_HEADER; + if(!VerifyTablePosition64(MpqOffset, pHeader->HetTablePos64, pHeader->HetTableSize64, 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) @@ -1332,7 +1349,7 @@ TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwNa memset(pHetTable->pNameHashes, 0, dwTotalCount); // Allocate the bit array for file indexes - pHetTable->pBetIndexes = TStormBits::Create(dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF); + pHetTable->pBetIndexes = TMPQBits::Create(dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF); if(pHetTable->pBetIndexes != NULL) { // Initialize the HET table from the source data (if given) @@ -1732,7 +1749,7 @@ static TMPQBetTable * TranslateBetTable( } // Load the bit-based file table - pBetTable->pFileTable = TStormBits::Create(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0); + pBetTable->pFileTable = TMPQBits::Create(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0); if(pBetTable->pFileTable != NULL) { LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8; @@ -1746,7 +1763,7 @@ static TMPQBetTable * TranslateBetTable( pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2; // Create and load the array of BET hashes - pBetTable->pNameHashes = TStormBits::Create(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0); + pBetTable->pNameHashes = TMPQBits::Create(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0); if(pBetTable->pNameHashes != NULL) { LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8; @@ -1771,7 +1788,7 @@ TMPQExtHeader * TranslateBetTable( TMPQBetHeader BetHeader; TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; - TStormBits * pBitArray = NULL; + TMPQBits * pBitArray = NULL; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; DWORD LengthInBytes; @@ -1791,7 +1808,7 @@ TMPQExtHeader * TranslateBetTable( pbTrgData = (LPBYTE)(pBetHeader + 1); // Save the bit-based block table - pBitArray = TStormBits::Create(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0); + pBitArray = TMPQBits::Create(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0); if(pBitArray != NULL) { DWORD dwFlagIndex = 0; @@ -1846,7 +1863,7 @@ TMPQExtHeader * TranslateBetTable( } // Create bit array for name hashes - pBitArray = TStormBits::Create(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0); + pBitArray = TMPQBits::Create(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0); if(pBitArray != NULL) { DWORD dwFileIndex = 0; @@ -2376,7 +2393,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) TMPQHeader * pHeader = ha->pHeader; // If the HET table position is not 0, we expect the table to be present - if(pHeader->HetTablePos64 != 0 && pHeader->HetTableSize64 != 0) + if(pHeader->HetTablePos64 && pHeader->HetTableSize64) { // Attempt to load the HET table (Hash Extended Table) pExtTable = LoadExtTable(ha, pHeader->HetTablePos64, (size_t)pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE); @@ -2398,7 +2415,7 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha) TMPQHeader * pHeader = ha->pHeader; // If the BET table position is not 0, we expect the table to be present - if(pHeader->BetTablePos64 != 0 && pHeader->BetTableSize64 != 0) + if(pHeader->BetTablePos64 && pHeader->BetTableSize64) { // Attempt to load the HET table (Hash Extended Table) pExtTable = LoadExtTable(ha, pHeader->BetTablePos64, (size_t)pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE); @@ -2518,7 +2535,7 @@ static int BuildFileTable_HetBet(TMPQArchive * ha) TMPQHetTable * pHetTable = ha->pHetTable; TMPQBetTable * pBetTable; TFileEntry * pFileEntry = ha->pFileTable; - TStormBits * pBitArray; + TMPQBits * pBitArray; DWORD dwBitPosition = 0; DWORD i; int nError = ERROR_FILE_CORRUPT; diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index 2020e56..d1cc4d6 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -5,7 +5,7 @@ /* E-mail : ladik@zezula.net */ /* WWW : www.zezula.net */ /*---------------------------------------------------------------------------*/ -/* Archive functions of Storm.dll */ +/* Implementation of archive functions */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ @@ -100,6 +100,7 @@ static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG ByteOffset; + //bool bMalformed = (ha->dwFlags & MPQ_FLAG_MALFORMED) ? true : false; // Check the begin of HET table if(pHeader->HetTablePos64) @@ -298,7 +299,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(MapType == MapTypeNotRecognized && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) + if(MapType != MapTypeWarcraft3 && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA) { diff --git a/src/StormLib.h b/src/StormLib.h index 4f19772..d913a54 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -480,7 +480,7 @@ typedef void (WINAPI * SFILE_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesW typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes); struct TFileStream; -struct TStormBits; +struct TMPQBits; //----------------------------------------------------------------------------- // Structures related to MPQ format @@ -748,7 +748,7 @@ typedef struct _TMPQBetHeader // Structure for parsed HET table typedef struct _TMPQHetTable { - TStormBits * pBetIndexes; // Bit array of FileIndex values + TMPQBits * pBetIndexes; // Bit array of FileIndex values LPBYTE pNameHashes; // Array of NameHash1 values (NameHash1 = upper 8 bits of FileName hashe) ULONGLONG AndMask64; // AND mask used for calculating file name hash ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash @@ -764,8 +764,8 @@ typedef struct _TMPQHetTable // Structure for parsed BET table typedef struct _TMPQBetTable { - TStormBits * pNameHashes; // Array of NameHash2 entries (lower 24 bits of FileName hash) - TStormBits * pFileTable; // Bit-based file table + TMPQBits * pNameHashes; // Array of NameHash2 entries (lower 24 bits of FileName hash) + TMPQBits * pFileTable; // Bit-based file table LPDWORD pFileFlags; // Array of file flags DWORD dwTableEntrySize; // Size of one table entry, in bits @@ -924,11 +924,16 @@ typedef struct _SFILE_CREATE_MPQ } SFILE_CREATE_MPQ, *PSFILE_CREATE_MPQ; +//----------------------------------------------------------------------------- +// TMPQBits support - functions + +void GetMPQBits(TMPQBits * pBits, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultByteSize); + //----------------------------------------------------------------------------- // Stream support - functions // Structure used by FileStream_GetBitmap -typedef struct _TStreamBitmap +struct TStreamBitmap { ULONGLONG StreamSize; // Size of the stream, in bytes DWORD BitmapSize; // Size of the block map, in bytes @@ -937,8 +942,7 @@ typedef struct _TStreamBitmap DWORD IsComplete; // Nonzero if the file is complete // Followed by the BYTE array, each bit means availability of one block - -} TStreamBitmap; +}; // UNICODE versions of the file access functions TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags); @@ -948,7 +952,7 @@ size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider); bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData); -bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, LPDWORD pcbLengthNeeded); +bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, DWORD * pcbLengthNeeded); bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead); bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite); bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize); diff --git a/test/StormTest.cpp b/test/StormTest.cpp index bb55eb8..47a8cb5 100644 --- a/test/StormTest.cpp +++ b/test/StormTest.cpp @@ -35,6 +35,13 @@ //------------------------------------------------------------------------------ // Local structures +#define TEST_OPEN_MPQ 0x00000000 +#define TEST_LOAD_FILE_CHECK_CRC 0x00010000 +#define TEST_OPEN_COMPARE_TWO_FILES 0x00020000 +#define TEST_MASK 0xFFFF0000 + +#define TEST_SETPOS 0x00000001 + typedef struct _TEST_INFO { LPCTSTR szMpqName1; @@ -1344,7 +1351,7 @@ struct TFileData DWORD dwBlockIndex; DWORD dwFileSize; DWORD dwFlags; - DWORD dwReserved; // Alignment + DWORD dwCrc32; BYTE FileData[1]; }; @@ -1496,7 +1503,8 @@ static TFileData * LoadMpqFile(TLogHelper * pLogger, HANDLE hMpq, LPCSTR szFileN DWORD dwFileSizeHi = 0xCCCCCCCC; DWORD dwFileSizeLo = 0; DWORD dwBytesRead; - int nError = ERROR_SUCCESS; + DWORD dwCrc32 = 0; + DWORD dwErrCode = ERROR_SUCCESS; // Notify the user that we are loading a file from MPQ pLogger->PrintProgress("Loading file %s ...", GetShortPlainName(szFileName)); @@ -1511,63 +1519,67 @@ static TFileData * LoadMpqFile(TLogHelper * pLogger, HANDLE hMpq, LPCSTR szFileN // Open the file from MPQ if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) - nError = pLogger->PrintError("Open failed: %s", szFileName); + dwErrCode = pLogger->PrintError("Open failed: %s", szFileName); + + // Get the CRC32 of the file + SFileGetFileInfo(hFile, SFileInfoCRC32, &dwCrc32, sizeof(dwCrc32), NULL); // Get the size of the file - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi); if(dwFileSizeLo == SFILE_INVALID_SIZE || dwFileSizeHi != 0) - nError = pLogger->PrintError("Failed to query the file size"); + dwErrCode = pLogger->PrintError("Failed to query the file size"); } // Spazzler protector: Creates fake files with size of 0x7FFFE7CA - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { if(dwFileSizeLo > 0x1FFFFFFF) - nError = ERROR_FILE_CORRUPT; + dwErrCode = ERROR_FILE_CORRUPT; } // Allocate buffer for the file content - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { pFileData = (TFileData *)STORM_ALLOC(BYTE, sizeof(TFileData) + dwFileSizeLo); if(pFileData == NULL) { pLogger->PrintError("Failed to allocate buffer for the file content"); - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } } // get the file index of the MPQ file - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { // Store the file size memset(pFileData, 0, sizeof(TFileData) + dwFileSizeLo); pFileData->dwFileSize = dwFileSizeLo; + pFileData->dwCrc32 = dwCrc32; // Retrieve the block index and file flags if(!SFileGetFileInfo(hFile, SFileInfoFileIndex, &pFileData->dwBlockIndex, sizeof(DWORD), NULL)) - nError = pLogger->PrintError("Failed retrieve the file index of %s", szFileName); + dwErrCode = pLogger->PrintError("Failed retrieve the file index of %s", szFileName); if(!SFileGetFileInfo(hFile, SFileInfoFlags, &pFileData->dwFlags, sizeof(DWORD), NULL)) - nError = pLogger->PrintError("Failed retrieve the file flags of %s", szFileName); + dwErrCode = pLogger->PrintError("Failed retrieve the file flags of %s", szFileName); } // Load the entire file - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { // Read the file data SFileReadFile(hFile, pFileData->FileData, dwFileSizeLo, &dwBytesRead, NULL); if(dwBytesRead != dwFileSizeLo) -// nError = pLogger->PrintError("Read failed: %s", szFileName); - nError = ERROR_FILE_CORRUPT; +// dwErrCode = pLogger->PrintError("Read failed: %s", szFileName); + dwErrCode = ERROR_FILE_CORRUPT; } // If failed, free the buffer - if(nError != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) { STORM_FREE(pFileData); - SetLastError(nError); + SetLastError(dwErrCode); pFileData = NULL; } @@ -1602,7 +1614,7 @@ static bool CompareTwoFiles(TLogHelper * pLogger, TFileData * pFileData1, TFileD return true; } -static int SearchArchive( +static DWORD SearchArchive( TLogHelper * pLogger, HANDLE hMpq, DWORD dwTestFlags = 0, @@ -1616,10 +1628,10 @@ static int SearchArchive( hash_state md5state; TCHAR szListFile[MAX_PATH] = _T(""); char szMostPatched[MAX_PATH] = ""; + DWORD dwErrCode = ERROR_SUCCESS; bool bFound = true; int nMaxPatchCount = 0; int nPatchCount = 0; - int nError = ERROR_SUCCESS; // Construct the full name of the listfile CreateFullPathName(szListFile, _countof(szListFile), szListFileDir, _T("ListFile_Blizzard.txt")); @@ -1632,9 +1644,9 @@ static int SearchArchive( hFind = SFileFindFirstFile(hMpq, "*", &sf, szListFile); if(hFind == NULL) { - nError = GetLastError(); - nError = (nError == ERROR_NO_MORE_FILES) ? ERROR_SUCCESS : nError; - return nError; + dwErrCode = GetLastError(); + dwErrCode = (dwErrCode == ERROR_NO_MORE_FILES) ? ERROR_SUCCESS : dwErrCode; + return dwErrCode; } // Perform the search @@ -1695,7 +1707,7 @@ static int SearchArchive( if(pbFileHash != NULL && (dwTestFlags & TEST_FLAG_HASH_FILES)) md5_done(&md5state, pbFileHash); - return nError; + return dwErrCode; } static int CreateNewArchive(TLogHelper * pLogger, LPCTSTR szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) @@ -2379,108 +2391,161 @@ static int TestFileStreamOperations(LPCTSTR szPlainName, DWORD dwStreamFlags) return nError; } -static DWORD TestArchive( - LPCTSTR szPlainName, // Plain name of the MPQ - LPCTSTR szListFile, - DWORD dwWhatToDo, - LPCSTR szFileName1, - LPCSTR szFileName2) +static DWORD TestArchive_LoadFiles(TLogHelper * pLogger, HANDLE hMpq, ...) { - return ERROR_CAN_NOT_COMPLETE; + TFileData * pFileData; + const char * szFileName; + va_list argList; + DWORD dwErrCode = ERROR_SUCCESS; + + va_start(argList, hMpq); + while((szFileName = va_arg(argList, const char *)) != NULL) + { + if(SFileHasFile(hMpq, szFileName)) + { + pFileData = LoadMpqFile(pLogger, hMpq, szFileName); + if(pFileData != NULL) + { + STORM_FREE(pFileData); + pFileData = NULL; + } + else + { + dwErrCode = pLogger->PrintError("Error loading the file %s", szFileName); + break; + } + } + } + va_end(argList); + + return dwErrCode; } -static int TestOpenFile_OpenById(LPCTSTR szPlainName) +static DWORD TestArchive_SetPos(HANDLE hMpq, const char * szFileName) { - TLogHelper Logger("OpenFileById", szPlainName); - TFileData * pFileData1 = NULL; - TFileData * pFileData2 = NULL; - HANDLE hMpq; - int nError; - - // Copy the archive so we won't fuck up the original one - nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq); + HANDLE hFile = NULL; + DWORD dwErrCode = ERROR_SUCCESS; - // Now try to open a file without knowing the file name - if(nError == ERROR_SUCCESS) + if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) { - // File00000023.xxx = music\dintro.wav - pFileData1 = LoadMpqFile(&Logger, hMpq, "File00000023.xxx"); - if(pFileData1 == NULL) - nError = Logger.PrintError("Failed to load the file %s", "File00000023.xxx"); - } + // First, use the SFileSetFilePointer WITHOUT the high-dword position + dwErrCode = TestSetFilePointers(hFile, false); - // Now try to open the file again with its original name - if(nError == ERROR_SUCCESS) - { - // File00000023.xxx = music\dintro.wav - pFileData2 = LoadMpqFile(&Logger, hMpq, "music\\dintro.wav"); - if(pFileData2 == NULL) - nError = Logger.PrintError("Failed to load the file %s", "music\\dintro.wav"); - } + // First, use the SFileSetFilePointer WITH the high-dword position + if(dwErrCode == ERROR_SUCCESS) + dwErrCode = TestSetFilePointers(hFile, true); - // Now compare both files - if(nError == ERROR_SUCCESS) - { - if(!CompareTwoFiles(&Logger, pFileData1, pFileData1)) - nError = Logger.PrintError("The file has different size/content when open without name"); + // Close the file + SFileCloseFile(hFile); } - // Close the archive - if(pFileData2 != NULL) - STORM_FREE(pFileData2); - if(pFileData1 != NULL) - STORM_FREE(pFileData1); - if(hMpq != NULL) - SFileCloseArchive(hMpq); - return nError; + return dwErrCode; } -static int TestOpenFile_OpenByName(LPCTSTR szPlainName, LPCSTR szFileName) +static DWORD TestArchive( + LPCTSTR szPlainName, // Plain name of the MPQ + LPCTSTR szListFile, // Listfile name (NULL if none) + DWORD dwWhatToDo, // Flags what to do + LPCSTR szFileName1, + LPCSTR szFileName2) { - TLogHelper Logger("OpenFileByName", szPlainName); + TFileData * pFileData2 = NULL; TFileData * pFileData = NULL; - HANDLE hFile = NULL; + TLogHelper Logger("TestMpq", szPlainName); +// HANDLE hFile = NULL; HANDLE hMpq = NULL; - DWORD dwCrc32_1 = 0; - DWORD dwCrc32_2 = 0; - int nError; + DWORD dwFileCount = 0; + DWORD dwErrCode; + DWORD dwCrc32 = 0; + TCHAR szFullName[MAX_PATH]; - // Copy the archive so we won't fuck up the original one - nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq); + // If the file is a partial MPQ, don't load all files + bool bIsPartialMpq = (_tcsstr(szPlainName, _T(".MPQ.part")) != NULL); - // Now try to open the given file - if(nError == ERROR_SUCCESS) + // Copy the archive so we won't fuck up the original one + dwErrCode = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq); + if(dwErrCode == ERROR_SUCCESS) { - // Retrieve the CRC32 - if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) + // If the listfile was given, add it to the MPQ + if(szListFile && szListFile[0]) { - SFileGetFileInfo(hFile, SFileInfoCRC32, &dwCrc32_1, sizeof(dwCrc32_1), NULL); - SFileCloseFile(hFile); + Logger.PrintProgress(_T("Adding listfile %s ..."), szListFile); + CreateFullPathName(szFullName, _countof(szFullName), szListFileDir, szListFile); + if((dwErrCode = SFileAddListFile(hMpq, szFullName)) != ERROR_SUCCESS) + Logger.PrintMessage("Failed to add the listfile to the MPQ"); } - // Load the entire file - pFileData = LoadMpqFile(&Logger, hMpq, szFileName); - if(pFileData != NULL) + // Perform work-specific duty + switch(dwWhatToDo & TEST_MASK) { - // Compare the CRC32, if available - dwCrc32_2 = crc32(0, (Bytef *)pFileData->FileData, (uInt)pFileData->dwFileSize); - STORM_FREE(pFileData); - } - else - nError = Logger.PrintError("Failed to load the file %s", szFileName); + case TEST_LOAD_FILE_CHECK_CRC: - // Compare the CRC32 - if(nError == ERROR_SUCCESS && dwCrc32_1 && dwCrc32_2) - { - if(dwCrc32_1 != dwCrc32_2) - Logger.PrintError("Warning: CRC32 error on %s", szFileName); - } + // Load the entire file + pFileData = LoadMpqFile(&Logger, hMpq, szFileName1); + if(pFileData == NULL) + { + dwErrCode = Logger.PrintError("Failed to load the file %s", szFileName1); + break; + } - // Close the archive - SFileCloseArchive(hMpq); + // Compare the CRC32, if available + dwCrc32 = crc32(0, (Bytef *)pFileData->FileData, (uInt)pFileData->dwFileSize); + if(dwCrc32 != pFileData->dwCrc32) + Logger.PrintError("Warning: CRC32 error on %s", szFileName1); + break; + + case TEST_OPEN_COMPARE_TWO_FILES: + + // Load the first file + pFileData = LoadMpqFile(&Logger, hMpq, szFileName1); + if(pFileData == NULL) + { + dwErrCode = Logger.PrintError("Failed to load the file %s", "File00000023.xxx"); + break; + } + + // Load the second file + pFileData2 = LoadMpqFile(&Logger, hMpq, szFileName2); + if(pFileData2 == NULL) + { + dwErrCode = Logger.PrintError("Failed to load the file %s", "music\\dintro.wav"); + break; + } + + // Compare both files + if(!CompareTwoFiles(&Logger, pFileData, pFileData2)) + dwErrCode = Logger.PrintError("The file has different size/content when open without name"); + break; + + case TEST_OPEN_MPQ: + + // Attempt to open the (listfile), (attributes), (signature) + dwErrCode = TestArchive_LoadFiles(&Logger, hMpq, LISTFILE_NAME, ATTRIBUTES_NAME, SIGNATURE_NAME, NULL); + if(dwErrCode != ERROR_SUCCESS) + break; + + // Test setting position + if((dwWhatToDo & TEST_SETPOS) && (szFileName1 != NULL)) + { + dwErrCode = TestArchive_SetPos(hMpq, szFileName1); + if(dwErrCode != ERROR_SUCCESS) + break; + } + + // Search the archive + dwErrCode = SearchArchive(&Logger, hMpq, (bIsPartialMpq ? 0 : TEST_FLAG_LOAD_FILES), &dwFileCount); + break; + } } - return nError; + // Common cleanup + if(pFileData2 != NULL) + STORM_FREE(pFileData2); + if(pFileData != NULL) + STORM_FREE(pFileData); + if(hMpq != NULL) + SFileCloseArchive(hMpq); + return dwErrCode; } static int TestOpenArchive(LPCTSTR szPlainName, LPCTSTR szListFile = NULL, LPCSTR szFileName = NULL, bool bDontCopyArchive = false) @@ -2591,7 +2656,7 @@ static int TestOpenArchive_SetPos(LPCTSTR szPlainName, LPCSTR szFileName) // First, use the SFileSetFilePointer WITH the high-dword position if(nError == ERROR_SUCCESS) - nError = TestSetFilePointers(hFile, false); + nError = TestSetFilePointers(hFile, true); // Close the file SFileCloseFile(hFile); @@ -4282,11 +4347,30 @@ static const TEST_INFO TestList_MasterMirror[] = // {_T("MPQ_2013_v4_alternate-downloaded.MPQ"), _T("http://www.zezula.net\\mpqs\\alternate.zip"), 0} }; -#define TEST_OPEN_COMPARE_TWO_FILES 0x01 - static const TEST_INFO Test_Mpqs[] = { - {_T("MPQ_1997_v1_Diablo1_DIABDAT.MPQ"), NULL, TEST_OPEN_COMPARE_TWO_FILES, "music\\dintro.wav", "File00000023.xxx"}, + {_T("MPQ_1997_v1_Diablo1_DIABDAT.MPQ"), NULL, TEST_OPEN_COMPARE_TWO_FILES, "music\\dintro.wav", "File00000023.xxx"}, + {_T("MPQ_2016_v1_D2XP_IX86_1xx_114a.mpq"), NULL, TEST_LOAD_FILE_CHECK_CRC, "waitingroombkgd.dc6" }, // The update MPQ from Diablo II (patch 2016) + {_T("MPQ_2018_v1_icon_error.w3m"), NULL, TEST_LOAD_FILE_CHECK_CRC, "file00000002.blp" }, + + {_T("MPQ_1997_v1_Diablo1_STANDARD.SNP"), _T("ListFile_Blizzard.txt"), TEST_OPEN_MPQ }, // Open a file whose archive's (signature) file has flags = 0x90000000 + {_T("MPQ_1997_v1_Diablo1_DIABDAT.MPQ"), NULL, TEST_OPEN_MPQ | TEST_SETPOS, "music\\dtowne.wav" }, // Test the SFileSetFilePointer operations + {_T("MPQ_2012_v2_EmptyMpq.MPQ") }, // Open an empty archive (found in WoW cache - it's just a header) + {_T("MPQ_2013_v4_EmptyMpq.MPQ") }, // Open an empty archive (created artificially - it's just a header) + {_T("MPQ_2013_v4_patch-base-16357.MPQ") }, // Open an empty archive (found in WoW cache - it's just a header) + {_T("MPQ_2011_v4_InvalidHetEntryCount.MPQ") }, // Open an empty archive (A buggy MPQ with invalid HET entry count) + {_T("MPQ_2002_v1_BlockTableCut.MPQ") }, // Open a truncated archive + + // TODO: Doesn't work anymore + //{_T("MPQ_2010_v2_HasUserData.s2ma") }, // Open a MPQ that actually has user data + + {_T("MPQ_2014_v1_AttributesOneEntryLess.w3x") }, // Open an Warcraft III map whose "(attributes)" file has (BlockTableSize-1) entries + {_T("MPQ_2020_v1_AHF04patch.mix") }, // Open a MIX file + {_T("MPQ_2010_v3_expansion-locale-frFR.MPQ") }, // Open a MPQ archive v 3.0 + {_T("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE") }, // Open an encrypted archive from Starcraft II installer + {_T("MPx_2013_v1_LongwuOnline.mpk") }, // Open a MPK archive from Longwu online + {_T("MPx_2013_v1_WarOfTheImmortals.sqp"), _T("ListFile_WarOfTheImmortals.txt") }, // Open a SQP archive from War of the Immortals + {_T("part-file://MPQ_2010_v2_HashTableCompressed.MPQ.part") }, // Open a partial MPQ with compressed hash table }; //----------------------------------------------------------------------------- @@ -4304,10 +4388,13 @@ int _tmain(int argc, TCHAR * argv[]) printf("==== Test Suite for StormLib version %s ====\n", STORMLIB_VERSION_STRING); dwErrCode = InitializeMpqDirectory(argv, argc); + // // Open all files from the command line + // + for(int i = 1; i < argc; i++) { - SFILE_FIND_DATA sf; + //SFILE_FIND_DATA sf; HANDLE hFile = NULL; HANDLE hMpq = NULL; BYTE Buffer[0x40]; @@ -4343,31 +4430,31 @@ int _tmain(int argc, TCHAR * argv[]) // Test file stream operations // - if(dwErrCode == ERROR_SUCCESS) - { - for(size_t i = 0; i < _countof(TestList_StreamOps); i++) - { - dwErrCode = TestFileStreamOperations(TestList_StreamOps[i].szMpqName1, TestList_StreamOps[i].dwFlags); - if(dwErrCode != ERROR_SUCCESS) - break; - } - } + //if(dwErrCode == ERROR_SUCCESS) + //{ + // for(size_t i = 0; i < _countof(TestList_StreamOps); i++) + // { + // dwErrCode = TestFileStreamOperations(TestList_StreamOps[i].szMpqName1, TestList_StreamOps[i].dwFlags); + // if(dwErrCode != ERROR_SUCCESS) + // break; + // } + //} // // Test master-mirror reading operations // - if(dwErrCode == ERROR_SUCCESS) - { - for(size_t i = 0; i < _countof(TestList_MasterMirror); i++) - { - dwErrCode = TestReadFile_MasterMirror(TestList_MasterMirror[i].szMpqName1, - TestList_MasterMirror[i].szMpqName2, - TestList_MasterMirror[i].dwFlags != 0); - if(dwErrCode != ERROR_SUCCESS) - break; - } - } + //if(dwErrCode == ERROR_SUCCESS) + //{ + // for(size_t i = 0; i < _countof(TestList_MasterMirror); i++) + // { + // dwErrCode = TestReadFile_MasterMirror(TestList_MasterMirror[i].szMpqName1, + // TestList_MasterMirror[i].szMpqName2, + // TestList_MasterMirror[i].dwFlags != 0); + // if(dwErrCode != ERROR_SUCCESS) + // break; + // } + //} // // Search in listfile @@ -4390,7 +4477,7 @@ int _tmain(int argc, TCHAR * argv[]) // // Test opening various archives - correct, damaged, protected // -/* + if(dwErrCode == ERROR_SUCCESS) { for(size_t i = 0; i < _countof(Test_Mpqs); i++) @@ -4404,81 +4491,6 @@ int _tmain(int argc, TCHAR * argv[]) break; } } -*/ - // Test working with an archive that has no listfile - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenFile_OpenById(_T("MPQ_1997_v1_Diablo1_DIABDAT.MPQ")); - - // Open the update MPQ from Diablo II (patch 2016) - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenFile_OpenByName(_T("MPQ_2016_v1_D2XP_IX86_1xx_114a.mpq"), "waitingroombkgd.dc6"); - - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenFile_OpenByName(_T("MPQ_2018_v1_icon_error.w3m"), "file00000002.blp"); - - // Open a file whose archive's (signature) file has flags = 0x90000000 - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_1997_v1_Diablo1_STANDARD.SNP"), _T("ListFile_Blizzard.txt")); - - // Test the SFileSetFilePointer operations - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive_SetPos(_T("MPQ_1997_v1_Diablo1_DIABDAT.MPQ"), "music\\dtowne.wav"); - - // Open an empty archive (found in WoW cache - it's just a header) - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2012_v2_EmptyMpq.MPQ")); - - // Open an empty archive (created artificially - it's just a header) - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2013_v4_EmptyMpq.MPQ")); - - // Open an empty archive (found in WoW cache - it's just a header) - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2013_v4_patch-base-16357.MPQ")); - - // Open an empty archive (A buggy MPQ with invalid HET entry count) - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2011_v4_InvalidHetEntryCount.MPQ")); - - // Open a truncated archive - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2002_v1_BlockTableCut.MPQ")); - - // Open a MPQ that actually has user data - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2010_v2_HasUserData.s2ma")); - - // Open a file whose archive's (signature) file has flags = 0x90000000 - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_1997_v1_Diablo1_STANDARD.SNP"), _T("ListFile_Blizzard.txt")); - - // Open an Warcraft III map whose "(attributes)" file has (BlockTableSize-1) entries - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2014_v1_AttributesOneEntryLess.w3x")); - - // Open a MIX file - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2020_v1_AHF04patch.mix")); - - // Open a MPQ archive v 3.0 - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPQ_2010_v3_expansion-locale-frFR.MPQ")); - - // Open an encrypted archive from Starcraft II installer - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE")); - - // Open a MPK archive from Longwu online - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPx_2013_v1_LongwuOnline.mpk")); - - // Open a SQP archive from War of the Immortals - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("MPx_2013_v1_WarOfTheImmortals.sqp"), _T("ListFile_WarOfTheImmortals.txt")); - - // Open a partial MPQ with compressed hash table - if(dwErrCode == ERROR_SUCCESS) - dwErrCode = TestOpenArchive(_T("part-file://MPQ_2010_v2_HashTableCompressed.MPQ.part")); // Open an protected map if(dwErrCode == ERROR_SUCCESS) -- cgit v1.2.3