static TFileEntry * FindBaseLstFile(TMPQArchive * ha) { TFileEntry * pFileEntry; const char * szLanguage; char szFileName[0x40]; // Prepare the file name tenplate memcpy(szFileName, "####-md5.lst", 13); // Try all languages for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage++) { // Copy the language name szFileName[0] = szLanguage[0]; szFileName[1] = szLanguage[1]; szFileName[2] = szLanguage[2]; szFileName[3] = szLanguage[3]; // Check whether this file exists pFileEntry = GetFileEntryLocale(ha, szFileName, 0); if(pFileEntry != NULL) return pFileEntry; } return NULL; }
static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pMpqInfo) { TFileEntry * pFileEntry; const char * szLanguage = LanguageList; char szFileName[0x40]; // Iterate through all localized languages while(pMpqInfo->szNameTemplate != NULL) { // Iterate through all languages for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage += 4) { // Construct the file name memcpy(szFileName, pMpqInfo->szNameTemplate, pMpqInfo->nLength); szFileName[pMpqInfo->nLangOffset + 0] = szLanguage[0]; szFileName[pMpqInfo->nLangOffset + 1] = szLanguage[1]; szFileName[pMpqInfo->nLangOffset + 2] = szLanguage[2]; szFileName[pMpqInfo->nLangOffset + 3] = szLanguage[3]; // Append the suffix memcpy(szFileName + pMpqInfo->nLength, "-md5.lst", 9); // Check whether the name exists pFileEntry = GetFileEntryLocale(ha, szFileName, 0); if(pFileEntry != NULL) return szLanguage; } // Move to the next language name pMpqInfo++; } // Not found return NULL; }
// Used in SFileGetFileInfo bool QueryMpqSignatureInfo( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI) { TFileEntry * pFileEntry; ULONGLONG ExtraBytes; DWORD dwFileSize; // Make sure it's all zeroed memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO)); // Calculate the range of the MPQ CalculateArchiveRange(ha, pSI); // If there is "(signature)" file in the MPQ, it has a weak signature pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Calculate the begin and end of the signature file itself pSI->BeginExclude = ha->MpqPos + pFileEntry->ByteOffset; pSI->EndExclude = pSI->BeginExclude + pFileEntry->dwCmpSize; dwFileSize = (DWORD)(pSI->EndExclude - pSI->BeginExclude); // Does the signature have proper size? if(dwFileSize == MPQ_SIGNATURE_FILE_SIZE) { // Read the weak signature if(!FileStream_Read(ha->pStream, &pSI->BeginExclude, pSI->Signature, dwFileSize)) return false; pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK; pSI->cbSignatureSize = dwFileSize; return true; } } // If there is extra bytes beyond the end of the archive, // it's the strong signature ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) { // Read the strong signature if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) return false; // Check the signature header "NGIS" if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') return false; pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG; return true; } // Succeeded, but no known signature found return true; }
bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) { TMPQArchive * ha = (TMPQArchive *)hMpq; TFileEntry * pFileEntry; DWORD dwFlagsToCheck = MPQ_FILE_EXISTS; DWORD dwFileIndex = 0; char szPatchFileName[MAX_PATH]; bool bIsPseudoName; int nError = ERROR_SUCCESS; if(!IsValidMpqHandle(ha)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) nError = ERROR_INVALID_PARAMETER; // Prepare the file opening if(nError == ERROR_SUCCESS) { // Different processing for pseudo-names bIsPseudoName = IsPseudoFileName(szFileName, &dwFileIndex); // Walk through the MPQ and all patches while(ha != NULL) { // Verify presence of the file pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, szFileName, lcFileLocale) : GetFileEntryByIndex(ha, dwFileIndex); // Verify the file flags if(pFileEntry != NULL && (pFileEntry->dwFlags & dwFlagsToCheck) == MPQ_FILE_EXISTS) return true; // If this is patched archive, go to the patch dwFlagsToCheck = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE; ha = ha->haPatch; // Prepare the patched file name if(ha != NULL) { strcpy(szPatchFileName, ha->szPatchPrefix); strcat(szPatchFileName, szFileName); szFileName = szPatchFileName; } } // Not found, sorry nError = ERROR_FILE_NOT_FOUND; } // Cleanup SetLastError(nError); return false; }
static bool CheckAndCreatePatchPrefix(TMPQArchive * ha, const char * szPatchPrefix, size_t nLength) { char szTempName[MAX_SC2_PATCH_PREFIX + 0x41]; bool bResult = false; // Prepare the patch file name if(nLength > MAX_SC2_PATCH_PREFIX) return false; // Prepare the patched file name memcpy(szTempName, szPatchPrefix, nLength); memcpy(&szTempName[nLength], "\\(patch_metadata)", 18); // Verifywhether that file exists if(GetFileEntryLocale(ha, szTempName, 0) != NULL) bResult = CreatePatchPrefix(ha, szPatchPrefix, nLength); return bResult; }
bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) { TMPQArchive * ha = (TMPQArchive *)hMpq; int nError = ERROR_SUCCESS; if (!IsValidMpqHandle(ha)) nError = ERROR_INVALID_HANDLE; if (*szFileName == 0) nError = ERROR_INVALID_PARAMETER; // Prepare the file opening if (nError == ERROR_SUCCESS) { if (GetFileEntryLocale(ha, szFileName, lcFileLocale) == NULL) { nError = ERROR_FILE_NOT_FOUND; } } // Cleanup if (nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; TFileEntry * pFileEntry = NULL; TMPQFile * hf = NULL; DWORD dwBlockIndex = 0; // Found table index int nError = ERROR_SUCCESS; // Don't accept NULL pointer to file handle if (phFile == NULL) nError = ERROR_INVALID_PARAMETER; // Prepare the file opening if (nError == ERROR_SUCCESS) { switch(dwSearchScope) { case SFILE_OPEN_PATCHED_FILE: // We want to open the updated version of the file return OpenPatchedFile(hMpq, szFileName, 0, phFile); case SFILE_OPEN_FROM_MPQ: if (!IsValidMpqHandle(ha)) { nError = ERROR_INVALID_HANDLE; break; } if (szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } // First of all, check the name as-is pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if (pFileEntry != NULL) break; // If the file doesn't exist in the MPQ, check file pseudo-name ("FileXXXXXXXX.ext") if (!IsPseudoFileName(szFileName, &dwBlockIndex)) { nError = ERROR_FILE_NOT_FOUND; break; } // Set the file name to the file index and fall through szFileName = (const char *)(DWORD_PTR)dwBlockIndex; dwSearchScope = SFILE_OPEN_BY_INDEX; // No break here, fall through. case SFILE_OPEN_BY_INDEX: if (!IsValidMpqHandle(ha)) { nError = ERROR_INVALID_HANDLE; break; } // Set handle size to be sizeof(TMPQFile) + length of FileXXXXXXXX.xxx pFileEntry = GetFileEntryByIndex(ha, (DWORD)(DWORD_PTR)szFileName); if (pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; break; case SFILE_OPEN_ANY_LOCALE: // This open option is reserved for opening MPQ internal listfile. // No argument validation. Tries to open file with neutral locale first, // then any other available. dwSearchScope = SFILE_OPEN_FROM_MPQ; pFileEntry = GetFileEntryAny(ha, szFileName); if (pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; break; case SFILE_OPEN_LOCAL_FILE: if (szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } return OpenLocalFile(szFileName, phFile); default: // Don't accept any other value nError = ERROR_INVALID_PARAMETER; break; } // Quick return if something failed if (nError != ERROR_SUCCESS) { SetLastError(nError); return false; } } // Test if the file was not already deleted. if (nError == ERROR_SUCCESS) { if ((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) nError = ERROR_FILE_NOT_FOUND; if (pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS) nError = ERROR_NOT_SUPPORTED; } // Allocate file handle if (nError == ERROR_SUCCESS) { if ((hf = ALLOCMEM(TMPQFile, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize file handle if (nError == ERROR_SUCCESS) { memset(hf, 0, sizeof(TMPQFile)); hf->pFileEntry = pFileEntry; hf->dwMagic = ID_MPQ_FILE; hf->ha = ha; hf->MpqFilePos = pFileEntry->ByteOffset; hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; hf->dwDataSize = pFileEntry->dwFileSize; hf->dwHashIndex = pFileEntry->dwHashIndex; hf->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); // If the MPQ has sector CRC enabled, enable if for the file if (ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC) hf->bCheckSectorCRCs = true; // Decrypt file key. Cannot be used if the file is given by index if (dwSearchScope == SFILE_OPEN_FROM_MPQ) { if (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { hf->dwFileKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); } } else { // If the file is encrypted and not compressed, we cannot detect the file key if (!SFileGetFileName(hf, NULL)) nError = GetLastError(); } } // If the file is actually a patch file, we have to load the patch file header if (nError == ERROR_SUCCESS && pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { assert(hf->PatchInfo == NULL); nError = AllocatePatchInfo(hf, true); } // Cleanup if (nError != ERROR_SUCCESS) { SetLastError(nError); FreeMPQFile(hf); } *phFile = hf; return (nError == ERROR_SUCCESS); }
// Verifies raw data of the archive Only works for MPQs version 4 or newer int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * szFileName) { TMPQArchive * ha = (TMPQArchive *)hMpq; TFileEntry * pFileEntry; TMPQHeader * pHeader; // Verify input parameters if(!IsValidMpqHandle(ha)) return ERROR_INVALID_PARAMETER; pHeader = ha->pHeader; // If the archive doesn't have raw data MD5, report it as OK if(pHeader->dwRawChunkSize == 0) return ERROR_SUCCESS; // If we have to verify MPQ header, do it switch(dwWhatToVerify) { case SFILE_VERIFY_MPQ_HEADER: // Only if the header is of version 4 or newer if(pHeader->dwHeaderSize >= (MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE)) return VerifyRawMpqData(ha, 0, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE); return ERROR_SUCCESS; case SFILE_VERIFY_HET_TABLE: // Only if we have HET table if(pHeader->HetTablePos64 && pHeader->HetTableSize64) return VerifyRawMpqData(ha, pHeader->HetTablePos64, (DWORD)pHeader->HetTableSize64); return ERROR_SUCCESS; case SFILE_VERIFY_BET_TABLE: // Only if we have BET table if(pHeader->BetTablePos64 && pHeader->BetTableSize64) return VerifyRawMpqData(ha, pHeader->BetTablePos64, (DWORD)pHeader->BetTableSize64); return ERROR_SUCCESS; case SFILE_VERIFY_HASH_TABLE: // Hash table is not protected by MD5 return ERROR_SUCCESS; case SFILE_VERIFY_BLOCK_TABLE: // Block table is not protected by MD5 return ERROR_SUCCESS; case SFILE_VERIFY_HIBLOCK_TABLE: // It is unknown if the hi-block table is protected my MD5 or not. return ERROR_SUCCESS; case SFILE_VERIFY_FILE: // Verify parameters if(szFileName == NULL || *szFileName == 0) return ERROR_INVALID_PARAMETER; // Get the offset of a file pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if(pFileEntry == NULL) return ERROR_FILE_NOT_FOUND; return VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize); } return ERROR_INVALID_PARAMETER; }
bool WINAPI SFileOpenArchive( const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq) { TMPQUserData * pUserData; TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle TFileEntry * pFileEntry; ULONGLONG FileSize = 0; // Size of the file LPBYTE pbHeaderBuffer = NULL; // Buffer for searching MPQ header DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK); bool bIsWarcraft3Map = false; int nError = ERROR_SUCCESS; // Verify the parameters if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // One time initialization of MPQ cryptography InitializeMpqCryptography(); dwPriority = dwPriority; // If not forcing MPQ v 1.0, also use file bitmap dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP; // Open the MPQ archive file pStream = FileStream_OpenFile(szMpqName, dwStreamFlags); if(pStream == NULL) return false; // Check the file size. There must be at least 0x20 bytes if(nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, &FileSize); if(FileSize < MPQ_HEADER_SIZE_V1) nError = ERROR_BAD_FORMAT; } // Allocate the MPQhandle if(nError == ERROR_SUCCESS) { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Allocate buffer for searching MPQ header if(nError == ERROR_SUCCESS) { pbHeaderBuffer = STORM_ALLOC(BYTE, HEADER_SEARCH_BUFFER_SIZE); if(pbHeaderBuffer == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Find the position of MPQ header if(nError == ERROR_SUCCESS) { ULONGLONG SearchOffset = 0; ULONGLONG EndOfSearch = FileSize; DWORD dwStrmFlags = 0; DWORD dwHeaderSize; DWORD dwHeaderID; bool bSearchComplete = false; memset(ha, 0, sizeof(TMPQArchive)); ha->pfnHashString = HashStringSlash; ha->pStream = pStream; pStream = NULL; // Set the archive read only if the stream is read-only FileStream_GetFlags(ha->pStream, &dwStrmFlags); ha->dwFlags |= (dwStrmFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0; // Also remember if we shall check sector CRCs when reading file 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; // Find the offset of MPQ header within the file while(bSearchComplete == false && SearchOffset < EndOfSearch) { // Always read at least 0x1000 bytes for performance. // This is what Storm.dll (2002) does. DWORD dwBytesAvailable = HEADER_SEARCH_BUFFER_SIZE; DWORD dwInBufferOffset = 0; // Cut the bytes available, if needed if((FileSize - SearchOffset) < HEADER_SEARCH_BUFFER_SIZE) dwBytesAvailable = (DWORD)(FileSize - SearchOffset); // Read the eventual MPQ header if(!FileStream_Read(ha->pStream, &SearchOffset, pbHeaderBuffer, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. if(SearchOffset == 0) { if(IsAviFile((DWORD *)pbHeaderBuffer)) { nError = ERROR_AVI_FILE; break; } bIsWarcraft3Map = IsWarcraft3Map((DWORD *)pbHeaderBuffer); } // Search the header buffer while(dwInBufferOffset < dwBytesAvailable) { // Copy the data from the potential header buffer to the MPQ header memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData)); // If there is the MPQ user data, process it // Note that Warcraft III does not check for user data, which is abused by many map protectors dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]); if(bIsWarcraft3Map == false && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA) { // Verify if this looks like a valid user data pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); if(pUserData != NULL) { // Fill the user data header ha->UserDataPos = SearchOffset; ha->pUserData = &ha->UserData; memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); // Continue searching from that position SearchOffset += ha->pUserData->dwHeaderOffs; break; } } } // There must be MPQ header signature. Note that STORM.dll from Warcraft III actually // tests the MPQ header size. It must be at least 0x20 bytes in order to load it // Abused by Spazzler Map protector. Note that the size check is not present // in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway. dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]); if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1) { // Now convert the header to version 4 nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags); bSearchComplete = true; break; } // Check for MPK archives (Longwu Online - MPQ fork) if(dwHeaderID == ID_MPK) { // Now convert the MPK header to MPQ Header version 4 nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags); bSearchComplete = true; break; } // If searching for the MPQ header is disabled, return an error if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH) { nError = ERROR_NOT_SUPPORTED; bSearchComplete = true; break; } // Move the pointers SearchOffset += 0x200; dwInBufferOffset += 0x200; } } // Did we identify one of the supported headers? if(nError == ERROR_SUCCESS) { // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) ha->UserDataPos = SearchOffset; // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchOffset; ha->FileSize = FileSize; // Sector size must be nonzero. if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0) nError = ERROR_BAD_FORMAT; } } // Fix table positions according to format if(nError == ERROR_SUCCESS) { // Dump the header // DumpMpqHeader(ha->pHeader); // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, // and ignores the MPQ format version as well. The trick is to // fake MPQ format 2, with an improper hi-word position of hash table and block table // We can overcome such protectors by forcing opening the archive as MPQ v 1.0 if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) { ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; ha->dwFlags |= MPQ_FLAG_READ_ONLY; ha->pUserData = NULL; } // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Remember whether whis is a map for Warcraft III if(bIsWarcraft3Map) ha->dwFlags |= MPQ_FLAG_WAR3_MAP; // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); // Verify if any of the tables doesn't start beyond the end of the file nError = VerifyMpqTablePositions(ha, FileSize); } // Read the hash table. Ignore the result, as hash table is no longer required // Read HET table. Ignore the result, as HET table is no longer required if(nError == ERROR_SUCCESS) { nError = LoadAnyHashTable(ha); } // Now, build the file table. It will be built by combining // the block table, BET table, hi-block table, (attributes) and (listfile). if(nError == ERROR_SUCCESS) { nError = BuildFileTable(ha); } // Load the internal listfile and include it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { // Quick check for (listfile) pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Ignore result of the operation. (listfile) is optional. SFileAddListFile((HANDLE)ha, NULL); ha->dwFileFlags1 = pFileEntry->dwFlags; } } // Load the "(attributes)" file and merge it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0 && (ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0) { // Quick check for (attributes) pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Ignore result of the operation. (attributes) is optional. SAttrLoadAttributes(ha); ha->dwFileFlags2 = pFileEntry->dwFlags; } } // Remember whether the archive has weak signature. Only for MPQs format 1.0. if(nError == ERROR_SUCCESS) { // Quick check for (signature) pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Just remember that the archive is weak-signed assert((pFileEntry->dwFlags & MPQ_FILE_EXISTS) != 0); ha->dwFileFlags3 = pFileEntry->dwFlags; } // Finally, set the MPQ_FLAG_READ_ONLY if the MPQ was found malformed ha->dwFlags |= (ha->dwFlags & MPQ_FLAG_MALFORMED) ? MPQ_FLAG_READ_ONLY : 0; } // Cleanup and exit if(nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeArchiveHandle(ha); SetLastError(nError); ha = NULL; } // Free the header buffer if(pbHeaderBuffer != NULL) STORM_FREE(pbHeaderBuffer); if(phMpq != NULL) *phMpq = ha; return (nError == ERROR_SUCCESS); }
static DWORD VerifyFile( HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5, DWORD dwFlags) { hash_state md5_state; unsigned char * pFileMd5; unsigned char md5[MD5_DIGEST_SIZE]; TFileEntry * pFileEntry; TMPQFile * hf; BYTE Buffer[0x1000]; HANDLE hFile = NULL; DWORD dwVerifyResult = 0; DWORD dwSearchScope = SFILE_OPEN_FROM_MPQ; DWORD dwTotalBytes = 0; DWORD dwBytesRead; DWORD dwCrc32 = 0; // Fix the open type for patched archives if(SFileIsPatchedArchive(hMpq)) dwSearchScope = SFILE_OPEN_PATCHED_FILE; // If we have to verify raw data MD5, do it before file open if(dwFlags & SFILE_VERIFY_RAW_MD5) { TMPQArchive * ha = (TMPQArchive *)hMpq; // Parse the base MPQ and all patches while(ha != NULL) { // Does the archive have support for raw MD5? if(ha->pHeader->dwRawChunkSize != 0) { // The file has raw MD5 if the archive supports it dwVerifyResult |= VERIFY_FILE_HAS_RAW_MD5; // Find file entry for the file pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if(pFileEntry != NULL) { // If the file's raw MD5 doesn't match, don't bother with more checks if(VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize) != ERROR_SUCCESS) return dwVerifyResult | VERIFY_FILE_RAW_MD5_ERROR; } } // Move to the next patch ha = ha->haPatch; } } // Attempt to open the file if(SFileOpenFileEx(hMpq, szFileName, dwSearchScope, &hFile)) { // Get the file size hf = (TMPQFile *)hFile; pFileEntry = hf->pFileEntry; dwTotalBytes = SFileGetFileSize(hFile, NULL); // Initialize the CRC32 and MD5 contexts md5_init(&md5_state); dwCrc32 = crc32(0, Z_NULL, 0); // Also turn on sector checksum verification if(dwFlags & SFILE_VERIFY_SECTOR_CRC) hf->bCheckSectorCRCs = true; // Go through entire file and update both CRC32 and MD5 for(;;) { // Read data from file SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL); if(dwBytesRead == 0) { if(GetLastError() == ERROR_CHECKSUM_ERROR) dwVerifyResult |= VERIFY_FILE_SECTOR_CRC_ERROR; break; } // Update CRC32 value if(dwFlags & SFILE_VERIFY_FILE_CRC) dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead); // Update MD5 value if(dwFlags & SFILE_VERIFY_FILE_MD5) md5_process(&md5_state, Buffer, dwBytesRead); // Decrement the total size dwTotalBytes -= dwBytesRead; } // If the file has sector checksums, indicate it in the flags if(dwFlags & SFILE_VERIFY_SECTOR_CRC) { if((hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->SectorChksums != NULL && hf->SectorChksums[0] != 0) dwVerifyResult |= VERIFY_FILE_HAS_SECTOR_CRC; } // Check if the entire file has been read // No point in checking CRC32 and MD5 if not // Skip checksum checks if the file has patches if(dwTotalBytes == 0) { // Check CRC32 and MD5 only if there is no patches if(hf->hfPatchFile == NULL) { // Check if the CRC32 matches. if(dwFlags & SFILE_VERIFY_FILE_CRC) { // Only check the CRC32 if it is valid if(pFileEntry->dwCrc32 != 0) { dwVerifyResult |= VERIFY_FILE_HAS_CHECKSUM; if(dwCrc32 != pFileEntry->dwCrc32) dwVerifyResult |= VERIFY_FILE_CHECKSUM_ERROR; } } // Check if MD5 matches if(dwFlags & SFILE_VERIFY_FILE_MD5) { // Patch files have their MD5 saved in the patch info pFileMd5 = (hf->pPatchInfo != NULL) ? hf->pPatchInfo->md5 : pFileEntry->md5; md5_done(&md5_state, md5); // Only check the MD5 if it is valid if(is_valid_md5(pFileMd5)) { dwVerifyResult |= VERIFY_FILE_HAS_MD5; if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE)) dwVerifyResult |= VERIFY_FILE_MD5_ERROR; } } } else { // Patched files are MD5-checked automatically dwVerifyResult |= VERIFY_FILE_HAS_MD5; } } else { dwVerifyResult |= VERIFY_READ_ERROR; } SFileCloseFile(hFile); } else { // Remember that the file couldn't be open dwVerifyResult |= VERIFY_OPEN_ERROR; } // If the caller required CRC32 and/or MD5, give it to him if(pdwCrc32 != NULL) *pdwCrc32 = dwCrc32; if(pMD5 != NULL) memcpy(pMD5, md5, MD5_DIGEST_SIZE); return dwVerifyResult; }
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; TFileEntry * pFileEntry = NULL; TMPQFile * hf = NULL; DWORD dwFileIndex = 0; bool bOpenByIndex = false; int nError = ERROR_SUCCESS; // Don't accept NULL pointer to file handle if(phFile == NULL) nError = ERROR_INVALID_PARAMETER; // Prepare the file opening if(nError == ERROR_SUCCESS) { switch(dwSearchScope) { case SFILE_OPEN_FROM_MPQ: case SFILE_OPEN_BASE_FILE: if(!IsValidMpqHandle(ha)) { nError = ERROR_INVALID_HANDLE; break; } if(szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } // Check the pseudo-file name if(IsPseudoFileName(szFileName, &dwFileIndex)) { pFileEntry = GetFileEntryByIndex(ha, dwFileIndex); bOpenByIndex = true; if(pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; } else { // If this MPQ is a patched archive, open the file as patched if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE) { // Otherwise, open the file from *this* MPQ pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if(pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; } else { return OpenPatchedFile(hMpq, szFileName, 0, phFile); } } break; case SFILE_OPEN_ANY_LOCALE: // This open option is reserved for opening MPQ internal listfile. // No argument validation. Tries to open file with neutral locale first, // then any other available. pFileEntry = GetFileEntryAny(ha, szFileName); if(pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; break; case SFILE_OPEN_LOCAL_FILE: if(szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } return OpenLocalFile(szFileName, phFile); default: // Don't accept any other value nError = ERROR_INVALID_PARAMETER; break; } // Quick return if something failed if(nError != ERROR_SUCCESS) { SetLastError(nError); return false; } } // Test if the file was not already deleted. if(nError == ERROR_SUCCESS) { if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) nError = ERROR_FILE_NOT_FOUND; if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS) nError = ERROR_NOT_SUPPORTED; } // Allocate file handle if(nError == ERROR_SUCCESS) { if((hf = STORM_ALLOC(TMPQFile, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize file handle if(nError == ERROR_SUCCESS) { memset(hf, 0, sizeof(TMPQFile)); hf->pFileEntry = pFileEntry; hf->dwMagic = ID_MPQ_FILE; hf->ha = ha; hf->MpqFilePos = pFileEntry->ByteOffset; hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; hf->dwDataSize = pFileEntry->dwFileSize; // If the MPQ has sector CRC enabled, enable if for the file if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC) hf->bCheckSectorCRCs = true; // If we know the real file name, copy it to the file entry if(bOpenByIndex == false) { // If there is no file name yet, allocate it AllocateFileName(pFileEntry, szFileName); // If the file is encrypted, we should detect the file key if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { hf->dwFileKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); } } else { // Try to auto-detect the file name if(!SFileGetFileName(hf, NULL)) nError = GetLastError(); } } // If the file is actually a patch file, we have to load the patch file header if(nError == ERROR_SUCCESS && pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { assert(hf->pPatchInfo == NULL); nError = AllocatePatchInfo(hf, true); } // Cleanup if(nError != ERROR_SUCCESS) { SetLastError(nError); FreeMPQFile(hf); } *phFile = hf; return (nError == ERROR_SUCCESS); }
bool WINAPI SFileOpenArchive( const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq) { TMPQUserData * pUserData; TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle TFileEntry * pFileEntry; ULONGLONG FileSize = 0; // Size of the file int nError = ERROR_SUCCESS; // Verify the parameters if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) nError = ERROR_INVALID_PARAMETER; // One time initialization of MPQ cryptography InitializeMpqCryptography(); dwPriority = dwPriority; // Open the MPQ archive file if(nError == ERROR_SUCCESS) { DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK); // If not forcing MPQ v 1.0, also use file bitmap dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP; // Initialize the stream pStream = FileStream_OpenFile(szMpqName, dwStreamFlags); if(pStream == NULL) nError = GetLastError(); } // Check the file size. There must be at least 0x20 bytes if(nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, &FileSize); if(FileSize < MPQ_HEADER_SIZE_V1) nError = ERROR_BAD_FORMAT; } // Allocate the MPQhandle if(nError == ERROR_SUCCESS) { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize handle structure and allocate structure for MPQ header if(nError == ERROR_SUCCESS) { ULONGLONG SearchOffset = 0; DWORD dwStreamFlags = 0; DWORD dwHeaderSize; DWORD dwHeaderID; memset(ha, 0, sizeof(TMPQArchive)); ha->pfnHashString = HashString; ha->pStream = pStream; pStream = NULL; // Set the archive read only if the stream is read-only FileStream_GetFlags(ha->pStream, &dwStreamFlags); 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; // Find the offset of MPQ header within the file while(SearchOffset < FileSize) { DWORD dwBytesAvailable = MPQ_HEADER_SIZE_V4; // Cut the bytes available, if needed if((FileSize - SearchOffset) < MPQ_HEADER_SIZE_V4) dwBytesAvailable = (DWORD)(FileSize - SearchOffset); // Read the eventual MPQ header if(!FileStream_Read(ha->pStream, &SearchOffset, ha->HeaderData, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. if(SearchOffset == 0 && IsAviFile(ha->HeaderData)) { nError = ERROR_AVI_FILE; break; } // If there is the MPQ user data signature, process it dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]); if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { // Verify if this looks like a valid user data pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); if(pUserData != NULL) { // Fill the user data header ha->UserDataPos = SearchOffset; ha->pUserData = &ha->UserData; memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); // Continue searching from that position SearchOffset += ha->pUserData->dwHeaderOffs; continue; } } // There must be MPQ header signature. Note that STORM.dll from Warcraft III actually // tests the MPQ header size. It must be at least 0x20 bytes in order to load it // Abused by Spazzler Map protector. Note that the size check is not present // in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway. dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]); if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1) { // Now convert the header to version 4 nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags); break; } // Check for MPK archives (Longwu Online - MPQ fork) if(dwHeaderID == ID_MPK) { // Now convert the MPK header to MPQ Header version 4 nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags); break; } // If searching for the MPQ header is disabled, return an error if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH) { nError = ERROR_NOT_SUPPORTED; break; } // Move to the next possible offset SearchOffset += 0x200; } // Did we identify one of the supported headers? if(nError == ERROR_SUCCESS) { // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) ha->UserDataPos = SearchOffset; // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchOffset; // Sector size must be nonzero. if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0) nError = ERROR_BAD_FORMAT; } } // Fix table positions according to format if(nError == ERROR_SUCCESS) { // Dump the header // DumpMpqHeader(ha->pHeader); // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, // and ignores the MPQ format version as well. The trick is to // fake MPQ format 2, with an improper hi-word position of hash table and block table // We can overcome such protectors by forcing opening the archive as MPQ v 1.0 if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) { ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; ha->dwFlags |= MPQ_FLAG_READ_ONLY; ha->pUserData = NULL; } // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); // Verify if any of the tables doesn't start beyond the end of the file nError = VerifyMpqTablePositions(ha, FileSize); } // Read the hash table. Ignore the result, as hash table is no longer required // Read HET table. Ignore the result, as HET table is no longer required if(nError == ERROR_SUCCESS) { nError = LoadAnyHashTable(ha); } // Now, build the file table. It will be built by combining // the block table, BET table, hi-block table, (attributes) and (listfile). if(nError == ERROR_SUCCESS) { nError = BuildFileTable(ha); } // Verify the file table, if no kind of malformation was detected if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_MALFORMED) == 0) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; ULONGLONG RawFilePos; // Parse all file entries for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // If that file entry is valid, check the file position if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) { // Get the 64-bit file position, // relative to the begin of the file RawFilePos = ha->MpqPos + pFileEntry->ByteOffset; // Begin of the file must be within range if(RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } // End of the file must be within range RawFilePos += pFileEntry->dwCmpSize; if(RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } } } } // Load the internal listfile and include it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { // Save the flags for (listfile) pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Ignore result of the operation. (listfile) is optional. SFileAddListFile((HANDLE)ha, NULL); ha->dwFileFlags1 = pFileEntry->dwFlags; } } // Load the "(attributes)" file and merge it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0) { // Save the flags for (attributes) pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Ignore result of the operation. (attributes) is optional. SAttrLoadAttributes(ha); ha->dwFileFlags2 = pFileEntry->dwFlags; } } // Cleanup and exit if(nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeMPQArchive(ha); SetLastError(nError); ha = NULL; } *phMpq = ha; return (nError == ERROR_SUCCESS); }
// Renames the file within the archive. bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName) { TMPQArchive * ha = IsValidMpqHandle(hMpq); TMPQFile * hf; int nError = ERROR_SUCCESS; // Test the valid parameters if(ha == NULL) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0) nError = ERROR_INVALID_PARAMETER; if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName)) nError = ERROR_INTERNAL_FILE; // Do not allow to rename files in MPQ open for read only if(nError == ERROR_SUCCESS) { if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; } // Open the new file. If exists, we don't allow rename operation if(nError == ERROR_SUCCESS) { if(GetFileEntryLocale(ha, szNewFileName, lcFileLocale) != NULL) nError = ERROR_ALREADY_EXISTS; } // Open the file from the MPQ if(nError == ERROR_SUCCESS) { // Attempt to open the file if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf)) { ULONGLONG RawDataOffs; TFileEntry * pFileEntry = hf->pFileEntry; // Invalidate the entries for internal files InvalidateInternalFiles(ha); // Rename the file entry in the table nError = RenameFileEntry(ha, hf, szNewFileName); // If the file is encrypted, we have to re-crypt the file content // with the new decryption key if((nError == ERROR_SUCCESS) && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) { // Recrypt the file data in the MPQ nError = RecryptFileData(ha, hf, szFileName, szNewFileName); // Update the MD5 of the raw block if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0) { RawDataOffs = ha->MpqPos + pFileEntry->ByteOffset; WriteMpqDataMD5(ha->pStream, RawDataOffs, pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); } } // Free the file handle FreeFileHandle(hf); } else { nError = GetLastError(); } } // We also need to rebuild the HET table, if present if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) nError = RebuildHetTable(ha); // Resolve error and exit if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }