// 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; }
// Renames the file within the archive. bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName) { TMPQArchive * ha = IsValidMpqHandle(hMpq); TMPQFile * hf; int nError = ERROR_SUCCESS; // Test the valid parameters if(ha == NULL) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0) nError = ERROR_INVALID_PARAMETER; if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName)) nError = ERROR_INTERNAL_FILE; // Do not allow to rename files in MPQ open for read only if(nError == ERROR_SUCCESS) { if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; } // Open the new file. If exists, we don't allow rename operation if(nError == ERROR_SUCCESS) { if(GetFileEntryLocale(ha, szNewFileName, lcFileLocale) != NULL) nError = ERROR_ALREADY_EXISTS; } // Open the file from the MPQ if(nError == ERROR_SUCCESS) { // Attempt to open the file if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf)) { ULONGLONG RawDataOffs; TFileEntry * pFileEntry = hf->pFileEntry; // Invalidate the entries for internal files InvalidateInternalFiles(ha); // Rename the file entry in the table nError = RenameFileEntry(ha, hf, szNewFileName); // If the file is encrypted, we have to re-crypt the file content // with the new decryption key if((nError == ERROR_SUCCESS) && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) { // Recrypt the file data in the MPQ nError = RecryptFileData(ha, hf, szFileName, szNewFileName); // Update the MD5 of the raw block if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0) { RawDataOffs = ha->MpqPos + pFileEntry->ByteOffset; WriteMpqDataMD5(ha->pStream, RawDataOffs, pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); } } // Free the file handle FreeFileHandle(hf); } else { nError = GetLastError(); } } // We also need to rebuild the HET table, if present if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) nError = RebuildHetTable(ha); // Resolve error and exit if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }
int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD dwCompression) { TMPQArchive * ha; TFileEntry * pFileEntry; int nError = ERROR_SUCCESS; // Don't bother if the caller gave us zero size if(pvData == NULL || dwSize == 0) return ERROR_SUCCESS; // Get pointer to the MPQ archive pFileEntry = hf->pFileEntry; ha = hf->ha; // Allocate file buffers if(hf->pbFileSector == NULL) { ULONGLONG RawFilePos = hf->RawFilePos; // Allocate buffer for file sector hf->nAddFileError = nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; // Allocate patch info, if the data is patch if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize)) { // Set the MPQ_FILE_PATCH_FILE flag hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE; // Allocate the patch info hf->nAddFileError = nError = AllocatePatchInfo(hf, false); if(nError != ERROR_SUCCESS) return nError; } // Allocate sector offsets if(hf->SectorOffsets == NULL) { hf->nAddFileError = nError = AllocateSectorOffsets(hf, false); if(nError != ERROR_SUCCESS) return nError; } // Create array of sector checksums if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)) { hf->nAddFileError = nError = AllocateSectorChecksums(hf, false); if(nError != ERROR_SUCCESS) return nError; } // Pre-save the patch info, if any if(hf->pPatchInfo != NULL) { if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pPatchInfo, hf->pPatchInfo->dwLength)) nError = GetLastError(); pFileEntry->dwCmpSize += hf->pPatchInfo->dwLength; RawFilePos += hf->pPatchInfo->dwLength; } // Pre-save the sector offset table, just to reserve space in the file. // Note that we dont need to swap the sector positions, nor encrypt the table // at the moment, as it will be written again after writing all file sectors. if(hf->SectorOffsets != NULL) { if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, hf->SectorOffsets[0])) nError = GetLastError(); pFileEntry->dwCmpSize += hf->SectorOffsets[0]; RawFilePos += hf->SectorOffsets[0]; } } // Write the MPQ data to the file if(nError == ERROR_SUCCESS) { // Save the first sector compression to the file structure // Note that the entire first file sector will be compressed // by compression that was passed to the first call of SFileAddFile_Write if(hf->dwFilePos == 0) hf->dwCompression0 = dwCompression; // Write the data to the MPQ nError = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression); } // If it succeeded and we wrote all the file data, // we need to re-save sector offset table if(nError == ERROR_SUCCESS) { if(hf->dwFilePos >= pFileEntry->dwFileSize) { // Finish calculating CRC32 hf->pFileEntry->dwCrc32 = hf->dwCrc32; // Finish calculating MD5 md5_done((hash_state *)hf->hctx, hf->pFileEntry->md5); // If we also have sector checksums, write them to the file if(hf->SectorChksums != NULL) { nError = WriteSectorChecksums(hf); } // Now write patch info if(hf->pPatchInfo != NULL) { memcpy(hf->pPatchInfo->md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE); hf->pPatchInfo->dwDataSize = hf->pFileEntry->dwFileSize; hf->pFileEntry->dwFileSize = hf->dwPatchedFileSize; nError = WritePatchInfo(hf); } // Now write sector offsets to the file if(hf->SectorOffsets != NULL) { nError = WriteSectorOffsets(hf); } // Write the MD5 hashes of each file chunk, if required if(ha->pHeader->dwRawChunkSize != 0) { nError = WriteMpqDataMD5(ha->pStream, ha->MpqPos + hf->pFileEntry->ByteOffset, hf->pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); } } } // Store the error code from the Write File operation hf->nAddFileError = nError; 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; }