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