aboutsummaryrefslogtreecommitdiff
path: root/src/SFilePatchArchives.cpp
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2014-12-05 10:59:41 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2014-12-05 10:59:41 +0100
commit48b5cb606e5f1b407069a1babfb17ccbcb630e55 (patch)
tree574e9bc1213a99a1a2cd0015755d83dc2190bd4b /src/SFilePatchArchives.cpp
parent3e594190e4a89252755907a313914a96b6abb1c8 (diff)
+ Optimized patching process
+ Fixed bug on opening Hearthstone patch files
Diffstat (limited to 'src/SFilePatchArchives.cpp')
-rw-r--r--src/SFilePatchArchives.cpp341
1 files changed, 190 insertions, 151 deletions
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp
index 99dec1e..21446d8 100644
--- a/src/SFilePatchArchives.cpp
+++ b/src/SFilePatchArchives.cpp
@@ -28,6 +28,28 @@ typedef struct _BLIZZARD_BSDIFF40_FILE
} BLIZZARD_BSDIFF40_FILE, *PBLIZZARD_BSDIFF40_FILE;
//-----------------------------------------------------------------------------
+// Local variables
+
+static const char * LanguageList[] =
+{
+ "deDE",
+ "enCN",
+ "enGB",
+ "enTW",
+ "enUS",
+ "esES",
+ "esMX",
+ "frFR",
+ "koKR",
+ "ptBR",
+ "ptPT",
+ "ruRU",
+ "zhCN",
+ "zhTW",
+ NULL
+};
+
+//-----------------------------------------------------------------------------
// Local functions
static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE pbCompressed, DWORD cbCompressed)
@@ -321,7 +343,7 @@ static int ApplyFilePatch(
hfFrom = hfBase;
if(!memcmp(hfPrev->FileDataMD5, pPatchHeader->md5_before_patch, MD5_DIGEST_SIZE))
hfFrom = hfPrev;
- if(hfFrom == hf)
+ if(hfFrom == NULL)
return ERROR_FILE_CORRUPT;
// Allocate the buffer for patched file content
@@ -376,23 +398,36 @@ static void FreePatchData(TMPQFile * hf)
//-----------------------------------------------------------------------------
// Local functions (patch prefix matching)
-static bool LoadPatchHeader(TMPQArchive * ha, const char * szFileName, TPatchHeader * pPatchHeader)
+static TFileEntry * FindMd5ListFile(TMPQArchive * ha)
{
- HANDLE hFile = NULL;
- DWORD dwTransferred = 0;
+ TFileEntry * pFileEntry = ha->pFileTable + ha->dwFileTableSize;
+ char * szLstName;
+ size_t nTryCount = 0;
+ size_t nLength;
- // Load the patch header
- if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
+ // Check every file entry for "*-md5.lst".
+ // Go backwards, as the entry is usually at the end of the file table
+ while(pFileEntry > ha->pFileTable && nTryCount < 10)
{
- SFileReadFile(hFile, pPatchHeader, sizeof(TPatchHeader), &dwTransferred, NULL);
- SFileCloseFile(hFile);
- }
+ // The file name must be valid
+ if(pFileEntry->szFileName != NULL)
+ {
+ // Get the name and length
+ szLstName = pFileEntry->szFileName;
+ nLength = strlen(szLstName);
- // Convert the patch header to the proper endian
- BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6);
+ // Check for the tail name
+ if(!_stricmp(szLstName + nLength - 8, "-md5.lst"))
+ return pFileEntry;
+ }
+
+ // Move back
+ pFileEntry--;
+ nTryCount++;
+ }
- // Check the patch header
- return (dwTransferred == sizeof(TPatchHeader) && pPatchHeader->dwSignature == PATCH_SIGNATURE_HEADER);
+ // Not found, sorry
+ return NULL;
}
static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, const char * szPrefixEnd)
@@ -400,6 +435,10 @@ static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, const c
TMPQNamePrefix * pNewPrefix;
size_t nLength;
+ // If the end of the patch prefix was not entered, find it
+ if(szFileName != NULL && szPrefixEnd == NULL)
+ szPrefixEnd = szFileName + strlen(szFileName);
+
// Create the patch prefix
nLength = (szPrefixEnd - szFileName);
pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength);
@@ -421,155 +460,158 @@ static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, const c
return (pNewPrefix != NULL);
}
-static bool FindPatchPrefix_OldWorld(
- TMPQArchive * haBase,
- TMPQArchive * haPatch)
+static bool IsMatchingPatchFile(
+ TMPQArchive * ha,
+ const char * szFileName,
+ LPBYTE pbFileMd5)
{
- TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
- TFileEntry * pPatchEntry;
- TFileEntry * pBaseEntry;
- TPatchHeader PatchHeader;
- char * szPatchName;
- char szBaseName[MAX_PATH + 0x20];
- size_t nBaseLength = 9;
-
- // Prepare the base prefix
- memcpy(szBaseName, "OldWorld\\", nBaseLength);
+ TPatchHeader PatchHeader = {0};
+ HANDLE hFile = NULL;
+ DWORD dwTransferred = 0;
+ bool bResult = false;
- // Check every file entry in the patch archive
- for(pPatchEntry = haPatch->pFileTable; pPatchEntry < pFileTableEnd; pPatchEntry++)
+ // Open the file and load the patch header
+ if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
{
- // If the file is a patch file, there must be a base file in the base MPQ
- if((pPatchEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (pPatchEntry->szFileName != NULL))
- {
- // Cut one subdirectory from the patch name
- szPatchName = strchr(pPatchEntry->szFileName, '\\');
- if(szPatchName != NULL)
- {
- // Construct the base name
- strcpy(szBaseName + nBaseLength, szPatchName + 1);
+ // Load the patch header
+ SFileReadFile(hFile, &PatchHeader, sizeof(TPatchHeader), &dwTransferred, NULL);
+ BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6);
- // Check if the name is in the base archive as-is.
- // Do not use locale search, patched archives no longer use locale ID.
- pBaseEntry = GetFileEntryAny(haBase, szBaseName);
- if(pBaseEntry != NULL && IsValidMD5(pBaseEntry->md5))
- {
- // Read the patch header from the file
- if(LoadPatchHeader(haPatch, pPatchEntry->szFileName, &PatchHeader))
- {
- // Compare the file MD5's. If they match,
- // it means that we have found the proper prefix
- if(!memcmp(pBaseEntry->md5, PatchHeader.md5_before_patch, MD5_DIGEST_SIZE))
- {
- return CreatePatchPrefix(haPatch, pPatchEntry->szFileName, szPatchName + 1);
- }
- }
- }
- }
- }
+ // If the file contains an incremental patch,
+ // compare the "MD5 before patching" with the base file MD5
+ if(dwTransferred == sizeof(TPatchHeader) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER)
+ bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE));
+
+ // Close the file
+ SFileCloseFile(hFile);
}
- return false;
+ return bResult;
}
-static bool FindPatchPrefix_Normal(
- TMPQArchive * haBase,
- TMPQArchive * haPatch)
+static const char * GetLstFileLanguage(const char * szFileName)
{
- TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
- TFileEntry * pPatchEntry;
- TFileEntry * pBaseEntry;
- TPatchHeader PatchHeader;
- char * szPatchName;
- char * szNextName;
+ char szLstSuffix[0x80];
+ size_t nLength;
+ size_t nSuffixLength;
- // Check every file entry in the patch archive
- for(pPatchEntry = haPatch->pFileTable; pPatchEntry < pFileTableEnd; pPatchEntry++)
- {
- // If the file is a patch file, there must be a base file in the base MPQ
- if((pPatchEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (pPatchEntry->szFileName != NULL))
- {
- // Set the start of the patch name
- szPatchName = pPatchEntry->szFileName;
+ // Each language-dependent file ends with "xxXX-md5.lst"
+ nLength = strlen(szFileName);
+ if(nLength < 12)
+ return NULL;
- // Only verify names that have at least one subdirectory
- while((szNextName = strchr(szPatchName, '\\')) != NULL)
- {
- // Check if the name is in the base archive as-is.
- // Do not use locale search, patched archives no longer use locale ID.
- pBaseEntry = GetFileEntryAny(haBase, szPatchName);
- if(pBaseEntry != NULL && IsValidMD5(pBaseEntry->md5))
- {
- // Read the patch header from the file
- if(LoadPatchHeader(haPatch, pPatchEntry->szFileName, &PatchHeader))
- {
- // Compare the file MD5's. If they match,
- // it means that we have found the proper prefix
- if(!memcmp(pBaseEntry->md5, PatchHeader.md5_before_patch, MD5_DIGEST_SIZE))
- {
- return CreatePatchPrefix(haPatch, pPatchEntry->szFileName, szPatchName);
- }
- }
- }
+ // Try each and every possibility
+ for(size_t i = 0; LanguageList[i] != NULL; i++)
+ {
+ nSuffixLength = sprintf(szLstSuffix, "%s-md5.lst", LanguageList[i]);
+ assert(nSuffixLength == 12);
- // Move one directory fuhrter
- szPatchName = szNextName + 1;
- }
- }
+ if(!_stricmp(szFileName + nLength - nSuffixLength, szLstSuffix))
+ return LanguageList[i];
}
- // Not found a match
- return false;
+ return NULL;
}
-static bool FindPatchPrefix_ByFileName(
- TMPQArchive * haBase,
- TMPQArchive * haPatch)
+static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch)
{
- TFileEntry * pFileTableEnd = haBase->pFileTable + haBase->dwFileTableSize;
TFileEntry * pFileEntry;
- const char * szBasePrefix;
- char * szLangName;
- char * szTailName;
- char * szLstName;
- size_t nLength;
- char szPrefix[6];
+ const char * szFilePrefix = "Base";
+ const char * szLanguage;
+ char szNamePrefix[0x10];
+ int nLength;
+
+ // Find a *-md5.lst file in the base archive
+ pFileEntry = FindMd5ListFile(haBase);
+ if(pFileEntry == NULL)
+ return false;
- // Check every file entry for "*-md5.lst".
- // Go backwards, as the entry is usually at the end of the file table
- for(pFileEntry = pFileTableEnd - 1; pFileEntry >= haBase->pFileTable; pFileEntry--)
- {
- // The file name must be valid
- if(pFileEntry->szFileName != NULL)
- {
- // Get the name and length
- szLstName = pFileEntry->szFileName;
- nLength = strlen(szLstName);
+ // Language-specific MPQs have the language identifier right before extension
+ szLanguage = GetLstFileLanguage(pFileEntry->szFileName);
+ if(szLanguage != NULL)
+ szFilePrefix = szLanguage;
- // Get the tail and lang name
- szLangName = szLstName + nLength - 13;
- szTailName = szLstName + nLength - 8;
+ // Format the name prefix
+ nLength = sprintf(szNamePrefix, "%s\\", szFilePrefix);
+ return CreatePatchPrefix(haPatch, szNamePrefix, &szNamePrefix[nLength]);
+}
- // Check for the tail name
- if(szTailName > szLstName && !_stricmp(szTailName, "-md5.lst"))
+//
+// Find match in Starcraft II patch MPQs
+// Match a LST file in the root directory if the MPQ with any of the file in subdirectories
+//
+// The problem:
+// Base: enGB-md5.lst
+// Patch: Campaigns\Liberty.SC2Campaign\enGB.SC2Assets\enGB-md5.lst
+// Campaigns\Liberty.SC2Campaign\enGB.SC2Data\enGB-md5.lst
+// Campaigns\LibertyStory.SC2Campaign\enGB.SC2Data\enGB-md5.lst
+// Campaigns\LibertyStory.SC2Campaign\enGB.SC2Data\enGB-md5.lst Mods\Core.SC2Mod\enGB.SC2Assets\enGB-md5.lst
+// Mods\Core.SC2Mod\enGB.SC2Data\enGB-md5.lst
+// Mods\Liberty.SC2Mod\enGB.SC2Assets\enGB-md5.lst
+// Mods\Liberty.SC2Mod\enGB.SC2Data\enGB-md5.lst
+// Mods\LibertyMulti.SC2Mod\enGB.SC2Data\enGB-md5.lst
+//
+// Solution:
+// We need to match the file by its MD5
+//
+
+static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch)
+{
+ TFileEntry * pFileTableEnd;
+ TFileEntry * pFileEntry;
+ TFileEntry * pBaseEntry;
+ const char * szPlainName;
+ char * szLstFileName;
+ size_t cchWorkBuffer = 0x400;
+ size_t cchBaseName;
+ size_t cchDirName;
+ bool bResult = false;
+
+ // Find a *-md5.lst file in the base archive
+ pBaseEntry = FindMd5ListFile(haBase);
+ if(pBaseEntry == NULL)
+ return false;
+ cchBaseName = strlen(pBaseEntry->szFileName) + 1;
+
+ // Allocate working buffer for merging LST file
+ szLstFileName = STORM_ALLOC(char, cchWorkBuffer);
+ if(szLstFileName != NULL)
+ {
+ // Find that file in the patch MPQ
+ pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
+ for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Find the "(patch_metadata)" file within that folder
+ // Note that the file is always relatively small and contains the patch prefix
+ // Checking for file size greatly speeds up the search process
+ if(pFileEntry->szFileName && !(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40))
{
- // Check the language name
- if(szLangName > szLstName && szLangName[0] == '-' && szLangName[5] == '-')
+ // If the plain file name matches, we need to check its MD5
+ szPlainName = GetPlainFileName(pFileEntry->szFileName);
+ cchDirName = (size_t)(szPlainName - pFileEntry->szFileName);
+
+ // The file name must not too long and must be PATCH_METADATA_NAME
+ if((cchDirName + cchBaseName) < cchWorkBuffer && _stricmp(szPlainName, PATCH_METADATA_NAME) == 0)
{
- memcpy(szPrefix, szLangName + 1, 4);
- szPrefix[4] = '\\';
- return CreatePatchPrefix(haPatch, szPrefix, szPrefix + 5);
- }
+ // Construct the name of the eventuall LST file
+ memcpy(szLstFileName, pFileEntry->szFileName, cchDirName);
+ memcpy(szLstFileName + cchDirName, pBaseEntry->szFileName, cchBaseName);
- // Stop searching
- break;
+ // If there is the "*-md5.lst" file in that directory, we check its MD5
+ if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5))
+ {
+ bResult = CreatePatchPrefix(haPatch, pFileEntry->szFileName, szPlainName);
+ break;
+ }
+ }
}
}
+
+ // Free the work buffer
+ STORM_FREE(szLstFileName);
}
- // Create the patch name with "base\\"
- szBasePrefix = "base\\";
- return CreatePatchPrefix(haPatch, szBasePrefix, szBasePrefix + 5);
+ return bResult;
}
static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const char * szPatchPathPrefix)
@@ -578,25 +620,22 @@ static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const c
if(szPatchPathPrefix != NULL)
return CreatePatchPrefix(haPatch, szPatchPathPrefix, szPatchPathPrefix + strlen(szPatchPathPrefix));
- // An old base MPQ from WoW-Cataclysm required to add "OldWorld\\"
- // as base file name prefix. Try to match
- // Match: OldWorld\Cameras\FlybyDraenei.m2 <==> base\Cameras\FlybyDraenei.m2
- if(GetFileEntryAny(haBase, "OldWorld-md5.lst") != NULL)
- return FindPatchPrefix_OldWorld(haBase, haPatch);
+ // Patches for World of Warcraft - mostly the do not use prefix.
+ // Those who do, they have the (patch_metadata) file present in the "base" subdirectory.
+ // All patches that use patch prefix have the "base\\(patch_metadata) file present
+ if(GetFileEntryAny(haPatch, "base\\" PATCH_METADATA_NAME))
+ return FindPatchPrefix_WoW_13164_13623(haBase, haPatch);
- // Find the patch so that file MD5 will match
- // Note: This must be done before checking PATCH_METADATA_NAME in the root of the archive
+ // Updates for Starcraft II
// Match: LocalizedData\GameHotkeys.txt <==> Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt
- if(FindPatchPrefix_Normal(haBase, haPatch))
- return true;
-
- // If the PATCH_METADATA_NAME is in the root, the patch prefix is empty
- // Match: Creature\Ragnaros2\Ragnaros2.M2 <==> Creature\Ragnaros2\Ragnaros2.M2
- if(GetFileEntryAny(haPatch, PATCH_METADATA_NAME) != NULL)
- return CreatePatchPrefix(haPatch, NULL, NULL);
-
- // Create the patch prefix by the base MPQ file name
- return FindPatchPrefix_ByFileName(haBase, haPatch);
+ // All Starcraft II base archives seem to have the file "StreamingBuckets.txt" present
+ if(GetFileEntryAny(haBase, "StreamingBuckets.txt"))
+ return FindPatchPrefix_SC2(haBase, haPatch);
+
+ // Diablo III patch MPQs don't use patch prefix
+ // Hearthstone MPQs don't use patch prefix
+ CreatePatchPrefix(haPatch, NULL, NULL);
+ return true;
}
//-----------------------------------------------------------------------------