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