aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/CascOpenStorage.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2019-06-06 16:48:21 +0200
committerShauren <shauren.trinity@gmail.com>2019-06-08 17:09:24 +0200
commitfc330fd8ff0115804d9c4b53a1f810c00dd63de9 (patch)
treecfa10998fed66779834bf0b7a9b8b799d33d91d4 /dep/CascLib/src/CascOpenStorage.cpp
parent82c7b6c5688495d90c4ee5995a4ff74039348296 (diff)
Dep/CascLib: Update to ladislav-zezula/CascLib@a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012
Diffstat (limited to 'dep/CascLib/src/CascOpenStorage.cpp')
-rw-r--r--dep/CascLib/src/CascOpenStorage.cpp1825
1 files changed, 968 insertions, 857 deletions
diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp
index ec277f67c13..53031169cbb 100644
--- a/dep/CascLib/src/CascOpenStorage.cpp
+++ b/dep/CascLib/src/CascOpenStorage.cpp
@@ -15,1101 +15,1221 @@
#include "CascCommon.h"
//-----------------------------------------------------------------------------
-// Local structures
+// Local defines
-// Size of one segment in the ENCODING table
-// The segment is filled by entries of type
-#define CASC_ENCODING_SEGMENT_SIZE 0x1000
+// Limit for "orphaned" items - those that are in index files, but are not in ENCODING manifest
+#define CASC_MAX_ORPHANED_ITEMS 0x100
-typedef struct _BLOCK_SIZE_AND_HASH
-{
- DWORD cbBlockSize;
- DWORD dwBlockHash;
+//-----------------------------------------------------------------------------
+// Local variables
-} BLOCK_SIZE_AND_HASH, *PBLOCK_SIZE_AND_HASH;
+static PFNPROGRESSCALLBACK PfnProgressCallback = NULL;
+static void * PtrProgressParam = NULL;
-typedef struct _FILE_INDEX_HEADER_V1
-{
- USHORT field_0;
- BYTE KeyIndex; // Key index (0 for data.i0x, 1 for data.i1x, 2 for data.i2x etc.)
- BYTE align_3;
- DWORD field_4;
- ULONGLONG field_8;
- ULONGLONG MaxFileOffset;
- BYTE SpanSizeBytes;
- BYTE SpanOffsBytes;
- BYTE KeyBytes;
- BYTE SegmentBits; // Number of bits for file offset
- DWORD KeyCount1;
- DWORD KeyCount2;
- DWORD KeysHash1;
- DWORD KeysHash2;
- DWORD dwHeaderHash;
-} FILE_INDEX_HEADER_V1, *PFILE_INDEX_HEADER_V1;
-
-typedef struct _FILE_INDEX_HEADER_V2
-{
- USHORT IndexVersion; // Must be 0x07
- BYTE KeyIndex; // Must be equal to the file key index
- BYTE ExtraBytes; // (?) Extra bytes in the key record
- BYTE SpanSizeBytes; // Size of field with file size
- BYTE SpanOffsBytes; // Size of field with file offset
- BYTE KeyBytes; // Size of the file key (bytes)
- BYTE SegmentBits; // Number of bits for the file offset (rest is archive index)
- ULONGLONG MaxFileOffset;
-
-} FILE_INDEX_HEADER_V2, *PFILE_INDEX_HEADER_V2;
-
-typedef struct _FILE_ENCODING_SEGMENT
+//-----------------------------------------------------------------------------
+// TCascStorage service functions
+
+TCascStorage::TCascStorage()
{
- BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment
- BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
+ // Prepare the base storage parameters
+ szClassName = "TCascStorage";
+ pRootHandler = NULL;
+ dwDefaultLocale = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB;
+ dwRefCount = 1;
+
+ szRootPath = szDataPath = szIndexPath = szBuildFile = szCdnServers = szCdnPath = szCodeName = NULL;
+ szProductName = NULL;
+ szIndexFormat = NULL;
+ szRegion = NULL;
+
+ memset(DataFiles, 0, sizeof(DataFiles));
+ Product = UnknownProduct;
+ dwBuildNumber = 0;
+ dwFeatures = 0;
+ bAllowOrphans = false;
+ BuildFileType = CascBuildNone;
+
+ LocalFiles = TotalFiles = EKeyEntries = OrphanItems = SkippedItems = EKeyLength = FileOffsetBits = 0;
+
+ // Take the callback param and data. Zero the global pointers
+ PfnCallback = PfnProgressCallback;
+ PtrCallbackParam = PtrProgressParam;
+ PfnProgressCallback = NULL;
+ PtrProgressParam = NULL;
+}
-} FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT;
+TCascStorage::~TCascStorage()
+{
+ // Free the root handler
+ if(pRootHandler != NULL)
+ delete pRootHandler;
+ pRootHandler = NULL;
-//-----------------------------------------------------------------------------
-// Local variables
+ // Close all data files
+ for(size_t i = 0; i < CASC_MAX_DATA_FILES; i++)
+ {
+ FileStream_Close(DataFiles[i]);
+ DataFiles[i] = NULL;
+ }
-static const TCHAR * szAllowedHexChars = _T("0123456789aAbBcCdDeEfF");
-static const TCHAR * szIndexFormat_V1 = _T("data.i%x%x");
-static const TCHAR * szIndexFormat_V2 = _T("%02x%08x.idx");
+ // 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);
+ szClassName = NULL;
+}
-//-----------------------------------------------------------------------------
-// Local functions
+TCascStorage * TCascStorage::AddRef()
+{
+ dwRefCount++;
+ return this;
+}
-inline void CopyFileKey(LPBYTE Trg, LPBYTE Src)
+TCascStorage * TCascStorage::Release()
{
- Trg[0x00] = Src[0x00];
- Trg[0x01] = Src[0x01];
- Trg[0x02] = Src[0x02];
- Trg[0x03] = Src[0x03];
- Trg[0x04] = Src[0x04];
- Trg[0x05] = Src[0x05];
- Trg[0x06] = Src[0x06];
- Trg[0x07] = Src[0x07];
- Trg[0x08] = Src[0x08];
- Trg[0x09] = Src[0x09];
- Trg[0x0A] = Src[0x0A];
- Trg[0x0B] = Src[0x0B];
- Trg[0x0C] = Src[0x0C];
- Trg[0x0D] = Src[0x0D];
- Trg[0x0E] = Src[0x0E];
- Trg[0x0F] = Src[0x0F];
+ if (dwRefCount == 1)
+ {
+ delete this;
+ return NULL;
+ }
+
+ dwRefCount--;
+ return NULL;
}
-TCascStorage * IsValidStorageHandle(HANDLE hStorage)
+TCascStorage * TCascStorage::IsValid(HANDLE hStorage)
{
TCascStorage * hs = (TCascStorage *)hStorage;
return (hs != NULL && hs->szClassName != NULL && !strcmp(hs->szClassName, "TCascStorage")) ? hs : NULL;
}
-// "data.iXY"
-static bool IsIndexFileName_V1(const TCHAR * szFileName)
-{
- // Check if the name looks like a valid index file
- return (_tcslen(szFileName) == 8 &&
- _tcsnicmp(szFileName, _T("data.i"), 6) == 0 &&
- _tcsspn(szFileName + 6, szAllowedHexChars) == 2);
-}
+//-----------------------------------------------------------------------------
+// Local functions
-static bool IsIndexFileName_V2(const TCHAR * szFileName)
+void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, size_t * pcbLengthNeeded)
{
- // Check if the name looks like a valid index file
- return (_tcslen(szFileName) == 14 &&
- _tcsspn(szFileName, _T("0123456789aAbBcCdDeEfF")) == 0x0A &&
- _tcsicmp(szFileName + 0x0A, _T(".idx")) == 0);
+ // 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 bool IsRootFile_Starcraft1(LPBYTE pbFileData, DWORD cbFileData)
+static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
{
- LPBYTE pbFileEnd = pbFileData + cbFileData;
- LPBYTE pbHashName;
- LPBYTE pbHashEnd;
-
- // Skip the file name
- while(pbFileData < pbFileEnd && pbFileData[0] != '|')
- pbFileData++;
- if(pbFileData[0] != '|')
- return false;
+ TCHAR * szIndexPath;
- // Then, a MD5 must follow
- pbHashName = pbHashEnd = pbFileData + 1;
- while(pbHashEnd < pbFileEnd && pbHashEnd[0] != 0x0A)
- pbHashEnd++;
- if(pbHashEnd[0] != 0x0A)
- return false;
+ // Combine the index path
+ szIndexPath = CombinePath(hs->szDataPath, szSubDir);
+ if (!DirectoryExists(szIndexPath))
+ {
+ CASC_FREE(szIndexPath);
+ }
- // The length must be exactly 32 characters
- return ((pbHashEnd - pbHashName) == 32);
+ return szIndexPath;
}
-static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData)
+static int CreateCKeyMaps(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader)
{
- PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData;
- DWORD dwHeaderHash;
- bool bResult = false;
+ size_t nEstimatedEntries = (EnHeader.CKeyPageCount * EnHeader.CKeyPageSize) / sizeof(FILE_CKEY_ENTRY);
+ size_t nIxEntries = hs->IndexArray.ItemCount();
+ int nError;
- // Check the size
- if(cbFileData >= sizeof(FILE_INDEX_HEADER_V1))
+ // Orphaned items: These are present in INDEX files (by EKey), but missing in the ENCODING manifest.
+ // Probably a bug in generator of "2018 - New CASC\00001", but we want to open the storage nontheless.
+ if(nEstimatedEntries < 0x100)
{
- // Save the header hash
- dwHeaderHash = pIndexHeader->dwHeaderHash;
- pIndexHeader->dwHeaderHash = 0;
+ nEstimatedEntries = nEstimatedEntries + nIxEntries;
+ hs->bAllowOrphans = true;
+ }
- // Calculate the hash
- if(hashlittle(pIndexHeader, sizeof(FILE_INDEX_HEADER_V1), 0) == dwHeaderHash)
- bResult = true;
+ // Allow some room for extra entries
+ nEstimatedEntries += CASC_MAX_ORPHANED_ITEMS;
- // Put the hash back
- pIndexHeader->dwHeaderHash = dwHeaderHash;
- }
+ // Create the array of CKey items
+ nError = hs->CKeyArray.Create(sizeof(CASC_CKEY_ENTRY), nEstimatedEntries);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Create the map CKey -> CASC_CKEY_ENTRY
+ nError = hs->CKeyMap.Create(nEstimatedEntries, EnHeader.CKeyLength, FIELD_OFFSET(CASC_CKEY_ENTRY, CKey));
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Create the map EKey -> CASC_CKEY_ENTRY
+ nError = hs->EKeyMap.Create(nEstimatedEntries, hs->EKeyLength, FIELD_OFFSET(CASC_CKEY_ENTRY, EKey));
+ if(nError != ERROR_SUCCESS)
+ return nError;
- return bResult;
+ return ERROR_SUCCESS;
}
-static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData)
+int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, size_t cbFileData)
{
- PBLOCK_SIZE_AND_HASH pSizeAndHash = (PBLOCK_SIZE_AND_HASH)pbFileData;
- unsigned int HashHigh = 0;
- unsigned int HashLow = 0;
+ PFILE_ENCODING_HEADER pFileHeader = (PFILE_ENCODING_HEADER)pbFileData;
- // Check for the header
- if(cbFileData < sizeof(BLOCK_SIZE_AND_HASH) || pSizeAndHash->cbBlockSize < 0x10)
- return false;
- if(cbFileData < pSizeAndHash->cbBlockSize + sizeof(BLOCK_SIZE_AND_HASH))
- return false;
+ // Check the signature ('EN') and version
+ if(cbFileData < sizeof(FILE_ENCODING_HEADER) || pFileHeader->Magic != FILE_MAGIC_ENCODING || pFileHeader->Version != 0x01)
+ return ERROR_BAD_FORMAT;
- // The index header for CASC v 2.0 begins with length and checksum
- hashlittle2(pSizeAndHash + 1, pSizeAndHash->cbBlockSize, &HashHigh, &HashLow);
- return (HashHigh == pSizeAndHash->dwBlockHash);
+ // 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 bool CutLastPathPart(TCHAR * szWorkPath)
+static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage)
{
- size_t nLength = _tcslen(szWorkPath);
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ 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);
- // Go one character back
- if(nLength > 0)
- nLength--;
+ // Insert the CKey entry into the array
+ pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1);
+ if(pCKeyEntry != NULL)
+ {
+ // 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);
+ pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64;
+ pCKeyEntry->TagBitMask = 0;
+ pCKeyEntry->EncodedSize = CASC_INVALID_SIZE;
+ pCKeyEntry->ContentSize = ConvertBytesToInteger_4(pFileEntry->ContentSize);
+ pCKeyEntry->RefCount = 0;
+ pCKeyEntry->Priority = 0;
+ pCKeyEntry->Flags = (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING);
+
+ // Insert the item into both maps
+ hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
+ hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
+ }
- // Cut ending (back)slashes, if any
- while(nLength > 0 && (szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/')))
- nLength--;
+ // Move to the next encoding entry
+ pbFileEntry = pbFileEntry + 2 + 4 + EnHeader.CKeyLength + (pFileEntry->EKeyCount * EnHeader.EKeyLength);
+ }
+ return ERROR_SUCCESS;
+}
+
+static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pSourceEntry, bool bAllowOrphans, bool * pbAllocatedNewEntry)
+{
+ PCASC_CKEY_ENTRY pCKeyEntry = NULL;
+ bool bAllocatedNewEntry = false;
- // Cut the last path part
- while(nLength > 0)
+ if(pSourceEntry->Flags & CASC_CE_HAS_EKEY)
{
- // End of path?
- if(szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/'))
+ // If there is that item already, reuse it
+ pCKeyEntry = FindCKeyEntry_EKey(hs, pSourceEntry->EKey);
+ if(pCKeyEntry == NULL)
{
- szWorkPath[nLength] = 0;
- return true;
+ // Increment number of orphaned index entries
+ hs->OrphanItems++;
+
+ // Insert the orphan item only of they are allowed and if we won't overflow the array
+ if(bAllowOrphans && (hs->CKeyArray.ItemCount() + 1) < hs->CKeyArray.ItemCountMax())
+ {
+ // Insert a new entry to the array
+ pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1);
+ if(pCKeyEntry != NULL)
+ {
+ // Copy CKey, EKey and some flags
+ if(pSourceEntry->Flags & CASC_CE_HAS_CKEY)
+ CopyMemory16(pCKeyEntry->CKey, pSourceEntry->CKey);
+
+ if(pSourceEntry->Flags & CASC_CE_HAS_EKEY)
+ CopyMemory16(pCKeyEntry->EKey, pSourceEntry->EKey);
+
+ pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64;
+ pCKeyEntry->TagBitMask = 0;
+ pCKeyEntry->RefCount = 0;
+ pCKeyEntry->Priority = 0;
+
+ pCKeyEntry->EncodedSize = CASC_INVALID_SIZE;
+ pCKeyEntry->ContentSize = CASC_INVALID_SIZE;
+ pCKeyEntry->Flags = (pSourceEntry->Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL));
+ bAllocatedNewEntry = true;
+ }
+ }
+ else
+ {
+ hs->SkippedItems++;
+ }
}
+ }
+
+ if(pbAllocatedNewEntry != NULL)
+ pbAllocatedNewEntry[0] = bAllocatedNewEntry;
+ return pCKeyEntry;
+}
+
+static PCASC_CKEY_ENTRY CopyBuildFileItemToCKeyArray(TCascStorage * hs, PCASC_CKEY_ENTRY pSourceEntry)
+{
+ PCASC_CKEY_ENTRY pCKeyEntry = NULL;
+ bool bAllocatedNewEntry = false;
- // Go one character back
- nLength--;
+ pCKeyEntry = InsertCKeyEntry(hs, pSourceEntry, true, &bAllocatedNewEntry);
+ if(pCKeyEntry != NULL)
+ {
+ // Fill the values that might be known
+ if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE)
+ pCKeyEntry->EncodedSize = pSourceEntry->EncodedSize;
+ if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
+ pCKeyEntry->ContentSize = pSourceEntry->ContentSize;
+
+ // If this is a new entry, we need to insert it to the maps
+ if(bAllocatedNewEntry)
+ {
+ if(pCKeyEntry->Flags & CASC_CE_HAS_CKEY)
+ hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
+ if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY)
+ hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
+ }
}
- return false;
+ return pCKeyEntry;
}
-static int InsertExtraFile(
- TCascStorage * hs,
- const char * szFileName,
- PQUERY_KEY pQueryKey)
+static int CopyBuildFileItemsToCKeyArray(TCascStorage * hs)
{
- // If the given key is not encoding key (aka, it's an index key),
- // we need to create a fake encoding entry
- if(pQueryKey->cbData == MD5_HASH_SIZE * 2)
+ // Insert the well-known files
+ CopyBuildFileItemToCKeyArray(hs, &hs->EncodingCKey);
+ CopyBuildFileItemToCKeyArray(hs, &hs->DownloadCKey);
+ CopyBuildFileItemToCKeyArray(hs, &hs->InstallCKey);
+ CopyBuildFileItemToCKeyArray(hs, &hs->PatchFile);
+ CopyBuildFileItemToCKeyArray(hs, &hs->RootFile);
+ CopyBuildFileItemToCKeyArray(hs, &hs->SizeFile);
+ CopyBuildFileItemToCKeyArray(hs, &hs->VfsRoot);
+
+ // Insert all VFS roots
+ for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++)
{
- PCASC_ENCODING_ENTRY pNewEntry;
- PCASC_INDEX_ENTRY pIndexEntry;
- QUERY_KEY IndexKey;
-
- // Find the entry in the index table in order to get the file size
- IndexKey.pbData = pQueryKey->pbData + MD5_HASH_SIZE;
- IndexKey.cbData = MD5_HASH_SIZE;
- pIndexEntry = FindIndexEntry(hs, &IndexKey);
- if(pIndexEntry == NULL)
- return ERROR_FILE_NOT_FOUND;
-
- // Create a fake entry in the encoding map
- pNewEntry = (PCASC_ENCODING_ENTRY)Array_Insert(&hs->ExtraEntries, NULL, 1);
- if(pNewEntry == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Fill the encoding entry
- pNewEntry->KeyCount = 1;
- pNewEntry->FileSizeBE[0] = pIndexEntry->FileSizeLE[3];
- pNewEntry->FileSizeBE[1] = pIndexEntry->FileSizeLE[2];
- pNewEntry->FileSizeBE[2] = pIndexEntry->FileSizeLE[1];
- pNewEntry->FileSizeBE[3] = pIndexEntry->FileSizeLE[0];
- memcpy(pNewEntry->EncodingKey, pQueryKey->pbData, MD5_HASH_SIZE);
- memcpy(pNewEntry + 1, pQueryKey->pbData + MD5_HASH_SIZE, MD5_HASH_SIZE);
-
- // Insert the entry to the map of encoding keys
- Map_InsertObject(hs->pEncodingMap, pNewEntry, pNewEntry->EncodingKey);
+ PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i);
+ CopyBuildFileItemToCKeyArray(hs, pCKeyEntry);
}
- // Now we need to insert the entry to the root handler in order
- // to be able to translate file name to encoding key
- return RootHandler_Insert(hs->pRootHandler, szFileName, pQueryKey->pbData);
+ return ERROR_SUCCESS;
}
-static int InitializeCascDirectories(TCascStorage * hs, const TCHAR * szDataPath)
+static int CopyIndexItemsToCKeyArray(TCascStorage * hs)
{
- TCHAR * szWorkPath;
- int nError = ERROR_NOT_ENOUGH_MEMORY;
+ PCASC_CKEY_ENTRY pIndexEntry;
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ size_t nItemCount = hs->IndexArray.ItemCount();
+ bool bAllocatedNewEntry = false;
- // Find the root directory of the storage. The root directory
- // is the one where ".build.info" is.
- szWorkPath = CascNewStr(szDataPath, 0);
- if(szWorkPath != NULL)
+ // Iterate over all index items
+ for(size_t i = 0; i < nItemCount; i++)
{
- // Get the length and go up until we find the ".build.info" or ".build.db"
- for(;;)
+ // Get the n-th index entry
+ pIndexEntry = (PCASC_CKEY_ENTRY)hs->IndexArray.ItemAt(i);
+
+ // Sometimes, there are multiple items with the same EKey in the index files
+ // Example: "2018 - New CASC\00001", EKey 37 89 16 5b 2d cc 71 c1 25 00 00 00 00 00 00 00
+ // Positions: 0x2D, 0x2E, 0x2F
+ //BREAK_ON_XKEY3(pIndexEntry->EKey, 0x37, 0x89, 0x16);
+
+ // Copy the index entry to the central storage
+ if((pCKeyEntry = InsertCKeyEntry(hs, pIndexEntry, hs->bAllowOrphans, &bAllocatedNewEntry)) != NULL)
{
- // Is this a game directory?
- nError = CheckGameDirectory(hs, szWorkPath);
- if(nError == ERROR_SUCCESS)
+ // Make sure that the CKey is zeroed when not present
+ if((pCKeyEntry->Flags & CASC_CE_HAS_CKEY) == 0)
+ ZeroMemory16(pCKeyEntry->CKey);
+
+ // Only copy the storage offset and sizes if not available yet
+ if(pCKeyEntry->StorageOffset == CASC_INVALID_OFFS64)
{
- nError = ERROR_SUCCESS;
- break;
+ pCKeyEntry->StorageOffset = pIndexEntry->StorageOffset;
+ pCKeyEntry->EncodedSize = pIndexEntry->EncodedSize;
}
- // Cut one path part
- if(!CutLastPathPart(szWorkPath))
+ if(bAllocatedNewEntry)
{
- nError = ERROR_FILE_NOT_FOUND;
- break;
+ if(pCKeyEntry->Flags & CASC_CE_HAS_CKEY)
+ hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey);
+ if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY)
+ hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
}
- }
- // Free the work path buffer
- CASC_FREE(szWorkPath);
+ // Mark the file as available locally
+ pCKeyEntry->Flags |= CASC_CE_FILE_IS_LOCAL;
+ }
}
- return nError;
+ // We free the index array at this point
+ hs->IndexArray.Free();
+ return ERROR_SUCCESS;
}
-static bool IndexDirectory_OnFileFound(
- const TCHAR * szFileName,
- PDWORD IndexArray,
- PDWORD OldIndexArray,
- void * pvContext)
+static int LoadEncodingManifest(TCascStorage * hs)
{
- TCascStorage * hs = (TCascStorage *)pvContext;
- DWORD IndexValue = 0;
- DWORD IndexVersion = 0;
+ LPBYTE pbEncodingFile;
+ DWORD cbEncodingFile = 0;
+ int nError = ERROR_SUCCESS;
- // Auto-detect the format of the index file name
- if(hs->szIndexFormat == NULL)
- {
- if(IsIndexFileName_V1(szFileName))
- hs->szIndexFormat = szIndexFormat_V1;
- else if(IsIndexFileName_V2(szFileName))
- hs->szIndexFormat = szIndexFormat_V2;
- else
- return false;
- }
+ // Inform the user about what we are doing
+ if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading ENCODING manifest", NULL, 0, 0))
+ return ERROR_CANCELLED;
- if(hs->szIndexFormat == szIndexFormat_V1)
+ // Load the entire encoding file to memory
+ pbEncodingFile = LoadInternalFileToMemory(hs, &hs->EncodingCKey, &cbEncodingFile);
+ if(pbEncodingFile != NULL && cbEncodingFile != 0)
{
- // Check the index file name format
- if(!IsIndexFileName_V1(szFileName))
- return false;
+ CASC_ENCODING_HEADER EnHeader;
- // Get the main index from the first two digits
- if(ConvertDigitToInt32(szFileName + 6, &IndexValue) != ERROR_SUCCESS)
- return false;
- if(ConvertDigitToInt32(szFileName + 7, &IndexVersion) != ERROR_SUCCESS)
- return false;
- }
-
- else if(hs->szIndexFormat == szIndexFormat_V2)
- {
- // Check the index file name format
- if(!IsIndexFileName_V2(szFileName))
- return false;
+ // Capture the header of the ENCODING file
+ nError = CaptureEncodingHeader(EnHeader, pbEncodingFile, cbEncodingFile);
+ if(nError == 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);
+
+ // Since ENCODING contains the full list of all files (even those not downloaded),
+ // we can now make a fair estimate about how large maps shall we create.
+ // So, we can build the maps CKey and EKey map.
+ if((nError = CreateCKeyMaps(hs, EnHeader)) == ERROR_SUCCESS)
+ {
+ // Go through all CKey pages and verify them
+ for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++)
+ {
+ PFILE_CKEY_ENTRY pCKeyEntry = (PFILE_CKEY_ENTRY)pbCKeyPage;
+
+ // Check if there is enough space in the buffer
+ if((pbCKeyPage + EnHeader.CKeyPageSize) > (pbEncodingFile + cbEncodingFile))
+ {
+ nError = 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))
+// {
+// nError = ERROR_FILE_CORRUPT;
+// break;
+// }
+
+ // Check if the CKey matches with the expected first value
+ if(memcmp(pCKeyEntry->CKey, pPageHeader[i].FirstKey, CASC_CKEY_SIZE))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Load the entire page of CKey entries.
+ // This operation will never fail, because all memory is already pre-allocated
+ nError = LoadEncodingCKeyPage(hs, EnHeader, pbCKeyPage, pbCKeyPage + EnHeader.CKeyPageSize);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Move to the next CKey page
+ pbCKeyPage += EnHeader.CKeyPageSize;
+ }
+ }
+ }
- // Get the main index from the first two digits
- if(ConvertStringToInt32(szFileName, 2, &IndexValue) != ERROR_SUCCESS)
- return false;
- if(ConvertStringToInt32(szFileName + 2, 8, &IndexVersion) != ERROR_SUCCESS)
- return false;
- }
- else
- {
- // Should never happen
- assert(false);
- return false;
- }
+ // All CKey->EKey entries from the text build files need to be copied to the CKey array
+ // This also includes the ENCODING file itself, which is vital for later loading
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = CopyBuildFileItemsToCKeyArray(hs);
+ }
- // The index value must not be greater than 0x0F
- if(IndexValue >= CASC_INDEX_COUNT)
- return false;
+ // Now supply all the entries from the index files
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = CopyIndexItemsToCKeyArray(hs);
+ }
- // If the new subindex is greater than the previous one,
- // use this one instead
- if(IndexVersion > IndexArray[IndexValue])
- {
- OldIndexArray[IndexValue] = IndexArray[IndexValue];
- IndexArray[IndexValue] = IndexVersion;
+ // Free the loaded ENCODING file
+ CASC_FREE(pbEncodingFile);
}
- else if(IndexVersion > OldIndexArray[IndexValue])
+ else
{
- OldIndexArray[IndexValue] = IndexVersion;
+ nError = GetLastError();
}
- // Note: WoW6 only keeps last two index files
- // Any additional index files are deleted at this point
- return true;
+ return nError;
}
-static TCHAR * CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion)
+size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount)
{
- TCHAR szPlainName[0x40];
-
- // Sanity checks
- assert(hs->szIndexFormat != NULL);
- assert(hs->szIndexPath != NULL);
- assert(IndexValue <= 0x0F);
-
- // Create the full path
- _stprintf(szPlainName, hs->szIndexFormat, IndexValue, IndexVersion);
- return CombinePath(hs->szIndexPath, szPlainName);
-}
+ size_t nBitmapLength;
-static int VerifyAndParseKeyMapping_V1(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
-{
- PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pKeyMapping->pbFileData;
- DWORD dwDataHash1;
- DWORD dwDataHash2;
-
- // Verify the format
- if(pIndexHeader->field_0 != 0x0005)
- return ERROR_NOT_SUPPORTED;
- if(pIndexHeader->KeyIndex != KeyIndex)
- return ERROR_NOT_SUPPORTED;
- if(pIndexHeader->field_8 == 0)
- return ERROR_NOT_SUPPORTED;
-
- // Verofy the bit counts
- if(pIndexHeader->SpanSizeBytes != 0x04 ||
- pIndexHeader->SpanOffsBytes != 0x05 ||
- pIndexHeader->KeyBytes != 0x09)
- return ERROR_NOT_SUPPORTED;
-
- pKeyMapping->ExtraBytes = 0;
- pKeyMapping->SpanSizeBytes = pIndexHeader->SpanSizeBytes;
- pKeyMapping->SpanOffsBytes = pIndexHeader->SpanOffsBytes;
- pKeyMapping->KeyBytes = pIndexHeader->KeyBytes;
- pKeyMapping->SegmentBits = pIndexHeader->SegmentBits;
- pKeyMapping->MaxFileOffset = pIndexHeader->MaxFileOffset;
-
- // Get the pointer to the key entry array
- pKeyMapping->nIndexEntries = pIndexHeader->KeyCount1 + pIndexHeader->KeyCount2;
- if(pKeyMapping->nIndexEntries != 0)
- pKeyMapping->pIndexEntries = (PCASC_INDEX_ENTRY)(pKeyMapping->pbFileData + sizeof(FILE_INDEX_HEADER_V1));
-
- // Verify hashes
- dwDataHash1 = hashlittle(pKeyMapping->pIndexEntries, pIndexHeader->KeyCount1 * sizeof(CASC_INDEX_ENTRY), 0);
- dwDataHash2 = hashlittle(pKeyMapping->pIndexEntries + pIndexHeader->KeyCount1, pIndexHeader->KeyCount2 * sizeof(CASC_INDEX_ENTRY), 0);
- if(dwDataHash1 != pIndexHeader->KeysHash1 || dwDataHash2 != pIndexHeader->KeysHash2)
- return ERROR_FILE_CORRUPT;
+ nBitmapLength = (EntryCount / 8) + ((EntryCount & 0x07) ? 1 : 0);
+ if ((pbFilePtr + nBitmapLength) > pbFileEnd)
+ nBitmapLength = (pbFileEnd - pbFilePtr);
- return ERROR_SUCCESS;
+ return nBitmapLength;
}
-static int VerifyAndParseKeyMapping_V2(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
+int CaptureDownloadHeader(CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, size_t cbFileData)
{
- PFILE_INDEX_HEADER_V2 pIndexHeader = (PFILE_INDEX_HEADER_V2)pKeyMapping->pbFileData;
- PBLOCK_SIZE_AND_HASH pSizeAndHash;
- LPBYTE pbLastPartEnd;
- LPBYTE pbLastPart;
- DWORD FilePosition;
- DWORD LastPartLength;
- unsigned int HashHigh = 0;
- unsigned int HashLow = 0;
-
- // The index header v2 begins after the SizeAndHash
- pSizeAndHash = (PBLOCK_SIZE_AND_HASH)pKeyMapping->pbFileData;
- pIndexHeader = (PFILE_INDEX_HEADER_V2)(pSizeAndHash + 1);
- if(pIndexHeader->IndexVersion != 0x07 || pIndexHeader->KeyIndex != KeyIndex)
- return ERROR_BAD_FORMAT;
+ PFILE_DOWNLOAD_HEADER pFileHeader = (PFILE_DOWNLOAD_HEADER)pbFileData;
- if(pIndexHeader->ExtraBytes != 0x00 ||
- pIndexHeader->SpanSizeBytes != 0x04 ||
- pIndexHeader->SpanOffsBytes != 0x05 ||
- pIndexHeader->KeyBytes != CASC_FILE_KEY_SIZE)
+ // Check the signature ('DL') and version
+ if(cbFileData < sizeof(FILE_DOWNLOAD_HEADER) || pFileHeader->Magic != FILE_MAGIC_DOWNLOAD || pFileHeader->Version > 3)
return ERROR_BAD_FORMAT;
- // Remember the sizes
- pKeyMapping->ExtraBytes = pIndexHeader->ExtraBytes;
- pKeyMapping->SpanSizeBytes = pIndexHeader->SpanSizeBytes;
- pKeyMapping->SpanOffsBytes = pIndexHeader->SpanOffsBytes;
- pKeyMapping->KeyBytes = pIndexHeader->KeyBytes;
- pKeyMapping->SegmentBits = pIndexHeader->SegmentBits;
- pKeyMapping->MaxFileOffset = pIndexHeader->MaxFileOffset;
-
- // Get the data position
- FilePosition = (sizeof(BLOCK_SIZE_AND_HASH) + pSizeAndHash->cbBlockSize + 0x0F) & 0xFFFFFFF0;
- if((FilePosition + 0x08) > pKeyMapping->cbFileData)
+ // 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;
- // Get the pointer to "size+hash" block
- pSizeAndHash = (PBLOCK_SIZE_AND_HASH)(pKeyMapping->pbFileData + FilePosition);
- FilePosition += 0x08;
+ // 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;
- if((FilePosition + pSizeAndHash->cbBlockSize) > pKeyMapping->cbFileData)
- return ERROR_BAD_FORMAT;
- if(pSizeAndHash->cbBlockSize < sizeof(CASC_INDEX_ENTRY))
- return ERROR_BAD_FORMAT;
+ // Capture header version 3
+ if (pFileHeader->Version >= 3)
+ {
+ DlHeader.BasePriority = pFileHeader->BasePriority;
+ DlHeader.HeaderLength = sizeof(FILE_DOWNLOAD_HEADER);
+ }
+ }
- // Remember the array of file keys
- pKeyMapping->pIndexEntries = (PCASC_INDEX_ENTRY)(pKeyMapping->pbFileData + FilePosition);
- pKeyMapping->nIndexEntries = pSizeAndHash->cbBlockSize / sizeof(CASC_INDEX_ENTRY);
- FilePosition += pSizeAndHash->cbBlockSize;
+ return ERROR_SUCCESS;
+}
- // Verify the integrity of the key array
- for(DWORD i = 0; i < pKeyMapping->nIndexEntries; i++)
- hashlittle2(pKeyMapping->pIndexEntries + i, sizeof(CASC_INDEX_ENTRY), &HashHigh, &HashLow);
- if(HashHigh != pSizeAndHash->dwBlockHash)
+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));
- // Align the data position up to next 0x1000
- FilePosition = ALIGN_TO_SIZE(FilePosition, 0x1000);
- if(FilePosition > pKeyMapping->cbFileData)
- return ERROR_BAD_FORMAT;
+ // Copy the EKey
+ memcpy(DlEntry.EKey, pbFilePtr, DlHeader.EKeyLength);
+ pbFilePtr += DlHeader.EKeyLength;
- LastPartLength = pKeyMapping->cbFileData - FilePosition;
- if(LastPartLength < 0x7800)
- return ERROR_BAD_FORMAT;
+ // Convert the file size
+ DlEntry.EncodedSize = ConvertBytesToInteger_5(pbFilePtr);
+ pbFilePtr += 5;
- pbLastPart = pKeyMapping->pbFileData + FilePosition;
- pbLastPartEnd = pbLastPart + ((LastPartLength >> 0x09) << 0x09);
+ // Copy the file priority
+ DlEntry.Priority = pbFilePtr[0];
+ pbFilePtr++;
- while(pbLastPart < pbLastPartEnd)
+ // Copy the checksum
+ if(DlHeader.EntryHasChecksum)
{
- for(int i = 0; i < 0x1F8; i += 0x18)
- {
- PDWORD PtrLastPart = (PDWORD)pbLastPart;
- if(PtrLastPart[0] == 0)
- return ERROR_SUCCESS;
-
- HashLow = hashlittle(PtrLastPart + i, 0x13, 0) | 0x80000000;
- if(HashLow != PtrLastPart[0])
- return ERROR_BAD_FORMAT;
- }
-
- pbLastPart += 0x200;
+ DlEntry.Checksum = ConvertBytesToInteger_4(pbFilePtr);
+ pbFilePtr += 4;
}
+ // Copy the flags
+ DlEntry.Flags = ConvertBytesToInteger_X(pbFilePtr, DlHeader.FlagByteSize);
return ERROR_SUCCESS;
}
-static int VerifyAndParseKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
+int CaptureDownloadTag(CASC_DOWNLOAD_HEADER & DlHeader, CASC_TAG_ENTRY1 & DlTag, LPBYTE pbFilePtr, LPBYTE pbFileEnd)
{
- // Sanity checks
- assert(pKeyMapping->pbFileData != NULL);
- assert(pKeyMapping->cbFileData != 0);
+ LPBYTE pbSaveFilePtr = pbFilePtr;
- // Check for CASC version 2
- if(IsCascIndexHeader_V2(pKeyMapping->pbFileData, pKeyMapping->cbFileData))
- return VerifyAndParseKeyMapping_V2(pKeyMapping, KeyIndex);
+ // Prepare the tag structure
+ memset(&DlTag, 0, sizeof(CASC_TAG_ENTRY1));
+ DlTag.szTagName = (const char *)pbFilePtr;
- // Check for CASC version 1
- if(IsCascIndexHeader_V1(pKeyMapping->pbFileData, pKeyMapping->cbFileData))
- return VerifyAndParseKeyMapping_V1(pKeyMapping, KeyIndex);
+ // 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++;
- // Unknown CASC version
- assert(false);
- return ERROR_BAD_FORMAT;
+ // 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 LoadKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
+static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, LPBYTE pbFileEnd)
{
- TFileStream * pStream;
- ULONGLONG FileSize = 0;
+ 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;
int nError = ERROR_SUCCESS;
- // Sanity checks
- assert(pKeyMapping->szFileName != NULL && pKeyMapping->szFileName[0] != 0);
-
- // Open the stream for read-only access and read the file
- pStream = FileStream_OpenFile(pKeyMapping->szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
- if(pStream != NULL)
+ // Does the storage support tags?
+ if(DlHeader.TagCount != 0)
{
- // Retrieve the file size
- FileStream_GetSize(pStream, &FileSize);
- if(0 < FileSize && FileSize <= 0x200000)
+ // 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)
{
- // WoW6 actually reads THE ENTIRE file to memory
- // Verified on Mac build (x64)
- pKeyMapping->pbFileData = CASC_ALLOC(BYTE, (DWORD)FileSize);
- pKeyMapping->cbFileData = (DWORD)FileSize;
+ // 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 data to memory and parse it
- if(pKeyMapping->pbFileData != NULL)
+ // Load the tags into array in the storage structure
+ nError = hs->TagsArray.Create(nTagEntryLengh, DlHeader.TagCount);
+ if(nError == ERROR_SUCCESS)
{
- if(FileStream_Read(pStream, NULL, pKeyMapping->pbFileData, pKeyMapping->cbFileData))
+ // Convert the array of CASC_DOWNLOAD_TAG1 to array of CASC_DOWNLOAD_TAG2
+ for(DWORD i = 0; i < DlHeader.TagCount; i++)
{
- nError = VerifyAndParseKeyMapping(pKeyMapping, KeyIndex);
+ 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)
+ {
+ nError = 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
- nError = ERROR_NOT_ENOUGH_MEMORY;
}
else
{
- assert(false);
- nError = ERROR_BAD_FORMAT;
+ nError = ERROR_NOT_ENOUGH_MEMORY;
}
-
- // Close the file stream
- FileStream_Close(pStream);
}
- else
- nError = GetLastError();
-
- return ERROR_SUCCESS;
-}
-static int CreateArrayOfIndexEntries(TCascStorage * hs)
-{
- PCASC_MAP pMap;
- DWORD TotalCount = 0;
- int nError = 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;
+ size_t BitMaskOffset = (i / 8);
+ BYTE BitMaskBit = 0x80 >> (i % 8);
- // Count the total number of files in the storage
- for(size_t i = 0; i < CASC_INDEX_COUNT; i++)
- TotalCount += hs->KeyMapping[i].nIndexEntries;
+ // Capture the download entry
+ if(CaptureDownloadEntry(DlHeader, DlEntry, pbEntry, pbFileEnd) != ERROR_SUCCESS)
+ break;
- // Create the map of all index entries
- pMap = Map_Create(TotalCount, CASC_FILE_KEY_SIZE, FIELD_OFFSET(CASC_INDEX_ENTRY, IndexKey));
- if(pMap != NULL)
- {
- // Put all index entries in the map
- for(size_t i = 0; i < CASC_INDEX_COUNT; i++)
+ // Make sure we have the entry in CKey table
+ pCKeyEntry = FindCKeyEntry_EKey(hs, DlEntry.EKey);
+ if(pCKeyEntry != NULL)
{
- PCASC_INDEX_ENTRY pIndexEntry = hs->KeyMapping[i].pIndexEntries;
- DWORD nIndexEntries = hs->KeyMapping[i].nIndexEntries;
+ ULONGLONG TagBit = 1;
+ size_t TagItemCount = hs->TagsArray.ItemCount();
- for(DWORD j = 0; j < nIndexEntries; j++)
+ // Supply the tag bits
+ for(size_t j = 0; j < TagItemCount; j++)
{
- // Insert the index entry to the map
- // Note that duplicate entries will not be inserted to the map
- //
- // Duplicate entries in WoW-WOD build 18179:
- // 9e dc a7 8f e2 09 ad d8 b7 (encoding file)
- // f3 5e bb fb d1 2b 3f ef 8b
- // c8 69 9f 18 a2 5e df 7e 52
- Map_InsertObject(pMap, pIndexEntry, pIndexEntry->IndexKey);
-
- // Move to the next entry
- pIndexEntry++;
+ // 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;
}
+
+ // If the EKey has partial EKey only, fix that
+ if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY_PARTIAL)
+ {
+ CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey);
+ pCKeyEntry->Flags &= ~CASC_CE_HAS_EKEY_PARTIAL;
+ }
+
+ // Supply the priority
+ pCKeyEntry->Priority = DlEntry.Priority;
+ pCKeyEntry->Flags |= CASC_CE_IN_DOWNLOAD;
}
- // Store the map to the storage handle
- hs->pIndexEntryMap = pMap;
- nError = ERROR_SUCCESS;
+ // 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 nError;
}
-static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumSegments)
+static int LoadDownloadManifest(TCascStorage * hs)
{
- PCASC_ENCODING_ENTRY pEncodingEntry;
- DWORD dwMaxEntries;
+ PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->DownloadCKey.CKey);
+ LPBYTE pbDownloadFile = NULL;
+ DWORD cbDownloadFile = 0;
int nError = ERROR_SUCCESS;
- // Sanity check
- assert(hs->pIndexEntryMap != NULL);
- assert(hs->pEncodingMap == NULL);
-
- // Calculate the largest eventual number of encoding entries
- // Add space for extra entries
- dwMaxEntries = (dwNumSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE);
+ // Inform the user about what we are doing
+ if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading DOWNLOAD manifest", NULL, 0, 0))
+ return ERROR_CANCELLED;
- // Create the map of the encoding entries
- hs->pEncodingMap = Map_Create(dwMaxEntries + CASC_EXTRA_FILES, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ENCODING_ENTRY, EncodingKey));
- if(hs->pEncodingMap != NULL)
+ // Load the entire DOWNLOAD file to memory
+ pbDownloadFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbDownloadFile);
+ if(pbDownloadFile != NULL && cbDownloadFile != 0)
{
- LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments);
+ CASC_DOWNLOAD_HEADER DlHeader;
- // Parse all segments
- for(DWORD i = 0; i < dwNumSegments; i++)
+ // Capture the header of the DOWNLOAD file
+ nError = CaptureDownloadHeader(DlHeader, pbDownloadFile, cbDownloadFile);
+ if(nError == ERROR_SUCCESS)
{
- LPBYTE pbEncodingEntry = pbStartOfSegment;
- LPBYTE pbEndOfSegment = pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE - sizeof(CASC_ENCODING_ENTRY) - MD5_HASH_SIZE;
-
- // Parse all encoding entries
- while(pbEncodingEntry <= pbEndOfSegment)
- {
- // Get pointer to the encoding entry
- pEncodingEntry = (PCASC_ENCODING_ENTRY)pbEncodingEntry;
- if(pEncodingEntry->KeyCount == 0)
- break;
-
- // Insert the pointer the array
- Map_InsertObject(hs->pEncodingMap, pEncodingEntry, pEncodingEntry->EncodingKey);
-
- // Move to the next encoding entry
- pbEncodingEntry += sizeof(CASC_ENCODING_ENTRY) + (pEncodingEntry->KeyCount * MD5_HASH_SIZE);
- }
-
- // Move to the next segment
- pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE;
+ // Parse the entire download manifest
+ nError = LoadDownloadManifest(hs, DlHeader, pbDownloadFile, pbDownloadFile + cbDownloadFile);
}
+
+ // Free the loaded manifest
+ CASC_FREE(pbDownloadFile);
}
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ // If the DOWNLOAD manifest is not present, we won't abort the downloading process.
return nError;
}
-static int LoadIndexFiles(TCascStorage * hs)
+//-----------------------------------------------------------------------------
+// INSTALL manifest. This is a replacement for ROOT, if loading ROOT fails
+// https://wowdev.wiki/TACT#Install_manifest
+
+static int LoadInstallManifest(TCascStorage * hs)
{
- DWORD IndexArray[CASC_INDEX_COUNT];
- DWORD OldIndexArray[CASC_INDEX_COUNT];
- int nError;
- int i;
+ PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->InstallCKey.CKey);
+ LPBYTE pbInstallFile = NULL;
+ DWORD cbInstallFile = 0;
+ int nError = ERROR_SUCCESS;
- // Scan all index files
- memset(IndexArray, 0, sizeof(IndexArray));
- memset(OldIndexArray, 0, sizeof(OldIndexArray));
- nError = ScanIndexDirectory(hs->szIndexPath, IndexDirectory_OnFileFound, IndexArray, OldIndexArray, hs);
- if(nError == ERROR_SUCCESS)
+ // Inform the user about what we are doing
+ if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "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)
{
- // Load each index file
- for(i = 0; i < CASC_INDEX_COUNT; i++)
- {
- hs->KeyMapping[i].szFileName = CreateIndexFileName(hs, i, IndexArray[i]);
- if(hs->KeyMapping[i].szFileName != NULL)
- {
- nError = LoadKeyMapping(&hs->KeyMapping[i], i);
- if(nError != ERROR_SUCCESS)
- break;
- }
- }
+ nError = RootHandler_CreateInstall(hs, pbInstallFile, cbInstallFile);
+ CASC_FREE(pbInstallFile);
}
-
- // Now we need to build the map of the index entries
- if(nError == ERROR_SUCCESS)
+ else
{
- nError = CreateArrayOfIndexEntries(hs);
+ nError = GetLastError();
}
return nError;
}
-static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
+static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC_CKEY_ENTRY & FakeCKeyEntry)
{
- CASC_ENCODING_HEADER EncodingHeader;
- LPBYTE pbEncodingFile = NULL;
- DWORD cbEncodingFile = 0;
- DWORD dwSegmentPos = 0;
- DWORD dwNumSegments = 0;
- DWORD dwBytesRead = 0;
- int nError = ERROR_BAD_FORMAT;
+ PCASC_CKEY_ENTRY pCKeyEntry = NULL;
- // Read the encoding header
- CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
- if(dwBytesRead == sizeof(CASC_ENCODING_HEADER))
+ // We need to find the CKey entry in the central array
+ if(FakeCKeyEntry.Flags & CASC_CE_HAS_CKEY)
{
- // Check the version and sizes
- if(EncodingHeader.Version != 0x01 || EncodingHeader.ChecksumSizeA != MD5_HASH_SIZE || EncodingHeader.ChecksumSizeB != MD5_HASH_SIZE)
+ // Did we find anything?
+ pCKeyEntry = FindCKeyEntry_CKey(hs, FakeCKeyEntry.CKey);
+ if(pCKeyEntry != NULL)
{
- assert(false);
- return NULL;
+ // Insert the key to the root handler, unless it's already referenced by a name
+ if(pCKeyEntry->RefCount == 0)
+ hs->pRootHandler->Insert(szFileName, pCKeyEntry);
+ return true;
}
-
- // Get the number of segments
- dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.Entries_TableA);
- dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.Size_StringTable1);
- if(EncodingHeader.Magic[0] == 'E' && EncodingHeader.Magic[1] == 'N' && dwSegmentPos != 0 && dwNumSegments != 0)
- nError = ERROR_SUCCESS;
- }
-
- // Calculate and allocate space for the entire file
- if(nError == ERROR_SUCCESS)
- {
- cbEncodingFile = sizeof(CASC_ENCODING_HEADER) +
- dwSegmentPos +
- dwNumSegments * (sizeof(FILE_ENCODING_SEGMENT) + CASC_ENCODING_SEGMENT_SIZE);
- pbEncodingFile = CASC_ALLOC(BYTE, cbEncodingFile);
- if(pbEncodingFile == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
}
- // If all went OK, we load the entire file to memory
- if(nError == ERROR_SUCCESS)
- {
- // Copy the header itself
- memcpy(pbEncodingFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER));
-
- // Read the rest of the data
- CascReadFile(hFile, pbEncodingFile + sizeof(CASC_ENCODING_HEADER), cbEncodingFile - sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
- if(dwBytesRead != (cbEncodingFile - sizeof(CASC_ENCODING_HEADER)))
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Give the loaded file length
- if(pcbEncodingFile != NULL)
- *pcbEncodingFile = cbEncodingFile;
- return pbEncodingFile;
+ return false;
}
-static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
+static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask)
{
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ PDWORD FileSignature;
LPBYTE pbRootFile = NULL;
DWORD cbRootFile = 0;
- DWORD dwBytesRead = 0;
- int nError = ERROR_SUCCESS;
+ int nError = ERROR_BAD_FORMAT;
- // Retrieve the size of the ROOT file
- cbRootFile = CascGetFileSize(hFile, NULL);
- if(cbRootFile == 0)
- nError = ERROR_BAD_FORMAT;
+ // Sanity checks
+ assert(hs->CKeyMap.IsInitialized() == true);
+ assert(hs->pRootHandler == NULL);
- // Allocate space for the entire file
- if(nError == ERROR_SUCCESS)
- {
- pbRootFile = CASC_ALLOC(BYTE, cbRootFile);
- if(pbRootFile == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
+ // Locale: The default parameter is 0 - in that case,
+ // we assign the default locale, loaded from the .build.info file
+ if(dwLocaleMask == 0)
+ dwLocaleMask = hs->dwDefaultLocale;
- // If all went OK, we load the entire file to memory
- if(nError == ERROR_SUCCESS)
+ // 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(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading ROOT manifest", NULL, 0, 0))
+ return ERROR_CANCELLED;
+
+ // Load the entire ROOT file to memory
+ pbRootFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbRootFile);
+ if(pbRootFile != NULL)
{
- // Read the entire file to memory
- CascReadFile(hFile, pbRootFile, cbRootFile, &dwBytesRead);
- if(dwBytesRead != cbRootFile)
- nError = ERROR_FILE_CORRUPT;
- }
+ // 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:
+ nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile);
+ break;
- // Give the loaded file length
- if(pcbRootFile != NULL)
- *pcbRootFile = cbRootFile;
- return pbRootFile;
-}
+ case CASC_DIABLO3_ROOT_SIGNATURE:
+ nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile);
+ break;
-static int LoadEncodingFile(TCascStorage * hs)
-{
- PFILE_ENCODING_SEGMENT pEncodingSegment;
- QUERY_KEY EncodingKey;
- LPBYTE pbStartOfSegment;
- LPBYTE pbEncodingFile = NULL;
- HANDLE hFile = NULL;
- DWORD cbEncodingFile = 0;
- DWORD dwNumSegments = 0;
- DWORD dwSegmentsPos = 0;
- int nError = ERROR_SUCCESS;
+ case CASC_TVFS_ROOT_SIGNATURE:
+ nError = RootHandler_CreateTVFS(hs, pbRootFile, cbRootFile);
+ break;
- // Open the encoding file
- EncodingKey.pbData = hs->EncodingKey.pbData + MD5_HASH_SIZE;
- EncodingKey.cbData = MD5_HASH_SIZE;
- if(!CascOpenFileByIndexKey((HANDLE)hs, &EncodingKey, 0, &hFile))
- nError = GetLastError();
+ case CASC_WOW82_ROOT_SIGNATURE:
+ nError = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask);
+ break;
- // Load the entire ENCODING file to memory
- if(nError == ERROR_SUCCESS)
- {
- // Load the necessary part of the ENCODING file to memory
- pbEncodingFile = LoadEncodingFileToMemory(hFile, &cbEncodingFile);
- if(pbEncodingFile == NULL || cbEncodingFile <= sizeof(CASC_ENCODING_HEADER))
- nError = ERROR_FILE_CORRUPT;
+ default:
+
+ //
+ // Each of these handler creators must verify their format first.
+ // If the format was not recognized, they need to return ERROR_BAD_FORMAT
+ //
+
+ nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile);
+ if(nError == ERROR_BAD_FORMAT)
+ {
+ nError = RootHandler_CreateStarcraft1(hs, pbRootFile, cbRootFile);
+ if(nError == ERROR_BAD_FORMAT)
+ {
+ nError = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask);
+ }
+ }
+ break;
+ }
+ }
- // Close the encoding file
- CascCloseFile(hFile);
+ // Free the root file
+ CASC_FREE(pbRootFile);
}
-
- // Verify all encoding segments
- if(nError == ERROR_SUCCESS)
+ else
{
- PCASC_ENCODING_HEADER pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile;
-
- // Convert size and offset
- dwNumSegments = ConvertBytesToInteger_4(pEncodingHeader->Entries_TableA);
- dwSegmentsPos = ConvertBytesToInteger_4(pEncodingHeader->Size_StringTable1);
+ nError = GetLastError();
+ }
- // Store the encoding file to the CASC storage
- hs->EncodingFile.pbData = pbEncodingFile;
- hs->EncodingFile.cbData = cbEncodingFile;
+ return nError;
+}
- // Allocate the array of encoding segments
- pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
- pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments);
+static int InitializeLocalDirectories(TCascStorage * hs, const TCHAR * szPath)
+{
+ TCHAR * szWorkPath;
+ int nError = ERROR_NOT_ENOUGH_MEMORY;
- // Go through all encoding segments and verify them
- for(DWORD i = 0; i < dwNumSegments; i++)
+ // Find the root directory of the storage. The root directory
+ // is the one with ".build.info" or ".build.db".
+ szWorkPath = CascNewStr(szPath);
+ if(szWorkPath != NULL)
+ {
+ // Get the length and go up until we find the ".build.info" or ".build.db"
+ for(;;)
{
- PCASC_ENCODING_ENTRY pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment;
-
- // Check if there is enough space in the buffer
- if((pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE) > (pbEncodingFile + cbEncodingFile))
+ // Is this a game directory?
+ nError = CheckGameDirectory(hs, szWorkPath);
+ if(nError == ERROR_SUCCESS)
{
- nError = ERROR_FILE_CORRUPT;
+ nError = ERROR_SUCCESS;
break;
}
- // Check the hash of the entire segment
- // Note that verifying takes considerable time of the storage loading
-// if(!VerifyDataBlockHash(pbStartOfSegment, CASC_ENCODING_SEGMENT_SIZE, pEncodingSegment->SegmentHash))
-// {
-// nError = ERROR_FILE_CORRUPT;
-// break;
-// }
-
- // Check if the encoding key matches with the expected first value
- if(memcmp(pEncodingEntry->EncodingKey, pEncodingSegment->FirstEncodingKey, MD5_HASH_SIZE))
+ // Cut one path part
+ if(!CutLastPathPart(szWorkPath))
{
- nError = ERROR_FILE_CORRUPT;
+ nError = ERROR_FILE_NOT_FOUND;
break;
}
+ }
+
+ // Find the index directory
+ if (nError == ERROR_SUCCESS)
+ {
+ // First, check for more common "data" subdirectory
+ if ((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
+ nError = ERROR_SUCCESS;
+
+ // Second, try the "darch" subdirectory (older builds of HOTS - Alpha)
+ else if ((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
+ nError = ERROR_SUCCESS;
- // Move to the next segment
- pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE;
- pEncodingSegment++;
+ else
+ nError = ERROR_FILE_NOT_FOUND;
}
- }
- // Create the map of the encoding keys
- // Note that the array of encoding keys is already sorted - no need to sort it
- if(nError == ERROR_SUCCESS)
- {
- pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
- nError = CreateMapOfEncodingKeys(hs, pEncodingSegment, dwNumSegments);
+ // Free the work path buffer
+ CASC_FREE(szWorkPath);
}
return nError;
}
-static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask)
+static int InitializeOnlineDirectories(TCascStorage * hs, LPCTSTR szLocalCache, LPCSTR szCodeName, LPCSTR szRegion)
{
- PDWORD FileSignature;
- HANDLE hFile = NULL;
- LPBYTE pbRootFile = NULL;
- DWORD cbRootFile = 0;
- int nError = ERROR_SUCCESS;
+ TCHAR szCodeNameT[0x40];
- // Sanity checks
- assert(hs->pEncodingMap != NULL);
- assert(hs->pRootHandler == NULL);
-
- // Locale: The default parameter is 0 - in that case,
- // we assign the default locale, loaded from the .build.info file
- if(dwLocaleMask == 0)
- dwLocaleMask = hs->dwDefaultLocale;
-
- // Load the entire ROOT file to memory
- if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile))
- nError = GetLastError();
-
- // Load the entire file to memory
- if(nError == ERROR_SUCCESS)
+ CascStrCopy(szCodeNameT, _countof(szCodeNameT), szCodeName);
+ hs->szRootPath = CombinePath(szLocalCache, szCodeNameT);
+ if (hs->szRootPath != NULL)
{
- pbRootFile = LoadRootFileToMemory(hFile, &cbRootFile);
- CascCloseFile(hFile);
+ // Create the name of the build file
+ hs->szBuildFile = CombinePath(hs->szRootPath, _T("versions"));
+ if(hs->szBuildFile != NULL)
+ {
+ hs->szCodeName = CascNewStr(szCodeNameT);
+ hs->szRegion = CascNewStr(szRegion);
+ if(hs->szCodeName != NULL)
+ {
+ hs->BuildFileType = CascVersionsDb;
+ hs->dwFeatures |= CASC_FEATURE_ONLINE;
+ return ERROR_SUCCESS;
+ }
+ }
}
- // Check if the version of the ROOT file
- if(nError == ERROR_SUCCESS && pbRootFile != NULL)
- {
- FileSignature = (PDWORD)pbRootFile;
- switch(FileSignature[0])
- {
- case CASC_MNDX_ROOT_SIGNATURE:
- nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile);
- break;
+ return ERROR_NOT_ENOUGH_MEMORY;
+}
- case CASC_DIABLO3_ROOT_SIGNATURE:
- nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile);
- break;
+static DWORD GetStorageTotalFileCount(TCascStorage * hs)
+{
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ size_t nItemCount = hs->CKeyArray.ItemCount();
+ DWORD TotalFileCount = 0;
- case CASC_OVERWATCH_ROOT_SIGNATURE:
- nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile);
- break;
+ for(size_t i = 0; i < nItemCount; i++)
+ {
+ if((pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(i)) != NULL)
+ {
+ if((pCKeyEntry->Flags & CASC_CE_FOLDER_ENTRY) == 0)
+ {
+ // 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;
- default:
- if(IsRootFile_Starcraft1(pbRootFile, cbRootFile))
- nError = RootHandler_CreateSC1(hs, pbRootFile, cbRootFile);
- else
- nError = RootHandler_CreateWoW6(hs, pbRootFile, cbRootFile, dwLocaleMask);
- break;
+ // Add the number of references to the total file count
+ TotalFileCount += RefCount;
+ }
}
}
- // Insert entry for the
- if(nError == ERROR_SUCCESS)
- {
- InsertExtraFile(hs, "ENCODING", &hs->EncodingKey);
- InsertExtraFile(hs, "ROOT", &hs->RootKey);
- InsertExtraFile(hs, "DOWNLOAD", &hs->DownloadKey);
- InsertExtraFile(hs, "INSTALL", &hs->InstallKey);
- }
+ return TotalFileCount;
+}
-#ifdef _DEBUG
- if(nError == ERROR_SUCCESS)
+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)
{
- //RootFile_Dump(hs,
- // pbRootFile,
- // cbRootFile,
- // _T("\\casc_root_%build%.txt"),
- // _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"),
- // DUMP_LEVEL_INDEX_ENTRIES);
+ pProductInfo->szProductName = hs->szProductName;
+ pProductInfo->dwBuildNumber = hs->dwBuildNumber;
+ pProductInfo->Product = hs->Product;
}
-#endif
- // Free the root file
- CASC_FREE(pbRootFile);
- return nError;
+ return (pProductInfo != NULL);
}
-static TCascStorage * FreeCascStorage(TCascStorage * hs)
+static bool GetStorageTags(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded)
{
- size_t i;
+ PCASC_STORAGE_TAGS pTags;
+ PCASC_TAG_ENTRY2 pTag;
+ char * szNameBuffer;
+ size_t cbMinLength;
- if(hs != NULL)
+ // Does the storage support tags?
+ if(hs->TagsArray.IsInitialized() == false)
{
- // Free the root handler
- if(hs->pRootHandler != NULL)
- RootHandler_Close(hs->pRootHandler);
- hs->pRootHandler = NULL;
-
- // Free the extra encoding entries
- Array_Free(&hs->ExtraEntries);
-
- // Free the pointers to file entries
- if(hs->pEncodingMap != NULL)
- Map_Free(hs->pEncodingMap);
- if(hs->EncodingFile.pbData != NULL)
- CASC_FREE(hs->EncodingFile.pbData);
- if(hs->pIndexEntryMap != NULL)
- Map_Free(hs->pIndexEntryMap);
-
- // Close all data files
- for(i = 0; i < CASC_MAX_DATA_FILES; i++)
- {
- if(hs->DataFileArray[i] != NULL)
- {
- FileStream_Close(hs->DataFileArray[i]);
- hs->DataFileArray[i] = NULL;
- }
- }
+ 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;
+ }
- // Close all key mappings
- for(i = 0; i < CASC_INDEX_COUNT; i++)
+ // 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++)
{
- if(hs->KeyMapping[i].szFileName != NULL)
- CASC_FREE(hs->KeyMapping[i].szFileName);
- if(hs->KeyMapping[i].pbFileData != NULL)
- CASC_FREE(hs->KeyMapping[i].pbFileData);
- hs->KeyMapping[i].pIndexEntries = NULL;
+ // 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;
}
-
- // Free the file paths
- if(hs->szRootPath != NULL)
- CASC_FREE(hs->szRootPath);
- if(hs->szDataPath != NULL)
- CASC_FREE(hs->szDataPath);
- if(hs->szBuildFile != NULL)
- CASC_FREE(hs->szBuildFile);
- if(hs->szIndexPath != NULL)
- CASC_FREE(hs->szIndexPath);
- if(hs->szUrlPath != NULL)
- CASC_FREE(hs->szUrlPath);
-
- // Free the blobs
- FreeCascBlob(&hs->CdnConfigKey);
- FreeCascBlob(&hs->CdnBuildKey);
- FreeCascBlob(&hs->ArchivesGroup);
- FreeCascBlob(&hs->ArchivesKey);
- FreeCascBlob(&hs->PatchArchivesKey);
- FreeCascBlob(&hs->PatchArchivesGroup);
- FreeCascBlob(&hs->RootKey);
- FreeCascBlob(&hs->PatchKey);
- FreeCascBlob(&hs->DownloadKey);
- FreeCascBlob(&hs->InstallKey);
- FreeCascBlob(&hs->EncodingKey);
-
- // Free the storage structure
- hs->szClassName = NULL;
- CASC_FREE(hs);
}
- return NULL;
+ return (pTags != NULL);
}
-//-----------------------------------------------------------------------------
-// Public functions
-
-bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage)
+static int LoadCascStorage(TCascStorage * hs, DWORD dwLocaleMask)
{
- TCascStorage * hs;
int nError = ERROR_SUCCESS;
- // Allocate the storage structure
- hs = (TCascStorage *)CASC_ALLOC(TCascStorage, 1);
- if(hs == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
-
- // Load the storage configuration
- if(nError == ERROR_SUCCESS)
+ // For online storages, we need to load CDN servers
+ if ((nError == ERROR_SUCCESS) && (hs->dwFeatures & CASC_FEATURE_ONLINE))
{
- // Prepare the base storage parameters
- memset(hs, 0, sizeof(TCascStorage));
- hs->szClassName = "TCascStorage";
- hs->dwFileBeginDelta = 0xFFFFFFFF;
- hs->dwDefaultLocale = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB;
- hs->dwRefCount = 1;
- nError = InitializeCascDirectories(hs, szDataPath);
+ nError = LoadCdnsInfo(hs);
}
- // Now we need to load the root file so we know the config files
+ // Now, load the main storage file ".build.info" (or ".build.db" in old storages)
if(nError == ERROR_SUCCESS)
{
nError = LoadBuildInfo(hs);
}
- // Load the index files
+ // If the .build.info OR .build.db file has been loaded,
+ // proceed with loading the CDN config file
+ if (nError == ERROR_SUCCESS)
+ {
+ nError = LoadCdnConfigFile(hs);
+ }
+
+ // Proceed with loading the CDN build file
+ if (nError == ERROR_SUCCESS)
+ {
+ nError = LoadCdnBuildFile(hs);
+ }
+
+ // Load the index files. Store information from the index files to the CKeyArray.
if(nError == ERROR_SUCCESS)
{
nError = LoadIndexFiles(hs);
}
- // Load the index files
+ // Load the ENCODING manifest
if(nError == ERROR_SUCCESS)
{
- nError = LoadEncodingFile(hs);
+ nError = LoadEncodingManifest(hs);
}
- // Initialize the dynamic array for extra files
- // Reserve space for 0x20 encoding entries
+ // 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(nError == ERROR_SUCCESS)
{
- nError = Array_Create(&hs->ExtraEntries, CASC_ENCODING_ENTRY_1, CASC_EXTRA_FILES);
+ nError = LoadDownloadManifest(hs);
}
- // Load the index files
+ // Load the build manifest ("ROOT" file)
if(nError == ERROR_SUCCESS)
{
- nError = LoadRootFile(hs, dwLocaleMask);
+ // If we fail to load the ROOT file, we take the file names from the INSTALL manifest
+ nError = LoadBuildManifest(hs, dwLocaleMask);
+ if (nError != ERROR_SUCCESS)
+ {
+ nError = LoadInstallManifest(hs);
+ }
}
- // If something failed, free the storage and return
- if(nError != ERROR_SUCCESS)
+ // 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 (nError == ERROR_SUCCESS)
+ {
+ InsertWellKnownFile(hs, "ENCODING", hs->EncodingCKey);
+ InsertWellKnownFile(hs, "DOWNLOAD", hs->DownloadCKey);
+ InsertWellKnownFile(hs, "INSTALL", hs->InstallCKey);
+ InsertWellKnownFile(hs, "PATCH", hs->PatchFile);
+ 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 (nError == ERROR_SUCCESS)
+ {
+ nError = CascLoadEncryptionKeys(hs);
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+void WINAPI CascSetProgressCallback(PFNPROGRESSCALLBACK PtrUserCallback, void * PtrUserParam)
+{
+ PfnProgressCallback = PtrUserCallback;
+ PtrProgressParam = PtrUserParam;
+}
+
+bool WINAPI CascOpenStorage(LPCTSTR szPath, DWORD dwLocaleMask, HANDLE * phStorage)
+{
+ TCascStorage * hs;
+ int nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate the storage structure
+ if((hs = new TCascStorage()) != NULL)
{
- hs = FreeCascStorage(hs);
- SetLastError(nError);
+ // Setup the directories
+ nError = InitializeLocalDirectories(hs, szPath);
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadCascStorage(hs, dwLocaleMask);
+ if(nError == ERROR_SUCCESS)
+ {
+ *phStorage = (HANDLE)hs;
+ return true;
+ }
+ }
+
+ // Delete the so-far-allocated storage
+ hs = hs->Release();
}
- *phStorage = (HANDLE)hs;
- return (nError == ERROR_SUCCESS);
+ // Failed
+ SetLastError(nError);
+ *phStorage = NULL;
+ return false;
+}
+
+// Allows to browse an online CDN storage
+// szLocalCache: Local folder, where the online file will be cached.
+// szCodeName: Product code name, e.g. "agent" for Battle.net Agent. More info: https://wowdev.wiki/TACT#Products
+// szRegion: The region (or subvariant) of the product. Corresponds to the first column of the "versions" file.
+bool WINAPI CascOpenOnlineStorage(LPCTSTR szLocalCache, LPCSTR szCodeName, LPCSTR szRegion, DWORD dwLocaleMask, HANDLE * phStorage)
+{
+ TCascStorage * hs;
+ int nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate the storage structure
+ if((hs = new TCascStorage()) != NULL)
+ {
+ // Setup the directories
+ nError = InitializeOnlineDirectories(hs, szLocalCache, szCodeName, szRegion);
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadCascStorage(hs, dwLocaleMask);
+ if(nError == ERROR_SUCCESS)
+ {
+ *phStorage = (HANDLE)hs;
+ return true;
+ }
+ }
+
+ // Delete the so-far-allocated storage
+ hs = hs->Release();
+ }
+
+ // Failed
+ SetLastError(nError);
+ *phStorage = NULL;
+ return false;
}
bool WINAPI CascGetStorageInfo(
@@ -1120,10 +1240,11 @@ bool WINAPI CascGetStorageInfo(
size_t * pcbLengthNeeded)
{
TCascStorage * hs;
+ PDWORD PtrOutputValue;
DWORD dwInfoValue = 0;
// Verify the storage handle
- hs = IsValidStorageHandle(hStorage);
+ hs = TCascStorage::IsValid(hStorage);
if(hs == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
@@ -1133,45 +1254,43 @@ bool WINAPI CascGetStorageInfo(
// Differentiate between info classes
switch(InfoClass)
{
- case CascStorageFileCount:
- dwInfoValue = (DWORD)hs->pIndexEntryMap->ItemCount;
- break;
-
- case CascStorageFeatures:
- dwInfoValue |= (hs->pRootHandler->dwRootFlags & ROOT_FLAG_HAS_NAMES) ? CASC_FEATURE_LISTFILE : 0;
+ case CascStorageLocalFileCount:
+ dwInfoValue = (DWORD)hs->LocalFiles;
break;
- case CascStorageGameInfo:
- dwInfoValue = hs->dwGameInfo;
+ case CascStorageTotalFileCount:
+ if(hs->TotalFiles == 0)
+ hs->TotalFiles = GetStorageTotalFileCount(hs);
+ dwInfoValue = (DWORD)hs->TotalFiles;
break;
- case CascStorageGameBuild:
- dwInfoValue = hs->dwBuildNumber;
+ 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);
+
default:
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
//
- // Return the required DWORD value
+ // Default: return a 32-bit unsigned value
//
- if(cbStorageInfo < sizeof(DWORD))
- {
- *pcbLengthNeeded = sizeof(DWORD);
- SetLastError(ERROR_INSUFFICIENT_BUFFER);
- return false;
- }
-
- // Give the number of files
- *(PDWORD)pvStorageInfo = dwInfoValue;
- return true;
+ PtrOutputValue = (PDWORD)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(DWORD), pcbLengthNeeded);
+ if(PtrOutputValue != NULL)
+ PtrOutputValue[0] = dwInfoValue;
+ return (PtrOutputValue != NULL);
}
bool WINAPI CascCloseStorage(HANDLE hStorage)
@@ -1179,7 +1298,7 @@ bool WINAPI CascCloseStorage(HANDLE hStorage)
TCascStorage * hs;
// Verify the storage handle
- hs = IsValidStorageHandle(hStorage);
+ hs = TCascStorage::IsValid(hStorage);
if(hs == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
@@ -1187,14 +1306,6 @@ bool WINAPI CascCloseStorage(HANDLE hStorage)
}
// Only free the storage if the reference count reaches 0
- if(hs->dwRefCount == 1)
- {
- FreeCascStorage(hs);
- return true;
- }
-
- // Just decrement number of references
- hs->dwRefCount--;
+ hs->Release();
return true;
}
-