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; }