mirror of
https://github.com/mangosfour/server.git
synced 2025-12-24 10:37:02 +00:00
587 lines
18 KiB
C++
587 lines
18 KiB
C++
/*****************************************************************************/
|
|
/* 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);
|
|
}
|