// Used in SFileGetFileInfo bool QueryMpqSignatureInfo( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI) { TFileEntry * pFileEntry; ULONGLONG ExtraBytes; DWORD dwFileSize; // Make sure it's all zeroed memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO)); // Calculate the range of the MPQ CalculateArchiveRange(ha, pSI); // If there is "(signature)" file in the MPQ, it has a weak signature pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Calculate the begin and end of the signature file itself pSI->BeginExclude = ha->MpqPos + pFileEntry->ByteOffset; pSI->EndExclude = pSI->BeginExclude + pFileEntry->dwCmpSize; dwFileSize = (DWORD)(pSI->EndExclude - pSI->BeginExclude); // Does the signature have proper size? if(dwFileSize == MPQ_SIGNATURE_FILE_SIZE) { // Read the weak signature if(!FileStream_Read(ha->pStream, &pSI->BeginExclude, pSI->Signature, dwFileSize)) return false; pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK; pSI->cbSignatureSize = dwFileSize; return true; } } // If there is extra bytes beyond the end of the archive, // it's the strong signature ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) { // Read the strong signature if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) return false; // Check the signature header "NGIS" if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') return false; pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG; return true; } // Succeeded, but no known signature found return true; }
static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG FilePosition1 = dwFilePos; ULONGLONG FilePosition2; DWORD dwBytesRead = 0; int nError = ERROR_SUCCESS; assert(hf->pStream != NULL); // Because stream I/O functions are designed to read // "all or nothing", we compare file position before and after, // and if they differ, we assume that number of bytes read // is the difference between them if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead)) { // If not all bytes have been read, then return the number of bytes read if((nError = GetLastError()) == ERROR_HANDLE_EOF) { FileStream_GetPos(hf->pStream, &FilePosition2); dwBytesRead = (DWORD)(FilePosition2 - FilePosition1); } } else { dwBytesRead = dwToRead; } *pdwBytesRead = dwBytesRead; return nError; }
// Calculate begin and end of the MPQ archive static void CalculateArchiveRange( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI) { ULONGLONG TempPos = 0; char szMapHeader[0x200]; // Get the MPQ begin pSI->BeginMpqData = ha->MpqPos; // Warcraft III maps are signed from the map header to the end if(FileStream_Read(ha->pStream, &TempPos, szMapHeader, sizeof(szMapHeader))) { // Is it a map header ? if(szMapHeader[0] == 'H' && szMapHeader[1] == 'M' && szMapHeader[2] == '3' && szMapHeader[3] == 'W') { // We will have to hash since the map header pSI->BeginMpqData = 0; } } // Get the MPQ data end. This is stored in our MPQ header, // and it's been already prepared by SFileOpenArchive, pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64; // Get the size of the entire file FileStream_GetSize(ha->pStream, pSI->EndOfFile); }
void * ListFile_OpenExternal(const TCHAR * szListFile) { PLISTFILE_CACHE pCache = NULL; TFileStream * pStream; ULONGLONG FileSize = 0; // Open the external listfile pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY); if(pStream != NULL) { // Retrieve the size of the external listfile FileStream_GetSize(pStream, &FileSize); if(0 < FileSize && FileSize <= 0x30000000) { // Create the in-memory cache for the entire listfile // The listfile does not have any data loaded yet pCache = CreateListFileCache((DWORD)FileSize); if(pCache != NULL) { if(!FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize)) { ListFile_Free(pCache); pCache = NULL; } } } // Close the file stream FileStream_Close(pStream); } return pCache; }
static bool CalculateMpqHashSha1(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI, unsigned char * sha1_tail0, unsigned char * sha1_tail1, unsigned char * sha1_tail2) { ULONGLONG BeginBuffer; hash_state sha1_state_temp; hash_state sha1_state; LPBYTE pbDigestBuffer = NULL; // Allocate buffer for creating the MPQ digest. pbDigestBuffer = ALLOCMEM(BYTE, MPQ_DIGEST_UNIT_SIZE); if (pbDigestBuffer == NULL) return false; // Initialize SHA1 state structure sha1_init(&sha1_state); // Calculate begin of data to be hashed BeginBuffer = pSI->BeginMpqData; // Create the digest for (;;) { ULONGLONG BytesRemaining; DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE; // Check the number of bytes remaining BytesRemaining = pSI->EndMpqData - BeginBuffer; if (BytesRemaining < MPQ_DIGEST_UNIT_SIZE) dwToRead = (DWORD)BytesRemaining; if (dwToRead == 0) break; // Read the next chunk if (!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead)) { FREEMEM(pbDigestBuffer); return false; } // Pass the buffer to the hashing function sha1_process(&sha1_state, pbDigestBuffer, dwToRead); // Move pointers BeginBuffer += dwToRead; } // Add all three known tails and generate three hashes memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); sha1_done(&sha1_state_temp, sha1_tail0); memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); AddTailToSha1(&sha1_state_temp, GetPlainFileName(ha->pStream->szFileName)); sha1_done(&sha1_state_temp, sha1_tail1); memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); AddTailToSha1(&sha1_state_temp, "ARCHIVE"); sha1_done(&sha1_state_temp, sha1_tail2); // Finalize the MD5 hash FREEMEM(pbDigestBuffer); return true; }
static int LoadFileFrames(TCascFile * hf) { PBLTE_FRAME pFileFrames; PBLTE_FRAME pFileFrame; ULONGLONG ArchiveFileOffset; DWORD FrameOffset = 0; DWORD FileSize = 0; int nError = ERROR_SUCCESS; assert(hf != NULL); assert(hf->pStream != NULL); assert(hf->pFrames != NULL); // Allocate frame array pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, hf->FrameCount); if(pFileFrames != NULL) { // Load the frame array ArchiveFileOffset = hf->FramesOffset; if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, hf->FrameCount * sizeof(BLTE_FRAME))) { // Move the raw archive offset ArchiveFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME)); // Copy the frames to the file structure for(DWORD i = 0; i < hf->FrameCount; i++, pFileFrame++) { hf->pFrames[i].FrameArchiveOffset = (DWORD)ArchiveFileOffset; hf->pFrames[i].FrameFileOffset = FrameOffset; hf->pFrames[i].CompressedSize = ConvertBytesToInteger_4(pFileFrame->CompressedSize); hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize); memcpy(hf->pFrames[i].md5, pFileFrame->md5, MD5_HASH_SIZE); ArchiveFileOffset += hf->pFrames[i].CompressedSize; FrameOffset += hf->pFrames[i].FrameSize; FileSize += hf->pFrames[i].FrameSize; } } else nError = GetLastError(); // Note: on ENCODING file, this value is almost always bigger // then the real size of ENCODING. We handle this problem // by calculating size of the ENCODIG file from its header. hf->FileSize = FileSize; #ifdef CASCLIB_TEST hf->FileSize_FrameSum = FileSize; #endif // Free the array CASC_FREE(pFileFrames); } else nError = ERROR_NOT_ENOUGH_MEMORY; return nError; }
static bool ExtractPatchPrefixFromFile(const TCHAR * szHelperFile, char * szPatchPrefix, size_t nMaxChars, size_t * PtrLength) { TFileStream * pStream; ULONGLONG FileSize = 0; size_t nLength; char szFileData[MAX_PATH+1]; bool bResult = false; pStream = FileStream_OpenFile(szHelperFile, STREAM_FLAG_READ_ONLY); if(pStream != NULL) { // Retrieve and check the file size FileStream_GetSize(pStream, &FileSize); if(12 <= FileSize && FileSize < MAX_PATH) { // Read the entire file to memory if(FileStream_Read(pStream, NULL, szFileData, (DWORD)FileSize)) { // Terminate the buffer with zero szFileData[(DWORD)FileSize] = 0; // The file data must begin with the "PatchPrefix" variable if(!_strnicmp(szFileData, "PatchPrefix", 11)) { char * szLinePtr = szFileData + 11; char * szLineEnd; // Skip spaces or '=' while(szLinePtr[0] == ' ' || szLinePtr[0] == '=') szLinePtr++; szLineEnd = szLinePtr; // Find the end while(szLineEnd[0] != 0 && szLineEnd[0] != 0x0A && szLineEnd[0] != 0x0D) szLineEnd++; nLength = (size_t)(szLineEnd - szLinePtr); // Copy the variable if(szLineEnd > szLinePtr && nLength <= nMaxChars) { memcpy(szPatchPrefix, szLinePtr, nLength); szPatchPrefix[nLength] = 0; PtrLength[0] = nLength; bResult = true; } } } } // Close the stream FileStream_Close(pStream); } return bResult; }
// Used in SFileGetFileInfo bool QueryMpqSignatureInfo( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI) { ULONGLONG ExtraBytes; TMPQFile * hf; HANDLE hFile; DWORD dwFileSize; // Make sure it's all zeroed memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO)); // Calculate the range of the MPQ CalculateArchiveRange(ha, pSI); // If there is "(signature)" file in the MPQ, it has a weak signature if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile)) { // Get the content of the signature SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL); // Verify the size of the signature hf = (TMPQFile *)hFile; // We have to exclude the signature file from the digest pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset; pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize; dwFileSize = hf->dwDataSize; // Close the file SFileCloseFile(hFile); pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK; return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false; } // If there is extra bytes beyond the end of the archive, // it's the strong signature ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) { // Read the strong signature if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) return false; // Check the signature header "NGIS" if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') return false; pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG; return true; } // Succeeded, but no known signature found return true; }
// Calculate begin and end of the MPQ archive static void CalculateArchiveRange( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG TempPos = 0; ULONGLONG MaxPos; char szMapHeader[0x200]; // Get the MPQ begin pSI->BeginMpqData = ha->MpqPos; // Warcraft III maps are signed from the map header to the end if(FileStream_Read(ha->pStream, &TempPos, szMapHeader, sizeof(szMapHeader))) { // Is it a map header ? if(szMapHeader[0] == 'H' && szMapHeader[1] == 'M' && szMapHeader[2] == '3' && szMapHeader[3] == 'W') { // We will have to hash since the map header pSI->BeginMpqData = 0; } } // Get the MPQ data end. The end is calculated as the biggest // value of (end of the last file), (end of block table), // (end of ext block table), (end of hash table) FindFreeMpqSpace(ha, &MaxPos); // Check if hash table is beyond TempPos = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + pHeader->HashTableSize64; if(TempPos > MaxPos) MaxPos = TempPos; // Check if block table is beyond TempPos = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + pHeader->BlockTableSize64; if(TempPos > MaxPos) MaxPos = TempPos; // Check if ext block table is beyond if(pHeader->HiBlockTablePos64 != 0) { TempPos = ha->MpqPos + pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64; if(TempPos > MaxPos) MaxPos = TempPos; } // Give the end pSI->EndMpqData = MaxPos; // Get the size of the entire file FileStream_GetSize(ha->pStream, pSI->EndOfFile); }
static void DumpIndexKey( FILE * fp, TCascStorage * hs, LPBYTE pbIndexKey, int nDumpLevel) { PCASC_INDEX_ENTRY pIndexEntry; TCascFile * hf; QUERY_KEY QueryKey; HANDLE hFile; BYTE HeaderArea[MAX_HEADER_AREA_SIZE]; char szBuffer[0x20]; QueryKey.pbData = pbIndexKey; QueryKey.cbData = MD5_HASH_SIZE; pIndexEntry = FindIndexEntry(hs, &QueryKey); if(pIndexEntry != NULL) { ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E); DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); // Mask the file offset FileOffset &= 0x3FFFFFFF; fprintf(fp, " data.%03u at 0x%08x (0x%lx bytes)\n", ArchIndex, (DWORD)FileOffset, FileSize); if(nDumpLevel > 2) { QueryKey.pbData = pIndexEntry->IndexKey; QueryKey.cbData = MD5_HASH_SIZE; if(CascOpenFileByIndexKey((HANDLE)hs, &QueryKey, 0, &hFile)) { // Make sure that the data file is open and frame header loaded CascGetFileSize(hFile, NULL); hf = IsValidFileHandle(hFile); assert(hf->pStream != NULL); // Read the header area FileOffset = hf->HeaderOffset - BLTE_HEADER_DELTA; FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea)); CascCloseFile(hFile); // Dump the header area fprintf(fp, " FileSize: %X Rest: %s\n", ConvertBytesToInteger_4_LE(&HeaderArea[0x10]), StringFromBinary(&HeaderArea[0x14], 10, szBuffer)); } } }
static int CopyNonMpqData( TMPQArchive * ha, TFileStream * pSrcStream, TFileStream * pTrgStream, ULONGLONG & ByteOffset, ULONGLONG & ByteCount) { ULONGLONG DataSize = ByteCount; DWORD dwToRead; char DataBuffer[0x1000]; int nError = ERROR_SUCCESS; // Copy the data while(DataSize > 0) { // Get the proper size of data dwToRead = sizeof(DataBuffer); if(DataSize < dwToRead) dwToRead = (DWORD)DataSize; // Read from the source stream if(!FileStream_Read(pSrcStream, &ByteOffset, DataBuffer, dwToRead)) { nError = GetLastError(); break; } // Write to the target stream if(!FileStream_Write(pTrgStream, NULL, DataBuffer, dwToRead)) { nError = GetLastError(); break; } // Update the progress if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwToRead; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes); } // Decrement the number of data to be copied ByteOffset += dwToRead; DataSize -= dwToRead; } return nError; }
static int CopyNonMpqData( TMPQArchive * ha, TFileStream * pSrcStream, TFileStream * pTrgStream, uint64_t * ByteOffset, uint64_t ByteCount) { uint64_t DataSize = ByteCount; uint32_t dwToRead; char DataBuffer[0x1000]; int nError = ERROR_SUCCESS; /* Copy the data */ while(DataSize > 0) { /* Get the proper size of data */ dwToRead = sizeof(DataBuffer); if(DataSize < dwToRead) dwToRead = (uint32_t)DataSize; /* Read from the source stream */ if(!FileStream_Read(pSrcStream, ByteOffset, DataBuffer, dwToRead)) { nError = GetLastError(); break; } /* Write to the target stream */ if(!FileStream_Write(pTrgStream, NULL, DataBuffer, dwToRead)) { nError = GetLastError(); break; } /* Update the progress */ if(ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += dwToRead; ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes); } /* Decrement the number of data to be copied */ *ByteOffset += dwToRead; DataSize -= dwToRead; } return nError; }
static int CopyNonMpqData( TFileStream * pSrcStream, TFileStream * pTrgStream, LARGE_INTEGER & ByteOffset, LARGE_INTEGER & ByteCount) { LARGE_INTEGER DataSize = ByteCount; DWORD dwToRead; char DataBuffer[0x1000]; int nError = ERROR_SUCCESS; // Copy the data while(DataSize.QuadPart > 0) { // Get the proper size of data dwToRead = sizeof(DataBuffer); if(DataSize.QuadPart < dwToRead) dwToRead = DataSize.LowPart; // Read from the source stream if(!FileStream_Read(pSrcStream, &ByteOffset, DataBuffer, dwToRead)) { nError = GetLastError(); break; } // Write to the target stream if(!FileStream_Write(pTrgStream, NULL, DataBuffer, dwToRead)) { nError = GetLastError(); break; } // Update the progress if(CompactCB != NULL) { CompactBytesProcessed.QuadPart += dwToRead; CompactCB(pvUserData, CCB_COPYING_NON_MPQ_DATA, &CompactBytesProcessed, &CompactTotalBytes); } // Decrement the number of data to be copied ByteOffset.QuadPart += dwToRead; DataSize.QuadPart -= dwToRead; } return ERROR_SUCCESS; }
static int LoadTextFile(const TCHAR * szFileName, PQUERY_KEY pFileBlob) { TFileStream * pStream; ULONGLONG FileSize = 0; int nError = ERROR_SUCCESS; // Open the agent file pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); if(pStream != NULL) { // Retrieve its size FileStream_GetSize(pStream, &FileSize); // Load the file to memory if(0 < FileSize && FileSize < 0x100000) { // Initialize the blob pFileBlob->cbData = (DWORD)FileSize; pFileBlob->pbData = CASC_ALLOC(BYTE, pFileBlob->cbData + 1); // Load the file data into the blob if(pFileBlob->pbData != NULL) { FileStream_Read(pStream, NULL, pFileBlob->pbData, (DWORD)FileSize); pFileBlob->pbData[pFileBlob->cbData] = 0; } else nError = ERROR_NOT_ENOUGH_MEMORY; } else nError = ERROR_INVALID_PARAMETER; FileStream_Close(pStream); } else nError = GetLastError(); 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; }
DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) { TMPQFile * hf = (TMPQFile *)hFile; ULONGLONG FilePosition; ULONGLONG MoveOffset; ULONGLONG FileSize; DWORD dwFilePosHi; // If the hFile is not a valid file handle, return an error. if(!IsValidFileHandle(hf)) { SetLastError(ERROR_INVALID_HANDLE); return SFILE_INVALID_POS; } // Get the relative point where to move from switch(dwMoveMethod) { case FILE_BEGIN: FilePosition = 0; break; case FILE_CURRENT: if(hf->pStream != NULL) { FileStream_GetPos(hf->pStream, FilePosition); } else { FilePosition = hf->dwFilePos; } break; case FILE_END: if(hf->pStream != NULL) { FileStream_GetSize(hf->pStream, FilePosition); } else { FilePosition = hf->dwDataSize; } break; default: SetLastError(ERROR_INVALID_PARAMETER); return SFILE_INVALID_POS; } // Get the current file size if(hf->pStream != NULL) { FileStream_GetSize(hf->pStream, FileSize); } else { FileSize = hf->dwDataSize; } // Now get the move offset. Note that both values form // a signed 64-bit value (a file pointer can be moved backwards) if(plFilePosHigh != NULL) dwFilePosHi = *plFilePosHigh; else dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0; MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos); // Now calculate the new file pointer // Do not allow the file pointer to go before the begin of the file FilePosition += MoveOffset; if(FilePosition < 0) FilePosition = 0; // Now apply the file pointer to the file if(hf->pStream != NULL) { // Apply the new file position if(!FileStream_Read(hf->pStream, &FilePosition, NULL, 0)) return SFILE_INVALID_POS; // Return the new file position if(plFilePosHigh != NULL) *plFilePosHigh = (LONG)(FilePosition >> 32); return (DWORD)FilePosition; } else { // Files in MPQ can't be bigger than 4 GB. // We don't allow to go past 4 GB if(FilePosition >> 32)
// 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)) { nError = AllocateSectorChecksums(hf, true); if(nError != ERROR_SUCCESS) return nError; } // If the file is compressed, also allocate secondary buffer pbInSector = pbRawSector = ALLOCMEM(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 PKWARE Data Compression Library ? if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); // Is the file compressed by Blizzard's multiple compression ? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) nResult = SCompDecompress((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) FREEMEM(pbRawSector); // Give the caller thenumber of bytes read *pdwBytesRead = dwBytesRead; 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->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->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->pPatchInfo != 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; }
bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped) { TMPQFile * hf = (TMPQFile *)hFile; DWORD dwBytesRead = 0; // Number of bytes read int nError = ERROR_SUCCESS; // Keep compilers happy lpOverlapped = lpOverlapped; // Check valid parameters if(!IsValidFileHandle(hf)) { SetLastError(ERROR_INVALID_HANDLE); return false; } if(pvBuffer == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // If the file is local file, read the data directly from the stream if(hf->pStream != NULL) { ULONGLONG FilePosition1; ULONGLONG FilePosition2; // Because stream I/O functions are designed to read // "all or nothing", we compare file position before and after, // and if they differ, we assume that number of bytes read // is the difference between them FileStream_GetPos(hf->pStream, FilePosition1); if(!FileStream_Read(hf->pStream, NULL, pvBuffer, dwToRead)) { // If not all bytes have been read, then return the number // of bytes read if((nError = GetLastError()) == ERROR_HANDLE_EOF) { FileStream_GetPos(hf->pStream, FilePosition2); dwBytesRead = (DWORD)(FilePosition2 - FilePosition1); } else { nError = GetLastError(); } } else { dwBytesRead = dwToRead; } } else { // If the file is a patch file, we have to read it special way if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) { nError = ReadMpqFilePatchFile(hf, pvBuffer, dwToRead, &dwBytesRead); } // If the file is single unit file, redirect it to read file else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) { nError = ReadMpqFileSingleUnit(hf, pvBuffer, dwToRead, &dwBytesRead); } // Otherwise read it as sector based MPQ file else { nError = ReadMpqFile(hf, pvBuffer, dwToRead, &dwBytesRead); } } // Give the caller the number of bytes read if(pdwRead != NULL) *pdwRead = dwBytesRead; // If the read operation succeeded, but not full number of bytes was read, // set the last error to ERROR_HANDLE_EOF if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead)) nError = ERROR_HANDLE_EOF; // If something failed, set the last error value if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }
static int EnsureHeaderAreaIsLoaded(TCascFile * hf) { TCascStorage * hs = hf->hs; ULONGLONG FileOffset = hf->HeaderOffset; LPBYTE pbHeaderArea; DWORD FileSignature; DWORD FileSize; BYTE HeaderArea[MAX_HEADER_AREA_SIZE]; int nError; // We need the data file to be open nError = EnsureDataStreamIsOpen(hf); if(nError != ERROR_SUCCESS) return nError; // Make sure that we already know the shift // to the begin of file data. // Note that older builds of Heroes of the Storm have entries pointing // to the beginning of the header area. // Newer versions of HOTS have encoding entries pointing directly to // the BLTE header if(hs->dwFileBeginDelta == 0xFFFFFFFF) { FileSignature = 0; FileOffset = hf->HeaderOffset; if(!FileStream_Read(hf->pStream, &FileOffset, &FileSignature, sizeof(DWORD))) return ERROR_FILE_CORRUPT; hs->dwFileBeginDelta = (FileSignature == BLTE_HEADER_SIGNATURE) ? BLTE_HEADER_DELTA : 0; } // If the file size is not loaded yet, do it if(hf->FrameCount == 0) { // Load the part before BLTE header + header itself FileOffset = hf->HeaderOffset - hs->dwFileBeginDelta; if(!FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea))) return ERROR_FILE_CORRUPT; // Copy the MD5 hash of the frame array memcpy(hf->FrameArrayHash, HeaderArea, MD5_HASH_SIZE); pbHeaderArea = HeaderArea + MD5_HASH_SIZE; // Copy the file size FileSize = ConvertBytesToInteger_4_LE(pbHeaderArea); pbHeaderArea += 0x0E; // Verify the BLTE signature if(ConvertBytesToInteger_4_LE(pbHeaderArea) != BLTE_HEADER_SIGNATURE) return ERROR_BAD_FORMAT; pbHeaderArea += sizeof(DWORD); // Load the size of the frame headers hf->HeaderSize = ConvertBytesToInteger_4(pbHeaderArea); if(hf->HeaderSize & 0x80000000) return ERROR_BAD_FORMAT; pbHeaderArea += sizeof(DWORD); // Read the header size assert(hs->dwFileBeginDelta <= BLTE_HEADER_DELTA); hf->HeaderOffset += (BLTE_HEADER_DELTA - hs->dwFileBeginDelta); hf->FrameCount = 1; // Retrieve the frame count, if different from 1 if(hf->HeaderSize != 0) { // The next byte must be 0x0F if(pbHeaderArea[0] != 0x0F) return ERROR_BAD_FORMAT; pbHeaderArea++; // Next three bytes form number of frames hf->FrameCount = ConvertBytesToInteger_3(pbHeaderArea); } #ifdef CASCLIB_TEST hf->FileSize_HdrArea = FileSize; #endif } return ERROR_SUCCESS; }
bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead) { PCASC_FILE_FRAME pFrame = NULL; ULONGLONG FileOffset; TCascFile * hf; LPBYTE pbBuffer = (LPBYTE)pvBuffer; DWORD dwStartPointer = 0; DWORD dwFilePointer = 0; DWORD dwEndPointer = 0; DWORD cbOutBuffer; int nError = ERROR_SUCCESS; // The buffer must be valid if(pvBuffer == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // Validate the file handle if((hf = IsValidFileHandle(hFile)) == NULL) { SetLastError(ERROR_INVALID_HANDLE); return false; } // If the file frames are not loaded yet, do it now if(nError == ERROR_SUCCESS) { nError = EnsureFrameHeadersLoaded(hf); } // If the file position is at or beyond end of file, do nothing if(nError == ERROR_SUCCESS && hf->FilePointer >= hf->FileSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // Find the file frame where to read from if(nError == ERROR_SUCCESS) { // Get the frame pFrame = FindFileFrame(hf, hf->FilePointer); if(pFrame == NULL) nError = ERROR_FILE_CORRUPT; } // Perform the read if(nError == ERROR_SUCCESS) { // If not enough bytes in the file remaining, cut them dwStartPointer = dwFilePointer = hf->FilePointer; dwEndPointer = dwStartPointer + dwBytesToRead; if(dwEndPointer > hf->FileSize) dwEndPointer = hf->FileSize; // Perform block read from each file frame while(dwFilePointer < dwEndPointer) { LPBYTE pbRawData = NULL; DWORD dwFrameStart = pFrame->FrameFileOffset; DWORD dwFrameEnd = pFrame->FrameFileOffset + pFrame->FrameSize; // Shall we populate the cache with a new data? if(dwFrameStart != hf->CacheStart || hf->CacheEnd != dwFrameEnd) { // Shall we reallocate the cache buffer? if(pFrame->FrameSize > hf->cbFileCache) { if(hf->pbFileCache != NULL) CASC_FREE(hf->pbFileCache); hf->pbFileCache = CASC_ALLOC(BYTE, pFrame->FrameSize); hf->cbFileCache = pFrame->FrameSize; } // We also need to allocate buffer for the raw data pbRawData = CASC_ALLOC(BYTE, pFrame->CompressedSize); if(pbRawData == NULL) { nError = ERROR_NOT_ENOUGH_MEMORY; break; } // Load the raw file data to memory FileOffset = pFrame->FrameArchiveOffset; if(!FileStream_Read(hf->pStream, &FileOffset, pbRawData, pFrame->CompressedSize)) { CASC_FREE(pbRawData); nError = GetLastError(); break; } // Verify the block MD5 if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5)) { CASC_FREE(pbRawData); nError = ERROR_FILE_CORRUPT; break; } // Decompress the file frame cbOutBuffer = pFrame->FrameSize; nError = CascDecompress(hf->pbFileCache, &cbOutBuffer, pbRawData, pFrame->CompressedSize); if(nError != ERROR_SUCCESS || cbOutBuffer != pFrame->FrameSize) { CASC_FREE(pbRawData); nError = ERROR_FILE_CORRUPT; break; } // Set the start and end of the cache hf->CacheStart = dwFrameStart; hf->CacheEnd = dwFrameEnd; // Free the decompress buffer, if needed CASC_FREE(pbRawData); } // Copy the decompressed data if(dwFrameEnd > dwEndPointer) dwFrameEnd = dwEndPointer; memcpy(pbBuffer, hf->pbFileCache + (dwFilePointer - dwFrameStart), (dwFrameEnd - dwFilePointer)); pbBuffer += (dwFrameEnd - dwFilePointer); // Move pointers dwFilePointer = dwFrameEnd; pFrame++; } } // Update the file position if(nError == ERROR_SUCCESS) { if(pdwBytesRead != NULL) *pdwBytesRead = (dwFilePointer - dwStartPointer); hf->FilePointer = dwFilePointer; } if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }
bool WINAPI SFileOpenArchive( const char * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq) { TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle ULONGLONG FileSize = 0; // Size of the file int nError = ERROR_SUCCESS; // Verify the parameters if (szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) nError = ERROR_INVALID_PARAMETER; // One time initialization of MPQ cryptography InitializeMpqCryptography(); dwPriority = dwPriority; // Open the MPQ archive file if (nError == ERROR_SUCCESS) { if (!(dwFlags & MPQ_OPEN_ENCRYPTED)) { pStream = FileStream_OpenFile(szMpqName, (dwFlags & MPQ_OPEN_READ_ONLY) ? false : true); if (pStream == NULL) nError = GetLastError(); } else { pStream = FileStream_OpenEncrypted(szMpqName); if (pStream == NULL) nError = GetLastError(); } } // Allocate the MPQhandle if (nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, FileSize); if ((ha = ALLOCMEM(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize handle structure and allocate structure for MPQ header if (nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); ha->pStream = pStream; pStream = NULL; // Remember if the archive is open for write if (ha->pStream->StreamFlags & (STREAM_FLAG_READ_ONLY | STREAM_FLAG_ENCRYPTED_FILE)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Also remember if we shall check sector CRCs when reading file if (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ha->dwFlags |= MPQ_FLAG_CHECK_SECTOR_CRC; } // Find the offset of MPQ header within the file if (nError == ERROR_SUCCESS) { ULONGLONG SearchPos = 0; DWORD dwHeaderID; while (SearchPos < FileSize) { DWORD dwBytesAvailable = MPQ_HEADER_SIZE_V4; // Cut the bytes available, if needed if ((FileSize - SearchPos) < MPQ_HEADER_SIZE_V4) dwBytesAvailable = (DWORD)(FileSize - SearchPos); // Read the eventual MPQ header if (!FileStream_Read(ha->pStream, &SearchPos, ha->HeaderData, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. if (SearchPos == 0 && IsAviFile(ha->HeaderData)) { nError = ERROR_AVI_FILE; break; } // If there is the MPQ user data signature, process it dwHeaderID = BSWAP_INT32_UNSIGNED(*(LPDWORD)ha->HeaderData); if (dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL) { // Ignore the MPQ user data completely if the caller wants to open the MPQ as V1.0 if ((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { // Fill the user data header ha->pUserData = &ha->UserData; memcpy(ha->pUserData, ha->HeaderData, sizeof(TMPQUserData)); BSWAP_TMPQUSERDATA(ha->pUserData); // Remember the position of the user data and continue search ha->UserDataPos = SearchPos; SearchPos += ha->pUserData->dwHeaderOffs; continue; } } // There must be MPQ header signature if (dwHeaderID == ID_MPQ) { // Save the position where the MPQ header has been found if (ha->pUserData == NULL) ha->UserDataPos = SearchPos; ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchPos; // Now convert the header to version 4 BSWAP_TMPQHEADER(ha->pHeader); ConvertMpqHeaderToFormat4(ha, FileSize, dwFlags); break; } // Move to the next possible offset SearchPos += 0x200; } // If we haven't found MPQ header in the file, it's an error if (ha->pHeader == NULL) nError = ERROR_BAD_FORMAT; } // Fix table positions according to format if (nError == ERROR_SUCCESS) { // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, // and probably ignores the MPQ format version as well. The trick is to // fake MPQ format 2, with an improper hi-word position of hash table and block table // We can overcome such protectors by forcing opening the archive as MPQ v 1.0 if (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) { ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; ha->dwFlags |= MPQ_FLAG_READ_ONLY; ha->pUserData = NULL; } // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if (dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); // Verify if any of the tables doesn't start beyond the end of the file nError = VerifyMpqTablePositions(ha, FileSize); } // Read the hash table. // "interface.MPQ.part" in trial version of World of Warcraft // has compressed block table and hash table. if (nError == ERROR_SUCCESS) { // // Note: We will not check if the hash table is properly decrypted. // Some MPQ protectors corrupt the hash table by rewriting part of it. // Hash table, the way how it works, allows arbitrary values for unused entries. // nError = LoadHashTable(ha); } // Read Het and Bet tables, if they are present if (nError == ERROR_SUCCESS) { nError = LoadHetAndBetTable(ha); if (ha->pHetTable || ha->pBetTable) ha->dwFlags |= MPQ_FLAG_READ_ONLY; } // Now, build the file table. It will be built by combining // the block table, hi-block table, (attributes) and (listfile). if (nError == ERROR_SUCCESS) { nError = BuildFileTable(ha, FileSize); } // Verify the block table, if no kind of protection was detected if (nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize; TFileEntry * pFileEntry = ha->pFileTable; // ULONGLONG ArchiveSize = 0; ULONGLONG RawFilePos; // Parse all file entries for (pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // If that file entry is valid, check the file position if (pFileEntry->dwFlags & MPQ_FILE_EXISTS) { // Get the 64-bit file position, // relative to the begin of the file RawFilePos = ha->MpqPos + pFileEntry->ByteOffset; // Begin of the file must be within range if (RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } // End of the file must be within range RawFilePos += pFileEntry->dwCmpSize; if (RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } // Also, we remember end of the file // if (RawFilePos > ArchiveSize) // ArchiveSize = RawFilePos; } } } // Load the "(attributes)" file and merge it to the file table if (nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0) { // Ignore result of the operation. (attributes) is optional. SAttrLoadAttributes(ha); } // Load the internal listfile and include it to the file table if (nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { // Ignore result of the operation. (listfile) is optional. SFileAddListFile((HANDLE)ha, NULL); } // Test the indexes from BET and BET table #ifdef __STORMLIB_TEST__ if (nError == ERROR_SUCCESS) { TestNewHashBlockTables(ha); } #endif // Cleanup and exit if (nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeMPQArchive(ha); SetLastError(nError); ha = NULL; } *phMpq = ha; return (nError == ERROR_SUCCESS); }
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; bool bIsReallyCompressed = false; 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_COMPRESS) { // Allocate space for compressed data pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; bIsReallyCompressed = true; 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); } // // 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. // if(hf->pPatchInfo != NULL) { if(pbRawData[0] == 'P' && pbRawData[1] == 'T' && pbRawData[2] == 'C' && pbRawData[3] == 'H') { assert(pFileEntry->dwCmpSize >= hf->dwDataSize); bIsReallyCompressed = false; } } else { if(pFileEntry->dwCmpSize >= hf->dwDataSize) bIsReallyCompressed = false; } // If the file is compressed, we have to decompress it now if(bIsReallyCompressed) { int cbOutBuffer = (int)hf->dwDataSize; // Note: Single unit files compressed with IMPLODE are not supported by Blizzard if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) { if(!SCompExplode((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) nError = ERROR_FILE_CORRUPT; } if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { if(!SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) nError = 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; }
bool WINAPI SFileOpenArchive( const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq) { TMPQUserData * pUserData; TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle TFileEntry * pFileEntry; ULONGLONG FileSize = 0; // Size of the file LPBYTE pbHeaderBuffer = NULL; // Buffer for searching MPQ header DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK); bool bIsWarcraft3Map = false; int nError = ERROR_SUCCESS; // Verify the parameters if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // One time initialization of MPQ cryptography InitializeMpqCryptography(); dwPriority = dwPriority; // If not forcing MPQ v 1.0, also use file bitmap dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP; // Open the MPQ archive file pStream = FileStream_OpenFile(szMpqName, dwStreamFlags); if(pStream == NULL) return false; // Check the file size. There must be at least 0x20 bytes if(nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, &FileSize); if(FileSize < MPQ_HEADER_SIZE_V1) nError = ERROR_BAD_FORMAT; } // Allocate the MPQhandle if(nError == ERROR_SUCCESS) { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Allocate buffer for searching MPQ header if(nError == ERROR_SUCCESS) { pbHeaderBuffer = STORM_ALLOC(BYTE, HEADER_SEARCH_BUFFER_SIZE); if(pbHeaderBuffer == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Find the position of MPQ header if(nError == ERROR_SUCCESS) { ULONGLONG SearchOffset = 0; ULONGLONG EndOfSearch = FileSize; DWORD dwStrmFlags = 0; DWORD dwHeaderSize; DWORD dwHeaderID; bool bSearchComplete = false; memset(ha, 0, sizeof(TMPQArchive)); ha->pfnHashString = HashStringSlash; ha->pStream = pStream; pStream = NULL; // Set the archive read only if the stream is read-only FileStream_GetFlags(ha->pStream, &dwStrmFlags); ha->dwFlags |= (dwStrmFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0; // Also remember if we shall check sector CRCs when reading file ha->dwFlags |= (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ? MPQ_FLAG_CHECK_SECTOR_CRC : 0; // Also remember if this MPQ is a patch ha->dwFlags |= (dwFlags & MPQ_OPEN_PATCH) ? MPQ_FLAG_PATCH : 0; // Limit the header searching to about 130 MB of data if(EndOfSearch > 0x08000000) EndOfSearch = 0x08000000; // Find the offset of MPQ header within the file while(bSearchComplete == false && SearchOffset < EndOfSearch) { // Always read at least 0x1000 bytes for performance. // This is what Storm.dll (2002) does. DWORD dwBytesAvailable = HEADER_SEARCH_BUFFER_SIZE; DWORD dwInBufferOffset = 0; // Cut the bytes available, if needed if((FileSize - SearchOffset) < HEADER_SEARCH_BUFFER_SIZE) dwBytesAvailable = (DWORD)(FileSize - SearchOffset); // Read the eventual MPQ header if(!FileStream_Read(ha->pStream, &SearchOffset, pbHeaderBuffer, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. if(SearchOffset == 0) { if(IsAviFile((DWORD *)pbHeaderBuffer)) { nError = ERROR_AVI_FILE; break; } bIsWarcraft3Map = IsWarcraft3Map((DWORD *)pbHeaderBuffer); } // Search the header buffer while(dwInBufferOffset < dwBytesAvailable) { // Copy the data from the potential header buffer to the MPQ header memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData)); // If there is the MPQ user data, process it // Note that Warcraft III does not check for user data, which is abused by many map protectors dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]); if(bIsWarcraft3Map == false && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA) { // Verify if this looks like a valid user data pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); if(pUserData != NULL) { // Fill the user data header ha->UserDataPos = SearchOffset; ha->pUserData = &ha->UserData; memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); // Continue searching from that position SearchOffset += ha->pUserData->dwHeaderOffs; break; } } } // There must be MPQ header signature. Note that STORM.dll from Warcraft III actually // tests the MPQ header size. It must be at least 0x20 bytes in order to load it // Abused by Spazzler Map protector. Note that the size check is not present // in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway. dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]); if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1) { // Now convert the header to version 4 nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags); bSearchComplete = true; break; } // Check for MPK archives (Longwu Online - MPQ fork) if(dwHeaderID == ID_MPK) { // Now convert the MPK header to MPQ Header version 4 nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags); bSearchComplete = true; break; } // If searching for the MPQ header is disabled, return an error if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH) { nError = ERROR_NOT_SUPPORTED; bSearchComplete = true; break; } // Move the pointers SearchOffset += 0x200; dwInBufferOffset += 0x200; } } // Did we identify one of the supported headers? if(nError == ERROR_SUCCESS) { // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) ha->UserDataPos = SearchOffset; // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchOffset; ha->FileSize = FileSize; // Sector size must be nonzero. if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0) nError = ERROR_BAD_FORMAT; } } // Fix table positions according to format if(nError == ERROR_SUCCESS) { // Dump the header // DumpMpqHeader(ha->pHeader); // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, // and ignores the MPQ format version as well. The trick is to // fake MPQ format 2, with an improper hi-word position of hash table and block table // We can overcome such protectors by forcing opening the archive as MPQ v 1.0 if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) { ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; ha->dwFlags |= MPQ_FLAG_READ_ONLY; ha->pUserData = NULL; } // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Remember whether whis is a map for Warcraft III if(bIsWarcraft3Map) ha->dwFlags |= MPQ_FLAG_WAR3_MAP; // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); // Verify if any of the tables doesn't start beyond the end of the file nError = VerifyMpqTablePositions(ha, FileSize); } // Read the hash table. Ignore the result, as hash table is no longer required // Read HET table. Ignore the result, as HET table is no longer required if(nError == ERROR_SUCCESS) { nError = LoadAnyHashTable(ha); } // Now, build the file table. It will be built by combining // the block table, BET table, hi-block table, (attributes) and (listfile). if(nError == ERROR_SUCCESS) { nError = BuildFileTable(ha); } // Load the internal listfile and include it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { // Quick check for (listfile) pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Ignore result of the operation. (listfile) is optional. SFileAddListFile((HANDLE)ha, NULL); ha->dwFileFlags1 = pFileEntry->dwFlags; } } // Load the "(attributes)" file and merge it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0 && (ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0) { // Quick check for (attributes) pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Ignore result of the operation. (attributes) is optional. SAttrLoadAttributes(ha); ha->dwFileFlags2 = pFileEntry->dwFlags; } } // Remember whether the archive has weak signature. Only for MPQs format 1.0. if(nError == ERROR_SUCCESS) { // Quick check for (signature) pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) { // Just remember that the archive is weak-signed assert((pFileEntry->dwFlags & MPQ_FILE_EXISTS) != 0); ha->dwFileFlags3 = pFileEntry->dwFlags; } // Finally, set the MPQ_FLAG_READ_ONLY if the MPQ was found malformed ha->dwFlags |= (ha->dwFlags & MPQ_FLAG_MALFORMED) ? MPQ_FLAG_READ_ONLY : 0; } // Cleanup and exit if(nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeArchiveHandle(ha); SetLastError(nError); ha = NULL; } // Free the header buffer if(pbHeaderBuffer != NULL) STORM_FREE(pbHeaderBuffer); if(phMpq != NULL) *phMpq = ha; return (nError == ERROR_SUCCESS); }
static bool CalculateMpqHashMd5( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI, LPBYTE pMd5Digest) { hash_state md5_state; ULONGLONG BeginBuffer; ULONGLONG EndBuffer; LPBYTE pbDigestBuffer = NULL; // Allocate buffer for creating the MPQ digest. pbDigestBuffer = ALLOCMEM(BYTE, MPQ_DIGEST_UNIT_SIZE); if(pbDigestBuffer == NULL) return false; // Initialize the MD5 hash state md5_init(&md5_state); // Set the byte offset of begin of the data BeginBuffer = pSI->BeginMpqData; // Create the digest for(;;) { ULONGLONG BytesRemaining; LPBYTE pbSigBegin = NULL; LPBYTE pbSigEnd = NULL; DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE; // Check the number of bytes remaining BytesRemaining = pSI->EndMpqData - BeginBuffer; if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE) dwToRead = (DWORD)BytesRemaining; if(dwToRead == 0) break; // Read the next chunk if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead)) { FREEMEM(pbDigestBuffer); return false; } // Move the current byte offset EndBuffer = BeginBuffer + dwToRead; // Check if the signature is within the loaded digest if(BeginBuffer <= pSI->BeginExclude && pSI->BeginExclude < EndBuffer) pbSigBegin = pbDigestBuffer + (size_t)(pSI->BeginExclude - BeginBuffer); if(BeginBuffer <= pSI->EndExclude && pSI->EndExclude < EndBuffer) pbSigEnd = pbDigestBuffer + (size_t)(pSI->EndExclude - BeginBuffer); // Zero the part that belongs to the signature if(pbSigBegin != NULL || pbSigEnd != NULL) { if(pbSigBegin == NULL) pbSigBegin = pbDigestBuffer; if(pbSigEnd == NULL) pbSigEnd = pbDigestBuffer + dwToRead; memset(pbSigBegin, 0, (pbSigEnd - pbSigBegin)); } // Pass the buffer to the hashing function md5_process(&md5_state, pbDigestBuffer, dwToRead); // Move pointers BeginBuffer += dwToRead; } // Finalize the MD5 hash md5_done(&md5_state, pMd5Digest); FREEMEM(pbDigestBuffer); return true; }
static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C) TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData = hf->pbFileSector; int nError = ERROR_SUCCESS; // We do not support patch files in MPK archives assert(hf->pPatchInfo == NULL); // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL) return nError; pbRawData = hf->pbFileSector; } // If the file sector is not loaded yet, do it if(hf->dwSectorOffs != 0) { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { // 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) { DecryptMpkTable(pbRawData, pFileEntry->dwCmpSize); } // If the file is compressed, we have to decompress it now if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { int cbOutBuffer = (int)hf->dwDataSize; hf->dwCompression0 = pbRawData[0]; if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, pbRawData, (int)pFileEntry->dwCmpSize)) nError = 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 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; 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 || hf->pbFileSector == NULL) return nError; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; pbRawData = hf->pbFileSector; // If the file sector is not loaded yet, do it if(hf->dwSectorOffs != 0) { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { // 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_COMPRESS_MASK) { 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) { // Remember the last used compression hf->dwCompression0 = pbRawData[0]; // Decompress the file if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); else nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, 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(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; } else { if(hf->pbFileSector != NULL && 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 VerifyRawMpqData( TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwDataSize) { hash_state md5_state; ULONGLONG DataOffset = ha->MpqPos + ByteOffset; LPBYTE pbDataChunk; LPBYTE pbMD5Array1; // Calculated MD5 array LPBYTE pbMD5Array2; // MD5 array loaded from the MPQ DWORD dwBytesInChunk; DWORD dwChunkCount; DWORD dwChunkSize = ha->pHeader->dwRawChunkSize; DWORD dwMD5Size; int nError = ERROR_SUCCESS; // Get the number of data chunks to calculate MD5 assert(dwChunkSize != 0); dwChunkCount = dwDataSize / dwChunkSize; if(dwDataSize % dwChunkSize) dwChunkCount++; dwMD5Size = dwChunkCount * MD5_DIGEST_SIZE; // Allocate space for data chunk and for the MD5 array pbDataChunk = ALLOCMEM(BYTE, dwChunkSize); if(pbDataChunk == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Allocate space for MD5 array pbMD5Array1 = ALLOCMEM(BYTE, dwMD5Size); pbMD5Array2 = ALLOCMEM(BYTE, dwMD5Size); if(pbMD5Array1 == NULL || pbMD5Array2 == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; // Calculate MD5 of each data chunk if(nError == ERROR_SUCCESS) { LPBYTE pbMD5 = pbMD5Array1; for(DWORD i = 0; i < dwChunkCount; i++) { // Get the number of bytes in the chunk dwBytesInChunk = STORMLIB_MIN(dwChunkSize, dwDataSize); // Read the data chunk if(!FileStream_Read(ha->pStream, &DataOffset, pbDataChunk, dwBytesInChunk)) { nError = ERROR_FILE_CORRUPT; break; } // Calculate MD5 md5_init(&md5_state); md5_process(&md5_state, pbDataChunk, dwBytesInChunk); md5_done(&md5_state, pbMD5); // Move pointers and offsets DataOffset += dwBytesInChunk; dwDataSize -= dwBytesInChunk; pbMD5 += MD5_DIGEST_SIZE; } } // Read the MD5 array if(nError == ERROR_SUCCESS) { // Read the array of MD5 if(!FileStream_Read(ha->pStream, &DataOffset, pbMD5Array2, dwMD5Size)) nError = GetLastError(); } // Compare the array of MD5 if(nError == ERROR_SUCCESS) { // Compare the MD5 if(memcmp(pbMD5Array1, pbMD5Array2, dwMD5Size)) nError = ERROR_FILE_CORRUPT; } // Free memory and return result if(pbMD5Array2 != NULL) FREEMEM(pbMD5Array2); if(pbMD5Array1 != NULL) FREEMEM(pbMD5Array1); if(pbDataChunk != NULL) FREEMEM(pbDataChunk); return nError; }
static bool ReloadCache_ExternalFile(void * pvCacheContext, LPBYTE pbBuffer, DWORD dwBytesToRead) { TFileStream * pStream = (TFileStream *)pvCacheContext; return FileStream_Read(pStream, NULL, pbBuffer, dwBytesToRead); }