From 922d3a82b897f3cbe45b45d4e072361c681a8959 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 3 Aug 2015 17:41:31 +0200 Subject: + Fixed patch archives + Empty MPQs are no longer marked as malformed --- src/SBaseFileTable.cpp | 11 ++-- src/SFilePatchArchives.cpp | 138 +++++++++++++++++++++------------------------ src/StormLib.h | 1 + test/StormTest.cpp | 38 ++++++++++--- 4 files changed, 102 insertions(+), 86 deletions(-) diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index b293574..ed0748d 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -374,10 +374,13 @@ int ConvertMpqHeaderToFormat4( // Label_ArchiveVersion1: - if(pHeader->dwHashTablePos <= pHeader->dwHeaderSize || (pHeader->dwHashTablePos & 0x80000000)) - ha->dwFlags |= MPQ_FLAG_MALFORMED; - if(pHeader->dwBlockTablePos <= pHeader->dwHeaderSize || (pHeader->dwBlockTablePos & 0x80000000)) - ha->dwFlags |= MPQ_FLAG_MALFORMED; + if(pHeader->dwBlockTableSize > 1) // Prevent empty MPQs being marked as malformed + { + if(pHeader->dwHashTablePos <= pHeader->dwHeaderSize || (pHeader->dwHashTablePos & 0x80000000)) + ha->dwFlags |= MPQ_FLAG_MALFORMED; + if(pHeader->dwBlockTablePos <= pHeader->dwHeaderSize || (pHeader->dwBlockTablePos & 0x80000000)) + ha->dwFlags |= MPQ_FLAG_MALFORMED; + } // Only low byte of sector size is really used if(pHeader->wSectorSize & 0xFF00) diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp index 7c6515e..d01aaf2 100644 --- a/src/SFilePatchArchives.cpp +++ b/src/SFilePatchArchives.cpp @@ -437,24 +437,37 @@ static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, size_t static bool IsMatchingPatchFile( TMPQArchive * ha, const char * szFileName, - LPBYTE pbFileMd5) + LPBYTE pbBaseFileMd5) { MPQ_PATCH_HEADER PatchHeader = {0}; HANDLE hFile = NULL; DWORD dwTransferred = 0; + DWORD dwFlags = 0; bool bResult = false; // Open the file and load the patch header if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) { - // Load the patch header - SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL); - BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6); - - // If the file contains an incremental patch, - // compare the "MD5 before patching" with the base file MD5 - if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) - bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE)); + // Retrieve the flags. We need to know whether the file is a patch or not + SFileGetFileInfo(hFile, SFileInfoFlags, &dwFlags, sizeof(DWORD), &dwTransferred); + if(dwFlags & MPQ_FILE_PATCH_FILE) + { + // Load the patch header + SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL); + BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6); + + // If the file contains an incremental patch, + // compare the "MD5 before patching" with the base file MD5 + if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) + bResult = (!memcmp(PatchHeader.md5_before_patch, pbBaseFileMd5, MD5_DIGEST_SIZE)); + } + else + { + // TODO: How to match it if it's not an incremental patch? + // Example: StarCraft II\Updates\enGB\s2-update-enGB-23258.MPQ: + // Mods\Core.SC2Mod\enGB.SC2Assets\StreamingBuckets.txt" + bResult = false; + } // Close the file SFileCloseFile(hFile); @@ -499,33 +512,6 @@ static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pM return NULL; } -static TFileEntry * FindBaseLstFile(TMPQArchive * ha) -{ - TFileEntry * pFileEntry; - const char * szLanguage; - char szFileName[0x40]; - - // Prepare the file name tenplate - memcpy(szFileName, "####-md5.lst", 13); - - // Try all languages - for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage++) - { - // Copy the language name - szFileName[0] = szLanguage[0]; - szFileName[1] = szLanguage[1]; - szFileName[2] = szLanguage[2]; - szFileName[3] = szLanguage[3]; - - // Check whether this file exists - pFileEntry = GetFileEntryLocale(ha, szFileName, 0); - if(pFileEntry != NULL) - return pFileEntry; - } - - return NULL; -} - static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch) { const char * szPatchPrefix; @@ -565,12 +551,11 @@ static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * // We need to match the file by its MD5 // - -static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) +// Note: pBaseEntry is the file entry of the base version of "StreamingBuckets.txt" +static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch, TFileEntry * pBaseEntry) { TMPQNamePrefix * pPatchPrefix; - TFileEntry * pBaseEntry; - char * szLstFileName; + char * szPatchFileName; char * szPlainName; size_t cchWorkBuffer = 0x400; bool bResult = false; @@ -583,17 +568,9 @@ static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) TFileEntry * pFileEntry; // Allocate working buffer for merging LST file - szLstFileName = STORM_ALLOC(char, cchWorkBuffer); - if(szLstFileName != NULL) + szPatchFileName = STORM_ALLOC(char, cchWorkBuffer); + if(szPatchFileName != NULL) { - // Find a *-md5.lst file in the base archive - pBaseEntry = FindBaseLstFile(haBase); - if(pBaseEntry == NULL) - { - STORM_FREE(szLstFileName); - return false; - } - // Parse the entire file table for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { @@ -601,21 +578,21 @@ static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) if(IsPatchMetadataFile(pFileEntry)) { // Construct the name of the MD5 file - strcpy(szLstFileName, pFileEntry->szFileName); - szPlainName = (char *)GetPlainFileName(szLstFileName); + strcpy(szPatchFileName, pFileEntry->szFileName); + szPlainName = (char *)GetPlainFileName(szPatchFileName); strcpy(szPlainName, pBaseEntry->szFileName); // Check for matching MD5 file - if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5)) + if(IsMatchingPatchFile(haPatch, szPatchFileName, pBaseEntry->md5)) { - bResult = CreatePatchPrefix(haPatch, szLstFileName, (size_t)(szPlainName - szLstFileName)); + bResult = CreatePatchPrefix(haPatch, szPatchFileName, (size_t)(szPlainName - szPatchFileName)); break; } } } // Delete the merge buffer - STORM_FREE(szLstFileName); + STORM_FREE(szPatchFileName); } } @@ -637,6 +614,8 @@ static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const char * szPatchPathPrefix) { + TFileEntry * pFileEntry; + // If the patch prefix was explicitly entered, we use that one if(szPatchPathPrefix != NULL) return CreatePatchPrefix(haPatch, szPatchPathPrefix, 0); @@ -649,8 +628,9 @@ static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const c // Updates for Starcraft II // Match: LocalizedData\GameHotkeys.txt <==> Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt // All Starcraft II base archives seem to have the file "StreamingBuckets.txt" present - if(GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0)) - return FindPatchPrefix_SC2(haBase, haPatch); + pFileEntry = GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0); + if(pFileEntry != NULL) + return FindPatchPrefix_SC2(haBase, haPatch, pFileEntry); // Diablo III patch MPQs don't use patch prefix // Hearthstone MPQs don't use patch prefix @@ -896,29 +876,37 @@ bool WINAPI SFileOpenPatchArchive( // Open the archive like it is normal archive if(nError == ERROR_SUCCESS) { - if(!SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY | MPQ_OPEN_PATCH, &hPatchMpq)) - return false; - haPatch = (TMPQArchive *)hPatchMpq; - - // We need to remember the proper patch prefix to match names of patched files - FindPatchPrefix(ha, (TMPQArchive *)hPatchMpq, szPatchPathPrefix); - - // Now add the patch archive to the list of patches to the original MPQ - while(ha != NULL) + if(SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY | MPQ_OPEN_PATCH, &hPatchMpq)) { - if(ha->haPatch == NULL) + // Cast the archive handle to structure pointer + haPatch = (TMPQArchive *)hPatchMpq; + + // We need to remember the proper patch prefix to match names of patched files + if(FindPatchPrefix(ha, (TMPQArchive *)hPatchMpq, szPatchPathPrefix)) { - haPatch->haBase = ha; - ha->haPatch = haPatch; - return true; + // Now add the patch archive to the list of patches to the original MPQ + while(ha != NULL) + { + if(ha->haPatch == NULL) + { + haPatch->haBase = ha; + ha->haPatch = haPatch; + return true; + } + + // Move to the next archive + ha = ha->haPatch; + } } - // Move to the next archive - ha = ha->haPatch; + // Close the archive + SFileCloseArchive(hPatchMpq); + nError = ERROR_CANT_FIND_PATCH_PREFIX; + } + else + { + nError = GetLastError(); } - - // Should never happen - nError = ERROR_CAN_NOT_COMPLETE; } SetLastError(nError); diff --git a/src/StormLib.h b/src/StormLib.h index b6cb0c9..fb32b8b 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -149,6 +149,7 @@ extern "C" { #define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ #define ERROR_FILE_INCOMPLETE 10006 // The required file part is missing #define ERROR_UNKNOWN_FILE_NAMES 10007 // A name of at least one file is unknown +#define ERROR_CANT_FIND_PATCH_PREFIX 10008 // StormLib was unable to find patch prefix for the patches // Values for SFileCreateArchive #define HASH_TABLE_SIZE_MIN 0x00000004 // Verified: If there is 1 file, hash table size is 4 diff --git a/test/StormTest.cpp b/test/StormTest.cpp index b67e851..685e7de 100644 --- a/test/StormTest.cpp +++ b/test/StormTest.cpp @@ -203,7 +203,7 @@ static const char * PatchList_SC2_34644_Maps[] = static const char * PatchList_SC2_32283_enGB[] = { - "MPQ_2013_v4_enGB.SC2Data", + "MPQ_2013_v4_Mods#Core.SC2Mod#enGB.SC2Assets", "s2-update-enGB-23258.MPQ", "s2-update-enGB-24540.MPQ", "s2-update-enGB-26147.MPQ", @@ -213,6 +213,18 @@ static const char * PatchList_SC2_32283_enGB[] = NULL }; +static const char * PatchList_SC2_36281_enGB[] = +{ + "MPQ_2013_v4_Mods#Liberty.SC2Mod#enGB.SC2Data", + "s2-update-enGB-23258.MPQ", + "s2-update-enGB-24540.MPQ", + "s2-update-enGB-26147.MPQ", + "s2-update-enGB-28522.MPQ", + "s2-update-enGB-30384.MPQ", + "s2-update-enGB-32281.MPQ", + NULL +}; + static const char * PatchList_HS_6898_enGB[] = { "MPQ_2014_v4_base-Win.MPQ", @@ -1924,10 +1936,15 @@ static int OpenPatchedArchive(TLogHelper * pLogger, HANDLE * phMpq, const char * } } - // Store the archive handle or close the archive - if(phMpq == NULL) + // If anything failed, close the MPQ handle + if(nError != ERROR_SUCCESS) + { SFileCloseArchive(hMpq); - else + hMpq = NULL; + } + + // Give the archive handle to the caller + if(phMpq != NULL) *phMpq = hMpq; return nError; } @@ -2494,7 +2511,7 @@ static int TestOpenArchive_Corrupt(const char * szPlainName) // Opens a patched MPQ archive -static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatchedFile, int nExpectedPatchCount) +static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatchedFile, int nExpectedPatchCount, bool bExpectedToFail = false) { TLogHelper Logger("OpenPatchedMpqTest", PatchList[0]); HANDLE hMpq; @@ -2530,6 +2547,9 @@ static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatc SFileCloseArchive(hMpq); } + // Clear the error if patch prefix was not found + if(nError == ERROR_CANT_FIND_PATCH_PREFIX && bExpectedToFail) + nError = ERROR_SUCCESS; return nError; } @@ -4283,10 +4303,14 @@ int main(int argc, char * argv[]) // Open a patched archive with new format of BSDIFF patch if(nError == ERROR_SUCCESS) nError = TestOpenArchive_Patched(PatchList_SC2_34644_Maps, "Maps\\Campaign\\THorner03.SC2Map\\BankList.xml", 3); - +*/ // Open a patched archive if(nError == ERROR_SUCCESS) - nError = TestOpenArchive_Patched(PatchList_SC2_32283_enGB, "LocalizedData\\GameHotkeys.txt", 6); + nError = TestOpenArchive_Patched(PatchList_SC2_32283_enGB, "LocalizedData\\GameHotkeys.txt", 0, true); +/* + // Open a patched archive where the "StreamingBuckets.txt" is not a patch file + if(nError == ERROR_SUCCESS) + nError = TestOpenArchive_Patched(PatchList_SC2_36281_enGB, "LocalizedData\\GameHotkeys.txt", 6); // Open a patched archive if(nError == ERROR_SUCCESS) -- cgit v1.2.3