/*****************************************************************************/
/* 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)
{
    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;
                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 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;

#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;
}
