Dep/CascLib: Update to 5d3789af34

Closes #13866
This commit is contained in:
Nayd
2015-01-11 22:12:49 +00:00
parent 8bbd9133d5
commit 31e3dc2e75
18 changed files with 1162 additions and 820 deletions

View File

@@ -1,10 +1,6 @@
StormLib history
================
CascLib history
===============
Version 1.00
- Created

View File

@@ -99,7 +99,7 @@ static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
return hs->szIndexPath;
}
delete [] szIndexPath;
CASC_FREE(szIndexPath);
return NULL;
}
@@ -270,7 +270,7 @@ static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const
// Initialize the blob
pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1);
pVarBlob->cbData = (size_t)(szLinePtr - szLineBegin);
pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin);
// Check for success
if(pVarBlob->pbData == NULL)
@@ -826,7 +826,7 @@ static int LoadCdnBuildFile(TCascStorage * hs, PQUERY_KEY pFileBlob)
//-----------------------------------------------------------------------------
// Public functions
int LoadBuildConfiguration(TCascStorage * hs)
int LoadBuildInfo(TCascStorage * hs)
{
QUERY_KEY InfoFile = {NULL, 0};
QUERY_KEY FileData = {NULL, 0};

View File

@@ -23,9 +23,9 @@
#include "CascPort.h"
#include "common/Common.h"
#include "common/Map.h"
#include "common/FileStream.h"
#include "common/ListFile.h"
#include "common/Map.h"
// Headers from LibTomCrypt
#include "libtomcrypt/src/headers/tomcrypt.h"
@@ -48,6 +48,20 @@
#define CASC_SEARCH_HAVE_NAME 0x0001 // Indicated that previous search found a name
#define BLTE_HEADER_SIGNATURE 0x45544C42 // 'BLTE' header in the data files
#define BLTE_HEADER_DELTA 0x1E // Distance of BLTE header from begin of the header area
#define MAX_HEADER_AREA_SIZE 0x2A // Length of the file header area
// File header area in the data.xxx:
// BYTE HeaderHash[MD5_HASH_SIZE]; // MD5 of the frame array
// DWORD dwFileSize; // Size of the file (see comment before CascGetFileSize for details)
// BYTE SomeSize[4]; // Some size (big endian)
// BYTE Padding[6]; // Padding (?)
// DWORD dwSignature; // Must be "BLTE"
// BYTE HeaderSizeAsBytes[4]; // Header size in bytes (big endian)
// BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSizeAsBytes != 0
// BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSizeAsBytes != 0
// Prevent problems with CRT "min" and "max" functions,
// as they are not defined on all platforms
#define CASCLIB_MIN(a, b) ((a < b) ? a : b)
@@ -57,16 +71,46 @@
#define CASC_PACKAGE_BUFFER 0x1000
//-----------------------------------------------------------------------------
// Structures
// On-disk structures
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
{
DWORD EncodingKey[4]; // 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;
//-----------------------------------------------------------------------------
// In-memory structures
class TMndxFindResult;
struct TFileStream;
struct _MAR_FILE;
typedef struct _CASC_INDEX_ENTRY
{
BYTE IndexKey[CASC_FILE_KEY_SIZE]; // The first 9 bytes of the encoding key
BYTE FileOffset[5]; //
BYTE FileSize[4];
BYTE FileOffsetBE[5]; // Index of data file and offset within (big endian).
BYTE FileSizeLE[4]; // Size occupied in the storage file (data.###). See comment before CascGetFileSize for details
} CASC_INDEX_ENTRY, *PCASC_INDEX_ENTRY;
typedef struct _CASC_MAPPING_TABLE
@@ -113,7 +157,7 @@ typedef struct _CASC_ENCODING_HEADER
typedef struct _CASC_ENCODING_ENTRY
{
USHORT KeyCount; // Number of subitems
BYTE FileSizeBytes[4]; // File size as bytes (big-endian)
BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key
// Followed by the index keys
@@ -132,29 +176,35 @@ typedef struct _CASC_ROOT_LOCALE_BLOCK
} CASC_ROOT_LOCALE_BLOCK, *PCASC_ROOT_LOCALE_BLOCK;
typedef struct _CASC_ROOT_ENTRY
{
BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key (MD5)
ULONGLONG FileNameHash; // Jenkins hash of the file name
DWORD Locales; // File locales (see CASC_LOCALE_XXX)
DWORD Flags; // File flags
} CASC_ROOT_ENTRY, *PCASC_ROOT_ENTRY;
typedef struct _CASC_ROOT_KEY_INFO
{
BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file, obtained from root info
ULONGLONG FileSize; // Size of the file, in bytes
BYTE Flags; // File flags
} CASC_ROOT_KEY_INFO, *PCASC_ROOT_KEY_INFO;
typedef struct _CASC_MNDX_ENTRY
// Root file entry for CASC storages with MNDX root file (Heroes of the Storm)
// Corresponds to the in-file structure
typedef struct _CASC_ROOT_ENTRY_MNDX
{
DWORD Flags; // High 8 bits: Flags, low 24 bits: package index
BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file
DWORD FileSize; // Size of the file, in bytes
DWORD FileSize; // Uncompressed file size, in bytes
} CASC_MNDX_ENTRY, *PCASC_MNDX_ENTRY;
} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX;
// Root file entry for CASC storages without MNDX root file (World of Warcraft 6.0+)
// Does not match to the in-file structure of the root entry
typedef struct _CASC_ROOT_ENTRY
{
ULONGLONG FileNameHash; // Jenkins hash of the file name
DWORD SumValue; // Sum value
DWORD Locales; // Locale flags of the file
DWORD EncodingKey[4]; // File encoding key (MD5)
} CASC_ROOT_ENTRY, *PCASC_ROOT_ENTRY;
// Definition of the hash table for CASC root items
typedef struct _CASC_ROOT_HASH_TABLE
{
PCASC_ROOT_ENTRY TablePtr; // Pointer to the CASC root table
DWORD TableSize; // Total size of the root table
DWORD ItemCount; // Number of items currently in the table
} CASC_ROOT_HASH_TABLE, *PCASC_ROOT_HASH_TABLE;
typedef struct _CASC_MNDX_INFO
{
@@ -174,8 +224,8 @@ typedef struct _CASC_MNDX_INFO
struct _MAR_FILE * pMarFile1; // File name list for the packages
struct _MAR_FILE * pMarFile2; // File name list for names stripped of package names
struct _MAR_FILE * pMarFile3; // File name list for complete names
PCASC_MNDX_ENTRY pMndxEntries;
PCASC_MNDX_ENTRY * ppValidEntries;
PCASC_ROOT_ENTRY_MNDX pMndxEntries;
PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
} CASC_MNDX_INFO, *PCASC_MNDX_INFO;
@@ -208,6 +258,7 @@ typedef struct _TCascStorage
DWORD dwRefCount; // Number of references
DWORD dwGameInfo; // Game type
DWORD dwBuildNumber; // Game build number
DWORD dwFileBeginDelta; // This is number of bytes to shift back from archive offset (from index entry) to actual begin of file data
QUERY_KEY CdnConfigKey;
QUERY_KEY CdnBuildKey;
@@ -239,9 +290,7 @@ typedef struct _TCascStorage
PCASC_ENCODING_ENTRY * ppEncodingEntries; // Map of encoding entries
size_t nEncodingEntries;
PCASC_ROOT_ENTRY * ppRootEntries; // Sorted array of root entries
PCASC_ROOT_ENTRY pRootEntries; // Linear array of root entries
size_t nRootEntries; // Number of root entries
CASC_ROOT_HASH_TABLE RootTable; // Hash table for the root entries
PCASC_MNDX_INFO pMndxInfo; // Used for storages which have MNDX/MAR file
PCASC_PACKAGES pPackages; // Linear list of present packages
@@ -258,9 +307,11 @@ typedef struct _TCascFile
DWORD ArchiveIndex; // Index of the archive (data.###)
DWORD HeaderOffset; // Offset of the BLTE header, relative to the begin of the archive
DWORD HeaderSize; // Length of the BLTE header
DWORD FramesOffset; // Offset of the frame data, relative to the begin of the archive
DWORD CompressedSize; // Compressed size of the file (in bytes)
DWORD FileSize; // Size of file, in bytes
BYTE FrameArrayHash[MD5_HASH_SIZE]; // MD5 hash of the frame array
PCASC_FILE_FRAME pFrames; // Array of file frames
DWORD FrameCount; // Number of the file frames
@@ -270,9 +321,15 @@ typedef struct _TCascFile
DWORD CacheStart; // Starting offset in the cache
DWORD CacheEnd; // Ending offset in the cache
} TCascFile;
#ifdef CASCLIB_TEST // Extra fields for analyzing the file size problem
DWORD FileSize_RootEntry; // File size, from the root entry
DWORD FileSize_EncEntry; // File size, from the encoding entry
DWORD FileSize_IdxEntry; // File size, from the index entry
DWORD FileSize_HdrArea; // File size, as stated in the file header area
DWORD FileSize_FrameSum; // File size as sum of frame sizes
#endif
class TMndxFindResult;
} TCascFile;
typedef struct _TCascSearch
{
@@ -284,8 +341,7 @@ typedef struct _TCascSearch
char * szMask;
char szFileName[MAX_PATH]; // Buffer for the file name
char szNormName[MAX_PATH]; // Buffer for normalized file name
ULONGLONG FileNameHash; // Name hash being searched right now
size_t RootIndex; // Root index of the previously found item
DWORD RootIndex; // Root index of the previously found item
DWORD dwState; // Pointer to the state (0 = listfile, 1 = nameless, 2 = done)
BYTE BitArray[1]; // Bit array of already-reported files
@@ -305,17 +361,10 @@ typedef struct _TCascSearch
//
#if defined(_MSC_VER) && defined(_DEBUG)
/*
void * DbgRealloc(void * ptr, size_t nSize);
#define CASC_REALLOC(type, ptr, count) (type *)DbgRealloc(ptr, ((count) * sizeof(type)))
#define CASC_REALLOC(type, ptr, count) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((count) * sizeof(type)))
#define CASC_ALLOC(type, count) (type *)HeapAlloc(GetProcessHeap(), 0, ((count) * sizeof(type)))
#define CASC_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
*/
#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type))
#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type))
#define CASC_FREE(ptr) free(ptr)
#else
@@ -336,7 +385,7 @@ ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes);
//-----------------------------------------------------------------------------
// Build configuration reading
int LoadBuildConfiguration(TCascStorage * hs);
int LoadBuildInfo(TCascStorage * hs);
//-----------------------------------------------------------------------------
// Internal file functions
@@ -344,31 +393,23 @@ int LoadBuildConfiguration(TCascStorage * hs);
TCascStorage * IsValidStorageHandle(HANDLE hStorage);
TCascFile * IsValidFileHandle(HANDLE hFile);
PCASC_ROOT_ENTRY FindFirstRootEntry(TCascStorage * hs, const char * szFileName, size_t * PtrIndex);
PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex);
PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex);
PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey);
int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer);
//-----------------------------------------------------------------------------
// Dump data
// Dumping CASC data structures
#ifdef _DEBUG
void CascDumpSparseArray(const char * szFileName, void * pvSparseArray);
void CascDumpNameFragTable(const char * szFileName, void * pvMarFile);
void CascDumpFileNames(const char * szFileName, void * pvMarFile);
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo);
void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs);
void CascDumpStorage(const char * szFileName, TCascStorage * hs, const TCHAR * szListFile);
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo);
void CascDumpRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, const char * szFormat, const TCHAR * szListFile, int nDumpLevel);
void CascDumpFile(const char * szFileName, HANDLE hFile);
#else // _DEBUG
#define CascDumpSparseArray(n,a) /* */
#define CascDumpNameFragTable(n, m) /* */
#define CascDumpFileNames(n, m) /* */
#define CascDumpMndxRoot(n,i) /* */
#define CascDumpIndexEntries(n,h) /* */
#define CascDumpStorage(n,h) /* */
#define CascDumpFile(n,h) /* */
#endif // _DEBUG
#endif // __CASCCOMMON_H__

View File

@@ -16,24 +16,19 @@
#ifdef _DEBUG // The entire file is only valid for debug purposes
//-----------------------------------------------------------------------------
// Local functions
// Forward definitions
static char * StringFromIndexKey(LPBYTE md5, char * szBuffer)
{
return StringFromBinary(md5, 9, szBuffer);
}
LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd);
static char * StringFromMD5(LPBYTE md5, char * szBuffer)
{
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
}
//-----------------------------------------------------------------------------
// Sort compare functions
static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1, const void * pvIndexEntry2)
{
PCASC_INDEX_ENTRY pIndexEntry1 = (PCASC_INDEX_ENTRY)pvIndexEntry1;
PCASC_INDEX_ENTRY pIndexEntry2 = (PCASC_INDEX_ENTRY)pvIndexEntry2;
ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffset);
ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffset);
ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffsetBE);
ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffsetBE);
DWORD ArchIndex1 = (DWORD)(FileOffset1 >> 0x1E);
DWORD ArchIndex2 = (DWORD)(FileOffset2 >> 0x1E);
@@ -54,98 +49,121 @@ static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1,
return 0;
}
//-----------------------------------------------------------------------------
// Local functions
static char ** CreateFileNameArray(TCascStorage * hs, const TCHAR * szListFile)
static char * StringFromMD5(LPBYTE md5, char * szBuffer)
{
PCASC_ROOT_ENTRY pRootEntry;
char ** FileNameArray = NULL;
void * pvListFile;
size_t nRootIndex;
char szFileName1[MAX_PATH+1];
char szFileName2[MAX_PATH+1];
// Open the listfile stream and initialize the listfile cache
pvListFile = ListFile_OpenExternal(szListFile);
if(pvListFile != NULL)
{
// Allocate the array of file names
FileNameArray = CASC_ALLOC(char*, hs->nRootEntries);
if(FileNameArray != NULL)
{
// Zero the name array
memset(FileNameArray, 0, hs->nRootEntries * sizeof(char *));
// Perform search
while(ListFile_GetNext(pvListFile, "*", szFileName1, MAX_PATH))
{
// Create normalized name
strcpy(szFileName2, szFileName1);
NormalizeFileName_UpperBkSlash(szFileName2);
// Try to find the root entry
pRootEntry = FindFirstRootEntry(hs, szFileName2, &nRootIndex);
if(pRootEntry != NULL)
{
assert(nRootIndex < hs->nRootEntries);
if(FileNameArray[nRootIndex] == NULL)
FileNameArray[nRootIndex] = NewStr(szFileName1, 0);
}
}
}
// Close the listfile cache
ListFile_Free(pvListFile);
}
return FileNameArray;
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
}
static void FreeFileNameArray(TCascStorage * hs, char ** FileNameArray)
static char * FormatFileName(const char * szFormat, TCascStorage * hs)
{
if(FileNameArray != NULL)
char * szFileName;
char * szSrc;
char * szTrg;
// Create copy of the file name
szFileName = szSrc = szTrg = NewStr(szFormat, 0);
if(szFileName != NULL)
{
// Free all sub-entries
for(size_t i = 0; i < hs->nRootEntries; i++)
// Format the file name
while(szSrc[0] != 0)
{
if(FileNameArray[i] != NULL)
CASC_FREE(FileNameArray[i]);
if(szSrc[0] == '%')
{
// Replace "%build%" with a build number
if(!strncmp(szSrc, "%build%", 7))
{
szTrg += sprintf(szTrg, "%u", hs->dwBuildNumber);
szSrc += 7;
continue;
}
}
// Just copy the character
*szTrg++ = *szSrc++;
}
// Free the array itself
CASC_FREE(FileNameArray);
// Terminate the target file name
szTrg[0] = 0;
}
return szFileName;
}
FILE * CreateDumpFile(const char * szFormat, TCascStorage * hs)
{
FILE * fp = NULL;
char * szFileName;
// Validate the storage handle
if(hs != NULL)
{
// Format the real file name
szFileName = FormatFileName(szFormat, hs);
if(szFileName != NULL)
{
// Create the dump file
fp = fopen(szFileName, "wt");
CASC_FREE(szFileName);
}
}
return fp;
}
static void DumpIndexKey(
FILE * fp,
TCascStorage * hs,
LPBYTE pbEncodingKey,
PCASC_INDEX_ENTRY * ppIndexEntries)
LPBYTE pbIndexKey,
int nDumpLevel)
{
PCASC_INDEX_ENTRY pIndexEntry;
TCascFile * hf;
QUERY_KEY QueryKey;
size_t EntryIndex = 0;
char szMd5[MD5_STRING_SIZE];
HANDLE hFile;
BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
char szBuffer[0x20];
QueryKey.pbData = pbEncodingKey;
QueryKey.pbData = pbIndexKey;
QueryKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(hs, &QueryKey);
if(pIndexEntry != NULL)
{
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset);
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E);
DWORD FileSize = *(PDWORD)pIndexEntry->FileSize;
// Mark the index entry as dumped
ppIndexEntries[EntryIndex] = NULL;
DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
// Mask the file offset
FileOffset &= 0x3FFFFFFF;
fprintf(fp, " Index: %s, ArchIdx: %02x FileOffset %08x FileSize: %lx\n",
StringFromIndexKey(pIndexEntry->IndexKey, szMd5),
fprintf(fp, " data.%03u at 0x%08x (0x%lx bytes)\n",
ArchIndex,
(DWORD)FileOffset,
FileSize);
if(nDumpLevel > 2)
{
QueryKey.pbData = pIndexEntry->IndexKey;
QueryKey.cbData = MD5_HASH_SIZE;
if(CascOpenFileByIndexKey((HANDLE)hs, &QueryKey, 0, &hFile))
{
// Make sure that the data file is open and frame header loaded
CascGetFileSize(hFile, NULL);
hf = IsValidFileHandle(hFile);
assert(hf->pStream != NULL);
// Read the header area
FileOffset = hf->HeaderOffset - BLTE_HEADER_DELTA;
FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea));
CascCloseFile(hFile);
// Dump the header area
fprintf(fp, " FileSize: %X Rest: %s\n",
ConvertBytesToInteger_4_LE(&HeaderArea[0x10]),
StringFromBinary(&HeaderArea[0x14], 10, szBuffer));
}
}
}
else
{
@@ -157,122 +175,31 @@ static void DumpEncodingEntry(
FILE * fp,
TCascStorage * hs,
PCASC_ENCODING_ENTRY pEncodingEntry,
PCASC_INDEX_ENTRY * ppIndexEntries)
int nDumpLevel)
{
LPBYTE pbEncodingKey;
LPBYTE pbIndexKey;
char szMd5[MD5_STRING_SIZE];
fprintf(fp, " Encoding Key: %s Key Count: %u Size: %lx\n",
StringFromMD5(pEncodingEntry->EncodingKey, szMd5),
pEncodingEntry->KeyCount,
ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes));
// If the encoding key exists
if(pEncodingEntry != NULL)
{
fprintf(fp, " Size %lx Key Count: %u\n",
ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE),
pEncodingEntry->KeyCount);
// If there is a file key
if(pEncodingEntry->KeyCount != 0)
{
// Get the first encoding key
pbEncodingKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
// Dump all encoding keys
// Dump all index keys
pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++)
{
fprintf(fp, " Index Key: %s\n", StringFromMD5(pbEncodingKey, szMd5));
DumpIndexKey(fp, hs, pbEncodingKey, ppIndexEntries);
pbEncodingKey += MD5_HASH_SIZE;
fprintf(fp, " %s\n", StringFromMD5(pbIndexKey, szMd5));
DumpIndexKey(fp, hs, pbIndexKey, nDumpLevel);
pbIndexKey += MD5_HASH_SIZE;
}
}
else
{
fprintf(fp, " ZERO FILE KEYS\n");
return;
}
}
static void DumpEncodingEntry(
FILE * fp,
TCascStorage * hs,
PCASC_ROOT_ENTRY pRootEntry,
PCASC_ENCODING_ENTRY * ppEncodingKeys,
PCASC_INDEX_ENTRY * ppIndexEntries)
{
PCASC_ENCODING_ENTRY pEncodingKey;
QUERY_KEY QueryKey;
size_t EntryIndex = 0;
// Find the encoding key
QueryKey.pbData = pRootEntry->EncodingKey;
QueryKey.cbData = MD5_HASH_SIZE;
pEncodingKey = FindEncodingEntry(hs, &QueryKey, &EntryIndex);
if(pEncodingKey == NULL)
{
fprintf(fp, " NO ENCODING KEY\n");
return;
}
// Get the file key, clear the encoding key
ppEncodingKeys[EntryIndex] = NULL;
DumpEncodingEntry(fp, hs, pEncodingKey, ppIndexEntries);
}
static void DumpRootEntries(FILE * fp, TCascStorage * hs, char ** FileNameArray)
{
PCASC_ENCODING_ENTRY * ppEncodingEntries; // Array of encoding entries
PCASC_INDEX_ENTRY * ppIndexEntries; // Complete list of key entries for all files
const char * szFileName = NULL;
ULONGLONG PrevNameHash = (ULONGLONG)-1;
char szMd5[MD5_STRING_SIZE];
// Create copy of the encoding keys and file keys
ppEncodingEntries = CASC_ALLOC(PCASC_ENCODING_ENTRY, hs->nEncodingEntries);
ppIndexEntries = CASC_ALLOC(PCASC_INDEX_ENTRY, hs->pIndexEntryMap->ItemCount);
if(ppEncodingEntries && ppIndexEntries)
{
// Copy all pointers
memcpy(ppEncodingEntries, hs->ppEncodingEntries, hs->nEncodingEntries * sizeof(PCASC_ENCODING_ENTRY));
Map_EnumObjects(hs->pIndexEntryMap, (void **)ppIndexEntries);
// Parse all entries
for(size_t i = 0; i < hs->nRootEntries; i++)
{
PCASC_ROOT_ENTRY pRootEntry = hs->ppRootEntries[i];
const char * szDuplicate = "";
// Check duplicates
if(pRootEntry->FileNameHash != PrevNameHash)
szFileName = FileNameArray[i];
else
szDuplicate = "(DUPLICATE) ";
// Dump the root entry
fprintf(fp, "NameHash: %016llx Locales: %08lx MD5: %s %sFileName: %s\n",
pRootEntry->FileNameHash,
pRootEntry->Locales,
StringFromMD5(pRootEntry->EncodingKey, szMd5),
szDuplicate,
szFileName);
DumpEncodingEntry(fp, hs, pRootEntry, ppEncodingEntries, ppIndexEntries);
PrevNameHash = pRootEntry->FileNameHash;
fprintf(fp, "\n");
}
// Dump all orphaned encoding keys
for(size_t i = 0; i < hs->nEncodingEntries; i++)
{
if(ppEncodingEntries[i] != NULL)
{
fprintf(fp, "[NO ROOT KEY]\n");
DumpEncodingEntry(fp, hs, ppEncodingEntries[i], ppIndexEntries);
ppEncodingEntries[i] = NULL;
fprintf(fp, "\n");
}
}
CASC_FREE(ppIndexEntries);
CASC_FREE(ppEncodingEntries);
fprintf(fp, " NO ENCODING KEYS\n");
}
}
@@ -398,7 +325,7 @@ void CascDumpFileNames(const char * szFileName, void * pvMarFile)
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo)
{
PCASC_MNDX_ENTRY pMndxEntry;
PCASC_ROOT_ENTRY_MNDX pRootEntry;
FILE * fp;
char szMd5[MD5_STRING_SIZE];
@@ -409,12 +336,12 @@ void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo)
fprintf(fp, "Indx Fl+Asset EncodingKey FileSize\n==== ======== ================================ ========\n");
for(DWORD i = 0; i < pMndxInfo->MndxEntriesValid; i++)
{
pMndxEntry = pMndxInfo->ppValidEntries[i];
pRootEntry = pMndxInfo->ppValidEntries[i];
fprintf(fp, "%04X %08X %s %08X\n", i,
pMndxEntry->Flags,
StringFromMD5(pMndxEntry->EncodingKey, szMd5),
pMndxEntry->FileSize);
pRootEntry->Flags,
StringFromMD5(pRootEntry->EncodingKey, szMd5),
pRootEntry->FileSize);
}
fclose(fp);
}
@@ -446,11 +373,11 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
for(size_t i = 0; i < nIndexEntries; i++)
{
PCASC_INDEX_ENTRY pIndexEntry = ppIndexEntries[i];
ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset);
ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
DWORD ArchIndex = (DWORD)(ArchOffset >> 0x1E);
DWORD FileSize;
FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSize);
FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
ArchOffset &= 0x3FFFFFFF;
fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey));
@@ -463,33 +390,81 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
}
}
void CascDumpStorage(const char * szFileName, TCascStorage * hs, const TCHAR * szListFile)
void CascDumpRootFile(
TCascStorage * hs,
LPBYTE pbRootFile,
DWORD cbRootFile,
const char * szFormat,
const TCHAR * szListFile,
int nDumpLevel)
{
char ** FileNameArray = NULL;
PCASC_ENCODING_ENTRY pEncodingEntry;
ROOT_BLOCK_INFO BlockInfo;
PLISTFILE_MAP pListMap;
QUERY_KEY EncodingKey;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
LPBYTE pbFilePointer;
FILE * fp;
char szOneLine[0x100];
DWORD i;
// Validate the storage handle
if(hs != NULL)
// This function only dumps WoW-style root file
assert(*(PDWORD)pbRootFile != CASC_MNDX_SIGNATURE);
// Create the dump file
fp = CreateDumpFile(szFormat, hs);
if(fp != NULL)
{
// Create the dump file
fp = fopen(szFileName, "wt");
if(fp != NULL)
// Create the listfile map
// DWORD dwTickCount = GetTickCount();
pListMap = ListFile_CreateMap(szListFile);
// dwTickCount = GetTickCount() - dwTickCount;
// Dump the root entries as-is
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
{
// If we also have listfile, open it
if(szListFile != NULL)
FileNameArray = CreateFileNameArray(hs, szListFile);
// Validate the root block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// Dump all root keys
fprintf(fp, "Root Entries\n=========\n\n");
DumpRootEntries(fp, hs, FileNameArray);
// Dump the locale block
fprintf(fp, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
"=========================================================\n",
BlockInfo.pLocaleBlockHdr->Flags,
BlockInfo.pLocaleBlockHdr->Locales,
BlockInfo.pLocaleBlockHdr->NumberOfFiles);
FreeFileNameArray(hs, FileNameArray);
fclose(fp);
// Dump the hashes and encoding keys
for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
{
// Dump the entry
fprintf(fp, "%08X %08X-%08X %s %s\n",
(DWORD)(BlockInfo.pInt32Array[i]),
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20),
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash),
StringFromMD5((LPBYTE)BlockInfo.pRootEntries[i].EncodingKey, szOneLine),
ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash));
// Find the encoding entry in the encoding table
if(nDumpLevel > 1)
{
EncodingKey.pbData = (LPBYTE)BlockInfo.pRootEntries[i].EncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL);
DumpEncodingEntry(fp, hs, pEncodingEntry, nDumpLevel);
}
}
// Put extra newline
fprintf(fp, "\n");
}
ListFile_FreeMap(pListMap);
fclose(fp);
}
}
void CascDumpFile(const char * szFileName, HANDLE hFile)
{
FILE * fp;

View File

@@ -72,7 +72,7 @@ static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry,
// dwRootEntries++;
// Now try to find that encoding key in the array of encoding keys
QueryKey.pbData = pRootEntry->EncodingKey;
QueryKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
QueryKey.cbData = MD5_HASH_SIZE;
pEncodingEntry = FindEncodingEntry(hs, &QueryKey, NULL);
if(pEncodingEntry == NULL)
@@ -94,7 +94,9 @@ static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry,
pFindData->FileNameHash = pRootEntry->FileNameHash;
pFindData->dwPackageIndex = 0;
pFindData->dwLocaleFlags = pRootEntry->Locales;
pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes);
// Fill-in the file size
pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
return true;
}
@@ -104,7 +106,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
size_t cbToAllocate;
// When using the MNDX info, do not allocate the extra bit array
cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->nRootEntries / 8) : 0);
cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->RootTable.TableSize / 8) : 0);
pSearch = (TCascSearch *)CASC_ALLOC(BYTE, cbToAllocate);
if(pSearch != NULL)
{
@@ -147,53 +149,28 @@ static bool DoStorageSearch_ListFile(TCascSearch * pSearch, PCASC_FIND_DATA pFin
{
PCASC_ROOT_ENTRY pRootEntry;
TCascStorage * hs = pSearch->hs;
size_t RootIndex;
DWORD TableIndex = 0;
for(;;)
// Get next file from the listfile
while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
{
// Shall we get a new file name from the listfile?
if(pSearch->FileNameHash == 0)
#ifdef _DEBUG
//if(!_stricmp(pSearch->szFileName, "Character\\NightElf\\Female\\NightElf_FemaleFacialLowerHair01_02_HD.blp"))
// DebugBreak();
#endif
// Find the root entry
pRootEntry = FindRootEntry(hs, pSearch->szFileName, &TableIndex);
if(pRootEntry != NULL)
{
// Try to get next file from the listfile
if(!ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
break;
// if(!_stricmp(pSearch->szFileName, "Interface\\Glues\\MODELS\\UI_MainMenu_Warlords\\Caustic_MedFreq.blp"))
// DebugBreak();
// Normalize the file name
strcpy(pSearch->szNormName, pSearch->szFileName);
NormalizeFileName_UpperBkSlash(pSearch->szNormName);
// Find the first root entry belonging to this file name
pRootEntry = FindFirstRootEntry(pSearch->hs, pSearch->szNormName, &RootIndex);
if(pRootEntry == NULL)
continue;
// We have the name now, search all locales
pSearch->FileNameHash = pRootEntry->FileNameHash;
pSearch->RootIndex = RootIndex;
}
// Is the root index in range?
while(pSearch->RootIndex < hs->nRootEntries)
{
// Get the next root entry. If name mismatches, stop searching
pRootEntry = hs->ppRootEntries[pSearch->RootIndex];
if(pRootEntry->FileNameHash != pSearch->FileNameHash)
break;
// Verify whether the file exists in the storage
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex++))
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, TableIndex))
{
strcpy(pFindData->szFileName, pSearch->szFileName);
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
return true;
}
}
// Reset the name hash and root index and retry search
pSearch->FileNameHash = 0;
}
// Listfile search ended
@@ -206,13 +183,13 @@ static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindDat
TCascStorage * hs = pSearch->hs;
// Check if there is more files with the same name hash
while(pSearch->RootIndex < hs->nRootEntries)
while(pSearch->RootIndex < hs->RootTable.TableSize)
{
// Get the pointer to the root entry
pRootEntry = hs->ppRootEntries[pSearch->RootIndex];
pRootEntry = hs->RootTable.TablePtr + pSearch->RootIndex;
// Verify if that root entry exists in the CASC storage
// Note that the file name will be there from the previous search
// and was not found before
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex))
{
pFindData->szFileName[0] = 0;
@@ -242,7 +219,6 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile);
// Move the search phase to the listfile searching
pSearch->FileNameHash = 0;
pSearch->RootIndex = 0;
pSearch->dwState++;
@@ -259,7 +235,7 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
return true;
// Move to the nameless search state
pSearch->RootIndex = 0;
assert(pSearch->RootIndex == 0);
pSearch->dwState++;
}

