diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2014-10-07 11:23:52 +0200 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2014-10-07 11:23:52 +0200 |
commit | 2d3b3e11c032728a74dbc65a4a3941714b870a0f (patch) | |
tree | cdeed734ed6b3882e37df2ea87e34ec776607e75 /src | |
parent | 1aa906cf21accf9d82b39340c1a7b5b54a9440e1 (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.cpp | 17 | ||||
-rw-r--r-- | src/SFileAttributes.cpp | 5 | ||||
-rw-r--r-- | src/SFileFindFile.cpp | 114 | ||||
-rw-r--r-- | src/SFileListFile.cpp | 4 | ||||
-rw-r--r-- | src/SFileOpenArchive.cpp | 8 | ||||
-rw-r--r-- | src/SFileOpenFileEx.cpp | 36 | ||||
-rw-r--r-- | src/SFilePatchArchives.cpp | 480 | ||||
-rw-r--r-- | src/SFileVerify.cpp | 9 | ||||
-rw-r--r-- | src/StormCommon.h | 14 | ||||
-rw-r--r-- | src/StormLib.h | 21 |
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. |