static PMPQ_PATCH_HEADER LoadFullFilePatch(TMPQFile * hf, MPQ_PATCH_HEADER & PatchHeader) { PMPQ_PATCH_HEADER pFullPatch; int nError = ERROR_SUCCESS; // BSWAP the entire header, if needed BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6); BSWAP_ARRAY32_UNSIGNED(&PatchHeader.dwXFRM, sizeof(DWORD) * 3); // Verify the signatures in the patch header if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM) return NULL; // Allocate space for patch header and compressed data pFullPatch = (PMPQ_PATCH_HEADER)STORM_ALLOC(BYTE, PatchHeader.dwSizeOfPatchData); if(pFullPatch != NULL) { // Copy the patch header memcpy(pFullPatch, &PatchHeader, sizeof(MPQ_PATCH_HEADER)); // Read the patch, depending on patch type if(nError == ERROR_SUCCESS) { switch(PatchHeader.dwPatchType) { case 0x59504f43: // 'COPY' nError = LoadFilePatch_COPY(hf, pFullPatch); break; case 0x30445342: // 'BSD0' nError = LoadFilePatch_BSD0(hf, pFullPatch); break; default: nError = ERROR_FILE_CORRUPT; break; } } // If something failed, free the patch buffer if(nError != ERROR_SUCCESS) { STORM_FREE(pFullPatch); pFullPatch = NULL; } } // Give the result to the caller return pFullPatch; }
static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData) { TMPQUserData * pUserData; // BSWAP the source data and copy them to our buffer BSWAP_ARRAY32_UNSIGNED(&pvUserData, sizeof(TMPQUserData)); pUserData = (TMPQUserData *)pvUserData; // Check the sizes if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs) { // Move to the position given by the userdata ByteOffset += pUserData->dwHeaderOffs; // The MPQ header should be within range of the file size if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize) { // Note: We should verify if there is the MPQ header. // However, the header could be at any position below that // that is multiplier of 0x200 return (TMPQUserData *)pvUserData; } } return NULL; }
static int IsMatchingPatchFile( TMPQArchive * ha, const char * szFileName, unsigned char * pbFileMd5) { MPQ_PATCH_HEADER PatchHeader = {0}; void * hFile = NULL; size_t dwTransferred = 0; int bResult = 0; /* Open the file and load the patch header */ if(SFileOpenFileEx((void *)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) { /* Load the patch header */ SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred); BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(uint32_t) * 6); /* If the file contains an incremental patch, */ /* compare the "MD5 before patching" with the base file MD5 */ if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE)); /* Close the file */ SFileCloseFile(hFile); } return bResult; }
static bool IsMatchingPatchFile( TMPQArchive * ha, const char * szFileName, LPBYTE pbFileMd5) { MPQ_PATCH_HEADER PatchHeader = {0}; HANDLE hFile = NULL; DWORD dwTransferred = 0; bool bResult = false; // Open the file and load the patch header if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) { // Load the patch header SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL); BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6); // If the file contains an incremental patch, // compare the "MD5 before patching" with the base file MD5 if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE)); // Close the file SFileCloseFile(hFile); } return bResult; }
static int LoadMpqPatch(TMPQFile * hf) { TPatchHeader PatchHeader; DWORD dwBytesRead; int nError = ERROR_SUCCESS; // Read the patch header SFileReadFile((HANDLE)hf, &PatchHeader, sizeof(TPatchHeader), &dwBytesRead); if(dwBytesRead != sizeof(TPatchHeader)) nError = ERROR_FILE_CORRUPT; // Verify the signatures in the patch header if(nError == ERROR_SUCCESS) { // BSWAP the entire header, if needed BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6); PatchHeader.dwXFRM = BSWAP_INT32_UNSIGNED(PatchHeader.dwXFRM); PatchHeader.dwXfrmBlockSize = BSWAP_INT32_UNSIGNED(PatchHeader.dwXfrmBlockSize); PatchHeader.dwPatchType = BSWAP_INT32_UNSIGNED(PatchHeader.dwPatchType); if(PatchHeader.dwSignature != 0x48435450 || PatchHeader.dwMD5 != 0x5f35444d || PatchHeader.dwXFRM != 0x4d524658) nError = ERROR_FILE_CORRUPT; } // Read the patch, depending on patch type if(nError == ERROR_SUCCESS) { switch(PatchHeader.dwPatchType) { case 0x59504f43: // 'COPY' nError = LoadMpqPatch_COPY(hf, &PatchHeader); break; case 0x30445342: // 'BSD0' nError = LoadMpqPatch_BSD0(hf, &PatchHeader); break; default: nError = ERROR_FILE_CORRUPT; break; } } return nError; }
static int CreatePseudoFileName(void * hFile, TFileEntry * pFileEntry, char * szFileName) { TMPQFile * hf = (TMPQFile *)hFile; /* MPQ File handle */ uint32_t FirstBytes[2] = {0, 0}; /* The first 4 bytes of the file */ size_t dwBytesRead = 0; size_t dwFilePos; /* Saved file position */ /* Read the first 2 uint32_ts bytes from the file */ dwFilePos = SFileSetFilePointer(hFile, 0, NULL, SEEK_CUR); SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead); SFileSetFilePointer(hFile, dwFilePos, NULL, SEEK_SET); /* If we read at least 8 bytes */ if(dwBytesRead == sizeof(FirstBytes)) { size_t i; /* Make sure that the array is properly BSWAP-ed */ BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); /* Try to guess file extension from those 2 ints */ for(i = 0; data2ext[i].szExt != NULL; i++) { if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) { char szPseudoName[20] = ""; /* Format the pseudo-name */ sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); /* Save the pseudo-name in the file entry as well */ AllocateFileName(hf->ha, pFileEntry, szPseudoName); /* If the caller wants to copy the file name, do it */ if(szFileName != NULL) strcpy(szFileName, szPseudoName); return ERROR_SUCCESS; } } } return ERROR_CAN_NOT_COMPLETE; }
static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) { TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file DWORD dwBytesRead = 0; DWORD dwFilePos; // Saved file position // Read the first 2 DWORDs bytes from the file dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); // If we read at least 8 bytes if(dwBytesRead == sizeof(FirstBytes)) { // Make sure that the array is properly BSWAP-ed BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); // Try to guess file extension from those 2 DWORDs for(size_t i = 0; data2ext[i].szExt != NULL; i++) { if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) { char szPseudoName[20] = ""; // Format the pseudo-name sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); // Save the pseudo-name in the file entry as well AllocateFileName(hf->ha, pFileEntry, szPseudoName); // If the caller wants to copy the file name, do it if(szFileName != NULL) strcpy(szFileName, szPseudoName); return ERROR_SUCCESS; } } } return ERROR_CAN_NOT_COMPLETE; }
static bool IsMatchingPatchFile( TMPQArchive * ha, const char * szFileName, LPBYTE pbBaseFileMd5) { MPQ_PATCH_HEADER PatchHeader = {0}; HANDLE hFile = NULL; DWORD dwTransferred = 0; DWORD dwFlags = 0; bool bResult = false; // Open the file and load the patch header if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) { // Retrieve the flags. We need to know whether the file is a patch or not SFileGetFileInfo(hFile, SFileInfoFlags, &dwFlags, sizeof(DWORD), &dwTransferred); if(dwFlags & MPQ_FILE_PATCH_FILE) { // Load the patch header SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL); BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6); // If the file contains an incremental patch, // compare the "MD5 before patching" with the base file MD5 if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) bResult = (!memcmp(PatchHeader.md5_before_patch, pbBaseFileMd5, MD5_DIGEST_SIZE)); } else { // TODO: How to match it if it's not an incremental patch? // Example: StarCraft II\Updates\enGB\s2-update-enGB-23258.MPQ: // Mods\Core.SC2Mod\enGB.SC2Assets\StreamingBuckets.txt" bResult = false; } // Close the file SFileCloseFile(hFile); } return bResult; }
// Copies all file sectors into another archive. static int CopyMpqFileSectors( TMPQArchive * ha, TMPQFile * hf, TFileStream * pNewStream) { TFileEntry * pFileEntry = hf->pFileEntry; ULONGLONG RawFilePos; // Used for calculating sector offset in the old MPQ archive ULONGLONG MpqFilePos; // MPQ file position in the new archive DWORD dwBytesToCopy = pFileEntry->dwCmpSize; DWORD dwPatchSize = 0; // Size of patch header DWORD dwFileKey1 = 0; // File key used for decryption DWORD dwFileKey2 = 0; // File key used for encryption DWORD dwCmpSize = 0; // Compressed file size, including patch header int nError = ERROR_SUCCESS; // Remember the position in the destination file FileStream_GetPos(pNewStream, &MpqFilePos); MpqFilePos -= ha->MpqPos; // Resolve decryption keys. Note that the file key given // in the TMPQFile structure also includes the key adjustment if(nError == ERROR_SUCCESS && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) { dwFileKey2 = dwFileKey1 = hf->dwFileKey; if(pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) { dwFileKey2 = (dwFileKey1 ^ pFileEntry->dwFileSize) - (DWORD)pFileEntry->ByteOffset; dwFileKey2 = (dwFileKey2 + (DWORD)MpqFilePos) ^ pFileEntry->dwFileSize; } } // If we have to save patch header, do it if(nError == ERROR_SUCCESS && hf->pPatchInfo != NULL) { BSWAP_ARRAY32_UNSIGNED(hf->pPatchInfo, sizeof(DWORD) * 3); if(!FileStream_Write(pNewStream, NULL, hf->pPatchInfo, hf->pPatchInfo->dwLength)) nError = GetLastError(); // Save the size of the patch info dwPatchSize = hf->pPatchInfo->dwLength; } // If we have to save sector offset table, do it. if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL) { DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD)); DWORD dwSectorOffsLen = hf->SectorOffsets[0]; assert((pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0); assert(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK); if(SectorOffsetsCopy == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; // Encrypt the secondary sector offset table and write it to the target file if(nError == ERROR_SUCCESS) { memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen); if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwFileKey2 - 1); BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen); if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorOffsLen)) nError = GetLastError(); dwBytesToCopy -= dwSectorOffsLen; dwCmpSize += dwSectorOffsLen; } // Update compact progress if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwSectorOffsLen; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); } STORM_FREE(SectorOffsetsCopy); } // Now we have to copy 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; // 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]; } // Last sector: If there is not enough bytes remaining in the file, cut the raw size if(dwRawDataInSector > dwBytesToCopy) dwRawDataInSector = dwBytesToCopy; // Calculate the raw file offset of the file sector CalculateRawSectorOffset(RawFilePos, 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. if((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) && dwFileKey1 != dwFileKey2) { BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey1 + dwSector); EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey2 + dwSector); BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); } // Now write the sector back to the file if(!FileStream_Write(pNewStream, NULL, hf->pbFileSector, dwRawDataInSector)) { nError = GetLastError(); break; } // Update compact progress if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwRawDataInSector; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); } // Adjust byte counts dwBytesToCopy -= dwRawDataInSector; dwCmpSize += dwRawDataInSector; } } // Copy the sector CRCs, if any // Sector CRCs are always compressed (not imploded) and unencrypted if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL && hf->SectorChksums != NULL) { DWORD dwCrcLength; dwCrcLength = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount]; if(dwCrcLength != 0) { if(!FileStream_Read(ha->pStream, NULL, hf->SectorChksums, dwCrcLength)) nError = GetLastError(); if(!FileStream_Write(pNewStream, NULL, hf->SectorChksums, dwCrcLength)) nError = GetLastError(); // Update compact progress if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwCrcLength; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); } // Size of the CRC block is also included in the compressed file size dwBytesToCopy -= dwCrcLength; dwCmpSize += dwCrcLength; } } // There might be extra data beyond sector checksum table // Sometimes, these data are even part of sector offset table // Examples: // 2012 - WoW\15354\locale-enGB.MPQ:DBFilesClient\SpellLevels.dbc // 2012 - WoW\15354\locale-enGB.MPQ:Interface\AddOns\Blizzard_AuctionUI\Blizzard_AuctionUI.xml if(nError == ERROR_SUCCESS && dwBytesToCopy != 0) { LPBYTE pbExtraData; // Allocate space for the extra data pbExtraData = STORM_ALLOC(BYTE, dwBytesToCopy); if(pbExtraData != NULL) { if(!FileStream_Read(ha->pStream, NULL, pbExtraData, dwBytesToCopy)) nError = GetLastError(); if(!FileStream_Write(pNewStream, NULL, pbExtraData, dwBytesToCopy)) nError = GetLastError(); // Include these extra data in the compressed size dwCmpSize += dwBytesToCopy; STORM_FREE(pbExtraData); } else nError = ERROR_NOT_ENOUGH_MEMORY; } // Write the MD5's of the raw file data, if needed if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0) { nError = WriteMpqDataMD5(pNewStream, ha->MpqPos + MpqFilePos, pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); } // Update file position in the block table if(nError == ERROR_SUCCESS) { // At this point, number of bytes written should be exactly // the same like the compressed file size. If it isn't, // there's something wrong (an unknown archive version, MPQ malformation, ...) // // Note: Diablo savegames have very weird layout, and the file "hero" // seems to have improper compressed size. Instead of real compressed size, // the "dwCmpSize" member of the block table entry contains // uncompressed size of file data + size of the sector table. // If we compact the archive, Diablo will refuse to load the game // // Note: Some patch files in WOW patches don't count the patch header // into compressed size // if(dwCmpSize <= pFileEntry->dwCmpSize && pFileEntry->dwCmpSize <= dwCmpSize + dwPatchSize) { // Note: DO NOT update the compressed size in the file entry, no matter how bad it is. pFileEntry->ByteOffset = MpqFilePos; } else { nError = ERROR_FILE_CORRUPT; assert(false); } } return nError; }
static int WriteDataToMpqFile( TMPQArchive * ha, TMPQFile * hf, LPBYTE pbFileData, DWORD dwDataSize, DWORD dwCompression) { TFileEntry * pFileEntry = hf->pFileEntry; ULONGLONG ByteOffset; LPBYTE pbCompressed = NULL; // Compressed (target) data LPBYTE pbToWrite = NULL; // Data to write to the file int nCompressionLevel = -1; // ADPCM compression level (only used for wave files) int nError = ERROR_SUCCESS; // If the caller wants ADPCM compression, we will set wave compression level to 4, // which corresponds to medium quality if(dwCompression & LOSSY_COMPRESSION_MASK) nCompressionLevel = 4; // Make sure that the caller won't overrun the previously initiated file size assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize); assert(hf->dwSectorCount != 0); assert(hf->pbFileSector != NULL); if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize) return ERROR_DISK_FULL; pbToWrite = hf->pbFileSector; // Now write all data to the file sector buffer if(nError == ERROR_SUCCESS) { DWORD dwBytesInSector = hf->dwFilePos % hf->dwSectorSize; DWORD dwSectorIndex = hf->dwFilePos / hf->dwSectorSize; DWORD dwBytesToCopy; // Process all data. while(dwDataSize != 0) { dwBytesToCopy = dwDataSize; // Check for sector overflow if(dwBytesToCopy > (hf->dwSectorSize - dwBytesInSector)) dwBytesToCopy = (hf->dwSectorSize - dwBytesInSector); // Copy the data to the file sector memcpy(hf->pbFileSector + dwBytesInSector, pbFileData, dwBytesToCopy); dwBytesInSector += dwBytesToCopy; pbFileData += dwBytesToCopy; dwDataSize -= dwBytesToCopy; // Update the file position hf->dwFilePos += dwBytesToCopy; // If the current sector is full, or if the file is already full, // then write the data to the MPQ if(dwBytesInSector >= hf->dwSectorSize || hf->dwFilePos >= pFileEntry->dwFileSize) { // Set the position in the file ByteOffset = hf->RawFilePos + pFileEntry->dwCmpSize; // Update CRC32 and MD5 of the file md5_process((hash_state *)hf->hctx, hf->pbFileSector, dwBytesInSector); hf->dwCrc32 = crc32(hf->dwCrc32, hf->pbFileSector, dwBytesInSector); // Compress the file sector, if needed if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) { int nOutBuffer = (int)dwBytesInSector; int nInBuffer = (int)dwBytesInSector; // If the file is compressed, allocate buffer for the compressed data. // Note that we allocate buffer that is a bit longer than sector size, // for case if the compression method performs a buffer overrun if(pbCompressed == NULL) { pbToWrite = pbCompressed = STORM_ALLOC(BYTE, hf->dwSectorSize + 0x100); if(pbCompressed == NULL) { nError = ERROR_NOT_ENOUGH_MEMORY; break; } } // // Note that both SCompImplode and SCompCompress give original buffer, // if they are unable to comperss the data. // if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) { SCompImplode((char *)pbCompressed, &nOutBuffer, (char *)hf->pbFileSector, nInBuffer); } if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { SCompCompress((char *)pbCompressed, &nOutBuffer, (char *)hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel); } // Update sector positions dwBytesInSector = nOutBuffer; if(hf->SectorOffsets != NULL) hf->SectorOffsets[dwSectorIndex+1] = hf->SectorOffsets[dwSectorIndex] + dwBytesInSector; // We have to calculate sector CRC, if enabled if(hf->SectorChksums != NULL) hf->SectorChksums[dwSectorIndex] = adler32(0, pbCompressed, nOutBuffer); } // Encrypt the sector, if necessary if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector); EncryptMpqBlock(pbToWrite, dwBytesInSector, hf->dwFileKey + dwSectorIndex); BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector); } // Write the file sector if(!FileStream_Write(ha->pStream, &ByteOffset, pbToWrite, dwBytesInSector)) { nError = GetLastError(); break; } // Call the compact callback, if any if(AddFileCB != NULL) AddFileCB(pvUserData, hf->dwFilePos, hf->dwDataSize, false); // Update the compressed file size pFileEntry->dwCmpSize += dwBytesInSector; dwBytesInSector = 0; dwSectorIndex++; } } } // Cleanup if(pbCompressed != NULL) STORM_FREE(pbCompressed); return nError; }
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 = GetPlainFileNameA(szNewFileName); szFileName = GetPlainFileNameA(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 = (DWORD *)STORM_ALLOC(BYTE, hf->SectorOffsets[0]); 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 CalculateRawSectorOffset(RawFilePos, 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; }
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData; int nError = ERROR_SUCCESS; // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL) return nError; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; pbRawData = hf->pbFileSector; // If the file sector is not loaded yet, do it if(hf->dwSectorOffs != 0) { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { // Allocate space for compressed data pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; pbRawData = pbCompressed; } // Load the raw (compressed, encrypted) data if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) { STORM_FREE(pbCompressed); return GetLastError(); } // If the file is encrypted, we have to decrypt the data first if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); } // If the file is compressed, we have to decompress it now if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { int cbOutBuffer = (int)hf->dwDataSize; int cbInBuffer = (int)pFileEntry->dwCmpSize; int nResult = 0; // // If the file is an incremental patch, the size of compressed data // is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo) // // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: // // File CmprSize DcmpSize DataSize Compressed? // -------------------------------------- ---------- -------- -------- --------------- // esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes // deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No // if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) cbInBuffer = cbInBuffer - sizeof(TPatchInfo); // Is the file compressed by Blizzard's multiple compression ? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { // Remember the last used compression hf->dwCompression0 = pbRawData[0]; // Decompress the file if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); else nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); } // Is the file compressed by PKWARE Data Compression Library ? // Note: Single unit files compressed with IMPLODE are not supported by Blizzard else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; } else { if(hf->pbFileSector != NULL && pbRawData != hf->pbFileSector) memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); } // Free the decompression buffer. if(pbCompressed != NULL) STORM_FREE(pbCompressed); // The file sector is now properly loaded hf->dwSectorOffs = 0; } // At this moment, we have the file loaded into the file buffer. // Copy as much as the caller wants if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) { // File position is greater or equal to file size ? if(dwFilePos >= hf->dwDataSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // If not enough bytes remaining in the file, cut them if((hf->dwDataSize - dwFilePos) < dwToRead) dwToRead = (hf->dwDataSize - dwFilePos); // Copy the bytes memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); // Give the number of bytes read *pdwBytesRead = dwToRead; return ERROR_SUCCESS; } // An error, sorry return ERROR_CAN_NOT_COMPLETE; }
// Copies all file sectors into another archive. static int CopyMpqFileSectors( TMPQArchive * ha, TMPQFile * hf, TFileStream * pNewStream) { TFileEntry * pFileEntry = hf->pFileEntry; ULONGLONG RawFilePos; // Used for calculating sector offset in the old MPQ archive ULONGLONG MpqFilePos; // MPQ file position in the new archive DWORD dwBytesToCopy = pFileEntry->dwCmpSize; DWORD dwFileKey1 = 0; // File key used for decryption DWORD dwFileKey2 = 0; // File key used for encryption DWORD dwCmpSize = 0; // Compressed file size int nError = ERROR_SUCCESS; // Remember the position in the destination file FileStream_GetPos(pNewStream, MpqFilePos); MpqFilePos -= ha->MpqPos; // Resolve decryption keys. Note that the file key given // in the TMPQFile structure also includes the key adjustment if (nError == ERROR_SUCCESS && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) { dwFileKey2 = dwFileKey1 = hf->dwFileKey; if (pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) { dwFileKey2 = (dwFileKey1 ^ pFileEntry->dwFileSize) - (DWORD)pFileEntry->ByteOffset; dwFileKey2 = (dwFileKey2 + (DWORD)MpqFilePos) ^ pFileEntry->dwFileSize; } } // If we have to save patch header, do it if (nError == ERROR_SUCCESS && hf->PatchInfo != NULL) { BSWAP_ARRAY32_UNSIGNED(hf->PatchInfo, sizeof(DWORD) * 3); if (!FileStream_Write(pNewStream, NULL, hf->PatchInfo, hf->PatchInfo->dwLength)) nError = GetLastError(); // Note: In wow-update-12694.MPQ, the dwCmpSize doesn't // include the patch header on some files. dwCmpSize += hf->PatchInfo->dwLength; } // If we have to save sector offset table, do it. if (nError == ERROR_SUCCESS && hf->SectorOffsets != NULL) { LPDWORD SectorOffsetsCopy = ALLOCMEM(DWORD, hf->dwSectorCount); DWORD dwSectorPosLen = hf->dwSectorCount * sizeof(DWORD); assert((pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0); assert(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED); if (SectorOffsetsCopy == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; // Encrypt the secondary sector offset table and write it to the target file if (nError == ERROR_SUCCESS) { memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorPosLen); if (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) EncryptMpqBlock(SectorOffsetsCopy, dwSectorPosLen, dwFileKey2 - 1); BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorPosLen); if (!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorPosLen)) nError = GetLastError(); dwCmpSize += dwSectorPosLen; } // Update compact progress if (CompactCB != NULL) { CompactBytesProcessed += dwSectorPosLen; CompactCB(pvUserData, CCB_COMPACTING_FILES, CompactBytesProcessed, CompactTotalBytes); } FREEMEM(SectorOffsetsCopy); } // Now we have to copy 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->dwDataSectors; 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 > dwBytesToCopy) dwRawDataInSector = dwBytesToCopy; // 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 CalculateRawSectorOffset(RawFilePos, 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. if ((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) && dwFileKey1 != dwFileKey2) { BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey1 + dwSector); EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey2 + dwSector); BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); } // Now write the sector back to the file if (!FileStream_Write(pNewStream, NULL, hf->pbFileSector, dwRawDataInSector)) { nError = GetLastError(); break; } // Update compact progress if (CompactCB != NULL) { CompactBytesProcessed += dwRawDataInSector; CompactCB(pvUserData, CCB_COMPACTING_FILES, CompactBytesProcessed, CompactTotalBytes); } // Adjust byte counts dwBytesToCopy -= hf->dwSectorSize; dwCmpSize += dwRawDataInSector; } } // Copy the sector CRCs, if any // Sector CRCs are always compressed (not imploded) and unencrypted if (nError == ERROR_SUCCESS && hf->SectorOffsets != NULL && hf->SectorChksums != NULL) { DWORD dwCrcLength; dwCrcLength = hf->SectorOffsets[hf->dwSectorCount - 1] - hf->SectorOffsets[hf->dwSectorCount - 2]; if (dwCrcLength != 0) { if (!FileStream_Read(ha->pStream, NULL, hf->SectorChksums, dwCrcLength)) nError = GetLastError(); if (!FileStream_Write(pNewStream, NULL, hf->SectorChksums, dwCrcLength)) nError = GetLastError(); // Update compact progress if (CompactCB != NULL) { CompactBytesProcessed += dwCrcLength; CompactCB(pvUserData, CCB_COMPACTING_FILES, CompactBytesProcessed, CompactTotalBytes); } // Size of the CRC block is also included in the compressed file size dwCmpSize += dwCrcLength; } } // Update file position in the block table if (nError == ERROR_SUCCESS) { // At this point, number of bytes written should be exactly // the same like the compressed file size. If it isn't, // there's something wrong (an unknown archive version, MPQ protection, ...) // // Note: Diablo savegames have very weird layout, and the file "hero" // seems to have improper compressed size. Instead of real compressed size, // the "dwCmpSize" member of the block table entry contains // uncompressed size of file data + size of the sector table. // If we compact the archive, Diablo will refuse to load the game // Seems like some sort of protection to me. if (dwCmpSize == pFileEntry->dwCmpSize) { // Update file pos in the block table pFileEntry->ByteOffset = MpqFilePos; } else { nError = ERROR_FILE_CORRUPT; assert(false); } } return nError; }
// TODO: Test for archives > 4GB BOOL WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) { TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle char * szExt = "xxx"; // Default extension DWORD dwFirstBytes[2]; // The first 4 bytes of the file DWORD dwFilePos; // Saved file position int nError = ERROR_SUCCESS; int i; // Pre-zero the output buffer if(szFileName != NULL) *szFileName = 0; // Check valid parameters if(nError == ERROR_SUCCESS) { if(hf == NULL || szFileName == NULL) nError = ERROR_INVALID_PARAMETER; } // If the file name is already filled, return it. if(nError == ERROR_SUCCESS && *hf->szFileName != 0) { if(szFileName != hf->szFileName) strcpy(szFileName, hf->szFileName); return TRUE; } if(nError == ERROR_SUCCESS) { if(hf->dwFileIndex == (DWORD)-1) nError = ERROR_CAN_NOT_COMPLETE; } // Read the first 8 bytes from the file if(nError == ERROR_SUCCESS) { dwFirstBytes[0] = dwFirstBytes[1] = 0; dwFilePos = SFileSetFilePointer(hf, 0, NULL, FILE_CURRENT); if(!SFileReadFile(hFile, &dwFirstBytes, sizeof(dwFirstBytes), NULL)) nError = GetLastError(); BSWAP_ARRAY32_UNSIGNED(dwFirstBytes, sizeof(dwFirstBytes) / sizeof(DWORD)); SFileSetFilePointer(hf, dwFilePos, NULL, FILE_BEGIN); } if(nError == ERROR_SUCCESS) { if((dwFirstBytes[0] & 0x0000FFFF) == ID_EXE) szExt = "exe"; else if(dwFirstBytes[0] == 0x00000006 && dwFirstBytes[1] == 0x00000001) szExt = "dc6"; else { for(i = 0; id2ext[i].szExt != NULL; i++) { if(id2ext[i].dwID == dwFirstBytes[0]) { szExt = id2ext[i].szExt; break; } } } // Create the file name sprintf(hf->szFileName, "File%08lu.%s", hf->dwFileIndex, szExt); if(szFileName != hf->szFileName) strcpy(szFileName, hf->szFileName); } return (nError == ERROR_SUCCESS); }
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData = NULL; int nError; // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; pbRawData = hf->pbFileSector; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; // If the file buffer is not loaded yet, do it if(hf->dwSectorOffs != 0) { // // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: // // File CmpSize FileSize Data // -------------------------------------- ------- -------- --------------- // esES\DBFilesClient\LightSkyBox.dbc 0xBE 0xBC Is compressed // deDE\DBFilesClient\MountCapability.dbc 0x93 0x77 Is uncompressed // // Now tell me how to deal with this mess. Apparently // someone made a mistake at Blizzard ... // if(hf->pPatchInfo != NULL) { // Allocate space for pbCompressed = ALLOCMEM(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Read the entire file if(!FileStream_Read(ha->pStream, &RawFilePos, pbCompressed, pFileEntry->dwCmpSize)) { FREEMEM(pbCompressed); return GetLastError(); } // We assume that patch files are not encrypted assert((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) == 0); assert((pFileEntry->dwFlags & MPQ_FILE_IMPLODE) == 0); // Check the 'PTCH' signature to find out if it's compressed or not if(pbCompressed[0] != 'P' || pbCompressed[1] != 'T' || pbCompressed[2] != 'C' || pbCompressed[3] != 'H') { int cbOutBuffer = (int)hf->dwDataSize; int nResult = SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbCompressed, (int)pFileEntry->dwCmpSize); if(nResult == 0) { FREEMEM(pbCompressed); return ERROR_FILE_CORRUPT; } } else { memcpy(hf->pbFileSector, pbCompressed, hf->dwDataSize); } // Free the decompression buffer. FREEMEM(pbCompressed); } else { // If the file is compressed, we have to allocate buffer for compressed data if(pFileEntry->dwCmpSize < hf->dwDataSize) { pbCompressed = ALLOCMEM(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; pbRawData = pbCompressed; } // Read the entire file if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) { FREEMEM(pbCompressed); return GetLastError(); } // If the file is encrypted, we have to decrypt the data first if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); } // If the file is compressed, we have to decompress it now if(pFileEntry->dwCmpSize < hf->dwDataSize) { int cbOutBuffer = (int)hf->dwDataSize; int nResult = 0; // Note: Single unit files compressed with IMPLODE are not supported by Blizzard if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize); if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) nResult = SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize); // Free the decompression buffer. FREEMEM(pbCompressed); if(nResult == 0) return ERROR_FILE_CORRUPT; } } // The file sector is now properly loaded hf->dwSectorOffs = 0; } // At this moment, we have the file loaded into the file buffer. // Copy as much as the caller wants if(hf->dwSectorOffs == 0) { // File position is greater or equal to file size ? if(hf->dwFilePos >= hf->dwDataSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // If not enough bytes remaining in the file, cut them if((hf->dwDataSize - hf->dwFilePos) < dwToRead) dwToRead = (hf->dwDataSize - hf->dwFilePos); // Copy the bytes memcpy(pvBuffer, hf->pbFileSector + hf->dwFilePos, dwToRead); hf->dwFilePos += dwToRead; // Give the number of bytes read *pdwBytesRead = dwToRead; return ERROR_SUCCESS; } // An error, sorry return ERROR_CAN_NOT_COMPLETE; }
static int RecryptFileData( TMPQArchive * ha, DWORD dwSaveBlockIndex, const char * szFileName, const char * szNewFileName) { LARGE_INTEGER BlockFilePos; LARGE_INTEGER MpqFilePos; TMPQBlockEx * pBlockEx = ha->pExtBlockTable + dwSaveBlockIndex; TMPQBlock * pBlock = ha->pBlockTable + dwSaveBlockIndex; const char * szPlainName; LPDWORD pdwBlockPos1 = NULL; LPDWORD pdwBlockPos2 = NULL; LPBYTE pbFileBlock = NULL; DWORD dwTransferred; DWORD dwOldSeed; DWORD dwNewSeed; DWORD dwToRead; int nBlocks; int nError = ERROR_SUCCESS; // The file must be encrypted assert(pBlock->dwFlags & MPQ_FILE_ENCRYPTED); // File decryption seed is calculated from the plain name szPlainName = strrchr(szFileName, '\\'); if(szPlainName != NULL) szFileName = szPlainName + 1; szPlainName = strrchr(szNewFileName, '\\'); if(szPlainName != NULL) szNewFileName = szPlainName + 1; // Calculate both file seeds dwOldSeed = DecryptFileSeed(szFileName); dwNewSeed = DecryptFileSeed(szNewFileName); if(pBlock->dwFlags & MPQ_FILE_FIXSEED) { dwOldSeed = (dwOldSeed + pBlock->dwFilePos) ^ pBlock->dwFSize; dwNewSeed = (dwNewSeed + pBlock->dwFilePos) ^ pBlock->dwFSize; } // Incase the seeds are equal, don't recrypt the file if(dwNewSeed == dwOldSeed) return ERROR_SUCCESS; // Calculate the file position of the archived file MpqFilePos.LowPart = pBlock->dwFilePos; MpqFilePos.HighPart = pBlockEx->wFilePosHigh; MpqFilePos.QuadPart += ha->MpqPos.QuadPart; // Calculate the number of file blocks nBlocks = pBlock->dwFSize / ha->dwBlockSize; if(pBlock->dwFSize % ha->dwBlockSize) nBlocks++; // If the file is stored as single unit, we recrypt one block only if(pBlock->dwFlags & MPQ_FILE_SINGLE_UNIT) { // Allocate the block pbFileBlock = ALLOCMEM(BYTE, pBlock->dwCSize); if(pbFileBlock == NULL) return ERROR_NOT_ENOUGH_MEMORY; SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, pbFileBlock, pBlock->dwCSize, &dwTransferred, NULL); if(dwTransferred == pBlock->dwCSize) nError = ERROR_FILE_CORRUPT; if(nError == ERROR_SUCCESS) { // Recrypt the block DecryptMPQBlock((DWORD *)pbFileBlock, pBlock->dwCSize, dwOldSeed); EncryptMPQBlock((DWORD *)pbFileBlock, pBlock->dwCSize, dwNewSeed); // Write it back SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN); WriteFile(ha->hFile, pbFileBlock, pBlock->dwCSize, &dwTransferred, NULL); if(dwTransferred != pBlock->dwCSize) nError = ERROR_WRITE_FAULT; } FREEMEM(pbFileBlock); return nError; } // If the file is compressed, we have to re-crypt block table first, // then all file blocks if(pBlock->dwFlags & MPQ_FILE_COMPRESSED) { // Allocate buffer for both blocks pdwBlockPos1 = ALLOCMEM(DWORD, nBlocks + 2); pdwBlockPos2 = ALLOCMEM(DWORD, nBlocks + 2); if(pdwBlockPos1 == NULL || pdwBlockPos2 == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Calculate number of bytes to be read dwToRead = (nBlocks + 1) * sizeof(DWORD); if(pBlock->dwFlags & MPQ_FILE_HAS_EXTRA) dwToRead += sizeof(DWORD); // Read the block positions SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, pdwBlockPos1, dwToRead, &dwTransferred, NULL); if(dwTransferred != dwToRead) nError = ERROR_FILE_CORRUPT; // Recrypt the block table if(nError == ERROR_SUCCESS) { BSWAP_ARRAY32_UNSIGNED(pdwBlockPos1, dwToRead / sizeof(DWORD)); DecryptMPQBlock(pdwBlockPos1, dwToRead, dwOldSeed - 1); if(pdwBlockPos1[0] != dwToRead) nError = ERROR_FILE_CORRUPT; memcpy(pdwBlockPos2, pdwBlockPos1, dwToRead); EncryptMPQBlock(pdwBlockPos2, dwToRead, dwNewSeed - 1); BSWAP_ARRAY32_UNSIGNED(pdwBlockPos2, dwToRead / sizeof(DWORD)); } // Write the recrypted block table back if(nError == ERROR_SUCCESS) { SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN); WriteFile(ha->hFile, pdwBlockPos2, dwToRead, &dwTransferred, NULL); if(dwTransferred != dwToRead) nError = ERROR_WRITE_FAULT; } } // Allocate the transfer buffer if(nError == ERROR_SUCCESS) { pbFileBlock = ALLOCMEM(BYTE, ha->dwBlockSize); if(pbFileBlock == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Now we have to recrypt all file blocks if(nError == ERROR_SUCCESS) { for(int nBlock = 0; nBlock < nBlocks; nBlock++) { // Calc position and length for uncompressed file BlockFilePos.QuadPart = MpqFilePos.QuadPart + (ha->dwBlockSize * nBlock); dwToRead = ha->dwBlockSize; if(nBlock == nBlocks - 1) dwToRead = pBlock->dwFSize - (ha->dwBlockSize * (nBlocks - 1)); // Fix position and length for compressed file if(pBlock->dwFlags & MPQ_FILE_COMPRESS) { BlockFilePos.QuadPart = MpqFilePos.QuadPart + pdwBlockPos1[nBlock]; dwToRead = pdwBlockPos1[nBlock+1] - pdwBlockPos1[nBlock]; } // Read the file block SetFilePointer(ha->hFile, BlockFilePos.LowPart, &BlockFilePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, pbFileBlock, dwToRead, &dwTransferred, NULL); if(dwTransferred != dwToRead) nError = ERROR_FILE_CORRUPT; // Recrypt the file block BSWAP_ARRAY32_UNSIGNED((DWORD *)pbFileBlock, dwToRead/sizeof(DWORD)); DecryptMPQBlock((DWORD *)pbFileBlock, dwToRead, dwOldSeed + nBlock); EncryptMPQBlock((DWORD *)pbFileBlock, dwToRead, dwNewSeed + nBlock); BSWAP_ARRAY32_UNSIGNED((DWORD *)pbFileBlock, dwToRead/sizeof(DWORD)); // Write the block back SetFilePointer(ha->hFile, BlockFilePos.LowPart, &BlockFilePos.HighPart, FILE_BEGIN); WriteFile(ha->hFile, pbFileBlock, dwToRead, &dwTransferred, NULL); if(dwTransferred != dwToRead) nError = ERROR_WRITE_FAULT; } } // Free buffers and exit if(pbFileBlock != NULL) FREEMEM(pbFileBlock); if(pdwBlockPos2 != NULL) FREEMEM(pdwBlockPos2); if(pdwBlockPos1 != NULL) FREEMEM(pdwBlockPos1); return nError; }
// Copies all file blocks into another archive. static int CopyMpqFileBlocks( HANDLE hFile, TMPQArchive * ha, TMPQBlockEx * pBlockEx, TMPQBlock * pBlock, DWORD dwSeed) { LARGE_INTEGER FilePos = {0}; DWORD * pdwBlockPos2 = NULL; // File block positions to be written to target file DWORD * pdwBlockPos = NULL; // File block positions (unencrypted) BYTE * pbBlock = NULL; // Buffer for the file block DWORD dwTransferred; // Number of bytes transferred DWORD dwCSize = 0; // Compressed file size DWORD dwBytes = 0; // Number of bytes DWORD dwSeed1 = 0; // File seed used for decryption DWORD dwSeed2 = 0; // File seed used for encryption DWORD nBlocks = 0; // Number of file blocks DWORD nBlock = 0; // Currently processed file block int nError = ERROR_SUCCESS; // When file length is zero, do nothing if(pBlock->dwFSize == 0) return ERROR_SUCCESS; // Calculate number of blocks in the file if(nError == ERROR_SUCCESS) { nBlocks = pBlock->dwFSize / ha->dwBlockSize; if(pBlock->dwFSize % ha->dwBlockSize) nBlocks++; pbBlock = ALLOCMEM(BYTE, ha->dwBlockSize); if(pbBlock == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Set the position to the begin of the file within archive if(nError == ERROR_SUCCESS) { FilePos.HighPart = pBlockEx->wFilePosHigh; FilePos.LowPart = pBlock->dwFilePos; FilePos.QuadPart += ha->MpqPos.QuadPart; if(SetFilePointer(ha->hFile, FilePos.LowPart, &FilePos.HighPart, FILE_BEGIN) != FilePos.LowPart) nError = GetLastError(); } // Remember the position in the destination file if(nError == ERROR_SUCCESS) { FilePos.HighPart = 0; FilePos.LowPart = SetFilePointer(hFile, 0, &FilePos.HighPart, FILE_CURRENT); } // Resolve decryption seeds. The 'dwSeed' parameter is the decryption // seed for the file. if(nError == ERROR_SUCCESS && (pBlock->dwFlags & MPQ_FILE_ENCRYPTED)) { dwSeed1 = dwSeed; if(pBlock->dwFlags & MPQ_FILE_FIXSEED) dwSeed = (dwSeed1 ^ pBlock->dwFSize) - pBlock->dwFilePos; dwSeed2 = dwSeed; if(pBlock->dwFlags & MPQ_FILE_FIXSEED) dwSeed2 = (dwSeed + (DWORD)(FilePos.QuadPart - ha->MpqPos.QuadPart)) ^ pBlock->dwFSize; } // Load the file positions from the archive and save it to the target file // (only if the file is compressed) if(pBlock->dwFlags & MPQ_FILE_COMPRESSED) { // Allocate buffers if(nError == ERROR_SUCCESS) { pdwBlockPos = ALLOCMEM(DWORD, nBlocks + 2); pdwBlockPos2 = ALLOCMEM(DWORD, nBlocks + 2); if(pdwBlockPos == NULL || pdwBlockPos2 == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Load the block positions if(nError == ERROR_SUCCESS) { dwBytes = (nBlocks + 1) * sizeof(DWORD); if(pBlock->dwFlags & MPQ_FILE_HAS_EXTRA) dwBytes += sizeof(DWORD); ReadFile(ha->hFile, pdwBlockPos, dwBytes, &dwTransferred, NULL); if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } // Re-encrypt the block table positions if(nError == ERROR_SUCCESS) { BSWAP_ARRAY32_UNSIGNED(pdwBlockPos, dwBytes / sizeof(DWORD)); if(pBlock->dwFlags & MPQ_FILE_ENCRYPTED) { DecryptMPQBlock(pdwBlockPos, dwBytes, dwSeed1 - 1); if(pdwBlockPos[0] != dwBytes) nError = ERROR_FILE_CORRUPT; memcpy(pdwBlockPos2, pdwBlockPos, dwBytes); EncryptMPQBlock(pdwBlockPos2, dwBytes, dwSeed2 - 1); } else { memcpy(pdwBlockPos2, pdwBlockPos, dwBytes); } BSWAP_ARRAY32_UNSIGNED(pdwBlockPos2, dwBytes / sizeof(DWORD)); } // Write to the target file if(nError == ERROR_SUCCESS) { WriteFile(hFile, pdwBlockPos2, dwBytes, &dwTransferred, NULL); dwCSize += dwTransferred; if(dwTransferred != dwBytes) nError = ERROR_DISK_FULL; } } // Now we have to copy all file blocks. We will do it without // recompression, because re-compression is not necessary in this case if(nError == ERROR_SUCCESS) { for(nBlock = 0; nBlock < nBlocks; nBlock++) { // Fix: The last block must not be exactly the size of one block. dwBytes = ha->dwBlockSize; if(nBlock == nBlocks - 1) { dwBytes = pBlock->dwFSize - (ha->dwBlockSize * (nBlocks - 1)); } if(pBlock->dwFlags & MPQ_FILE_COMPRESSED) dwBytes = pdwBlockPos[nBlock+1] - pdwBlockPos[nBlock]; // Read the file block ReadFile(ha->hFile, pbBlock, dwBytes, &dwTransferred, NULL); if(dwTransferred != dwBytes) { nError = ERROR_FILE_CORRUPT; break; } // If necessary, re-encrypt the block // Note: Recompression is not necessary here. Unlike encryption, // the compression does not depend on the position of the file in MPQ. if((pBlock->dwFlags & MPQ_FILE_ENCRYPTED) && dwSeed1 != dwSeed2) { BSWAP_ARRAY32_UNSIGNED((DWORD *)pbBlock, dwBytes/sizeof(DWORD)); DecryptMPQBlock((DWORD *)pbBlock, dwBytes, dwSeed1 + nBlock); EncryptMPQBlock((DWORD *)pbBlock, dwBytes, dwSeed2 + nBlock); BSWAP_ARRAY32_UNSIGNED((DWORD *)pbBlock, dwBytes/sizeof(DWORD)); } // Now write the block back to the file WriteFile(hFile, pbBlock, dwBytes, &dwTransferred, NULL); dwCSize += dwTransferred; if(dwTransferred != dwBytes) { nError = ERROR_DISK_FULL; break; } } } // Copy the file extras, if any // These extras does not seem to be encrypted, and their purpose is unknown if(nError == ERROR_SUCCESS && (pBlock->dwFlags & MPQ_FILE_HAS_EXTRA)) { dwBytes = pdwBlockPos[nBlocks + 1] - pdwBlockPos[nBlocks]; if(dwBytes != 0) { ReadFile(ha->hFile, pbBlock, dwBytes, &dwTransferred, NULL); if(dwTransferred == dwBytes) { WriteFile(hFile, pbBlock, dwBytes, &dwTransferred, NULL); dwCSize += dwTransferred; if(dwTransferred != dwBytes) nError = ERROR_DISK_FULL; } else { nError = ERROR_FILE_CORRUPT; } } } // Update file position in the block table if(nError == ERROR_SUCCESS) { // At this point, number of bytes written should be exactly // the same like the compressed file size. If it isn't, there's something wrong // (maybe new archive version ?) assert(dwCSize == pBlock->dwCSize); // Update file pos in the block table FilePos.QuadPart -= ha->MpqPos.QuadPart; pBlockEx->wFilePosHigh = (USHORT)FilePos.HighPart; pBlock->dwFilePos = FilePos.LowPart; } // Cleanup and return if(pdwBlockPos2 != NULL) FREEMEM(pdwBlockPos2); if(pdwBlockPos != NULL) FREEMEM(pdwBlockPos); if(pbBlock != NULL) FREEMEM(pbBlock); return nError; }
/* Copies all file sectors into another archive. */ static int CopyMpqFileSectors( TMPQArchive * ha, TMPQFile * hf, TFileStream * pNewStream, uint64_t MpqFilePos) /* MPQ file position in the new archive */ { TFileEntry * pFileEntry = hf->pFileEntry; uint64_t RawFilePos; /* Used for calculating sector offset in the old MPQ archive */ uint32_t dwBytesToCopy = pFileEntry->dwCmpSize; uint32_t dwPatchSize = 0; /* Size of patch header */ uint32_t dwFileKey1 = 0; /* File key used for decryption */ uint32_t dwFileKey2 = 0; /* File key used for encryption */ uint32_t dwCmpSize = 0; /* Compressed file size, including patch header */ int nError = ERROR_SUCCESS; /* Resolve decryption keys. Note that the file key given */ /* in the TMPQFile structure also includes the key adjustment */ if(nError == ERROR_SUCCESS && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) { dwFileKey2 = dwFileKey1 = hf->dwFileKey; if(pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) { dwFileKey2 = (dwFileKey1 ^ pFileEntry->dwFileSize) - (uint32_t)pFileEntry->ByteOffset; dwFileKey2 = (dwFileKey2 + (uint32_t)MpqFilePos) ^ pFileEntry->dwFileSize; } } /* If we have to save patch header, do it */ if(nError == ERROR_SUCCESS && hf->pPatchInfo != NULL) { BSWAP_ARRAY32_UNSIGNED(hf->pPatchInfo, sizeof(uint32_t) * 3); if(!FileStream_Write(pNewStream, NULL, hf->pPatchInfo, hf->pPatchInfo->dwLength)) nError = GetLastError(); /* Save the size of the patch info */ dwPatchSize = hf->pPatchInfo->dwLength; } /* If we have to save sector offset table, do it. */ if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL) { uint32_t * SectorOffsetsCopy = STORM_ALLOC(uint32_t, hf->SectorOffsets[0] / sizeof(uint32_t)); uint32_t dwSectorOffsLen = hf->SectorOffsets[0]; assert((pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0); assert(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK); if(SectorOffsetsCopy == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; /* Encrypt the secondary sector offset table and write it to the target file */ if(nError == ERROR_SUCCESS) { memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen); if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwFileKey2 - 1); BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen); if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorOffsLen)) nError = GetLastError(); dwBytesToCopy -= dwSectorOffsLen; dwCmpSize += dwSectorOffsLen; } /* Update compact progress */ if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwSectorOffsLen; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); } STORM_FREE(SectorOffsetsCopy); } /* Now we have to copy all file sectors. We do it without */ /* recompression, because recompression is not necessary in this case */ if(nError == ERROR_SUCCESS) { uint32_t dwSector; for(dwSector = 0; dwSector < hf->dwSectorCount; dwSector++) { uint32_t dwRawDataInSector = hf->dwSectorSize; uint32_t dwRawByteOffset = dwSector * hf->dwSectorSize; /* 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]; } /* Last sector: If there is not enough bytes remaining in the file, cut the raw size */ if(dwRawDataInSector > dwBytesToCopy) dwRawDataInSector = dwBytesToCopy; /* 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. */ if((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) && dwFileKey1 != dwFileKey2) { if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPT_SERPENT) DecryptMpqBlockSerpent(hf->pbFileSector, dwRawDataInSector, &(ha->keyScheduleSerpent)); if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPT_ANUBIS) DecryptMpqBlockAnubis(hf->pbFileSector, dwRawDataInSector, &(ha->keyScheduleAnubis)); BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey1 + dwSector); EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey2 + dwSector); BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPT_ANUBIS) EncryptMpqBlockAnubis(hf->pbFileSector, dwRawDataInSector, &(ha->keyScheduleAnubis)); if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPT_SERPENT) EncryptMpqBlockSerpent(hf->pbFileSector, dwRawDataInSector, &(ha->keyScheduleSerpent)); } /* Now write the sector back to the file */ if(!FileStream_Write(pNewStream, NULL, hf->pbFileSector, dwRawDataInSector)) { nError = GetLastError(); break; } /* Update compact progress */ if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwRawDataInSector; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); } /* Adjust byte counts */ dwBytesToCopy -= dwRawDataInSector; dwCmpSize += dwRawDataInSector; } } /* Copy the sector CRCs, if any */ /* Sector CRCs are always compressed (not imploded) and unencrypted */ if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL && hf->SectorChksums != NULL) { uint32_t dwCrcLength; dwCrcLength = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount]; if(dwCrcLength != 0) { if(!FileStream_Read(ha->pStream, NULL, hf->SectorChksums, dwCrcLength)) nError = GetLastError(); if(!FileStream_Write(pNewStream, NULL, hf->SectorChksums, dwCrcLength)) nError = GetLastError(); /* Update compact progress */ if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwCrcLength; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); } /* Size of the CRC block is also included in the compressed file size */ dwBytesToCopy -= dwCrcLength; dwCmpSize += dwCrcLength; } } /* There might be extra data beyond sector checksum table * Sometimes, these data are even part of sector offset table * Examples: * 2012 - WoW\15354\locale-enGB.MPQ:DBFilesClient\SpellLevels.dbc * 2012 - WoW\15354\locale-enGB.MPQ:Interface\AddOns\Blizzard_AuctionUI\Blizzard_AuctionUI.xml */ if(nError == ERROR_SUCCESS && dwBytesToCopy != 0) { unsigned char * pbExtraData; /* Allocate space for the extra data */ pbExtraData = STORM_ALLOC(uint8_t, dwBytesToCopy); if(pbExtraData != NULL) { if(!FileStream_Read(ha->pStream, NULL, pbExtraData, dwBytesToCopy)) nError = GetLastError(); if(!FileStream_Write(pNewStream, NULL, pbExtraData, dwBytesToCopy)) nError = GetLastError(); /* Include these extra data in the compressed size */ dwCmpSize += dwBytesToCopy; STORM_FREE(pbExtraData); } else nError = ERROR_NOT_ENOUGH_MEMORY; } /* Write the MD5's of the raw file data, if needed */ if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0) { nError = WriteMpqDataMD5(pNewStream, ha->MpqPos + MpqFilePos, pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); } /* Verify the number of bytes written */ if(nError == ERROR_SUCCESS) { /* At this point, number of bytes written should be exactly * the same like the compressed file size. If it isn't, * there's something wrong (an unknown archive version, MPQ malformation, ...) * * Note: Diablo savegames have very weird layout, and the file "hero" * seems to have improper compressed size. Instead of real compressed size, * the "dwCmpSize" member of the block table entry contains * uncompressed size of file data + size of the sector table. * If we compact the archive, Diablo will refuse to load the game * * Note: Some patch files in WOW patches don't count the patch header * into compressed size */ if(!(dwCmpSize <= pFileEntry->dwCmpSize && pFileEntry->dwCmpSize <= dwCmpSize + dwPatchSize)) { nError = ERROR_FILE_CORRUPT; assert(0); } } return nError; }
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData = NULL; bool bIsReallyCompressed = false; int nError = ERROR_SUCCESS; // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; pbRawData = hf->pbFileSector; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; // If the file sector is not loaded yet, do it if(hf->dwSectorOffs != 0) { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { // Allocate space for compressed data pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; bIsReallyCompressed = true; pbRawData = pbCompressed; } // Load the raw (compressed, encrypted) data if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) { STORM_FREE(pbCompressed); return GetLastError(); } // If the file is encrypted, we have to decrypt the data first if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); } // // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: // // File CmpSize FileSize Data // -------------------------------------- ------- -------- --------------- // esES\DBFilesClient\LightSkyBox.dbc 0xBE 0xBC Is compressed // deDE\DBFilesClient\MountCapability.dbc 0x93 0x77 Is uncompressed // // Now tell me how to deal with this mess. // if(hf->pPatchInfo != NULL) { if(pbRawData[0] == 'P' && pbRawData[1] == 'T' && pbRawData[2] == 'C' && pbRawData[3] == 'H') { assert(pFileEntry->dwCmpSize >= hf->dwDataSize); bIsReallyCompressed = false; } } else { if(pFileEntry->dwCmpSize >= hf->dwDataSize) bIsReallyCompressed = false; } // If the file is compressed, we have to decompress it now if(bIsReallyCompressed) { int cbOutBuffer = (int)hf->dwDataSize; // Note: Single unit files compressed with IMPLODE are not supported by Blizzard if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) { if(!SCompExplode((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) nError = ERROR_FILE_CORRUPT; } if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { if(!SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) nError = ERROR_FILE_CORRUPT; } } else { if(pbRawData != hf->pbFileSector) memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); } // Free the decompression buffer. if(pbCompressed != NULL) STORM_FREE(pbCompressed); // The file sector is now properly loaded hf->dwSectorOffs = 0; } // At this moment, we have the file loaded into the file buffer. // Copy as much as the caller wants if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) { // File position is greater or equal to file size ? if(dwFilePos >= hf->dwDataSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // If not enough bytes remaining in the file, cut them if((hf->dwDataSize - dwFilePos) < dwToRead) dwToRead = (hf->dwDataSize - dwFilePos); // Copy the bytes memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); // Give the number of bytes read *pdwBytesRead = dwToRead; return ERROR_SUCCESS; } // An error, sorry return ERROR_CAN_NOT_COMPLETE; }
BOOL SFileOpenArchiveEx( const char * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMPQ, DWORD dwAccessMode) { LARGE_INTEGER TempPos; TMPQArchive * ha = NULL; // Archive handle HANDLE hFile = INVALID_HANDLE_VALUE;// Opened archive file handle DWORD dwMaxBlockIndex = 0; // Maximum value of block entry DWORD dwBlockTableSize = 0; // Block table size. DWORD dwTransferred; // Number of bytes read DWORD dwBytes = 0; // Number of bytes to read int nError = ERROR_SUCCESS; // Check the right parameters if(nError == ERROR_SUCCESS) { if(szMpqName == NULL || *szMpqName == 0 || phMPQ == NULL) nError = ERROR_INVALID_PARAMETER; } // Ensure that StormBuffer is allocated if(nError == ERROR_SUCCESS) nError = PrepareStormBuffer(); // Open the MPQ archive file if(nError == ERROR_SUCCESS) { hFile = CreateFile(szMpqName, dwAccessMode, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) nError = GetLastError(); } // Allocate the MPQhandle if(nError == ERROR_SUCCESS) { if((ha = ALLOCMEM(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize handle structure and allocate structure for MPQ header if(nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); strncpy(ha->szFileName, szMpqName, strlen(szMpqName)); ha->hFile = hFile; ha->dwPriority = dwPriority; ha->pHeader = &ha->Header; ha->pListFile = NULL; hFile = INVALID_HANDLE_VALUE; } // Find the offset of MPQ header within the file if(nError == ERROR_SUCCESS) { LARGE_INTEGER SearchPos = {0}; LARGE_INTEGER MpqPos = {0}; DWORD dwHeaderID; for(;;) { // Invalidate the MPQ ID and read the eventual header SetFilePointer(ha->hFile, MpqPos.LowPart, &MpqPos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pHeader, sizeof(TMPQHeader2), &dwTransferred, NULL); dwHeaderID = BSWAP_INT32_UNSIGNED(ha->pHeader->dwID); // Special check : Some MPQs are actually AVI files, only with // changed extension. if(MpqPos.QuadPart == 0 && IsAviFile(ha->pHeader)) { nError = ERROR_AVI_FILE; break; } // If different number of bytes read, break the loop if(dwTransferred != sizeof(TMPQHeader2)) { nError = ERROR_BAD_FORMAT; break; } // If there is the MPQ shunt signature, process it if(dwHeaderID == ID_MPQ_SHUNT && ha->pShunt == NULL) { // Fill the shunt header ha->ShuntPos = MpqPos; ha->pShunt = &ha->Shunt; memcpy(ha->pShunt, ha->pHeader, sizeof(TMPQShunt)); BSWAP_TMPQSHUNT(ha->pShunt); // Set the MPQ pos and repeat the search MpqPos.QuadPart = SearchPos.QuadPart + ha->pShunt->dwHeaderPos; continue; } // There must be MPQ header signature if(dwHeaderID == ID_MPQ) { BSWAP_TMPQHEADER(ha->pHeader); // Save the position where the MPQ header has been found ha->MpqPos = MpqPos; // If valid signature has been found, break the loop if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) { // W3M Map Protectors set some garbage value into the "dwHeaderSize" // field of MPQ header. This value is apparently ignored by Storm.dll if(ha->pHeader->dwHeaderSize != sizeof(TMPQHeader) && ha->pHeader->dwHeaderSize != sizeof(TMPQHeader2)) { ha->dwFlags |= MPQ_FLAG_PROTECTED; ha->pHeader->dwHeaderSize = sizeof(TMPQHeader); } if(ha->pHeader->dwHashTablePos < ha->pHeader->dwArchiveSize && ha->pHeader->dwBlockTablePos < ha->pHeader->dwArchiveSize) { break; } } if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_2) { break; } nError = ERROR_NOT_SUPPORTED; break; } // If a MPQ shunt already has been found, // and no MPQ header was at potision pointed by the shunt, // then the archive is corrupt if(ha->pShunt != NULL) { nError = ERROR_BAD_FORMAT; break; } // Move to the next possible offset SearchPos.QuadPart += 0x200; MpqPos = SearchPos; } } // Relocate tables position if(nError == ERROR_SUCCESS) { // Clear the fields not supported in older formats if(ha->pHeader->wFormatVersion < MPQ_FORMAT_VERSION_2) { ha->pHeader->ExtBlockTablePos.QuadPart = 0; ha->pHeader->wBlockTablePosHigh = 0; ha->pHeader->wHashTablePosHigh = 0; } ha->dwBlockSize = (0x200 << ha->pHeader->wBlockSize); nError = RelocateMpqTablePositions(ha); } // Allocate buffers if(nError == ERROR_SUCCESS) { // // Note that the block table should be as large as the hash table // (For later file additions). // // I have found a MPQ which has the block table larger than // the hash table. We should avoid buffer overruns caused by that. // dwBlockTableSize = max(ha->pHeader->dwHashTableSize, ha->pHeader->dwBlockTableSize); ha->pHashTable = ALLOCMEM(TMPQHash, ha->pHeader->dwHashTableSize); ha->pBlockTable = ALLOCMEM(TMPQBlock, dwBlockTableSize); ha->pExtBlockTable = ALLOCMEM(TMPQBlockEx, dwBlockTableSize); ha->pbBlockBuffer = ALLOCMEM(BYTE, ha->dwBlockSize); if(!ha->pHashTable || !ha->pBlockTable || !ha->pExtBlockTable || !ha->pbBlockBuffer) nError = ERROR_NOT_ENOUGH_MEMORY; } // Read the hash table into memory if(nError == ERROR_SUCCESS) { dwBytes = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); SetFilePointer(ha->hFile, ha->HashTablePos.LowPart, &ha->HashTablePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pHashTable, dwBytes, &dwTransferred, NULL); if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } // Decrypt hash table and check if it is correctly decrypted if(nError == ERROR_SUCCESS) { TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; TMPQHash * pHash; // We have to convert the hash table from LittleEndian BSWAP_ARRAY32_UNSIGNED((DWORD *)ha->pHashTable, (dwBytes / sizeof(DWORD))); DecryptHashTable((DWORD *)ha->pHashTable, (BYTE *)"(hash table)", (ha->pHeader->dwHashTableSize * 4)); // Check hash table if is correctly decrypted for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) { // Note: Some MPQs from World of Warcraft have wPlatform set to 0x0100. // If not free or deleted hash entry, check for valid values if(pHash->dwBlockIndex < HASH_ENTRY_DELETED) { // The block index should not be larger than size of the block table if(pHash->dwBlockIndex > ha->pHeader->dwBlockTableSize) { nError = ERROR_BAD_FORMAT; break; } // Remember the highest block table entry if(pHash->dwBlockIndex > dwMaxBlockIndex) dwMaxBlockIndex = pHash->dwBlockIndex; } } } // Now, read the block table if(nError == ERROR_SUCCESS) { memset(ha->pBlockTable, 0, dwBlockTableSize * sizeof(TMPQBlock)); dwBytes = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); SetFilePointer(ha->hFile, ha->BlockTablePos.LowPart, &ha->BlockTablePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pBlockTable, dwBytes, &dwTransferred, NULL); // We have to convert every DWORD in ha->block from LittleEndian BSWAP_ARRAY32_UNSIGNED((DWORD *)ha->pBlockTable, dwBytes / sizeof(DWORD)); if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } // Decrypt block table. // Some MPQs don't have Decrypted block table, e.g. cracked Diablo version // We have to check if block table is really encrypted if(nError == ERROR_SUCCESS) { TMPQBlock * pBlockEnd = ha->pBlockTable + ha->pHeader->dwBlockTableSize; TMPQBlock * pBlock = ha->pBlockTable; BOOL bBlockTableEncrypted = FALSE; // Verify all blocks entries in the table // The loop usually stops at the first entry while(pBlock < pBlockEnd) { // The lower 8 bits of the MPQ flags are always zero. // Note that this may change in next MPQ versions if(pBlock->dwFlags & 0x000000FF) { bBlockTableEncrypted = TRUE; break; } // Move to the next block table entry pBlock++; } if(bBlockTableEncrypted) { DecryptBlockTable((DWORD *)ha->pBlockTable, (BYTE *)"(block table)", (ha->pHeader->dwBlockTableSize * 4)); } } // Now, read the extended block table. // For V1 archives, we still will maintain the extended block table // (it will be filled with zeros) // TODO: Test with >4GB if(nError == ERROR_SUCCESS) { memset(ha->pExtBlockTable, 0, dwBlockTableSize * sizeof(TMPQBlockEx)); if(ha->pHeader->ExtBlockTablePos.QuadPart != 0) { dwBytes = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlockEx); SetFilePointer(ha->hFile, ha->ExtBlockTablePos.LowPart, &ha->ExtBlockTablePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pExtBlockTable, dwBytes, &dwTransferred, NULL); // We have to convert every DWORD in ha->block from LittleEndian BSWAP_ARRAY16_UNSIGNED((USHORT *)ha->pExtBlockTable, dwBytes / sizeof(USHORT)); // The extended block table is not encrypted (so far) if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } } // Verify the both block tables (If the MPQ file is not protected) if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0) { TMPQBlockEx * pBlockEx = ha->pExtBlockTable; TMPQBlock * pBlockEnd = ha->pBlockTable + dwMaxBlockIndex + 1; TMPQBlock * pBlock = ha->pBlockTable; // If the MPQ file is not protected, // we will check if all sizes in the block table is correct. // Note that we will not relocate the block table (change from previous versions) for(; pBlock < pBlockEnd; pBlock++, pBlockEx++) { if(pBlock->dwFlags & MPQ_FILE_EXISTS) { // Get the 64-bit file position TempPos.HighPart = pBlockEx->wFilePosHigh; TempPos.LowPart = pBlock->dwFilePos; if(TempPos.QuadPart > ha->MpqSize.QuadPart || pBlock->dwCSize > ha->MpqSize.QuadPart) { nError = ERROR_BAD_FORMAT; break; } } } } // If the user didn't specified otherwise, // include the internal listfile to the TMPQArchive structure if((dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { if(nError == ERROR_SUCCESS) SListFileCreateListFile(ha); // Add the internal listfile if(nError == ERROR_SUCCESS) SFileAddListFile((HANDLE)ha, NULL); } // Cleanup and exit if(nError != ERROR_SUCCESS) { FreeMPQArchive(ha); if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); SetLastError(nError); } else { if(pFirstOpen == NULL) pFirstOpen = ha; } *phMPQ = ha; return (nError == ERROR_SUCCESS); }
// hf - MPQ File handle. // pbBuffer - Pointer to target buffer to store sectors. // dwByteOffset - Position of sector in the file (relative to file begin) // dwBytesToRead - Number of bytes to read. Must be multiplier of sector size. // pdwBytesRead - Stored number of bytes loaded static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbRawSector = NULL; LPBYTE pbOutSector = pbBuffer; LPBYTE pbInSector = pbBuffer; DWORD dwRawBytesToRead; DWORD dwRawSectorOffset = dwByteOffset; DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize; DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize; DWORD dwSectorsDone = 0; DWORD dwBytesRead = 0; int nError = ERROR_SUCCESS; // Note that dwByteOffset must be aligned to size of one sector // Note that dwBytesToRead must be a multiplier of one sector size // This is local function, so we won't check if that's true. // Note that files stored in single units are processed by a separate function // If there is not enough bytes remaining, cut dwBytesToRead if((dwByteOffset + dwBytesToRead) > hf->dwDataSize) dwBytesToRead = hf->dwDataSize - dwByteOffset; dwRawBytesToRead = dwBytesToRead; // Perform all necessary work to do with compressed files if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) { // If the sector positions are not loaded yet, do it if(hf->SectorOffsets == NULL) { nError = AllocateSectorOffsets(hf, true); if(nError != ERROR_SUCCESS) return nError; } // If the sector checksums are not loaded yet, load them now. if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)) { nError = AllocateSectorChecksums(hf, true); if(nError != ERROR_SUCCESS) return nError; } // If the file is compressed, also allocate secondary buffer pbInSector = pbRawSector = ALLOCMEM(BYTE, dwBytesToRead); if(pbRawSector == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Assign the temporary buffer as target for read operation dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex]; dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset; } // Calculate raw file offset where the sector(s) are stored. CalculateRawSectorOffset(RawFilePos, hf, dwRawSectorOffset); // Set file pointer and read all required sectors if(!FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead)) return GetLastError(); dwBytesRead = 0; // Now we have to decrypt and decompress all file sectors that have been loaded for(DWORD i = 0; i < dwSectorsToRead; i++) { DWORD dwRawBytesInThisSector = ha->dwSectorSize; DWORD dwBytesInThisSector = ha->dwSectorSize; DWORD dwIndex = dwSectorIndex + i; // If there is not enough bytes in the last sector, // cut the number of bytes in this sector if(dwRawBytesInThisSector > dwBytesToRead) dwRawBytesInThisSector = dwBytesToRead; if(dwBytesInThisSector > dwBytesToRead) dwBytesInThisSector = dwBytesToRead; // If the file is compressed, we have to adjust the raw sector size if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex]; // If the file is encrypted, we have to decrypt the sector if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); // If we don't know the key, try to detect it by file content if(hf->dwFileKey == 0) { hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector); if(hf->dwFileKey == 0) { nError = ERROR_UNKNOWN_FILE_KEY; break; } } DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex); BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); } // If the file has sector CRC check turned on, perform it if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL) { DWORD dwAdlerExpected = hf->SectorChksums[dwIndex]; DWORD dwAdlerValue = 0; // We can only check sector CRC when it's not zero // Neither can we check it if it's 0xFFFFFFFF. if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF) { dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector); if(dwAdlerValue != dwAdlerExpected) { nError = ERROR_CHECKSUM_ERROR; break; } } } // If the sector is really compressed, decompress it. // WARNING : Some sectors may not be compressed, it can be determined only // by comparing uncompressed and compressed size !!! if(dwRawBytesInThisSector < dwBytesInThisSector) { int cbOutSector = dwBytesInThisSector; int cbInSector = dwRawBytesInThisSector; int nResult = 0; // Is the file compressed by PKWARE Data Compression Library ? if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); // Is the file compressed by Blizzard's multiple compression ? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) nResult = SCompDecompress((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); // Did the decompression fail ? if(nResult == 0) { nError = ERROR_FILE_CORRUPT; break; } } else { if(pbOutSector != pbInSector) memcpy(pbOutSector, pbInSector, dwBytesInThisSector); } // Move pointers dwBytesToRead -= dwBytesInThisSector; dwByteOffset += dwBytesInThisSector; dwBytesRead += dwBytesInThisSector; pbOutSector += dwBytesInThisSector; pbInSector += dwRawBytesInThisSector; dwSectorsDone++; } // Free all used buffers if(pbRawSector != NULL) FREEMEM(pbRawSector); // Give the caller thenumber of bytes read *pdwBytesRead = dwBytesRead; return nError; }
// TODO: Test for archives > 4GB static DWORD WINAPI ReadMPQBlocks(TMPQFile * hf, DWORD dwBlockPos, BYTE * buffer, DWORD blockBytes) { LARGE_INTEGER FilePos; TMPQArchive * ha = hf->ha; // Archive handle BYTE * tempBuffer = NULL; // Buffer for reading compressed data from the file DWORD dwFilePos = dwBlockPos; // Reading position from the file DWORD dwToRead; // Number of bytes to read DWORD blockNum; // Block number (needed for decrypt) DWORD dwBytesRead = 0; // Total number of bytes read DWORD bytesRemain = 0; // Number of data bytes remaining up to the end of the file DWORD nBlocks; // Number of blocks to load DWORD i; // Test parameters. Block position and block size must be block-aligned, block size nonzero if((dwBlockPos & (ha->dwBlockSize - 1)) || blockBytes == 0) return 0; // Check the end of file if((dwBlockPos + blockBytes) > hf->pBlock->dwFSize) blockBytes = hf->pBlock->dwFSize - dwBlockPos; bytesRemain = hf->pBlock->dwFSize - dwBlockPos; blockNum = dwBlockPos / ha->dwBlockSize; nBlocks = blockBytes / ha->dwBlockSize; if(blockBytes % ha->dwBlockSize) nBlocks++; // If file has variable block positions, we have to load them if((hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) && hf->bBlockPosLoaded == FALSE) { // Move file pointer to the begin of the file in the MPQ if(hf->MpqFilePos.QuadPart != ha->FilePointer.QuadPart) { SetFilePointer(ha->hFile, hf->MpqFilePos.LowPart, &hf->MpqFilePos.HighPart, FILE_BEGIN); } // Read block positions from begin of file. dwToRead = (hf->nBlocks+1) * sizeof(DWORD); if(hf->pBlock->dwFlags & MPQ_FILE_HAS_EXTRA) dwToRead += sizeof(DWORD); // Read the block pos table and convert the buffer to little endian ReadFile(ha->hFile, hf->pdwBlockPos, dwToRead, &dwBytesRead, NULL); BSWAP_ARRAY32_UNSIGNED(hf->pdwBlockPos, (hf->nBlocks+1)); // // If the archive if protected some way, perform additional check // Sometimes, the file appears not to be encrypted, but it is. // // Note: In WoW 1.10+, there's a new flag. With this flag present, // there's one additional entry in the block table. // if(hf->pdwBlockPos[0] != dwBytesRead) hf->pBlock->dwFlags |= MPQ_FILE_ENCRYPTED; // Decrypt loaded block positions if necessary if(hf->pBlock->dwFlags & MPQ_FILE_ENCRYPTED) { // If we don't know the file seed, try to find it. if(hf->dwSeed1 == 0) hf->dwSeed1 = DetectFileSeed(hf->pdwBlockPos, dwBytesRead); // If we don't know the file seed, sorry but we cannot extract the file. if(hf->dwSeed1 == 0) return 0; // Decrypt block positions DecryptMPQBlock(hf->pdwBlockPos, dwBytesRead, hf->dwSeed1 - 1); // Check if the block positions are correctly decrypted // I don't know why, but sometimes it will result invalid block positions on some files if(hf->pdwBlockPos[0] != dwBytesRead) { // Try once again to detect file seed and decrypt the blocks // TODO: Test with >4GB SetFilePointer(ha->hFile, hf->MpqFilePos.LowPart, &hf->MpqFilePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, hf->pdwBlockPos, dwToRead, &dwBytesRead, NULL); BSWAP_ARRAY32_UNSIGNED(hf->pdwBlockPos, (hf->nBlocks+1)); hf->dwSeed1 = DetectFileSeed(hf->pdwBlockPos, dwBytesRead); DecryptMPQBlock(hf->pdwBlockPos, dwBytesRead, hf->dwSeed1 - 1); // Check if the block positions are correctly decrypted if(hf->pdwBlockPos[0] != dwBytesRead) return 0; } } // Update hf's variables ha->FilePointer.QuadPart = hf->MpqFilePos.QuadPart + dwBytesRead; hf->bBlockPosLoaded = TRUE; } // Get file position and number of bytes to read dwFilePos = dwBlockPos; dwToRead = blockBytes; if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) { dwFilePos = hf->pdwBlockPos[blockNum]; dwToRead = hf->pdwBlockPos[blockNum + nBlocks] - dwFilePos; } FilePos.QuadPart = hf->MpqFilePos.QuadPart + dwFilePos; // Get work buffer for store read data tempBuffer = buffer; if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) { if((tempBuffer = ALLOCMEM(BYTE, dwToRead)) == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } } // Set file pointer, if necessary if(ha->FilePointer.QuadPart != FilePos.QuadPart) { SetFilePointer(ha->hFile, FilePos.LowPart, &FilePos.HighPart, FILE_BEGIN); } // 15018F87 : Read all requested blocks ReadFile(ha->hFile, tempBuffer, dwToRead, &dwBytesRead, NULL); ha->FilePointer.QuadPart = FilePos.QuadPart + dwBytesRead; // Block processing part. DWORD blockStart = 0; // Index of block start in work buffer DWORD blockSize = min(blockBytes, ha->dwBlockSize); DWORD index = blockNum; // Current block index dwBytesRead = 0; // Clear read byte counter // Walk through all blocks for(i = 0; i < nBlocks; i++, index++) { BYTE * inputBuffer = tempBuffer + blockStart; int outLength = ha->dwBlockSize; if(bytesRemain < (DWORD)outLength) outLength = bytesRemain; // Get current block length if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) blockSize = hf->pdwBlockPos[index+1] - hf->pdwBlockPos[index]; // If block is encrypted, we have to decrypt it. if(hf->pBlock->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED((DWORD *)inputBuffer, blockSize / sizeof(DWORD)); // If we don't know the seed, try to decode it as WAVE file if(hf->dwSeed1 == 0) hf->dwSeed1 = DetectFileSeed2((DWORD *)inputBuffer, 3, ID_WAVE, hf->pBlock->dwFSize - 8, 0x45564157); // Let's try MSVC's standard EXE or header if(hf->dwSeed1 == 0) hf->dwSeed1 = DetectFileSeed2((DWORD *)inputBuffer, 2, 0x00905A4D, 0x00000003); if(hf->dwSeed1 == 0) return 0; DecryptMPQBlock((DWORD *)inputBuffer, blockSize, hf->dwSeed1 + index); BSWAP_ARRAY32_UNSIGNED((DWORD *)inputBuffer, blockSize / sizeof(DWORD)); } // If the block is really compressed, decompress it. // WARNING : Some block may not be compressed, it can be determined only // by comparing uncompressed and compressed size !!! if(blockSize < (DWORD)outLength) { // Is the file compressed with PKWARE Data Compression Library ? if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESS_PKWARE) Decompress_pklib((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize); // Is it a file compressed by Blizzard's multiple compression ? // Note that Storm.dll v 1.0.9 distributed with Warcraft III // passes the full path name of the opened archive as the new last parameter if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESS_MULTI) SCompDecompress((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize); dwBytesRead += outLength; buffer += outLength; } else { if(buffer != inputBuffer) memcpy(buffer, inputBuffer, blockSize); dwBytesRead += blockSize; buffer += blockSize; } blockStart += blockSize; bytesRemain -= outLength; } // Delete input buffer, if necessary if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) FREEMEM(tempBuffer); return dwBytesRead; }