View File

@@ -66,7 +66,7 @@ extern "C" {
#define CASC_LOCALE_UNKNOWN1 0x00000001
#define CASC_LOCALE_ENUS 0x00000002
#define CASC_LOCALE_KOKR 0x00000004
#define CASC_LOCALE_UNKNOWN8 0x00000008
#define CASC_LOCALE_RESERVED 0x00000008
#define CASC_LOCALE_FRFR 0x00000010
#define CASC_LOCALE_DEDE 0x00000020
#define CASC_LOCALE_ZHCN 0x00000040
@@ -81,6 +81,24 @@ extern "C" {
#define CASC_LOCALE_ITIT 0x00008000
#define CASC_LOCALE_PTPT 0x00010000
#define CASC_LOCALE_BIT_ENUS 0x01
#define CASC_LOCALE_BIT_KOKR 0x02
#define CASC_LOCALE_DUAL_LANG 0x03
#define CASC_LOCALE_BIT_FRFR 0x04
#define CASC_LOCALE_BIT_DEDE 0x05
#define CASC_LOCALE_BIT_ZHCN 0x06
#define CASC_LOCALE_BIT_ESES 0x07
#define CASC_LOCALE_BIT_ZHTW 0x08
#define CASC_LOCALE_BIT_ENGB 0x09
#define CASC_LOCALE_BIT_ENCN 0x0A
#define CASC_LOCALE_BIT_ENTW 0x0B
#define CASC_LOCALE_BIT_ESMX 0x0C
#define CASC_LOCALE_BIT_RURU 0x0D
#define CASC_LOCALE_BIT_PTBR 0x0E
#define CASC_LOCALE_BIT_ITIT 0x0F
#define CASC_LOCALE_BIT_PTPT 0x10
#define MAX_CASC_KEY_LENGTH 0x10 // Maximum length of the key (equal to MD5 hash)
#ifndef MD5_HASH_SIZE
@@ -110,6 +128,8 @@ typedef enum _CASC_STORAGE_INFO_CLASS
{
CascStorageFileCount,
CascStorageFeatures,
CascStorageGameInfo,
CascStorageGameBuild,
CascStorageInfoClassMax
} CASC_STORAGE_INFO_CLASS, *PCASC_STORAGE_INFO_CLASS;
@@ -148,7 +168,7 @@ void qsort_pointer_array(void ** base, size_t num, int (*compare)(const void *,
//-----------------------------------------------------------------------------
// Functions for storage manipulation
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage);
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage);
bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded);
bool WINAPI CascCloseStorage(HANDLE hStorage);

View File

@@ -202,6 +202,11 @@ DWORD GetNumberOfSetBits(DWORD Value32)
static bool RootFileRead(LPBYTE pbFilePointer, LPBYTE pbFileEnd, void * pvBuffer, size_t dwBytesToRead)
{
// False if the file pointer is beyond the end
if(pbFilePointer > pbFileEnd)
return false;
// False if there is not enough bytes available
if((size_t)(pbFileEnd - pbFilePointer) < dwBytesToRead)
return false;
@@ -2887,7 +2892,7 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndxFindResult * pStruct1C)
{
CASC_ROOT_KEY_INFO RootKeyInfo;
PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL;
TCascStorage * hs = pSearch->hs;
PCASC_PACKAGE pPackage;
char * szStrippedName;
@@ -2899,6 +2904,7 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx
// Fill the file name
memcpy(pFindData->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath);
pFindData->szFileName[pStruct1C->cchFoundPath] = 0;
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
pFindData->dwFileSize = CASC_INVALID_SIZE;
// Fill the file size
@@ -2910,10 +2916,10 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx
while(szStrippedName[0] == '/')
szStrippedName++;
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &RootKeyInfo);
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntry);
if(nError == ERROR_SUCCESS)
{
pFindData->dwFileSize = (DWORD)RootKeyInfo.FileSize;
pFindData->dwFileSize = pRootEntry->FileSize;
}
}
return true;
@@ -3014,7 +3020,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
if(pMndxInfo->pMarFile1 == NULL || pMndxInfo->pMarFile2 == NULL || pMndxInfo->pMarFile3 == NULL)
nError = ERROR_BAD_FORMAT;
if(pMndxInfo->MndxEntrySize != sizeof(CASC_MNDX_ENTRY))
if(pMndxInfo->MndxEntrySize != sizeof(CASC_ROOT_ENTRY_MNDX))
nError = ERROR_BAD_FORMAT;
}
@@ -3028,7 +3034,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(nError == ERROR_SUCCESS && FileNameCount == pMndxInfo->MndxEntriesValid)
{
cbToAllocate = pMndxInfo->MndxEntriesTotal * pMndxInfo->MndxEntrySize;
pMndxInfo->pMndxEntries = (PCASC_MNDX_ENTRY)CASC_ALLOC(BYTE, cbToAllocate);
pMndxInfo->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate);
if(pMndxInfo->pMndxEntries != NULL)
{
if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pMndxInfo->pMndxEntries, cbToAllocate))
@@ -3045,10 +3051,10 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(nError == ERROR_SUCCESS)
{
assert(pMndxInfo->MndxEntriesValid <= pMndxInfo->MndxEntriesTotal);
pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_MNDX_ENTRY, pMndxInfo->MndxEntriesValid + 1);
pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1);
if(pMndxInfo->ppValidEntries != NULL)
{
PCASC_MNDX_ENTRY pMndxEntry = pMndxInfo->pMndxEntries;
PCASC_ROOT_ENTRY_MNDX pRootEntry = pMndxInfo->pMndxEntries;
DWORD ValidEntryCount = 1; // edx
DWORD nIndex1 = 0;
@@ -3056,14 +3062,14 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
pMndxInfo->ppValidEntries[nIndex1++] = pMndxInfo->pMndxEntries;
// Put the remaining entries
for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pMndxEntry++)
for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pRootEntry++)
{
if(ValidEntryCount > pMndxInfo->MndxEntriesValid)
break;
if(pMndxEntry->Flags & 0x80000000)
if(pRootEntry->Flags & 0x80000000)
{
pMndxInfo->ppValidEntries[nIndex1++] = pMndxEntry + 1;
pMndxInfo->ppValidEntries[nIndex1++] = pRootEntry + 1;
ValidEntryCount++;
}
}
@@ -3106,9 +3112,9 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
return nError;
}
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_KEY_INFO pFoundInfo)
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry)
{
PCASC_MNDX_ENTRY pMndxEntry;
PCASC_ROOT_ENTRY_MNDX pRootEntry;
TMndxFindResult Struct1C;
// Search the database for the file name
@@ -3124,20 +3130,19 @@ int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwP
if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid)
{
// HOTS: E945F4
pMndxEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex];
while((pMndxEntry->Flags & 0x00FFFFFF) != dwPackage)
pRootEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex];
while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage)
{
// The highest bit serves as a terminator if set
if(pMndxEntry->Flags & 0x80000000)
if(pRootEntry->Flags & 0x80000000)
return ERROR_FILE_NOT_FOUND;
pMndxEntry++;
pRootEntry++;
}
// Fill the root info
memcpy(pFoundInfo->EncodingKey, pMndxEntry->EncodingKey, MD5_HASH_SIZE);
pFoundInfo->FileSize = pMndxEntry->FileSize;
pFoundInfo->Flags = (BYTE)((pMndxEntry->Flags >> 0x18) & 0x3F);
// Give the root entry pointer to the caller
if(ppRootEntry != NULL)
ppRootEntry[0] = pRootEntry;
return ERROR_SUCCESS;
}
}
@@ -3213,14 +3218,18 @@ extern "C" {
extern "C" void * allocate_zeroed_memory_x86(size_t bytes)
{
return calloc(bytes, 1);
void * ptr = CASC_ALLOC(BYTE, bytes);
if(ptr != NULL)
memset(ptr, 0, bytes);
return ptr;
}
extern "C" void free_memory_x86(void * ptr)
{
if(ptr != NULL)
{
free(ptr);
CASC_FREE(ptr);
}
}

View File

@@ -358,7 +358,7 @@ inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex)
int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName);
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_KEY_INFO pFoundInfo);
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppFoundInfo);
bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData);
void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo);

