diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-09 14:51:40 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-09 14:51:40 +0100 |
commit | 5106d34fdaaf6378abf0fa8bb0b30d54a487e398 (patch) | |
tree | c5a25c22fdb67a0c0f0021c49380d081ff2679eb | |
parent | cc0ed30d33eb020c4fda8b7ceeb7fde7a0af9b41 (diff) |
+ Bugfixes
-rw-r--r-- | StormLib_test.vcproj | 2 | ||||
-rw-r--r-- | src/SBaseCommon.cpp | 3 | ||||
-rw-r--r-- | src/SBaseFileTable.cpp | 161 | ||||
-rw-r--r-- | src/SFileAttributes.cpp | 36 | ||||
-rw-r--r-- | src/SFileCompactArchive.cpp | 3 | ||||
-rw-r--r-- | src/SFileCreateArchive.cpp | 6 | ||||
-rw-r--r-- | src/SFileOpenArchive.cpp | 30 | ||||
-rw-r--r-- | src/StormCommon.h | 21 | ||||
-rw-r--r-- | src/StormLib.h | 3 | ||||
-rw-r--r-- | test/Readme.txt | 4 | ||||
-rw-r--r-- | test/Test.cpp | 543 |
11 files changed, 481 insertions, 331 deletions
diff --git a/StormLib_test.vcproj b/StormLib_test.vcproj index 9d6ac24..3276b53 100644 --- a/StormLib_test.vcproj +++ b/StormLib_test.vcproj @@ -106,7 +106,7 @@ OutputDirectory="./bin/$(ProjectName)/$(PlatformName)/$(ConfigurationName)" IntermediateDirectory="./bin/$(ProjectName)/$(PlatformName)/$(ConfigurationName)" ConfigurationType="1" - CharacterSet="2" + CharacterSet="1" > <Tool Name="VCPreBuildEventTool" diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 3bb10de..a0e789b 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -710,8 +710,7 @@ void * LoadMpqTable( pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize); if(pbMpqTable != NULL) { - // "interface.MPQ.part" in trial version of World of Warcraft - // has block table and hash table compressed. + // Check if the MPQ table is encrypted if(dwCompressedSize < dwTableSize) { // Allocate temporary buffer for holding compressed data diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 6b4eada..bf6be59 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -229,47 +229,53 @@ void SetBits( //----------------------------------------------------------------------------- // Support for MPQ header -static DWORD GetArchiveSize32(TMPQArchive * ha, TMPQBlock * pBlockTable, DWORD dwBlockTableSize) +static DWORD GetMaxFileOffset32(TMPQArchive * ha, TMPQBlock * pBlockTable, DWORD dwBlockTableSize) { TMPQHeader * pHeader = ha->pHeader; - ULONGLONG FileSize = 0; - DWORD dwArchiveSize = pHeader->dwHeaderSize; + DWORD dwMaxFileOffset = ha->pHeader->dwArchiveSize; DWORD dwByteOffset; DWORD dwBlockIndex; - // Increment by hash table size - dwByteOffset = pHeader->dwHashTablePos + (pHeader->dwHashTableSize * sizeof(TMPQHash)); - if(dwByteOffset > dwArchiveSize) - dwArchiveSize = dwByteOffset; + // We can call this only for malformed archives v 1.0 + assert(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1); + assert((ha->dwFlags & MPQ_FLAG_MALFORMED) != 0); + + // If the block table is at the end, decrement the limit by the block table + if(pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)) >= dwMaxFileOffset) + dwMaxFileOffset = pHeader->dwBlockTablePos; + + // If the hash table is at the end, decrement the limit by the hash table + if(pHeader->dwHashTablePos + (pHeader->dwHashTableSize * sizeof(TMPQBlock)) >= dwMaxFileOffset) + dwMaxFileOffset = pHeader->dwHashTablePos; + + // Return what we found + return dwMaxFileOffset; +} - // Increment by block table size - dwByteOffset = pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); - if(dwByteOffset > dwArchiveSize) - dwArchiveSize = dwByteOffset; +static ULONGLONG DetermineEndOfArchive_V1_V2( + TMPQArchive * ha, + ULONGLONG MpqOffset, + ULONGLONG FileSize) +{ + ULONGLONG ByteOffset; + ULONGLONG EndOfMpq = FileSize; + DWORD SignatureHeader = 0; - // If any of the MPQ files is beyond the hash table/block table, set the end to the file size - for(dwBlockIndex = 0; dwBlockIndex < dwBlockTableSize; dwBlockIndex++) + // Check if there is a signature header + if((EndOfMpq - MpqOffset) > (MPQ_STRONG_SIGNATURE_SIZE + 4)) { - // Only count files that exists - if(pBlockTable[dwBlockIndex].dwFlags & MPQ_FILE_EXISTS) - { - // If this file begins past the end of tables, - // assume that the hash/block table is not at the end of the archive - if(pBlockTable[dwBlockIndex].dwFilePos > dwArchiveSize) - { - FileStream_GetSize(ha->pStream, &FileSize); - dwArchiveSize = (DWORD)(FileSize - ha->MpqPos); - break; - } - } + ByteOffset = EndOfMpq - MPQ_STRONG_SIGNATURE_SIZE - 4; + FileStream_Read(ha->pStream, &ByteOffset, &SignatureHeader, sizeof(DWORD)); + if(BSWAP_INT32_UNSIGNED(SignatureHeader) == MPQ_STRONG_SIGNATURE_ID) + EndOfMpq = EndOfMpq - MPQ_STRONG_SIGNATURE_SIZE - 4; } - // Return what we found - return dwArchiveSize; + // Return the returned archive size + return (EndOfMpq - MpqOffset); } // This function converts the MPQ header so it always looks like version 4 -static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader) +/*static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader) { ULONGLONG ArchiveSize = pHeader->dwHeaderSize; ULONGLONG ByteOffset = pHeader->dwHeaderSize; @@ -316,13 +322,16 @@ static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader) return ArchiveSize; } - +*/ int ConvertMpqHeaderToFormat4( TMPQArchive * ha, + ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags) { TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; + ULONGLONG BlockTablePos64 = 0; + ULONGLONG HashTablePos64 = 0; ULONGLONG ByteOffset; USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion); int nError = ERROR_SUCCESS; @@ -342,7 +351,7 @@ int ConvertMpqHeaderToFormat4( if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) { pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; - ha->dwFlags |= MPQ_FLAG_PROTECTED; + ha->dwFlags |= MPQ_FLAG_MALFORMED; } // @@ -351,35 +360,87 @@ int ConvertMpqHeaderToFormat4( // ("w3xmaster" protector). // - // Fill the rest of the header with zeros + Label_ArchiveVersion1: + if(pHeader->dwHashTablePos <= pHeader->dwHeaderSize) + ha->dwFlags |= MPQ_FLAG_MALFORMED; + if(pHeader->dwBlockTablePos <= pHeader->dwHeaderSize) + ha->dwFlags |= MPQ_FLAG_MALFORMED; + if(pHeader->dwArchiveSize != pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock))) + ha->dwFlags |= MPQ_FLAG_MALFORMED; + + // Fill the rest of the header memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V1, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V1); pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + + // Determine the archive size on malformed MPQs + if(ha->dwFlags & MPQ_FLAG_MALFORMED) + { + pHeader->ArchiveSize64 = DetermineEndOfArchive_V1_V2(ha, MpqOffset, FileSize); + pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64; + } break; case MPQ_FORMAT_VERSION_2: - // Check for malformed MPQ header version 2.0 + // Check for malformed MPQ header version 1.0 BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_2); if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V2) { pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; - pHeader->HiBlockTablePos64 = 0; - pHeader->wHashTablePosHi = 0; - pHeader->wBlockTablePosHi = 0; - ha->dwFlags |= MPQ_FLAG_PROTECTED; + ha->dwFlags |= MPQ_FLAG_MALFORMED; + goto Label_ArchiveVersion1; } // Fill the rest of the header with zeros memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2); - if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) - pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); - if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - if(pHeader->HiBlockTablePos64) - pHeader->HiBlockTableSize64 = pHeader->dwBlockTableSize * sizeof(USHORT); - pHeader->ArchiveSize64 = GetArchiveSize64(pHeader); + + // Calculate the expected hash table size + pHeader->HashTableSize64 = (pHeader->dwHashTableSize * sizeof(TMPQHash)); + HashTablePos64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + + // Calculate the expected block table size + pHeader->BlockTableSize64 = (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + BlockTablePos64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + + // We require the block table to follow hash table + if(BlockTablePos64 > HashTablePos64) + { + // HashTableSize64 may be less than TblSize * sizeof(TMPQHash). + // That means that the hash table is compressed. + pHeader->HashTableSize64 = BlockTablePos64 - HashTablePos64; + + // Calculate the compressed block table size + if(pHeader->HiBlockTablePos64 != 0) + { + // BlockTableSize64 may be less than TblSize * sizeof(TMPQBlock). + // That means that the hash table is compressed. + pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - BlockTablePos64; + assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock))); + + // Determine the size of the hi-block table + pHeader->HiBlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, MpqOffset, FileSize) - pHeader->HiBlockTablePos64; + assert(pHeader->HiBlockTableSize64 == (pHeader->dwBlockTableSize * sizeof(USHORT))); + + // Recalculate the archive size + pHeader->ArchiveSize64 = pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64; + } + else + { + // Block table size is the end of the archive minus the block table position + pHeader->BlockTableSize64 = DetermineEndOfArchive_V1_V2(ha, MpqOffset, FileSize) - BlockTablePos64; + assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock))); + + // dwArchiveSize in the header v 2.0 is 32-bit only + pHeader->ArchiveSize64 = BlockTablePos64 + pHeader->BlockTableSize64; + } + } + else + { + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + ha->dwFlags |= MPQ_FLAG_MALFORMED; + } break; case MPQ_FORMAT_VERSION_3: @@ -2123,7 +2184,7 @@ static void FixBlockTableSize( { dwFixedTableSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); BlockTableEnd = FileDataStart; - ha->dwFlags |= MPQ_FLAG_PROTECTED; + ha->dwFlags |= MPQ_FLAG_MALFORMED; } } @@ -2159,7 +2220,7 @@ static void FixCompressedFileSize( TMPQBlock * pBlock; size_t nElements = 0; size_t nIndex; - DWORD dwArchiveSize; + DWORD dwMaxFileOffs; // Only perform this check on MPQs version 1.0 assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1); @@ -2169,7 +2230,7 @@ static void FixCompressedFileSize( if(SortTable != NULL) { // Calculate the end of the archive - dwArchiveSize = GetArchiveSize32(ha, pBlockTable, pHeader->dwBlockTableSize); + dwMaxFileOffs = GetMaxFileOffset32(ha, pBlockTable, pHeader->dwBlockTableSize); // Put all blocks to a sort table for(pBlock = pBlockTable; pBlock < pBlockTableEnd; pBlock++) @@ -2191,12 +2252,12 @@ static void FixCompressedFileSize( TMPQBlock * pBlock1 = SortTable[nIndex + 1]; TMPQBlock * pBlock0 = SortTable[nIndex]; - pBlock0->dwCSize = (pBlock->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (pBlock1->dwFilePos - pBlock0->dwFilePos) : pBlock0->dwFSize; + pBlock0->dwCSize = (pBlock0->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (pBlock1->dwFilePos - pBlock0->dwFilePos) : pBlock0->dwFSize; } // Fix the last entry pBlock = SortTable[nElements - 1]; - pBlock->dwCSize = dwArchiveSize - pBlock->dwFilePos; + pBlock->dwCSize = (pBlock->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (dwMaxFileOffs - pBlock->dwFilePos) : pBlock->dwFSize; } STORM_FREE(SortTable); @@ -2239,7 +2300,7 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries) pHeader->BlockTableSize64 = FileSize - ByteOffset; pHeader->dwBlockTableSize = (DWORD)(pHeader->BlockTableSize64 / sizeof(TMPQBlock)); dwTableSize = dwCmpSize = (DWORD)pHeader->BlockTableSize64; - ha->dwFlags |= MPQ_FLAG_PROTECTED; + ha->dwFlags |= MPQ_FLAG_MALFORMED; } // @@ -2259,7 +2320,7 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries) FixBlockTableSize(ha, pBlockTable); // Defense against protectors that set invalid compressed size - if((ha->dwFlags & MPQ_FLAG_PROTECTED) && (bDontFixEntries == false)) + if((ha->dwFlags & MPQ_FLAG_MALFORMED) && (bDontFixEntries == false)) FixCompressedFileSize(ha, pBlockTable); } break; diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp index 7f12028..a0ba774 100644 --- a/src/SFileAttributes.cpp +++ b/src/SFileAttributes.cpp @@ -40,16 +40,39 @@ static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwFileTableSize) cbAttrFile += dwFileTableSize * sizeof(ULONGLONG); if(dwAttrFlags & MPQ_ATTRIBUTE_MD5) cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE; - - // The bit array has been create without the last bit belonging to (attributes) + + // The bit array has been created without the last bit belonging to (attributes) // When the number of files is a multiplier of 8 plus one, then the size of (attributes) // if 1 byte less than expected. // Example: wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) cbAttrFile += (dwFileTableSize + 6) / 8; + + return cbAttrFile; +} + +#ifdef _DEBUG +static DWORD GetSizeOfAttributesFile_v2(DWORD dwAttrFlags, DWORD dwFileTableSize) +{ + DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER); + + // Calculate size of the (attributes) file + if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32) + cbAttrFile += dwFileTableSize * sizeof(DWORD); + if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + cbAttrFile += dwFileTableSize * sizeof(ULONGLONG); + if(dwAttrFlags & MPQ_ATTRIBUTE_MD5) + cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE; + + // interface.MPQ.part from WoW build 10958 has + // the MPQ_ATTRIBUTE_PATCH_BIT set, but there's an array of DWORDs instead. + // The array is filled with zeros, so we don't know what it should contain + if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) + cbAttrFile += dwFileTableSize * sizeof(DWORD); return cbAttrFile; } +#endif static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile) { @@ -67,9 +90,14 @@ static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrF if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1) return ERROR_BAD_FORMAT; - // Verify the flags and size of the file + // Verify the size of the file assert((pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL) == 0); - assert(GetSizeOfAttributesFile(pAttrHeader->dwFlags, dwBlockTableSize) == cbAttrFile); + + // Verify the size of the file +#ifdef _DEBUG + assert(cbAttrFile == GetSizeOfAttributesFile(pAttrHeader->dwFlags, dwBlockTableSize) || + cbAttrFile == GetSizeOfAttributesFile_v2(pAttrHeader->dwFlags, dwBlockTableSize)); +#endif ha->dwAttrFlags = pAttrHeader->dwFlags; pbAttrPtr = (LPBYTE)(pAttrHeader + 1); diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp index 64c599d..ad9801b 100644 --- a/src/SFileCompactArchive.cpp +++ b/src/SFileCompactArchive.cpp @@ -332,14 +332,13 @@ static int CopyMpqFileSectors( { // At this point, number of bytes written should be exactly // the same like the compressed file size. If it isn't, - // there's something wrong (an unknown archive version, MPQ protection, ...) + // there's something wrong (an unknown archive version, MPQ malformation, ...) // // Note: Diablo savegames have very weird layout, and the file "hero" // seems to have improper compressed size. Instead of real compressed size, // the "dwCmpSize" member of the block table entry contains // uncompressed size of file data + size of the sector table. // If we compact the archive, Diablo will refuse to load the game - // Seems like some sort of protection to me. // // Note: Some patch files in WOW patches don't count the patch header // into compressed size diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp index 2b51efa..4ae71e9 100644 --- a/src/SFileCreateArchive.cpp +++ b/src/SFileCreateArchive.cpp @@ -80,11 +80,15 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWO CreateInfo.dwStreamFlags = STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE; CreateInfo.dwFileFlags1 = (dwCreateFlags & MPQ_CREATE_LISTFILE) ? MPQ_FILE_EXISTS : 0; CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_EXISTS : 0; - CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0; + CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? (MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5) : 0; CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000; CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0; CreateInfo.dwMaxFileCount = dwMaxFileCount; + // Set the proper attribute parts + if((CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) && (dwCreateFlags & MPQ_CREATE_ATTRIBUTES)) + CreateInfo.dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; + // Backward compatibility: SFileCreateArchive always used to add (listfile) // We would break loads of applications if we change that CreateInfo.dwFileFlags1 = MPQ_FILE_EXISTS; diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index b05cff3..417b43d 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -213,7 +213,7 @@ bool WINAPI SFileOpenArchive( // Initialize handle structure and allocate structure for MPQ header if(nError == ERROR_SUCCESS) { - ULONGLONG SearchPos = 0; + ULONGLONG SearchOffset = 0; DWORD dwHeaderID; memset(ha, 0, sizeof(TMPQArchive)); @@ -230,23 +230,23 @@ bool WINAPI SFileOpenArchive( ha->dwFlags |= MPQ_FLAG_CHECK_SECTOR_CRC; // Find the offset of MPQ header within the file - while(SearchPos < FileSize) + while(SearchOffset < FileSize) { DWORD dwBytesAvailable = MPQ_HEADER_SIZE_V4; // Cut the bytes available, if needed - if((FileSize - SearchPos) < MPQ_HEADER_SIZE_V4) - dwBytesAvailable = (DWORD)(FileSize - SearchPos); + if((FileSize - SearchOffset) < MPQ_HEADER_SIZE_V4) + dwBytesAvailable = (DWORD)(FileSize - SearchOffset); // Read the eventual MPQ header - if(!FileStream_Read(ha->pStream, &SearchPos, ha->HeaderData, dwBytesAvailable)) + if(!FileStream_Read(ha->pStream, &SearchOffset, ha->HeaderData, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. - if(SearchPos == 0 && IsAviFile(ha->HeaderData)) + if(SearchOffset == 0 && IsAviFile(ha->HeaderData)) { nError = ERROR_AVI_FILE; break; @@ -257,16 +257,16 @@ bool WINAPI SFileOpenArchive( if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { // Verify if this looks like a valid user data - pUserData = IsValidMpqUserData(SearchPos, FileSize, ha->HeaderData); + pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); if(pUserData != NULL) { // Fill the user data header - ha->UserDataPos = SearchPos; + ha->UserDataPos = SearchOffset; ha->pUserData = &ha->UserData; memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); // Continue searching from that position - SearchPos += ha->pUserData->dwHeaderOffs; + SearchOffset += ha->pUserData->dwHeaderOffs; continue; } } @@ -275,7 +275,7 @@ bool WINAPI SFileOpenArchive( if(dwHeaderID == ID_MPQ) { // Now convert the header to version 4 - nError = ConvertMpqHeaderToFormat4(ha, FileSize, dwFlags); + nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags); break; } @@ -295,7 +295,7 @@ bool WINAPI SFileOpenArchive( } // Move to the next possible offset - SearchPos += 0x200; + SearchOffset += 0x200; } // Did we identify one of the supported headers? @@ -303,11 +303,11 @@ bool WINAPI SFileOpenArchive( { // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) - ha->UserDataPos = SearchPos; + ha->UserDataPos = SearchOffset; // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; - ha->MpqPos = SearchPos; + ha->MpqPos = SearchOffset; // Sector size must be nonzero. if(ha->pHeader->wSectorSize == 0) @@ -376,8 +376,8 @@ bool WINAPI SFileOpenArchive( nError = BuildFileTable(ha); } - // Verify the file table, if no kind of protection was detected - if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0) + // Verify the file table, if no kind of malformation was detected + if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_MALFORMED) == 0) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; ULONGLONG RawFilePos; diff --git a/src/StormCommon.h b/src/StormCommon.h index bfe99f7..aeedbca 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -74,20 +74,21 @@ // MPQ signature information // Size of each signature type -#define MPQ_WEAK_SIGNATURE_SIZE 64 -#define MPQ_STRONG_SIGNATURE_SIZE 256 +#define MPQ_WEAK_SIGNATURE_SIZE 64 +#define MPQ_STRONG_SIGNATURE_SIZE 256 +#define MPQ_STRONG_SIGNATURE_ID 0x5349474E // ID of the strong signature ("NGIS") // MPQ signature info typedef struct _MPQ_SIGNATURE_INFO { - ULONGLONG BeginMpqData; // File offset where the hashing starts - ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) - ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) - ULONGLONG EndMpqData; // File offset where the hashing ends - ULONGLONG EndOfFile; // Size of the entire file + ULONGLONG BeginMpqData; // File offset where the hashing starts + ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) + ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) + ULONGLONG EndMpqData; // File offset where the hashing ends + ULONGLONG EndOfFile; // Size of the entire file BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; - DWORD cbSignatureSize; // Length of the signature - DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX + DWORD cbSignatureSize; // Length of the signature + DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX } MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; @@ -167,7 +168,7 @@ TMPQFile * IsValidFileHandle(HANDLE hFile); //----------------------------------------------------------------------------- // Support for MPQ file tables -int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); +int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags); TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale); TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName); diff --git a/src/StormLib.h b/src/StormLib.h index 3659c23..9cdb28b 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -174,7 +174,7 @@ extern "C" { // Flags for TMPQArchive::dwFlags #define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access #define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed -#define MPQ_FLAG_PROTECTED 0x00000004 // Some kind of protector detected (W3M maps) +#define MPQ_FLAG_MALFORMED 0x00000004 // Malformed data structure detected (W3M map protectors) #define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000008 // Checking sector CRC when reading files #define MPQ_FLAG_LISTFILE_INVALID 0x00000020 // If set, it means that the (listfile) has been invalidated #define MPQ_FLAG_ATTRIBUTES_INVALID 0x00000040 // If set, it means that the (attributes) has been invalidated @@ -286,6 +286,7 @@ extern "C" { // Deprecated #define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY #define MPQ_OPEN_ENCRYPTED STREAM_PROVIDER_ENCRYPTED +#define MPQ_OPEN_PARTIAL STREAM_PROVIDER_PARTIAL // Flags for SFileCreateArchive #define MPQ_CREATE_LISTFILE 0x00100000 // Also add the (listfile) file diff --git a/test/Readme.txt b/test/Readme.txt index 8ef0c67..19bdf53 100644 --- a/test/Readme.txt +++ b/test/Readme.txt @@ -46,7 +46,7 @@ MPQ_1997_v1_Diablo1_single_0.sha 201 // Single player MPQ_1997_v1_Diablo1_single_0.sv 98 980 MPQ_1999_v1_WeakSignature.exe 1 031 826 // War2Patch_202.exe from Warcraft II Bnet Edition MPQ_1999_v1_WeakSignature.sha 260 -MPQ_2002_v1_BlockTableCut.MPQ 27 765 301 // Cut file War3Patch.mpq from Warcraft III +MPQ_2002_v1_BlockTableCut.MPQ 27 498 436 // Cut file War3Patch.mpq from Warcraft III MPQ_2002_v1_BlockTableCut.sha 250 MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.sha 1 067 MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.w3x 1 089 638 // Protected Warcraft III map @@ -58,6 +58,8 @@ MPQ_2002_v1_StrongSignature.sha 250 MPQ_2002_v1_StrongSignature.w3m 306 818 // (10)DustwallowKeys.w3m from Warcraft III MPQ_2009_v2_WoW_patch.MPQ.part 31 396 380 // patch.MPQ.part from trial WoW build 10958 MPQ_2009_v2_WoW_patch.MPQ.sha 226 +MPQ_2010_v2_HashTableCompressed.MPQ.part 14 546 972 // interface.MPQ.part from WoW build 10958 +MPQ_2010_v2_HashTableCompressed.MPQ.sha 277 MPQ_2010_v2_HasUserData.s2ma 1 972 177 // (4) - AI - Kulas Ravine (1x).s2ma from Starcraft II Beta MPQ_2010_v2_HasUserData.sha 261 MPQ_2010_v3_expansion-locale-frFR.MPQ 2 980 489 // expansion-locale-frFR.MPQ from WoW 12911 diff --git a/test/Test.cpp b/test/Test.cpp index 9dcea46..eee67d5 100644 --- a/test/Test.cpp +++ b/test/Test.cpp @@ -173,116 +173,56 @@ static const char * PatchList_WoW16965[] = static char szMpqDirectory[MAX_PATH]; size_t cchMpqDirectory = 0; -static size_t ConvertSha1ToText(const unsigned char * sha1_digest, char * szSha1Text) -{ - const char * szTable = "0123456789abcdef"; - - for(size_t i = 0; i < SHA1_DIGEST_SIZE; i++) - { - *szSha1Text++ = szTable[(sha1_digest[0] >> 0x04)]; - *szSha1Text++ = szTable[(sha1_digest[0] & 0x0F)]; - sha1_digest++; - } - - *szSha1Text = 0; - return (SHA1_DIGEST_SIZE * 2); -} - -#ifdef _UNICODE -static const TCHAR * GetShortPlainName(const TCHAR * szFileName) +static bool IsMpqExtension(const char * szFileName) { - const TCHAR * szPlainName = szFileName; - const TCHAR * szPlainEnd = szFileName + _tcslen(szFileName); - - // If there is terminating slash or backslash, move to it - while(szFileName < szPlainEnd) - { - if(szFileName[0] == _T('\\') || szFileName[0] == _T('/')) - szPlainName = szFileName + 1; - szFileName++; + const char * szExtension = strrchr(szFileName, '.'); + + if(szExtension != NULL) + { + if(!_stricmp(szExtension, ".mpq")) + return true; + if(!_stricmp(szExtension, ".w3m")) + return true; + if(!_stricmp(szExtension, ".w3x")) + return true; + if(!_stricmp(szExtension, ".mpqe")) + return true; + if(!_stricmp(szExtension, ".part")) + return true; + if(!_stricmp(szExtension, ".sv")) + return true; + if(!_stricmp(szExtension, ".s2ma")) + return true; + if(!_stricmp(szExtension, ".SC2Map")) + return true; } - - // If the name is still too long, cut it - if((szPlainEnd - szPlainName) > 50) - szPlainName = szPlainEnd - 50; - return szPlainName; + + return false; } -static void CreateFullPathName(TCHAR * szBuffer, const char * szSubDir, const char * szNamePart1, const char * szNamePart2 = NULL) +static bool IsUnicodeNameConvertableToAnsi(const TCHAR * szFileNameT, const char * szFileNameA) { - size_t nLength; - - // Copy the master MPQ directory - mbstowcs(szBuffer, szMpqDirectory, cchMpqDirectory); - szBuffer += cchMpqDirectory; - - // Append the subdirectory, if any - if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0) - { - // No leading or trailing separators allowed - assert(szSubDir[0] != '/' && szSubDir[0] != '\\'); - assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\'); - - // Append file path separator - *szBuffer++ = PATH_SEPARATOR; - - // Copy the subdirectory - mbstowcs(szBuffer, szSubDir, nLength); - - // Fix the path separators - for(size_t i = 0; i < nLength; i++) - szBuffer[i] = (szBuffer[i] != '\\' && szBuffer[i] != '/') ? szBuffer[i] : PATH_SEPARATOR; - - // Move the buffer pointer - szBuffer += nLength; - } - - // Copy the file name, if any - if(szNamePart1 != NULL && (nLength = strlen(szNamePart1)) != 0) - { - // No path separator can be there - assert(strchr(szNamePart1, '\\') == NULL); - assert(strchr(szNamePart1, '/') == NULL); - - // Append file path separator - *szBuffer++ = PATH_SEPARATOR; - - // Copy the file name - mbstowcs(szBuffer, szNamePart1, nLength); - szBuffer += nLength; - } + TCHAR szUnicodeName[MAX_PATH]; - // Append the second part of the name - if(szNamePart2 != NULL && (nLength = strlen(szNamePart2)) != 0) - { - // Copy the file name - mbstowcs(szBuffer, szNamePart2, nLength); - szBuffer += nLength; - } - - // Terminate the buffer with zero - *szBuffer = 0; + // Convert the ANSI to UNICODE and compare them + CopyFileName(szUnicodeName, szFileNameA, strlen(szFileNameA)); + return (_tcsicmp(szUnicodeName, szFileNameT) == 0); } -TFileStream * FileStream_OpenFile(const char * szFileName, DWORD dwStreamFlags) +static size_t ConvertSha1ToText(const unsigned char * sha1_digest, char * szSha1Text) { - TFileStream * pStream = NULL; - TCHAR * szFileNameT; - size_t nLength = strlen(szFileName); + const char * szTable = "0123456789abcdef"; - // Allocate buffer for the UNICODE file name - szFileNameT = STORM_ALLOC(TCHAR, nLength + 1); - if(szFileNameT != NULL) + for(size_t i = 0; i < SHA1_DIGEST_SIZE; i++) { - CopyFileName(szFileNameT, szFileName, nLength); - pStream = FileStream_OpenFile(szFileNameT, dwStreamFlags); - STORM_FREE(szFileNameT); + *szSha1Text++ = szTable[(sha1_digest[0] >> 0x04)]; + *szSha1Text++ = szTable[(sha1_digest[0] & 0x0F)]; + sha1_digest++; } - // Return what we got - return pStream; + *szSha1Text = 0; + return (SHA1_DIGEST_SIZE * 2); } -#endif static const char * GetShortPlainName(const char * szFileName) { @@ -359,13 +299,29 @@ static void CreateFullPathName(char * szBuffer, const char * szSubDir, const cha *szBuffer = 0; } +TFileStream * OpenLocalFile(const char * szFileName, DWORD dwStreamFlags) +{ + TCHAR szFileNameT[MAX_PATH]; + + CopyFileName(szFileNameT, szFileName, strlen(szFileName)); + return FileStream_OpenFile(szFileNameT, dwStreamFlags); +} + +TFileStream * CreateLocalFile(const char * szFileName, DWORD dwStreamFlags) +{ + TCHAR szFileNameT[MAX_PATH]; + + CopyFileName(szFileNameT, szFileName, strlen(szFileName)); + return FileStream_CreateFile(szFileNameT, dwStreamFlags); +} + static int InitializeMpqDirectory(char * argv[], int argc) { TLogHelper Logger("InitWorkDir"); TFileStream * pStream; const char * szWhereFrom = NULL; const char * szDirName; - TCHAR szFileName[MAX_PATH]; + char szFullPath[MAX_PATH]; #ifdef _MSC_VER // Mix the random number generator @@ -397,8 +353,8 @@ static int InitializeMpqDirectory(char * argv[], int argc) Logger.PrintMessage("Work directory %s (%s)", szMpqDirectory, szWhereFrom); // Verify if the work MPQ directory is writable - CreateFullPathName(szFileName, NULL, "TestFile.bin"); - pStream = FileStream_CreateFile(szFileName, 0); + CreateFullPathName(szFullPath, NULL, "TestFile.bin"); + pStream = CreateLocalFile(szFullPath, 0); if(pStream == NULL) return Logger.PrintError("MPQ subdirectory is not writable"); @@ -406,10 +362,10 @@ static int InitializeMpqDirectory(char * argv[], int argc) FileStream_Close(pStream); // Verify if the working directory exists and if there is a subdirectory with the file name - CreateFullPathName(szFileName, szMpqSubDir, "ListFile_Blizzard.txt"); - pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY); + CreateFullPathName(szFullPath, szMpqSubDir, "ListFile_Blizzard.txt"); + pStream = OpenLocalFile(szFullPath, STREAM_FLAG_READ_ONLY); if(pStream == NULL) - return Logger.PrintError(_T("The main listfile (%s) was not found. Check your paths"), szFileName); + return Logger.PrintError("The main listfile (%s) was not found. Check your paths", GetShortPlainName(szFullPath)); // Close the stream FileStream_Close(pStream); @@ -495,22 +451,27 @@ static int VerifyFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * return ERROR_SUCCESS; } -static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONGLONG FileSize, TCHAR * szBuffer) +static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONGLONG FileSize, char * szBuffer) { TFileStream * pStream; + char szFullPath[MAX_PATH]; // Notify the user pLogger->PrintProgress("Creating empty file %s ...", szPlainName); // Construct the full path and crete the file - CreateFullPathName(szBuffer, NULL, szPlainName); - pStream = FileStream_CreateFile(szBuffer, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + CreateFullPathName(szFullPath, NULL, szPlainName); + pStream = CreateLocalFile(szFullPath, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pStream == NULL) - return pLogger->PrintError(_T("Failed to create file %s"), szBuffer); + return pLogger->PrintError("Failed to create file %s", szBuffer); // Write the required size FileStream_SetSize(pStream, FileSize); FileStream_Close(pStream); + + // Give the caller the full file name + if(szBuffer != NULL) + strcpy(szBuffer, szFullPath); return ERROR_SUCCESS; } @@ -633,7 +594,7 @@ static int CreateMpqCopy( TLogHelper * pLogger, const char * szPlainName, const char * szFileCopy, - TCHAR * szBuffer, + char * szBuffer, ULONGLONG PreMpqDataSize = 0, ULONGLONG UserDataSize = 0) { @@ -641,8 +602,8 @@ static int CreateMpqCopy( TFileStream * pStream2; // Target file ULONGLONG ByteOffset = 0; ULONGLONG FileSize = 0; - TCHAR szFileName1[MAX_PATH]; - TCHAR szFileName2[MAX_PATH]; + char szFileName1[MAX_PATH]; + char szFileName2[MAX_PATH]; int nError = ERROR_SUCCESS; // Notify the user @@ -651,22 +612,22 @@ static int CreateMpqCopy( // Construct both file names. Check if they are not the same CreateFullPathName(szFileName1, szMpqSubDir, szPlainName); CreateFullPathName(szFileName2, NULL, szFileCopy); - if(!_tcsicmp(szFileName1, szFileName2)) + if(!_stricmp(szFileName1, szFileName2)) { pLogger->PrintError("Failed to create copy of MPQ (the copy name is the same like the original name)"); return ERROR_CAN_NOT_COMPLETE; } // Open the source file - pStream1 = FileStream_OpenFile(szFileName1, STREAM_FLAG_READ_ONLY); + pStream1 = OpenLocalFile(szFileName1, STREAM_FLAG_READ_ONLY); if(pStream1 == NULL) { - pLogger->PrintError(_T("Failed to open the source file %s"), szFileName1); + pLogger->PrintError("Failed to open the source file %s", szFileName1); return ERROR_CAN_NOT_COMPLETE; } // Create the destination file - pStream2 = FileStream_CreateFile(szFileName2, 0); + pStream2 = CreateLocalFile(szFileName2, 0); if(pStream2 != NULL) { // If we should write some pre-MPQ data to the target file, do it @@ -700,7 +661,7 @@ static int CreateMpqCopy( FileStream_Close(pStream1); if(szBuffer != NULL) - _tcscpy(szBuffer, szFileName2); + strcpy(szBuffer, szFileName2); if(nError != ERROR_SUCCESS) pLogger->PrintError("Failed to create copy of MPQ"); return nError; @@ -805,7 +766,7 @@ static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName, pLogger->PrintProgress("Loading local file ..."); // Attempt to open the file - pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY); + pStream = OpenLocalFile(szFileName, STREAM_FLAG_READ_ONLY); if(pStream == NULL) { if(pLogger != NULL && bMustSucceed == true) @@ -1026,17 +987,21 @@ static int SearchArchive( return nError; } -static int CreateNewArchive_FullPath(TLogHelper * pLogger, const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +static int CreateNewArchive(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) { HANDLE hMpq = NULL; + TCHAR szMpqName[MAX_PATH]; + char szFullPath[MAX_PATH]; // Make sure that the MPQ is deleted - _tremove(szMpqName); + CreateFullPathName(szFullPath, NULL, szPlainName); + remove(szFullPath); // Fix the flags dwCreateFlags |= (MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES); // Create the new MPQ + CopyFileName(szMpqName, szFullPath, strlen(szFullPath)); if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq)) return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName); @@ -1049,37 +1014,85 @@ static int CreateNewArchive_FullPath(TLogHelper * pLogger, const TCHAR * szMpqNa return ERROR_SUCCESS; } -static int CreateNewArchive_AddPrefix(TLogHelper * pLogger, const wchar_t * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +// Creates new archive with UNICODE name. Adds prefix to the name +static int CreateNewArchiveU(TLogHelper * pLogger, const wchar_t * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount) { #ifdef _UNICODE - wchar_t szMpqName[MAX_PATH]; + HANDLE hMpq = NULL; + TCHAR szMpqName[MAX_PATH]; + char szFullPath[MAX_PATH]; - CreateFullPathName(szMpqName, NULL, "StormLibTest_"); - _tcscat(szMpqName, szPlainName); - return CreateNewArchive_FullPath(pLogger, szMpqName, dwCreateFlags, dwMaxFileCount, phMpq); + // Construct the full UNICODE name + CreateFullPathName(szFullPath, NULL, "StormLibTest_"); + CopyFileName(szMpqName, szFullPath, strlen(szFullPath)); + wcscat(szMpqName, szPlainName); + + // Make sure that the MPQ is deleted + _tremove(szMpqName); + + // Create the archive + pLogger->PrintProgress("Creating new archive with UNICODE name ..."); + if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq)) + return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName); + + SFileCloseArchive(hMpq); #else pLogger = pLogger; szPlainName = szPlainName; dwCreateFlags = dwCreateFlags; dwMaxFileCount = dwMaxFileCount; - phMpq = phMpq; - return ERROR_SUCCESS; #endif + return ERROR_SUCCESS; } -static int CreateNewArchive(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +static int OpenExistingArchive(TLogHelper * pLogger, const char * szFullPath, DWORD dwFlags, HANDLE * phMpq) { + HANDLE hMpq = NULL; TCHAR szMpqName[MAX_PATH]; + int nError = ERROR_SUCCESS; + + // Is it an encrypted MPQ ? + if(strstr(szFullPath, ".MPQE") != NULL) + dwFlags |= STREAM_PROVIDER_ENCRYPTED; + if(strstr(szFullPath, ".MPQ.part") != NULL) + dwFlags |= STREAM_PROVIDER_PARTIAL; + + // Open the copied archive + pLogger->PrintProgress("Opening archive %s ...", GetShortPlainName(szFullPath)); + CopyFileName(szMpqName, szFullPath, strlen(szFullPath)); + if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) + { + // Ignore the error if it's an AVI file + if(GetLastError() == ERROR_AVI_FILE) + return ERROR_AVI_FILE; + return pLogger->PrintError("Failed to open archive %s", szFullPath); + } - CreateFullPathName(szMpqName, NULL, szPlainName); - return CreateNewArchive_FullPath(pLogger, szMpqName, dwCreateFlags, dwMaxFileCount, phMpq); + // Store the archive handle or close the archive + if(phMpq == NULL) + SFileCloseArchive(hMpq); + else + *phMpq = hMpq; + return nError; } -static int OpenExistingArchive(TLogHelper * pLogger, const char * szFileName, const char * szCopyName, HANDLE * phMpq) +static int OpenPatchArchive(TLogHelper * pLogger, HANDLE hMpq, const char * szFullPath) +{ + TCHAR szPatchName[MAX_PATH]; + int nError = ERROR_SUCCESS; + + pLogger->PrintProgress("Adding patch %s ...", GetShortPlainName(szFullPath)); + CopyFileName(szPatchName, szFullPath, strlen(szFullPath)); + if(!SFileOpenPatchArchive(hMpq, szPatchName, NULL, 0)) + nError = pLogger->PrintError("Failed to add patch %s ...", szFullPath); + + return nError; +} + +static int OpenExistingArchiveWithCopy(TLogHelper * pLogger, const char * szFileName, const char * szCopyName, HANDLE * phMpq) { - TCHAR szMpqName[MAX_PATH]; - HANDLE hMpq = NULL; DWORD dwFlags = 0; + char szFullPath[MAX_PATH]; int nError = ERROR_SUCCESS; // We expect MPQ directory to be already prepared by InitializeMpqDirectory @@ -1091,7 +1104,7 @@ static int OpenExistingArchive(TLogHelper * pLogger, const char * szFileName, co // If both names entered, create a copy if(szFileName != NULL && szCopyName != NULL) { - nError = CreateMpqCopy(pLogger, szFileName, szCopyName, szMpqName); + nError = CreateMpqCopy(pLogger, szFileName, szCopyName, szFullPath); if(nError != ERROR_SUCCESS) return nError; } @@ -1099,60 +1112,42 @@ static int OpenExistingArchive(TLogHelper * pLogger, const char * szFileName, co // If only source name entered, open it for read-only access else if(szFileName != NULL && szCopyName == NULL) { - CreateFullPathName(szMpqName, szMpqSubDir, szFileName); + CreateFullPathName(szFullPath, szMpqSubDir, szFileName); dwFlags |= MPQ_OPEN_READ_ONLY; } // If only target name entered, open it directly else if(szFileName == NULL && szCopyName != NULL) { - CreateFullPathName(szMpqName, NULL, szCopyName); + CreateFullPathName(szFullPath, NULL, szCopyName); } - // Is it an encrypted MPQ ? - if(_tcsstr(szMpqName, _T(".MPQE")) != NULL) - dwFlags |= MPQ_OPEN_ENCRYPTED; - - // Open the copied archive - pLogger->PrintProgress("Opening archive %s ...", (szCopyName != NULL) ? szCopyName : szFileName); - if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) - return pLogger->PrintError(_T("Failed to open archive %s"), szMpqName); - - // Store the archive handle or close the archive - if(phMpq == NULL) - SFileCloseArchive(hMpq); - else - *phMpq = hMpq; - return nError; + // Open the archive + return OpenExistingArchive(pLogger, szFullPath, dwFlags, phMpq); } static int OpenPatchedArchive(TLogHelper * pLogger, HANDLE * phMpq, const char * PatchList[]) { - TCHAR szMpqName[MAX_PATH]; HANDLE hMpq = NULL; + char szFullPath[MAX_PATH]; int nError = ERROR_SUCCESS; // The first file is expected to be valid assert(PatchList[0] != NULL); // Open the primary MPQ - CreateFullPathName(szMpqName, szMpqSubDir, PatchList[0]); - pLogger->PrintProgress("Opening base MPQ %s ...", PatchList[0]); - if(!SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq)) - nError = pLogger->PrintError(_T("Failed to open the archive %s"), szMpqName); + CreateFullPathName(szFullPath, szMpqSubDir, PatchList[0]); + nError = OpenExistingArchive(pLogger, szFullPath, MPQ_OPEN_READ_ONLY, &hMpq); // Add all patches if(nError == ERROR_SUCCESS) { for(size_t i = 1; PatchList[i] != NULL; i++) { - CreateFullPathName(szMpqName, szMpqPatchDir, PatchList[i]); - pLogger->PrintProgress("Adding patch %s ...", PatchList[i]); - if(!SFileOpenPatchArchive(hMpq, szMpqName, NULL, 0)) - { - nError = pLogger->PrintError(_T("Failed to add patch %s ..."), szMpqName); + CreateFullPathName(szFullPath, szMpqPatchDir, PatchList[i]); + nError = OpenPatchArchive(pLogger, hMpq, szFullPath); + if(nError != ERROR_SUCCESS) break; - } } } @@ -1208,15 +1203,16 @@ static int AddLocalFileToMpq( TLogHelper * pLogger, HANDLE hMpq, const char * szArchivedName, - const TCHAR * szFileName, + const char * szLocalFileName, DWORD dwFlags = 0, DWORD dwCompression = 0, bool bMustSucceed = false) { + TCHAR szFileName[MAX_PATH]; DWORD dwVerifyResult; // Notify the user - pLogger->PrintProgress("Adding file %s (%u of %u)...", szArchivedName, pLogger->UserCount, pLogger->UserTotal); + pLogger->PrintProgress("Adding file %s (%u of %u)...", GetShortPlainName(szLocalFileName), pLogger->UserCount, pLogger->UserTotal); pLogger->UserString = szArchivedName; // Get the default flags @@ -1229,6 +1225,7 @@ static int AddLocalFileToMpq( SFileSetAddFileCallback(hMpq, AddFileCallback, pLogger); // Add the file to the MPQ + CopyFileName(szFileName, szLocalFileName, strlen(szLocalFileName)); if(!SFileAddFileEx(hMpq, szFileName, szArchivedName, dwFlags, dwCompression, MPQ_COMPRESSION_NEXT_SAME)) { if(bMustSucceed) @@ -1346,7 +1343,7 @@ static int TestVerifyFileChecksum(const char * szFullPath) TLogHelper Logger("VerifyFileHash", szShortPlainName); // Open the file to be verified - pStream = FileStream_OpenFile(szFullPath, STREAM_FLAG_READ_ONLY); + pStream = OpenLocalFile(szFullPath, STREAM_FLAG_READ_ONLY); if(pStream != NULL) { // Notify the user @@ -1453,15 +1450,15 @@ static int TestPartFileRead(const char * szPlainName) ULONGLONG ByteOffset; ULONGLONG FileSize = 0; TFileStream * pStream; - TCHAR szFileName[MAX_PATH]; + char szFileName[MAX_PATH]; BYTE Buffer[0x100]; int nError = ERROR_SUCCESS; // Open the partial file CreateFullPathName(szFileName, szMpqSubDir, szPlainName); - pStream = FileStream_OpenFile(szFileName, STREAM_PROVIDER_PARTIAL | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY); + pStream = OpenLocalFile(szFileName, STREAM_PROVIDER_PARTIAL | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY); if(pStream == NULL) - nError = Logger.PrintError(_T("Failed to open %s"), szFileName); + nError = Logger.PrintError("Failed to open %s", szFileName); // Get the size of the stream if(nError == ERROR_SUCCESS) @@ -1510,7 +1507,7 @@ static int TestOpenFile_OpenById(const char * szPlainName) int nError; // Copy the archive so we won't fuck up the original one - nError = OpenExistingArchive(&Logger, szPlainName, NULL, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq); // Now try to open a file without knowing the file name if(nError == ERROR_SUCCESS) @@ -1554,11 +1551,16 @@ static int TestOpenArchive(const char * szPlainName, const char * szListFile = N TFileData * pFileData; HANDLE hMpq; DWORD dwFileCount = 0; + DWORD dwTestFlags; char szListFileBuff[MAX_PATH]; + bool bIsPartialMpq = false; int nError; + // If the file is a partial MPQ, don;t load all files + bIsPartialMpq = (strstr(szPlainName, ".MPQ.part") != NULL); + // Copy the archive so we won't fuck up the original one - nError = OpenExistingArchive(&Logger, szPlainName, NULL, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq); if(nError == ERROR_SUCCESS) { // If the listfile was given, add it to the MPQ @@ -1588,7 +1590,8 @@ static int TestOpenArchive(const char * szPlainName, const char * szListFile = N } // Search the archive and load every file - nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES, &dwFileCount); + dwTestFlags = bIsPartialMpq ? 0 : TEST_FLAG_LOAD_FILES; + nError = SearchArchive(&Logger, hMpq, dwTestFlags, &dwFileCount); SFileCloseArchive(hMpq); } @@ -1628,24 +1631,18 @@ static int TestOpenArchive_ReadOnly(const char * szPlainName, bool bReadOnly) const char * szCopyName; TLogHelper Logger("ReadOnlyTest", szPlainName); HANDLE hMpq; - TCHAR szMpqName[MAX_PATH]; - DWORD dwFlags = 0; + char szFullPathName[MAX_PATH]; + DWORD dwFlags = bReadOnly ? MPQ_OPEN_READ_ONLY : 0;; bool bMustSucceed; int nError; // Copy the fiel so we wont screw up something szCopyName = bReadOnly ? "StormLibTest_ReadOnly.mpq" : "StormLibTest_ReadWrite.mpq"; - nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szMpqName); + nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szFullPathName); // Now open the archive for read-only access if(nError == ERROR_SUCCESS) - { - Logger.PrintProgress("Opening archive %s ...", szCopyName); - - dwFlags = bReadOnly ? MPQ_OPEN_READ_ONLY : 0; - if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) - nError = Logger.PrintError("Failed to open the archive %s", szCopyName); - } + nError = OpenExistingArchive(&Logger, szFullPathName, dwFlags, &hMpq); // Now try to add a file. This must fail if the MPQ is read only if(nError == ERROR_SUCCESS) @@ -1692,8 +1689,8 @@ static int TestOpenArchive_GetFileInfo(const char * szPlainName1, const char * s int nError4; // Copy the archive so we won't fuck up the original one - nError1 = OpenExistingArchive(&Logger, szPlainName1, NULL, &hMpq1); - nError4 = OpenExistingArchive(&Logger, szPlainName4, NULL, &hMpq4); + nError1 = OpenExistingArchiveWithCopy(&Logger, szPlainName1, NULL, &hMpq1); + nError4 = OpenExistingArchiveWithCopy(&Logger, szPlainName4, NULL, &hMpq4); if(nError1 == ERROR_SUCCESS && nError4 == ERROR_SUCCESS) { // Invalid handle - expected (false, ERROR_INVALID_HANDLE) @@ -1766,7 +1763,7 @@ static int TestOpenArchive_VerifySignature(const char * szPlainName, const char int nError = ERROR_SUCCESS; // We need original name for the signature check - nError = OpenExistingArchive(&Logger, szPlainName, szOriginalName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szOriginalName, &hMpq); if(nError == ERROR_SUCCESS) { // Query the signature types @@ -1803,20 +1800,21 @@ static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char HANDLE hMpq; DWORD dwFileCount1 = 0; DWORD dwFileCount2 = 0; - TCHAR szMpqName[MAX_PATH]; BYTE FileHash1[MD5_DIGEST_SIZE]; BYTE FileHash2[MD5_DIGEST_SIZE]; + char szFullPath[MAX_PATH]; int nError; // Create copy of the archive, with interleaving some user data - nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szMpqName, 0x400, 0x531); + nError = CreateMpqCopy(&Logger, szPlainName, szCopyName, szFullPath, 0x400, 0x531); // Open the archive and load some files if(nError == ERROR_SUCCESS) { - Logger.PrintProgress("Opening archive %s ...", szCopyName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - return Logger.PrintError(_T("Failed to open archive %s"), szMpqName); + // Open the archive + nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq); + if(nError != ERROR_SUCCESS) + return nError; // Verify presence of (listfile) and (attributes) CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); @@ -1831,14 +1829,14 @@ static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char if(nError == ERROR_SUCCESS) { // Open the archive again - Logger.PrintProgress("Reopening archive %s ...", szCopyName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - return Logger.PrintError(_T("Failed to open archive %s"), szMpqName); + nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq); + if(nError != ERROR_SUCCESS) + return nError; // Compact the archive - Logger.PrintProgress("Compacting archive %s ...", szMpqName); + Logger.PrintProgress("Compacting archive %s ...", GetShortPlainName(szFullPath)); if(!SFileSetCompactCallback(hMpq, CompactCallback, &Logger)) - nError = Logger.PrintError(_T("Failed to compact archive %s"), szMpqName); + nError = Logger.PrintError("Failed to compact archive %s", szFullPath); SFileCompactArchive(hMpq, NULL, false); SFileCloseArchive(hMpq); @@ -1847,9 +1845,10 @@ static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char // Open the archive and load some files if(nError == ERROR_SUCCESS) { - Logger.PrintProgress("Reopening archive %s ...", szCopyName); - if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) - return Logger.PrintError(_T("Failed to open archive %s"), szMpqName); + // Open the archive + nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq); + if(nError != ERROR_SUCCESS) + return nError; // Verify presence of (listfile) and (attributes) CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); @@ -1873,6 +1872,35 @@ static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char return nError; } + +// Searches a direcroty +static int TestOpenEachArchive_EachFile(const char * szFullPath) +{ + HANDLE hMpq = NULL; + DWORD dwFileCount = 0; + int nError = ERROR_SUCCESS; + + // Check if it's a MPQ file type + if(IsMpqExtension(szFullPath)) + { + TLogHelper Logger("OpenEachMpqTest", GetShortPlainName(szFullPath)); + + // Open the MPQ name + nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq); + if(nError == ERROR_AVI_FILE) + return ERROR_SUCCESS; + + // Search the archive and load every file + if(nError == ERROR_SUCCESS) + { + nError = SearchArchive(&Logger, hMpq, 0, &dwFileCount); + SFileCloseArchive(hMpq); + } + } + + return nError; +} + // Adding a file to MPQ that had no (listfile) and no (attributes). // We expect that neither of these will be present after the archive is closed static int TestAddFile_ListFileTest(const char * szSourceMpq, bool bShouldHaveListFile, bool bShouldHaveAttributes) @@ -1887,7 +1915,7 @@ static int TestAddFile_ListFileTest(const char * szSourceMpq, bool bShouldHaveLi int nError = ERROR_SUCCESS; // Copy the archive so we won't fuck up the original one - nError = OpenExistingArchive(&Logger, szSourceMpq, szBackupMpq, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, szSourceMpq, szBackupMpq, &hMpq); // Add a file if(nError == ERROR_SUCCESS) @@ -1899,7 +1927,7 @@ static int TestAddFile_ListFileTest(const char * szSourceMpq, bool bShouldHaveLi // Now reopen the archive if(nError == ERROR_SUCCESS) - nError = OpenExistingArchive(&Logger, NULL, szBackupMpq, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szBackupMpq, &hMpq); // Now the file has been written and the MPQ has been saved. // We Reopen the MPQ and check if there is no (listfile) nor (attributes). @@ -1962,7 +1990,7 @@ static int TestCreateArchive_EmptyMpq(const char * szPlainName, DWORD dwCreateFl // Reopen the empty MPQ if(nError == ERROR_SUCCESS) { - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); if(nError == ERROR_SUCCESS) { SFileGetFileInfo(hMpq, SFileMpqNumberOfFiles, &dwFileCount, sizeof(dwFileCount), NULL); @@ -2018,7 +2046,7 @@ static int TestCreateArchive_FillArchive(const char * szPlainName) // Reopen the archive again if(nError == ERROR_SUCCESS) - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); // The archive should still be full if(nError == ERROR_SUCCESS) @@ -2060,7 +2088,7 @@ static int TestCreateArchive_FillArchive(const char * szPlainName) // Reopen the archive for the third time to verify that both internal files are there if(nError == ERROR_SUCCESS) { - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); if(nError == ERROR_SUCCESS) { CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true); @@ -2099,7 +2127,7 @@ static int TestCreateArchive_IncMaxFileCount(const char * szPlainName) for(DWORD i = 0; i < 10; i++) { // Open the archive again - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); if(nError != ERROR_SUCCESS) break; @@ -2134,27 +2162,27 @@ static int TestCreateArchive_UnicodeNames() TLogHelper Logger("MpqUnicodeName"); int nError = ERROR_SUCCESS; - nError = CreateNewArchive_AddPrefix(&Logger, szUnicodeName1, MPQ_CREATE_ARCHIVE_V1, 15, NULL); + nError = CreateNewArchiveU(&Logger, szUnicodeName1, MPQ_CREATE_ARCHIVE_V1, 15); if(nError != ERROR_SUCCESS) return nError; - nError = CreateNewArchive_AddPrefix(&Logger, szUnicodeName2, MPQ_CREATE_ARCHIVE_V2, 58, NULL); + nError = CreateNewArchiveU(&Logger, szUnicodeName2, MPQ_CREATE_ARCHIVE_V2, 58); if(nError != ERROR_SUCCESS) return nError; - nError = CreateNewArchive_AddPrefix(&Logger, szUnicodeName3, MPQ_CREATE_ARCHIVE_V3, 15874, NULL); + nError = CreateNewArchiveU(&Logger, szUnicodeName3, MPQ_CREATE_ARCHIVE_V3, 15874); if(nError != ERROR_SUCCESS) return nError; - nError = CreateNewArchive_AddPrefix(&Logger, szUnicodeName4, MPQ_CREATE_ARCHIVE_V4, 87541, NULL); + nError = CreateNewArchiveU(&Logger, szUnicodeName4, MPQ_CREATE_ARCHIVE_V4, 87541); if(nError != ERROR_SUCCESS) return nError; - nError = CreateNewArchive_AddPrefix(&Logger, szUnicodeName5, MPQ_CREATE_ARCHIVE_V3, 87541, NULL); + nError = CreateNewArchiveU(&Logger, szUnicodeName5, MPQ_CREATE_ARCHIVE_V3, 87541); if(nError != ERROR_SUCCESS) return nError; - nError = CreateNewArchive_AddPrefix(&Logger, szUnicodeName5, MPQ_CREATE_ARCHIVE_V2, 87541, NULL); + nError = CreateNewArchiveU(&Logger, szUnicodeName5, MPQ_CREATE_ARCHIVE_V2, 87541); if(nError != ERROR_SUCCESS) return nError; @@ -2165,9 +2193,9 @@ static int TestCreateArchive_FileFlagTest(const char * szPlainName) { TLogHelper Logger("FileFlagTest", szPlainName); HANDLE hMpq = NULL; // Handle of created archive - TCHAR szFileName1[MAX_PATH]; - TCHAR szFileName2[MAX_PATH]; - TCHAR szMpqName[MAX_PATH]; + char szFileName1[MAX_PATH]; + char szFileName2[MAX_PATH]; + char szFullPath[MAX_PATH]; const char * szMiddleFile = "FileTest_10.exe"; LCID LocaleIDs[] = {0x000, 0x405, 0x406, 0x407, 0xFFFF}; char szArchivedName[MAX_PATH]; @@ -2181,11 +2209,11 @@ static int TestCreateArchive_FileFlagTest(const char * szPlainName) CreateFullPathName(szFileName2, szMpqSubDir, "AddFile.bin"); // Create an empty file that will serve as holder for the MPQ - nError = CreateEmptyFile(&Logger, szPlainName, 0x100000, szMpqName); + nError = CreateEmptyFile(&Logger, szPlainName, 0x100000, szFullPath); // Create new MPQ archive over that file if(nError == ERROR_SUCCESS) - nError = CreateNewArchive_FullPath(&Logger, szMpqName, MPQ_CREATE_ARCHIVE_V1, 17, &hMpq); + nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V1, 17, &hMpq); // Add the same file multiple times if(nError == ERROR_SUCCESS) @@ -2314,7 +2342,7 @@ static int TestCreateArchive_FileFlagTest(const char * szPlainName) hMpq = NULL; // Try to reopen the archive - nError = OpenExistingArchive(&Logger, NULL, szPlainName, NULL); + nError = OpenExistingArchive(&Logger, szFullPath, 0, NULL); return nError; } @@ -2322,8 +2350,7 @@ static int TestCreateArchive_CompressionsTest(const char * szPlainName) { TLogHelper Logger("CompressionsTest", szPlainName); HANDLE hMpq = NULL; // Handle of created archive - TCHAR szFileName[MAX_PATH]; // Source file to be added - TCHAR szMpqName[MAX_PATH]; + char szFileName[MAX_PATH]; // Source file to be added char szArchivedName[MAX_PATH]; DWORD dwCmprCount = sizeof(Compressions) / sizeof(DWORD); DWORD dwAddedFiles = 0; @@ -2333,10 +2360,9 @@ static int TestCreateArchive_CompressionsTest(const char * szPlainName) // Create paths for local file to be added CreateFullPathName(szFileName, szMpqSubDir, "AddFile.wav"); - CreateFullPathName(szMpqName, NULL, szPlainName); // Create new archive - nError = CreateNewArchive_FullPath(&Logger, szMpqName, MPQ_CREATE_ARCHIVE_V4, 0x40, &hMpq); + nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4, 0x40, &hMpq); // Add the same file multiple times if(nError == ERROR_SUCCESS) @@ -2359,7 +2385,7 @@ static int TestCreateArchive_CompressionsTest(const char * szPlainName) // Reopen the archive extract each WAVE file and try to play it if(nError == ERROR_SUCCESS) { - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); if(nError == ERROR_SUCCESS) { SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_PLAY_WAVES, &dwFoundFiles, NULL); @@ -2427,7 +2453,7 @@ static int TestCreateArchive_ListFilePos(const char * szPlainName) // Reopen the archive to catch any asserts if(nError == ERROR_SUCCESS) - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); // Check that (listfile) is at the end if(nError == ERROR_SUCCESS) @@ -2473,7 +2499,7 @@ static int TestCreateArchive_BigArchive(const char * szPlainName) const char * szFileMask = "AddedFile_%02u.txt"; TLogHelper Logger("BigMpqTest"); HANDLE hMpq = NULL; // Handle of created archive - TCHAR szFileName[MAX_PATH]; + char szLocalFileName[MAX_PATH]; char szArchivedName[MAX_PATH]; DWORD dwMaxFileCount = 0x20; DWORD dwAddedCount = 0; @@ -2485,13 +2511,13 @@ static int TestCreateArchive_BigArchive(const char * szPlainName) if(nError == ERROR_SUCCESS) { // Now add few really big files - CreateFullPathName(szFileName, szMpqSubDir, "MPQ_1997_v1_Diablo1_DIABDAT.MPQ"); + CreateFullPathName(szLocalFileName, szMpqSubDir, "MPQ_1997_v1_Diablo1_DIABDAT.MPQ"); Logger.UserTotal = (dwMaxFileCount / 2); for(i = 0; i < dwMaxFileCount / 2; i++) { sprintf(szArchivedName, szFileMask, i + 1); - nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName, 0, 0, true); + nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szLocalFileName, 0, 0, true); if(nError != ERROR_SUCCESS) break; @@ -2507,7 +2533,7 @@ static int TestCreateArchive_BigArchive(const char * szPlainName) // Reopen the archive to catch any asserts if(nError == ERROR_SUCCESS) - nError = OpenExistingArchive(&Logger, NULL, szPlainName, &hMpq); + nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq); // Check that (listfile) is at the end if(nError == ERROR_SUCCESS) @@ -2521,21 +2547,27 @@ static int TestCreateArchive_BigArchive(const char * szPlainName) return nError; } -static int TestForEachArchive(ARCHIVE_TEST pfnTest, char * szSearchMask, char * szPlainName) +static int TestForEachArchive(ARCHIVE_TEST pfnTest, TCHAR * szSearchMask, TCHAR * szPlainName) { - char * szPathBuff = NULL; + TCHAR szPathBuffT[MAX_PATH]; + char szPathBuffA[MAX_PATH]; + char szFullPath[MAX_PATH]; int nError = ERROR_SUCCESS; - // If the name was not entered, use new one + // If the name was not entered, construct new one if(szSearchMask == NULL) { - szPathBuff = STORM_ALLOC(char, MAX_PATH); - if(szPathBuff != NULL) - { - CreateFullPathName(szPathBuff, szMpqSubDir, "*"); - szSearchMask = szPathBuff; - szPlainName = strrchr(szSearchMask, '*'); - } + CreateFullPathName(szPathBuffA, szMpqSubDir, "*"); + CopyFileName(szPathBuffT, szPathBuffA, strlen(szPathBuffA)); + szSearchMask = szPathBuffT; + } + + // Get the position of the plain name + if(szPlainName == NULL) + { + szPlainName = _tcsrchr(szSearchMask, _T('*')); + if(szPlainName == NULL) + return ERROR_SUCCESS; } // At this point, both pointers must be valid @@ -2545,31 +2577,36 @@ static int TestForEachArchive(ARCHIVE_TEST pfnTest, char * szSearchMask, char * if(szSearchMask != NULL && szPlainName != NULL) { #ifdef PLATFORM_WINDOWS - WIN32_FIND_DATAA wf; + WIN32_FIND_DATA wf; HANDLE hFind; // Initiate search. Use ANSI function only - hFind = FindFirstFileA(szSearchMask, &wf); + hFind = FindFirstFile(szSearchMask, &wf); if(hFind != INVALID_HANDLE_VALUE) { // Skip the first entry, since it's always "." or ".." - while(FindNextFileA(hFind, &wf) && nError == ERROR_SUCCESS) + while(FindNextFile(hFind, &wf) && nError == ERROR_SUCCESS) { // Found a directory? if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if(wf.cFileName[0] != '.') { - sprintf(szPlainName, "%s\\*", wf.cFileName); - nError = TestForEachArchive(pfnTest, szSearchMask, strrchr(szSearchMask, '*')); + _stprintf(szPlainName, _T("%s\\*"), wf.cFileName); + nError = TestForEachArchive(pfnTest, szSearchMask, NULL); } } else { if(pfnTest != NULL) { - strcpy(szPlainName, wf.cFileName); - nError = pfnTest(szSearchMask); + // Create the full path as TCHAR + _tcscpy(szPlainName, wf.cFileName); + CopyFileName(szFullPath, szSearchMask, _tcslen(szSearchMask)); + + // Check for UNICODE names + if(IsUnicodeNameConvertableToAnsi(szSearchMask, szFullPath)) + nError = pfnTest(szFullPath); } } } @@ -2580,12 +2617,22 @@ static int TestForEachArchive(ARCHIVE_TEST pfnTest, char * szSearchMask, char * } // Free the path buffer, if any - if(szPathBuff != NULL) - STORM_FREE(szPathBuff); - szPathBuff = NULL; return nError; } +static int TestOpenArchive_EachArchive() +{ + TCHAR szSearchMaskT[MAX_PATH]; + char szSearchMaskA[MAX_PATH]; + + // Create the TCHAR name of search mask + CreateFullPathName(szSearchMaskA, NULL, "*"); + CopyFileName(szSearchMaskT, szSearchMaskA, strlen(szSearchMaskA)); + + // Invoke the searching function + return TestForEachArchive(TestOpenEachArchive_EachFile, szSearchMaskT, NULL); +} + //----------------------------------------------------------------------------- // Main @@ -2600,7 +2647,7 @@ int main(int argc, char * argv[]) // Initialize storage and mix the random number generator printf("==== Test Suite for StormLib version %s ====\n", STORMLIB_VERSION_STRING); nError = InitializeMpqDirectory(argv, argc); -/* + // Search all testing archives and verify their SHA1 hash if(nError == ERROR_SUCCESS) nError = TestForEachArchive(TestVerifyFileChecksum, NULL, NULL); @@ -2669,6 +2716,10 @@ int main(int argc, char * argv[]) if(nError == ERROR_SUCCESS) nError = TestOpenArchive("MPx_2013_v1_WarOfTheImmortals.sqp", "ListFile_WarOfTheImmortals.txt"); + // Open a partial MPQ with compressed hash table + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive("MPQ_2010_v2_HashTableCompressed.MPQ.part"); + // Open a patched archive if(nError == ERROR_SUCCESS) nError = TestOpenArchive_Patched(PatchList_WoW_OldWorld13286, "OldWorld\\World\\Model.blob", 2); @@ -2713,6 +2764,10 @@ int main(int argc, char * argv[]) if(nError == ERROR_SUCCESS) nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_expansion1.MPQ", "StormLibTest_CraftedMpq3_v4.mpq"); + // Open every MPQ that we have in the storage + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_EachArchive(); + // Test modifying file with no (listfile) and no (attributes) if(nError == ERROR_SUCCESS) nError = TestAddFile_ListFileTest("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false, false); @@ -2752,7 +2807,7 @@ int main(int argc, char * argv[]) // Check if the listfile is always created at the end of the file table in the archive if(nError == ERROR_SUCCESS) nError = TestCreateArchive_ListFilePos("StormLibTest_ListFilePos.mpq"); -*/ + // Open a MPQ (add custom user data to it) if(nError == ERROR_SUCCESS) nError = TestCreateArchive_BigArchive("StormLibTest_BigArchive_v4.mpq"); |