#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
#define CASC_MAX_EXTRA_ITEMS 0x40
#define CHECKED_KEY {0x00, 0x00, 0x0F, 0x84}
#if defined(CASCLIB_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) { }
#endif
TCascStorage::TCascStorage()
{
ClassName = CASC_MAGIC_STORAGE;
pRootHandler = NULL;
dwRefCount = 1;
szRootPath = szDataPath = szIndexPath = szFilesPath = szConfigPath = szMainFile = NULL;
szCdnHostUrl = szCdnServers = szCdnPath = 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()
{
if(pRootHandler != NULL)
delete pRootHandler;
pRootHandler = NULL;
for(size_t i = 0; i < CASC_MAX_DATA_FILES; i++)
{
FileStream_Close(DataFiles[i]);
DataFiles[i] = NULL;
}
FreeIndexFiles(this);
CascFreeLock(StorageLock);
CASC_FREE(szRootPath);
CASC_FREE(szDataPath);
CASC_FREE(szIndexPath);
CASC_FREE(szFilesPath);
CASC_FREE(szConfigPath);
CASC_FREE(szMainFile);
CASC_FREE(szCdnHostUrl);
CASC_FREE(szCdnServers);
CASC_FREE(szCdnPath);
CASC_FREE(szCodeName);
CASC_FREE(szRegion);
CASC_FREE(szBuildKey);
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)
{
if(dwFeatures & CASC_FEATURE_ONLINE)
sockets_set_caching(false);
delete this;
return NULL;
}
return this;
}
void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, size_t * pcbLengthNeeded)
{
if(cbLength < cbMinLength)
{
SetCascError(ERROR_INSUFFICIENT_BUFFER);
pvBuffer = NULL;
}
if(pcbLengthNeeded != NULL)
pcbLengthNeeded[0] = cbMinLength;
return pvBuffer;
}
static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_CKEY_ENTRY & CKeyEntry)
{
PCASC_CKEY_ENTRY pCKeyEntry = NULL;
BREAK_ON_WATCHED(CKeyEntry.EKey);
if(CKeyEntry.Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY))
{
if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKeyEntry.CKey)) == NULL)
{
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false);
if(pCKeyEntry == NULL)
return NULL;
memcpy(pCKeyEntry, &CKeyEntry, sizeof(CASC_CKEY_ENTRY));
if(CKeyEntry.Flags & CASC_CE_HAS_CKEY)
hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
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;
}
static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, PFILE_CKEY_ENTRY pFileEntry)
{
PCASC_CKEY_ENTRY pCKeyEntry;
BREAK_ON_WATCHED(pFileEntry->EKey);
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false);
if(pCKeyEntry != 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;
CopyEKeyEntry(hs, pCKeyEntry);
hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
}
else
{
assert(false);
}
return pCKeyEntry;
}
static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_DOWNLOAD_ENTRY & DlEntry)
{
PCASC_CKEY_ENTRY pCKeyEntry;
BREAK_ON_WATCHED(DlEntry.EKey);
if((pCKeyEntry = FindCKeyEntry_EKey(hs, DlEntry.EKey)) == NULL)
{
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false);
if(pCKeyEntry == NULL)
{
assert(false);
return NULL;
}
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;
CopyEKeyEntry(hs, pCKeyEntry);
hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
}
else
{
if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY_PARTIAL)
CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey);
if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE)
pCKeyEntry->EncodedSize = (DWORD)DlEntry.EncodedSize;
pCKeyEntry->Flags = (pCKeyEntry->Flags & ~CASC_CE_HAS_EKEY_PARTIAL) | CASC_CE_IN_DOWNLOAD;
}
pCKeyEntry->Priority = DlEntry.Priority;
return pCKeyEntry;
}
static DWORD CopyBuildFileItemsToCKeyArray(TCascStorage * hs)
{
InsertCKeyEntry(hs, hs->DownloadCKey);
InsertCKeyEntry(hs, hs->InstallCKey);
InsertCKeyEntry(hs, hs->PatchFile);
InsertCKeyEntry(hs, hs->RootFile);
InsertCKeyEntry(hs, hs->SizeFile);
InsertCKeyEntry(hs, hs->VfsRoot);
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;
}
static size_t GetEstimatedNumberOfFiles(TCascStorage * hs)
{
size_t nNumberOfFiles1 = 0;
size_t nNumberOfFiles2 = 0;
if(hs->DownloadCKey.ContentSize != CASC_INVALID_SIZE)
nNumberOfFiles1 = (hs->DownloadCKey.ContentSize / sizeof(FILE_DOWNLOAD_ENTRY)) + CASC_MAX_EXTRA_ITEMS;
if(hs->EncodingCKey.ContentSize != CASC_INVALID_SIZE)
nNumberOfFiles2 = (hs->EncodingCKey.ContentSize / sizeof(FILE_CKEY_ENTRY)) + CASC_MAX_EXTRA_ITEMS;
if(nNumberOfFiles1 || nNumberOfFiles2)
return CASCLIB_MAX(nNumberOfFiles1, nNumberOfFiles2);
return 1000000;
}
static DWORD InitCKeyArray(TCascStorage * hs)
{
size_t nNumberOfFiles = GetEstimatedNumberOfFiles(hs);
DWORD dwErrCode;
dwErrCode = hs->CKeyArray.Create(sizeof(CASC_CKEY_ENTRY), nNumberOfFiles);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
dwErrCode = hs->CKeyMap.Create(nNumberOfFiles, MD5_HASH_SIZE, FIELD_OFFSET(CASC_CKEY_ENTRY, CKey));
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
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;
if(cbFileData < sizeof(FILE_ENCODING_HEADER) || pFileHeader->Magic != FILE_MAGIC_ENCODING || pFileHeader->Version != 0x01)
return ERROR_BAD_FORMAT;
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 DWORD LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage)
{
PFILE_CKEY_ENTRY pFileEntry;
LPBYTE pbFileEntry = pbPageBegin;
assert(hs->CKeyMap.IsInitialized());
assert(hs->EKeyMap.IsInitialized());
while(pbFileEntry < pbEndOfPage)
{
pFileEntry = (PFILE_CKEY_ENTRY)pbFileEntry;
if(pFileEntry->EKeyCount == 0)
break;
InsertCKeyEntry(hs, pFileEntry);
pbFileEntry = pbFileEntry + 2 + 4 + EnHeader.CKeyLength + (pFileEntry->EKeyCount * EnHeader.EKeyLength);
}
return ERROR_SUCCESS;
}
static DWORD LoadEncodingManifest(TCascStorage * hs)
{
CASC_CKEY_ENTRY & CKeyEntry = hs->EncodingCKey;
CASC_BLOB EncodingFile;
DWORD dwErrCode = ERROR_SUCCESS;
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "ENCODING", 0, 0))
return ERROR_CANCELLED;
if(!CopyEKeyEntry(hs, &CKeyEntry))
return ERROR_FILE_NOT_FOUND;
InsertCKeyEntry(hs, CKeyEntry);
dwErrCode = LoadInternalFileToMemory(hs, &hs->EncodingCKey, EncodingFile);
if(dwErrCode == ERROR_SUCCESS && EncodingFile.cbData != 0)
{
CASC_ENCODING_HEADER EnHeader;
dwErrCode = CaptureEncodingHeader(EnHeader, EncodingFile.pbData, EncodingFile.cbData);
if(dwErrCode == ERROR_SUCCESS)
{
PFILE_CKEY_PAGE pPageHeader = (PFILE_CKEY_PAGE)(EncodingFile.pbData + sizeof(FILE_ENCODING_HEADER) + EnHeader.ESpecBlockSize);
LPBYTE pbEncodingEnd = EncodingFile.pbData + EncodingFile.cbData;
LPBYTE pbCKeyPage = (LPBYTE)(pPageHeader + EnHeader.CKeyPageCount);
for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++)
{
if((pbCKeyPage + EnHeader.CKeyPageSize) > pbEncodingEnd)
{
dwErrCode = ERROR_FILE_CORRUPT;
break;
}
if(memcmp(((PFILE_CKEY_ENTRY)pbCKeyPage)->CKey, pPageHeader[i].FirstKey, MD5_HASH_SIZE))
{
dwErrCode = ERROR_FILE_CORRUPT;
break;
}
dwErrCode = LoadEncodingCKeyPage(hs, EnHeader, pbCKeyPage, pbCKeyPage + EnHeader.CKeyPageSize);
if(dwErrCode != ERROR_SUCCESS)
break;
pbCKeyPage += EnHeader.CKeyPageSize;
}
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = CopyBuildFileItemsToCKeyArray(hs);
}
}
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;
if(cbFileData < sizeof(FILE_DOWNLOAD_HEADER) || pFileHeader->Magic != FILE_MAGIC_DOWNLOAD || pFileHeader->Version > 3)
return ERROR_BAD_FORMAT;
if(pFileHeader->EKeyLength > MD5_HASH_SIZE)
return ERROR_BAD_FORMAT;
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);
if(pFileHeader->Version >= 2)
{
DlHeader.FlagByteSize = pFileHeader->FlagByteSize;
DlHeader.HeaderLength = FIELD_OFFSET(FILE_DOWNLOAD_HEADER, BasePriority);
DlHeader.EntryLength += DlHeader.FlagByteSize;
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)
{
if((pbFilePtr + DlHeader.EntryLength) >= pbFileEnd)
return ERROR_BAD_FORMAT;
memset(&DlEntry, 0, sizeof(CASC_DOWNLOAD_ENTRY));
memcpy(DlEntry.EKey, pbFilePtr, DlHeader.EKeyLength);
pbFilePtr += DlHeader.EKeyLength;
DlEntry.EncodedSize = ConvertBytesToInteger_5(pbFilePtr);
pbFilePtr += 5;
DlEntry.Priority = pbFilePtr[0];
pbFilePtr++;
if(DlHeader.EntryHasChecksum)
{
DlEntry.Checksum = ConvertBytesToInteger_4(pbFilePtr);
pbFilePtr += 4;
}
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;
memset(&DlTag, 0, sizeof(CASC_TAG_ENTRY1));
DlTag.szTagName = (const char *)pbFilePtr;
while(pbFilePtr < pbFileEnd && pbFilePtr[0] != 0)
pbFilePtr++;
if(pbFilePtr >= pbFileEnd)
return ERROR_BAD_FORMAT;
DlTag.NameLength = (pbFilePtr - pbSaveFilePtr);
pbFilePtr++;
if((pbFilePtr + sizeof(DWORD)) > pbFileEnd)
return ERROR_BAD_FORMAT;
DlTag.TagValue = ConvertBytesToInteger_2(pbFilePtr);
pbFilePtr += 2;
DlTag.Bitmap = pbFilePtr;
DlTag.BitmapLength = GetTagBitmapLength(pbFilePtr, pbFileEnd, DlHeader.EntryCount);
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;
if(DlHeader.TagCount != 0)
{
hs->dwFeatures |= CASC_FEATURE_TAGS;
TagArray = CASC_ALLOC<CASC_TAG_ENTRY1>(DlHeader.TagCount);
if(TagArray != NULL)
{
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;
}
nTagEntryLengh = FIELD_OFFSET(CASC_TAG_ENTRY2, szTagName) + nMaxNameLength;
nTagEntryLengh = ALIGN_TO_SIZE(nTagEntryLengh, 8);
dwErrCode = hs->TagsArray.Create(nTagEntryLengh, DlHeader.TagCount);
if(dwErrCode == ERROR_SUCCESS)
{
for(DWORD i = 0; i < DlHeader.TagCount; i++)
{
PCASC_TAG_ENTRY1 pSourceTag = &TagArray[i];
PCASC_TAG_ENTRY2 pTargetTag;
pTargetTag = (PCASC_TAG_ENTRY2)hs->TagsArray.Insert(1);
if(pTargetTag == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
break;
}
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;
}
}
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);
if(CaptureDownloadEntry(DlHeader, DlEntry, pbEntry, pbFileEnd) != ERROR_SUCCESS)
break;
if((pCKeyEntry = InsertCKeyEntry(hs, DlEntry)) != NULL)
{
if(TagArray != NULL)
{
for(size_t j = 0; j < TagItemCount; j++)
{
if((BitMaskOffset < TagArray[j].BitmapLength) && (TagArray[j].Bitmap[BitMaskOffset] & BitMaskBit))
pCKeyEntry->TagBitMask |= TagBit;
TagBit <<= 1;
}
}
}
pbEntry += DlHeader.EntryLength;
}
CASC_FREE(TagArray);
hs->TotalFiles = hs->CKeyArray.ItemCount();
return dwErrCode;
}
static int LoadDownloadManifest(TCascStorage * hs)
{
PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->DownloadCKey.CKey);
CASC_BLOB DownloadFile;
DWORD dwErrCode = ERROR_SUCCESS;
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "DOWNLOAD", 0, 0))
return ERROR_CANCELLED;
dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, DownloadFile);
if(dwErrCode == ERROR_SUCCESS && DownloadFile.cbData != 0)
{
CASC_DOWNLOAD_HEADER DlHeader;
dwErrCode = CaptureDownloadHeader(DlHeader, DownloadFile.pbData, DownloadFile.cbData);
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadDownloadManifest(hs, DlHeader, DownloadFile.pbData, DownloadFile.pbData + DownloadFile.cbData);
}
}
return dwErrCode;
}
static int LoadInstallManifest(TCascStorage * hs)
{
PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->InstallCKey.CKey);
CASC_BLOB InstallFile;
DWORD dwErrCode = ERROR_SUCCESS;
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "INSTALL", 0, 0))
return ERROR_CANCELLED;
dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, InstallFile);
if(dwErrCode == ERROR_SUCCESS && InstallFile.cbData != 0)
{
dwErrCode = RootHandler_CreateInstall(hs, InstallFile);
}
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;
if(FakeCKeyEntry.Flags & CASC_CE_HAS_CKEY)
{
pCKeyEntry = FindCKeyEntry_CKey(hs, FakeCKeyEntry.CKey);
if(pCKeyEntry != NULL)
{
hs->pRootHandler->Insert(szFileName, pCKeyEntry);
pCKeyEntry->Flags |= (dwFlags | CASC_CE_IN_BUILD);
return true;
}
}
if((dwFlags & CASC_CE_FILE_PATCH) && (hs->dwFeatures & CASC_FEATURE_ONLINE))
{
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;
CASC_BLOB RootFile;
PDWORD FileSignature;
DWORD dwErrCode = ERROR_BAD_FORMAT;
assert(hs->CKeyMap.IsInitialized() == true);
assert(hs->pRootHandler == NULL);
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "ROOT", 0, 0))
return ERROR_CANCELLED;
dwLocaleMask = (dwLocaleMask != 0) ? dwLocaleMask : 0xFFFFFFFF;
if(hs->VfsRoot.ContentSize != CASC_INVALID_SIZE)
pCKeyEntry = &hs->VfsRoot;
__LoadRootFile:
pCKeyEntry = FindCKeyEntry_CKey(hs, pCKeyEntry->CKey);
dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, RootFile);
if(dwErrCode == ERROR_SUCCESS)
{
if(RootFile.cbData > MD5_STRING_SIZE)
{
FileSignature = (PDWORD)(RootFile.pbData);
switch(FileSignature[0])
{
case CASC_MNDX_ROOT_SIGNATURE:
dwErrCode = RootHandler_CreateMNDX(hs, RootFile);
break;
case CASC_DIABLO3_ROOT_SIGNATURE:
dwErrCode = RootHandler_CreateDiablo3(hs, RootFile);
break;
case CASC_TVFS_ROOT_SIGNATURE:
dwErrCode = RootHandler_CreateTVFS(hs, RootFile);
break;
case CASC_WOW_ROOT_SIGNATURE:
dwErrCode = RootHandler_CreateWoW(hs, RootFile, dwLocaleMask);
break;
default:
dwErrCode = RootHandler_CreateOverwatch(hs, RootFile);
if(dwErrCode == ERROR_BAD_FORMAT)
{
dwErrCode = RootHandler_CreateStarcraft1(hs, RootFile);
if(dwErrCode == ERROR_BAD_FORMAT)
{
dwErrCode = RootHandler_CreateWoW(hs, RootFile, dwLocaleMask);
}
}
break;
}
}
else
{
dwErrCode = ERROR_BAD_FORMAT;
}
}
else
{
dwErrCode = GetCascError();
}
if(dwErrCode == ERROR_REPARSE_ROOT && pCKeyEntry != &hs->RootFile)
{
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "ROOT (reparsed)", 0, 0))
return ERROR_CANCELLED;
pOldRootHandler = hs->pRootHandler;
hs->pRootHandler = NULL;
pCKeyEntry = &hs->RootFile;
goto __LoadRootFile;
}
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())
{
DWORD RefCount = (pCKeyEntry->RefCount) ? pCKeyEntry->RefCount : 1;
TotalFileCount += RefCount;
}
}
}
return TotalFileCount;
}
static bool GetStorageProduct(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded)
{
PCASC_STORAGE_PRODUCT pProductInfo;
pProductInfo = (PCASC_STORAGE_PRODUCT)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(CASC_STORAGE_PRODUCT), pcbLengthNeeded);
if(pProductInfo != NULL)
{
memset(pProductInfo, 0, sizeof(CASC_STORAGE_PRODUCT));
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;
if(hs->TagsArray.IsInitialized() == false)
{
SetCascError(ERROR_NOT_SUPPORTED);
return false;
}
cbMinLength = FIELD_OFFSET(CASC_STORAGE_TAGS, Tags) + hs->TagsArray.ItemCount() * sizeof(CASC_STORAGE_TAG);
szNameBuffer = (char *)pvStorageInfo + cbMinLength;
for(size_t i = 0; i < hs->TagsArray.ItemCount(); i++)
{
pTag = (PCASC_TAG_ENTRY2)hs->TagsArray.ItemAt(i);
cbMinLength = cbMinLength + pTag->NameLength + 1;
}
pTags = (PCASC_STORAGE_TAGS)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, cbMinLength, pcbLengthNeeded);
if(pTags != NULL)
{
pTags->TagCount = hs->TagsArray.ItemCount();
pTags->Reserved = 0;
for(size_t i = 0; i < hs->TagsArray.ItemCount(); i++)
{
pTag = (PCASC_TAG_ENTRY2)hs->TagsArray.ItemAt(i);
pTags->Tags[i].szTagName = szNameBuffer;
pTags->Tags[i].TagNameLength = (DWORD)pTag->NameLength;
pTags->Tags[i].TagValue = pTag->TagValue;
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;
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++;
szBuffer = (LPTSTR)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, (nLength * sizeof(TCHAR)), pcbLengthNeeded);
if(szBuffer != NULL)
{
LPTSTR szBufferEnd = szBuffer + nMaxChars;
CascStrCopy(szBuffer, (szBufferEnd - szBuffer), hs->szRootPath);
szBuffer += _tcslen(hs->szRootPath);
if(hs->szCodeName != NULL)
{
*szBuffer++ = _T(CASC_PARAM_SEPARATOR);
CascStrCopy(szBuffer, (szBufferEnd - szBuffer), hs->szCodeName);
szBuffer += _tcslen(hs->szCodeName);
}
if(hs->szRegion != NULL)
{
*szBuffer++ = _T(CASC_PARAM_SEPARATOR);
CascStrCopy(szBuffer, (szBufferEnd - szBuffer), hs->szRegion);
}
}
return (szBuffer != NULL);
}
static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs, LPCTSTR szMainFile, CBLD_TYPE BuildFileType, DWORD dwFeatures)
{
LPCTSTR szCdnHostUrl = NULL;
LPCTSTR szCodeName = NULL;
LPCTSTR szRegion = NULL;
LPCTSTR szBuildKey = NULL;
DWORD dwLocaleMask = 0;
DWORD dwErrCode = ERROR_SUCCESS;
hs->pArgs = pArgs;
ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, dwLocaleMask), &dwLocaleMask);
if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szCdnHostUrl), &szCdnHostUrl) && szCdnHostUrl != NULL)
hs->szCdnHostUrl = CascNewStr(szCdnHostUrl);
if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szCodeName), &szCodeName) && szCodeName != NULL)
hs->szCodeName = CascNewStr(szCodeName);
if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szRegion), &szRegion) && szRegion != NULL)
hs->szRegion = CascNewStrT2A(szRegion);
if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szBuildKey), &szBuildKey) && szBuildKey != NULL)
hs->szBuildKey = CascNewStrT2A(szBuildKey);
hs->dwFeatures |= (dwFeatures & (CASC_FEATURE_DATA_ARCHIVES | CASC_FEATURE_DATA_FILES | CASC_FEATURE_ONLINE));
hs->dwFeatures |= (pArgs->dwFlags & CASC_FEATURE_FORCE_DOWNLOAD);
hs->dwFeatures |= (BuildFileType == CascVersions) ? CASC_FEATURE_ONLINE : 0;
hs->BuildFileType = BuildFileType;
hs->szMainFile = CascNewStr(szMainFile);
CASC_PATH<TCHAR> RootPath(szMainFile, NULL);
hs->szRootPath = RootPath.New(true);
if(hs->szRootPath == NULL || hs->szMainFile == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
}
if(dwErrCode == ERROR_SUCCESS)
{
if(hs->dwFeatures & CASC_FEATURE_DATA_ARCHIVES)
{
if(CheckArchiveFilesDirectories(hs) != ERROR_SUCCESS)
hs->dwFeatures &= ~CASC_FEATURE_DATA_ARCHIVES;
}
if(hs->dwFeatures & CASC_FEATURE_DATA_FILES)
{
if(CheckDataFilesDirectory(hs) != ERROR_SUCCESS)
hs->dwFeatures &= ~CASC_FEATURE_DATA_FILES;
}
if(hs->dwFeatures & CASC_FEATURE_ONLINE)
sockets_set_caching(true);
dwErrCode = LoadMainFile(hs);
}
if(dwErrCode == ERROR_SUCCESS && hs->CdnConfigKey.Valid())
{
dwErrCode = LoadCdnConfigFile(hs);
if(dwErrCode != ERROR_SUCCESS && (hs->dwFeatures & CASC_FEATURE_ONLINE) == 0)
dwErrCode = ERROR_SUCCESS;
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadCdnBuildFile(hs);
}
if(hs->dwBuildNumber == 0)
{
hs->dwBuildNumber = 21742 + hs->InstallCKey.ContentSize;
}
if(hs->szCodeName == NULL && hs->dwBuildNumber == 22267)
{
hs->SetProductCodeName("wow", 3);
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = InitCKeyArray(hs);
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadIndexFiles(hs);
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadEncodingManifest(hs);
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadDownloadManifest(hs);
}
if(dwErrCode == ERROR_SUCCESS)
{
dwLocaleMask = (dwLocaleMask != 0) ? dwLocaleMask : hs->dwDefaultLocale;
dwErrCode = LoadBuildManifest(hs, dwLocaleMask);
if(dwErrCode != ERROR_SUCCESS && dwErrCode != ERROR_NOT_ENOUGH_MEMORY)
{
dwErrCode = LoadInstallManifest(hs);
}
}
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);
hs->TotalFiles = 0;
}
if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = CascLoadEncryptionKeys(hs);
}
FreeIndexFiles(hs);
hs->pArgs = NULL;
return dwErrCode;
}
static bool IsUrl(LPCTSTR szString)
{
while(szString[0] != 0 && szString[0] != CASC_PARAM_SEPARATOR)
{
if(!_tcsncmp(szString, _T("://"), 3))
return true;
if(szString[0] == '.' || szString[0] == '/')
return true;
szString++;
}
return false;
}
static LPTSTR GetNextParam(LPTSTR szParamsPtr, bool bMustBeUrl = false)
{
LPTSTR szSeparator = NULL;
if(szParamsPtr != NULL)
{
if((szSeparator = _tcschr(szParamsPtr, _T(CASC_PARAM_SEPARATOR))) != NULL)
{
if(bMustBeUrl && IsUrl(szSeparator + 1) == false)
return NULL;
*szSeparator++ = 0;
}
}
return szSeparator;
}
static DWORD ParseOpenParams(LPTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs)
{
LPTSTR szParamsPtr = szParams;
LPTSTR szParamsTmp;
if(pArgs->szLocalPath && pArgs->szLocalPath[0])
return ERROR_INVALID_PARAMETER;
pArgs->szLocalPath = szParams;
if((szParamsTmp = GetNextParam(szParamsPtr, true)) != NULL)
{
if(pArgs->szCdnHostUrl && pArgs->szCdnHostUrl[0])
return ERROR_INVALID_PARAMETER;
pArgs->szCdnHostUrl = szParamsTmp;
szParamsPtr = szParamsTmp;
}
if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL)
{
if(pArgs->szCodeName && pArgs->szCodeName[0])
return ERROR_INVALID_PARAMETER;
pArgs->szCodeName = szParamsPtr;
}
if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL)
{
if(pArgs->szRegion && pArgs->szRegion[0])
return ERROR_INVALID_PARAMETER;
pArgs->szRegion = szParamsPtr;
}
if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL)
{
if(pArgs->szBuildKey && pArgs->szBuildKey[0])
return ERROR_INVALID_PARAMETER;
pArgs->szBuildKey = szParamsPtr;
}
return ERROR_SUCCESS;
}
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;
pArgs = (pArgs != NULL) ? pArgs : &LocalArgs;
if(szParams != NULL)
{
if((szParamsCopy = CascNewStr(szParams)) == NULL)
{
SetCascError(ERROR_NOT_ENOUGH_MEMORY);
return false;
}
dwErrCode = ParseOpenParams(szParamsCopy, pArgs);
}
if(dwErrCode == ERROR_SUCCESS)
{
if(pArgs->szLocalPath == NULL || pArgs->szLocalPath[0] == 0)
{
dwErrCode = ERROR_INVALID_PARAMETER;
}
}
if(dwErrCode == ERROR_SUCCESS)
{
if((hs = new TCascStorage()) != NULL)
{
CASC_BUILD_FILE BuildFile = {NULL};
if((dwErrCode = CheckCascBuildFileExact(BuildFile, pArgs->szLocalPath)) == ERROR_SUCCESS)
{
dwErrCode = LoadCascStorage(hs, pArgs, BuildFile.szFullPath, BuildFile.BuildFileType, CASC_FEATURE_DATA_ARCHIVES | CASC_FEATURE_DATA_FILES);
}
else if((dwErrCode = CheckCascBuildFileDirs(BuildFile, pArgs->szLocalPath)) == ERROR_SUCCESS)
{
dwErrCode = LoadCascStorage(hs, pArgs, BuildFile.szFullPath, BuildFile.BuildFileType, CASC_FEATURE_DATA_ARCHIVES | CASC_FEATURE_DATA_FILES);
}
else if((dwErrCode = CheckOnlineStorage(pArgs, BuildFile, bOnlineStorage)) == ERROR_SUCCESS)
{
dwErrCode = LoadCascStorage(hs, pArgs, BuildFile.szFullPath, BuildFile.BuildFileType, CASC_FEATURE_DATA_FILES);
}
}
}
if(dwErrCode != ERROR_SUCCESS)
hs = hs->Release();
CASC_FREE(szParamsCopy);
if(phStorage != NULL)
phStorage[0] = (HANDLE)hs;
if(dwErrCode != ERROR_SUCCESS)
SetCascError(dwErrCode);
return (dwErrCode == ERROR_SUCCESS);
}
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);
}
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;
hs = TCascStorage::IsValid(hStorage);
if(hs == NULL)
{
SetCascError(ERROR_INVALID_HANDLE);
return false;
}
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;
}
PtrOutputValue = (PDWORD)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(DWORD), pcbLengthNeeded);
if(PtrOutputValue != NULL)
PtrOutputValue[0] = dwInfoValue;
return (PtrOutputValue != NULL);
}
bool WINAPI CascCloseStorage(HANDLE hStorage)
{
TCascStorage * hs;
hs = TCascStorage::IsValid(hStorage);
if(hs == NULL)
{
SetCascError(ERROR_INVALID_PARAMETER);
return false;
}
hs->Release();
return true;
}