aboutsummaryrefslogtreecommitdiff
path: root/src/SFilePatchArchives.cpp
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2013-01-11 14:55:08 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2013-01-11 14:55:08 +0100
commit3a926f0228c68d7d91cf3946624d7859976440ec (patch)
treec4e7d36dc8157576929988cdfcf5bfd8262cd09c /src/SFilePatchArchives.cpp
parentdf4b0c085478389c9a21a09521d46735a0109c8a (diff)
Initial creation
Diffstat (limited to 'src/SFilePatchArchives.cpp')
-rw-r--r--src/SFilePatchArchives.cpp587
1 files changed, 587 insertions, 0 deletions
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp
new file mode 100644
index 0000000..8f259f4
--- /dev/null
+++ b/src/SFilePatchArchives.cpp
@@ -0,0 +1,587 @@
+/*****************************************************************************/
+/* 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
+
+typedef struct _BLIZZARD_BSDIFF40_FILE
+{
+ ULONGLONG Signature;
+ ULONGLONG CtrlBlockSize;
+ ULONGLONG DataBlockSize;
+ ULONGLONG NewFileSize;
+} BLIZZARD_BSDIFF40_FILE, *PBLIZZARD_BSDIFF40_FILE;
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static bool GetDefaultPatchPrefix(
+ const TCHAR * szBaseMpqName,
+ char * szBuffer)
+{
+ const TCHAR * szExtension;
+ const TCHAR * szDash;
+
+ // Ensure that both names are plain names
+ szBaseMpqName = GetPlainFileNameT(szBaseMpqName);
+
+ // Patch prefix is for the Cataclysm MPQs, whose names
+ // are like "locale-enGB.MPQ" or "speech-enGB.MPQ"
+ szExtension = _tcsrchr(szBaseMpqName, _T('.'));
+ szDash = _tcsrchr(szBaseMpqName, _T('-'));
+ strcpy(szBuffer, "Base");
+
+ // If the length of the prefix doesn't match, use default one
+ if(szExtension != NULL && szDash != NULL && (szExtension - szDash) == 5)
+ {
+ // Copy the prefix
+ szBuffer[0] = (char)szDash[1];
+ szBuffer[1] = (char)szDash[2];
+ szBuffer[2] = (char)szDash[3];
+ szBuffer[3] = (char)szDash[4];
+ szBuffer[4] = 0;
+ }
+
+ return true;
+}
+
+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);
+ cbCompressed -= 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 int LoadMpqPatch_COPY(TMPQFile * hf, TPatchHeader * pPatchHeader)
+{
+ 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;
+
+ // 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;
+}
+
+static int LoadMpqPatch_BSD0(TMPQFile * hf, TPatchHeader * pPatchHeader)
+{
+ LPBYTE pbDecompressed = NULL;
+ 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_SUCCESS;
+
+ // 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)
+ {
+ cbDecompressed = pPatchHeader->dwSizeOfPatchData - sizeof(TPatchHeader);
+ hf->pPatchHeader = (TPatchHeader *)STORM_ALLOC(BYTE, pPatchHeader->dwSizeOfPatchData);
+ if(hf->pPatchHeader == 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)
+ {
+ Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed);
+ }
+ else
+ {
+ assert(cbCompressed == cbDecompressed);
+ memcpy(pbDecompressed, pbCompressed, cbCompressed);
+ }
+ }
+
+ // Free buffers and exit
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+ return nError;
+}
+
+static int ApplyMpqPatch_COPY(
+ TMPQFile * hf,
+ TPatchHeader * pPatchHeader)
+{
+ LPBYTE pbNewFileData;
+ DWORD cbNewFileData;
+
+ // Allocate space for new file data
+ cbNewFileData = pPatchHeader->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER;
+ pbNewFileData = STORM_ALLOC(BYTE, cbNewFileData);
+ if(pbNewFileData == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Copy the patch data as-is
+ memcpy(pbNewFileData, (LPBYTE)pPatchHeader + sizeof(TPatchHeader), cbNewFileData);
+
+ // Free the old file data
+ STORM_FREE(hf->pbFileData);
+
+ // Put the new file data there
+ hf->pbFileData = pbNewFileData;
+ hf->cbFileData = cbNewFileData;
+ return ERROR_SUCCESS;
+}
+
+static int ApplyMpqPatch_BSD0(
+ TMPQFile * hf,
+ TPatchHeader * pPatchHeader)
+{
+ PBLIZZARD_BSDIFF40_FILE pBsdiff;
+ LPDWORD pCtrlBlock;
+ LPBYTE pbPatchData = (LPBYTE)pPatchHeader + sizeof(TPatchHeader);
+ LPBYTE pDataBlock;
+ LPBYTE pExtraBlock;
+ LPBYTE pbNewData = NULL;
+ LPBYTE pbOldData = (LPBYTE)hf->pbFileData;
+ DWORD dwNewOffset = 0; // Current position to patch
+ DWORD dwOldOffset = 0; // Current source position
+ DWORD dwNewSize; // Patched file size
+ DWORD dwOldSize = hf->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 = (LPDWORD)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);
+
+ // Allocate new buffer
+ pbNewData = STORM_ALLOC(BYTE, dwNewSize);
+ if(pbNewData == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // 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 i;
+
+ // Sanity check
+ if((dwNewOffset + dwAddDataLength) > dwNewSize)
+ {
+ STORM_FREE(pbNewData);
+ return ERROR_FILE_CORRUPT;
+ }
+
+ // Read the diff string to the target buffer
+ memcpy(pbNewData + dwNewOffset, pDataBlock, dwAddDataLength);
+ pDataBlock += dwAddDataLength;
+
+ // Now combine the patch data with the original file
+ for(i = 0; i < dwAddDataLength; i++)
+ {
+ if(dwOldOffset < dwOldSize)
+ pbNewData[dwNewOffset] = pbNewData[dwNewOffset] + pbOldData[dwOldOffset];
+
+ dwNewOffset++;
+ dwOldOffset++;
+ }
+
+ // Sanity check
+ if((dwNewOffset + dwMovDataLength) > dwNewSize)
+ {
+ STORM_FREE(pbNewData);
+ 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 += 3;
+ }
+
+ // Free the old file data
+ STORM_FREE(hf->pbFileData);
+
+ // Put the new data to the fil structure
+ hf->pbFileData = pbNewData;
+ hf->cbFileData = dwNewSize;
+ return ERROR_SUCCESS;
+}
+
+
+static int LoadMpqPatch(TMPQFile * hf)
+{
+ TPatchHeader PatchHeader;
+ DWORD dwBytesRead;
+ int nError = ERROR_SUCCESS;
+
+ // Read the patch header
+ SFileReadFile((HANDLE)hf, &PatchHeader, sizeof(TPatchHeader), &dwBytesRead, NULL);
+ if(dwBytesRead != sizeof(TPatchHeader))
+ nError = ERROR_FILE_CORRUPT;
+
+ // 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 != 0x48435450 || PatchHeader.dwMD5 != 0x5f35444d || PatchHeader.dwXFRM != 0x4d524658)
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Read the patch, depending on patch type
+ if(nError == ERROR_SUCCESS)
+ {
+ switch(PatchHeader.dwPatchType)
+ {
+ case 0x59504f43: // 'COPY'
+ nError = LoadMpqPatch_COPY(hf, &PatchHeader);
+ break;
+
+ case 0x30445342: // 'BSD0'
+ nError = LoadMpqPatch_BSD0(hf, &PatchHeader);
+ break;
+
+ default:
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+ }
+
+ return nError;
+}
+
+static int ApplyMpqPatch(
+ TMPQFile * hf,
+ TPatchHeader * pPatchHeader)
+{
+ int nError = ERROR_SUCCESS;
+
+ // Verify the original file before patching
+ if(pPatchHeader->dwSizeBeforePatch != 0)
+ {
+ if(!VerifyDataBlockHash(hf->pbFileData, hf->cbFileData, pPatchHeader->md5_before_patch))
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Apply the patch
+ if(nError == ERROR_SUCCESS)
+ {
+ switch(pPatchHeader->dwPatchType)
+ {
+ case 0x59504f43: // 'COPY'
+ nError = ApplyMpqPatch_COPY(hf, pPatchHeader);
+ break;
+
+ case 0x30445342: // 'BSD0'
+ nError = ApplyMpqPatch_BSD0(hf, pPatchHeader);
+ break;
+
+ default:
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+ }
+
+ // Verify MD5 after patch
+ if(nError == ERROR_SUCCESS && pPatchHeader->dwSizeAfterPatch != 0)
+ {
+ // Verify the patched file
+ if(!VerifyDataBlockHash(hf->pbFileData, hf->cbFileData, pPatchHeader->md5_after_patch))
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions (StormLib internals)
+
+bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize)
+{
+ TPatchHeader * pPatchHeader = (TPatchHeader *)pvData;
+ BLIZZARD_BSDIFF40_FILE DiffFile;
+ DWORD dwPatchType;
+
+ if(cbData >= sizeof(TPatchHeader) + 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;
+}
+
+int PatchFileData(TMPQFile * hf)
+{
+ TMPQFile * hfBase = hf;
+ int nError = ERROR_SUCCESS;
+
+ // Move to the first patch
+ hf = hf->hfPatchFile;
+
+ // Now go through all patches and patch the original data
+ while(hf != NULL)
+ {
+ // This must be true
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
+
+ // Make sure that the patch data is loaded
+ nError = LoadMpqPatch(hf);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Apply the patch
+ nError = ApplyMpqPatch(hfBase, hf->pPatchHeader);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Move to the next patch
+ hf = hf->hfPatchFile;
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+//
+// 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
+//
+
+bool WINAPI SFileOpenPatchArchive(
+ HANDLE hMpq,
+ const TCHAR * szPatchMpqName,
+ const char * szPatchPathPrefix,
+ DWORD dwFlags)
+{
+ TMPQArchive * haPatch;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ HANDLE hPatchMpq = NULL;
+ char szPatchPrefixBuff[MPQ_PATCH_PREFIX_LEN];
+ int nError = ERROR_SUCCESS;
+
+ // Keep compiler happy
+ dwFlags = dwFlags;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(ha))
+ nError = ERROR_INVALID_HANDLE;
+ if(szPatchMpqName == NULL || *szPatchMpqName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // If the user didn't give the patch prefix, get default one
+ if(szPatchPathPrefix != NULL)
+ {
+ // Save length of the patch prefix
+ if(strlen(szPatchPathPrefix) > MPQ_PATCH_PREFIX_LEN - 2)
+ nError = 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(nError == ERROR_SUCCESS)
+ {
+ if(!FileStream_IsReadOnly(ha->pStream))
+ nError = ERROR_ACCESS_DENIED;
+ }
+
+ // Open the archive like it is normal archive
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY, &hPatchMpq))
+ return false;
+ haPatch = (TMPQArchive *)hPatchMpq;
+
+ // Older WoW patches (build 13914) used to have
+ // several language versions in one patch file
+ // Those patches needed to have a path prefix
+ // We can distinguish such patches by not having the (patch_metadata) file
+ if(szPatchPathPrefix == NULL)
+ {
+ if(!SFileHasFile(hPatchMpq, PATCH_METADATA_NAME))
+ {
+ GetDefaultPatchPrefix(FileStream_GetFileName(ha->pStream), szPatchPrefixBuff);
+ szPatchPathPrefix = szPatchPrefixBuff;
+ }
+ }
+
+ // Save the prefix for patch file names.
+ // Make sure that there is backslash after it
+ if(szPatchPathPrefix != NULL && *szPatchPathPrefix != 0)
+ {
+ strcpy(haPatch->szPatchPrefix, szPatchPathPrefix);
+ strcat(haPatch->szPatchPrefix, "\\");
+ haPatch->cchPatchPrefix = strlen(haPatch->szPatchPrefix);
+ }
+
+ // 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;
+ }
+
+ // Should never happen
+ nError = ERROR_CAN_NOT_COMPLETE;
+ }
+
+ SetLastError(nError);
+ return false;
+}
+
+bool WINAPI SFileIsPatchedArchive(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(ha))
+ return false;
+
+ return (ha->haPatch != NULL);
+}