aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/CascOpenStorage.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2014-10-10 20:17:30 +0200
committerShauren <shauren.trinity@gmail.com>2014-10-10 20:17:30 +0200
commit88ae3da6373dee1f04d03b823ee63d6f1db1502e (patch)
treef9ac27f0a743a57b70e90b37f5971e024992eb00 /dep/CascLib/src/CascOpenStorage.cpp
parentbc97908822c4afa23740ce70151c2486c340e2c2 (diff)
Tools/Extractors: Updated map extractor
Diffstat (limited to 'dep/CascLib/src/CascOpenStorage.cpp')
-rw-r--r--dep/CascLib/src/CascOpenStorage.cpp1226
1 files changed, 1226 insertions, 0 deletions
diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp
new file mode 100644
index 00000000000..f9e6ba6f4d4
--- /dev/null
+++ b/dep/CascLib/src/CascOpenStorage.cpp
@@ -0,0 +1,1226 @@
+/*****************************************************************************/
+/* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */
+/*---------------------------------------------------------------------------*/
+/* Storage functions for CASC */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.04.14 1.00 Lad The first version of CascOpenStorage.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+#include "CascMndxRoot.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+#define CASC_ENCODING_SEGMENT_SIZE 0x1000
+
+typedef struct _BLOCK_SIZE_AND_HASH
+{
+ DWORD cbBlockSize;
+ DWORD dwBlockHash;
+
+} BLOCK_SIZE_AND_HASH, *PBLOCK_SIZE_AND_HASH;
+
+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_HEADER
+{
+ BYTE Magic[2]; // "EN"
+ BYTE field_2;
+ BYTE field_3;
+ BYTE field_4;
+ BYTE field_5[2];
+ BYTE field_7[2];
+ BYTE NumSegments[4]; // Number of entries (big endian)
+ BYTE field_D[4];
+ BYTE field_11;
+ BYTE SegmentsPos[4]; // Offset of encoding segments
+
+} FILE_ENCODING_HEADER, *PFILE_ENCODING_HEADER;
+
+typedef struct _FILE_ENCODING_SEGMENT
+{
+ BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment
+ BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
+
+} FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT;
+
+typedef struct _FILE_LOCALE_BLOCK
+{
+ DWORD NumberOfFiles; // Number of entries
+ DWORD Flags;
+ DWORD Locales; // File locale mask (CASC_LOCALE_XXX)
+
+ // Followed by a block of 32-bit integers (count: NumberOfFiles)
+ // Followed by the MD5 and file name hash (count: NumberOfFiles)
+
+} FILE_LOCALE_BLOCK, *PFILE_LOCALE_BLOCK;
+
+typedef struct _FILE_ROOT_ENTRY
+{
+ BYTE EncodingKey[MD5_HASH_SIZE]; // MD5 of the file
+ ULONGLONG FileNameHash; // Jenkins hash of the file name
+
+} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
+
+typedef struct _ROOT_BLOCK_INFO
+{
+ PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
+ PDWORD pInt32Array; // Pointer to the array of 32-bit integers
+ PFILE_ROOT_ENTRY pRootEntries;
+
+} ROOT_BLOCK_INFO, *PROOT_BLOCK_INFO;
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+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");
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+inline void CopyFileKey(LPBYTE Trg, LPBYTE Src)
+{
+ 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];
+}
+
+TCascStorage * IsValidStorageHandle(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);
+}
+
+static bool IsIndexFileName_V2(const TCHAR * szFileName)
+{
+ // 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);
+}
+
+static void QUERY_KEY_Free(PQUERY_KEY pBlob)
+{
+ if(pBlob != NULL)
+ {
+ if(pBlob->pbData != NULL)
+ CASC_FREE(pBlob->pbData);
+
+ pBlob->pbData = NULL;
+ pBlob->cbData = 0;
+ }
+}
+
+static void QUERY_KEY_FreeArray(PQUERY_KEY pBlobArray)
+{
+ // Free the buffer in the first blob
+ // (will also free all buffers in the array)
+ QUERY_KEY_Free(pBlobArray);
+
+ // Free the array itself
+ CASC_FREE(pBlobArray);
+}
+
+static int CompareRootEntries(const void *, const void * pvKeyEntry1, const void * pvKeyEntry2)
+{
+ PCASC_ROOT_ENTRY pRootEntry1 = (PCASC_ROOT_ENTRY)pvKeyEntry1;
+ PCASC_ROOT_ENTRY pRootEntry2 = (PCASC_ROOT_ENTRY)pvKeyEntry2;
+
+ // Compare name hash first
+ if(pRootEntry1->FileNameHash < pRootEntry2->FileNameHash)
+ return -1;
+ if(pRootEntry1->FileNameHash > pRootEntry2->FileNameHash)
+ return +1;
+ return 0;
+}
+
+static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData)
+{
+ PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData;
+ DWORD dwHeaderHash;
+ bool bResult = false;
+
+ // Check the size
+ if(cbFileData >= sizeof(FILE_INDEX_HEADER_V1))
+ {
+ // Save the header hash
+ dwHeaderHash = pIndexHeader->dwHeaderHash;
+ pIndexHeader->dwHeaderHash = 0;
+
+ // Calculate the hash
+ if(hashlittle(pIndexHeader, sizeof(FILE_INDEX_HEADER_V1), 0) == dwHeaderHash)
+ bResult = true;
+
+ // Put the hash back
+ pIndexHeader->dwHeaderHash = dwHeaderHash;
+ }
+
+ return bResult;
+}
+
+static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData)
+{
+ PBLOCK_SIZE_AND_HASH pSizeAndHash = (PBLOCK_SIZE_AND_HASH)pbFileData;
+ unsigned int HashHigh = 0;
+ unsigned int HashLow = 0;
+
+ // 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;
+
+ // The index header for CASC v 2.0 begins with length and checksum
+ hashlittle2(pSizeAndHash + 1, pSizeAndHash->cbBlockSize, &HashHigh, &HashLow);
+ return (HashHigh == pSizeAndHash->dwBlockHash);
+}
+
+static LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
+{
+ // Validate the locale header
+ pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
+ pbFilePointer += sizeof(FILE_LOCALE_BLOCK);
+ if(pbFilePointer >= pbFileEnd)
+ return NULL;
+
+ // Validate the array of 32-bit integers
+ pBlockInfo->pInt32Array = (PDWORD)pbFilePointer;
+ pbFilePointer = (LPBYTE)(pBlockInfo->pInt32Array + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
+ if(pbFilePointer >= pbFileEnd)
+ return NULL;
+
+ // Validate the array of root entries
+ pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer;
+ pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
+ if(pbFilePointer > pbFileEnd)
+ return NULL;
+
+ // Return the position of the next block
+ return pbFilePointer;
+}
+
+static int InitializeCascDirectories(TCascStorage * hs, const TCHAR * szDataPath)
+{
+ TCHAR * szLastPathPart;
+
+ // Save the game data directory
+ hs->szDataPath = NewStr(szDataPath, 0);
+
+ // Save the root game directory
+ hs->szRootPath = NewStr(szDataPath, 0);
+
+ // Find the last part
+ szLastPathPart = hs->szRootPath;
+ for(size_t i = 0; hs->szRootPath[i] != 0; i++)
+ {
+ if(hs->szRootPath[i] == '\\' || hs->szRootPath[i] == '/')
+ szLastPathPart = hs->szRootPath + i;
+ }
+
+ // Cut the last part
+ if(szLastPathPart != NULL)
+ szLastPathPart[0] = 0;
+ return (hs->szRootPath && hs->szDataPath) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
+}
+
+static bool IndexDirectory_OnFileFound(
+ const TCHAR * szFileName,
+ PDWORD IndexArray,
+ PDWORD OldIndexArray,
+ void * pvContext)
+{
+ TCascStorage * hs = (TCascStorage *)pvContext;
+ DWORD IndexValue = 0;
+ DWORD IndexVersion = 0;
+
+ // 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;
+ }
+
+ if(hs->szIndexFormat == szIndexFormat_V1)
+ {
+ // Check the index file name format
+ if(!IsIndexFileName_V1(szFileName))
+ return false;
+
+ // 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;
+
+ // 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;
+ }
+
+ // The index value must not be greater than 0x0F
+ if(IndexValue >= CASC_INDEX_COUNT)
+ return false;
+
+ // 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;
+ }
+ else if(IndexVersion > OldIndexArray[IndexValue])
+ {
+ OldIndexArray[IndexValue] = IndexVersion;
+ }
+
+ // Note: WoW6 only keeps last two index files
+ // Any additional index files are deleted at this point
+ return true;
+}
+
+static TCHAR * CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion)
+{
+ 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);
+}
+
+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;
+
+ return ERROR_SUCCESS;
+}
+
+static int VerifyAndParseKeyMapping_V2(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
+{
+ 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;
+
+ if(pIndexHeader->ExtraBytes != 0x00 ||
+ pIndexHeader->SpanSizeBytes != 0x04 ||
+ pIndexHeader->SpanOffsBytes != 0x05 ||
+ pIndexHeader->KeyBytes != CASC_FILE_KEY_SIZE)
+ 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)
+ return ERROR_BAD_FORMAT;
+
+ // Get the pointer to "size+hash" block
+ pSizeAndHash = (PBLOCK_SIZE_AND_HASH)(pKeyMapping->pbFileData + FilePosition);
+ FilePosition += 0x08;
+
+ if((FilePosition + pSizeAndHash->cbBlockSize) > pKeyMapping->cbFileData)
+ return ERROR_BAD_FORMAT;
+ if(pSizeAndHash->cbBlockSize < sizeof(CASC_INDEX_ENTRY))
+ return ERROR_BAD_FORMAT;
+
+ // 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;
+
+ // 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)
+ return ERROR_BAD_FORMAT;
+
+ // Align the data position up to next 0x1000
+ FilePosition = ALIGN_TO_SIZE(FilePosition, 0x1000);
+ if(FilePosition > pKeyMapping->cbFileData)
+ return ERROR_BAD_FORMAT;
+
+ LastPartLength = pKeyMapping->cbFileData - FilePosition;
+ if(LastPartLength < 0x7800)
+ return ERROR_BAD_FORMAT;
+
+ pbLastPart = pKeyMapping->pbFileData + FilePosition;
+ pbLastPartEnd = pbLastPart + ((LastPartLength >> 0x09) << 0x09);
+
+ while(pbLastPart < pbLastPartEnd)
+ {
+ for(int i = 0; i < 0x1F8; i += 0x18)
+ {
+ PDWORD PtrLastPart = (PDWORD)pbLastPart;
+ if(PtrLastPart[0] == 0)
+ return ERROR_SUCCESS;
+
+ HashLow = hashlittle(PtrLastPart + 1, 0x13, 0) | 0x80000000;
+ if(HashLow != PtrLastPart[0])
+ return ERROR_BAD_FORMAT;
+ }
+
+ pbLastPart += 0x200;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int VerifyAndParseKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
+{
+ // Sanity checks
+ assert(pKeyMapping->pbFileData != NULL);
+ assert(pKeyMapping->cbFileData != 0);
+
+ // Check for CASC version 2
+ if(IsCascIndexHeader_V2(pKeyMapping->pbFileData, pKeyMapping->cbFileData))
+ return VerifyAndParseKeyMapping_V2(pKeyMapping, KeyIndex);
+
+ // Check for CASC version 1
+ if(IsCascIndexHeader_V1(pKeyMapping->pbFileData, pKeyMapping->cbFileData))
+ return VerifyAndParseKeyMapping_V1(pKeyMapping, KeyIndex);
+
+ // Unknown CASC version
+ assert(false);
+ return ERROR_BAD_FORMAT;
+}
+
+static int LoadKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
+{
+ TFileStream * pStream;
+ ULONGLONG FileSize = 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)
+ {
+ // Retrieve the file size
+ FileStream_GetSize(pStream, &FileSize);
+ if((0 < FileSize && FileSize <= 0x90000) || 1)
+ {
+ // WoW6 actually reads THE ENTIRE file to memory
+ // Verified on Mac build (x64)
+ pKeyMapping->pbFileData = CASC_ALLOC(BYTE, (DWORD)FileSize);
+ pKeyMapping->cbFileData = (DWORD)FileSize;
+
+ // Load the data to memory and parse it
+ if(pKeyMapping->pbFileData != NULL)
+ {
+ if(FileStream_Read(pStream, NULL, pKeyMapping->pbFileData, pKeyMapping->cbFileData))
+ {
+ nError = VerifyAndParseKeyMapping(pKeyMapping, KeyIndex);
+ }
+ }
+ else
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ else
+ {
+ assert(false);
+ nError = ERROR_BAD_FORMAT;
+ }
+
+ // 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;
+
+ // Count the total number of files in the storage
+ for(size_t i = 0; i < CASC_INDEX_COUNT; i++)
+ TotalCount += hs->KeyMapping[i].nIndexEntries;
+
+ // 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++)
+ {
+ PCASC_INDEX_ENTRY pIndexEntry = hs->KeyMapping[i].pIndexEntries;
+ DWORD nIndexEntries = hs->KeyMapping[i].nIndexEntries;
+
+ for(DWORD j = 0; j < nIndexEntries; 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->IndexKey);
+
+ // Move to the next entry
+ pIndexEntry++;
+ }
+ }
+
+ // Store the map to the storage handle
+ hs->pIndexEntryMap = pMap;
+ nError = ERROR_SUCCESS;
+ }
+
+ return nError;
+}
+
+static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumberOfSegments)
+{
+ PCASC_ENCODING_ENTRY pEncodingEntry;
+ size_t nMaxEntries;
+ size_t nEntries = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Sanity check
+ assert(hs->ppEncodingEntries == NULL);
+ assert(hs->pIndexEntryMap != NULL);
+
+ // Calculate the largest eventual number of encodign entries
+ nMaxEntries = (dwNumberOfSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE);
+
+ // Allocate the array of pointers to encoding entries
+ hs->ppEncodingEntries = CASC_ALLOC(PCASC_ENCODING_ENTRY, nMaxEntries);
+ if(hs->ppEncodingEntries != NULL)
+ {
+ LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumberOfSegments);
+
+ // Parse all segments
+ for(DWORD i = 0; i < dwNumberOfSegments; i++)
+ {
+ 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
+ hs->ppEncodingEntries[nEntries++] = pEncodingEntry;
+
+ // 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;
+ }
+
+ // Remember the total number of encoding entries
+ hs->nEncodingEntries = nEntries;
+ }
+ else
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ return nError;
+}
+
+static DWORD GetSizeOfEncodingFile(HANDLE hFile)
+{
+ CASC_ENCODING_HEADER EncodingHeader;
+ DWORD cbEncodingFile = 0;
+ DWORD dwSegmentPos;
+ DWORD dwNumSegments;
+ DWORD dwBytesRead;
+
+ // Read the endoding header
+ CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
+ if(dwBytesRead == sizeof(CASC_ENCODING_HEADER))
+ {
+ dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.NumSegments);
+ dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.SegmentsPos);
+
+ cbEncodingFile = sizeof(CASC_ENCODING_HEADER) +
+ dwSegmentPos +
+ dwNumSegments * (sizeof(FILE_ENCODING_SEGMENT) + CASC_ENCODING_SEGMENT_SIZE);
+ }
+
+ // Reset the position back
+ CascSetFilePointer(hFile, 0, NULL, FILE_BEGIN);
+ return cbEncodingFile;
+}
+
+static LPBYTE LoadCascFile(HANDLE hFile, DWORD cbMaxSize, PDWORD pcbFileData)
+{
+ LPBYTE pbFileData = NULL;
+ DWORD cbFileData;
+ DWORD dwBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Retrieve the size of the file
+ cbFileData = CascGetFileSize(hFile, NULL);
+ if(cbFileData != 0 && cbFileData != CASC_INVALID_SIZE)
+ {
+ // Trim the size to the maximum
+ cbFileData = CASCLIB_MIN(cbMaxSize, cbFileData);
+
+ // Allocate the buffer that will hold the entire file
+ pbFileData = CASC_ALLOC(BYTE, cbFileData);
+ if(pbFileData != NULL)
+ {
+ // Read the entire file to memory
+ CascReadFile(hFile, pbFileData, cbFileData, &dwBytesRead);
+ if(dwBytesRead != cbFileData)
+ nError = ERROR_FILE_CORRUPT;
+ }
+ else
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ else
+ nError = ERROR_FILE_CORRUPT;
+
+ // If something failed, clean-up the buffers
+ if(nError != ERROR_SUCCESS)
+ {
+ // Clear the file data
+ if(pbFileData != NULL)
+ CASC_FREE(pbFileData);
+ pbFileData = NULL;
+ cbFileData = 0;
+
+ // Set the last error value
+ SetLastError(nError);
+ }
+
+ // Return what we got
+ if(pcbFileData != NULL)
+ *pcbFileData = cbFileData;
+ return pbFileData;
+}
+
+static int LoadIndexFiles(TCascStorage * hs)
+{
+ DWORD IndexArray[CASC_INDEX_COUNT];
+ DWORD OldIndexArray[CASC_INDEX_COUNT];
+ int nError;
+ int i;
+
+ // 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)
+ {
+ // 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;
+ }
+ }
+ }
+
+ // Now we need to build the map of the index entries
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = CreateArrayOfIndexEntries(hs);
+ }
+
+ return nError;
+}
+
+static int LoadEncodingFile(TCascStorage * hs)
+{
+ PFILE_ENCODING_SEGMENT pEncodingSegment;
+ PCASC_ENCODING_ENTRY pEncodingEntry;
+ LPBYTE pbStartOfSegment;
+ LPBYTE pbEncodingFile = NULL;
+ HANDLE hFile = NULL;
+ DWORD cbEncodingFile = 0;
+ DWORD dwNumberOfSegments = 0;
+ DWORD dwSegmentsPos = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Open the encoding file
+ if(!CascOpenFileByIndexKey((HANDLE)hs, &hs->EncodingEKey, 0, &hFile))
+ nError = GetLastError();
+
+ // Load the encoding file to memory
+ if(nError == ERROR_SUCCESS)
+ {
+ // Retrieve the CASC header. We do not usually need to load
+ // the entire file, but we need to know how big part of it we need
+ cbEncodingFile = GetSizeOfEncodingFile(hFile);
+
+ // Load the entire file to memory
+ pbEncodingFile = LoadCascFile(hFile, cbEncodingFile, &cbEncodingFile);
+ if(pbEncodingFile == NULL || cbEncodingFile <= sizeof(CASC_ENCODING_HEADER))
+ nError = ERROR_FILE_CORRUPT;
+
+ CascCloseFile(hFile);
+ }
+
+ // Verify all encoding segments
+ if(nError == ERROR_SUCCESS)
+ {
+ // Save the encoding header
+ hs->pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile;
+
+ // Convert size and offset
+ dwNumberOfSegments = ConvertBytesToInteger_4(hs->pEncodingHeader->NumSegments);
+ dwSegmentsPos = ConvertBytesToInteger_4(hs->pEncodingHeader->SegmentsPos);
+
+ // Allocate the array of encoding segments
+ pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
+ pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumberOfSegments);
+
+ // Go through all encoding segments and verify them
+ for(DWORD i = 0; i < dwNumberOfSegments; i++)
+ {
+ // Check if there is enough space in the buffer
+ if((pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE) > (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(pbStartOfSegment, CASC_ENCODING_SEGMENT_SIZE, pEncodingSegment->SegmentHash))
+// {
+// nError = ERROR_FILE_CORRUPT;
+// break;
+// }
+
+ // Check if the encoding key matches
+ pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment;
+ if(memcmp(pEncodingEntry->EncodingKey, pEncodingSegment->FirstEncodingKey, MD5_HASH_SIZE))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Move to the next segment
+ pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE;
+ pEncodingSegment++;
+ }
+ }
+
+ // 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, dwNumberOfSegments);
+ }
+ return nError;
+}
+
+static int LoadRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
+{
+ PFILE_ROOT_ENTRY pSrcEntry;
+ PCASC_ROOT_ENTRY pTrgEntry;
+ ROOT_BLOCK_INFO BlockInfo;
+ LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
+ LPBYTE pbFilePointer;
+ size_t nRootEntries = 0;
+ size_t nRootIndex = 0;
+ int nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Calculate the root entries
+ for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
+ {
+ // Validate the root block
+ pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
+ if(pbFilePointer == NULL)
+ break;
+
+ // Add the number of entries
+ nRootEntries = nRootEntries + BlockInfo.pLocaleBlockHdr->NumberOfFiles;
+ }
+
+ // Create a linear array of the root entries and sort it
+ hs->pRootEntries = pTrgEntry = CASC_ALLOC(CASC_ROOT_ENTRY, nRootEntries);
+ hs->ppRootEntries = CASC_ALLOC(PCASC_ROOT_ENTRY, nRootEntries);
+ if(hs->ppRootEntries && hs->pRootEntries)
+ {
+ // Convert each entry from FILE_ROOT_ENTRY to CASC_ROOT_ENTRY
+ for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
+ {
+ // Validate the root block
+ pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
+ if(pbFilePointer == NULL)
+ break;
+
+ // Get the pointer to the first root entry
+ pSrcEntry = (PFILE_ROOT_ENTRY)BlockInfo.pRootEntries;
+
+ // Convert all entries
+ for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
+ {
+ // Copy the root entry
+ CopyFileKey(pTrgEntry->EncodingKey, pSrcEntry->EncodingKey);
+ pTrgEntry->FileNameHash = pSrcEntry->FileNameHash;
+ pTrgEntry->Locales = BlockInfo.pLocaleBlockHdr->Locales;
+ pTrgEntry->Flags = BlockInfo.pLocaleBlockHdr->Flags;
+
+// if(pTrgEntry->FileNameHash == 0x5ddb88608673f698ULL)
+// DebugBreak();
+
+ // Insert the CASC root entry to the linear array of pointers
+ hs->ppRootEntries[nRootIndex++] = pTrgEntry;
+
+ // Move to the next root entry
+ pSrcEntry++;
+ pTrgEntry++;
+ }
+ }
+
+ // Save the number of entries
+ assert(nRootIndex == nRootEntries);
+ hs->nRootEntries = nRootIndex;
+
+ // Now sort the array
+ qsort_pointer_array((void **)hs->ppRootEntries, hs->nRootEntries, CompareRootEntries, NULL);
+ nError = ERROR_SUCCESS;
+ }
+
+ return nError;
+
+/*
+ FILE * fp = fopen("E:\\root_entries.txt", "wt");
+ if(fp != NULL)
+ {
+ for(size_t i = 0; i < nRootEntries; i++)
+ {
+ fprintf(fp, "%08X: %016I64lX\n", i, hs->ppRootEntries[i]->FileNameHash);
+ }
+ fclose(fp);
+ }
+*/
+}
+
+static int LoadRootFile(TCascStorage * hs)
+{
+ PDWORD FileSignature;
+ HANDLE hFile = NULL;
+ LPBYTE pbRootFile = NULL;
+ DWORD cbRootFile = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Sanity checks
+ assert(hs->ppEncodingEntries != NULL);
+ assert(hs->pRootEntries == NULL);
+ assert(hs->nRootEntries == 0);
+
+ // The root file is either MNDX file (Heroes of the Storm)
+ // or a file containing an array of root entries (World of Warcraft 6.0+)
+ // Note: The "root" key file's MD5 hash is equal to its name
+ // in the configuration
+ if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile))
+ nError = GetLastError();
+
+ // Load ther entire root file to memory
+ if(nError == ERROR_SUCCESS)
+ {
+ // Load the entire root file to memory
+ pbRootFile = LoadCascFile(hFile, 0xFFFFFFFF, &cbRootFile);
+ if(pbRootFile == NULL || cbRootFile == 0)
+ nError = ERROR_FILE_CORRUPT;
+
+ // Close the root file
+ CascCloseFile(hFile);
+ }
+
+ // Check if the file is a MNDX file
+ if(nError == ERROR_SUCCESS)
+ {
+ FileSignature = (PDWORD)pbRootFile;
+ if(FileSignature[0] == CASC_MNDX_SIGNATURE)
+ {
+ nError = LoadMndxRootFile(hs, pbRootFile, cbRootFile);
+ }
+ else
+ {
+ nError = LoadRootFile(hs, pbRootFile, cbRootFile);
+ }
+ }
+
+ // Free the root file
+ CASC_FREE(pbRootFile);
+ return nError;
+}
+
+static TCascStorage * FreeCascStorage(TCascStorage * hs)
+{
+ size_t i;
+
+ if(hs != NULL)
+ {
+ // Free the MNDX info
+ if(hs->pPackages != NULL)
+ CASC_FREE(hs->pPackages);
+ if(hs->pMndxInfo != NULL)
+ FreeMndxInfo(hs->pMndxInfo);
+
+ // Free the pointers to file entries
+ if(hs->ppRootEntries != NULL)
+ CASC_FREE(hs->ppRootEntries);
+ if(hs->pRootEntries != NULL)
+ CASC_FREE(hs->pRootEntries);
+ if(hs->ppEncodingEntries != NULL)
+ CASC_FREE(hs->ppEncodingEntries);
+ if(hs->pEncodingHeader != NULL)
+ CASC_FREE(hs->pEncodingHeader);
+ 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;
+ }
+ }
+
+ // Close all key mappings
+ for(i = 0; i < CASC_INDEX_COUNT; 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;
+ }
+
+ // Free the file paths
+ if(hs->szRootPath != NULL)
+ CASC_FREE(hs->szRootPath);
+ if(hs->szDataPath != NULL)
+ CASC_FREE(hs->szDataPath);
+ if(hs->szIndexPath != NULL)
+ CASC_FREE(hs->szIndexPath);
+ if(hs->szUrlPath != NULL)
+ CASC_FREE(hs->szUrlPath);
+
+ // Fre the blob arrays
+ QUERY_KEY_FreeArray(hs->pArchiveArray);
+ QUERY_KEY_FreeArray(hs->pPatchArchiveArray);
+ QUERY_KEY_FreeArray(hs->pEncodingKeys);
+
+ // Free the blobs
+ QUERY_KEY_Free(&hs->CdnConfigKey);
+ QUERY_KEY_Free(&hs->CdnBuildKey);
+ QUERY_KEY_Free(&hs->ArchiveGroup);
+ QUERY_KEY_Free(&hs->PatchArchiveGroup);
+ QUERY_KEY_Free(&hs->RootKey);
+ QUERY_KEY_Free(&hs->PatchKey);
+ QUERY_KEY_Free(&hs->DownloadKey);
+ QUERY_KEY_Free(&hs->InstallKey);
+
+ // Free the storage structure
+ hs->szClassName = NULL;
+ CASC_FREE(hs);
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage)
+{
+ TCascStorage * hs;
+ int nError = ERROR_SUCCESS;
+
+ CASCLIB_UNUSED(dwFlags);
+
+ // 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)
+ {
+ // Prepare the base storage parameters
+ memset(hs, 0, sizeof(TCascStorage));
+ hs->szClassName = "TCascStorage";
+ hs->dwRefCount = 1;
+ nError = InitializeCascDirectories(hs, szDataPath);
+ }
+
+ // Now we need to load the root file so we know the config files
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadBuildConfiguration(hs);
+ }
+
+ // Load the index files
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadIndexFiles(hs);
+ }
+
+ // Load the index files
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadEncodingFile(hs);
+ }
+
+ // Load the index files
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadRootFile(hs);
+ }
+
+#ifdef _DEBUG
+// if(nError == ERROR_SUCCESS)
+// {
+// CascDumpStorage("E:\\casc_dump.txt", hs, _T("e:\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"));
+// CascDumpIndexEntries("E:\\casc_index.txt", hs);
+// }
+#endif
+
+ // If something failed, free the storage and return
+ if(nError != ERROR_SUCCESS)
+ {
+ hs = FreeCascStorage(hs);
+ SetLastError(nError);
+ }
+
+ *phStorage = (HANDLE)hs;
+ return (nError == ERROR_SUCCESS);
+}
+
+bool WINAPI CascGetStorageInfo(
+ HANDLE hStorage,
+ CASC_STORAGE_INFO_CLASS InfoClass,
+ void * pvStorageInfo,
+ size_t cbStorageInfo,
+ size_t * pcbLengthNeeded)
+{
+ TCascStorage * hs;
+ DWORD dwCascFeatures = 0;
+
+ // Verify the storage handle
+ hs = IsValidStorageHandle(hStorage);
+ if(hs == NULL)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ // Differentiate between info classes
+ switch(InfoClass)
+ {
+ case CascStorageFileCount:
+
+ // Check the buffer size
+ if(cbStorageInfo < sizeof(DWORD))
+ {
+ *pcbLengthNeeded = sizeof(DWORD);
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return false;
+ }
+
+ // Give the number of files
+ *(PDWORD)pvStorageInfo = (DWORD)hs->pIndexEntryMap->ItemCount;
+ return true;
+
+ case CascStorageFeatures:
+
+ // Check the buffer size
+ if(cbStorageInfo < sizeof(DWORD))
+ {
+ *pcbLengthNeeded = sizeof(DWORD);
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return false;
+ }
+
+ // Construct the features
+ if(hs->pMndxInfo != NULL)
+ dwCascFeatures |= CASC_FEATURE_LISTFILE;
+
+ // Give the number of files
+ *(PDWORD)pvStorageInfo = dwCascFeatures;
+ return true;
+
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+}
+
+
+
+bool WINAPI CascCloseStorage(HANDLE hStorage)
+{
+ TCascStorage * hs;
+
+ // Verify the storage handle
+ hs = IsValidStorageHandle(hStorage);
+ if(hs == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // 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--;
+ return true;
+}
+