View File

@@ -69,115 +69,47 @@ PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKe
}
// Also used in CascSearchFile
PCASC_ROOT_ENTRY FindFirstRootEntry(TCascStorage * hs, const char * szFileName, size_t * PtrIndex)
PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex)
{
PCASC_ROOT_ENTRY pFoundEntry = NULL;
PCASC_ROOT_ENTRY pRootEntry;
ULONGLONG FileNameHash;
DWORD TableIndex;
uint32_t dwHashHigh = 0;
uint32_t dwHashLow = 0;
size_t StartEntry = 0;
size_t MidlEntry = 0;
size_t EndEntry = hs->nRootEntries;
// Calculate the HASH value of the normalized file name
hashlittle2(szFileName, strlen(szFileName), &dwHashHigh, &dwHashLow);
FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
// Perform binary search
while(StartEntry < EndEntry)
{
// Calculate the middle of the interval
MidlEntry = StartEntry + ((EndEntry - StartEntry) / 2);
pRootEntry = hs->ppRootEntries[MidlEntry];
// Get the first table index
TableIndex = (DWORD)(FileNameHash & (hs->RootTable.TableSize - 1));
assert(hs->RootTable.ItemCount < hs->RootTable.TableSize);
// Did we find it?
// Search the proper entry
for(;;)
{
// Does the has match?
pRootEntry = hs->RootTable.TablePtr + TableIndex;
if(pRootEntry->FileNameHash == FileNameHash)
{
pFoundEntry = pRootEntry;
break;
if(PtrTableIndex != NULL)
PtrTableIndex[0] = TableIndex;
return pRootEntry;
}
// Move the interval to the left or right
(FileNameHash < pRootEntry->FileNameHash) ? EndEntry = MidlEntry : StartEntry = MidlEntry + 1;
// If the entry is free, the file is not there
if(pRootEntry->FileNameHash == 0 && pRootEntry->SumValue == 0)
return NULL;
// Move to the next entry
TableIndex = (DWORD)((TableIndex + 1) & (hs->RootTable.TableSize - 1));
}
// Move the pointer back to the first entry with that hash
if(pFoundEntry != NULL)
{
while(MidlEntry > 0 && hs->ppRootEntries[MidlEntry - 1]->FileNameHash == FileNameHash)
{
pFoundEntry = hs->ppRootEntries[MidlEntry - 1];
MidlEntry--;
}
}
// Return what we found
if(PtrIndex != NULL)
*PtrIndex = MidlEntry;
return pFoundEntry;
}
// Check the root directory for that hash
PCASC_ROOT_ENTRY FindRootEntryLocale(TCascStorage * hs, char * szFileName, DWORD Locale)
{
PCASC_ROOT_ENTRY pThatEntry = NULL;
PCASC_ROOT_ENTRY pENUSEntry = NULL;
PCASC_ROOT_ENTRY pENGBEntry = NULL;
PCASC_ROOT_ENTRY pAnyEntry = NULL;
PCASC_ROOT_ENTRY pEndEntry = NULL;
PCASC_ROOT_ENTRY pRootEntry = NULL;
ULONGLONG FileNameHash;
size_t EntryIndex = 0;
size_t EndEntry = hs->nRootEntries;
// Find a root entry with the given name hash
pRootEntry = FindFirstRootEntry(hs, szFileName, &EntryIndex);
if(pRootEntry != NULL)
{
// Rememeber the file name hash
pEndEntry = hs->pRootEntries + hs->nRootEntries;
FileNameHash = pRootEntry->FileNameHash;
// Find all suitable root entries
while(EntryIndex < EndEntry)
{
// Get the root entry
pRootEntry = hs->ppRootEntries[EntryIndex++];
if(pRootEntry->FileNameHash != FileNameHash)
break;
// If a locale has been given, check it
if(pThatEntry == NULL && Locale != 0 && (Locale & pRootEntry->Locales))
pThatEntry = pRootEntry;
if(pENUSEntry == NULL && (pRootEntry->Locales & CASC_LOCALE_ENUS))
pENUSEntry = pRootEntry;
if(pENGBEntry == NULL && (pRootEntry->Locales & CASC_LOCALE_ENGB))
pENGBEntry = pRootEntry;
if(pAnyEntry == NULL)
pAnyEntry = pRootEntry;
// Move to the next one
pRootEntry++;
}
// Return the key by priority
if(pThatEntry != NULL)
return pThatEntry;
if(pENGBEntry != NULL)
return pENGBEntry;
if(pENUSEntry != NULL)
return pENUSEntry;
}
// Return whatever we got
return pAnyEntry;
}
static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexEntry)
{
ULONGLONG FileOffsMask = ((ULONGLONG)1 << hs->KeyMapping[0].SegmentBits) - 1;
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset);
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
TCascFile * hf;
// Allocate the CASC file structure
@@ -190,12 +122,13 @@ static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexE
hf->HeaderOffset = (DWORD)(FileOffset & FileOffsMask);
hf->szClassName = "TCascFile";
// Copy the compressed file size
hf->CompressedSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSize) - 0x1E;
// Copy the file size. Note that for all files except ENCODING,
// this is the compressed file size
hf->CompressedSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
// For now, we set the file size to be equal to compressed size
// This is used when loading the "encoding" file, which does not
// have entry in the encoding itself
// This is used when loading the ENCODING file, which does not
// have entry in the encoding table
hf->FileSize = hf->CompressedSize;
// Increment the number of references to the archive
@@ -206,10 +139,9 @@ static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexE
return hf;
}
static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, HANDLE * phFile)
static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, TCascFile ** ppCascFile)
{
PCASC_INDEX_ENTRY pIndexEntry;
TCascFile * hf = NULL;
int nError = ERROR_SUCCESS;
CASCLIB_UNUSED(dwFlags);
@@ -222,43 +154,53 @@ static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dw
// Create the file handle structure
if(nError == ERROR_SUCCESS)
{
hf = CreateFileHandle(hs, pIndexEntry);
*phFile = (HANDLE)hf;
if(hf == NULL)
ppCascFile[0] = CreateFileHandle(hs, pIndexEntry);
if(ppCascFile[0] == NULL)
nError = ERROR_FILE_NOT_FOUND;
}
#ifdef CASCLIB_TEST
if(nError == ERROR_SUCCESS && ppCascFile[0] != NULL)
{
ppCascFile[0]->FileSize_IdxEntry = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
}
#endif
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return (nError == ERROR_SUCCESS);
}
static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile)
static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, TCascFile ** ppCascFile)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
QUERY_KEY IndexKey;
TCascFile * hf = NULL;
int nError = ERROR_SUCCESS;
// Find the encoding entry
pEncodingEntry = FindEncodingEntry(hs, pEncodingKey, NULL);
if(pEncodingEntry == NULL)
nError = ERROR_FILE_NOT_FOUND;
{
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
}
// Prepare the file index and open the file by index
// Note: We don't know what to do if there is more than just one index key
// We always take the first file present. Is that correct?
// IndexKey.pbData = pEncodingEntry->EncodingKey + (MD5_HASH_SIZE * pEncodingEntry->KeyCount);
// assert(pEncodingEntry->KeyCount == 1);
IndexKey.pbData = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
IndexKey.cbData = MD5_HASH_SIZE;
if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, phFile))
if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, ppCascFile))
{
// Fix the file size from the encoding key
hf = IsValidFileHandle(*phFile);
if(hf != NULL)
// Check if the file handle was created
if(ppCascFile[0] != NULL)
{
hf->FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes);
// Fill-in the file size. For all files except ENCODING,
// this overrides the value stored in the index entry.
ppCascFile[0]->FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
#ifdef CASCLIB_TEST
ppCascFile[0]->FileSize_EncEntry = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
#endif
return true;
}
}
@@ -289,7 +231,7 @@ bool WINAPI CascOpenFileByIndexKey(HANDLE hStorage, PQUERY_KEY pIndexKey, DWORD
}
// Use the internal function to open the file
return OpenFileByIndexKey(hs, pIndexKey, dwFlags, phFile);
return OpenFileByIndexKey(hs, pIndexKey, dwFlags, (TCascFile **)phFile);
}
bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile)
@@ -312,12 +254,12 @@ bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey,
}
// Use the internal function fo open the file
return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, phFile);
return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, (TCascFile **)phFile);
}
bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile)
{
CASC_ROOT_KEY_INFO EncodingKeyInfo;
PCASC_ROOT_ENTRY_MNDX pRootEntryMndx = NULL;
PCASC_ROOT_ENTRY pRootEntry;
PCASC_PACKAGE pPackage;
TCascStorage * hs;
@@ -326,6 +268,8 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
char * szFileName2;
int nError = ERROR_SUCCESS;
CASCLIB_UNUSED(dwLocale);
// Validate the storage handle
hs = IsValidStorageHandle(hStorage);
if(hs == NULL)
@@ -360,11 +304,11 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
while(szStrippedName[0] == '/')
szStrippedName++;
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &EncodingKeyInfo);
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntryMndx);
if(nError == ERROR_SUCCESS)
{
// Prepare the encoding key
EncodingKey.pbData = EncodingKeyInfo.EncodingKey;
EncodingKey.pbData = pRootEntryMndx->EncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
}
}
@@ -376,23 +320,27 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
else
{
// Convert the file name to lowercase + slashes
NormalizeFileName_UpperBkSlash(szFileName2);
NormalizeFileName_UpperBkSlash(szFileName2, szFileName2);
// Check the root directory for that hash
pRootEntry = FindRootEntryLocale(hs, szFileName2, dwLocale);
nError = (pRootEntry != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
pRootEntry = FindRootEntry(hs, szFileName2, NULL);
if(pRootEntry != NULL)
{
// Prepare the root key
EncodingKey.pbData = pRootEntry->EncodingKey;
EncodingKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
nError = ERROR_SUCCESS;
}
else
{
nError = ERROR_FILE_NOT_FOUND;
}
}
// Use the root key to find the file in the encoding table entry
if(nError == ERROR_SUCCESS)
{
if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, phFile))
if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, (TCascFile **)phFile))
{
assert(GetLastError() != ERROR_SUCCESS);
nError = GetLastError();
@@ -400,11 +348,18 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
}
// Delete the file name copy
delete [] szFileName2;
CASC_FREE(szFileName2);
}
else
nError = ERROR_NOT_ENOUGH_MEMORY;
#ifdef CASCLIB_TEST
if(phFile[0] != NULL && pRootEntryMndx != NULL)
{
((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize;
}
#endif
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return (nError == ERROR_SUCCESS);

View File

@@ -2,6 +2,8 @@
/* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Storage functions for CASC */
/* Note: WoW6 offsets refer to WoW.exe 6.0.3.19116 (32-bit) */
/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
@@ -13,10 +15,18 @@
#include "CascCommon.h"
#include "CascMndxRoot.h"
//-----------------------------------------------------------------------------
// Dumping options
#ifdef _DEBUG
#define CASC_DUMP_ROOT_FILE 2 // The root file will be dumped (level 2)
#endif
//-----------------------------------------------------------------------------
// Local structures
#define CASC_ENCODING_SEGMENT_SIZE 0x1000
#define CASC_INITIAL_ROOT_TABLE_SIZE 0x00100000
#define CASC_ENCODING_SEGMENT_SIZE 0x1000
typedef struct _BLOCK_SIZE_AND_HASH
{
@@ -79,32 +89,6 @@ typedef struct _FILE_ENCODING_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
@@ -181,19 +165,6 @@ static void QUERY_KEY_FreeArray(PQUERY_KEY pBlobArray)
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;
@@ -235,18 +206,18 @@ static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData)
return (HashHigh == pSizeAndHash->dwBlockHash);
}
static LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
{
// Validate the locale header
// Validate the file locale block
pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
pbFilePointer += sizeof(FILE_LOCALE_BLOCK);
if(pbFilePointer >= pbFileEnd)
pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
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)
if(pbFilePointer > pbFileEnd)
return NULL;
// Validate the array of root entries
@@ -488,7 +459,7 @@ static int VerifyAndParseKeyMapping_V2(PCASC_MAPPING_TABLE pKeyMapping, DWORD Ke
if(PtrLastPart[0] == 0)
return ERROR_SUCCESS;
HashLow = hashlittle(PtrLastPart + 1, 0x13, 0) | 0x80000000;
HashLow = hashlittle(PtrLastPart + i, 0x13, 0) | 0x80000000;
if(HashLow != PtrLastPart[0])
return ERROR_BAD_FORMAT;
}
@@ -533,7 +504,7 @@ static int LoadKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
{
// Retrieve the file size
FileStream_GetSize(pStream, &FileSize);
if((0 < FileSize && FileSize <= 0x90000) || 1)
if(0 < FileSize && FileSize <= 0x100000)
{
// WoW6 actually reads THE ENTIRE file to memory
// Verified on Mac build (x64)
@@ -664,79 +635,6 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn
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];
@@ -772,6 +670,113 @@ static int LoadIndexFiles(TCascStorage * hs)
return nError;
}
static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
{
CASC_ENCODING_HEADER EncodingHeader;
LPBYTE pbEncodingFile = NULL;
DWORD cbEncodingFile = 0;
DWORD dwSegmentPos = 0;
DWORD dwNumSegments = 0;
DWORD dwBytesRead;
int nError = ERROR_BAD_FORMAT;
// Read the encoding 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);
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;
}
static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
{
TCascFile * hf;
LPBYTE pbRootFile = NULL;
DWORD cbRootFile = 0;
DWORD dwBytesRead;
BYTE StartOfFile[0x10];
int nError = ERROR_SUCCESS;
// Dummy read the first 16 bytes
CascReadFile(hFile, &StartOfFile, sizeof(StartOfFile), &dwBytesRead);
if(dwBytesRead != sizeof(StartOfFile))
nError = ERROR_BAD_FORMAT;
// Calculate and allocate space for the entire file
if(nError == ERROR_SUCCESS)
{
// Convert the file handle to pointer to TCascFile
hf = IsValidFileHandle(hFile);
if(hf != NULL)
{
// Parse the frames to get the file size
for(DWORD i = 0; i < hf->FrameCount; i++)
{
cbRootFile += hf->pFrames[i].FrameSize;
}
}
// Evaluate the error
nError = (cbRootFile != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
}
// Allocate space for the entire file
if(nError == ERROR_SUCCESS)
{
pbRootFile = CASC_ALLOC(BYTE, cbRootFile);
if(pbRootFile == 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(pbRootFile, StartOfFile, sizeof(StartOfFile));
// Read the rest of the data
CascReadFile(hFile, pbRootFile + sizeof(StartOfFile), cbRootFile - sizeof(StartOfFile), &dwBytesRead);
if(dwBytesRead != (cbRootFile - sizeof(StartOfFile)))
nError = ERROR_FILE_CORRUPT;
}
// Give the loaded file length
if(pcbRootFile != NULL)
*pcbRootFile = cbRootFile;
return pbRootFile;
}
static int LoadEncodingFile(TCascStorage * hs)
{
PFILE_ENCODING_SEGMENT pEncodingSegment;
@@ -788,18 +793,15 @@ static int LoadEncodingFile(TCascStorage * hs)
if(!CascOpenFileByIndexKey((HANDLE)hs, &hs->EncodingEKey, 0, &hFile))
nError = GetLastError();
// Load the encoding file to memory
// Load the entire 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);
// 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;
// Close the encoding file
CascCloseFile(hFile);
}
@@ -859,91 +861,237 @@ static int LoadEncodingFile(TCascStorage * hs)
return nError;
}
static int LoadRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
typedef struct _CHECK_ROOT_ENTRY_INPUT
{
PFILE_ROOT_ENTRY pSrcEntry;
PCASC_ROOT_ENTRY pTrgEntry;
ULONGLONG FileNameHash;
DWORD SumValue;
DWORD EncodingKey[4];
} CHECK_ROOT_ENTRY_INPUT, *PCHECK_ROOT_ENTRY_INPUT;
typedef struct _CHECK_ROOT_ENTRY_OUTPUT
{
DWORD field_0;
DWORD field_4;
DWORD field_8;
bool field_C;
} CHECK_ROOT_ENTRY_OUTPUT, *PCHECK_ROOT_ENTRY_OUTPUT;
// WoW6: 00413F61
static bool EnlargeHashTableIfMoreThan75PercentUsed(PCASC_ROOT_HASH_TABLE pRootTable, DWORD NewItemCount)
{
// Don't relocate anything, just check
assert((double)NewItemCount / (double)pRootTable->TableSize < .75);
return true;
}
// WOW6: 00414402
// Finds an existing root table entry or a free one
PCASC_ROOT_ENTRY CascRootTable_FindFreeEntryWithEnlarge(
PCASC_ROOT_HASH_TABLE pRootTable,
PCASC_ROOT_ENTRY pNewEntry)
{
PCASC_ROOT_ENTRY pEntry;
DWORD TableIndex;
// The table size must be a power of two
assert((pRootTable->TableSize & (pRootTable->TableSize - 1)) == 0);
// Make sure that number of occupied items is never bigger
// than 75% of the table size
if(!EnlargeHashTableIfMoreThan75PercentUsed(pRootTable, pRootTable->ItemCount + 1))
return NULL;
// Get the start index of the table
TableIndex = (DWORD)(pNewEntry->FileNameHash) & (pRootTable->TableSize - 1);
// If that entry is already occupied, move to a next entry
for(;;)
{
// Check that entry if it's free or not
pEntry = pRootTable->TablePtr + TableIndex;
if(pEntry->SumValue == 0)
break;
// Is the found entry equal to the existing one?
if(pEntry->FileNameHash == pNewEntry->FileNameHash)
break;
// Move to the next entry
TableIndex = (TableIndex + 1) & (pRootTable->TableSize - 1);
}
// Either return a free entry or an existing one
return pEntry;
}
// WOW6: 004145D1
static void CascRootTable_InsertTableEntry(
PCASC_ROOT_HASH_TABLE pRootTable,
PCASC_ROOT_ENTRY pNewEntry)
{
PCASC_ROOT_ENTRY pEntry;
// Find an existing entry or an empty one
pEntry = CascRootTable_FindFreeEntryWithEnlarge(pRootTable, pNewEntry);
assert(pEntry != NULL);
// If that entry is not used yet, fill it in
if(pEntry->FileNameHash == 0)
{
*pEntry = *pNewEntry;
pRootTable->ItemCount++;
}
}
static int LoadWowRootFileLocales(
TCascStorage * hs,
LPBYTE pbRootFile,
DWORD cbRootFile,
DWORD dwLocaleMask,
bool bLoadBlocksWithFlags80,
BYTE HighestBitValue)
{
CASC_ROOT_ENTRY NewRootEntry;
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
// Now parse the root file
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
{
// Validate the root block
// Validate the file locale block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// Add the number of entries
nRootEntries = nRootEntries + BlockInfo.pLocaleBlockHdr->NumberOfFiles;
}
// WoW.exe (build 19116): Entries with flag 0x100 set are skipped
if(BlockInfo.pLocaleBlockHdr->Flags & 0x100)
continue;
// 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; )
// WoW.exe (build 19116): Entries with flag 0x80 set are skipped if arg_4 is set to FALSE (which is by default)
if(bLoadBlocksWithFlags80 == 0 && (BlockInfo.pLocaleBlockHdr->Flags & 0x80))
continue;
// WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to arg_8 are skipped
if((BYTE)(BlockInfo.pLocaleBlockHdr->Flags >> 0x1F) != HighestBitValue)
continue;
// WoW.exe (build 19116): Locales other than defined mask are skipped too
if((BlockInfo.pLocaleBlockHdr->Locales & dwLocaleMask) == 0)
continue;
// Reset the sum value
NewRootEntry.SumValue = 0;
// WoW.exe (build 19116): Blocks with zero files are skipped
for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
{
// Validate the root block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// (004147A3) Prepare the CASC_ROOT_ENTRY structure
NewRootEntry.FileNameHash = BlockInfo.pRootEntries[i].FileNameHash;
NewRootEntry.SumValue = NewRootEntry.SumValue + BlockInfo.pInt32Array[i];
NewRootEntry.Locales = BlockInfo.pLocaleBlockHdr->Locales;
NewRootEntry.EncodingKey[0] = BlockInfo.pRootEntries[i].EncodingKey[0];
NewRootEntry.EncodingKey[1] = BlockInfo.pRootEntries[i].EncodingKey[1];
NewRootEntry.EncodingKey[2] = BlockInfo.pRootEntries[i].EncodingKey[2];
NewRootEntry.EncodingKey[3] = BlockInfo.pRootEntries[i].EncodingKey[3];
// 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++;
}
// Insert the root table item to the hash table
CascRootTable_InsertTableEntry(&hs->RootTable, &NewRootEntry);
NewRootEntry.SumValue++;
}
// 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);
}
*/
return 1;
}
static int LoadRootFile(TCascStorage * hs)
// WoW.exe: 004146C7 (BuildManifest::Load)
static int LoadWowRootFileWithParams(
TCascStorage * hs,
LPBYTE pbRootFile,
DWORD cbRootFile,
DWORD dwLocaleBits,
BYTE HighestBitValue)
{
// Load the locale as-is
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, dwLocaleBits, false, HighestBitValue);
// If we wanted enGB, we also load enUS for the missing files
if(dwLocaleBits == CASC_LOCALE_ENGB)
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
if(dwLocaleBits == CASC_LOCALE_PTPT)
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
return ERROR_SUCCESS;
}
/*
// Code from WoW.exe
if(dwLocaleBits == CASC_LOCALE_DUAL_LANG)
{
// Is this english version of WoW?
if(arg_4 == CASC_LOCALE_BIT_ENUS)
{
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, false, HighestBitValue);
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
return ERROR_SUCCESS;
}
// Is this portuguese version of WoW?
if(arg_4 == CASC_LOCALE_BIT_PTBR)
{
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, false, HighestBitValue);
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
}
}
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << arg_4), false, HighestBitValue);
*/
static int LoadWowRootFile(
TCascStorage * hs,
LPBYTE pbRootFile,
DWORD cbRootFile,
DWORD dwLocaleMask)
{
int nError;
// Dump the root file, if needed
#ifdef CASC_DUMP_ROOT_FILE
//CascDumpRootFile(hs,
// pbRootFile,
// cbRootFile,
// "\\casc_root_%build%.txt",
// _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"),
// CASC_DUMP_ROOT_FILE);
#endif
// Allocate root table entries. Note that the initial size
// of the root table is set to 0x00200000 by World of Warcraft 6.x
hs->RootTable.TablePtr = CASC_ALLOC(CASC_ROOT_ENTRY, CASC_INITIAL_ROOT_TABLE_SIZE);
hs->RootTable.TableSize = CASC_INITIAL_ROOT_TABLE_SIZE;
if(hs->RootTable.TablePtr == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Clear the entire table
memset(hs->RootTable.TablePtr, 0, CASC_INITIAL_ROOT_TABLE_SIZE * sizeof(CASC_ROOT_ENTRY));
// Load the root file
nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 0);
if(nError != ERROR_SUCCESS)
return nError;
nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 1);
if(nError != ERROR_SUCCESS)
return nError;
return ERROR_SUCCESS;
}
static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask)
{
PDWORD FileSignature;
HANDLE hFile = NULL;
@@ -952,9 +1100,14 @@ static int LoadRootFile(TCascStorage * hs)
int nError = ERROR_SUCCESS;
// Sanity checks
assert(hs->RootTable.TablePtr == NULL);
assert(hs->RootTable.ItemCount == 0);
assert(hs->ppEncodingEntries != NULL);
assert(hs->pRootEntries == NULL);
assert(hs->nRootEntries == 0);
// Locale: The default parameter is 0 - in that case,
// we load enUS+enGB
if(dwLocaleMask == 0)
dwLocaleMask = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB;
// 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+)
@@ -963,15 +1116,15 @@ static int LoadRootFile(TCascStorage * hs)
if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile))
nError = GetLastError();
// Load ther entire root file to memory
// Load the 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)
// Load the necessary part of the ENCODING file to memory
pbRootFile = LoadRootFileToMemory(hFile, &cbRootFile);
if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK))
nError = ERROR_FILE_CORRUPT;
// Close the root file
// Close the encoding file
CascCloseFile(hFile);
}
@@ -985,7 +1138,8 @@ static int LoadRootFile(TCascStorage * hs)
}
else
{
nError = LoadRootFile(hs, pbRootFile, cbRootFile);
// WOW6: 00415000
nError = LoadWowRootFile(hs, pbRootFile, cbRootFile, dwLocaleMask);
}
}
@@ -1007,10 +1161,8 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
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->RootTable.TablePtr != NULL)
CASC_FREE(hs->RootTable.TablePtr);
if(hs->ppEncodingEntries != NULL)
CASC_FREE(hs->ppEncodingEntries);
if(hs->pEncodingHeader != NULL)
@@ -1074,13 +1226,11 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
//-----------------------------------------------------------------------------
// Public functions
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage)
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage)
{
TCascStorage * hs;
int nError = ERROR_SUCCESS;
CASCLIB_UNUSED(dwFlags);
// Allocate the storage structure
hs = (TCascStorage *)CASC_ALLOC(TCascStorage, 1);
if(hs == NULL)
@@ -1092,6 +1242,7 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph
// Prepare the base storage parameters
memset(hs, 0, sizeof(TCascStorage));
hs->szClassName = "TCascStorage";
hs->dwFileBeginDelta = 0xFFFFFFFF;
hs->dwRefCount = 1;
nError = InitializeCascDirectories(hs, szDataPath);
}
@@ -1099,7 +1250,7 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph
// Now we need to load the root file so we know the config files
if(nError == ERROR_SUCCESS)
{
nError = LoadBuildConfiguration(hs);
nError = LoadBuildInfo(hs);
}
// Load the index files
@@ -1117,17 +1268,9 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph
// Load the index files
if(nError == ERROR_SUCCESS)
{
nError = LoadRootFile(hs);
nError = LoadRootFile(hs, dwLocaleMask);
}
#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)
{
@@ -1147,7 +1290,7 @@ bool WINAPI CascGetStorageInfo(
size_t * pcbLengthNeeded)
{
TCascStorage * hs;
DWORD dwCascFeatures = 0;
DWORD dwInfoValue = 0;
// Verify the storage handle
hs = IsValidStorageHandle(hStorage);
@@ -1161,41 +1304,41 @@ bool WINAPI CascGetStorageInfo(
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;
dwInfoValue = (DWORD)hs->pIndexEntryMap->ItemCount;
break;
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;
dwInfoValue |= CASC_FEATURE_LISTFILE;
break;
// Give the number of files
*(PDWORD)pvStorageInfo = dwCascFeatures;
return true;
case CascStorageGameInfo:
dwInfoValue = hs->dwGameInfo;
break;
case CascStorageGameBuild:
dwInfoValue = hs->dwBuildNumber;
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
//
// Return the required DWORD value
//
if(cbStorageInfo < sizeof(DWORD))
{
*pcbLengthNeeded = sizeof(DWORD);
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return false;
}
// Give the number of files
*(PDWORD)pvStorageInfo = dwInfoValue;
return true;
}

View File

@@ -15,28 +15,11 @@
//-----------------------------------------------------------------------------
// Local structures
#define BLTE_HEADER_SIGNATURE 0x45544C42
// Data file begin:
// BYTE HeaderHash[MD5_HASH_SIZE]; // MD5 of the frame array
// DWORD dwFileSize; // Size of the file
// BYTE SomeSize[4]; // Some size (big endian)
// BYTE Padding[6]; // Padding (?)
typedef struct _BLTE_HEADER
{
DWORD dwSignature; // Must be "BLTE"
BYTE HeaderSizeAsBytes[4]; // Header size in bytes (big endian)
BYTE MustBe0F; // Must be 0x0F
BYTE FrameCount[3]; // Number of frames (big endian)
} BLTE_HEADER, *PBLTE_HEADER;
typedef struct _BLTE_FRAME
{
BYTE CompressedSize[4]; // Compressed file size as big endian
BYTE FrameSize[4]; // File size as big endian
BYTE md5[MD5_HASH_SIZE]; // Hash of the frame
BYTE md5[MD5_HASH_SIZE]; // Hash of the compressed frame
} BLTE_FRAME, *PBLTE_FRAME;
@@ -65,11 +48,6 @@ static int EnsureDataStreamIsOpen(TCascFile * hf)
// Open the stream
pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
hs->DataFileArray[hf->ArchiveIndex] = pStream;
// TODO: There is 0x1E bytes at the beginning of the file stream
// Ignore them for now, but we will want to know what they mean
// Offs0000: MD5 of something
// Offs0010: 2 bytes
CASC_FREE(szDataFile);
}
}
@@ -79,7 +57,7 @@ static int EnsureDataStreamIsOpen(TCascFile * hf)
return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
}
static int LoadFileFrames(TCascFile * hf, DWORD FrameCount)
static int LoadFileFrames(TCascFile * hf)
{
PBLTE_FRAME pFileFrames;
PBLTE_FRAME pFileFrame;
@@ -93,40 +71,41 @@ static int LoadFileFrames(TCascFile * hf, DWORD FrameCount)
assert(hf->pFrames != NULL);
// Allocate frame array
pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, FrameCount);
pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, hf->FrameCount);
if(pFileFrames != NULL)
{
// Load the frame array
ArchiveFileOffset = hf->FramesOffset;
if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, FrameCount * sizeof(BLTE_FRAME)))
if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, hf->FrameCount * sizeof(BLTE_FRAME)))
{
// Move the raw archive offset
ArchiveFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME));
// Copy the frames to the file structure
for(DWORD i = 0; i < FrameCount; i++, pFileFrame++)
for(DWORD i = 0; i < hf->FrameCount; i++, pFileFrame++)
{
hf->pFrames[i].FrameArchiveOffset = (DWORD)ArchiveFileOffset;
hf->pFrames[i].FrameFileOffset = FrameOffset;
hf->pFrames[i].CompressedSize = ConvertBytesToInteger_4(pFileFrame->CompressedSize);
hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize);
hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize);
memcpy(hf->pFrames[i].md5, pFileFrame->md5, MD5_HASH_SIZE);
ArchiveFileOffset += hf->pFrames[i].CompressedSize;
FrameOffset += hf->pFrames[i].FrameSize;
FileSize += hf->pFrames[i].FrameSize;
}
// Fill-in the frame count
hf->FrameCount = FrameCount;
}
else
nError = GetLastError();
// Verify the file size
// assert(FileSize == hf->FileSize);
// Note: Do not take the FileSize from the sum of frames.
// This value is invalid when loading the ENCODING file.
// hf->FileSize = FileSize;
#ifdef CASCLIB_TEST
hf->FileSize_FrameSum = FileSize;
#endif
// Free the array
CASC_FREE(pFileFrames);
}
@@ -136,66 +115,115 @@ static int LoadFileFrames(TCascFile * hf, DWORD FrameCount)
return nError;
}
static int EnsureHeaderAreaIsLoaded(TCascFile * hf)
{
TCascStorage * hs = hf->hs;
ULONGLONG FileOffset = hf->HeaderOffset;
LPBYTE pbHeaderArea;
DWORD FileSignature;
DWORD FileSize;
BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
int nError;
// We need the data file to be open
nError = EnsureDataStreamIsOpen(hf);
if(nError != ERROR_SUCCESS)
return nError;
// Make sure that we already know the shift
// to the begin of file data.
// Note that older builds of Heroes of the Storm have entries pointing
// to the beginning of the header area.
// Newer versions of HOTS have encoding entries pointing directly to
// the BLTE header
if(hs->dwFileBeginDelta == 0xFFFFFFFF)
{
FileSignature = 0;
FileOffset = hf->HeaderOffset;
if(!FileStream_Read(hf->pStream, &FileOffset, &FileSignature, sizeof(DWORD)))
return ERROR_FILE_CORRUPT;
hs->dwFileBeginDelta = (FileSignature == BLTE_HEADER_SIGNATURE) ? BLTE_HEADER_DELTA : 0;
}
// If the file size is not loaded yet, do it
if(hf->FrameCount == 0)
{
// Load the part before BLTE header + header itself
FileOffset = hf->HeaderOffset - hs->dwFileBeginDelta;
if(!FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea)))
return ERROR_FILE_CORRUPT;
// Copy the MD5 hash of the frame array
memcpy(hf->FrameArrayHash, HeaderArea, MD5_HASH_SIZE);
pbHeaderArea = HeaderArea + MD5_HASH_SIZE;
// Copy the file size
FileSize = ConvertBytesToInteger_4_LE(pbHeaderArea);
pbHeaderArea += 0x0E;
// Verify the BLTE signature
if(ConvertBytesToInteger_4_LE(pbHeaderArea) != BLTE_HEADER_SIGNATURE)
return ERROR_BAD_FORMAT;
pbHeaderArea += sizeof(DWORD);
// Load the size of the frame headers
hf->HeaderSize = ConvertBytesToInteger_4(pbHeaderArea);
if(hf->HeaderSize & 0x80000000)
return ERROR_BAD_FORMAT;
pbHeaderArea += sizeof(DWORD);
// Read the header size
assert(hs->dwFileBeginDelta <= BLTE_HEADER_DELTA);
hf->HeaderOffset += (BLTE_HEADER_DELTA - hs->dwFileBeginDelta);
hf->FrameCount = 1;
// Retrieve the frame count, if different from 1
if(hf->HeaderSize != 0)
{
// The next byte must be 0x0F
if(pbHeaderArea[0] != 0x0F)
return ERROR_BAD_FORMAT;
pbHeaderArea++;
// Next three bytes form number of frames
hf->FrameCount = ConvertBytesToInteger_3(pbHeaderArea);
}
#ifdef CASCLIB_TEST
hf->FileSize_HdrArea = FileSize;
#endif
}
return ERROR_SUCCESS;
}
static int EnsureFrameHeadersLoaded(TCascFile * hf)
{
PBLTE_HEADER pBlteHeader;
ULONGLONG FileOffset = hf->HeaderOffset;
DWORD dwHeaderOffsetFixup = 0;
DWORD dwFrameHeaderSize;
DWORD dwFrameCount;
BYTE HeaderBuffer[sizeof(BLTE_HEADER) + 0x20];
int nError = ERROR_SUCCESS;
int nError;
// Sanity check
assert(hf->pStream != NULL);
// Make sure we have header area loaded
nError = EnsureHeaderAreaIsLoaded(hf);
if(nError != ERROR_SUCCESS)
return nError;
// If the frame headers are not loaded yet, do it
if(hf->pFrames == NULL)
{
// Note that older builds of Heroes of the Storm have entries pointing
// to the begin of the BLTE header, which is MD5 + some junk.
// Newer versions of HOTS have encoding entries pointing directly to
// the BLTE header
FileStream_Read(hf->pStream, &FileOffset, HeaderBuffer, sizeof(HeaderBuffer));
pBlteHeader = (PBLTE_HEADER)HeaderBuffer;
// If we don't have the BLTE header right there,
// just get the block that is 0x1E bytes later
if(pBlteHeader->dwSignature != BLTE_HEADER_SIGNATURE)
{
memcpy(&HeaderBuffer[0x00], &HeaderBuffer[0x1E], sizeof(BLTE_HEADER));
dwHeaderOffsetFixup = 0x1E;
}
// Check for the BLTE header signature
if(pBlteHeader->dwSignature != BLTE_HEADER_SIGNATURE)
return ERROR_BAD_FORMAT;
hf->HeaderOffset += dwHeaderOffsetFixup;
// Check for a single unit file
dwFrameHeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSizeAsBytes);
dwFrameCount = (dwFrameHeaderSize != 0) ? ConvertBytesToInteger_3(pBlteHeader->FrameCount) : 1;
// Allocate the frame array
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, dwFrameCount);
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount);
if(hf->pFrames != NULL)
{
// Save the number of frames
hf->FrameCount = dwFrameCount;
// Either load the frames from the file or supply them on our own
if(dwFrameHeaderSize != 0)
if(hf->HeaderSize != 0)
{
if(pBlteHeader->MustBe0F != 0x0F)
return ERROR_FILE_CORRUPT;
hf->FramesOffset = hf->HeaderOffset + sizeof(BLTE_HEADER);
nError = LoadFileFrames(hf, dwFrameCount);
hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD) + sizeof(DWORD);
nError = LoadFileFrames(hf);
}
else
{
// Offset of the first frame is right after the file frames
hf->FramesOffset = hf->HeaderOffset + sizeof(pBlteHeader->dwSignature) + sizeof(pBlteHeader->HeaderSizeAsBytes);
hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD);
hf->pFrames[0].FrameArchiveOffset = hf->FramesOffset;
hf->pFrames[0].FrameFileOffset = 0;
@@ -239,9 +267,27 @@ static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer)
//-----------------------------------------------------------------------------
// Public functions
//
// THE FILE SIZE PROBLEM
//
// There are members called "FileSize" in many CASC-related structure
// For various files, these variables have different meaning.
//
// Storage FileName FileSize FrameSum HdrArea IdxEntry EncEntry RootEntry
// ----------- -------- ---------- -------- -------- -------- -------- ---------
// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 0x0024BA45 n/a n/a
// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x0010db65 0x00193340 n/a
// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x000008eb 0x00001080 0x00001080
//
// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b 0x030d487b n/a n/a
// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x0131313d 0x016a9800 n/a
// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x00000397 0x000007d0 n/a
//
DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
{
TCascFile * hf;
int nError;
CASCLIB_UNUSED(pdwFileSizeHigh);
@@ -252,6 +298,14 @@ DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
return CASC_INVALID_SIZE;
}
// Make sure that the file header area is loaded
nError = EnsureHeaderAreaIsLoaded(hf);
if(nError != ERROR_SUCCESS)
{
SetLastError(nError);
return CASC_INVALID_SIZE;
}
// Give the file size to the caller
if(pdwFileSizeHigh != NULL)
*pdwFileSizeHigh = 0;
@@ -348,25 +402,19 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
return false;
}
// If the file position is at or beyond end of file, do nothing
if(hf->FilePointer >= hf->FileSize)
{
*pdwBytesRead = 0;
return ERROR_SUCCESS;
}
// Make sure we have that data file open
if(nError == ERROR_SUCCESS)
{
nError = EnsureDataStreamIsOpen(hf);
}
// If the file frames are not loaded yet, do it now
if(nError == ERROR_SUCCESS)
{
nError = EnsureFrameHeadersLoaded(hf);
}
// If the file position is at or beyond end of file, do nothing
if(nError == ERROR_SUCCESS && hf->FilePointer >= hf->FileSize)
{
*pdwBytesRead = 0;
return ERROR_SUCCESS;
}
// Find the file frame where to read from
if(nError == ERROR_SUCCESS)
{
@@ -423,7 +471,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
}
// Verify the block MD5
if(IsValidMD5(pFrame->md5) && !VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
{
CASC_FREE(pbRawData);
nError = ERROR_FILE_CORRUPT;

View File

@@ -61,20 +61,6 @@ unsigned char AsciiToUpperTable[256] =
unsigned char IntToHexChar[] = "0123456789abcdef";
//-----------------------------------------------------------------------------
// Support for memory reallocation
#if defined(_MSC_VER) && defined(_DEBUG)
void * DbgRealloc(void * ptr, size_t nSize)
{
// HeapReAlloc does not support NULL as previous block
if(ptr == NULL)
return HeapAlloc(GetProcessHeap, 0, nSize);
return HeapReAlloc(GetProcessHeap(), 0, ptr, nSize);
}
#endif
//-----------------------------------------------------------------------------
// GetLastError/SetLastError support for non-Windows platform
@@ -232,11 +218,14 @@ TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir)
return szFullPath;
}
void NormalizeFileName_UpperBkSlash(char * szFileName)
void NormalizeFileName_UpperBkSlash(const char * szSrcFileName, char * szTrgFileName)
{
size_t i;
// Normalize the file name: ToLower + BackSlashToSlash
for(size_t i = 0; szFileName[i] != 0; i++)
szFileName[i] = AsciiToUpperTable[szFileName[i]];
for(i = 0; szSrcFileName[i] != 0; i++)
szTrgFileName[i] = AsciiToUpperTable[szSrcFileName[i]];
szTrgFileName[i] = 0;
}
void NormalizeFileName_LowerSlash(char * szFileName)

