Files
TrinityCore/dep/CascLib/src/CascOpenStorage.cpp

1415 lines
48 KiB
C++

/*****************************************************************************/
/* 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 The first version of CascOpenStorage.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local defines
// Limit for "additional" items in CKey table
#define CASC_MAX_EXTRA_ITEMS 0x40
//-----------------------------------------------------------------------------
// TCascStorage class functions
TCascStorage::TCascStorage()
{
// Prepare the base storage parameters
ClassName = CASC_MAGIC_STORAGE;
pRootHandler = NULL;
dwRefCount = 1;
szRootPath = szDataPath = szIndexPath = szBuildFile = szCdnServers = szCdnPath = szCodeName = NULL;
szIndexFormat = NULL;
szRegion = NULL;
memset(DataFiles, 0, sizeof(DataFiles));
dwBuildNumber = 0;
dwFeatures = 0;
BuildFileType = CascBuildNone;
LocalFiles = TotalFiles = EKeyEntries = EKeyLength = FileOffsetBits = 0;
}
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;
}
// 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(szRegion);
// Free the blobs
FreeCascBlob(&CdnConfigKey);
FreeCascBlob(&CdnBuildKey);
FreeCascBlob(&ArchiveGroup);
FreeCascBlob(&ArchivesKey);
FreeCascBlob(&PatchArchivesKey);
FreeCascBlob(&PatchArchivesGroup);
FreeCascBlob(&BuildFiles);
ClassName = 0;
}
TCascStorage * TCascStorage::AddRef()
{
CascInterlockedIncrement(&dwRefCount);
return this;
}
TCascStorage * TCascStorage::Release()
{
if(CascInterlockedDecrement(&dwRefCount) == 0)
{
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)
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
pvBuffer = NULL;
}
// Give the output length and return result
if(pcbLengthNeeded != NULL)
pcbLengthNeeded[0] = cbMinLength;
return pvBuffer;
}
static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
{
TCHAR * szIndexPath;
// Combine the index path
szIndexPath = CombinePath(hs->szDataPath, szSubDir);
if (!DirectoryExists(szIndexPath))
{
CASC_FREE(szIndexPath);
}
return 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;
// 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;
// Check whether the entry is already there
if((pCKeyEntry = FindCKeyEntry_EKey(hs, pFileEntry->EKey)) == 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;
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;
// Insert the item into both maps
hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
}
else
{
// Supply both CKey and EKey. Rewrite EKey regardless, because ENCODING manifest contains a full one
CopyMemory16(pCKeyEntry->CKey, pFileEntry->CKey);
CopyMemory16(pCKeyEntry->EKey, pFileEntry->EKey);
// Supply the content size
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
pCKeyEntry->ContentSize = ConvertBytesToInteger_4(pFileEntry->ContentSize);
pCKeyEntry->Flags |= CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING;
pCKeyEntry->Flags &= ~CASC_CE_HAS_EKEY_PARTIAL;
// Insert the item into CKey map
hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
}
return pCKeyEntry;
}
// Inserts an entry from DOWNLOAD
static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_DOWNLOAD_ENTRY & DlEntry)
{
PCASC_CKEY_ENTRY pCKeyEntry;
// 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)
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;
// 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 the array
static size_t GetEstimatedNumberOfFiles(TCascStorage * hs)
{
// 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 26 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)
return (hs->DownloadCKey.ContentSize / 26) + 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)
return (hs->EncodingCKey.ContentSize / 26) + CASC_MAX_EXTRA_ITEMS;
// By default, it's gonna be half a million, which is the maximum observed number of files
// for all older storages (HOTS before 39445, WoW before 19116)
return 500000;
}
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;
// Insert the entry of ENCODING file. This is vital for its opening and loading
InsertCKeyEntry(hs, hs->EncodingCKey);
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->EKey, 0x09, 0xF3, 0xCD);
// 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)
{
PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->EncodingCKey.CKey);
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;
// Load the entire encoding file to memory
pbEncodingFile = LoadInternalFileToMemory(hs, pCKeyEntry, &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);
}
// Now supply all the entries from the index files
//if(dwErrCode == ERROR_SUCCESS)
//{
// dwErrCode = CopyIndexItemsToCKeyArray(hs);
//}
// Free the loaded ENCODING file
CASC_FREE(pbEncodingFile);
}
else
{
dwErrCode = GetLastError();
}
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<CASC_TAG_ENTRY1>(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 = GetLastError();
}
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);
pCKeyEntry->Flags |= (CASC_CE_IN_BUILD | dwFlags);
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 |= (CASC_CE_IN_BUILD | dwFlags);
return true;
}
}
return false;
}
static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask)
{
PCASC_CKEY_ENTRY pCKeyEntry;
PDWORD FileSignature;
LPBYTE pbRootFile = NULL;
DWORD cbRootFile = 0;
DWORD dwErrCode = ERROR_BAD_FORMAT;
// Sanity checks
assert(hs->CKeyMap.IsInitialized() == true);
assert(hs->pRootHandler == NULL);
// 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
pCKeyEntry = (hs->VfsRoot.ContentSize != CASC_INVALID_SIZE) ? &hs->VfsRoot : &hs->RootFile;
pCKeyEntry = FindCKeyEntry_CKey(hs, pCKeyEntry->CKey);
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading ROOT manifest", NULL, 0, 0))
return ERROR_CANCELLED;
// Load the entire ROOT file to memory
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 = GetLastError();
}
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)
{
SetLastError(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)
{
TCHAR * 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;
return ERROR_SUCCESS;
}
return ERROR_NOT_ENOUGH_MEMORY;
}
static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
{
LPCTSTR szCodeName = NULL;
LPCTSTR szRegion = NULL;
char szRegionA[0x40];
DWORD dwLocaleMask = 0;
DWORD dwErrCode = ERROR_SUCCESS;
// Pass the argument array to the storage
hs->pArgs = pArgs;
// Extract optional arguments
ExtractVersionedArgument(pArgs, offsetof(CASC_OPEN_STORAGE_ARGS, dwLocaleMask), &dwLocaleMask);
// Extract the product code name
if(ExtractVersionedArgument(pArgs, offsetof(CASC_OPEN_STORAGE_ARGS, szCodeName), &szCodeName) && szCodeName != NULL)
hs->szCodeName = CascNewStr(szCodeName);
// Extract the region (optional)
if(ExtractVersionedArgument(pArgs, offsetof(CASC_OPEN_STORAGE_ARGS, szRegion), &szRegion) && szRegion != NULL)
{
CascStrCopy(szRegionA, _countof(szRegionA), szRegion);
hs->szRegion = CascNewStr(szRegionA);
}
// For online storages, we need to load CDN servers
if ((dwErrCode == ERROR_SUCCESS) && (hs->dwFeatures & CASC_FEATURE_ONLINE))
{
dwErrCode = LoadCdnsFile(hs);
}
// Now, load the main storage file ".build.info" (or ".build.db" in old storages)
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadBuildInfo(hs);
}
// If the .build.info OR .build.db file has been loaded,
// 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 central file array
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = InitCKeyArray(hs);
}
// Load the index files. Store information from the index files to the CKeyArray.
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. This will give us the information about
// how many physical files are in the storage, so we can start building file tables
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadDownloadManifest(hs);
}
// Load the build manifest ("ROOT" file)
if(dwErrCode == ERROR_SUCCESS)
{
// If we fail to load the ROOT file, we take the file names from the INSTALL manifest
dwErrCode = LoadBuildManifest(hs, dwLocaleMask);
if (dwErrCode != ERROR_SUCCESS)
{
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);
}
// Clear the arg structure
hs->pArgs = pArgs;
return dwErrCode;
}
static LPTSTR ParseOpenParams(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs)
{
LPTSTR szParamsCopy;
// The 'szParams' must not be empty
if(szParams == NULL || pArgs == NULL || szParams[0] == 0)
{
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// The 'pArgs' must be valid but must not contain 'szLocalPath', 'szCodeName' or 'szRegion'
if(pArgs->szLocalPath != NULL || pArgs->szCodeName != NULL || pArgs->szRegion != NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// Make a copy of the parameters so we can temper with them
if((szParamsCopy = CascNewStr(szParams)) != NULL)
{
LPTSTR szPlainName = (LPTSTR)GetPlainFileName(szParamsCopy);
LPTSTR szSeparator;
// The local path is always set
pArgs->szLocalPath = szParamsCopy;
pArgs->szCodeName = NULL;
pArgs->szRegion = NULL;
// Find the first ":". This will indicate the end of local path and also begin of product code
if((szSeparator = _tcschr(szPlainName, _T(':'))) != NULL)
{
// The found string is a product code name
pArgs->szCodeName = szSeparator + 1;
szSeparator[0] = 0;
// Try again. If found, it is a product region
if((szSeparator = _tcschr(szSeparator + 1, _T(':'))) != NULL)
{
pArgs->szRegion = szSeparator + 1;
szSeparator[0] = 0;
}
}
}
else
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
}
return szParamsCopy;
}
//-----------------------------------------------------------------------------
// 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;
LPTSTR szParamsCopy = NULL;
DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
// The storage path[+product[+region]] must either be passed in szParams or in pArgs. Not both.
// It is allowed to pass NULL as pArgs if the szParams is not NULL
if(szParams != NULL)
{
if(pArgs == NULL)
pArgs = &LocalArgs;
szParamsCopy = ParseOpenParams(szParams, pArgs);
if(szParamsCopy == NULL)
return false;
}
else
{
// The arguments and the local path must be entered
if(pArgs == NULL || pArgs->szLocalPath == NULL || pArgs->szLocalPath[0] == 0)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
}
// Allocate the storage structure
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);
}
// Free the storage structure on fail
if(dwErrCode != ERROR_SUCCESS)
{
hs = hs->Release();
}
}
// Give the output parameter to the caller
CASC_FREE(szParamsCopy);
*phStorage = (HANDLE)hs;
// Return the result
if(dwErrCode != ERROR_SUCCESS)
SetLastError(dwErrCode);
return (dwErrCode == ERROR_SUCCESS);
}
// szParams: "LocalPath:CodeName", e.g. "C:\\Games\\World of Warcraft:wowt"
// * LocalPath: Local folder, where the online file will be cached.
// * CodeName: 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);
}
// Allows to browse an online CDN storage
// szParams: "CachePath:CodeName:Region", e.g. "C:\\Cache:wowt:us"
// * CachePath: Local folder, where the online file will be cached.
// * CodeName: 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)
{
SetLastError(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:
SetLastError(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)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Only free the storage if the reference count reaches 0
hs->Release();
return true;
}