diff options
author | unknown <E:\Ladik\Mail> | 2015-05-01 07:06:29 +0200 |
---|---|---|
committer | unknown <E:\Ladik\Mail> | 2015-05-01 07:06:29 +0200 |
commit | 46930855f500c1b494e3b16bb7a3323c07d4d5fb (patch) | |
tree | 6220cc643761137a930841d8ec828db9f3db53cf /src/SFilePatchArchives.cpp | |
parent | a205159d004871efbedd7cbfb686b8fe82bfb532 (diff) |
+ Removed back reference of FileTable -> HashTable, as it is logically incorrect
+ Optimized patching process so it consimes less memory
+ Added hash table and block table defragmenting for malformed War3 maps
Diffstat (limited to 'src/SFilePatchArchives.cpp')
-rw-r--r-- | src/SFilePatchArchives.cpp | 747 |
1 files changed, 420 insertions, 327 deletions
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp index 21446d8..f4867b0 100644 --- a/src/SFilePatchArchives.cpp +++ b/src/SFilePatchArchives.cpp @@ -19,6 +19,31 @@ #define PATCH_SIGNATURE_MD5 0x5f35444d #define PATCH_SIGNATURE_XFRM 0x4d524658 +#define SIZE_OF_XFRM_HEADER 0x0C + +// Header for incremental patch files +typedef struct _MPQ_PATCH_HEADER +{ + //-- PATCH header ----------------------------------- + DWORD dwSignature; // 'PTCH' + DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed) + DWORD dwSizeBeforePatch; // Size of the file before patch + DWORD dwSizeAfterPatch; // Size of file after patch + + //-- MD5 block -------------------------------------- + DWORD dwMD5; // 'MD5_' + DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself + BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file + BYTE md5_after_patch[0x10]; // MD5 of the patched file + + //-- XFRM block ------------------------------------- + DWORD dwXFRM; // 'XFRM' + DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data + DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY') + + // Followed by the patch data +} MPQ_PATCH_HEADER, *PMPQ_PATCH_HEADER; + typedef struct _BLIZZARD_BSDIFF40_FILE { ULONGLONG Signature; @@ -27,31 +52,61 @@ typedef struct _BLIZZARD_BSDIFF40_FILE ULONGLONG NewFileSize; } BLIZZARD_BSDIFF40_FILE, *PBLIZZARD_BSDIFF40_FILE; +typedef struct _BSDIFF_CTRL_BLOCK +{ + DWORD dwAddDataLength; + DWORD dwMovDataLength; + DWORD dwOldMoveLength; + +} BSDIFF_CTRL_BLOCK, *PBSDIFF_CTRL_BLOCK; + +typedef struct _LOCALIZED_MPQ_INFO +{ + const char * szNameTemplate; // Name template + size_t nLangOffset; // Offset of the language + size_t nLength; // Length of the name template +} LOCALIZED_MPQ_INFO, *PLOCALIZED_MPQ_INFO; + //----------------------------------------------------------------------------- // Local variables -static const char * LanguageList[] = +// 4-byte groups for all languages +static const char * LanguageList = "baseteenenUSenGBenCNenTWdeDEesESesMXfrFRitITkoKRptBRptPTruRUzhCNzhTW"; + +// List of localized MPQs for World of Warcraft +static LOCALIZED_MPQ_INFO LocaleMpqs_WoW[] = { - "deDE", - "enCN", - "enGB", - "enTW", - "enUS", - "esES", - "esMX", - "frFR", - "koKR", - "ptBR", - "ptPT", - "ruRU", - "zhCN", - "zhTW", - NULL + {"expansion1-locale-####", 18, 22}, + {"expansion1-speech-####", 18, 22}, + {"expansion2-locale-####", 18, 22}, + {"expansion2-speech-####", 18, 22}, + {"expansion3-locale-####", 18, 22}, + {"expansion3-speech-####", 18, 22}, + {"locale-####", 7, 11}, + {"speech-####", 7, 11}, + {NULL, 0, 0} }; //----------------------------------------------------------------------------- // Local functions +static inline bool IsPatchMetadataFile(TFileEntry * pFileEntry) +{ + // The file must ave a name + if(pFileEntry->szFileName != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + { + // The file must be small + if(0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40) + { + // Compare the plain name + return (_stricmp(GetPlainFileName(pFileEntry->szFileName), PATCH_METADATA_NAME) == 0); + } + } + + // Not a patch_metadata + return false; +} + static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE pbCompressed, DWORD cbCompressed) { LPBYTE pbDecompressedEnd = pbDecompressed + cbDecompressed; @@ -89,122 +144,94 @@ static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE p } } -static int LoadFilePatch_COPY(TMPQFile * hf, TPatchHeader * pPatchHeader) +static int LoadFilePatch_COPY(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch) { - int nError = ERROR_SUCCESS; - - // Allocate space for patch header and compressed data - hf->pPatchHeader = (TPatchHeader *)STORM_ALLOC(BYTE, pPatchHeader->dwSizeOfPatchData); - if(hf->pPatchHeader == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; + DWORD cbBytesToRead = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER); + DWORD cbBytesRead = 0; - // Load the patch data and decide if they are compressed or not - if(nError == ERROR_SUCCESS) - { - LPBYTE pbPatchFile = (LPBYTE)hf->pPatchHeader; - - // Copy the patch header itself - memcpy(pbPatchFile, pPatchHeader, sizeof(TPatchHeader)); - pbPatchFile += sizeof(TPatchHeader); - - // Load the rest of the patch - if(!SFileReadFile((HANDLE)hf, pbPatchFile, pPatchHeader->dwSizeOfPatchData - sizeof(TPatchHeader), NULL, NULL)) - nError = GetLastError(); - } - - return nError; + // Simply load the rest of the patch + SFileReadFile((HANDLE)hf, (pFullPatch + 1), cbBytesToRead, &cbBytesRead, NULL); + return (cbBytesRead == cbBytesToRead) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; } -static int LoadFilePatch_BSD0(TMPQFile * hf, TPatchHeader * pPatchHeader) +static int LoadFilePatch_BSD0(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch) { - LPBYTE pbDecompressed = NULL; + LPBYTE pbDecompressed = (LPBYTE)(pFullPatch + 1); LPBYTE pbCompressed = NULL; DWORD cbDecompressed = 0; DWORD cbCompressed = 0; DWORD dwBytesRead = 0; int nError = ERROR_SUCCESS; - // Allocate space for compressed data - cbCompressed = pPatchHeader->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER; - pbCompressed = STORM_ALLOC(BYTE, cbCompressed); - if(pbCompressed == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; + // Calculate the size of compressed data + cbDecompressed = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER); + cbCompressed = pFullPatch->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER; - // Read the compressed patch data - if(nError == ERROR_SUCCESS) - { - // Load the rest of the header - SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL); - if(dwBytesRead != cbCompressed) - nError = ERROR_FILE_CORRUPT; - } - - // Get the uncompressed size of the patch - if(nError == ERROR_SUCCESS) + // Is that file compressed? + if(cbCompressed < cbDecompressed) { - cbDecompressed = pPatchHeader->dwSizeOfPatchData - sizeof(TPatchHeader); - hf->pPatchHeader = (TPatchHeader *)STORM_ALLOC(BYTE, pPatchHeader->dwSizeOfPatchData); - if(hf->pPatchHeader == NULL) + pbCompressed = STORM_ALLOC(BYTE, cbCompressed); + if(pbCompressed == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Now decompress the patch data - if(nError == ERROR_SUCCESS) - { - // Copy the patch header - memcpy(hf->pPatchHeader, pPatchHeader, sizeof(TPatchHeader)); - pbDecompressed = (LPBYTE)hf->pPatchHeader + sizeof(TPatchHeader); - // Uncompress or copy the patch data - if(cbCompressed < cbDecompressed) + // Read the compressed patch data + if(nError == ERROR_SUCCESS) { - Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed); - } - else - { - assert(cbCompressed == cbDecompressed); - memcpy(pbDecompressed, pbCompressed, cbCompressed); + SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL); + if(dwBytesRead != cbCompressed) + nError = ERROR_FILE_CORRUPT; } + + // Decompress the data + if(nError == ERROR_SUCCESS) + Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed); + + if(pbCompressed != NULL) + STORM_FREE(pbCompressed); + } + else + { + SFileReadFile((HANDLE)hf, pbDecompressed, cbDecompressed, &dwBytesRead, NULL); + if(dwBytesRead != cbDecompressed) + nError = ERROR_FILE_CORRUPT; } - // Free buffers and exit - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); return nError; } static int ApplyFilePatch_COPY( - TMPQFile * hfFrom, - TMPQFile * hf, - TPatchHeader * pPatchHeader) + TMPQPatcher * pPatcher, + PMPQ_PATCH_HEADER pFullPatch, + LPBYTE pbTarget, + LPBYTE pbSource) { // Sanity checks - assert(hf->cbFileData == (pPatchHeader->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER)); - assert(hf->pbFileData != NULL); - hfFrom = hfFrom; + assert(pPatcher->cbMaxFileData >= pPatcher->cbFileData); + pFullPatch = pFullPatch; // Copy the patch data as-is - memcpy(hf->pbFileData, (LPBYTE)pPatchHeader + sizeof(TPatchHeader), hf->cbFileData); + memcpy(pbTarget, pbSource, pPatcher->cbFileData); return ERROR_SUCCESS; } static int ApplyFilePatch_BSD0( - TMPQFile * hfFrom, - TMPQFile * hf, - TPatchHeader * pPatchHeader) + TMPQPatcher * pPatcher, + PMPQ_PATCH_HEADER pFullPatch, + LPBYTE pbTarget, + LPBYTE pbSource) { PBLIZZARD_BSDIFF40_FILE pBsdiff; - LPDWORD pCtrlBlock; - LPBYTE pbPatchData = (LPBYTE)pPatchHeader + sizeof(TPatchHeader); + PBSDIFF_CTRL_BLOCK pCtrlBlock; + LPBYTE pbPatchData = (LPBYTE)(pFullPatch + 1); LPBYTE pDataBlock; LPBYTE pExtraBlock; - LPBYTE pbOldData = hfFrom->pbFileData; - LPBYTE pbNewData = hf->pbFileData; + LPBYTE pbOldData = pbSource; + LPBYTE pbNewData = pbTarget; DWORD dwCombineSize; DWORD dwNewOffset = 0; // Current position to patch DWORD dwOldOffset = 0; // Current source position DWORD dwNewSize; // Patched file size - DWORD dwOldSize = hfFrom->cbFileData; // File size before patch + DWORD dwOldSize = pPatcher->cbFileData; // File size before patch // Get pointer to the patch header // Format of BSDIFF header corresponds to original BSDIFF, which is: @@ -221,7 +248,7 @@ static int ApplyFilePatch_BSD0( // 0000 4 bytes Length to copy from the BSDIFF data block the new file // 0004 4 bytes Length to copy from the BSDIFF extra block // 0008 4 bytes Size to increment source file offset - pCtrlBlock = (LPDWORD)pbPatchData; + pCtrlBlock = (PBSDIFF_CTRL_BLOCK)pbPatchData; pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->CtrlBlockSize); // Get the pointer to the data block @@ -235,9 +262,9 @@ static int ApplyFilePatch_BSD0( // Now patch the file while(dwNewOffset < dwNewSize) { - DWORD dwAddDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock[0]); - DWORD dwMovDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock[1]); - DWORD dwOldMoveLength = BSWAP_INT32_UNSIGNED(pCtrlBlock[2]); + DWORD dwAddDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwAddDataLength); + DWORD dwMovDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwMovDataLength); + DWORD dwOldMoveLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwOldMoveLength); DWORD i; // Sanity check @@ -272,175 +299,122 @@ static int ApplyFilePatch_BSD0( if(dwOldMoveLength & 0x80000000) dwOldMoveLength = 0x80000000 - dwOldMoveLength; dwOldOffset += dwOldMoveLength; - pCtrlBlock += 3; + pCtrlBlock++; } - // Success + // The size after patch must match + if(dwNewOffset != pFullPatch->dwSizeAfterPatch) + return ERROR_FILE_CORRUPT; + + // Update the new data size + pPatcher->cbFileData = dwNewOffset; return ERROR_SUCCESS; } - -static int LoadFilePatch(TMPQFile * hf) +static PMPQ_PATCH_HEADER LoadFullFilePatch(TMPQFile * hf, MPQ_PATCH_HEADER & PatchHeader) { - TPatchHeader PatchHeader; - DWORD dwBytesRead; + PMPQ_PATCH_HEADER pFullPatch; int nError = ERROR_SUCCESS; - // Read the patch header - SFileReadFile((HANDLE)hf, &PatchHeader, sizeof(TPatchHeader), &dwBytesRead, NULL); - if(dwBytesRead != sizeof(TPatchHeader)) - nError = ERROR_FILE_CORRUPT; + // BSWAP the entire header, if needed + BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6); + BSWAP_ARRAY32_UNSIGNED(&PatchHeader.dwXFRM, sizeof(DWORD) * 3); // Verify the signatures in the patch header - if(nError == ERROR_SUCCESS) - { - // BSWAP the entire header, if needed - BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6); - PatchHeader.dwXFRM = BSWAP_INT32_UNSIGNED(PatchHeader.dwXFRM); - PatchHeader.dwXfrmBlockSize = BSWAP_INT32_UNSIGNED(PatchHeader.dwXfrmBlockSize); - PatchHeader.dwPatchType = BSWAP_INT32_UNSIGNED(PatchHeader.dwPatchType); - - if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM) - nError = ERROR_FILE_CORRUPT; - } + if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM) + return NULL; - // Read the patch, depending on patch type - if(nError == ERROR_SUCCESS) + // Allocate space for patch header and compressed data + pFullPatch = (PMPQ_PATCH_HEADER)STORM_ALLOC(BYTE, PatchHeader.dwSizeOfPatchData); + if(pFullPatch != NULL) { - switch(PatchHeader.dwPatchType) + // Copy the patch header + memcpy(pFullPatch, &PatchHeader, sizeof(MPQ_PATCH_HEADER)); + + // Read the patch, depending on patch type + if(nError == ERROR_SUCCESS) { - case 0x59504f43: // 'COPY' - nError = LoadFilePatch_COPY(hf, &PatchHeader); - break; + switch(PatchHeader.dwPatchType) + { + case 0x59504f43: // 'COPY' + nError = LoadFilePatch_COPY(hf, pFullPatch); + break; - case 0x30445342: // 'BSD0' - nError = LoadFilePatch_BSD0(hf, &PatchHeader); - break; + case 0x30445342: // 'BSD0' + nError = LoadFilePatch_BSD0(hf, pFullPatch); + break; - default: - nError = ERROR_FILE_CORRUPT; - break; + default: + nError = ERROR_FILE_CORRUPT; + break; + } + } + + // If something failed, free the patch buffer + if(nError != ERROR_SUCCESS) + { + STORM_FREE(pFullPatch); + pFullPatch = NULL; } } - return nError; + // Give the result to the caller + return pFullPatch; } static int ApplyFilePatch( - TMPQFile * hfBase, // The file in the base MPQ - TMPQFile * hfPrev, // The file in the previous MPQ - TMPQFile * hf) + TMPQPatcher * pPatcher, + PMPQ_PATCH_HEADER pFullPatch) { - TPatchHeader * pPatchHeader = hf->pPatchHeader; - TMPQFile * hfFrom = NULL; - int nError = ERROR_SUCCESS; + LPBYTE pbSource = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData2 : pPatcher->pbFileData1; + LPBYTE pbTarget = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData1 : pPatcher->pbFileData2; + int nError; // Sanity checks - assert(hf->pbFileData == NULL); - - // Either take the base version or the previous version - if(!memcmp(hfBase->FileDataMD5, pPatchHeader->md5_before_patch, MD5_DIGEST_SIZE)) - hfFrom = hfBase; - if(!memcmp(hfPrev->FileDataMD5, pPatchHeader->md5_before_patch, MD5_DIGEST_SIZE)) - hfFrom = hfPrev; - if(hfFrom == NULL) - return ERROR_FILE_CORRUPT; - - // Allocate the buffer for patched file content - hf->pbFileData = STORM_ALLOC(BYTE, pPatchHeader->dwSizeAfterPatch); - hf->cbFileData = pPatchHeader->dwSizeAfterPatch; - if(hf->pbFileData == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + assert(pFullPatch->dwSizeAfterPatch <= pPatcher->cbMaxFileData); - // Apply the patch - if(nError == ERROR_SUCCESS) + // Apply the patch according to the type + switch(pFullPatch->dwPatchType) { - switch(pPatchHeader->dwPatchType) - { - case 0x59504f43: // 'COPY' - nError = ApplyFilePatch_COPY(hfFrom, hf, pPatchHeader); - break; + case 0x59504f43: // 'COPY' + nError = ApplyFilePatch_COPY(pPatcher, pFullPatch, pbTarget, pbSource); + break; - case 0x30445342: // 'BSD0' - nError = ApplyFilePatch_BSD0(hfFrom, hf, pPatchHeader); - break; + case 0x30445342: // 'BSD0' + nError = ApplyFilePatch_BSD0(pPatcher, pFullPatch, pbTarget, pbSource); + break; - default: - nError = ERROR_FILE_CORRUPT; - break; - } + default: + nError = ERROR_FILE_CORRUPT; + break; } // Verify MD5 after patch - if(nError == ERROR_SUCCESS && pPatchHeader->dwSizeAfterPatch != 0) + if(nError == ERROR_SUCCESS && pFullPatch->dwSizeAfterPatch != 0) { // Verify the patched file - if(!VerifyDataBlockHash(hf->pbFileData, hf->cbFileData, pPatchHeader->md5_after_patch)) + if(!VerifyDataBlockHash(pbTarget, pFullPatch->dwSizeAfterPatch, pFullPatch->md5_after_patch)) nError = ERROR_FILE_CORRUPT; // Copy the MD5 of the new block - memcpy(hf->FileDataMD5, pPatchHeader->md5_after_patch, MD5_DIGEST_SIZE); + memcpy(pPatcher->this_md5, pFullPatch->md5_after_patch, MD5_DIGEST_SIZE); } return nError; } -static void FreePatchData(TMPQFile * hf) -{ - STORM_FREE(hf->pbFileData); - hf->pbFileData = NULL; - hf->cbFileData = 0; - - STORM_FREE(hf->pPatchHeader); - hf->pPatchHeader = NULL; -} - //----------------------------------------------------------------------------- // Local functions (patch prefix matching) -static TFileEntry * FindMd5ListFile(TMPQArchive * ha) -{ - TFileEntry * pFileEntry = ha->pFileTable + ha->dwFileTableSize; - char * szLstName; - size_t nTryCount = 0; - size_t nLength; - - // 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) - { - // The file name must be valid - if(pFileEntry->szFileName != NULL) - { - // Get the name and length - szLstName = pFileEntry->szFileName; - nLength = strlen(szLstName); - - // Check for the tail name - if(!_stricmp(szLstName + nLength - 8, "-md5.lst")) - return pFileEntry; - } - - // Move back - pFileEntry--; - nTryCount++; - } - - // Not found, sorry - return NULL; -} - -static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, const char * szPrefixEnd) +static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, size_t nLength) { 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); + if(szFileName != NULL && nLength == 0) + nLength = strlen(szFileName); // Create the patch prefix - nLength = (szPrefixEnd - szFileName); pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength); if(pNewPrefix != NULL) { @@ -465,7 +439,7 @@ static bool IsMatchingPatchFile( const char * szFileName, LPBYTE pbFileMd5) { - TPatchHeader PatchHeader = {0}; + MPQ_PATCH_HEADER PatchHeader = {0}; HANDLE hFile = NULL; DWORD dwTransferred = 0; bool bResult = false; @@ -474,12 +448,12 @@ static bool IsMatchingPatchFile( if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) { // Load the patch header - SFileReadFile(hFile, &PatchHeader, sizeof(TPatchHeader), &dwTransferred, NULL); + 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(TPatchHeader) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) + if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE)); // Close the file @@ -489,51 +463,87 @@ static bool IsMatchingPatchFile( return bResult; } -static const char * GetLstFileLanguage(const char * szFileName) +static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pMpqInfo) { - char szLstSuffix[0x80]; - size_t nLength; - size_t nSuffixLength; - - // Each language-dependent file ends with "xxXX-md5.lst" - nLength = strlen(szFileName); - if(nLength < 12) - return NULL; + TFileEntry * pFileEntry; + const char * szLanguage = LanguageList; + char szFileName[0x40]; - // Try each and every possibility - for(size_t i = 0; LanguageList[i] != NULL; i++) + // Iterate through all localized languages + while(pMpqInfo->szNameTemplate != NULL) { - nSuffixLength = sprintf(szLstSuffix, "%s-md5.lst", LanguageList[i]); - assert(nSuffixLength == 12); + // Iterate through all languages + for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage += 4) + { + // Construct the file name + memcpy(szFileName, pMpqInfo->szNameTemplate, pMpqInfo->nLength); + szFileName[pMpqInfo->nLangOffset + 0] = szLanguage[0]; + szFileName[pMpqInfo->nLangOffset + 1] = szLanguage[1]; + szFileName[pMpqInfo->nLangOffset + 2] = szLanguage[2]; + szFileName[pMpqInfo->nLangOffset + 3] = szLanguage[3]; + + // Append the suffix + memcpy(szFileName + pMpqInfo->nLength, "-md5.lst", 9); + + // Check whether the name exists + pFileEntry = GetFileEntryLocale(ha, szFileName, 0); + if(pFileEntry != NULL) + return szLanguage; + } - if(!_stricmp(szFileName + nLength - nSuffixLength, szLstSuffix)) - return LanguageList[i]; + // Move to the next language name + pMpqInfo++; } + // Not found return NULL; } -static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch) +static TFileEntry * FindBaseLstFile(TMPQArchive * ha) { TFileEntry * pFileEntry; - const char * szFilePrefix = "Base"; const char * szLanguage; - char szNamePrefix[0x10]; - int nLength; + char szFileName[0x40]; - // Find a *-md5.lst file in the base archive - pFileEntry = FindMd5ListFile(haBase); - if(pFileEntry == NULL) - return false; + // Prepare the file name tenplate + memcpy(szFileName, "####-md5.lst", 13); - // Language-specific MPQs have the language identifier right before extension - szLanguage = GetLstFileLanguage(pFileEntry->szFileName); - if(szLanguage != NULL) - szFilePrefix = szLanguage; + // 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; + } - // Format the name prefix - nLength = sprintf(szNamePrefix, "%s\\", szFilePrefix); - return CreatePatchPrefix(haPatch, szNamePrefix, &szNamePrefix[nLength]); + return NULL; +} + +static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch) +{ + const char * szPatchPrefix; + char szNamePrefix[0x08]; + + // Try to find the language of the MPQ archive + szPatchPrefix = FindArchiveLanguage(haBase, LocaleMpqs_WoW); + if(szPatchPrefix == NULL) + szPatchPrefix = "Base"; + + // Format the patch prefix + szNamePrefix[0] = szPatchPrefix[0]; + szNamePrefix[1] = szPatchPrefix[1]; + szNamePrefix[2] = szPatchPrefix[2]; + szNamePrefix[3] = szPatchPrefix[3]; + szNamePrefix[4] = '\\'; + szNamePrefix[5] = 0; + return CreatePatchPrefix(haPatch, szNamePrefix, 5); } // @@ -555,60 +565,68 @@ 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) { - TFileEntry * pFileTableEnd; - TFileEntry * pFileEntry; + TMPQNamePrefix * pPatchPrefix; TFileEntry * pBaseEntry; - const char * szPlainName; char * szLstFileName; + char * szPlainName; 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) + // First-level patches: Find the same file within the patch archive + // and verify by MD5-before-patch + if(haBase->haPatch == NULL) { - // Find that file in the patch MPQ - pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize; - for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize; + TFileEntry * pFileEntry; + + // Allocate working buffer for merging LST file + szLstFileName = STORM_ALLOC(char, cchWorkBuffer); + if(szLstFileName != NULL) { - // 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)) - { - // If the plain file name matches, we need to check its MD5 - szPlainName = GetPlainFileName(pFileEntry->szFileName); - cchDirName = (size_t)(szPlainName - pFileEntry->szFileName); + // Find a *-md5.lst file in the base archive + pBaseEntry = FindBaseLstFile(haBase); + if(pBaseEntry == NULL) + return false; - // The file name must not too long and must be PATCH_METADATA_NAME - if((cchDirName + cchBaseName) < cchWorkBuffer && _stricmp(szPlainName, PATCH_METADATA_NAME) == 0) + // Parse the entire file table + for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + // Look for "patch_metadata" file + if(IsPatchMetadataFile(pFileEntry)) { - // Construct the name of the eventuall LST file - memcpy(szLstFileName, pFileEntry->szFileName, cchDirName); - memcpy(szLstFileName + cchDirName, pBaseEntry->szFileName, cchBaseName); + // Construct the name of the MD5 file + strcpy(szLstFileName, pFileEntry->szFileName); + szPlainName = (char *)GetPlainFileName(szLstFileName); + strcpy(szPlainName, pBaseEntry->szFileName); - // If there is the "*-md5.lst" file in that directory, we check its MD5 + // Check for matching MD5 file if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5)) { - bResult = CreatePatchPrefix(haPatch, pFileEntry->szFileName, szPlainName); + bResult = CreatePatchPrefix(haPatch, szLstFileName, (size_t)(szPlainName - szLstFileName)); break; } } } + + // Delete the merge buffer + STORM_FREE(szLstFileName); } + } - // Free the work buffer - STORM_FREE(szLstFileName); + // For second-level patches, just take the patch prefix from the lower level patch + else + { + // There must be at least two patches in the chain + assert(haBase->haPatch->pPatchPrefix != NULL); + pPatchPrefix = haBase->haPatch->pPatchPrefix; + + // Copy the patch prefix + bResult = CreatePatchPrefix(haPatch, + pPatchPrefix->szPatchPrefix, + pPatchPrefix->nLength); } return bResult; @@ -618,23 +636,22 @@ static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const c { // If the patch prefix was explicitly entered, we use that one if(szPatchPathPrefix != NULL) - return CreatePatchPrefix(haPatch, szPatchPathPrefix, szPatchPathPrefix + strlen(szPatchPathPrefix)); + return CreatePatchPrefix(haPatch, szPatchPathPrefix, 0); - // 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. + // Patches for World of Warcraft - they mostly do not use prefix. // All patches that use patch prefix have the "base\\(patch_metadata) file present - if(GetFileEntryAny(haPatch, "base\\" PATCH_METADATA_NAME)) + if(GetFileEntryLocale(haPatch, "base\\" PATCH_METADATA_NAME, 0)) return FindPatchPrefix_WoW_13164_13623(haBase, haPatch); // 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(GetFileEntryAny(haBase, "StreamingBuckets.txt")) + if(GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0)) 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); + CreatePatchPrefix(haPatch, NULL, 0); return true; } @@ -643,11 +660,11 @@ static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const c bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize) { - TPatchHeader * pPatchHeader = (TPatchHeader *)pvData; + PMPQ_PATCH_HEADER pPatchHeader = (PMPQ_PATCH_HEADER)pvData; BLIZZARD_BSDIFF40_FILE DiffFile; DWORD dwPatchType; - if(cbData >= sizeof(TPatchHeader) + sizeof(BLIZZARD_BSDIFF40_FILE)) + if(cbData >= sizeof(MPQ_PATCH_HEADER) + sizeof(BLIZZARD_BSDIFF40_FILE)) { dwPatchType = BSWAP_INT32_UNSIGNED(pPatchHeader->dwPatchType); if(dwPatchType == 0x30445342) @@ -666,6 +683,40 @@ bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatche return false; } +int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf) +{ + DWORD cbMaxFileData = 0; + + // Overflow check + if((sizeof(MPQ_PATCH_HEADER) + cbMaxFileData) < cbMaxFileData) + return ERROR_NOT_ENOUGH_MEMORY; + if(hf->hfPatch == NULL) + return ERROR_INVALID_PARAMETER; + + // Initialize the entire structure with zeros + memset(pPatcher, 0, sizeof(TMPQPatcher)); + + // Copy the MD5 of the current file + memcpy(pPatcher->this_md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE); + + // Find out the biggest data size needed during the patching process + while(hf != NULL) + { + if(hf->pFileEntry->dwFileSize > cbMaxFileData) + cbMaxFileData = hf->pFileEntry->dwFileSize; + hf = hf->hfPatch; + } + + // Allocate primary and secondary buffer + pPatcher->pbFileData1 = STORM_ALLOC(BYTE, cbMaxFileData); + pPatcher->pbFileData2 = STORM_ALLOC(BYTE, cbMaxFileData); + if(!pPatcher->pbFileData1 || !pPatcher->pbFileData2) + return ERROR_NOT_ENOUGH_MEMORY; + + pPatcher->cbMaxFileData = cbMaxFileData; + return ERROR_SUCCESS; +} + // // Note: The patch may either be applied to the base file or to the previous version // In Starcraft II, Mods\Core.SC2Mod\Base.SC2Data, file StreamingBuckets.txt: @@ -685,59 +736,101 @@ bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatche // 9 patches in a row, each requiring 70 MB memory (35 MB patch data + 35 MB work buffer) // -int PatchFileData(TMPQFile * hf) +int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf) { + PMPQ_PATCH_HEADER pFullPatch; + MPQ_PATCH_HEADER PatchHeader1; + MPQ_PATCH_HEADER PatchHeader2 = {0}; TMPQFile * hfBase = hf; - TMPQFile * hfPrev = hf; + DWORD cbBytesRead = 0; int nError = ERROR_SUCCESS; - // We need to calculate the MD5 of the entire file - assert(hf->pbFileData != NULL); - assert(hf->cbFileData != 0); - CalculateDataBlockHash(hf->pbFileData, hf->cbFileData, hf->FileDataMD5); + // Move to the first patch + assert(hfBase->pbFileData == NULL); + assert(hfBase->cbFileData == 0); + hf = hf->hfPatch; + + // Read the header of the current patch + SFileReadFile((HANDLE)hf, &PatchHeader1, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL); + if(cbBytesRead != sizeof(MPQ_PATCH_HEADER)) + return ERROR_FILE_CORRUPT; - // Apply all patches - for(hf = hf->hfPatch; hf != NULL; hf = hf->hfPatch) + // Perform the patching process + while(nError == ERROR_SUCCESS && hf != NULL) { - // This must be true - assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE); + // Try to read the next patch header. If the md5_before_patch + // still matches we go directly to the next one and repeat + while(hf->hfPatch != NULL) + { + // Attempt to read the patch header + SFileReadFile((HANDLE)hf->hfPatch, &PatchHeader2, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL); + if(cbBytesRead != sizeof(MPQ_PATCH_HEADER)) + return ERROR_FILE_CORRUPT; - // Make sure that the patch data is loaded - nError = LoadFilePatch(hf); - if(nError != ERROR_SUCCESS) - break; + // Compare the md5_before_patch + if(memcmp(PatchHeader2.md5_before_patch, pPatcher->this_md5, MD5_DIGEST_SIZE)) + break; - // Apply the patch - nError = ApplyFilePatch(hfBase, hfPrev, hf); - if(nError != ERROR_SUCCESS) - break; + // Move one patch fuhrter + PatchHeader1 = PatchHeader2; + hf = hf->hfPatch; + } - // Only keep base file version and previous version - if(hfPrev != hfBase) - FreePatchData(hfPrev); + // Allocate memory for the patch data + pFullPatch = LoadFullFilePatch(hf, PatchHeader1); + if(pFullPatch != NULL) + { + // Apply the patch + nError = ApplyFilePatch(pPatcher, pFullPatch); + STORM_FREE(pFullPatch); + } + else + { + nError = ERROR_FILE_CORRUPT; + } - // Is this the last patch in the chain? - if(hf->hfPatch == NULL) - break; - hfPrev = hf; + // Move to the next patch + PatchHeader1 = PatchHeader2; + pPatcher->nCounter++; + hf = hf->hfPatch; } - // When done, we need to rewrite the base file data - // with the last of the patch chain + // Put the result data to the file structure if(nError == ERROR_SUCCESS) { - // Free the base file data - STORM_FREE(hfBase->pbFileData); - - // Switch the latest patched data to the base file - hfBase->pbFileData = hf->pbFileData; - hfBase->cbFileData = hf->cbFileData; - hf->pbFileData = NULL; - hf->cbFileData = 0; + // Swap the pointer to the file data structure + if(pPatcher->nCounter & 0x01) + { + hfBase->pbFileData = pPatcher->pbFileData2; + pPatcher->pbFileData2 = NULL; + } + else + { + hfBase->pbFileData = pPatcher->pbFileData1; + pPatcher->pbFileData1 = NULL; + } + + // Also supply the data size + hfBase->cbFileData = pPatcher->cbFileData; + } + + return ERROR_SUCCESS; +} + +void Patch_Finalize(TMPQPatcher * pPatcher) +{ + if(pPatcher != NULL) + { + if(pPatcher->pbFileData1 != NULL) + STORM_FREE(pPatcher->pbFileData1); + if(pPatcher->pbFileData2 != NULL) + STORM_FREE(pPatcher->pbFileData2); + + memset(pPatcher, 0, sizeof(TMPQPatcher)); } - return nError; } + //----------------------------------------------------------------------------- // Public functions |