View File

@@ -28,13 +28,6 @@ extern unsigned char AsciiToLowerTable[256];
extern unsigned char AsciiToUpperTable[256];
extern unsigned char IntToHexChar[];
//-----------------------------------------------------------------------------
// Memory management helper
#if defined(_MSC_VER) && defined(_DEBUG)
void * DbgRealloc(void * ptr, size_t nSize);
#endif
//-----------------------------------------------------------------------------
// GetLastError/SetLastError support for non-Windows platform
@@ -57,7 +50,7 @@ TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd);
TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir);
void NormalizeFileName_UpperBkSlash(char * szFileName);
void NormalizeFileName_UpperBkSlash(const char * szSrcFileName, char * szTrgFileName);
void NormalizeFileName_LowerSlash(char * szFileName);
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue);

View File

@@ -2551,6 +2551,14 @@ bool FileStream_SetCallback(TFileStream * pStream, STREAM_DOWNLOAD_CALLBACK pfnC
*/
bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead)
{
//FILE * fp = fopen("E:\\Loading.txt", "at");
//if(fp != NULL)
//{
// ULONGLONG ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : 0;
// fprintf(fp, "%-32ws\t%08X\t%08X\n", GetPlainFileName(pStream->szFileName), (ULONG)ByteOffset, dwBytesToRead);
// fclose(fp);
//}
assert(pStream->StreamRead != NULL);
return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead);
}

