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; 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_COMPRESSED) { // 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_COMPRESSED) { 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) { if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) nResult = SCompDecompress2((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, cbInBuffer); else nResult = SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)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((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, cbInBuffer); nError = (nResult != 0) ? ERROR_SUCCESS : 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; }
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; }
// 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) && hf->bLoadedSectorCRCs == false) { // // Sector CRCs is plain crap feature. It is almost never present, // often it's empty, or the end offset of sector CRCs is zero. // We only try to load sector CRCs once, and regardless if it fails // or not, we won't try that again for the given file. // AllocateSectorChecksums(hf, true); hf->bLoadedSectorCRCs = true; } // TODO: If the raw data MD5s are not loaded yet, load them now // Only do it if the MPQ is of format 4.0 // if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0) // { // nError = AllocateRawMD5s(hf, true); // if(nError != ERROR_SUCCESS) // return nError; // } // If the file is compressed, also allocate secondary buffer pbInSector = pbRawSector = STORM_ALLOC(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 Blizzard's multiple compression ? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) nResult = SCompDecompress2((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); else nResult = SCompDecompress((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); } // Is the file compressed by PKWARE Data Compression Library ? else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) { nResult = SCompExplode((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) STORM_FREE(pbRawSector); // Give the caller thenumber of bytes read *pdwBytesRead = dwBytesRead; 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 = (DWORD *)STORM_ALLOC(BYTE, hf->SectorOffsets[0]); DWORD dwSectorOffsLen = hf->SectorOffsets[0]; 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, 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(CompactCB != NULL) { CompactBytesProcessed += dwSectorOffsLen; CompactCB(pvUserData, CCB_COMPACTING_FILES, CompactBytesProcessed, 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(CompactCB != NULL) { CompactBytesProcessed += dwRawDataInSector; CompactCB(pvUserData, CCB_COMPACTING_FILES, CompactBytesProcessed, 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(CompactCB != NULL) { CompactBytesProcessed += dwCrcLength; CompactCB(pvUserData, CCB_COMPACTING_FILES, CompactBytesProcessed, 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; dwBytesToCopy = 0; 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 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. // // 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 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->PatchInfo != NULL) RawFilePos += hf->PatchInfo->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->PatchInfo != 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; }
// Copies all file sectors into another archive. static int CopyMpqFileSectors( TMPQArchive * ha, TMPQFile * hf, TFileStream * pNewStream) { LARGE_INTEGER RawFilePos; // Used for calculating sector offset in the old MPQ archive LARGE_INTEGER MpqFilePos; // MPQ file position in the new archive TMPQBlockEx * pBlockEx = hf->pBlockEx; TMPQBlock * pBlock = hf->pBlock; DWORD dwBytesToCopy = pBlock->dwCSize; DWORD dwFileKey1 = 0; // File key used for decryption DWORD dwFileKey2 = 0; // File key used for encryption DWORD dwCSize = 0; // Compressed file size int nError = ERROR_SUCCESS; // Remember the position in the destination file FileStream_GetPos(pNewStream, &MpqFilePos); MpqFilePos.QuadPart -= ha->MpqPos.QuadPart; // Resolve decryption keys. Note that the file key given // in the TMPQFile structure also includes the key adjustment if(nError == ERROR_SUCCESS && (pBlock->dwFlags & MPQ_FILE_ENCRYPTED)) { dwFileKey2 = dwFileKey1 = hf->dwFileKey; if(pBlock->dwFlags & MPQ_FILE_FIX_KEY) { dwFileKey2 = (dwFileKey1 ^ pBlock->dwFSize) - pBlock->dwFilePos; dwFileKey2 = (dwFileKey2 + MpqFilePos.LowPart) ^ pBlock->dwFSize; } } // If we have to save sector offset table, do it. if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL) { DWORD * SectorOffsetsCopy = ALLOCMEM(DWORD, hf->dwSectorCount); DWORD dwSectorPosLen = hf->dwSectorCount * sizeof(DWORD); assert((pBlock->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0); assert(pBlock->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(pBlock->dwFlags & MPQ_FILE_ENCRYPTED) EncryptMpqBlock(SectorOffsetsCopy, dwSectorPosLen, dwFileKey2 - 1); BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorPosLen); if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorPosLen)) nError = GetLastError(); dwCSize += dwSectorPosLen; } // Update compact progress if(CompactCB != NULL) { CompactBytesProcessed.QuadPart += 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((pBlock->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.QuadPart += dwRawDataInSector; CompactCB(pvUserData, CCB_COMPACTING_FILES, &CompactBytesProcessed, &CompactTotalBytes); } // Adjust byte counts dwBytesToCopy -= hf->dwSectorSize; dwCSize += 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.QuadPart += dwCrcLength; CompactCB(pvUserData, CCB_COMPACTING_FILES, &CompactBytesProcessed, &CompactTotalBytes); } // Size of the CRC block is also included in the compressed file size dwCSize += 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 "dwCSize" 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(dwCSize == pBlock->dwCSize) { // Update file pos in the block table pBlockEx->wFilePosHigh = (USHORT)MpqFilePos.HighPart; pBlock->dwFilePos = MpqFilePos.LowPart; } 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; }