/*****************************************************************************/ /* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */ /*---------------------------------------------------------------------------*/ /* Storage functions for CASC */ /* Note: WoW6 offsets refer to WoW.exe 6.0.3.19116 (32-bit) */ /* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ /* 29.04.14 1.00 Lad Created */ /*****************************************************************************/ #define __CASCLIB_SELF__ #include "CascLib.h" #include "CascCommon.h" #ifdef INTERLOCKED_NOT_SUPPORTED #pragma error Interlocked operations are not supported on this architecture. Multi-threaded access to CASC storages will not work properly. #endif //----------------------------------------------------------------------------- // Local defines // Limit for "additional" items in CKey table #define CASC_MAX_EXTRA_ITEMS 0x40 //----------------------------------------------------------------------------- // DEBUG functions #define CHECKED_KEY {0x00, 0x00, 0x0F, 0x84} #if defined(_DEBUG) && defined(CHECKED_KEY) inline bool CheckForXKey(LPBYTE XKey) { BYTE CheckedKey[] = CHECKED_KEY; for(size_t i = 0; i < _countof(CheckedKey); i++) { if(XKey[i] != CheckedKey[i]) return false; } return true; } #define BREAK_ON_WATCHED(XKey) if(CheckForXKey((LPBYTE)XKey)) { __debugbreak(); } #else #define BREAK_ON_WATCHED(XKey) { /* NOTHING */ } #endif //----------------------------------------------------------------------------- // TCascStorage class functions TCascStorage::TCascStorage() { // Prepare the base storage parameters ClassName = CASC_MAGIC_STORAGE; pRootHandler = NULL; dwRefCount = 1; szRootPath = szDataPath = szIndexPath = szBuildFile = szCdnServers = szCdnPath = szCdnHostUrl = szCodeName = NULL; szIndexFormat = NULL; szRegion = NULL; szBuildKey = NULL; memset(DataFiles, 0, sizeof(DataFiles)); memset(IndexFiles, 0, sizeof(IndexFiles)); CascInitLock(StorageLock); dwDefaultLocale = 0; dwBuildNumber = 0; dwFeatures = 0; BuildFileType = CascBuildNone; LastFailKeyName = 0; LocalFiles = TotalFiles = EKeyEntries = EKeyLength = FileOffsetBits = 0; pArgs = NULL; } TCascStorage::~TCascStorage() { // Free the root handler if(pRootHandler != NULL) delete pRootHandler; pRootHandler = NULL; // Close all data files for(size_t i = 0; i < CASC_MAX_DATA_FILES; i++) { FileStream_Close(DataFiles[i]); DataFiles[i] = NULL; } // Cleanup space occupied by index files FreeIndexFiles(this); // Cleanup the lock CascFreeLock(StorageLock); // Free the file paths CASC_FREE(szDataPath); CASC_FREE(szRootPath); CASC_FREE(szBuildFile); CASC_FREE(szIndexPath); CASC_FREE(szCdnServers); CASC_FREE(szCdnPath); CASC_FREE(szCodeName); CASC_FREE(szCdnHostUrl); CASC_FREE(szRegion); CASC_FREE(szBuildKey); // Free the blobs FreeCascBlob(&CdnConfigKey); FreeCascBlob(&CdnBuildKey); FreeCascBlob(&ArchiveGroup); FreeCascBlob(&ArchivesKey); FreeCascBlob(&PatchArchivesKey); FreeCascBlob(&PatchArchivesGroup); FreeCascBlob(&BuildFiles); ClassName = 0; } TCascStorage * TCascStorage::AddRef() { // Need this to be atomic to make multi-threaded file opens work CascInterlockedIncrement(&dwRefCount); return this; } TCascStorage * TCascStorage::Release() { // If the reference count reached zero, we close the archive // Need this to be atomic to make multi-threaded file opens work if(CascInterlockedDecrement(&dwRefCount) == 0) { // Release all references in the socket cache if(dwFeatures & CASC_FEATURE_ONLINE) sockets_set_caching(false); // Delete the object and return NULL delete this; return NULL; } return this; } //----------------------------------------------------------------------------- // Local functions void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, size_t * pcbLengthNeeded) { // Verify the output length if(cbLength < cbMinLength) { SetCascError(ERROR_INSUFFICIENT_BUFFER); pvBuffer = NULL; } // Give the output length and return result if(pcbLengthNeeded != NULL) pcbLengthNeeded[0] = cbMinLength; return pvBuffer; } static LPTSTR CheckForIndexDirectory(TCascStorage * hs, LPCTSTR szSubDir) { TCHAR szIndexPath[MAX_PATH]; // Combine the index path CombinePath(szIndexPath, _countof(szIndexPath), hs->szDataPath, szSubDir, NULL); // Check whether the path exists if(!DirectoryExists(szIndexPath)) return NULL; return CascNewStr(szIndexPath); } // Inserts an entry from the text build file static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_CKEY_ENTRY & CKeyEntry) { PCASC_CKEY_ENTRY pCKeyEntry = NULL; // Stop on file-of-interest BREAK_ON_WATCHED(CKeyEntry.EKey); // Skip entries without any key if(CKeyEntry.Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY)) { // Check if there is an existing entry if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKeyEntry.CKey)) == NULL) { // Insert a new entry to the array. DO NOT ALLOW enlarge array here pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); if(pCKeyEntry == NULL) return NULL; // Fill in the item memcpy(pCKeyEntry, &CKeyEntry, sizeof(CASC_CKEY_ENTRY)); // If we have CKey present, insert it to the CKey map if(CKeyEntry.Flags & CASC_CE_HAS_CKEY) hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); // If we have EKey present, insert it to the EKey map if(CKeyEntry.Flags & CASC_CE_HAS_EKEY) hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); } else { if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) pCKeyEntry->ContentSize = CKeyEntry.ContentSize; if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) pCKeyEntry->EncodedSize = CKeyEntry.EncodedSize; } } return pCKeyEntry; } // Inserts an entry from ENCODING static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, PFILE_CKEY_ENTRY pFileEntry) { PCASC_CKEY_ENTRY pCKeyEntry; // Stop on file-of-interest BREAK_ON_WATCHED(pFileEntry->EKey); // Insert a new entry to the array. DO NOT ALLOW enlarge array here pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); if(pCKeyEntry != NULL) { // Initialize the entry CopyMemory16(pCKeyEntry->CKey, pFileEntry->CKey); CopyMemory16(pCKeyEntry->EKey, pFileEntry->EKey); pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64; pCKeyEntry->TagBitMask = 0; pCKeyEntry->ContentSize = ConvertBytesToInteger_4(pFileEntry->ContentSize); pCKeyEntry->EncodedSize = CASC_INVALID_SIZE; pCKeyEntry->Flags = CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING; pCKeyEntry->RefCount = 0; pCKeyEntry->SpanCount = 1; pCKeyEntry->Priority = 0; // Copy the information from index files to the CKey entry CopyEKeyEntry(hs, pCKeyEntry); // Insert the item into both maps hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); } else { assert(false); } return pCKeyEntry; } // Inserts an entry from DOWNLOAD static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_DOWNLOAD_ENTRY & DlEntry) { PCASC_CKEY_ENTRY pCKeyEntry; // Stop on file-of-interest BREAK_ON_WATCHED(DlEntry.EKey); // Check whether the entry is already there if((pCKeyEntry = FindCKeyEntry_EKey(hs, DlEntry.EKey)) == NULL) { // Insert dummy CKey entry to the array. DO NOT allow to enlarge the array pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); if(pCKeyEntry == NULL) { assert(false); return NULL; } // Copy the entry ZeroMemory16(pCKeyEntry->CKey); CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey); pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64; pCKeyEntry->TagBitMask = 0; pCKeyEntry->ContentSize = CASC_INVALID_SIZE; pCKeyEntry->EncodedSize = (DWORD)DlEntry.EncodedSize; pCKeyEntry->Flags = CASC_CE_HAS_EKEY | CASC_CE_IN_DOWNLOAD; pCKeyEntry->RefCount = 0; pCKeyEntry->SpanCount = 1; // Copy the information from index files to the CKey entry CopyEKeyEntry(hs, pCKeyEntry); // Insert the entry to the map. Only insert it to the EKey map, as there is no CKey present hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); } else { // Copy the EKey if we only have partial one if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY_PARTIAL) CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey); // Supply the encoded size, if unknown yet if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) pCKeyEntry->EncodedSize = (DWORD)DlEntry.EncodedSize; pCKeyEntry->Flags = (pCKeyEntry->Flags & ~CASC_CE_HAS_EKEY_PARTIAL) | CASC_CE_IN_DOWNLOAD; } // Supply the rest pCKeyEntry->Priority = DlEntry.Priority; return pCKeyEntry; } static DWORD CopyBuildFileItemsToCKeyArray(TCascStorage * hs) { // Insert the well-known files // InsertCKeyEntry(hs, hs->EncodingCKey); InsertCKeyEntry(hs, hs->DownloadCKey); InsertCKeyEntry(hs, hs->InstallCKey); InsertCKeyEntry(hs, hs->PatchFile); InsertCKeyEntry(hs, hs->RootFile); InsertCKeyEntry(hs, hs->SizeFile); InsertCKeyEntry(hs, hs->VfsRoot); // Insert all VFS roots for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++) { PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); InsertCKeyEntry(hs, *pCKeyEntry); } return ERROR_SUCCESS; } // Estimate the total number of files, so we won't have to re-allocate arrays and maps // and thus speed-up storage loading. In theory, we could guess the file count by // measuring size of ENCODING or DOWNLOAD manifests. static size_t GetEstimatedNumberOfFiles(TCascStorage * hs) { size_t nNumberOfFiles1 = 0; size_t nNumberOfFiles2 = 0; // If we know the size of DOWNLOAD at this point, we estimate number of files from it. // Size of one entry in DOWNLOAD is at least 22 bytes. This is the most reliable method. // However, for some online storages ("agent"), this is a very small value if(hs->DownloadCKey.ContentSize != CASC_INVALID_SIZE) nNumberOfFiles1 = (hs->DownloadCKey.ContentSize / sizeof(FILE_DOWNLOAD_ENTRY)) + CASC_MAX_EXTRA_ITEMS; // If we know the size of ENCODING at this point, we estimate number of files from it. // Size of one entry in ENCODING is at least 38 bytes. This method fails on storages // with TVFS file system, as ENCODING only contains a small subset of file. // Fortunately, all known TVFS-based storages have "download-size" present if(hs->EncodingCKey.ContentSize != CASC_INVALID_SIZE) nNumberOfFiles2 = (hs->EncodingCKey.ContentSize / sizeof(FILE_CKEY_ENTRY)) + CASC_MAX_EXTRA_ITEMS; // Do we know any of them? if(nNumberOfFiles1 || nNumberOfFiles2) return CASCLIB_MAX(nNumberOfFiles1, nNumberOfFiles2); // Older storages (HOTS before 39445, WoW before 19116) don't state sizes of ENCODING // and DOWNLOAD in the Build Config files. Solution: Assume there is max 1M of files return 1000000; } static DWORD InitCKeyArray(TCascStorage * hs) { size_t nNumberOfFiles = GetEstimatedNumberOfFiles(hs); DWORD dwErrCode; // // Allocate array and map of CKey entries // // Create the array of CKey items dwErrCode = hs->CKeyArray.Create(sizeof(CASC_CKEY_ENTRY), nNumberOfFiles); if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Create the map CKey -> CASC_CKEY_ENTRY dwErrCode = hs->CKeyMap.Create(nNumberOfFiles, MD5_HASH_SIZE, FIELD_OFFSET(CASC_CKEY_ENTRY, CKey)); if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Create the map CKey -> CASC_CKEY_ENTRY. Note that TVFS root references files // using 9-byte EKey, so cut the search EKey length to 9 bytes dwErrCode = hs->EKeyMap.Create(nNumberOfFiles, CASC_EKEY_SIZE, FIELD_OFFSET(CASC_CKEY_ENTRY, EKey)); if(dwErrCode != ERROR_SUCCESS) return dwErrCode; return ERROR_SUCCESS; } int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, size_t cbFileData) { PFILE_ENCODING_HEADER pFileHeader = (PFILE_ENCODING_HEADER)pbFileData; // Check the signature ('EN') and version if(cbFileData < sizeof(FILE_ENCODING_HEADER) || pFileHeader->Magic != FILE_MAGIC_ENCODING || pFileHeader->Version != 0x01) return ERROR_BAD_FORMAT; // Note that we don't support CKey and EKey sizes other than 0x10 in the ENCODING file if(pFileHeader->CKeyLength != MD5_HASH_SIZE || pFileHeader->EKeyLength != MD5_HASH_SIZE) return ERROR_BAD_FORMAT; EnHeader.Magic = pFileHeader->Magic; EnHeader.Version = pFileHeader->Version; EnHeader.CKeyLength = pFileHeader->CKeyLength; EnHeader.EKeyLength = pFileHeader->EKeyLength; EnHeader.CKeyPageCount = ConvertBytesToInteger_4(pFileHeader->CKeyPageCount); EnHeader.CKeyPageSize = ConvertBytesToInteger_2(pFileHeader->CKeyPageSize) * 1024; EnHeader.EKeyPageCount = ConvertBytesToInteger_4(pFileHeader->EKeyPageCount); EnHeader.EKeyPageSize = ConvertBytesToInteger_2(pFileHeader->EKeyPageSize) * 1024; EnHeader.ESpecBlockSize = ConvertBytesToInteger_4(pFileHeader->ESpecBlockSize); return ERROR_SUCCESS; } static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage) { PFILE_CKEY_ENTRY pFileEntry; LPBYTE pbFileEntry = pbPageBegin; // Sanity checks assert(hs->CKeyMap.IsInitialized()); assert(hs->EKeyMap.IsInitialized()); // Parse all encoding entries while(pbFileEntry < pbEndOfPage) { // Get pointer to the encoding entry pFileEntry = (PFILE_CKEY_ENTRY)pbFileEntry; if(pFileEntry->EKeyCount == 0) break; // Example of a file entry with multiple EKeys: // Overwatch build 24919, CKey: 0e 90 94 fa d2 cb 85 ac d0 7c ea 09 f9 c5 ba 00 // BREAKIF(pFileEntry->EKeyCount > 1); // BREAK_ON_XKEY3(pFileEntry->CKey, 0x34, 0x82, 0x1f); // Insert the entry to the central CKey table InsertCKeyEntry(hs, pFileEntry); // Move to the next encoding entry pbFileEntry = pbFileEntry + 2 + 4 + EnHeader.CKeyLength + (pFileEntry->EKeyCount * EnHeader.EKeyLength); } return ERROR_SUCCESS; } static int LoadEncodingManifest(TCascStorage * hs) { CASC_CKEY_ENTRY & CKeyEntry = hs->EncodingCKey; LPBYTE pbEncodingFile; DWORD cbEncodingFile = 0; DWORD dwErrCode = ERROR_SUCCESS; // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading ENCODING manifest", NULL, 0, 0)) return ERROR_CANCELLED; // Fill-in the information from the index entry and insert it to the file tree if(!CopyEKeyEntry(hs, &CKeyEntry)) return ERROR_FILE_NOT_FOUND; InsertCKeyEntry(hs, CKeyEntry); // Load the entire encoding file to memory pbEncodingFile = LoadInternalFileToMemory(hs, &hs->EncodingCKey, &cbEncodingFile); if(pbEncodingFile != NULL && cbEncodingFile != 0) { CASC_ENCODING_HEADER EnHeader; // Capture the header of the ENCODING file dwErrCode = CaptureEncodingHeader(EnHeader, pbEncodingFile, cbEncodingFile); if(dwErrCode == ERROR_SUCCESS) { // Get the CKey page header and the first page PFILE_CKEY_PAGE pPageHeader = (PFILE_CKEY_PAGE)(pbEncodingFile + sizeof(FILE_ENCODING_HEADER) + EnHeader.ESpecBlockSize); LPBYTE pbCKeyPage = (LPBYTE)(pPageHeader + EnHeader.CKeyPageCount); // Go through all CKey pages and verify them for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++) { // Check if there is enough space in the buffer if((pbCKeyPage + EnHeader.CKeyPageSize) > (pbEncodingFile + cbEncodingFile)) { dwErrCode = ERROR_FILE_CORRUPT; break; } // Check the hash of the entire segment // Note that verifying takes considerable time of the storage loading // if(!VerifyDataBlockHash(pbCKeyPage, EnHeader.CKeyPageSize, pEncodingSegment->SegmentHash)) // { // dwErrCode = ERROR_FILE_CORRUPT; // break; // } // Check if the CKey matches with the expected first value if(memcmp(((PFILE_CKEY_ENTRY)pbCKeyPage)->CKey, pPageHeader[i].FirstKey, MD5_HASH_SIZE)) { dwErrCode = ERROR_FILE_CORRUPT; break; } // Load the entire page of CKey entries. // This operation will never fail, because all memory is already pre-allocated dwErrCode = LoadEncodingCKeyPage(hs, EnHeader, pbCKeyPage, pbCKeyPage + EnHeader.CKeyPageSize); if(dwErrCode != ERROR_SUCCESS) break; // Move to the next CKey page pbCKeyPage += EnHeader.CKeyPageSize; } } // All CKey->EKey entries from the text build files need to be copied to the CKey array if(dwErrCode == ERROR_SUCCESS) { dwErrCode = CopyBuildFileItemsToCKeyArray(hs); } // Free the loaded ENCODING file CASC_FREE(pbEncodingFile); } else { dwErrCode = GetCascError(); } return dwErrCode; } size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount) { size_t nBitmapLength; nBitmapLength = (EntryCount / 8) + ((EntryCount & 0x07) ? 1 : 0); if((pbFilePtr + nBitmapLength) > pbFileEnd) nBitmapLength = (pbFileEnd - pbFilePtr); return nBitmapLength; } int CaptureDownloadHeader(CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, size_t cbFileData) { PFILE_DOWNLOAD_HEADER pFileHeader = (PFILE_DOWNLOAD_HEADER)pbFileData; // Check the signature ('DL') and version if(cbFileData < sizeof(FILE_DOWNLOAD_HEADER) || pFileHeader->Magic != FILE_MAGIC_DOWNLOAD || pFileHeader->Version > 3) return ERROR_BAD_FORMAT; // Note that we don't support CKey sizes greater than 0x10 in the DOWNLOAD file if(pFileHeader->EKeyLength > MD5_HASH_SIZE) return ERROR_BAD_FORMAT; // Capture the header version 1 memset(&DlHeader, 0, sizeof(CASC_DOWNLOAD_HEADER)); DlHeader.Magic = pFileHeader->Magic; DlHeader.Version = pFileHeader->Version; DlHeader.EKeyLength = pFileHeader->EKeyLength; DlHeader.EntryHasChecksum = pFileHeader->EntryHasChecksum; DlHeader.EntryCount = ConvertBytesToInteger_4(pFileHeader->EntryCount); DlHeader.TagCount = ConvertBytesToInteger_2(pFileHeader->TagCount); DlHeader.HeaderLength = FIELD_OFFSET(FILE_DOWNLOAD_HEADER, FlagByteSize); DlHeader.EntryLength = DlHeader.EKeyLength + 5 + 1 + (DlHeader.EntryHasChecksum ? 4 : 0); // Capture header version 2 if(pFileHeader->Version >= 2) { DlHeader.FlagByteSize = pFileHeader->FlagByteSize; DlHeader.HeaderLength = FIELD_OFFSET(FILE_DOWNLOAD_HEADER, BasePriority); DlHeader.EntryLength += DlHeader.FlagByteSize; // Capture header version 3 if(pFileHeader->Version >= 3) { DlHeader.BasePriority = pFileHeader->BasePriority; DlHeader.HeaderLength = sizeof(FILE_DOWNLOAD_HEADER); } } return ERROR_SUCCESS; } int CaptureDownloadEntry(CASC_DOWNLOAD_HEADER & DlHeader, CASC_DOWNLOAD_ENTRY & DlEntry, LPBYTE pbFilePtr, LPBYTE pbFileEnd) { // Check the range if((pbFilePtr + DlHeader.EntryLength) >= pbFileEnd) return ERROR_BAD_FORMAT; memset(&DlEntry, 0, sizeof(CASC_DOWNLOAD_ENTRY)); // Copy the EKey memcpy(DlEntry.EKey, pbFilePtr, DlHeader.EKeyLength); pbFilePtr += DlHeader.EKeyLength; // Convert the file size DlEntry.EncodedSize = ConvertBytesToInteger_5(pbFilePtr); pbFilePtr += 5; // Copy the file priority DlEntry.Priority = pbFilePtr[0]; pbFilePtr++; // Copy the checksum if(DlHeader.EntryHasChecksum) { DlEntry.Checksum = ConvertBytesToInteger_4(pbFilePtr); pbFilePtr += 4; } // Copy the flags DlEntry.Flags = ConvertBytesToInteger_X(pbFilePtr, DlHeader.FlagByteSize); return ERROR_SUCCESS; } int CaptureDownloadTag(CASC_DOWNLOAD_HEADER & DlHeader, CASC_TAG_ENTRY1 & DlTag, LPBYTE pbFilePtr, LPBYTE pbFileEnd) { LPBYTE pbSaveFilePtr = pbFilePtr; // Prepare the tag structure memset(&DlTag, 0, sizeof(CASC_TAG_ENTRY1)); DlTag.szTagName = (const char *)pbFilePtr; // Skip the tag string while(pbFilePtr < pbFileEnd && pbFilePtr[0] != 0) pbFilePtr++; if(pbFilePtr >= pbFileEnd) return ERROR_BAD_FORMAT; // Save the length of the tag name DlTag.NameLength = (pbFilePtr - pbSaveFilePtr); pbFilePtr++; // Get the tag value if((pbFilePtr + sizeof(DWORD)) > pbFileEnd) return ERROR_BAD_FORMAT; DlTag.TagValue = ConvertBytesToInteger_2(pbFilePtr); pbFilePtr += 2; // Get the bitmap DlTag.Bitmap = pbFilePtr; // Get the bitmap length. // If the bitmap is last in the list and it's shorter than declared, we make it shorter DlTag.BitmapLength = GetTagBitmapLength(pbFilePtr, pbFileEnd, DlHeader.EntryCount); // Get the entry length DlTag.TagLength = (pbFilePtr - pbSaveFilePtr) + DlTag.BitmapLength; return ERROR_SUCCESS; } static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, LPBYTE pbFileEnd) { PCASC_TAG_ENTRY1 TagArray = NULL; LPBYTE pbEntries = pbFileData + DlHeader.HeaderLength; LPBYTE pbEntry = pbEntries; LPBYTE pbTags = pbEntries + DlHeader.EntryLength * DlHeader.EntryCount; LPBYTE pbTag = pbTags; size_t nMaxNameLength = 0; size_t nTagEntryLengh = 0; DWORD dwErrCode = ERROR_SUCCESS; // Does the storage support tags? if(DlHeader.TagCount != 0) { // Remember that we support tags hs->dwFeatures |= CASC_FEATURE_TAGS; // Allocate space for the tag array TagArray = CASC_ALLOC(DlHeader.TagCount); if(TagArray != NULL) { // Get the longest tag name for(DWORD i = 0; i < DlHeader.TagCount; i++) { if(CaptureDownloadTag(DlHeader, TagArray[i], pbTag, pbFileEnd) == ERROR_SUCCESS) nMaxNameLength = CASCLIB_MAX(nMaxNameLength, TagArray[i].NameLength); pbTag = pbTag + TagArray[i].TagLength; } // Determine the tag entry length nTagEntryLengh = FIELD_OFFSET(CASC_TAG_ENTRY2, szTagName) + nMaxNameLength; nTagEntryLengh = ALIGN_TO_SIZE(nTagEntryLengh, 8); // Load the tags into array in the storage structure dwErrCode = hs->TagsArray.Create(nTagEntryLengh, DlHeader.TagCount); if(dwErrCode == ERROR_SUCCESS) { // Convert the array of CASC_DOWNLOAD_TAG1 to array of CASC_DOWNLOAD_TAG2 for(DWORD i = 0; i < DlHeader.TagCount; i++) { PCASC_TAG_ENTRY1 pSourceTag = &TagArray[i]; PCASC_TAG_ENTRY2 pTargetTag; // Insert the tag to the array pTargetTag = (PCASC_TAG_ENTRY2)hs->TagsArray.Insert(1); if(pTargetTag == NULL) { dwErrCode = ERROR_NOT_ENOUGH_MEMORY; break; } // Copy the tag structure memset(pTargetTag, 0, nTagEntryLengh); memcpy(pTargetTag->szTagName, pSourceTag->szTagName, pSourceTag->NameLength); pTargetTag->NameLength = pSourceTag->NameLength; pTargetTag->TagValue = pSourceTag->TagValue; } } } else { dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } } // Now parse all entries. For each entry, mark the corresponding tag bit in the EKey table for(DWORD i = 0; i < DlHeader.EntryCount; i++) { CASC_DOWNLOAD_ENTRY DlEntry; PCASC_CKEY_ENTRY pCKeyEntry; ULONGLONG TagBit = 1; size_t BitMaskOffset = (i / 8); size_t TagItemCount = hs->TagsArray.ItemCount(); BYTE BitMaskBit = 0x80 >> (i % 8); // Capture the download entry if(CaptureDownloadEntry(DlHeader, DlEntry, pbEntry, pbFileEnd) != ERROR_SUCCESS) break; // COD4: zone/base.xpak //BREAK_ON_XKEY3(DlEntry.EKey, 0xa5, 0x00, 0x16); // Insert the entry to the central CKey table if((pCKeyEntry = InsertCKeyEntry(hs, DlEntry)) != NULL) { // Supply the tag bits for(size_t j = 0; j < TagItemCount; j++) { // Set the bit in the entry, if the tag for it is present if((BitMaskOffset < TagArray[j].BitmapLength) && (TagArray[j].Bitmap[BitMaskOffset] & BitMaskBit)) pCKeyEntry->TagBitMask |= TagBit; // Move to the next bit TagBit <<= 1; } } // Move to the next entry pbEntry += DlHeader.EntryLength; } // Free the tag array, if any CASC_FREE(TagArray); // Remember the total file count hs->TotalFiles = hs->CKeyArray.ItemCount(); return dwErrCode; } static int LoadDownloadManifest(TCascStorage * hs) { PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->DownloadCKey.CKey); LPBYTE pbDownloadFile = NULL; DWORD cbDownloadFile = 0; DWORD dwErrCode = ERROR_SUCCESS; // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading DOWNLOAD manifest", NULL, 0, 0)) return ERROR_CANCELLED; // Load the entire DOWNLOAD file to memory pbDownloadFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbDownloadFile); if(pbDownloadFile != NULL && cbDownloadFile != 0) { CASC_DOWNLOAD_HEADER DlHeader; // Capture the header of the DOWNLOAD file dwErrCode = CaptureDownloadHeader(DlHeader, pbDownloadFile, cbDownloadFile); if(dwErrCode == ERROR_SUCCESS) { // Parse the entire download manifest dwErrCode = LoadDownloadManifest(hs, DlHeader, pbDownloadFile, pbDownloadFile + cbDownloadFile); } // Free the loaded manifest CASC_FREE(pbDownloadFile); } // If the DOWNLOAD manifest is not present, we won't abort the downloading process. return dwErrCode; } //----------------------------------------------------------------------------- // INSTALL manifest. This is a replacement for ROOT, if loading ROOT fails // https://wowdev.wiki/TACT#Install_manifest static int LoadInstallManifest(TCascStorage * hs) { PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->InstallCKey.CKey); LPBYTE pbInstallFile = NULL; DWORD cbInstallFile = 0; DWORD dwErrCode = ERROR_SUCCESS; // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading INSTALL manifest", NULL, 0, 0)) return ERROR_CANCELLED; // Load the entire DOWNLOAD file to memory pbInstallFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbInstallFile); if(pbInstallFile != NULL && cbInstallFile != 0) { dwErrCode = RootHandler_CreateInstall(hs, pbInstallFile, cbInstallFile); CASC_FREE(pbInstallFile); } else { dwErrCode = GetCascError(); } return dwErrCode; } static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC_CKEY_ENTRY & FakeCKeyEntry, DWORD dwFlags = 0) { PCASC_CKEY_ENTRY pCKeyEntry = NULL; // We need to find the CKey entry in the central array if(FakeCKeyEntry.Flags & CASC_CE_HAS_CKEY) { // Did we find anything? pCKeyEntry = FindCKeyEntry_CKey(hs, FakeCKeyEntry.CKey); if(pCKeyEntry != NULL) { // Insert the key to the root handler. Note that the file can already be referenced // ("index" vs "vfs-root" in Warcraft III storages) hs->pRootHandler->Insert(szFileName, pCKeyEntry); // Copy some flags pCKeyEntry->Flags |= (dwFlags | CASC_CE_IN_BUILD); return true; } } // Special case: the PATCH file is usually not in any indices. // It's also never locally available if((dwFlags & CASC_CE_FILE_PATCH) && (hs->dwFeatures & CASC_FEATURE_ONLINE)) { // Get or insert the PATCH entry pCKeyEntry = InsertCKeyEntry(hs, FakeCKeyEntry); if(pCKeyEntry != NULL) { hs->pRootHandler->Insert(szFileName, pCKeyEntry); pCKeyEntry->Flags |= (dwFlags | CASC_CE_IN_BUILD); return true; } } return false; } static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask) { PCASC_CKEY_ENTRY pCKeyEntry = &hs->RootFile; TRootHandler * pOldRootHandler = NULL; PDWORD FileSignature; LPBYTE pbRootFile = NULL; DWORD cbRootFile = 0; DWORD dwErrCode = ERROR_BAD_FORMAT; // Sanity checks assert(hs->CKeyMap.IsInitialized() == true); assert(hs->pRootHandler == NULL); // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading ROOT manifest", NULL, 0, 0)) return ERROR_CANCELLED; // Locale: The default parameter is 0 - in that case, we load all locales dwLocaleMask = (dwLocaleMask != 0) ? dwLocaleMask : 0xFFFFFFFF; // Prioritize the VFS root over legacy ROOT file, unless it's WoW if(hs->VfsRoot.ContentSize != CASC_INVALID_SIZE) pCKeyEntry = &hs->VfsRoot; __LoadRootFile: // Load the entire ROOT file to memory pCKeyEntry = FindCKeyEntry_CKey(hs, pCKeyEntry->CKey); pbRootFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbRootFile); if(pbRootFile != NULL) { // Ignore ROOT files that contain just a MD5 hash if(cbRootFile > MD5_STRING_SIZE) { // Check the type of the ROOT file FileSignature = (PDWORD)pbRootFile; switch(FileSignature[0]) { case CASC_MNDX_ROOT_SIGNATURE: dwErrCode = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile); break; case CASC_DIABLO3_ROOT_SIGNATURE: dwErrCode = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile); break; case CASC_TVFS_ROOT_SIGNATURE: dwErrCode = RootHandler_CreateTVFS(hs, pbRootFile, cbRootFile); break; case CASC_WOW82_ROOT_SIGNATURE: dwErrCode = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); break; default: // // Each of these handler creators must verify their format first. // If the format was not recognized, they need to return ERROR_BAD_FORMAT // dwErrCode = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile); if(dwErrCode == ERROR_BAD_FORMAT) { dwErrCode = RootHandler_CreateStarcraft1(hs, pbRootFile, cbRootFile); if(dwErrCode == ERROR_BAD_FORMAT) { dwErrCode = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); } } break; } } // Free the root file CASC_FREE(pbRootFile); } else { dwErrCode = GetCascError(); } // Handle reparsing of the root file if(dwErrCode == ERROR_REPARSE_ROOT && pCKeyEntry != &hs->RootFile) { if(InvokeProgressCallback(hs, "Loading ROOT manifest (reparsed)", NULL, 0, 0)) return ERROR_CANCELLED; // Replace the root handler pOldRootHandler = hs->pRootHandler; hs->pRootHandler = NULL; // Replace the CKey entry for the ROOT file pCKeyEntry = &hs->RootFile; goto __LoadRootFile; } // If we reparsed the ROOT file and we have the old one, we need to copy all items to the new one if(hs->pRootHandler && pOldRootHandler) { hs->pRootHandler->Copy(pOldRootHandler); delete pOldRootHandler; } return dwErrCode; } static DWORD GetStorageTotalFileCount(TCascStorage * hs) { PCASC_CKEY_ENTRY pCKeyEntry; size_t nItemCount = hs->CKeyArray.ItemCount(); DWORD TotalFileCount = 0; for(size_t i = 0; i < nItemCount; i++) { if((pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(i)) != NULL) { if(pCKeyEntry->IsFile()) { // If there is zero or one file name reference, we count the item as one file. // If there is more than 1 name reference, we count the file as many times as number of references DWORD RefCount = (pCKeyEntry->RefCount > 0) ? pCKeyEntry->RefCount : 1; // Add the number of references to the total file count TotalFileCount += RefCount; } } } return TotalFileCount; } static bool GetStorageProduct(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded) { PCASC_STORAGE_PRODUCT pProductInfo; // Verify whether we have enough space in the buffer pProductInfo = (PCASC_STORAGE_PRODUCT)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(CASC_STORAGE_PRODUCT), pcbLengthNeeded); if(pProductInfo != NULL) { // Clear the entire structure memset(pProductInfo, 0, sizeof(CASC_STORAGE_PRODUCT)); // Copy the product code name and build number if(hs->szCodeName != NULL) CascStrCopy(pProductInfo->szCodeName, _countof(pProductInfo->szCodeName), hs->szCodeName); pProductInfo->BuildNumber = hs->dwBuildNumber; } return (pProductInfo != NULL); } static bool GetStorageTags(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded) { PCASC_STORAGE_TAGS pTags; PCASC_TAG_ENTRY2 pTag; char * szNameBuffer; size_t cbMinLength; // Does the storage support tags? if(hs->TagsArray.IsInitialized() == false) { SetCascError(ERROR_NOT_SUPPORTED); return false; } // Calculate the length of the tags cbMinLength = FIELD_OFFSET(CASC_STORAGE_TAGS, Tags) + hs->TagsArray.ItemCount() * sizeof(CASC_STORAGE_TAG); szNameBuffer = (char *)pvStorageInfo + cbMinLength; // Also include the tag length for(size_t i = 0; i < hs->TagsArray.ItemCount(); i++) { pTag = (PCASC_TAG_ENTRY2)hs->TagsArray.ItemAt(i); cbMinLength = cbMinLength + pTag->NameLength + 1; } // Verify whether we have enough space in the buffer pTags = (PCASC_STORAGE_TAGS)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, cbMinLength, pcbLengthNeeded); if(pTags != NULL) { // Fill the output structure pTags->TagCount = hs->TagsArray.ItemCount(); pTags->Reserved = 0; // Copy the tags for(size_t i = 0; i < hs->TagsArray.ItemCount(); i++) { // Get the source tag pTag = (PCASC_TAG_ENTRY2)hs->TagsArray.ItemAt(i); // Fill the target tag pTags->Tags[i].szTagName = szNameBuffer; pTags->Tags[i].TagNameLength = (DWORD)pTag->NameLength; pTags->Tags[i].TagValue = pTag->TagValue; // Copy the tag name memcpy(szNameBuffer, pTag->szTagName, pTag->NameLength); szNameBuffer[pTag->NameLength] = 0; szNameBuffer = szNameBuffer + pTag->NameLength + 1; } } return (pTags != NULL); } static bool GetStoragePathProduct(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded) { LPTSTR szBuffer = (LPTSTR)pvStorageInfo; size_t nMaxChars = cbStorageInfo / sizeof(TCHAR); size_t nLength; // Calculate the length needed nLength = _tcslen(hs->szRootPath); if(hs->szCodeName != NULL) nLength = nLength + 1 + _tcslen(hs->szCodeName); if(hs->szRegion != NULL) nLength = nLength + 1 + strlen(hs->szRegion); nLength++; // Verify whether we have enough space in the buffer szBuffer = (LPTSTR)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, (nLength * sizeof(TCHAR)), pcbLengthNeeded); if(szBuffer != NULL) { LPTSTR szBufferEnd = szBuffer + nMaxChars; // Copy the storage path CascStrCopy(szBuffer, (szBufferEnd - szBuffer), hs->szRootPath); szBuffer += _tcslen(hs->szRootPath); // Append the product code name, if any if(hs->szCodeName != NULL) { *szBuffer++ = _T(':'); CascStrCopy(szBuffer, (szBufferEnd - szBuffer), hs->szCodeName); szBuffer += _tcslen(hs->szCodeName); } // Append the product region, if any if(hs->szRegion != NULL) { *szBuffer++ = _T(':'); CascStrCopy(szBuffer, (szBufferEnd - szBuffer), hs->szRegion); } } return (szBuffer != NULL); } static DWORD InitializeLocalDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) { LPTSTR szWorkPath; DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; // Find the root directory of the storage. The root directory // is the one with ".build.info" or ".build.db". szWorkPath = CascNewStr(pArgs->szLocalPath); if(szWorkPath != NULL) { // Get the length and go up until we find the ".build.info" or ".build.db" for(;;) { // Is this a game directory? dwErrCode = CheckGameDirectory(hs, szWorkPath); if(dwErrCode == ERROR_SUCCESS) { dwErrCode = ERROR_SUCCESS; break; } // Cut one path part if(!CutLastPathPart(szWorkPath)) { dwErrCode = ERROR_FILE_NOT_FOUND; break; } } // Find the index directory if(dwErrCode == ERROR_SUCCESS) { // First, check for more common "data" subdirectory if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL) dwErrCode = ERROR_SUCCESS; // Second, try the "darch" subdirectory (older builds of HOTS - Alpha) else if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL) dwErrCode = ERROR_SUCCESS; else dwErrCode = ERROR_FILE_NOT_FOUND; } // Free the work path buffer CASC_FREE(szWorkPath); } return dwErrCode; } static DWORD InitializeOnlineDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) { // Create the root path hs->szRootPath = CascNewStr(pArgs->szLocalPath); if(hs->szRootPath != NULL) { hs->BuildFileType = CascVersionsDb; hs->dwFeatures |= CASC_FEATURE_ONLINE; hs->dwFeatures |= (pArgs->dwFlags & (CASC_FEATURE_LOCAL_CDNS | CASC_FEATURE_LOCAL_VERSIONS)); return ERROR_SUCCESS; } return ERROR_NOT_ENOUGH_MEMORY; } static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) { LPCTSTR szCdnHostUrl = NULL; LPCTSTR szCodeName = NULL; LPCTSTR szRegion = NULL; LPCTSTR szBuildKey = NULL; DWORD dwLocaleMask = 0; DWORD dwErrCode = ERROR_SUCCESS; // Pass the argument array to the storage hs->pArgs = pArgs; // Extract optional arguments ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, dwLocaleMask), &dwLocaleMask); // Extract the CDN host URL if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szCdnHostUrl), &szCdnHostUrl) && szCdnHostUrl != NULL) hs->szCdnHostUrl = CascNewStr(szCdnHostUrl); // Extract the product code name if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szCodeName), &szCodeName) && szCodeName != NULL) hs->szCodeName = CascNewStr(szCodeName); // Extract the region (optional) if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szRegion), &szRegion) && szRegion != NULL) hs->szRegion = CascNewStrT2A(szRegion); // Extract the build key (optional) if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szBuildKey), &szBuildKey) && szBuildKey != NULL) hs->szBuildKey = CascNewStrT2A(szBuildKey); // Special handling to online storages if(hs->dwFeatures & CASC_FEATURE_ONLINE) { // Enable caching of the sockets. This will add references // to all existing and all future sockets sockets_set_caching(true); // For online storages, we need to load CDN servers dwErrCode = LoadCdnsFile(hs); } // Now, load the main storage file ".build.info" (or ".build.db" in old storages) if(dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadBuildInfo(hs); } // Proceed with loading the CDN config file if(dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadCdnConfigFile(hs); if(dwErrCode != ERROR_SUCCESS && (hs->dwFeatures & CASC_FEATURE_ONLINE) == 0) dwErrCode = ERROR_SUCCESS; } // Proceed with loading the CDN build file if(dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadCdnBuildFile(hs); } // Create the array of CKey entries. Each entry represents a file in the storage if(dwErrCode == ERROR_SUCCESS) { dwErrCode = InitCKeyArray(hs); } // Pre-load the local index files if(dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadIndexFiles(hs); } // Load the ENCODING manifest if(dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadEncodingManifest(hs); } // We need to load the DOWNLOAD manifest if(dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadDownloadManifest(hs); } // Load the build manifest ("ROOT" file) if(dwErrCode == ERROR_SUCCESS) { // For WoW storages, multiple files are present in the storage (same name, same file data ID, different locale). // Failing to select storage on them will lead to the first-in-order file in the list being loaded. // Example: WoW build 32144, file: DBFilesClient\Achievement.db2, file data ID: 1260179 // Locales: koKR frFR deDE zhCN esES zhTW enUS&enGB esMX ruRU itIT ptBT&ptPT (in order of appearance in the build manifest) if(dwLocaleMask == 0) { dwLocaleMask = hs->dwDefaultLocale; } // Continue loading the manifest dwErrCode = LoadBuildManifest(hs, dwLocaleMask); if(dwErrCode != ERROR_SUCCESS) { // If we fail to load the ROOT file, we take the file names from the INSTALL manifest dwErrCode = LoadInstallManifest(hs); } } // Insert entries for files with well-known names. Their CKeys are in the BUILD file // See https://wowdev.wiki/TACT#Encoding_table for their list if(dwErrCode == ERROR_SUCCESS) { InsertWellKnownFile(hs, "ENCODING", hs->EncodingCKey); InsertWellKnownFile(hs, "DOWNLOAD", hs->DownloadCKey); InsertWellKnownFile(hs, "INSTALL", hs->InstallCKey); InsertWellKnownFile(hs, "PATCH", hs->PatchFile, CASC_CE_FILE_PATCH); InsertWellKnownFile(hs, "ROOT", hs->RootFile); InsertWellKnownFile(hs, "SIZE", hs->SizeFile); // Also reset the total file count. CascGetStorageInfo will update it on next call hs->TotalFiles = 0; } // Load the encryption keys if(dwErrCode == ERROR_SUCCESS) { dwErrCode = CascLoadEncryptionKeys(hs); } // Cleanup and exit FreeIndexFiles(hs); hs->pArgs = NULL; return dwErrCode; } // Check for URL pattern. Note that the string may be terminated by ':' instead of '\0' static bool IsUrl(LPCTSTR szString) { while(szString[0] != 0 && szString[0] != '*') { // Check for "://" if(!_tcsncmp(szString, _T("://"), 3)) return true; // Dot or slash both indicate an URL if(szString[0] == '.' || szString[0] == '/') return true; szString++; } return false; } static LPTSTR GetNextParam(LPTSTR szParamsPtr, bool bMustBeUrl = false) { LPTSTR szSeparator = NULL; // The 'szParamsPtr' must be valid if(szParamsPtr != NULL) { // Find the separator ("*") or end of string if((szSeparator = _tcschr(szParamsPtr, _T('*'))) != NULL) { // Check for URL pattern, if needed if(bMustBeUrl && IsUrl(szSeparator + 1) == false) return NULL; // Put the EOS there *szSeparator++ = 0; } } return szSeparator; } static DWORD ParseOpenParams(LPTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs) { LPTSTR szParamsPtr = szParams; LPTSTR szParamsTmp; // // Format of the params: // // Local: local_path*code_name ("C:\\Games\\World of Warcraft*wowt") // Online: local_cache_path[*cdn_url]*code_name*region" ("C:\\Cache*wowt*us) // // If the caller supplied the local_path/local_cache_path // both in szParams and pArgs, it's a conflict if(pArgs->szLocalPath && pArgs->szLocalPath[0]) return ERROR_INVALID_PARAMETER; pArgs->szLocalPath = szParams; // Extract the optional CDN path. If present, then we put it // into CASC_OPEN_STORAGE_ARGS::szCdnHostUrl if((szParamsTmp = GetNextParam(szParamsPtr, true)) != NULL) { if(pArgs->szCdnHostUrl && pArgs->szCdnHostUrl[0]) return ERROR_INVALID_PARAMETER; pArgs->szCdnHostUrl = szParamsTmp; szParamsPtr = szParamsTmp; } // The next must be the code name of the product if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL) { if(pArgs->szCodeName && pArgs->szCodeName[0]) return ERROR_INVALID_PARAMETER; pArgs->szCodeName = szParamsPtr; } // There could be region appended at the end if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL) { if(pArgs->szRegion && pArgs->szRegion[0]) return ERROR_INVALID_PARAMETER; pArgs->szRegion = szParamsPtr; } return ERROR_SUCCESS; } //----------------------------------------------------------------------------- // Public functions bool WINAPI CascOpenStorageEx(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs, bool bOnlineStorage, HANDLE * phStorage) { CASC_OPEN_STORAGE_ARGS LocalArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)}; TCascStorage * hs = NULL; LPTSTR szParamsCopy = NULL; DWORD dwErrCode = ERROR_SUCCESS; // Supply the local args if not given by the caller pArgs = (pArgs != NULL) ? pArgs : &LocalArgs; // // Parse the parameter string and put the parts into CASC_OPEN_STORAGE_ARGS // // Note that the parameter string is optional - is it possible // to enter all params purely in CASC_OPEN_STORAGE_ARGS structure. // if(szParams != NULL) { // Make a copy of the parameters so we can tamper with them if((szParamsCopy = CascNewStr(szParams)) == NULL) { SetCascError(ERROR_NOT_ENOUGH_MEMORY); return false; } // Parse the parameter string and put the corresponding parts // into the CASC_OPEN_STORAGE_ARGS structure. dwErrCode = ParseOpenParams(szParamsCopy, pArgs); } // Verify the minimum arguments if(dwErrCode == ERROR_SUCCESS) { if(pArgs->szLocalPath == NULL || pArgs->szLocalPath[0] == 0) { dwErrCode = ERROR_INVALID_PARAMETER; } } // Allocate the storage structure if(dwErrCode == ERROR_SUCCESS) { if((hs = new TCascStorage()) != NULL) { // Setup the directories dwErrCode = (bOnlineStorage) ? InitializeOnlineDirectories(hs, pArgs) : InitializeLocalDirectories(hs, pArgs); if(dwErrCode == ERROR_SUCCESS) { // Perform the entire storage loading dwErrCode = LoadCascStorage(hs, pArgs); } } } // Handle errors if(dwErrCode != ERROR_SUCCESS) { SetCascError(dwErrCode); hs = hs->Release(); } // Free the copy of the parameters CASC_FREE(szParamsCopy); // Give the output parameter to the caller *phStorage = (HANDLE)hs; return (dwErrCode == ERROR_SUCCESS); } // // Opens a local CASC storage // // szParams: "local_path:code_name", like "C:\\Games\\World of Warcraft:wowt" // // local_path Local folder, where the online file will be cached. // // code_name: Product code name, e.g. "agent" for Battle.net Agent. // More info: https://wowdev.wiki/TACT#Products // bool WINAPI CascOpenStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage) { CASC_OPEN_STORAGE_ARGS OpenArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)}; OpenArgs.dwLocaleMask = dwLocaleMask; return CascOpenStorageEx(szParams, &OpenArgs, false, phStorage); } // // Opens an online CDN storage // // szParams: "local_cache_path[:cdn_url]:code_name:region", e.g. "C:\\Cache:wowt:us" // // local_cache_path Local folder, where the online file will be cached. // cdn_url URL of the custom CDN server. Can also contain port. // This parameter is optional. Example: http://eu.custom-wow-cdn.com:8000 // code_name Product code name, e.g. "agent" for Battle.net Agent. // More info: https://wowdev.wiki/TACT#Products // region The region (or subvariant) of the product. // Corresponds to the first column of the "versions" file. // bool WINAPI CascOpenOnlineStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage) { CASC_OPEN_STORAGE_ARGS OpenArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)}; OpenArgs.dwLocaleMask = dwLocaleMask; return CascOpenStorageEx(szParams, &OpenArgs, true, phStorage); } bool WINAPI CascGetStorageInfo( HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded) { TCascStorage * hs; PDWORD PtrOutputValue; DWORD dwInfoValue = 0; // Verify the storage handle hs = TCascStorage::IsValid(hStorage); if(hs == NULL) { SetCascError(ERROR_INVALID_HANDLE); return false; } // Differentiate between info classes switch(InfoClass) { case CascStorageLocalFileCount: dwInfoValue = (DWORD)hs->LocalFiles; break; case CascStorageTotalFileCount: if(hs->TotalFiles == 0) hs->TotalFiles = GetStorageTotalFileCount(hs); dwInfoValue = (DWORD)hs->TotalFiles; break; case CascStorageFeatures: dwInfoValue = hs->dwFeatures | hs->pRootHandler->GetFeatures(); break; case CascStorageInstalledLocales: dwInfoValue = hs->dwDefaultLocale; break; case CascStorageProduct: return GetStorageProduct(hs, pvStorageInfo, cbStorageInfo, pcbLengthNeeded); case CascStorageTags: return GetStorageTags(hs, pvStorageInfo, cbStorageInfo, pcbLengthNeeded); case CascStoragePathProduct: return GetStoragePathProduct(hs, pvStorageInfo, cbStorageInfo, pcbLengthNeeded); default: SetCascError(ERROR_INVALID_PARAMETER); return false; } // // Default: return a 32-bit unsigned value // PtrOutputValue = (PDWORD)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(DWORD), pcbLengthNeeded); if(PtrOutputValue != NULL) PtrOutputValue[0] = dwInfoValue; return (PtrOutputValue != NULL); } bool WINAPI CascCloseStorage(HANDLE hStorage) { TCascStorage * hs; // Verify the storage handle hs = TCascStorage::IsValid(hStorage); if(hs == NULL) { SetCascError(ERROR_INVALID_PARAMETER); return false; } // Only free the storage if the reference count reaches 0 hs->Release(); return true; }