View File

@@ -125,12 +125,12 @@ static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, size_t nM
}
// If we have found a newline, stop loading
if(*pCache->pPos == 0x0D || *pCache->pPos == 0x0A)
if(pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x0A)
break;
// Blizzard listfiles can also contain information about patch:
// Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326)
if(*pCache->pPos == '~')
if(pCache->pPos[0] == '~')
szExtraString = szLine;
// Copy the character
@@ -190,7 +190,7 @@ static TListFileCache * CreateListFileCache(RELOAD_CACHE pfnReloadCache, CLOSE_S
}
//-----------------------------------------------------------------------------
// Listfile functions
// Functions for parsing an external listfile
void * ListFile_OpenExternal(const TCHAR * szListFile)
{
@@ -264,3 +264,160 @@ void ListFile_Free(void * pvListFile)
CASC_FREE(pCache);
}
}
//-----------------------------------------------------------------------------
// Functions for creating a listfile map
#define LISTMAP_INITIAL 0x100000
static PLISTFILE_MAP ListMap_Create()
{
PLISTFILE_MAP pListMap;
size_t cbToAllocate;
// Create buffer for the listfile
// Note that because the listfile is quite big and CASC_REALLOC
// is a costly operation, we want to have as few reallocs as possible.
cbToAllocate = sizeof(LISTFILE_MAP) + LISTMAP_INITIAL;
pListMap = (PLISTFILE_MAP)CASC_ALLOC(BYTE, cbToAllocate);
if(pListMap != NULL)
{
// Fill the listfile buffer
memset(pListMap, 0, sizeof(LISTFILE_MAP));
pListMap->cbBufferMax = LISTMAP_INITIAL;
}
return pListMap;
}
static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szFileName, size_t nLength)
{
PLISTFILE_ENTRY pListEntry;
char szFileName2[MAX_PATH+1];
size_t cbToAllocate;
size_t cbEntrySize;
uint32_t dwHashHigh = 0;
uint32_t dwHashLow = 0;
// Make sure there is enough space in the list map
cbEntrySize = sizeof(LISTFILE_ENTRY) + nLength;
cbEntrySize = ALIGN_TO_SIZE(cbEntrySize, 8);
if((pListMap->cbBuffer + cbEntrySize) > pListMap->cbBufferMax)
{
cbToAllocate = sizeof(LISTFILE_MAP) + (pListMap->cbBufferMax * 3) / 2;
pListMap = (PLISTFILE_MAP)CASC_REALLOC(BYTE, pListMap, cbToAllocate);
if(pListMap == NULL)
return NULL;
pListMap->cbBufferMax = (pListMap->cbBufferMax * 3) / 2;
}
// Get the pointer to the first entry
pListEntry = (PLISTFILE_ENTRY)((LPBYTE)(pListMap + 1) + pListMap->cbBuffer);
// Get the name hash
NormalizeFileName_UpperBkSlash(szFileName, szFileName2);
hashlittle2(szFileName2, nLength, &dwHashHigh, &dwHashLow);
// Calculate the HASH value of the normalized file name
pListEntry->FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
pListEntry->cbEntrySize = (DWORD)cbEntrySize;
memcpy(pListEntry->szFileName, szFileName, nLength);
pListEntry->szFileName[nLength] = 0;
// Move the next entry
pListMap->cbBuffer += cbEntrySize;
pListMap->nEntries++;
return pListMap;
}
static PLISTFILE_MAP ListMap_Finish(PLISTFILE_MAP pListMap)
{
PLISTFILE_ENTRY pListEntry;
PCASC_MAP pMap;
LPBYTE pbEntry;
// Sanity check
assert(pListMap->pNameMap == NULL);
// Create the map
pListMap->pNameMap = pMap = Map_Create((DWORD)pListMap->nEntries, sizeof(ULONGLONG), 0);
if(pListMap->pNameMap == NULL)
{
ListFile_FreeMap(pListMap);
return NULL;
}
// Fill the map
pbEntry = (LPBYTE)(pListMap + 1);
for(size_t i = 0; i < pListMap->nEntries; i++)
{
// Get the listfile entry
pListEntry = (PLISTFILE_ENTRY)pbEntry;
pbEntry += pListEntry->cbEntrySize;
// Insert the entry to the map
Map_InsertObject(pMap, pListEntry);
}
return pListMap;
}
PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile)
{
PLISTFILE_MAP pListMap = NULL;
void * pvListFile;
char szFileName[MAX_PATH+1];
size_t nLength;
// Only if the listfile name has been given
if(szListFile != NULL)
{
// Create map for the listfile
pListMap = ListMap_Create();
if(pListMap != NULL)
{
// Open the external listfile
pvListFile = ListFile_OpenExternal(szListFile);
if(pvListFile != NULL)
{
// Go through the entire listfile and insert each name to the map
while((nLength = ListFile_GetNext(pvListFile, "*", szFileName, MAX_PATH)) != 0)
{
// Insert the file name to the map
pListMap = ListMap_InsertName(pListMap, szFileName, nLength);
if(pListMap == NULL)
break;
}
// Finish the listfile map
pListMap = ListMap_Finish(pListMap);
// Free the listfile
ListFile_Free(pvListFile);
}
}
}
// Return the created map
return pListMap;
}
const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash)
{
PLISTFILE_ENTRY pListEntry = NULL;
if(pListMap != NULL)
pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash);
return (pListEntry != NULL) ? pListEntry->szFileName : "";
}
void ListFile_FreeMap(PLISTFILE_MAP pListMap)
{
if(pListMap != NULL)
{
if(pListMap->pNameMap != NULL)
Map_Free(pListMap->pNameMap);
CASC_FREE(pListMap);
}
}

