aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2014-10-07 11:23:52 +0200
committerLadislav Zezula <ladislav.zezula@avg.com>2014-10-07 11:23:52 +0200
commit2d3b3e11c032728a74dbc65a4a3941714b870a0f (patch)
treecdeed734ed6b3882e37df2ea87e34ec776607e75 /src
parent1aa906cf21accf9d82b39340c1a7b5b54a9440e1 (diff)
+ Improved patching process so that it works for Starcraft II patches
+ Removed memory leaks in file search
Diffstat (limited to 'src')
-rw-r--r--src/SBaseCommon.cpp17
-rw-r--r--src/SFileAttributes.cpp5
-rw-r--r--src/SFileFindFile.cpp114
-rw-r--r--src/SFileListFile.cpp4
-rw-r--r--src/SFileOpenArchive.cpp8
-rw-r--r--src/SFileOpenFileEx.cpp36
-rw-r--r--src/SFilePatchArchives.cpp480
-rw-r--r--src/SFileVerify.cpp9
-rw-r--r--src/StormCommon.h14
-rw-r--r--src/StormLib.h21
10 files changed, 479 insertions, 229 deletions
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index c8d4ba6..a3b2a38 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -1410,10 +1410,10 @@ void FreeFileHandle(TMPQFile *& hf)
FreeFileHandle(hf->hfPatch);
// Then free all buffers allocated in the file structure
- if(hf->pPatchHeader != NULL)
- STORM_FREE(hf->pPatchHeader);
if(hf->pbFileData != NULL)
STORM_FREE(hf->pbFileData);
+ if(hf->pPatchHeader != NULL)
+ STORM_FREE(hf->pPatchHeader);
if(hf->pPatchInfo != NULL)
STORM_FREE(hf->pPatchInfo);
if(hf->SectorOffsets != NULL)
@@ -1438,6 +1438,10 @@ void FreeArchiveHandle(TMPQArchive *& ha)
if(ha->haPatch != NULL)
FreeArchiveHandle(ha->haPatch);
+ // Free the patch prefix, if any
+ if(ha->pPatchPrefix != NULL)
+ STORM_FREE(ha->pPatchPrefix);
+
// Close the file stream
FileStream_Close(ha->pStream);
ha->pStream = NULL;
@@ -1517,12 +1521,9 @@ bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex)
bool IsValidMD5(LPBYTE pbMd5)
{
- BYTE BitSummary = 0;
-
- // The MD5 is considered invalid of it is zeroed
- BitSummary |= pbMd5[0x00] | pbMd5[0x01] | pbMd5[0x02] | pbMd5[0x03] | pbMd5[0x04] | pbMd5[0x05] | pbMd5[0x06] | pbMd5[0x07];
- BitSummary |= pbMd5[0x08] | pbMd5[0x09] | pbMd5[0x0A] | pbMd5[0x0B] | pbMd5[0x0C] | pbMd5[0x0D] | pbMd5[0x0E] | pbMd5[0x0F];
- return (BitSummary != 0);
+ LPDWORD Md5 = (LPDWORD)pbMd5;
+
+ return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
}
bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp
index 0278426..0c5dd06 100644
--- a/src/SFileAttributes.cpp
+++ b/src/SFileAttributes.cpp
@@ -24,6 +24,11 @@ typedef struct _MPQ_ATTRIBUTES_HEADER
// Followed by an array of file times
// Followed by an array of MD5
// Followed by an array of patch bits
+
+ // Note: The MD5 in (attributes), if present, is a hash of the entire file.
+ // In case the file is an incremental patch, it contains MD5 of the file
+ // after being patched.
+
} MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER;
//-----------------------------------------------------------------------------
diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp
index 42f5380..2809b68 100644
--- a/src/SFileFindFile.cpp
+++ b/src/SFileFindFile.cpp
@@ -167,14 +167,14 @@ static bool FileWasFoundBefore(
{
// If we are in patch MPQ, we check if patch prefix matches
// and then trim the patch prefix
- if(ha->cchPatchPrefix != 0)
+ if(ha->pPatchPrefix != NULL)
{
// If the patch prefix doesn't fit, we pretend that the file
// was there before and it will be skipped
- if(_strnicmp(szRealFileName, ha->szPatchPrefix, ha->cchPatchPrefix))
+ if(_strnicmp(szRealFileName, ha->pPatchPrefix->szPatchPrefix, ha->pPatchPrefix->nLength))
return true;
- szRealFileName += ha->cchPatchPrefix;
+ szRealFileName += ha->pPatchPrefix->nLength;
}
// Calculate the hash to the table
@@ -213,6 +213,14 @@ static bool FileWasFoundBefore(
return false;
}
+static inline bool FileEntryIsInvalid(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry)
+{
+ // Spazzler3 protector: Some files are clearly wrong
+ return ((ha->dwFlags & MPQ_FLAG_MALFORMED) && (pFileEntry->dwCmpSize & 0xFFFF0000) >= 0x7FFF0000);
+}
+
static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
{
TFileEntry * pPatchEntry = NULL;
@@ -225,9 +233,11 @@ static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
{
// Move to the patch archive
ha = ha->haPatch;
+ szFileName[0] = 0;
// Prepare the prefix for the file name
- strcpy(szFileName, ha->szPatchPrefix);
+ if(ha->pPatchPrefix != NULL)
+ strcpy(szFileName, ha->pPatchPrefix->szPatchPrefix);
strcat(szFileName, pFileEntry->szFileName);
// Try to find the file there
@@ -261,7 +271,7 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
pFileEntry = ha->pFileTable + hs->dwNextIndex;
// Get the length of the patch prefix (0 if none)
- nPrefixLength = strlen(ha->szPatchPrefix);
+ nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0;
// Parse the file table
while(pFileEntry < pFileTableEnd)
@@ -269,56 +279,60 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
// Increment the next index for subsequent search
hs->dwNextIndex++;
- // Is it a file and not a patch file?
+ // Is it a file but not a patch file?
if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS)
{
- // Now we have to check if this file was not enumerated before
- if(!FileWasFoundBefore(ha, hs, pFileEntry))
- {
- // Find a patch to this file
- pPatchEntry = FindPatchEntry(ha, pFileEntry);
- if(pPatchEntry == NULL)
- pPatchEntry = pFileEntry;
+ // Spazzler3 protector: Some files are clearly wrong
+ if(!FileEntryIsInvalid(ha, pFileEntry))
+ {
+ // Now we have to check if this file was not enumerated before
+ if(!FileWasFoundBefore(ha, hs, pFileEntry))
+ {
+ // Find a patch to this file
+ pPatchEntry = FindPatchEntry(ha, pFileEntry);
+ if(pPatchEntry == NULL)
+ pPatchEntry = pFileEntry;
- // Prepare the block index
- dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
+ // Prepare the block index
+ dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
- // Get the file name. If it's not known, we will create pseudo-name
- szFileName = pFileEntry->szFileName;
- if(szFileName == NULL)
- {
- // Open the file by its pseudo-name.
- // This also generates the file name with a proper extension
- sprintf(szPseudoName, "File%08u.xxx", (unsigned int)dwBlockIndex);
- if(SFileOpenFileEx((HANDLE)hs->ha, szPseudoName, SFILE_OPEN_BASE_FILE, &hFile))
+ // Get the file name. If it's not known, we will create pseudo-name
+ szFileName = pFileEntry->szFileName;
+ if(szFileName == NULL)
{
- szFileName = (pFileEntry->szFileName != NULL) ? pFileEntry->szFileName : szPseudoName;
- SFileCloseFile(hFile);
+ // Open the file by its pseudo-name.
+ // This also generates the file name with a proper extension
+ sprintf(szPseudoName, "File%08u.xxx", (unsigned int)dwBlockIndex);
+ if(SFileOpenFileEx((HANDLE)hs->ha, szPseudoName, SFILE_OPEN_BASE_FILE, &hFile))
+ {
+ szFileName = (pFileEntry->szFileName != NULL) ? pFileEntry->szFileName : szPseudoName;
+ SFileCloseFile(hFile);
+ }
}
- }
- // If the file name is still NULL, we cannot include the file to the search
- if(szFileName != NULL)
- {
- // Check the file name against the wildcard
- if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
+ // If the file name is still NULL, we cannot include the file to the search
+ if(szFileName != NULL)
{
- // Fill the found entry
- lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex;
- lpFindFileData->dwBlockIndex = dwBlockIndex;
- lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
- lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
- lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
- lpFindFileData->lcLocale = pPatchEntry->lcLocale;
-
- // Fill the filetime
- lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
- lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
-
- // Fill the file name and plain file name
- strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength);
- lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName);
- return ERROR_SUCCESS;
+ // Check the file name against the wildcard
+ if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
+ {
+ // Fill the found entry
+ lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex;
+ lpFindFileData->dwBlockIndex = dwBlockIndex;
+ lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
+ lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
+ lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
+ lpFindFileData->lcLocale = pPatchEntry->lcLocale;
+
+ // Fill the filetime
+ lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
+ lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
+
+ // Fill the file name and plain file name
+ strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength);
+ lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName);
+ return ERROR_SUCCESS;
+ }
}
}
}
@@ -327,6 +341,12 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
pFileEntry++;
}
+ // If there is no more patches in the chain, stop it.
+ // This also keeps hs->ha non-NULL, which is required
+ // for freeing the handle later
+ if(ha->haPatch == NULL)
+ break;
+
// Move to the next patch in the patch chain
hs->ha = ha = ha->haPatch;
hs->dwNextIndex = 0;
diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp
index 3428bcc..f4069c0 100644
--- a/src/SFileListFile.cpp
+++ b/src/SFileListFile.cpp
@@ -189,6 +189,10 @@ static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, int nMaxC
if(*pCache->pPos == '~')
szExtraString = szLine;
+ // Remember that last occurence of a slash or backslash
+// if(*pCache->pPos == '\\' || *pCache->pPos == '/')
+// szPlainName = szLine + 1;
+
// Copy the character
*szLine++ = *pCache->pPos++;
}
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index 15c395c..19d18c2 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -211,9 +211,11 @@ bool WINAPI SFileOpenArchive(
ha->dwFlags |= (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0;
// Also remember if we shall check sector CRCs when reading file
- if(dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC)
- ha->dwFlags |= MPQ_FLAG_CHECK_SECTOR_CRC;
-
+ ha->dwFlags |= (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ? MPQ_FLAG_CHECK_SECTOR_CRC : 0;
+
+ // Also remember if this MPQ is a patch
+ ha->dwFlags |= (dwFlags & MPQ_OPEN_PATCH) ? MPQ_FLAG_PATCH : 0;
+
// Limit the header searching to about 130 MB of data
if(EndOfSearch > 0x08000000)
EndOfSearch = 0x08000000;
diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp
index e994414..3a9976e 100644
--- a/src/SFileOpenFileEx.cpp
+++ b/src/SFileOpenFileEx.cpp
@@ -18,18 +18,23 @@
static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
{
- if(ha->cchPatchPrefix != 0)
+ TMPQNamePrefix * pPrefix;
+
+ // Are there patches in the current MPQ?
+ if(ha->dwFlags & MPQ_FLAG_PATCH)
{
- // Copy the patch prefix
- memcpy(szBuffer, ha->szPatchPrefix, ha->cchPatchPrefix);
-
+ // The patch prefix must be already known here
+ assert(ha->pPatchPrefix != NULL);
+ pPrefix = ha->pPatchPrefix;
+
// The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY"
- // We need to remove the "Oldworld\\" prefix
+ // We need to remove the "OldWorld\\" prefix
if(!_strnicmp(szFileName, "OldWorld\\", 9))
szFileName += 9;
- // Copy the rest of the name
- strcpy(szBuffer + ha->cchPatchPrefix, szFileName);
+ // Create the file name from the known patch entry
+ memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength);
+ strcpy(szBuffer + pPrefix->nLength, szFileName);
szFileName = szBuffer;
}
@@ -67,7 +72,7 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile)
return false;
}
-bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HANDLE * phFile)
+bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * phFile)
{
TMPQArchive * haBase = NULL;
TMPQArchive * ha = (TMPQArchive *)hMpq;
@@ -76,17 +81,14 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN
TMPQFile * hfBase = NULL; // Pointer to base open file
TMPQFile * hf = NULL;
HANDLE hPatchFile;
- char szPrefixBuffer[MAX_PATH];
-
- // Keep this flag here for future updates
- dwReserved = dwReserved;
+ char szNameBuffer[MAX_PATH];
// First of all, find the latest archive where the file is in base version
// (i.e. where the original, unpatched version of the file exists)
while(ha != NULL)
{
// If the file is there, then we remember the archive
- pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), 0);
+ pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0);
if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
haBase = ha;
@@ -102,7 +104,7 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN
}
// Now open the base file
- if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
+ if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
{
// The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0);
@@ -112,7 +114,7 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN
for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch)
{
// Prepare the file name with a correct prefix
- if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
+ if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
{
// Remember the new version
hfPatch = (TMPQFile *)hPatchFile;
@@ -130,7 +132,7 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN
// Give the updated base MPQ
if(phFile != NULL)
*phFile = (HANDLE)hfBase;
- return true;
+ return (hfBase != NULL);
}
/*****************************************************************************/
@@ -328,7 +330,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch
}
else
{
- return OpenPatchedFile(hMpq, szFileName, 0, phFile);
+ return OpenPatchedFile(hMpq, szFileName, phFile);
}
}
break;
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp
index 4b49c59..99dec1e 100644
--- a/src/SFilePatchArchives.cpp
+++ b/src/SFilePatchArchives.cpp
@@ -15,6 +15,10 @@
//-----------------------------------------------------------------------------
// Local structures
+#define PATCH_SIGNATURE_HEADER 0x48435450
+#define PATCH_SIGNATURE_MD5 0x5f35444d
+#define PATCH_SIGNATURE_XFRM 0x4d524658
+
typedef struct _BLIZZARD_BSDIFF40_FILE
{
ULONGLONG Signature;
@@ -26,36 +30,6 @@ typedef struct _BLIZZARD_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 = GetPlainFileName(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;
@@ -93,7 +67,7 @@ static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE p
}
}
-static int LoadMpqPatch_COPY(TMPQFile * hf, TPatchHeader * pPatchHeader)
+static int LoadFilePatch_COPY(TMPQFile * hf, TPatchHeader * pPatchHeader)
{
int nError = ERROR_SUCCESS;
@@ -119,7 +93,7 @@ static int LoadMpqPatch_COPY(TMPQFile * hf, TPatchHeader * pPatchHeader)
return nError;
}
-static int LoadMpqPatch_BSD0(TMPQFile * hf, TPatchHeader * pPatchHeader)
+static int LoadFilePatch_BSD0(TMPQFile * hf, TPatchHeader * pPatchHeader)
{
LPBYTE pbDecompressed = NULL;
LPBYTE pbCompressed = NULL;
@@ -177,32 +151,23 @@ static int LoadMpqPatch_BSD0(TMPQFile * hf, TPatchHeader * pPatchHeader)
return nError;
}
-static int ApplyMpqPatch_COPY(
+static int ApplyFilePatch_COPY(
+ TMPQFile * hfFrom,
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;
+ // Sanity checks
+ assert(hf->cbFileData == (pPatchHeader->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER));
+ assert(hf->pbFileData != NULL);
+ hfFrom = hfFrom;
// 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;
+ memcpy(hf->pbFileData, (LPBYTE)pPatchHeader + sizeof(TPatchHeader), hf->cbFileData);
return ERROR_SUCCESS;
}
-static int ApplyMpqPatch_BSD0(
+static int ApplyFilePatch_BSD0(
+ TMPQFile * hfFrom,
TMPQFile * hf,
TPatchHeader * pPatchHeader)
{
@@ -211,12 +176,13 @@ static int ApplyMpqPatch_BSD0(
LPBYTE pbPatchData = (LPBYTE)pPatchHeader + sizeof(TPatchHeader);
LPBYTE pDataBlock;
LPBYTE pExtraBlock;
- LPBYTE pbNewData = NULL;
- LPBYTE pbOldData = (LPBYTE)hf->pbFileData;
+ LPBYTE pbOldData = hfFrom->pbFileData;
+ LPBYTE pbNewData = hf->pbFileData;
+ DWORD dwCombineSize;
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
+ DWORD dwOldSize = hfFrom->cbFileData; // File size before patch
// Get pointer to the patch header
// Format of BSDIFF header corresponds to original BSDIFF, which is:
@@ -244,11 +210,6 @@ static int ApplyMpqPatch_BSD0(
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)
{
@@ -259,31 +220,26 @@ static int ApplyMpqPatch_BSD0(
// 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];
+ // Get the longest block that we can combine
+ dwCombineSize = ((dwOldOffset + dwAddDataLength) >= dwOldSize) ? (dwOldSize - dwOldOffset) : dwAddDataLength;
- dwNewOffset++;
- dwOldOffset++;
- }
+ // 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)
- {
- STORM_FREE(pbNewData);
return ERROR_FILE_CORRUPT;
- }
// Copy the data from the extra block in BSDIFF patch
memcpy(pbNewData + dwNewOffset, pExtraBlock, dwMovDataLength);
@@ -297,17 +253,12 @@ static int ApplyMpqPatch_BSD0(
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;
+ // Success
return ERROR_SUCCESS;
}
-static int LoadMpqPatch(TMPQFile * hf)
+static int LoadFilePatch(TMPQFile * hf)
{
TPatchHeader PatchHeader;
DWORD dwBytesRead;
@@ -327,7 +278,7 @@ static int LoadMpqPatch(TMPQFile * hf)
PatchHeader.dwXfrmBlockSize = BSWAP_INT32_UNSIGNED(PatchHeader.dwXfrmBlockSize);
PatchHeader.dwPatchType = BSWAP_INT32_UNSIGNED(PatchHeader.dwPatchType);
- if(PatchHeader.dwSignature != 0x48435450 || PatchHeader.dwMD5 != 0x5f35444d || PatchHeader.dwXFRM != 0x4d524658)
+ if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM)
nError = ERROR_FILE_CORRUPT;
}
@@ -337,11 +288,11 @@ static int LoadMpqPatch(TMPQFile * hf)
switch(PatchHeader.dwPatchType)
{
case 0x59504f43: // 'COPY'
- nError = LoadMpqPatch_COPY(hf, &PatchHeader);
+ nError = LoadFilePatch_COPY(hf, &PatchHeader);
break;
case 0x30445342: // 'BSD0'
- nError = LoadMpqPatch_BSD0(hf, &PatchHeader);
+ nError = LoadFilePatch_BSD0(hf, &PatchHeader);
break;
default:
@@ -353,18 +304,31 @@ static int LoadMpqPatch(TMPQFile * hf)
return nError;
}
-static int ApplyMpqPatch(
- TMPQFile * hf,
- TPatchHeader * pPatchHeader)
+static int ApplyFilePatch(
+ TMPQFile * hfBase, // The file in the base MPQ
+ TMPQFile * hfPrev, // The file in the previous MPQ
+ TMPQFile * hf)
{
+ TPatchHeader * pPatchHeader = hf->pPatchHeader;
+ TMPQFile * hfFrom = NULL;
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;
- }
+ // 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 == hf)
+ 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;
// Apply the patch
if(nError == ERROR_SUCCESS)
@@ -372,11 +336,11 @@ static int ApplyMpqPatch(
switch(pPatchHeader->dwPatchType)
{
case 0x59504f43: // 'COPY'
- nError = ApplyMpqPatch_COPY(hf, pPatchHeader);
+ nError = ApplyFilePatch_COPY(hfFrom, hf, pPatchHeader);
break;
case 0x30445342: // 'BSD0'
- nError = ApplyMpqPatch_BSD0(hf, pPatchHeader);
+ nError = ApplyFilePatch_BSD0(hfFrom, hf, pPatchHeader);
break;
default:
@@ -391,11 +355,250 @@ static int ApplyMpqPatch(
// Verify the patched file
if(!VerifyDataBlockHash(hf->pbFileData, hf->cbFileData, pPatchHeader->md5_after_patch))
nError = ERROR_FILE_CORRUPT;
+
+ // Copy the MD5 of the new block
+ memcpy(hf->FileDataMD5, pPatchHeader->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 bool LoadPatchHeader(TMPQArchive * ha, const char * szFileName, TPatchHeader * pPatchHeader)
+{
+ HANDLE hFile = NULL;
+ DWORD dwTransferred = 0;
+
+ // Load the patch header
+ if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
+ {
+ SFileReadFile(hFile, pPatchHeader, sizeof(TPatchHeader), &dwTransferred, NULL);
+ SFileCloseFile(hFile);
+ }
+
+ // Convert the patch header to the proper endian
+ BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6);
+
+ // Check the patch header
+ return (dwTransferred == sizeof(TPatchHeader) && pPatchHeader->dwSignature == PATCH_SIGNATURE_HEADER);
+}
+
+static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, const char * szPrefixEnd)
+{
+ TMPQNamePrefix * pNewPrefix;
+ size_t nLength;
+
+ // Create the patch prefix
+ nLength = (szPrefixEnd - szFileName);
+ pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength);
+ if(pNewPrefix != NULL)
+ {
+ // Fill the name prefix
+ pNewPrefix->nLength = nLength;
+ pNewPrefix->szPatchPrefix[0] = 0;
+
+ // Fill the name prefix. Also add the backslash
+ if(szFileName && nLength)
+ {
+ memcpy(pNewPrefix->szPatchPrefix, szFileName, nLength);
+ pNewPrefix->szPatchPrefix[nLength] = 0;
+ }
+ }
+
+ ha->pPatchPrefix = pNewPrefix;
+ return (pNewPrefix != NULL);
+}
+
+static bool FindPatchPrefix_OldWorld(
+ TMPQArchive * haBase,
+ TMPQArchive * haPatch)
+{
+ TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
+ TFileEntry * pPatchEntry;
+ TFileEntry * pBaseEntry;
+ TPatchHeader PatchHeader;
+ char * szPatchName;
+ char szBaseName[MAX_PATH + 0x20];
+ size_t nBaseLength = 9;
+
+ // Prepare the base prefix
+ memcpy(szBaseName, "OldWorld\\", nBaseLength);
+
+ // Check every file entry in the patch archive
+ for(pPatchEntry = haPatch->pFileTable; pPatchEntry < pFileTableEnd; pPatchEntry++)
+ {
+ // If the file is a patch file, there must be a base file in the base MPQ
+ if((pPatchEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (pPatchEntry->szFileName != NULL))
+ {
+ // Cut one subdirectory from the patch name
+ szPatchName = strchr(pPatchEntry->szFileName, '\\');
+ if(szPatchName != NULL)
+ {
+ // Construct the base name
+ strcpy(szBaseName + nBaseLength, szPatchName + 1);
+
+ // Check if the name is in the base archive as-is.
+ // Do not use locale search, patched archives no longer use locale ID.
+ pBaseEntry = GetFileEntryAny(haBase, szBaseName);
+ if(pBaseEntry != NULL && IsValidMD5(pBaseEntry->md5))
+ {
+ // Read the patch header from the file
+ if(LoadPatchHeader(haPatch, pPatchEntry->szFileName, &PatchHeader))
+ {
+ // Compare the file MD5's. If they match,
+ // it means that we have found the proper prefix
+ if(!memcmp(pBaseEntry->md5, PatchHeader.md5_before_patch, MD5_DIGEST_SIZE))
+ {
+ return CreatePatchPrefix(haPatch, pPatchEntry->szFileName, szPatchName + 1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static bool FindPatchPrefix_Normal(
+ TMPQArchive * haBase,
+ TMPQArchive * haPatch)
+{
+ TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
+ TFileEntry * pPatchEntry;
+ TFileEntry * pBaseEntry;
+ TPatchHeader PatchHeader;
+ char * szPatchName;
+ char * szNextName;
+
+ // Check every file entry in the patch archive
+ for(pPatchEntry = haPatch->pFileTable; pPatchEntry < pFileTableEnd; pPatchEntry++)
+ {
+ // If the file is a patch file, there must be a base file in the base MPQ
+ if((pPatchEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (pPatchEntry->szFileName != NULL))
+ {
+ // Set the start of the patch name
+ szPatchName = pPatchEntry->szFileName;
+
+ // Only verify names that have at least one subdirectory
+ while((szNextName = strchr(szPatchName, '\\')) != NULL)
+ {
+ // Check if the name is in the base archive as-is.
+ // Do not use locale search, patched archives no longer use locale ID.
+ pBaseEntry = GetFileEntryAny(haBase, szPatchName);
+ if(pBaseEntry != NULL && IsValidMD5(pBaseEntry->md5))
+ {
+ // Read the patch header from the file
+ if(LoadPatchHeader(haPatch, pPatchEntry->szFileName, &PatchHeader))
+ {
+ // Compare the file MD5's. If they match,
+ // it means that we have found the proper prefix
+ if(!memcmp(pBaseEntry->md5, PatchHeader.md5_before_patch, MD5_DIGEST_SIZE))
+ {
+ return CreatePatchPrefix(haPatch, pPatchEntry->szFileName, szPatchName);
+ }
+ }
+ }
+
+ // Move one directory fuhrter
+ szPatchName = szNextName + 1;
+ }
+ }
+ }
+
+ // Not found a match
+ return false;
+}
+
+static bool FindPatchPrefix_ByFileName(
+ TMPQArchive * haBase,
+ TMPQArchive * haPatch)
+{
+ TFileEntry * pFileTableEnd = haBase->pFileTable + haBase->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ const char * szBasePrefix;
+ char * szLangName;
+ char * szTailName;
+ char * szLstName;
+ size_t nLength;
+ char szPrefix[6];
+
+ // Check every file entry for "*-md5.lst".
+ // Go backwards, as the entry is usually at the end of the file table
+ for(pFileEntry = pFileTableEnd - 1; pFileEntry >= haBase->pFileTable; pFileEntry--)
+ {
+ // The file name must be valid
+ if(pFileEntry->szFileName != NULL)
+ {
+ // Get the name and length
+ szLstName = pFileEntry->szFileName;
+ nLength = strlen(szLstName);
+
+ // Get the tail and lang name
+ szLangName = szLstName + nLength - 13;
+ szTailName = szLstName + nLength - 8;
+
+ // Check for the tail name
+ if(szTailName > szLstName && !_stricmp(szTailName, "-md5.lst"))
+ {
+ // Check the language name
+ if(szLangName > szLstName && szLangName[0] == '-' && szLangName[5] == '-')
+ {
+ memcpy(szPrefix, szLangName + 1, 4);
+ szPrefix[4] = '\\';
+ return CreatePatchPrefix(haPatch, szPrefix, szPrefix + 5);
+ }
+
+ // Stop searching
+ break;
+ }
+ }
+ }
+
+ // Create the patch name with "base\\"
+ szBasePrefix = "base\\";
+ return CreatePatchPrefix(haPatch, szBasePrefix, szBasePrefix + 5);
+}
+
+static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const char * szPatchPathPrefix)
+{
+ // If the patch prefix was explicitly entered, we use that one
+ if(szPatchPathPrefix != NULL)
+ return CreatePatchPrefix(haPatch, szPatchPathPrefix, szPatchPathPrefix + strlen(szPatchPathPrefix));
+
+ // An old base MPQ from WoW-Cataclysm required to add "OldWorld\\"
+ // as base file name prefix. Try to match
+ // Match: OldWorld\Cameras\FlybyDraenei.m2 <==> base\Cameras\FlybyDraenei.m2
+ if(GetFileEntryAny(haBase, "OldWorld-md5.lst") != NULL)
+ return FindPatchPrefix_OldWorld(haBase, haPatch);
+
+ // Find the patch so that file MD5 will match
+ // Note: This must be done before checking PATCH_METADATA_NAME in the root of the archive
+ // Match: LocalizedData\GameHotkeys.txt <==> Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt
+ if(FindPatchPrefix_Normal(haBase, haPatch))
+ return true;
+
+ // If the PATCH_METADATA_NAME is in the root, the patch prefix is empty
+ // Match: Creature\Ragnaros2\Ragnaros2.M2 <==> Creature\Ragnaros2\Ragnaros2.M2
+ if(GetFileEntryAny(haPatch, PATCH_METADATA_NAME) != NULL)
+ return CreatePatchPrefix(haPatch, NULL, NULL);
+
+ // Create the patch prefix by the base MPQ file name
+ return FindPatchPrefix_ByFileName(haBase, haPatch);
+}
+
//-----------------------------------------------------------------------------
// Public functions (StormLib internals)
@@ -424,34 +627,75 @@ bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatche
return false;
}
+//
+// 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)
+//
+
int PatchFileData(TMPQFile * hf)
{
TMPQFile * hfBase = hf;
+ TMPQFile * hfPrev = hf;
int nError = ERROR_SUCCESS;
- // Move to the first patch
- hf = hf->hfPatch;
+ // 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);
- // Now go through all patches and patch the original data
- while(hf != NULL)
+ // Apply all patches
+ for(hf = hf->hfPatch; hf != NULL; hf = hf->hfPatch)
{
// This must be true
assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
// Make sure that the patch data is loaded
- nError = LoadMpqPatch(hf);
+ nError = LoadFilePatch(hf);
if(nError != ERROR_SUCCESS)
break;
// Apply the patch
- nError = ApplyMpqPatch(hfBase, hf->pPatchHeader);
+ nError = ApplyFilePatch(hfBase, hfPrev, hf);
if(nError != ERROR_SUCCESS)
break;
- // Move to the next patch
- hf = hf->hfPatch;
+ // Only keep base file version and previous version
+ if(hfPrev != hfBase)
+ FreePatchData(hfPrev);
+
+ // Is this the last patch in the chain?
+ if(hf->hfPatch == NULL)
+ break;
+ hfPrev = hf;
}
+ // When done, we need to rewrite the base file data
+ // with the last of the patch chain
+ 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;
+ }
return nError;
}
@@ -485,7 +729,6 @@ bool WINAPI SFileOpenPatchArchive(
TMPQArchive * haPatch;
TMPQArchive * ha = (TMPQArchive *)hMpq;
HANDLE hPatchMpq = NULL;
- char szPatchPrefixBuff[MPQ_PATCH_PREFIX_LEN];
int nError = ERROR_SUCCESS;
// Keep compiler happy
@@ -497,14 +740,6 @@ bool WINAPI SFileOpenPatchArchive(
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
//
@@ -526,31 +761,12 @@ bool WINAPI SFileOpenPatchArchive(
// Open the archive like it is normal archive
if(nError == ERROR_SUCCESS)
{
- if(!SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY, &hPatchMpq))
+ if(!SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY | MPQ_OPEN_PATCH, &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);
- }
+ // We need to remember the proper patch prefix to match names of patched files
+ FindPatchPrefix(ha, (TMPQArchive *)hPatchMpq, szPatchPathPrefix);
// Now add the patch archive to the list of patches to the original MPQ
while(ha != NULL)
diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp
index a506fc3..4267016 100644
--- a/src/SFileVerify.cpp
+++ b/src/SFileVerify.cpp
@@ -113,13 +113,6 @@ static void memrev(unsigned char *buf, size_t count)
}
}
-static bool is_valid_md5(void * pvMd5)
-{
- LPDWORD Md5 = (LPDWORD)pvMd5;
-
- return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
-}
-
static bool decode_base64_key(const char * szKeyBase64, rsa_key * key)
{
unsigned char decoded_key[0x200];
@@ -685,7 +678,7 @@ static DWORD VerifyFile(
md5_done(&md5_state, md5);
// Only check the MD5 if it is valid
- if(is_valid_md5(pFileMd5))
+ if(IsValidMD5(pFileMd5))
{
dwVerifyResult |= VERIFY_FILE_HAS_MD5;
if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE))
diff --git a/src/StormCommon.h b/src/StormCommon.h
index 25cc981..dbd1d0d 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -105,17 +105,17 @@ typedef struct _MPQ_SIGNATURE_INFO
// - Memory freeing function doesn't have to test the pointer to NULL
//
-#if defined(_MSC_VER) && defined(_DEBUG)
-
-#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
-#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
-
-#else
+//#if defined(_MSC_VER) && defined(_DEBUG)
+//
+//#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
+//#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
+//
+//#else
#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type))
#define STORM_FREE(ptr) free(ptr)
-#endif
+//#endif
//-----------------------------------------------------------------------------
// StormLib internal global variables
diff --git a/src/StormLib.h b/src/StormLib.h
index ce1c02f..3ea3e19 100644
--- a/src/StormLib.h
+++ b/src/StormLib.h
@@ -162,8 +162,6 @@ extern "C" {
#define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure
-#define MPQ_PATCH_PREFIX_LEN 0x20 // Maximum length of the patch prefix
-
// Values for SFileOpenArchive
#define SFILE_OPEN_HARD_DISK_FILE 2 // Open the archive on HDD
#define SFILE_OPEN_CDROM_FILE 3 // Open the archive only if it is on CDROM
@@ -183,6 +181,7 @@ extern "C" {
#define MPQ_FLAG_ATTRIBUTES_INVALID 0x00000040 // If set, it means that the (attributes) has been invalidated
#define MPQ_FLAG_SIGNATURE_INVALID 0x00000080 // If set, it means that the (signature) has been invalidated
#define MPQ_FLAG_SAVING_TABLES 0x00000100 // If set, we are saving MPQ internal files and MPQ tables
+#define MPQ_FLAG_PATCH 0x00000200 // If set, this MPQ is a patch archive
// Values for TMPQArchive::dwSubType
#define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games)
@@ -287,6 +286,7 @@ extern "C" {
#define MPQ_OPEN_NO_HEADER_SEARCH 0x00040000 // Don't search for the MPQ header past the begin of the file
#define MPQ_OPEN_FORCE_MPQ_V1 0x00080000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header
#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00100000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file
+#define MPQ_OPEN_PATCH 0x00200000 // This archive is a patch MPQ. Used internally.
#define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY
// Flags for SFileCreateArchive
@@ -821,9 +821,16 @@ typedef struct _TMPQBetTable
DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2
DWORD dwBitCount_NameHash2; // Effective size of the NameHash2
DWORD dwEntryCount; // Number of entries
- DWORD dwFlagCount; // Number of fil flags in pFileFlags
+ DWORD dwFlagCount; // Number of file flags in pFileFlags
} TMPQBetTable;
+// Structure for patch prefix
+typedef struct _TMPQNamePrefix
+{
+ size_t nLength; // Length of this patch prefix. Can be 0
+ char szPatchPrefix[1]; // Patch name prefix (variable length). If not empty, it always starts with backslash.
+} TMPQNamePrefix;
+
// Archive handle structure
typedef struct _TMPQArchive
{
@@ -834,8 +841,7 @@ typedef struct _TMPQArchive
struct _TMPQArchive * haPatch; // Pointer to patch archive, if any
struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any
- char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs
- size_t cchPatchPrefix; // Length of the patch prefix, in characters
+ TMPQNamePrefix * pPatchPrefix; // Patch prefix to precede names of patch files
TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file)
TMPQHeader * pHeader; // MPQ file header
@@ -883,8 +889,9 @@ typedef struct _TMPQFile
struct _TMPQFile * hfPatch; // Pointer to opened patch file
TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file
- LPBYTE pbFileData; // Loaded and patched file data. Only used if the file is a patch file
- DWORD cbFileData; // Size of loaded patched data
+ LPBYTE pbFileData; // Data of the file (single unit files, patched files)
+ DWORD cbFileData; // Size of file data
+ BYTE FileDataMD5[MD5_DIGEST_SIZE];// MD5 hash of the loaded file data. Used during patch process
TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table
DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files.