bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead) { PCASC_FILE_FRAME pFrame = NULL; ULONGLONG StreamSize; ULONGLONG FileOffset; TCascFile * hf; LPBYTE pbBuffer = (LPBYTE)pvBuffer; DWORD dwStartPointer = 0; DWORD dwFilePointer = 0; DWORD dwEndPointer = 0; DWORD dwFrameSize; bool bReadResult; 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 || pFrame->CompressedSize < 1) 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 pbFrameData = 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 pbFrameData = CASC_ALLOC(BYTE, pFrame->CompressedSize); if(pbFrameData == NULL) { nError = ERROR_NOT_ENOUGH_MEMORY; break; } // Load the raw file data to memory FileOffset = pFrame->FrameArchiveOffset; bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbFrameData, pFrame->CompressedSize); // Note: The raw file data size could be less than expected // Happened in WoW build 19342 with the ROOT file. MD5 in the frame header // is zeroed, which means it should not be checked // Frame File: data.029 // Frame Offs: 0x013ED9F0 size 0x01325B32 // Frame End: 0x02713522 // File Size: 0x027134FC if(bReadResult == false && GetLastError() == ERROR_HANDLE_EOF && !IsValidMD5(pFrame->md5)) { // Get the size of the remaining file FileStream_GetSize(hf->pStream, &StreamSize); dwFrameSize = (DWORD)(StreamSize - FileOffset); // If the frame offset is before EOF and frame end is beyond EOF, correct it if(FileOffset < StreamSize && dwFrameSize < pFrame->CompressedSize) { memset(pbFrameData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize)); bReadResult = true; } } // If the read result failed, we cannot finish reading it if(bReadResult && VerifyDataBlockHash(pbFrameData, pFrame->CompressedSize, pFrame->md5)) { // Convert the source frame to the file cache nError = ProcessFileFrame(hf->pbFileCache, pFrame->FrameSize, pbFrameData, pFrame->CompressedSize, (DWORD)(pFrame - hf->pFrames)); if(nError == ERROR_SUCCESS) { // Set the start and end of the cache hf->CacheStart = dwFrameStart; hf->CacheEnd = dwFrameEnd; } } else { nError = ERROR_FILE_CORRUPT; } // Free the raw frame data CASC_FREE(pbFrameData); } // 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); }
static int ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) { const char * szLineBegin; const char * szLineEnd = NULL; int nError; // Initialize the size of the internal files ConvertIntegerToBytes_4(CASC_INVALID_SIZE, hs->EncodingFile.ContentSize); // Initialize the empty VFS array nError = hs->VfsRootList.Create<QUERY_KEY_PAIR>(0x10); if (nError != ERROR_SUCCESS) return nError; // Parse all variables while(ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd) != 0) { // Product name and build name if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "build-product", LoadBuildProduct, NULL)) continue; if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "build-name", LoadBuildName, NULL)) continue; // Content key of the ROOT file. Look this up in ENCODING to get the encoded key if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "root", LoadCkeyEkey, &hs->RootFile)) continue; // Content key [+ encoded key] of the INSTALL file // If CKey is absent, you need to query the ENCODING file for it if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "install", LoadCkeyEkey, &hs->InstallFile)) continue; // Content key [+ encoded key] of the DOWNLOAD file // If CKey is absent, you need to query the ENCODING file for it if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "download", LoadCkeyEkey, &hs->DownloadFile)) continue; // Content key + encoded key of the ENCODING file. Contains CKey+EKey // If either none or 1 is found, the game (at least Wow) switches to plain-data(?). Seen in build 20173 if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "encoding", LoadCkeyEkey, &hs->EncodingFile)) continue; // Content and encoded size of the ENCODING file. This helps us to determine size // of the ENCODING file better, as the size in the EKEY entries is almost always wrong on WoW storages if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "encoding-size", LoadCkeyEkeySize, &hs->EncodingFile)) continue; // PATCH file if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "patch", LoadCkeyEkey, &hs->PatchFile)) continue; // Load the CKey+EKey of a VFS root file (the root file of the storage VFS) if (CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "vfs-root", LoadCkeyEkey, &hs->VfsRoot)) continue; // Load the content size of the VFS root if (CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "vfs-root-size", LoadCkeyEkeySize, &hs->VfsRoot)) continue; // Load a directory entry to the VFS if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "vfs-*", LoadVfsRootEntry, &hs->VfsRootList)) continue; } // Both CKey and EKey of ENCODING file is required if(!IsValidMD5(hs->EncodingFile.CKey) || !IsValidMD5(hs->EncodingFile.EKey)) return ERROR_BAD_FORMAT; return nError; }
static DWORD VerifyFile( HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5, DWORD dwFlags) { hash_state md5_state; unsigned char * pFileMd5; unsigned char md5[MD5_DIGEST_SIZE]; TFileEntry * pFileEntry; TMPQFile * hf; BYTE Buffer[0x1000]; HANDLE hFile = NULL; DWORD dwVerifyResult = 0; DWORD dwTotalBytes = 0; DWORD dwCrc32 = 0; // // Note: When the MPQ is patched, it will // automatically check the patched version of the file // // Make sure the md5 is initialized memset(md5, 0, sizeof(md5)); // If we have to verify raw data MD5, do it before file open if(dwFlags & SFILE_VERIFY_RAW_MD5) { TMPQArchive * ha = (TMPQArchive *)hMpq; // Parse the base MPQ and all patches while(ha != NULL) { // Does the archive have support for raw MD5? if(ha->pHeader->dwRawChunkSize != 0) { // The file has raw MD5 if the archive supports it dwVerifyResult |= VERIFY_FILE_HAS_RAW_MD5; // Find file entry for the file pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if(pFileEntry != NULL) { // If the file's raw MD5 doesn't match, don't bother with more checks if(VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize) != ERROR_SUCCESS) return dwVerifyResult | VERIFY_FILE_RAW_MD5_ERROR; } } // Move to the next patch ha = ha->haPatch; } } // Attempt to open the file if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile)) { // Get the file size hf = (TMPQFile *)hFile; pFileEntry = hf->pFileEntry; dwTotalBytes = SFileGetFileSize(hFile, NULL); // Initialize the CRC32 and MD5 contexts md5_init(&md5_state); dwCrc32 = crc32(0, Z_NULL, 0); // Also turn on sector checksum verification if(dwFlags & SFILE_VERIFY_SECTOR_CRC) hf->bCheckSectorCRCs = true; // Go through entire file and update both CRC32 and MD5 for(;;) { DWORD dwBytesRead = 0; // Read data from file SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL); if(dwBytesRead == 0) { if(GetLastError() == ERROR_CHECKSUM_ERROR) dwVerifyResult |= VERIFY_FILE_SECTOR_CRC_ERROR; break; } // Update CRC32 value if(dwFlags & SFILE_VERIFY_FILE_CRC) dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead); // Update MD5 value if(dwFlags & SFILE_VERIFY_FILE_MD5) md5_process(&md5_state, Buffer, dwBytesRead); // Decrement the total size dwTotalBytes -= dwBytesRead; } // If the file has sector checksums, indicate it in the flags if(dwFlags & SFILE_VERIFY_SECTOR_CRC) { if((hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->SectorChksums != NULL && hf->SectorChksums[0] != 0) dwVerifyResult |= VERIFY_FILE_HAS_SECTOR_CRC; } // Check if the entire file has been read // No point in checking CRC32 and MD5 if not // Skip checksum checks if the file has patches if(dwTotalBytes == 0) { // Check CRC32 and MD5 only if there is no patches if(hf->hfPatch == NULL) { // Check if the CRC32 matches. if(dwFlags & SFILE_VERIFY_FILE_CRC) { // Only check the CRC32 if it is valid if(pFileEntry->dwCrc32 != 0) { dwVerifyResult |= VERIFY_FILE_HAS_CHECKSUM; if(dwCrc32 != pFileEntry->dwCrc32) dwVerifyResult |= VERIFY_FILE_CHECKSUM_ERROR; } } // Check if MD5 matches if(dwFlags & SFILE_VERIFY_FILE_MD5) { // Patch files have their MD5 saved in the patch info pFileMd5 = (hf->pPatchInfo != NULL) ? hf->pPatchInfo->md5 : pFileEntry->md5; md5_done(&md5_state, md5); // Only check the MD5 if it is valid if(IsValidMD5(pFileMd5)) { dwVerifyResult |= VERIFY_FILE_HAS_MD5; if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE)) dwVerifyResult |= VERIFY_FILE_MD5_ERROR; } } } else { // Patched files are MD5-checked automatically dwVerifyResult |= VERIFY_FILE_HAS_MD5; } } else { dwVerifyResult |= VERIFY_READ_ERROR; } SFileCloseFile(hFile); } else { // Remember that the file couldn't be open dwVerifyResult |= VERIFY_OPEN_ERROR; } // If the caller required CRC32 and/or MD5, give it to him if(pdwCrc32 != NULL) *pdwCrc32 = dwCrc32; if(pMD5 != NULL) memcpy(pMD5, md5, MD5_DIGEST_SIZE); return dwVerifyResult; }