mxwcore-legion/dep/CascLib/src/CascRootFile_WoW6.cpp
2023-11-05 15:26:19 -05:00

596 lines
22 KiB
C++

/*****************************************************************************/
/* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Storage functions for CASC */
/* Note: WoW6 offsets refer to WoW.exe 6.0.3.19116 (32-bit) */
/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascOpenStorage.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local structures
#define ROOT_SEARCH_PHASE_INITIALIZING 0
#define ROOT_SEARCH_PHASE_LISTFILE 1
#define ROOT_SEARCH_PHASE_NAMELESS 2
#define ROOT_SEARCH_PHASE_FINISHED 2
// On-disk version of locale block
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;
// On-disk version of root entry
typedef struct _FILE_ROOT_ENTRY
{
ENCODING_KEY EncodingKey; // MD5 of the file
ULONGLONG FileNameHash; // Jenkins hash of the file name
} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
typedef struct _CASC_ROOT_BLOCK
{
PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
PDWORD FileDataIds; // Pointer to the array of File Data IDs
PFILE_ROOT_ENTRY pRootEntries;
} CASC_ROOT_BLOCK, *PCASC_ROOT_BLOCK;
// 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_FILE_ENTRY
{
ENCODING_KEY EncodingKey; // File encoding key (MD5)
ULONGLONG FileNameHash; // Jenkins hash of the file name
DWORD FileDataId; // File Data Index
DWORD Locales; // Locale flags of the file
} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY;
struct TRootHandler_WoW6 : public TRootHandler
{
// Linear global list of file entries
DYNAMIC_ARRAY FileTable;
DYNAMIC_ARRAY FileDataIdLookupTable;
// Global map of FileName -> FileEntry
PCASC_MAP pRootMap;
// For counting files
DWORD dwTotalFileCount;
};
// Prototype for root file parsing routine
typedef int (*PARSE_ROOT)(TRootHandler_WoW6 * pRootHandler, PCASC_ROOT_BLOCK pBlockInfo);
//-----------------------------------------------------------------------------
// Local functions
static bool IsFileDataIdName(const char * szFileName)
{
BYTE BinaryValue[4];
// File name must begin with "File", case insensitive
if(AsciiToUpperTable_BkSlash[szFileName[0]] == 'F' &&
AsciiToUpperTable_BkSlash[szFileName[1]] == 'I' &&
AsciiToUpperTable_BkSlash[szFileName[2]] == 'L' &&
AsciiToUpperTable_BkSlash[szFileName[3]] == 'E')
{
// Then, 8 hexadecimal digits must follow
if(ConvertStringToBinary(szFileName + 4, 8, BinaryValue) == ERROR_SUCCESS)
{
// Must be followed by an extension or end-of-string
return (szFileName[0x0C] == 0 || szFileName[0x0C] == '.');
}
}
return false;
}
static int FileDataIdCompare(const void *, const void * pvFile1, const void * pvFile2)
{
return ((PCASC_FILE_ENTRY)pvFile1)->FileDataId - ((PCASC_FILE_ENTRY)pvFile2)->FileDataId;
}
// Search by FileDataId
PCASC_FILE_ENTRY FindRootEntry(DYNAMIC_ARRAY & FileTable, DWORD FileDataId)
{
PCASC_FILE_ENTRY* pStartEntry = (PCASC_FILE_ENTRY*)FileTable.ItemArray;
PCASC_FILE_ENTRY* pMidlEntry;
PCASC_FILE_ENTRY* pEndEntry = pStartEntry + FileTable.ItemCount - 1;
int nResult;
// Perform binary search on the table
while(pStartEntry < pEndEntry)
{
// Calculate the middle of the interval
pMidlEntry = pStartEntry + ((pEndEntry - pStartEntry) / 2);
// Did we find it?
nResult = (int)FileDataId - (int)(*pMidlEntry)->FileDataId;
if(nResult == 0)
return *pMidlEntry;
// Move the interval to the left or right
(nResult < 0) ? pEndEntry = pMidlEntry : pStartEntry = pMidlEntry + 1;
}
return NULL;
}
// Search by file name hash
// Also used in CascSearchFile
PCASC_FILE_ENTRY FindRootEntry(PCASC_MAP pRootMap, const char * szFileName, DWORD * PtrTableIndex)
{
// Calculate the HASH value of the normalized file name
ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
// Perform the hash search
return (PCASC_FILE_ENTRY)Map_FindObject(pRootMap, &FileNameHash, PtrTableIndex);
}
LPBYTE VerifyLocaleBlock(PCASC_ROOT_BLOCK pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
{
// Validate the file locale block
pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
if(pbFilePointer > pbFileEnd)
return NULL;
// Validate the array of 32-bit integers
pBlockInfo->FileDataIds = (PDWORD)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->FileDataIds + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
if(pbFilePointer > pbFileEnd)
return NULL;
// Validate the array of root entries
pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
if(pbFilePointer > pbFileEnd)
return NULL;
// Return the position of the next block
return pbFilePointer;
}
static int ParseRoot_CountFiles(
TRootHandler_WoW6 * pRootHandler,
PCASC_ROOT_BLOCK pRootBlock)
{
// Add the file count to the total file count
pRootHandler->dwTotalFileCount += pRootBlock->pLocaleBlockHdr->NumberOfFiles;
return ERROR_SUCCESS;
}
static int ParseRoot_AddRootEntries(
TRootHandler_WoW6 * pRootHandler,
PCASC_ROOT_BLOCK pRootBlock)
{
PCASC_FILE_ENTRY pFileEntry;
DWORD dwFileDataId = 0;
// Sanity checks
assert(pRootHandler->FileTable.ItemArray != NULL);
assert(pRootHandler->FileTable.ItemCountMax != 0);
assert(pRootHandler->FileDataIdLookupTable.ItemArray != NULL);
assert(pRootHandler->FileDataIdLookupTable.ItemCountMax != 0);
// WoW.exe (build 19116): Blocks with zero files are skipped
for(DWORD i = 0; i < pRootBlock->pLocaleBlockHdr->NumberOfFiles; i++)
{
// Create new entry, with overflow check
if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
return ERROR_INSUFFICIENT_BUFFER;
pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
if (pRootHandler->FileDataIdLookupTable.ItemCount >= pRootHandler->FileDataIdLookupTable.ItemCountMax)
return ERROR_INSUFFICIENT_BUFFER;
Array_Insert(&pRootHandler->FileDataIdLookupTable, &pFileEntry, 1);
// (004147A3) Prepare the CASC_FILE_ENTRY structure
pFileEntry->FileNameHash = pRootBlock->pRootEntries[i].FileNameHash;
pFileEntry->FileDataId = dwFileDataId + pRootBlock->FileDataIds[i];
pFileEntry->Locales = pRootBlock->pLocaleBlockHdr->Locales;
pFileEntry->EncodingKey = pRootBlock->pRootEntries[i].EncodingKey;
// Also, insert the entry to the map
Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
// Update the local File Data Id
assert((pFileEntry->FileDataId + 1) > pFileEntry->FileDataId);
dwFileDataId = pFileEntry->FileDataId + 1;
// Move to the next root entry
pFileEntry++;
}
return ERROR_SUCCESS;
}
static int ParseWowRootFileInternal(
TRootHandler_WoW6 * pRootHandler,
PARSE_ROOT pfnParseRoot,
LPBYTE pbRootFile,
LPBYTE pbRootFileEnd,
DWORD dwLocaleMask,
BYTE bOverrideArchive,
BYTE bAudioLocale)
{
CASC_ROOT_BLOCK RootBlock;
// Now parse the root file
while(pbRootFile < pbRootFileEnd)
{
// Validate the file locale block
pbRootFile = VerifyLocaleBlock(&RootBlock, pbRootFile, pbRootFileEnd);
if(pbRootFile == NULL)
break;
// WoW.exe (build 19116): Entries with flag 0x100 set are skipped
if(RootBlock.pLocaleBlockHdr->Flags & 0x100)
continue;
// WoW.exe (build 19116): Entries with flag 0x80 set are skipped if overrideArchive CVAR is set to FALSE (which is by default in non-chinese clients)
if((RootBlock.pLocaleBlockHdr->Flags & 0x80) && bOverrideArchive == 0)
continue;
// WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to bAudioLocale are skipped
if((RootBlock.pLocaleBlockHdr->Flags >> 0x1F) != bAudioLocale)
continue;
// WoW.exe (build 19116): Locales other than defined mask are skipped too
if((RootBlock.pLocaleBlockHdr->Locales & dwLocaleMask) == 0)
continue;
// Now call the custom function
pfnParseRoot(pRootHandler, &RootBlock);
}
return ERROR_SUCCESS;
}
/*
// known dwRegion values returned from sub_661316 (7.0.3.22210 x86 win), also referred by lua GetCurrentRegion
#define WOW_REGION_US 0x01
#define WOW_REGION_KR 0x02
#define WOW_REGION_EU 0x03
#define WOW_REGION_TW 0x04
#define WOW_REGION_CN 0x05
#define WOW_LOCALE_ENUS 0x00
#define WOW_LOCALE_KOKR 0x01
#define WOW_LOCALE_FRFR 0x02
#define WOW_LOCALE_DEDE 0x03
#define WOW_LOCALE_ZHCN 0x04
#define WOW_LOCALE_ZHTW 0x05
#define WOW_LOCALE_ESES 0x06
#define WOW_LOCALE_ESMX 0x07
#define WOW_LOCALE_RURU 0x08
#define WOW_LOCALE_PTBR 0x0A
#define WOW_LOCALE_ITIT 0x0B
// dwLocale is obtained from a WOW_LOCALE_* to CASC_LOCALE_BIT_* mapping (sub_6615D0 in 7.0.3.22210 x86 win)
// because (ENUS, ENGB) and (PTBR, PTPT) pairs share the same value on WOW_LOCALE_* enum
// dwRegion is used to distinguish them
if(dwRegion == WOW_REGION_EU)
{
// Is this english version of WoW?
if(dwLocale == CASC_LOCALE_BIT_ENUS)
{
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, bOverrideArchive, bAudioLocale);
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, bOverrideArchive, bAudioLocale);
return ERROR_SUCCESS;
}
// Is this portuguese version of WoW?
if(dwLocale == CASC_LOCALE_BIT_PTBR)
{
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, bOverrideArchive, bAudioLocale);
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, bOverrideArchive, bAudioLocale);
}
}
else
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << dwLocale), bOverrideArchive, bAudioLocale);
*/
static int ParseWowRootFile2(
TRootHandler_WoW6 * pRootHandler,
PARSE_ROOT pfnParseRoot,
LPBYTE pbRootFile,
LPBYTE pbRootFileEnd,
DWORD dwLocaleMask,
BYTE bAudioLocale)
{
// Load the locale as-is
ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, false, bAudioLocale);
// If we wanted enGB, we also load enUS for the missing files
if(dwLocaleMask == CASC_LOCALE_ENGB)
ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_ENUS, false, bAudioLocale);
if(dwLocaleMask == CASC_LOCALE_PTPT)
ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_PTBR, false, bAudioLocale);
return ERROR_SUCCESS;
}
// WoW.exe: 004146C7 (BuildManifest::Load)
static int ParseWowRootFile(
TRootHandler_WoW6 * pRootHandler,
PARSE_ROOT pfnParseRoot,
LPBYTE pbRootFile,
LPBYTE pbRootFileEnd,
DWORD dwLocaleMask)
{
ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 0);
ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 1);
return ERROR_SUCCESS;
}
//-----------------------------------------------------------------------------
// Implementation of WoW6 root file
static int WowHandler_Insert(
TRootHandler_WoW6 * pRootHandler,
const char * szFileName,
LPBYTE pbEncodingKey)
{
PCASC_FILE_ENTRY pFileEntry;
DWORD FileDataId = 0;
// Don't let the number of items to overflow
if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
return ERROR_NOT_ENOUGH_MEMORY;
if (pRootHandler->FileDataIdLookupTable.ItemCount >= pRootHandler->FileDataIdLookupTable.ItemCountMax)
return ERROR_NOT_ENOUGH_MEMORY;
// Insert the item to the linear file list
pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
if(pFileEntry != NULL)
{
Array_Insert(&pRootHandler->FileDataIdLookupTable, &pFileEntry, 1);
// Get the file data ID of the previous item (0 if this is the first one)
if(pRootHandler->FileTable.ItemCount > 1)
FileDataId = pFileEntry[-1].FileDataId;
// Fill-in the new entry
pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey;
pFileEntry->FileNameHash = CalcFileNameHash(szFileName);
pFileEntry->FileDataId = FileDataId + 1;
pFileEntry->Locales = CASC_LOCALE_ALL;
// Verify collisions (debug version only)
assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL);
// Insert the entry to the map
Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
}
return ERROR_SUCCESS;
}
static LPBYTE WowHandler_Search(
TRootHandler_WoW6 * pRootHandler,
TCascSearch * pSearch,
PDWORD /* PtrFileSize */,
PDWORD PtrLocaleFlags,
PDWORD PtrFileDataId)
{
PCASC_FILE_ENTRY pFileEntry;
// Only if we have a listfile
if(pSearch->pCache != NULL)
{
// Keep going through the listfile
while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
{
// Find the root entry
pFileEntry = FindRootEntry(pRootHandler->pRootMap, pSearch->szFileName, NULL);
if(pFileEntry != NULL)
{
// Give the caller the locale mask
if(PtrLocaleFlags != NULL)
PtrLocaleFlags[0] = pFileEntry->Locales;
if(PtrFileDataId != NULL)
PtrFileDataId[0] = pFileEntry->FileDataId;
return pFileEntry->EncodingKey.Value;
}
}
}
// No more files
return NULL;
}
static LPBYTE WowHandler_GetKey(TRootHandler_WoW6 * pRootHandler, const char * szFileName)
{
PCASC_FILE_ENTRY pFileEntry;
DWORD FileDataId;
BYTE FileDataIdLE[4];
// Open by FileDataId. The file name must be as following:
// File########.unk, where '#' are hexa-decimal numbers (case insensitive).
// Extension is ignored in that case
if(IsFileDataIdName(szFileName))
{
ConvertStringToBinary(szFileName + 4, 8, FileDataIdLE);
FileDataId = ConvertBytesToInteger_4(FileDataIdLE);
pFileEntry = FindRootEntry(pRootHandler->FileDataIdLookupTable, FileDataId);
}
else
{
// Find by the file name hash
pFileEntry = FindRootEntry(pRootHandler->pRootMap, szFileName, NULL);
}
return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL;
}
static void WowHandler_EndSearch(TRootHandler_WoW6 * /* pRootHandler */, TCascSearch * pSearch)
{
if(pSearch->pRootContext != NULL)
CASC_FREE(pSearch->pRootContext);
pSearch->pRootContext = NULL;
}
static DWORD WowHandler_GetFileId(TRootHandler_WoW6 * pRootHandler, const char * szFileName)
{
PCASC_FILE_ENTRY pFileEntry;
// Find by the file name hash
pFileEntry = FindRootEntry(pRootHandler->pRootMap, szFileName, NULL);
return (pFileEntry != NULL) ? pFileEntry->FileDataId : 0;
}
static void WowHandler_Close(TRootHandler_WoW6 * pRootHandler)
{
if(pRootHandler != NULL)
{
Array_Free(&pRootHandler->FileTable);
Array_Free(&pRootHandler->FileDataIdLookupTable);
Map_Free(pRootHandler->pRootMap);
CASC_FREE(pRootHandler);
}
}
#ifdef _DEBUG
static void TRootHandlerWoW6_Dump(
TCascStorage * hs,
TDumpContext * dc, // Pointer to an opened file
LPBYTE pbRootFile,
DWORD cbRootFile,
const TCHAR * szListFile,
int nDumpLevel)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
CASC_ROOT_BLOCK BlockInfo;
PLISTFILE_MAP pListMap;
QUERY_KEY EncodingKey;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
LPBYTE pbFilePointer;
char szOneLine[0x100];
DWORD i;
// Create the listfile map
pListMap = ListFile_CreateMap(szListFile);
// Dump the root entries
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
{
// Validate the root block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// Dump the locale block
dump_print(dc, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
"=========================================================\n",
BlockInfo.pLocaleBlockHdr->Flags,
BlockInfo.pLocaleBlockHdr->Locales,
BlockInfo.pLocaleBlockHdr->NumberOfFiles);
// Dump the hashes and encoding keys
for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
{
// Dump the entry
dump_print(dc, "%08X %08X-%08X %s %s\n",
(DWORD)(BlockInfo.FileDataIds[i]),
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20),
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash),
StringFromMD5(BlockInfo.pRootEntries[i].EncodingKey.Value, szOneLine),
ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash));
// Find the encoding entry in the encoding table
if(nDumpLevel >= DUMP_LEVEL_ENCODING_FILE)
{
EncodingKey.pbData = BlockInfo.pRootEntries[i].EncodingKey.Value;
EncodingKey.cbData = MD5_HASH_SIZE;
pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL);
CascDumpEncodingEntry(hs, dc, pEncodingEntry, nDumpLevel);
}
}
// Put extra newline
dump_print(dc, "\n");
}
ListFile_FreeMap(pListMap);
}
#endif
//-----------------------------------------------------------------------------
// Public functions
int RootHandler_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask)
{
TRootHandler_WoW6 * pRootHandler;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
int nError;
// Verify the size
if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK))
nError = ERROR_FILE_CORRUPT;
// Allocate the root handler object
hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_WoW6, 1);
if(pRootHandler == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill-in the handler functions
memset(pRootHandler, 0, sizeof(TRootHandler_WoW6));
pRootHandler->Insert = (ROOT_INSERT)WowHandler_Insert;
pRootHandler->Search = (ROOT_SEARCH)WowHandler_Search;
pRootHandler->EndSearch = (ROOT_ENDSEARCH)WowHandler_EndSearch;
pRootHandler->GetKey = (ROOT_GETKEY)WowHandler_GetKey;
pRootHandler->Close = (ROOT_CLOSE)WowHandler_Close;
pRootHandler->GetFileId = (ROOT_GETFILEID)WowHandler_GetFileId;
#ifdef _DEBUG
pRootHandler->Dump = TRootHandlerWoW6_Dump; // Support for ROOT file dump
#endif // _DEBUG
// Count the files that are going to be loaded
ParseWowRootFile(pRootHandler, ParseRoot_CountFiles, pbRootFile, pbRootFileEnd, dwLocaleMask);
pRootHandler->dwTotalFileCount += CASC_EXTRA_FILES;
// Create linear table that will contain all root items
nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, pRootHandler->dwTotalFileCount);
if(nError != ERROR_SUCCESS)
return nError;
// Create sorted table that will contain all root items to lookup by FileDataId
nError = Array_Create(&pRootHandler->FileDataIdLookupTable, PCASC_FILE_ENTRY, pRootHandler->dwTotalFileCount);
if (nError != ERROR_SUCCESS)
return nError;
// Create the map of FileHash ->FileEntry
pRootHandler->pRootMap = Map_Create(pRootHandler->dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash));
if(pRootHandler->pRootMap == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Parse the root file again and insert all files to the map
ParseWowRootFile(pRootHandler, ParseRoot_AddRootEntries, pbRootFile, pbRootFileEnd, dwLocaleMask);
// Sort entries by FileDataId for searches
qsort_pointer_array((void**)pRootHandler->FileDataIdLookupTable.ItemArray, pRootHandler->FileDataIdLookupTable.ItemCount, &FileDataIdCompare, NULL);
return ERROR_SUCCESS;
}