diff options
| author | briaguya <70942617+briaguya-ai@users.noreply.github.com> | 2022-11-14 05:22:34 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-11-14 11:22:34 +0100 |
| commit | 3914781ebfd5cb22717cc207cb3d37d8754d0eb7 (patch) | |
| tree | 2e412b87e66b1deef47bef242e7189a200cea0ff /StormLib/src/SFilePatchArchives.cpp | |
| parent | 9eb29ed81cb497a6a216811255f8af7daf3f8bd8 (diff) | |
Import libultraship as a submodule (#1943)
Co-authored-by: briaguya <briaguya>
Co-authored-by: Christopher Leggett <chris@leggett.dev>
Co-authored-by: David Chavez <davi@dcvz.io>
Diffstat (limited to 'StormLib/src/SFilePatchArchives.cpp')
| -rw-r--r-- | StormLib/src/SFilePatchArchives.cpp | 1175 |
1 files changed, 0 insertions, 1175 deletions
diff --git a/StormLib/src/SFilePatchArchives.cpp b/StormLib/src/SFilePatchArchives.cpp deleted file mode 100644 index 03d567441..000000000 --- a/StormLib/src/SFilePatchArchives.cpp +++ /dev/null @@ -1,1175 +0,0 @@ -/*****************************************************************************/ -/* SFilePatchArchives.cpp Copyright (c) Ladislav Zezula 2010 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 18.08.10 1.00 Lad The first version of SFilePatchArchives.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Local structures - -#define MAX_SC2_PATCH_PREFIX 0x80 - -#define PATCH_SIGNATURE_HEADER 0x48435450 -#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; - ULONGLONG CtrlBlockSize; - ULONGLONG DataBlockSize; - 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 - -// 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[] = -{ - {"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; - LPBYTE pbCompressedEnd = pbCompressed + cbCompressed; - BYTE RepeatCount; - BYTE OneByte; - - // Cut the initial DWORD from the compressed chunk - pbCompressed += sizeof(DWORD); - - // Pre-fill decompressed buffer with zeros - memset(pbDecompressed, 0, cbDecompressed); - - // Unpack - while(pbCompressed < pbCompressedEnd && pbDecompressed < pbDecompressedEnd) - { - OneByte = *pbCompressed++; - - // Is it a repetition byte ? - if(OneByte & 0x80) - { - RepeatCount = (OneByte & 0x7F) + 1; - for(BYTE i = 0; i < RepeatCount; i++) - { - if(pbDecompressed == pbDecompressedEnd || pbCompressed == pbCompressedEnd) - break; - - *pbDecompressed++ = *pbCompressed++; - } - } - else - { - pbDecompressed += (OneByte + 1); - } - } -} - -static DWORD LoadFilePatch_COPY(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch) -{ - DWORD cbBytesToRead = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER); - DWORD cbBytesRead = 0; - - // Simply load the rest of the patch - SFileReadFile((HANDLE)hf, (pFullPatch + 1), cbBytesToRead, &cbBytesRead, NULL); - return (cbBytesRead == cbBytesToRead) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; -} - -static DWORD LoadFilePatch_BSD0(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch) -{ - LPBYTE pbDecompressed = (LPBYTE)(pFullPatch + 1); - LPBYTE pbCompressed = NULL; - DWORD cbDecompressed = 0; - DWORD cbCompressed = 0; - DWORD dwBytesRead = 0; - DWORD dwErrCode = ERROR_SUCCESS; - - // Calculate the size of compressed data - cbDecompressed = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER); - cbCompressed = pFullPatch->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER; - - // Is that file compressed? - if(cbCompressed < cbDecompressed) - { - pbCompressed = STORM_ALLOC(BYTE, cbCompressed); - if(pbCompressed == NULL) - dwErrCode = ERROR_NOT_ENOUGH_MEMORY; - - // Read the compressed patch data - if(dwErrCode == ERROR_SUCCESS) - { - SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL); - if(dwBytesRead != cbCompressed) - dwErrCode = ERROR_FILE_CORRUPT; - } - - // Decompress the data - if(dwErrCode == 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) - dwErrCode = ERROR_FILE_CORRUPT; - } - - return dwErrCode; -} - -static DWORD ApplyFilePatch_COPY( - TMPQPatcher * pPatcher, - PMPQ_PATCH_HEADER pFullPatch, - LPBYTE pbTarget, - LPBYTE pbSource) -{ - // Sanity checks - assert(pPatcher->cbMaxFileData >= pPatcher->cbFileData); - pFullPatch = pFullPatch; - - // Copy the patch data as-is - memcpy(pbTarget, pbSource, pPatcher->cbFileData); - return ERROR_SUCCESS; -} - -static DWORD ApplyFilePatch_BSD0( - TMPQPatcher * pPatcher, - PMPQ_PATCH_HEADER pFullPatch, - LPBYTE pbTarget, - LPBYTE pbSource) -{ - PBLIZZARD_BSDIFF40_FILE pBsdiff; - PBSDIFF_CTRL_BLOCK pCtrlBlock; - LPBYTE pbPatchData = (LPBYTE)(pFullPatch + 1); - LPBYTE pDataBlock; - LPBYTE pExtraBlock; - 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 = pPatcher->cbFileData; // File size before patch - - // Get pointer to the patch header - // Format of BSDIFF header corresponds to original BSDIFF, which is: - // 0000 8 bytes signature "BSDIFF40" - // 0008 8 bytes size of the control block - // 0010 8 bytes size of the data block - // 0018 8 bytes new size of the patched file - pBsdiff = (PBLIZZARD_BSDIFF40_FILE)pbPatchData; - pbPatchData += sizeof(BLIZZARD_BSDIFF40_FILE); - - // Get pointer to the 32-bit BSDIFF control block - // The control block follows immediately after the BSDIFF header - // and consists of three 32-bit integers - // 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 = (PBSDIFF_CTRL_BLOCK)pbPatchData; - pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->CtrlBlockSize); - - // Get the pointer to the data block - pDataBlock = (LPBYTE)pbPatchData; - pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->DataBlockSize); - - // Get the pointer to the extra block - pExtraBlock = (LPBYTE)pbPatchData; - dwNewSize = (DWORD)BSWAP_INT64_UNSIGNED(pBsdiff->NewFileSize); - - // Now patch the file - while(dwNewOffset < dwNewSize) - { - 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 - if((dwNewOffset + dwAddDataLength) > dwNewSize) - return ERROR_FILE_CORRUPT; - - // Read the diff string to the target buffer - memcpy(pbNewData + dwNewOffset, pDataBlock, dwAddDataLength); - pDataBlock += dwAddDataLength; - - // Get the longest block that we can combine - dwCombineSize = ((dwOldOffset + dwAddDataLength) >= dwOldSize) ? (dwOldSize - dwOldOffset) : dwAddDataLength; - if((dwNewOffset + dwCombineSize) > dwNewSize || (dwNewOffset + dwCombineSize) < dwNewOffset) - return ERROR_FILE_CORRUPT; - - // Now combine the patch data with the original file - for(i = 0; i < dwCombineSize; i++) - pbNewData[dwNewOffset + i] = pbNewData[dwNewOffset + i] + pbOldData[dwOldOffset + i]; - - // Move the offsets - dwNewOffset += dwAddDataLength; - dwOldOffset += dwAddDataLength; - - // Sanity check - if((dwNewOffset + dwMovDataLength) > dwNewSize) - return ERROR_FILE_CORRUPT; - - // Copy the data from the extra block in BSDIFF patch - memcpy(pbNewData + dwNewOffset, pExtraBlock, dwMovDataLength); - pExtraBlock += dwMovDataLength; - dwNewOffset += dwMovDataLength; - - // Move the old offset - if(dwOldMoveLength & 0x80000000) - dwOldMoveLength = 0x80000000 - dwOldMoveLength; - dwOldOffset += dwOldMoveLength; - pCtrlBlock++; - } - - // 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 PMPQ_PATCH_HEADER LoadFullFilePatch(TMPQFile * hf, MPQ_PATCH_HEADER & PatchHeader) -{ - PMPQ_PATCH_HEADER pFullPatch; - DWORD dwErrCode = ERROR_SUCCESS; - - // 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(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM) - return NULL; - - // Allocate space for patch header and compressed data - pFullPatch = (PMPQ_PATCH_HEADER)STORM_ALLOC(BYTE, PatchHeader.dwSizeOfPatchData); - if(pFullPatch != NULL) - { - // Copy the patch header - memcpy(pFullPatch, &PatchHeader, sizeof(MPQ_PATCH_HEADER)); - - // Read the patch, depending on patch type - if(dwErrCode == ERROR_SUCCESS) - { - switch(PatchHeader.dwPatchType) - { - case 0x59504f43: // 'COPY' - dwErrCode = LoadFilePatch_COPY(hf, pFullPatch); - break; - - case 0x30445342: // 'BSD0' - dwErrCode = LoadFilePatch_BSD0(hf, pFullPatch); - break; - - default: - dwErrCode = ERROR_FILE_CORRUPT; - break; - } - } - - // If something failed, free the patch buffer - if(dwErrCode != ERROR_SUCCESS) - { - STORM_FREE(pFullPatch); - pFullPatch = NULL; - } - } - - // Give the result to the caller - return pFullPatch; -} - -static DWORD ApplyFilePatch( - TMPQPatcher * pPatcher, - PMPQ_PATCH_HEADER pFullPatch) -{ - LPBYTE pbSource = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData2 : pPatcher->pbFileData1; - LPBYTE pbTarget = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData1 : pPatcher->pbFileData2; - DWORD dwErrCode; - - // Sanity checks - assert(pFullPatch->dwSizeAfterPatch <= pPatcher->cbMaxFileData); - - // Apply the patch according to the type - switch(pFullPatch->dwPatchType) - { - case 0x59504f43: // 'COPY' - dwErrCode = ApplyFilePatch_COPY(pPatcher, pFullPatch, pbTarget, pbSource); - break; - - case 0x30445342: // 'BSD0' - dwErrCode = ApplyFilePatch_BSD0(pPatcher, pFullPatch, pbTarget, pbSource); - break; - - default: - dwErrCode = ERROR_FILE_CORRUPT; - break; - } - - // Verify MD5 after patch - if(dwErrCode == ERROR_SUCCESS && pFullPatch->dwSizeAfterPatch != 0) - { - // Verify the patched file - if(!VerifyDataBlockHash(pbTarget, pFullPatch->dwSizeAfterPatch, pFullPatch->md5_after_patch)) - dwErrCode = ERROR_FILE_CORRUPT; - - // Copy the MD5 of the new block - memcpy(pPatcher->this_md5, pFullPatch->md5_after_patch, MD5_DIGEST_SIZE); - } - - return dwErrCode; -} - -//----------------------------------------------------------------------------- -// Local functions (patch prefix matching) - -static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, size_t nLength) -{ - TMPQNamePrefix * pNewPrefix; - - // If the length of the patch prefix was not entered, find it - // Not that the patch prefix must always begin with backslash - if(szFileName != NULL && nLength == 0) - nLength = strlen(szFileName); - - // Create the patch prefix - pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength + 1); - if(pNewPrefix != NULL) - { - // Fill the name prefix. Also add the backslash - if(szFileName && nLength) - { - memcpy(pNewPrefix->szPatchPrefix, szFileName, nLength); - if(pNewPrefix->szPatchPrefix[nLength - 1] != '\\') - pNewPrefix->szPatchPrefix[nLength++] = '\\'; - } - - // Terminate the string and fill the length - pNewPrefix->szPatchPrefix[nLength] = 0; - pNewPrefix->nLength = nLength; - } - - ha->pPatchPrefix = pNewPrefix; - return (pNewPrefix != NULL); -} - -static bool CheckAndCreatePatchPrefix(TMPQArchive * ha, const char * szPatchPrefix, size_t nLength) -{ - char szTempName[MAX_SC2_PATCH_PREFIX + 0x41]; - bool bResult = false; - - // Prepare the patch file name - if(nLength > MAX_SC2_PATCH_PREFIX) - return false; - - // Prepare the patched file name - memcpy(szTempName, szPatchPrefix, nLength); - memcpy(&szTempName[nLength], "\\(patch_metadata)", 18); - - // Verifywhether that file exists - if(GetFileEntryLocale(ha, szTempName, 0) != NULL) - bResult = CreatePatchPrefix(ha, szPatchPrefix, nLength); - - return bResult; -} - -static bool IsMatchingPatchFile( - TMPQArchive * ha, - const char * szFileName, - 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)) - { - // 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(&PatchHeader, 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); - } - - return bResult; -} - -static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pMpqInfo) -{ - TFileEntry * pFileEntry; - const char * szLanguage = LanguageList; - char szFileName[0x40]; - - // Iterate through all localized languages - while(pMpqInfo->szNameTemplate != NULL) - { - // 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; - } - - // Move to the next language name - pMpqInfo++; - } - - // Not found - return NULL; -} - -//----------------------------------------------------------------------------- -// Finding ratch prefix for an temporary build of WoW (Pre-Cataclysm) - -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); -} - -//----------------------------------------------------------------------------- -// Finding patch prefix for Starcraft II (Pre-Legacy of the Void) - -// -// This method tries to match the patch by placement of the archive (in the game subdirectory) -// -// Archive Path: %GAME_DIR%\Mods\SwarmMulti.SC2Mod\Base.SC2Data -// Patch Prefix: Mods\SwarmMulti.SC2Mod\Base.SC2Data -// -// Archive Path: %ANY_DIR%\MPQ_2013_v4_Mods#Liberty.SC2Mod#enGB.SC2Data -// Patch Prefix: Mods\Liberty.SC2Mod\enGB.SC2Data -// - -static bool CheckPatchPrefix_SC2_ArchiveName( - TMPQArchive * haPatch, - const TCHAR * szPathPtr, - const TCHAR * szSeparator, - const TCHAR * szPathEnd, - const TCHAR * szExpectedString, - size_t cchExpectedString) -{ - char szPatchPrefix[MAX_SC2_PATCH_PREFIX+0x41]; - size_t nLength = 0; - bool bResult = false; - - // Check whether the length is equal to the length of the expected string - if((size_t)(szSeparator - szPathPtr) == cchExpectedString) - { - // Now check the string itself - if(!_tcsnicmp(szPathPtr, szExpectedString, szSeparator - szPathPtr)) - { - // Copy the name string - for(; szPathPtr < szPathEnd; szPathPtr++) - { - if(szPathPtr[0] != _T('/') && szPathPtr[0] != _T('#')) - szPatchPrefix[nLength++] = (char)szPathPtr[0]; - else - szPatchPrefix[nLength++] = '\\'; - } - - // Check and create the patch prefix - bResult = CheckAndCreatePatchPrefix(haPatch, szPatchPrefix, nLength); - } - } - - return bResult; -} - -static bool FindPatchPrefix_SC2_ArchiveName(TMPQArchive * haBase, TMPQArchive * haPatch) -{ - const TCHAR * szPathBegin = FileStream_GetFileName(haBase->pStream); - const TCHAR * szSeparator = NULL; - const TCHAR * szPathEnd = szPathBegin + _tcslen(szPathBegin); - const TCHAR * szPathPtr; - int nSlashCount = 0; - int nDotCount = 0; - - // Skip the part where the patch prefix would be too long - if((szPathEnd - szPathBegin) > MAX_SC2_PATCH_PREFIX) - szPathBegin = szPathEnd - MAX_SC2_PATCH_PREFIX; - - // Search for the file extension - for(szPathPtr = szPathEnd; szPathPtr > szPathBegin; szPathPtr--) - { - if(szPathPtr[0] == _T('.')) - { - nDotCount++; - break; - } - } - - // Search for the possible begin of the prefix name - for(/* NOTHING */; szPathPtr > szPathBegin; szPathPtr--) - { - // Check the slashes, backslashes and hashes - if(szPathPtr[0] == _T('\\') || szPathPtr[0] == _T('/') || szPathPtr[0] == _T('#')) - { - if(nDotCount == 0) - return false; - szSeparator = szPathPtr; - nSlashCount++; - } - - // Check the path parts - if(szSeparator != NULL && nSlashCount >= nDotCount) - { - if(CheckPatchPrefix_SC2_ArchiveName(haPatch, szPathPtr, szSeparator, szPathEnd, _T("Battle.net"), 10)) - return true; - if(CheckPatchPrefix_SC2_ArchiveName(haPatch, szPathPtr, szSeparator, szPathEnd, _T("Campaigns"), 9)) - return true; - if(CheckPatchPrefix_SC2_ArchiveName(haPatch, szPathPtr, szSeparator, szPathEnd, _T("Mods"), 4)) - return true; - } - } - - // Not matched, sorry - return false; -} - -// -// This method tries to read the patch prefix from a helper file -// -// Example -// ========================================================= -// MPQ File Name: MPQ_2013_v4_Base1.SC2Data -// Helper File : MPQ_2013_v4_Base1.SC2Data-PATCH -// File Contains: PatchPrefix=Mods\Core.SC2Mod\Base.SC2Data -// Patch Prefix : Mods\Core.SC2Mod\Base.SC2Data -// - -static bool ExtractPatchPrefixFromFile(const TCHAR * szHelperFile, char * szPatchPrefix, size_t nMaxChars, size_t * PtrLength) -{ - TFileStream * pStream; - ULONGLONG FileSize = 0; - size_t nLength; - char szFileData[MAX_PATH+1]; - bool bResult = false; - - pStream = FileStream_OpenFile(szHelperFile, STREAM_FLAG_READ_ONLY); - if(pStream != NULL) - { - // Retrieve and check the file size - FileStream_GetSize(pStream, &FileSize); - if(12 <= FileSize && FileSize < MAX_PATH) - { - // Read the entire file to memory - if(FileStream_Read(pStream, NULL, szFileData, (DWORD)FileSize)) - { - // Terminate the buffer with zero - szFileData[(DWORD)FileSize] = 0; - - // The file data must begin with the "PatchPrefix" variable - if(!_strnicmp(szFileData, "PatchPrefix", 11)) - { - char * szLinePtr = szFileData + 11; - char * szLineEnd; - - // Skip spaces or '=' - while(szLinePtr[0] == ' ' || szLinePtr[0] == '=') - szLinePtr++; - szLineEnd = szLinePtr; - - // Find the end - while(szLineEnd[0] != 0 && szLineEnd[0] != 0x0A && szLineEnd[0] != 0x0D) - szLineEnd++; - nLength = (size_t)(szLineEnd - szLinePtr); - - // Copy the variable - if(szLineEnd > szLinePtr && nLength <= nMaxChars) - { - memcpy(szPatchPrefix, szLinePtr, nLength); - szPatchPrefix[nLength] = 0; - PtrLength[0] = nLength; - bResult = true; - } - } - } - } - - // Close the stream - FileStream_Close(pStream); - } - - return bResult; -} - - -static bool FindPatchPrefix_SC2_HelperFile(TMPQArchive * haBase, TMPQArchive * haPatch) -{ - TCHAR szHelperFile[MAX_PATH+1]; - char szPatchPrefix[MAX_SC2_PATCH_PREFIX+0x41]; - size_t nLength = 0; - bool bResult = false; - - // Create the name of the patch helper file - _tcscpy(szHelperFile, FileStream_GetFileName(haBase->pStream)); - if(_tcslen(szHelperFile) + 6 > MAX_PATH) - return false; - _tcscat(szHelperFile, _T("-PATCH")); - - // Open the patch helper file and read the line - if(ExtractPatchPrefixFromFile(szHelperFile, szPatchPrefix, MAX_SC2_PATCH_PREFIX, &nLength)) - bResult = CheckAndCreatePatchPrefix(haPatch, szPatchPrefix, nLength); - - return bResult; -} - -// -// 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: -// File in the base MPQ: enGB-md5.lst -// File in the patch MPQ: 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_MatchFiles(TMPQArchive * haBase, TMPQArchive * haPatch, TFileEntry * pBaseEntry) -{ - TMPQNamePrefix * pPatchPrefix; - char * szPatchFileName; - char * szPlainName; - size_t cchWorkBuffer = 0x400; - bool bResult = false; - - // First-level patches: Find the same file within the patch archive - // and verify by MD5-before-patch - if(haBase->haPatch == NULL) - { - TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize; - TFileEntry * pFileEntry; - - // Allocate working buffer for merging LST file - szPatchFileName = STORM_ALLOC(char, cchWorkBuffer); - if(szPatchFileName != NULL) - { - // 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 MD5 file - strcpy(szPatchFileName, pFileEntry->szFileName); - szPlainName = (char *)GetPlainFileName(szPatchFileName); - strcpy(szPlainName, pBaseEntry->szFileName); - - // Check for matching MD5 file - if(IsMatchingPatchFile(haPatch, szPatchFileName, pBaseEntry->md5)) - { - bResult = CreatePatchPrefix(haPatch, szPatchFileName, (size_t)(szPlainName - szPatchFileName)); - break; - } - } - } - - // Delete the merge buffer - STORM_FREE(szPatchFileName); - } - } - - // 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; -} - -// Note: pBaseEntry is the file entry of the base version of "StreamingBuckets.txt" -static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch, TFileEntry * pBaseEntry) -{ - // Method 1: Try it by the placement of the archive. - // Works when someone is opening an archive in the game (sub)directory - if(FindPatchPrefix_SC2_ArchiveName(haBase, haPatch)) - return true; - - // Method 2: Try to locate the Name.Ext-PATCH file and read the patch prefix from it - if(FindPatchPrefix_SC2_HelperFile(haBase, haPatch)) - return true; - - // Method 3: Try to pair any version of "StreamingBuckets.txt" from the patch MPQ - // with the "StreamingBuckets.txt" in the base MPQ. Does not always work - if(FindPatchPrefix_SC2_MatchFiles(haBase, haPatch, pBaseEntry)) - return true; - - return false; -} - -// -// Patch prefix is the path subdirectory where the patched files are within MPQ. -// -// Example 1: -// Main MPQ: locale-enGB.MPQ -// Patch MPQ: wow-update-12694.MPQ -// File in main MPQ: DBFilesClient\Achievement.dbc -// File in patch MPQ: enGB\DBFilesClient\Achievement.dbc -// Path prefix: enGB -// -// Example 2: -// Main MPQ: expansion1.MPQ -// Patch MPQ: wow-update-12694.MPQ -// File in main MPQ: DBFilesClient\Achievement.dbc -// File in patch MPQ: Base\DBFilesClient\Achievement.dbc -// Path prefix: Base -// -// Example 3: -// Main MPQ: %GAME%\Battle.net\Battle.net.MPQ -// Patch MPQ: s2-update-base-26147.MPQ -// File in main MPQ: Battle.net\i18n\deDE\String\CLIENT_ACHIEVEMENTS.xml -// File in patch MPQ: Battle.net\Battle.net.MPQ\Battle.net\i18n\deDE\String\CLIENT_ACHIEVEMENTS.xml -// Path prefix: Battle.net\Battle.net.MPQ -// -// Example 4: -// Main MPQ: %GAME%\Campaigns\Liberty.SC2Campaign\enGB.SC2Data -// *OR* %ANY_DIR%\%ANY_NAME%Campaigns#Liberty.SC2Campaign#enGB.SC2Data -// Patch MPQ: s2-update-enGB-23258.MPQ -// File in main MPQ: LocalizedData\GameHotkeys.txt -// File in patch MPQ: Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt -// Patch Prefix: Campaigns\Liberty.SC2Campaign\enGB.SC2Data -// - -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); - - // 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(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 - 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 - CreatePatchPrefix(haPatch, NULL, 0); - return true; -} - -//----------------------------------------------------------------------------- -// Public functions (StormLib internals) - -bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize) -{ - PMPQ_PATCH_HEADER pPatchHeader = (PMPQ_PATCH_HEADER)pvData; - BLIZZARD_BSDIFF40_FILE DiffFile; - DWORD dwPatchType; - - if(cbData >= sizeof(MPQ_PATCH_HEADER) + sizeof(BLIZZARD_BSDIFF40_FILE)) - { - dwPatchType = BSWAP_INT32_UNSIGNED(pPatchHeader->dwPatchType); - if(dwPatchType == 0x30445342) - { - // Give the caller the patch file size - if(pdwPatchedFileSize != NULL) - { - Decompress_RLE((LPBYTE)&DiffFile, sizeof(BLIZZARD_BSDIFF40_FILE), (LPBYTE)(pPatchHeader + 1), sizeof(BLIZZARD_BSDIFF40_FILE)); - DiffFile.NewFileSize = BSWAP_INT64_UNSIGNED(DiffFile.NewFileSize); - *pdwPatchedFileSize = (DWORD)DiffFile.NewFileSize; - return true; - } - } - } - - return false; -} - -DWORD Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf) -{ - DWORD cbMaxFileData = 0; - - // Overflow check - if((cbMaxFileData + (DWORD)sizeof(MPQ_PATCH_HEADER)) < 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: -// -// Base file MD5: 31376b0344b6df59ad009d4296125539 -// -// s2-update-base-23258: from 31376b0344b6df59ad009d4296125539 to 941a82683452e54bf024a8d491501824 -// s2-update-base-24540: from 31376b0344b6df59ad009d4296125539 to 941a82683452e54bf024a8d491501824 -// s2-update-base-26147: from 31376b0344b6df59ad009d4296125539 to d5d5253c762fac6b9761240288a0771a -// s2-update-base-28522: from 31376b0344b6df59ad009d4296125539 to 5a76c4b356920aab7afd22e0e1913d7a -// s2-update-base-30508: from 31376b0344b6df59ad009d4296125539 to 8cb0d4799893fe801cc78ae4488a3671 -// s2-update-base-32283: from 31376b0344b6df59ad009d4296125539 to 8cb0d4799893fe801cc78ae4488a3671 -// -// We don't keep all intermediate versions in memory, as it would cause massive -// memory usage during patching process. A prime example is the file -// DBFilesClient\\Item-Sparse.db2 from locale-enGB.MPQ (WoW 16965), which has -// 9 patches in a row, each requiring 70 MB memory (35 MB patch data + 35 MB work buffer) -// - -DWORD Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf) -{ - PMPQ_PATCH_HEADER pFullPatch; - MPQ_PATCH_HEADER PatchHeader1; - MPQ_PATCH_HEADER PatchHeader2 = {0}; - TMPQFile * hfBase = hf; - DWORD cbBytesRead = 0; - DWORD dwErrCode = ERROR_SUCCESS; - - // 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; - - // Perform the patching process - while(dwErrCode == ERROR_SUCCESS && hf != NULL) - { - // 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; - - // Compare the md5_before_patch - if(memcmp(PatchHeader2.md5_before_patch, pPatcher->this_md5, MD5_DIGEST_SIZE)) - break; - - // Move one patch fuhrter - PatchHeader1 = PatchHeader2; - hf = hf->hfPatch; - } - - // Allocate memory for the patch data - pFullPatch = LoadFullFilePatch(hf, PatchHeader1); - if(pFullPatch != NULL) - { - // Apply the patch - dwErrCode = ApplyFilePatch(pPatcher, pFullPatch); - STORM_FREE(pFullPatch); - } - else - { - dwErrCode = ERROR_FILE_CORRUPT; - } - - // Move to the next patch - PatchHeader1 = PatchHeader2; - pPatcher->nCounter++; - hf = hf->hfPatch; - } - - // Put the result data to the file structure - if(dwErrCode == ERROR_SUCCESS) - { - // 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)); - } -} - - -//----------------------------------------------------------------------------- -// Public functions - -bool WINAPI SFileOpenPatchArchive( - HANDLE hMpq, - const TCHAR * szPatchMpqName, - const char * szPatchPathPrefix, - DWORD dwFlags) -{ - TMPQArchive * haPatch; - TMPQArchive * ha = (TMPQArchive *)hMpq; - HANDLE hPatchMpq = NULL; - DWORD dwErrCode = ERROR_SUCCESS; - - // Keep compiler happy - dwFlags = dwFlags; - - // Verify input parameters - if(!IsValidMpqHandle(hMpq)) - dwErrCode = ERROR_INVALID_HANDLE; - if(szPatchMpqName == NULL || *szPatchMpqName == 0) - dwErrCode = ERROR_INVALID_PARAMETER; - - // - // We don't allow adding patches to archives that have been open for write - // - // Error scenario: - // - // 1) Open archive for writing - // 2) Modify or replace a file - // 3) Add patch archive to the opened MPQ - // 4) Read patched file - // 5) Now what ? - // - - if(dwErrCode == ERROR_SUCCESS) - { - if(!(ha->dwFlags & MPQ_FLAG_READ_ONLY)) - dwErrCode = ERROR_ACCESS_DENIED; - } - - // Open the archive like it is normal archive - if(dwErrCode == ERROR_SUCCESS) - { - if(SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY | MPQ_OPEN_PATCH, &hPatchMpq)) - { - // 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)) - { - // 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; - } - } - - // Close the archive - SFileCloseArchive(hPatchMpq); - dwErrCode = ERROR_CANT_FIND_PATCH_PREFIX; - } - else - { - dwErrCode = GetLastError(); - } - } - - SetLastError(dwErrCode); - return false; -} - -bool WINAPI SFileIsPatchedArchive(HANDLE hMpq) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - // Verify input parameters - if(!IsValidMpqHandle(hMpq)) - return false; - - return (ha->haPatch != NULL); -} |