View File

@@ -11,8 +11,40 @@
#ifndef __LISTFILE_H__
#define __LISTFILE_H__
//-----------------------------------------------------------------------------
// Structures
typedef struct _LISTFILE_ENTRY
{
ULONGLONG FileNameHash; // Hash of the file name
DWORD cbEntrySize; // Length of this entry, in bytes
char szFileName[1]; // File name, aligned to 8-byte boundary
} LISTFILE_ENTRY, *PLISTFILE_ENTRY;
typedef struct _LISTFILE_MAP
{
PCASC_MAP pNameMap; // Map of hash-to-name
size_t cbBufferMax; // Total size of the buffer, in bytes
size_t cbBuffer; // Current size of the buffer, in bytes
size_t nEntries; // Number of entries
// First LISTFILE_ENTRY starts here
} LISTFILE_MAP, *PLISTFILE_MAP;
//-----------------------------------------------------------------------------
// Functions for parsing an external listfile
void * ListFile_OpenExternal(const TCHAR * szListFile);
size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars);
void ListFile_Free(void * pvListFile);
//-----------------------------------------------------------------------------
// Functions for creating a listfile map
PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile);
const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash);
void ListFile_FreeMap(PLISTFILE_MAP pListMap);
#endif // __LISTFILE_H__

View File

@@ -14,7 +14,7 @@
//-----------------------------------------------------------------------------
// Structures
#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object
#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object
typedef struct _CASC_MAP
{

View File

@@ -38,7 +38,7 @@ recastnavigation (Recast is state of the art navigation mesh construction toolse
CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014)
https://github.com/ladislav-zezula/CascLib
Version: 3e3f4f443cb7b2893cbbe60b0a6efafebf48bc40
Version: 5d3789af3435534c288c2145e158d422651c7fe1
zmqpp (C++ binding for 0mq/zmq is a 'high-level' library that hides most of the c-style interface core 0mq provides.)
https://github.com/zeromq/zmqpp