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 int ForceCreatePath(TCHAR * szFullPath) { TCHAR * szPlainName = (TCHAR *)GetPlainFileName(szFullPath) - 1; TCHAR * szPathPart = szFullPath; TCHAR chSaveChar; // Skip disk drive and root directory if(szPathPart[0] != 0 && szPathPart[1] == _T(':')) szPathPart += 3; while(szPathPart <= szPlainName) { // If there is a delimiter, create the path fragment if(szPathPart[0] == _T('\\') || szPathPart[0] == _T('/')) { chSaveChar = szPathPart[0]; szPathPart[0] = 0; CREATE_DIRECTORY(szFullPath); szPathPart[0] = chSaveChar; } // Move to the next character szPathPart++; } return ERROR_SUCCESS; }
static bool CalculateMpqHashSha1(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI, unsigned char * sha1_tail0, unsigned char * sha1_tail1, unsigned char * sha1_tail2) { ULONGLONG BeginBuffer; hash_state sha1_state_temp; hash_state sha1_state; LPBYTE pbDigestBuffer = NULL; // Allocate buffer for creating the MPQ digest. pbDigestBuffer = ALLOCMEM(BYTE, MPQ_DIGEST_UNIT_SIZE); if (pbDigestBuffer == NULL) return false; // Initialize SHA1 state structure sha1_init(&sha1_state); // Calculate begin of data to be hashed BeginBuffer = pSI->BeginMpqData; // Create the digest for (;;) { ULONGLONG BytesRemaining; DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE; // Check the number of bytes remaining BytesRemaining = pSI->EndMpqData - BeginBuffer; if (BytesRemaining < MPQ_DIGEST_UNIT_SIZE) dwToRead = (DWORD)BytesRemaining; if (dwToRead == 0) break; // Read the next chunk if (!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead)) { FREEMEM(pbDigestBuffer); return false; } // Pass the buffer to the hashing function sha1_process(&sha1_state, pbDigestBuffer, dwToRead); // Move pointers BeginBuffer += dwToRead; } // Add all three known tails and generate three hashes memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); sha1_done(&sha1_state_temp, sha1_tail0); memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); AddTailToSha1(&sha1_state_temp, GetPlainFileName(ha->pStream->szFileName)); sha1_done(&sha1_state_temp, sha1_tail1); memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); AddTailToSha1(&sha1_state_temp, "ARCHIVE"); sha1_done(&sha1_state_temp, sha1_tail2); // Finalize the MD5 hash FREEMEM(pbDigestBuffer); return true; }
static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) { TFileEntry * pFileTableEnd; TFileEntry * pFileEntry; TFileEntry * pBaseEntry; const char * szPlainName; char * szLstFileName; size_t cchWorkBuffer = 0x400; size_t cchBaseName; size_t cchDirName; bool bResult = false; // Find a *-md5.lst file in the base archive pBaseEntry = FindMd5ListFile(haBase); if(pBaseEntry == NULL) return false; cchBaseName = strlen(pBaseEntry->szFileName) + 1; // Allocate working buffer for merging LST file szLstFileName = STORM_ALLOC(char, cchWorkBuffer); if(szLstFileName != NULL) { // Find that file in the patch MPQ pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize; for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // Find the "(patch_metadata)" file within that folder // Note that the file is always relatively small and contains the patch prefix // Checking for file size greatly speeds up the search process if(pFileEntry->szFileName && !(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) && (0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40)) { // If the plain file name matches, we need to check its MD5 szPlainName = GetPlainFileName(pFileEntry->szFileName); cchDirName = (size_t)(szPlainName - pFileEntry->szFileName); // The file name must not too long and must be PATCH_METADATA_NAME if((cchDirName + cchBaseName) < cchWorkBuffer && _stricmp(szPlainName, PATCH_METADATA_NAME) == 0) { // Construct the name of the eventuall LST file memcpy(szLstFileName, pFileEntry->szFileName, cchDirName); memcpy(szLstFileName + cchDirName, pBaseEntry->szFileName, cchBaseName); // If there is the "*-md5.lst" file in that directory, we check its MD5 if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5)) { bResult = CreatePatchPrefix(haPatch, pFileEntry->szFileName, szPlainName); break; } } } } // Free the work buffer STORM_FREE(szLstFileName); } return bResult; }
static void GetPlainAnsiFileName( const TCHAR * szFileName, char * szPlainName) { const TCHAR * szPlainNameT = GetPlainFileName(szFileName); // Convert the plain name to ANSI while(*szPlainNameT != 0) *szPlainName++ = (char)*szPlainNameT++; *szPlainName = 0; }
static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) { TMPQNamePrefix * pPatchPrefix; TFileEntry * pBaseEntry; char * szLstFileName; char * szPlainName; size_t cchWorkBuffer = 0x400; bool bResult = false; // First-level patches: Find the same file within the patch archive // and verify by MD5-before-patch if(haBase->haPatch == NULL) { TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize; TFileEntry * pFileEntry; // Allocate working buffer for merging LST file szLstFileName = STORM_ALLOC(char, cchWorkBuffer); if(szLstFileName != NULL) { // Find a *-md5.lst file in the base archive pBaseEntry = FindBaseLstFile(haBase); if(pBaseEntry == NULL) { STORM_FREE(szLstFileName); return false; } // Parse the entire file table for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // Look for "patch_metadata" file if(IsPatchMetadataFile(pFileEntry)) { // Construct the name of the MD5 file strcpy(szLstFileName, pFileEntry->szFileName); szPlainName = (char *)GetPlainFileName(szLstFileName); strcpy(szPlainName, pBaseEntry->szFileName); // Check for matching MD5 file if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5)) { bResult = CreatePatchPrefix(haPatch, szLstFileName, (size_t)(szPlainName - szLstFileName)); break; } } } // Delete the merge buffer STORM_FREE(szLstFileName); } }
static inline bool IsPatchMetadataFile(TFileEntry * pFileEntry) { // The file must ave a name if(pFileEntry->szFileName != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) { // The file must be small if(0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40) { // Compare the plain name return (_stricmp(GetPlainFileName(pFileEntry->szFileName), PATCH_METADATA_NAME) == 0); } } // Not a patch_metadata return false; }
static inline int IsPatchMetadataFile(TFileEntry * pFileEntry) { /* The file must ave a namet */ if(pFileEntry->szFileName != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) { /* The file must be small */ if(0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40) { /* Compare the plain name */ return (strcasecmp(GetPlainFileName(pFileEntry->szFileName), PATCH_METADATA_NAME) == 0); } } /* Not a patch_metadata */ return 0; }
static bool DoMPQSearch_FileEntry( TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha, TMPQHash * pHashEntry, TFileEntry * pFileEntry) { TFileEntry * pPatchEntry; HANDLE hFile = NULL; const char * szFileName; size_t nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0; DWORD dwBlockIndex; char szNameBuff[MAX_PATH]; // 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)) { // if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy")) // DebugBreak(); // Find a patch to this file pPatchEntry = FindPatchEntry(ha, pFileEntry); if(pPatchEntry == NULL) pPatchEntry = pFileEntry; // 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. sprintf(szNameBuff, "File%08u.xxx", (unsigned int)dwBlockIndex); if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile)) { SFileGetFileName(hFile, szNameBuff); szFileName = szNameBuff; SFileCloseFile(hFile); } } // If the file name is still NULL, we cannot include the file to search results if(szFileName != NULL) { // Check the file name against the wildcard if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) { // Fill the found entry. hash entry and block index are taken from the base MPQ lpFindFileData->dwHashIndex = HASH_ENTRY_FREE; lpFindFileData->dwBlockIndex = dwBlockIndex; lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; lpFindFileData->lcLocale = 0; // pPatchEntry->lcLocale; // Fill the filetime lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); // Fill-in the entries from hash table entry, if given if(pHashEntry != NULL) { lpFindFileData->dwHashIndex = (DWORD)(pHashEntry - ha->pHashTable); lpFindFileData->lcLocale = pHashEntry->lcLocale; } // Fill the file name and plain file name StringCopyA(lpFindFileData->cFileName, szFileName + nPrefixLength, MAX_PATH-1); lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName); return true; } } }
// Performs one MPQ search static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) { TMPQArchive * ha = hs->ha; TFileEntry * pFileTableEnd; TFileEntry * pPatchEntry; TFileEntry * pFileEntry; const char * szFileName; HANDLE hFile; char szPseudoName[20]; DWORD dwBlockIndex; size_t nPrefixLength; // Start searching with base MPQ while(ha != NULL) { // Now parse the file entry table in order to get all files. pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; pFileEntry = ha->pFileTable + hs->dwNextIndex; // Get the length of the patch prefix (0 if none) nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0; // Parse the file table while(pFileEntry < pFileTableEnd) { // Increment the next index for subsequent search hs->dwNextIndex++; // Is it a file but not a patch file? if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS) { // 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)) { // if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy")) // DebugBreak(); // Find a patch to this file pPatchEntry = FindPatchEntry(ha, pFileEntry); if(pPatchEntry == NULL) pPatchEntry = pFileEntry; // 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)) { szFileName = (pFileEntry->szFileName != NULL) ? pFileEntry->szFileName : szPseudoName; SFileCloseFile(hFile); } } // If the file name is still NULL, we cannot include the file to search results if(szFileName != NULL) { // Check the file name against the wildcard if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) { // Fill the found entry. hash entry and block index are taken from the base MPQ lpFindFileData->dwHashIndex = pFileEntry->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; } } } } } pFileEntry++; }
// Performs one MPQ search static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) { TMPQArchive * ha = hs->ha; TFileEntry * pFileTableEnd; TFileEntry * pPatchEntry; TFileEntry * pFileEntry; const char * szFileName; HANDLE hFile; char szPseudoName[20]; DWORD dwBlockIndex; size_t nPrefixLength; // Start searching with base MPQ while(ha != NULL) { // Now parse the file entry table in order to get all files. pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; pFileEntry = ha->pFileTable + hs->dwNextIndex; // Get the start and end of the hash table nPrefixLength = strlen(ha->szPatchPrefix); // Parse the file table while(pFileEntry < pFileTableEnd) { // Increment the next index for subsequent search hs->dwNextIndex++; // Is it a file and 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; // 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 index in order to check if the file exists if(SFileOpenFileEx((HANDLE)hs->ha, (char *)(DWORD_PTR)dwBlockIndex, SFILE_OPEN_BY_INDEX, &hFile)) SFileCloseFile(hFile); // If the name was retrieved, use that one. Otherwise, just use generic pseudo-name szFileName = pFileEntry->szFileName; if(szFileName == NULL) { sprintf(szPseudoName, "File%08u.xxx", dwBlockIndex); szFileName = szPseudoName; } } // Check the file name against the wildcard if(CheckWildCard(szFileName, 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; } } } pFileEntry++; }
static int RecryptFileData( TMPQArchive * ha, TMPQFile * hf, const char * szFileName, const char * szNewFileName) { ULONGLONG RawFilePos; TFileEntry * pFileEntry = hf->pFileEntry; DWORD dwBytesToRecrypt = pFileEntry->dwCmpSize; DWORD dwOldKey; DWORD dwNewKey; int nError = ERROR_SUCCESS; // The file must be encrypted assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED); // File decryption key is calculated from the plain name szNewFileName = GetPlainFileName(szNewFileName); szFileName = GetPlainFileName(szFileName); // Calculate both file keys dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); dwNewKey = DecryptFileKey(szNewFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); // Incase the keys are equal, don't recrypt the file if(dwNewKey == dwOldKey) return ERROR_SUCCESS; hf->dwFileKey = dwOldKey; // Calculate the raw position of the file in the archive hf->MpqFilePos = pFileEntry->ByteOffset; hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; // Allocate buffer for file transfer nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; // Also allocate buffer for sector offsets // Note: Don't load sector checksums, we don't need to recrypt them nError = AllocateSectorOffsets(hf, true); if(nError != ERROR_SUCCESS) return nError; // If we have sector offsets, recrypt these as well if(hf->SectorOffsets != NULL) { // Allocate secondary buffer for sectors copy DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD)); DWORD dwSectorOffsLen = hf->SectorOffsets[0]; if(SectorOffsetsCopy == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Recrypt the array of sector offsets memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen); EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwNewKey - 1); BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen); // Write the recrypted array back if(!FileStream_Write(ha->pStream, &hf->RawFilePos, SectorOffsetsCopy, dwSectorOffsLen)) nError = GetLastError(); STORM_FREE(SectorOffsetsCopy); } // Now we have to recrypt all file sectors. We do it without // recompression, because recompression is not necessary in this case if(nError == ERROR_SUCCESS) { for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++) { DWORD dwRawDataInSector = hf->dwSectorSize; DWORD dwRawByteOffset = dwSector * hf->dwSectorSize; // Last sector: If there is not enough bytes remaining in the file, cut the raw size if(dwRawDataInSector > dwBytesToRecrypt) dwRawDataInSector = dwBytesToRecrypt; // Fix the raw data length if the file is compressed if(hf->SectorOffsets != NULL) { dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector]; dwRawByteOffset = hf->SectorOffsets[dwSector]; } // Calculate the raw file offset of the file sector RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset); // Read the file sector if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector)) { nError = GetLastError(); break; } // If necessary, re-encrypt the sector // Note: Recompression is not necessary here. Unlike encryption, // the compression does not depend on the position of the file in MPQ. BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwOldKey + dwSector); EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwNewKey + dwSector); BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); // Write the sector back if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector)) { nError = GetLastError(); break; } // Decrement number of bytes remaining dwBytesToRecrypt -= hf->dwSectorSize; } } return nError; }
// Performs one MPQ search static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) { TMPQArchive * ha = hs->ha; TFileEntry * pFileTableEnd; TFileEntry * pFileEntry; const char * szFileName; char szPseudoName[20]; DWORD dwBlockIndex; size_t nPrefixLength; // Do that for all files in the patch chain while(ha != NULL) { // Now parse the file entry table in order to get all files. pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; pFileEntry = ha->pFileTable + hs->dwNextIndex; // Get the start and end of the hash table nPrefixLength = strlen(ha->szPatchPrefix); // Parse the file table while(pFileEntry < pFileTableEnd) { // Increment the next index for subsequent search hs->dwNextIndex++; // Does the block exist ? if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) { // 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) { HANDLE hFile; // Open the file by index in order to check if the file exists if(SFileOpenFileEx((HANDLE)hs->ha, (char *)(DWORD_PTR)dwBlockIndex, SFILE_OPEN_BY_INDEX, &hFile)) SFileCloseFile(hFile); // If the name was retrieved, use that one. Otherwise, just use generic pseudo-name szFileName = pFileEntry->szFileName; if(szFileName == NULL) { sprintf(szPseudoName, "File%08u.xxx", dwBlockIndex); szFileName = szPseudoName; } } // If we are already in the patch MPQ, we skip all files // that don't have the appropriate patch prefix and are patch files if(ha->haBase != NULL) { // If the file has different patch prefix, don't report it if(nPrefixLength != 0 && _strnicmp(szFileName, ha->szPatchPrefix, nPrefixLength)) goto __SkipThisFile; // // We need to properly handle the following case: // // 1) Base MPQ file doesn't contain the desired file // 2) First patch MPQ contains the file with MPQ_FILE_PATCH_FILE // 3) Second patch contains full version of the file (MPQ_FILE_PATCH_FILE is not set) // if(IsBaseFileMissing(ha, szFileName, szFileName + nPrefixLength, pFileEntry->lcLocale)) goto __SkipThisFile; } // Check the file name. if(CheckWildCard(szFileName, hs->szSearchMask)) { // Fill the found entry lpFindFileData->dwHashIndex = pFileEntry->dwHashIndex; lpFindFileData->dwBlockIndex = dwBlockIndex; lpFindFileData->dwFileSize = pFileEntry->dwFileSize; lpFindFileData->dwFileFlags = pFileEntry->dwFlags; lpFindFileData->dwCompSize = pFileEntry->dwCmpSize; lpFindFileData->lcLocale = pFileEntry->lcLocale; // Fill the filetime lpFindFileData->dwFileTimeHi = (DWORD)(pFileEntry->FileTime >> 32); lpFindFileData->dwFileTimeLo = (DWORD)(pFileEntry->FileTime); // Fill the file name and plain file name strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength); lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName); return ERROR_SUCCESS; } } // Move to the next file entry __SkipThisFile: pFileEntry++; }