static PQUERY_KEY LoadMultipleBlobs(LPBYTE pbLineBegin, LPBYTE pbLineEnd, DWORD * pdwBlobCount) { PQUERY_KEY pBlobArray = NULL; LPBYTE pbBuffer = NULL; DWORD dwBlobCount = GetBlobCount(pbLineBegin, pbLineEnd); int nError; // Only if there is at least 1 blob if(dwBlobCount != 0) { // Allocate the array of blobs pBlobArray = CASC_ALLOC(QUERY_KEY, dwBlobCount); if(pBlobArray != NULL) { // Zero the blob array memset(pBlobArray, 0, dwBlobCount * sizeof(QUERY_KEY)); // Allocate buffer for the blobs pbBuffer = CASC_ALLOC(BYTE, dwBlobCount * MAX_CASC_KEY_LENGTH); if(pbBuffer != NULL) { // Zero the buffer memset(pbBuffer, 0, dwBlobCount * MAX_CASC_KEY_LENGTH); // Load the entire blob array nError = LoadBlobArray(pBlobArray, dwBlobCount, pbLineBegin, pbLineEnd, pbBuffer, dwBlobCount * MAX_CASC_KEY_LENGTH); if(nError == ERROR_SUCCESS) { *pdwBlobCount = dwBlobCount; return pBlobArray; } // Free the buffer CASC_FREE(pbBuffer); } // Free the array of blobs CASC_FREE(pBlobArray); pBlobArray = NULL; } // Reset the blob count dwBlobCount = 0; } *pdwBlobCount = dwBlobCount; return pBlobArray; }
void ListFile_Free(void * pvListFile) { if(pvListFile != NULL) { CASC_FREE(pvListFile); } }
static int EnsureDataStreamIsOpen(TCascFile * hf) { TCascStorage * hs = hf->hs; TFileStream * pStream = NULL; TCHAR * szDataFile; TCHAR szPlainName[0x40]; // If the file is not open yet, do it if(hs->DataFileArray[hf->ArchiveIndex] == NULL) { // Prepare the name of the data file _stprintf(szPlainName, _T("data.%03u"), hf->ArchiveIndex); szDataFile = CombinePath(hs->szIndexPath, szPlainName); // Open the data file if(szDataFile != NULL) { // Open the stream pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); hs->DataFileArray[hf->ArchiveIndex] = pStream; CASC_FREE(szDataFile); } } // Return error or success hf->pStream = hs->DataFileArray[hf->ArchiveIndex]; return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; }
static int FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PQUERY_KEY pFileBlob) { TCHAR * szFileName; int nError; // Construct the local file name szFileName = NewStr(hs->szDataPath, 8 + 3 + 3 + 32); if(szFileName == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Add the part where the config file path is AppendConfigFilePath(szFileName, pFileKey); // Load the config file nError = LoadTextFile(szFileName, pFileBlob); if(nError == ERROR_SUCCESS) { // Verify the blob's MD5 if(!VerifyDataBlockHash(pFileBlob->pbData, pFileBlob->cbData, pFileKey->pbData)) { FreeCascBlob(pFileBlob); nError = ERROR_BAD_FORMAT; } } CASC_FREE(szFileName); return nError; }
// Checks whether there is a ".agent.db". If yes, the function // sets "szRootPath" and "szDataPath" in the storage structure // and returns ERROR_SUCCESS int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory) { QUERY_KEY AgentFile; TCHAR * szFilePath; size_t nLength = 0; char * szValue; int nError = ERROR_FILE_NOT_FOUND; // Create the full name of the .agent.db file szFilePath = CombinePath(szDirectory, _T(".agent.db")); if(szFilePath != NULL) { // Load the file to memory nError = LoadTextFile(szFilePath, &AgentFile); if(nError == ERROR_SUCCESS) { // Extract the data directory from the ".agent.db" file szValue = ExtractStringVariable(&AgentFile, "data_dir", &nLength); if(szValue != NULL) { hs->szRootPath = CascNewStr(szDirectory, 0); hs->szDataPath = CombinePathAndString(szDirectory, szValue, nLength); nError = (hs->szDataPath != NULL) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; } // Free the loaded blob FreeCascBlob(&AgentFile); } // Freee the file path CASC_FREE(szFilePath); } return nError; }
static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory) { TCHAR * szDataPath; int nError = ERROR_FILE_NOT_FOUND; // Try all known subdirectories for(size_t i = 0; DataDirs[i] != NULL; i++) { // Create the eventual data path szDataPath = CombinePath(szDirectory, DataDirs[i]); if(szDataPath != NULL) { // Does that directory exist? if(DirectoryExists(szDataPath)) { hs->szDataPath = szDataPath; return ERROR_SUCCESS; } // Free the data path CASC_FREE(szDataPath); } } return nError; }
static void * FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey) { TCHAR * szFileName; void * pvListFile = NULL; // Construct the local file name szFileName = CascNewStr(hs->szDataPath, 8 + 3 + 3 + 32); if(szFileName != NULL) { // Add the part where the config file path is AppendConfigFilePath(szFileName, pFileKey); // Load and verify the external listfile pvListFile = ListFile_OpenExternal(szFileName); if(pvListFile != NULL) { if(!ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) { ListFile_Free(pvListFile); pvListFile = NULL; } } // Free the file name CASC_FREE(szFileName); } return pvListFile; }
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 void WowHandler_Close(TRootHandler_WoW6 * pRootHandler) { if(pRootHandler != NULL) { Array_Free(&pRootHandler->FileTable); Map_Free(pRootHandler->pRootMap); CASC_FREE(pRootHandler); } }
void ListFile_FreeMap(PLISTFILE_MAP pListMap) { if(pListMap != NULL) { if(pListMap->pNameMap != NULL) Map_Free(pListMap->pNameMap); CASC_FREE(pListMap); } }
static void FreeCascBlob(PQUERY_KEY pBlob) { if(pBlob != NULL) { if(pBlob->pbData != NULL) CASC_FREE(pBlob->pbData); pBlob->pbData = NULL; pBlob->cbData = 0; } }
void ListFile_Free(void * pvListFile) { TListFileCache * pCache = (TListFileCache *)pvListFile; // Valid parameter check if(pCache != NULL) { if(pCache->pfnCloseStream != NULL) pCache->pfnCloseStream(pCache->pvCacheContext); CASC_FREE(pCache); } }
static DWORD ScanDirectoryFile( TCascStorage * hs, LPBYTE pbRootFile, LPBYTE pbFileEnd) { PDIABLO3_NAMED_ENTRY pNamedEntry; DIABLO3_DIR_HEADER RootHeader; DIABLO3_DIR_HEADER DirHeader; LPBYTE pbSubDir; DWORD dwTotalFileCount; DWORD cbNamedEntry; DWORD cbSubDir; int nError; // Parse the directory header in order to retrieve the items nError = ParseDirectoryHeader(&RootHeader, pbRootFile, pbFileEnd); if(nError != ERROR_SUCCESS) return 0; // Add the root directory's entries dwTotalFileCount = RootHeader.dwEntries1 + RootHeader.dwEntries2 + RootHeader.dwEntries3; // Parse the named entries for(DWORD i = 0; i < RootHeader.dwEntries3; i++) { // Get the this named entry if((cbNamedEntry = VerifyNamedFileEntry(RootHeader.pbEntries3, pbFileEnd)) == 0) return 0; pNamedEntry = (PDIABLO3_NAMED_ENTRY)RootHeader.pbEntries3; RootHeader.pbEntries3 += cbNamedEntry; // Load the subdirectory to memory pbSubDir = LoadFileToMemory(hs, pNamedEntry->EncodingKey.Value, &cbSubDir); if(pbSubDir != NULL) { // Count the files in the subdirectory if(ParseDirectoryHeader(&DirHeader, pbSubDir, pbSubDir + cbSubDir) == ERROR_SUCCESS) { dwTotalFileCount += DirHeader.dwEntries1 + DirHeader.dwEntries2 + DirHeader.dwEntries3; } // Free the subdirectory CASC_FREE(pbSubDir); } } // Return the total number of entries return dwTotalFileCount; }
static void D3Handler_Close(TRootHandler_Diablo3 * pRootHandler) { if(pRootHandler != NULL) { // Free the file map Map_Free(pRootHandler->pRootMap); // Free the array of the file entries and file names Array_Free(&pRootHandler->FileTable); Array_Free(&pRootHandler->FileNames); // Free the root file itself CASC_FREE(pRootHandler); } }
static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir) { TCHAR * szIndexPath; // Cpmbine the index path szIndexPath = CombinePath(hs->szDataPath, szSubDir); if(DirectoryExists(szIndexPath)) { hs->szIndexPath = szIndexPath; return hs->szIndexPath; } CASC_FREE(szIndexPath); return NULL; }
FILE * CreateDumpFile(const char * szFormat, TCascStorage * hs) { FILE * fp = NULL; char * szFileName; // Validate the storage handle if(hs != NULL) { // Format the real file name szFileName = FormatFileName(szFormat, hs); if(szFileName != NULL) { // Create the dump file fp = fopen(szFileName, "wt"); CASC_FREE(szFileName); } } return fp; }
static int FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARSEINFOFILE PfnParseProc) { TCHAR * szFileName; void * pvListFile = NULL; int nError = ERROR_CAN_NOT_COMPLETE; // Construct the local file name szFileName = CascNewStr(hs->szDataPath, 8 + 3 + 3 + 32); if (szFileName != NULL) { // Add the part where the config file path is AppendConfigFilePath(szFileName, pFileKey); // Load and verify the external listfile pvListFile = ListFile_OpenExternal(szFileName); if (pvListFile != NULL) { if (ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) { nError = PfnParseProc(hs, pvListFile); } else { nError = ERROR_FILE_CORRUPT; } ListFile_Free(pvListFile); } else { nError = ERROR_FILE_NOT_FOUND; } CASC_FREE(szFileName); } else { nError = ERROR_NOT_ENOUGH_MEMORY; } return nError; }
// Checks whether there is a ".build.info" or ".build.db". // If yes, the function sets "szDataPath" and "szIndexPath" // in the storage structure and returns ERROR_SUCCESS int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory) { TFileStream * pStream; TCHAR * szBuildFile; int nError = ERROR_FILE_NOT_FOUND; // Try to find any of the root files used in the history for (size_t i = 0; BuildTypes[i].szFileName != NULL; i++) { // Create the full name of the .agent.db file szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName); if (szBuildFile != NULL) { // Attempt to open the file pStream = FileStream_OpenFile(szBuildFile, STREAM_FLAG_READ_ONLY); if (pStream != NULL) { // Free the stream FileStream_Close(pStream); // Check for the data directory nError = CheckDataDirectory(hs, szDirectory); if (nError == ERROR_SUCCESS) { hs->szBuildFile = szBuildFile; hs->BuildFileType = BuildTypes[i].BuildFileType; return ERROR_SUCCESS; } } CASC_FREE(szBuildFile); } } return nError; }
int LoadBuildInfo(TCascStorage * hs) { QUERY_KEY InfoFile = {NULL, 0}; QUERY_KEY FileData = {NULL, 0}; TCHAR * szAgentFile; TCHAR * szInfoFile; bool bBuildConfigComplete = false; int nError = ERROR_SUCCESS; // Since HOTS build 30027, the game uses build.info file for storage info if(bBuildConfigComplete == false) { szInfoFile = CombinePath(hs->szRootPath, _T(".build.info")); if(szInfoFile != NULL) { nError = LoadTextFile(szInfoFile, &InfoFile); if(nError == ERROR_SUCCESS) { // Parse the info file nError = ParseInfoFile(hs, &InfoFile); if(nError == ERROR_SUCCESS) bBuildConfigComplete = true; // Free the loaded blob FreeCascBlob(&InfoFile); } CASC_FREE(szInfoFile); } } // If the info file has not been loaded, try the legacy .build.db if(bBuildConfigComplete == false) { szAgentFile = CombinePath(hs->szRootPath, _T(".build.db")); if(szAgentFile != NULL) { nError = LoadTextFile(szAgentFile, &FileData); if(nError == ERROR_SUCCESS) { nError = ParseAgentFile(hs, &FileData); if(nError == ERROR_SUCCESS) bBuildConfigComplete = true; FreeCascBlob(&FileData); } CASC_FREE(szAgentFile); } } // If the .build.info and .build.db file hasn't been loaded, if(nError == ERROR_SUCCESS && bBuildConfigComplete == false) { nError = ERROR_FILE_CORRUPT; } // Load the configuration file if(nError == ERROR_SUCCESS) { nError = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey, &FileData); if(nError == ERROR_SUCCESS) { nError = LoadCdnConfigFile(hs, &FileData); FreeCascBlob(&FileData); } } // Load the build file if(nError == ERROR_SUCCESS) { nError = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey, &FileData); if(nError == ERROR_SUCCESS) { nError = LoadCdnBuildFile(hs, &FileData); FreeCascBlob(&FileData); } } // Fill the index directory if(nError == ERROR_SUCCESS) { // First, check for more common "data" subdirectory if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL) return ERROR_SUCCESS; // Second, try the "darch" subdirectory (older builds of HOTS - Alpha) if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL) return ERROR_SUCCESS; nError = ERROR_FILE_NOT_FOUND; } return nError; }
static int ProcessFileFrame( LPBYTE pbOutBuffer, DWORD cbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex) { LPBYTE pbTempBuffer; LPBYTE pbWorkBuffer; DWORD cbTempBuffer = CASCLIB_MAX(cbInBuffer, cbOutBuffer); DWORD cbWorkBuffer = cbOutBuffer + 1; DWORD dwStepCount = 0; bool bWorkComplete = false; int nError = ERROR_SUCCESS; // Allocate the temporary buffer that will serve as output pbWorkBuffer = pbTempBuffer = CASC_ALLOC(BYTE, cbTempBuffer); if(pbWorkBuffer == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Perform the loop for(;;) { // Set the output buffer. // Even operations: extract to temporary buffer // Odd operations: extract to output buffer pbWorkBuffer = (dwStepCount & 0x01) ? pbOutBuffer : pbTempBuffer; cbWorkBuffer = (dwStepCount & 0x01) ? cbOutBuffer : cbTempBuffer; // Perform the operation specific to the operation ID switch(pbInBuffer[0]) { case 'E': // Encrypted files nError = CascDecrypt(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex); bWorkComplete = (nError != ERROR_SUCCESS); break; case 'Z': // ZLIB compressed files nError = CascDecompress(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1); bWorkComplete = true; break; case 'N': // Normal stored files nError = CascDirectCopy(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1); bWorkComplete = true; break; case 'F': // Recursive frames - not supported default: // Unrecognized - if we unpacked something, we consider it done nError = ERROR_NOT_SUPPORTED; bWorkComplete = true; assert(false); break; } // Are we done? if(bWorkComplete) break; // Set the input buffer to the work buffer pbInBuffer = pbWorkBuffer; cbInBuffer = cbWorkBuffer; dwStepCount++; } // If the data are currently in the temporary buffer, // we need to copy them to output buffer if(nError == ERROR_SUCCESS && pbWorkBuffer != pbOutBuffer) { if(cbWorkBuffer != cbOutBuffer) nError = ERROR_INSUFFICIENT_BUFFER; memcpy(pbOutBuffer, pbWorkBuffer, cbOutBuffer); } // Free the temporary buffer CASC_FREE(pbTempBuffer); return nError; }
static int ParseCoreTOC( TRootHandler_Diablo3 * pRootHandler, PCASC_MAP pPackageMap, LPBYTE pbCoreTocFile, LPBYTE pbCoreTocEnd) { PDIABLO3_CORE_TOC_HEADER pTocHeader; PDIABLO3_CORE_TOC_ENTRY pSortedEntries; PDIABLO3_CORE_TOC_ENTRY pTocEntry; LPBYTE pbCoreTocNames; DWORD dwFileIndexes = 0; DWORD i; // Check the space for header if((pbCoreTocFile + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbCoreTocEnd) return ERROR_FILE_CORRUPT; pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbCoreTocFile; pbCoreTocFile += sizeof(DIABLO3_CORE_TOC_HEADER); // Calculate space needed for allocation for(i = 0; i < DIABLO3_MAX_ASSETS; i++) { // Get the first entry pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); // Find out the entry with the maximum index for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) { if(pTocEntry->FileIndex >= dwFileIndexes) dwFileIndexes = pTocEntry->FileIndex + 1; pTocEntry++; } } // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs pSortedEntries = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwFileIndexes); if(pSortedEntries != NULL) { // Initialize all entries to invalid memset(pSortedEntries, 0xFF, dwFileIndexes * sizeof(DIABLO3_CORE_TOC_ENTRY)); // Populate the linear array with the entries for(i = 0; i < DIABLO3_MAX_ASSETS; i++) { // Set the pointers pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]); // Setup the entries for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) { pSortedEntries[pTocEntry->FileIndex].AssetIndex = pTocEntry->AssetIndex; pSortedEntries[pTocEntry->FileIndex].FileIndex = pTocEntry->FileIndex; pSortedEntries[pTocEntry->FileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocFile) + pTocEntry->NameOffset; pTocEntry++; } } // Now use the linear array to resolve the asset indexes and plain names ResolveFullFileNames(pRootHandler, pSortedEntries, pPackageMap, pbCoreTocFile, dwFileIndexes); CASC_FREE(pSortedEntries); } return ERROR_SUCCESS; }
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); }
int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { TRootHandler_Diablo3 * pRootHandler; PCASC_MAP pPackageMap = NULL; LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; LPBYTE pbPackagesDat = NULL; DWORD dwTotalFileCount; DWORD cbPackagesDat = 0; int nError; // Allocate the root handler object hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Diablo3, 1); if(pRootHandler == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Fill-in the handler functions memset(pRootHandler, 0, sizeof(TRootHandler_Diablo3)); pRootHandler->Insert = (ROOT_INSERT)D3Handler_Insert; pRootHandler->Search = (ROOT_SEARCH)D3Handler_Search; pRootHandler->EndSearch = (ROOT_ENDSEARCH)D3Handler_EndSearch; pRootHandler->GetKey = (ROOT_GETKEY)D3Handler_GetKey; pRootHandler->Close = (ROOT_CLOSE)D3Handler_Close; pRootHandler->GetFileId = (ROOT_GETFILEID)D3Handler_GetFileId; // Fill-in the flags pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES; // Scan the total number of files in the root directories // Reserve space for extra files dwTotalFileCount = ScanDirectoryFile(hs, pbRootFile, pbRootFileEnd); if(dwTotalFileCount == 0) return ERROR_FILE_CORRUPT; dwTotalFileCount += CASC_EXTRA_FILES; // Allocate the global linear file table // Note: This is about 18 MB of memory for Diablo III PTR build 30013 nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, dwTotalFileCount); if(nError != ERROR_SUCCESS) return nError; // Allocate global buffer for file names. // The size of the buffer was taken from Diablo III build 30013 nError = Array_Create(&pRootHandler->FileNames, char, 0x01000000); if(nError != ERROR_SUCCESS) return nError; // Create map of ROOT_ENTRY -> FileEntry pRootHandler->pRootMap = Map_Create(dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash)); if(pRootHandler->pRootMap == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Parse the ROOT file and insert all entries in the file table nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFileEnd, DIABLO3_INVALID_INDEX); if(nError == ERROR_SUCCESS) { size_t dwRootEntries = pRootHandler->FileTable.ItemCount; // We expect the number of level-0 to be less than maximum assert(dwRootEntries < DIABLO3_MAX_SUBDIRS); // Now parse the all root items and load them for(DWORD i = 0; i < dwRootEntries; i++) { PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i); // Load the entire file to memory pbRootFile = LoadFileToMemory(hs, pRootEntry->EncodingKey.Value, &cbRootFile); if(pbRootFile != NULL) { nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFile + cbRootFile, i); CASC_FREE(pbRootFile); } } } // Note: The file "Base\Data_D3\PC\Misc\Packages.dat" contains the names // of the files (without level-0 and level-1 directory). We can use these // names for supplying the missing extensions if(nError == ERROR_SUCCESS) { // Load the entire file to memory pbPackagesDat = LoadFileToMemory(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat", &cbPackagesDat); if(pbPackagesDat != NULL) { pPackageMap = CreatePackageMap(pbPackagesDat, pbPackagesDat + cbPackagesDat); } } // Vast majorify of files at this moment don't have names. // We can load the Base\CoreTOC.dat file in order // to get directory asset indexes, file names and extensions if(nError == ERROR_SUCCESS) { LPBYTE pbCoreTOC; DWORD cbCoreTOC = 0; // Load the entire file to memory pbCoreTOC = LoadFileToMemory(hs, "Base\\CoreTOC.dat", &cbCoreTOC); if(pbCoreTOC != NULL) { ParseCoreTOC(pRootHandler, pPackageMap, pbCoreTOC, pbCoreTOC + cbCoreTOC); CASC_FREE(pbCoreTOC); } } // Free the packages map if(pPackageMap != NULL) Map_Free(pPackageMap); if(pbPackagesDat != NULL) CASC_FREE(pbPackagesDat); return nError; }
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); }
int LoadBuildInfo(TCascStorage * hs) { TCHAR * szAgentFile; TCHAR * szInfoFile; void * pvListFile; bool bBuildConfigComplete = false; int nError = ERROR_SUCCESS; // Since HOTS build 30027, the game uses .build.info file for storage info if(bBuildConfigComplete == false) { szInfoFile = CombinePath(hs->szRootPath, _T(".build.info")); if(szInfoFile != NULL) { pvListFile = ListFile_OpenExternal(szInfoFile); if(pvListFile != NULL) { // Parse the info file nError = ParseInfoFile(hs, pvListFile); if(nError == ERROR_SUCCESS) bBuildConfigComplete = true; ListFile_Free(pvListFile); } CASC_FREE(szInfoFile); } } // If the info file has not been loaded, try the legacy .build.db if(bBuildConfigComplete == false) { szAgentFile = CombinePath(hs->szRootPath, _T(".build.db")); if(szAgentFile != NULL) { pvListFile = ListFile_OpenExternal(szAgentFile); if(pvListFile != NULL) { nError = ParseAgentFile(hs, pvListFile); if(nError == ERROR_SUCCESS) bBuildConfigComplete = true; ListFile_Free(pvListFile); } CASC_FREE(szAgentFile); } } // If the .build.info OR .build.db file has been loaded, // proceed with loading the CDN config file and CDN build file if(nError == ERROR_SUCCESS && bBuildConfigComplete) { // Load the configuration file pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey); if(pvListFile != NULL) { nError = LoadCdnConfigFile(hs, pvListFile); ListFile_Free(pvListFile); } else nError = ERROR_FILE_NOT_FOUND; } // Load the build file if(nError == ERROR_SUCCESS && bBuildConfigComplete) { pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey); if(pvListFile != NULL) { nError = LoadCdnBuildFile(hs, pvListFile); ListFile_Free(pvListFile); } else nError = ERROR_FILE_NOT_FOUND; } // Fill the index directory if(nError == ERROR_SUCCESS) { // First, check for more common "data" subdirectory if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL) return ERROR_SUCCESS; // Second, try the "darch" subdirectory (older builds of HOTS - Alpha) if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL) return ERROR_SUCCESS; nError = ERROR_FILE_NOT_FOUND; } return nError; }
static void WowHandler_EndSearch(TRootHandler_WoW6 * /* pRootHandler */, TCascSearch * pSearch) { if(pSearch->pRootContext != NULL) CASC_FREE(pSearch->pRootContext); pSearch->pRootContext = NULL; }