/*****************************************************************************/
/* CascRootFile_MNDX.cpp                  Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Common functions for CascLib                                              */
/* Note: "HOTS" refers to Play.exe, v2.5.0.29049 (Heroes of the Storm Alpha) */
/*---------------------------------------------------------------------------*/
/*   Date    Ver   Who  Comment                                              */
/* --------  ----  ---  -------                                              */
/* 18.05.14  1.00  Lad  The first version of CascRootFile_MNDX.cpp           */
/*****************************************************************************/

#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"

//-----------------------------------------------------------------------------
// Local defines

#define MNDX_MAR_SIGNATURE 0x0052414d           // 'MAR\0'
#define MAR_PACKAGE_NAMES           0           // MAR with package names only
#define MAR_STRIPPED_NAMES          1           // MAR with names where packages were stripped
#define MAR_FULL_NAMES              2           // MAR with full file names
#define MAR_COUNT                   3           // Maximum of 3 MAR files are supported

#define MNDX_SEARCH_INITIALIZING    0
#define MNDX_SEARCH_SEARCHING       2
#define MNDX_SEARCH_FINISHED        4

#define MNDX_MAX_ENTRIES(type)  (0xFFFFFFFF / sizeof(type))

#define MNDX_INVALID_SIZE_T        ((size_t)(-1))

#define MNDX_LAST_CKEY_ENTRY       0x80000000

//-----------------------------------------------------------------------------
// Local structures

typedef union _SETBITS
{
    struct
    {
        DWORD Lower08 : 8;                          // Number of set bits in the lower 1 byte
        DWORD Lower16 : 8;                          // Number of set bits in the lower 2 bytes
        DWORD Lower24 : 8;                          // Number of set bits in the lower 3 bytes
        DWORD Lower32 : 8;                          // Number of set bits in the 32-bit integer
    } u;

    DWORD SetBitsAll;                               // The total set bits mask

} SETBITS, *PSETBITS;

typedef struct _HASH_ENTRY
{
    DWORD NodeIndex;                                // Index of the path node
    DWORD NextIndex;                                // ID of the first subnode in the hash table

    union
    {
        DWORD FragmentOffset;                       // Offset of the path fragment in the TPathFragmentTable
        DWORD ChildTableIndex;                      // Starting search index for the child database (if child database is present)
        char SingleChar;                            // If the upper 24 bits of the FragmentOffset is 0xFFFFFFFF, this single character
    };
                                                    // Otherwise --> Offset to the name fragment table
} HASH_ENTRY, *PHASH_ENTRY;

typedef struct _FILE_MNDX_HEADER
{
    DWORD Signature;                                // 'MNDX'
    DWORD HeaderVersion;                            // Must be <= 2
    DWORD FormatVersion;

} FILE_MNDX_HEADER, *PFILE_MNDX_HEADER;

typedef struct _MNDX_PACKAGE
{
    char * szFileName;                              // Pointer to file name
    size_t nLength;                                 // Length of the file name
    size_t nIndex;                                  // Package index

} MNDX_PACKAGE, *PMNDX_PACKAGE;

// Root file entry for CASC storages with MNDX root file (Heroes of the Storm)
// Corresponds to the in-file structure
typedef struct _MNDX_CKEY_ENTRY
{
    DWORD Flags;                                    // High 8 bits: Flags, low 24 bits: package index
    BYTE  CKey[MD5_HASH_SIZE];                      // Content key for the file
    DWORD ContentSize;                              // Uncompressed file size, in bytes

} MNDX_CKEY_ENTRY, *PMNDX_CKEY_ENTRY;

typedef struct _FILE_MAR_INFO
{
    DWORD MarIndex;
    DWORD MarDataSize;
    DWORD MarDataSizeHi;
    DWORD MarDataOffset;
    DWORD MarDataOffsetHi;
} FILE_MAR_INFO, *PFILE_MAR_INFO;

//-----------------------------------------------------------------------------
// Local variables

unsigned char table_1BA1818[0x800] =
{
    0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
    0x07, 0x07, 0x07, 0x01, 0x07, 0x02, 0x02, 0x01, 0x07, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x05, 0x05, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x05, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x06, 0x06, 0x01, 0x06, 0x02, 0x02, 0x01, 0x06, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x06, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x06, 0x05, 0x05, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x05, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x07, 0x07, 0x01, 0x07, 0x02, 0x02, 0x01, 0x07, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x05, 0x05, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x05, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x06, 0x06, 0x01, 0x06, 0x02, 0x02, 0x01, 0x06, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x06, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x06, 0x05, 0x05, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x05, 0x04, 0x04, 0x01, 0x04, 0x02, 0x02, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x02, 0x01,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x02, 0x07, 0x07, 0x07, 0x03, 0x07, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x04, 0x07, 0x04, 0x04, 0x02, 0x07, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x02, 0x07, 0x05, 0x05, 0x03, 0x05, 0x03, 0x03, 0x02,
    0x07, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x02, 0x05, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x02, 0x07, 0x06, 0x06, 0x03, 0x06, 0x03, 0x03, 0x02,
    0x07, 0x06, 0x06, 0x04, 0x06, 0x04, 0x04, 0x02, 0x06, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x02, 0x06, 0x05, 0x05, 0x03, 0x05, 0x03, 0x03, 0x02,
    0x06, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x02, 0x05, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x02, 0x07, 0x07, 0x07, 0x03, 0x07, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x04, 0x07, 0x04, 0x04, 0x02, 0x07, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x02, 0x07, 0x05, 0x05, 0x03, 0x05, 0x03, 0x03, 0x02,
    0x07, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x02, 0x05, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x02, 0x07, 0x06, 0x06, 0x03, 0x06, 0x03, 0x03, 0x02,
    0x07, 0x06, 0x06, 0x04, 0x06, 0x04, 0x04, 0x02, 0x06, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x02, 0x06, 0x05, 0x05, 0x03, 0x05, 0x03, 0x03, 0x02,
    0x06, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x02, 0x05, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x02,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x04, 0x07, 0x07, 0x07, 0x04, 0x07, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x03,
    0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x04, 0x07, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x03,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x04, 0x07, 0x06, 0x06, 0x04, 0x06, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x03,
    0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x04, 0x06, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x04, 0x07, 0x07, 0x07, 0x04, 0x07, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x03,
    0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x04, 0x07, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x03,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x04, 0x07, 0x06, 0x06, 0x04, 0x06, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x03,
    0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x04, 0x06, 0x05, 0x05, 0x04, 0x05, 0x04, 0x04, 0x03,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x07, 0x07, 0x07, 0x05, 0x07, 0x05, 0x05, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05,
    0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x06, 0x06, 0x05, 0x06, 0x05, 0x05, 0x04,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x05,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x06, 0x05,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07
};

//-----------------------------------------------------------------------------
// Local functions - Number of set bits in an integer

// HOTS: inlined
static SETBITS GetNumberOfSetBits(DWORD Value32)
{
    SETBITS SetBits;

    Value32 = ((Value32 >> 1) & 0x55555555) + (Value32 & 0x55555555);
    Value32 = ((Value32 >> 2) & 0x33333333) + (Value32 & 0x33333333);
    Value32 = ((Value32 >> 4) & 0x0F0F0F0F) + (Value32 & 0x0F0F0F0F);
    SetBits.SetBitsAll = Value32 * 0x01010101;

    return SetBits;
}

static DWORD GetNumberOfSetBits32(DWORD Value32)
{
    return GetNumberOfSetBits(Value32).u.Lower32;
}

static LPBYTE CaptureData(LPBYTE pbRootPtr, LPBYTE pbRootEnd, void * pvBuffer, size_t cbLength)
{
    // Check whether there is enough data in the buffer
    if((pbRootPtr + cbLength) > pbRootEnd)
        return NULL;

    // Copy the data
    memcpy(pvBuffer, pbRootPtr, cbLength);
    return pbRootPtr + cbLength;
}

//-----------------------------------------------------------------------------
// The TPathStop structure

struct TPathStop
{
    TPathStop()
    {
        LoBitsIndex = 0;
        field_4 = 0;
        Count = 0;
        HiBitsIndex_PathFragment = CASC_INVALID_INDEX;
        field_10 = 0xFFFFFFFF;
    }

    TPathStop(DWORD arg_0, DWORD arg_4, DWORD arg_8)
    {
        LoBitsIndex = arg_0;
        field_4 = arg_4;
        Count = arg_8;
        HiBitsIndex_PathFragment = CASC_INVALID_INDEX;
        field_10 = 0xFFFFFFFF;
    }

    DWORD LoBitsIndex;
    DWORD field_4;
    DWORD Count;
    DWORD HiBitsIndex_PathFragment;
    DWORD field_10;
};

//-----------------------------------------------------------------------------
// Basic array implementations

class TByteStream
{
    public:

    // HOTS: 01959990
    TByteStream()
    {
        pbByteData = NULL;
        pvMappedFile = NULL;
        cbByteData = 0;
        field_C = 0;
        hFile = 0;
        hMap = 0;
    }

    // HOTS: 19599F0
    template <typename T>
    int GetBytes(size_t length, T ** Pointer)
    {
        // Is there enough bytes in the array?
        if(length > cbByteData)
            return ERROR_BAD_FORMAT;

        // Give the buffer to the caller
        Pointer[0] = (T *)(pbByteData);

        // Move pointers
        pbByteData += length;
        cbByteData -= length;
        return ERROR_SUCCESS;
    }

    int CopyBytes(void * value, size_t length)
    {
        // Is there enough bytes in the array?
        if(length > cbByteData)
            return ERROR_BAD_FORMAT;

        // Give the buffer to the caller
        memcpy(value, pbByteData, length);

        // Move pointers
        pbByteData += length;
        cbByteData -= length;
        return ERROR_SUCCESS;
    }

    // HOTS: 1959A60
    int SkipBytes(size_t cbByteCount)
    {
        LPBYTE Pointer;

        return GetBytes<BYTE>(cbByteCount, &Pointer);
    }

    // HOTS: 1959AF0
    int SetByteBuffer(LPBYTE pbNewByteData, size_t cbNewByteData)
    {
        if(pbNewByteData != NULL || cbNewByteData == 0)
        {
            pbByteData = pbNewByteData;
            cbByteData = cbNewByteData;
            return ERROR_SUCCESS;
        }

        return ERROR_INVALID_PARAMETER;
    }

    // HOTS: 1957160 <DWORD>
    template <typename T>
    DWORD GetValue(T & Value)
    {
        T * Pointer;
        DWORD dwErrCode;

        dwErrCode = GetBytes(sizeof(T), (LPBYTE *)(&Pointer));
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        Value = Pointer[0];
        return ERROR_SUCCESS;
    }

    // Retrieves the item count in the array
    template <typename T>
    DWORD GetArrayItemCount(DWORD & ArraySize, DWORD & ItemCount)
    {
        ULONGLONG ByteCount;
        DWORD dwErrCode;

        // The first 8 bytes is the byte size of the array
        dwErrCode = GetValue<ULONGLONG>(ByteCount);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        // Extract the number of bytes
        if(ByteCount > 0xFFFFFFFF || (ByteCount % sizeof(T)) != 0)
            return ERROR_BAD_FORMAT;

        // Give the result to the caller
        ItemCount = (DWORD)(ByteCount / sizeof(T));
        ArraySize = (DWORD)(ByteCount);
        return ERROR_SUCCESS;
    }

    // HOTS: 1957190: <DWORD>
    // HOTS: 19571E0: <BASEVALS>
    // HOTS: 1957230: <BYTE>
    // HOTS: 1957280: <HASH_ENTRY>
    template <typename T>
    DWORD GetArray(T ** Pointer, size_t ItemCount)
    {
        DWORD dwErrCode = ERROR_SUCCESS;

        // Verify parameters
        if(Pointer == NULL && ItemCount != 0)
            return ERROR_INVALID_PARAMETER;
        if(ItemCount > MNDX_MAX_ENTRIES(T))
            return ERROR_NOT_ENOUGH_MEMORY;

        // Allocate bytes for the array
        if(Pointer != NULL)
        {
            Pointer[0] = CASC_ALLOC<T>(ItemCount);
            if(Pointer[0] == NULL)
                return ERROR_NOT_ENOUGH_MEMORY;

            // Get the pointer to the array
            dwErrCode = CopyBytes(Pointer[0], sizeof(T) * ItemCount);
        }

        return dwErrCode;
    }

    LPBYTE pbByteData;
    void * pvMappedFile;
    size_t cbByteData;
    DWORD field_C;
    HANDLE hFile;
    HANDLE hMap;
};

//-----------------------------------------------------------------------------
// TGenericArray interface/implementation

template <typename T>
class TGenericArray
{
    public:

    TGenericArray()
    {
        ItemArray = NULL;
        ItemCount = 0;
        MaxItemCount = 0;
        bIsValidArray = false;
    }

    ~TGenericArray()
    {
        CASC_FREE(ItemArray);
    }

    T & operator[] (size_t index)
    {
        assert(index < ItemCount);
        return ItemArray[index];
    }

    // HOTS: 1957090 (SetDwordsValid)
    // HOTS: 19570B0 (SetBaseValsValid)
    // HOTS: 19570D0 (? SetBitsValid ?)
    // HOTS: 19570F0 (SetPathFragmentsValid)
    int SetArrayValid()
    {
        if(bIsValidArray != 0)
            return ERROR_ALREADY_EXISTS;

        bIsValidArray = true;
        return ERROR_SUCCESS;
    }

    // HOTS: 19575A0 (char)
    // HOTS: 1957600 (TPathStop)
    void SetMaxItems(DWORD NewMaxItemCount)
    {
        T * OldArray = ItemArray;
        T * NewArray;

        // Allocate new data buffer
        NewArray = CASC_ALLOC<T>(NewMaxItemCount);
        if(NewArray != NULL)
        {
            // Copy the old items to the buffer
            for(size_t i = 0; i < ItemCount; i++)
            {
                NewArray[i] = ItemArray[i];
            }
        }

        ItemArray = NewArray;
        MaxItemCount = NewMaxItemCount;
        CASC_FREE(OldArray);
    }

    // HOTS: 19575A0 (char)
    // HOTS: 1957600 (TPathStop)
    void SetMaxItemsIf(DWORD NewMaxItemCount)
    {
        if(NewMaxItemCount > MaxItemCount)
        {
            if(MaxItemCount > (NewMaxItemCount / 2))
            {
                if(MaxItemCount <= (MNDX_MAX_ENTRIES(T) / 2))
                    NewMaxItemCount = MaxItemCount + MaxItemCount;
                else
                    NewMaxItemCount = MNDX_MAX_ENTRIES(T);
            }

            SetMaxItems(NewMaxItemCount);
        }
    }

    // HOTS: inline  <char>
    // HOTS: 1958330 <TPathStop>
    void Insert(T NewItem)
    {
        // Make sure we have enough capacity for the new item
        SetMaxItemsIf(ItemCount + 1);

        // Put the character to the slot that has been reserved
        ItemArray[ItemCount++] = NewItem;
    }

    // HOTS: 19583A0 <TPathStop>
    void GrowArray(DWORD NewItemCount)
    {
        DWORD OldMaxItemCount = MaxItemCount;

        // Make sure we have enough capacity for new items
        SetMaxItemsIf(NewItemCount);

        // Initialize the newly inserted items
        for(DWORD i = OldMaxItemCount; i < NewItemCount; i++)
        {
            ItemArray[i] = T();
        }

        ItemCount = NewItemCount;
    }

    // HOTS: 1957440 <DWORD>
    // HOTS: 19574E0 <BASEVALS>
    // HOTS: 1957690 <BYTE>
    // HOTS: 1957700 <HASH_ENTRY>
    // HOTS: 195A220 <char>
    // HOTS: 1958580 <TBitStream, DWORD>
    DWORD LoadFromStream(TByteStream & InStream)
    {
        DWORD NumberOfBytes;
        DWORD dwErrCode;

        // Get and verify the number of items
        dwErrCode = InStream.GetArrayItemCount<T>(NumberOfBytes, ItemCount);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        // Get the pointer to the array
        dwErrCode = InStream.GetArray<T>(&ItemArray, ItemCount);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        return SetArrayValid();
    }

    T * ItemArray;
    DWORD ItemCount;                           // Number of items in the array
    DWORD MaxItemCount;                        // Capacity of the array
    bool bIsValidArray;
};

class TBitEntryArray : public TGenericArray<DWORD>
{
    public:

    TBitEntryArray() : TGenericArray()
    {
        BitsPerEntry = 0;
        EntryBitMask = 0;
        TotalEntries = 0;
    }

    ~TBitEntryArray()
    {}

    DWORD GetItem(DWORD EntryIndex)
    {
        DWORD dwItemIndex = (EntryIndex * BitsPerEntry) >> 0x05;
        DWORD dwStartBit = (EntryIndex * BitsPerEntry) & 0x1F;
        DWORD dwEndBit = dwStartBit + BitsPerEntry;
        DWORD dwResult;

        // If the end bit index is greater than 32,
        // we also need to load from the next 32-bit item
        if(dwEndBit > 0x20)
        {
            dwResult = (ItemArray[dwItemIndex + 1] << (0x20 - dwStartBit)) | (ItemArray[dwItemIndex] >> dwStartBit);
        }
        else
        {
            dwResult = ItemArray[dwItemIndex] >> dwStartBit;
        }

        // Now we also need to mask the result by the bit mask
        return dwResult & EntryBitMask;
    }

    DWORD LoadBitsFromStream(TByteStream & InStream)
    {
        ULONGLONG Value64 = 0;
        DWORD dwErrCode;

        dwErrCode = LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = InStream.GetValue<DWORD>(BitsPerEntry);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;
        if(BitsPerEntry > 0x20)
            return ERROR_BAD_FORMAT;

        dwErrCode = InStream.GetValue<DWORD>(EntryBitMask);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = InStream.GetValue<ULONGLONG>(Value64);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;
        if(Value64 > 0xFFFFFFFF)
            return ERROR_BAD_FORMAT;
        TotalEntries = (DWORD)Value64;

        assert((BitsPerEntry * TotalEntries) / 32 <= ItemCount);
        return ERROR_SUCCESS;
    }

    DWORD BitsPerEntry;
    DWORD EntryBitMask;
    DWORD TotalEntries;
};

//-----------------------------------------------------------------------------
// TSparseArray functions

#define INDEX_TO_GROUP(val)   (val >> 9)
#define GROUP_TO_INDEX(grp)   (grp << 9)

// For each 0x200-th bit, this contains information about amount of "1" bits
typedef struct _BASEVALS
{
    DWORD BaseValue200;             // Item value of every 0x200-th item

    DWORD AddValue40 : 7;           // For each 0x40 items (above the base200),
    DWORD AddValue80 : 8;           // we have extra shortcut to the item value
    DWORD AddValueC0 : 8;           // that is to be added to BaseValue200
    DWORD AddValue100 : 9;
    DWORD AddValue140 : 9;
    DWORD AddValue180 : 9;
    DWORD AddValue1C0 : 9;

    DWORD __xalignment : 5;         // Filling
} BASEVALS, *PBASEVALS;

class TSparseArray
{
    public:

    TSparseArray()
    {
        TotalItemCount = 0;
        ValidItemCount = 0;
    }

    // HOTS: 1958630
    DWORD LoadFromStream(TByteStream & InStream)
    {
        DWORD total_count = 0;
        DWORD valid_count = 0;
        DWORD dwErrCode;

        dwErrCode = ItemBits.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = InStream.GetValue<DWORD>(total_count);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;
        dwErrCode = InStream.GetValue<DWORD>(valid_count);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;
        if(valid_count > total_count)
            return ERROR_FILE_CORRUPT;

        TotalItemCount = total_count;
        ValidItemCount = valid_count;

        dwErrCode = BaseVals.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = IndexToItem0.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = IndexToItem1.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        return ERROR_SUCCESS;
    }

    // Returns true if the array is empty
    bool IsEmpty()
    {
        return (TotalItemCount == 0);
    }

    // Returns true if the item at n-th position is present
    bool IsItemPresent(size_t index)
    {
        // (index >> 0x05) gives the DWORD, (1 << (ItemIndex & 0x1F)) gives the bit
        return (ItemBits[index >> 0x05] & (1 << (index & 0x1F))) ? true : false;
    }

    // Retrieves the value of the n-th item in the sparse array.
    // Note that for items that are not present, the value is equal
    // to the nearest lower present value
    DWORD GetItemValueAt(size_t index)
    {
        BASEVALS & SetBitsCount = BaseVals[index >> 0x09];
        DWORD IntValue;
        DWORD BitMask;

        //
        // Since we don't want to count bits for the entire array,
        // there are item value shortcuts every 0x200 items,
        // and then every 0x40 items above the 0x200 base
        //

        // 1) We have base value for every 0x200-th item
        IntValue = SetBitsCount.BaseValue200;

        // 2) Add the base value for each 0x40-th item above the 0x200 base
        switch(((index >> 0x06) & 0x07) - 1)
        {
            case 0:     // Add the 1st value (7 bits)
                IntValue += SetBitsCount.AddValue40;
                break;

            case 1:     // Add the 2nd value (8 bits)
                IntValue += SetBitsCount.AddValue80;
                break;

            case 2:     // Add the 3rd value (8 bits)
                IntValue += SetBitsCount.AddValueC0;
                break;

            case 3:     // Add the 4th value (9 bits)
                IntValue += SetBitsCount.AddValue100;
                break;

            case 4:     // Add the 5th value (9 bits)
                IntValue += SetBitsCount.AddValue140;
                break;

            case 5:     // Add the 6th value (9 bits)
                IntValue += SetBitsCount.AddValue180;
                break;

            case 6:     // Add the 7th value (9 bits)
                IntValue += SetBitsCount.AddValue1C0;
                break;
        }

        // 3) Count the bits of the higher DWORD, if the index 0x20 - 0x30 above the 0x200 base
        if(index & 0x20)
            IntValue += GetNumberOfSetBits32(ItemBits[(index >> 0x05) - 1]);

        // 4) Count the bits in the current DWORD (masked by bit index mask)
        BitMask = (1 << (index & 0x1F)) - 1;
        return IntValue + GetNumberOfSetBits32(ItemBits[index >> 0x05] & BitMask);
    }

    DWORD FindGroup_Items0(DWORD index)
    {
        // Setup the group range to search
        DWORD minGroup = (IndexToItem0[INDEX_TO_GROUP(index) + 0]) >> 9;
        DWORD maxGroup = (IndexToItem0[INDEX_TO_GROUP(index) + 1] + 0x1FF) >> 9;

        // Search the groups and find the BASEVALS structure
        // For spans less than 10 groups, use sequential search, otherwise binary search.
        if((maxGroup - minGroup) < 10)
        {
            // HOTS: 1959CF7
            while (index >= GROUP_TO_INDEX(minGroup) - BaseVals[minGroup + 1].BaseValue200 + 0x200)
            {
                // HOTS: 1959D14
                minGroup++;
            }
        }
        else
        {
            // HOTS: 1959D2E
            while ((minGroup + 1) < maxGroup)
            {
                // HOTS: 1959D38
                DWORD middleValue = (maxGroup + minGroup) >> 1;

                if(index < (maxGroup << 0x09) - BaseVals[maxGroup].BaseValue200)
                {
                    // HOTS: 01959D4B
                    maxGroup = middleValue;
                }
                else
                {
                    // HOTS: 1959D50
                    minGroup = middleValue;
                }
            }
        }

        return minGroup;
    }

    DWORD FindGroup_Items1(DWORD index)
    {
        DWORD groupIndex = (index >> 0x09);
        DWORD startValue = IndexToItem1[groupIndex] >> 9;
        DWORD nextValue = (IndexToItem1[groupIndex + 1] + 0x1FF) >> 9;

        // Find the BASEVALS structure which the start index belongs to
        // For less than 10 values, use sequential search. Otherwise, use binary search
        if((nextValue - startValue) < 10)
        {
            // HOTS: 01959F94
            while (index >= BaseVals[startValue + 1].BaseValue200)
            {
                // HOTS: 1959FA3
                startValue++;
            }
        }
        else
        {
            // Binary search (HOTS: 1959FAD)
            if((startValue + 1) < nextValue)
            {
                // HOTS: 1959FB4
                DWORD middleValue = (nextValue + startValue) >> 1;

                if(index < BaseVals[middleValue].BaseValue200)
                {
                    // HOTS: 1959FC4
                    nextValue = middleValue;
                }
                else
                {
                    // HOTS: 1959FC8
                    startValue = middleValue;
                }
            }
        }

        return startValue;
    }

    // Returns the value of Item0[index] (HOTS: 1959CB0)
    DWORD GetItem0(DWORD index)
    {
        SETBITS zeroBits;
        DWORD groupIndex;
        DWORD dwordIndex;
        DWORD itemIndex;
        DWORD bitGroup;
        DWORD edx = index;

#ifdef CASCLIB_DEBUG
        //if(TotalItemCount > 0x200)
        //{
        //    FILE * fp = fopen("e:\\Ladik\\Appdir\\CascLib\\doc\\mndx-sparse-array.txt", "wt");
        //    Dump(fp);
        //    fclose(fp);
        //}
#endif

        // If the index is at begin of the group, we just return the start value
        if((index & 0x1FF) == 0)
            return IndexToItem0[INDEX_TO_GROUP(index)];

        // Find the group where the index belongs to
        groupIndex = FindGroup_Items0(index);

        // HOTS: 1959D5F
        edx += BaseVals[groupIndex].BaseValue200 - (groupIndex << 0x09);
        dwordIndex = (groupIndex << 4);

        if(edx < 0x100 - BaseVals[groupIndex].AddValue100)
        {
            // HOTS: 1959D8C
            if(edx < 0x80 - BaseVals[groupIndex].AddValue80)
            {
                // HOTS: 01959DA2
                if(edx >= 0x40 - BaseVals[groupIndex].AddValue40)
                {
                    // HOTS: 01959DB7
                    dwordIndex += 2;
                    edx = edx + BaseVals[groupIndex].AddValue40 - 0x40;
                }
            }
            else
            {
                // HOTS: 1959DC0
                if(edx < 0xC0 - BaseVals[groupIndex].AddValueC0)
                {
                    // HOTS: 1959DD3
                    dwordIndex += 4;
                    edx = edx + BaseVals[groupIndex].AddValue80 - 0x80;
                }
                else
                {
                    // HOTS: 1959DD3
                    dwordIndex += 6;
                    edx = edx + BaseVals[groupIndex].AddValueC0 - 0xC0;
                }
            }
        }
        else
        {
            // HOTS: 1959DE8
            if(edx < 0x180 - BaseVals[groupIndex].AddValue180)
            {
                // HOTS: 01959E00
                if(edx < 0x140 - BaseVals[groupIndex].AddValue140)
                {
                    // HOTS: 1959E11
                    dwordIndex += 8;
                    edx = edx + BaseVals[groupIndex].AddValue100 - 0x100;
                }
                else
                {
                    // HOTS: 1959E1D
                    dwordIndex += 10;
                    edx = edx + BaseVals[groupIndex].AddValue140 - 0x140;
                }
            }
            else
            {
                // HOTS: 1959E29
                if(edx < 0x1C0 - BaseVals[groupIndex].AddValue1C0)
                {
                    // HOTS: 1959E3D
                    dwordIndex += 12;
                    edx = edx + BaseVals[groupIndex].AddValue180 - 0x180;
                }
                else
                {
                    // HOTS: 1959E49
                    dwordIndex += 14;
                    edx = edx + BaseVals[groupIndex].AddValue1C0 - 0x1C0;
                }
            }
        }

        // HOTS: 1959E53:
        // Calculate the number of bits set in the value of "bitGroup"
        bitGroup = ~ItemBits[dwordIndex];
        zeroBits = GetNumberOfSetBits(bitGroup);

        if(edx >= zeroBits.u.Lower32)
        {
            // HOTS: 1959ea4
            bitGroup = ~ItemBits[++dwordIndex];
            edx = edx - zeroBits.u.Lower32;
            zeroBits = GetNumberOfSetBits(bitGroup);
        }

        // Re-calculate the item index
        itemIndex = (dwordIndex << 0x05);

        // HOTS: 1959eea
        if(edx < zeroBits.u.Lower16)
        {
            // HOTS: 1959EFC
            if(edx >= zeroBits.u.Lower08)
            {
                // HOTS: 1959F05
                bitGroup >>= 0x08;
                itemIndex += 0x08;
                edx -= zeroBits.u.Lower08;
            }
        }
        else
        {
            // HOTS: 1959F0D
            if(edx < zeroBits.u.Lower24)
            {
                // HOTS: 1959F19
                bitGroup >>= 0x10;
                itemIndex += 0x10;
                edx -= zeroBits.u.Lower16;
            }
            else
            {
                // HOTS: 1959F23
                bitGroup >>= 0x18;
                itemIndex += 0x18;
                edx -= zeroBits.u.Lower24;
            }
        }

        // HOTS: 1959f2b
        edx = edx << 0x08;
        bitGroup = bitGroup & 0xFF;

        // BUGBUG: Possible buffer overflow here. Happens when dwItemIndex >= 0x9C.
        // The same happens in Heroes of the Storm (build 29049), so I am not sure
        // if this is a bug or a case that never happens
        assert((bitGroup + edx) < sizeof(table_1BA1818));
        return table_1BA1818[bitGroup + edx] + itemIndex;
    }

    DWORD GetItem1(DWORD index)
    {
        SETBITS setBits;
        DWORD distFromBase;
        DWORD groupIndex;
        DWORD dwordIndex;
        DWORD itemIndex;
        DWORD bitGroup;

        // If the index is at begin of the group, we just return the start value
        if((index & 0x1FF) == 0)
            return IndexToItem1[INDEX_TO_GROUP(index)];

        // Find the group where the index belongs to
        groupIndex = FindGroup_Items1(index);

        // Calculate the base200 dword index (HOTS: 1959FD4)
        distFromBase = index - BaseVals[groupIndex].BaseValue200;
        dwordIndex = groupIndex << 0x04;

        // Calculate the dword index including the sub-checkpoint
        if(distFromBase < BaseVals[groupIndex].AddValue100)
        {
            // HOTS: 1959FF1
            if(distFromBase < BaseVals[groupIndex].AddValue80)
            {
                // HOTS: 0195A000
                if(distFromBase >= BaseVals[groupIndex].AddValue40)
                {
                    // HOTS: 195A007
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValue40;
                    dwordIndex += 2;
                }
            }
            else
            {
                // HOTS: 195A00E
                if(distFromBase < BaseVals[groupIndex].AddValueC0)
                {
                    // HOTS: 195A01A
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValue80;
                    dwordIndex += 4;
                }
                else
                {
                    // HOTS: 195A01F
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValueC0;
                    dwordIndex += 6;
                }
            }
        }
        else
        {
            // HOTS: 195A026
            if(distFromBase < BaseVals[groupIndex].AddValue180)
            {
                // HOTS: 195A037
                if(distFromBase < BaseVals[groupIndex].AddValue140)
                {
                    // HOTS: 195A041
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValue100;
                    dwordIndex += 8;
                }
                else
                {
                    // HOTS: 195A048
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValue140;
                    dwordIndex += 10;
                }
            }
            else
            {
                // HOTS: 195A04D
                if(distFromBase < BaseVals[groupIndex].AddValue1C0)
                {
                    // HOTS: 195A05A
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValue180;
                    dwordIndex += 12;
                }
                else
                {
                    // HOTS: 195A061
                    distFromBase = distFromBase - BaseVals[groupIndex].AddValue1C0;
                    dwordIndex += 14;
                }
            }
        }

        // HOTS: 195A066
        bitGroup = ItemBits[dwordIndex];
        setBits = GetNumberOfSetBits(bitGroup);

        // Get total number of set bits in the bit group
        if(distFromBase >= setBits.u.Lower32)
        {
            // HOTS: 195A0B2
            bitGroup = ItemBits[++dwordIndex];
            distFromBase = distFromBase - setBits.u.Lower32;
            setBits = GetNumberOfSetBits(bitGroup);
        }

        // Calculate the item index
        itemIndex = (dwordIndex << 0x05);

        // Get the number of set bits in the lower word (HOTS: 195A0F6)
        if(distFromBase < setBits.u.Lower16)
        {
            // HOTS: 195A111
            if(distFromBase >= setBits.u.Lower08)
            {
                // HOTS: 195A111
                itemIndex = itemIndex + 0x08;
                bitGroup = bitGroup >> 0x08;
                distFromBase = distFromBase - setBits.u.Lower08;
            }
        }
        else
        {
            // HOTS: 195A119
            if(distFromBase < setBits.u.Lower24)
            {
                // HOTS: 195A125
                bitGroup = bitGroup >> 0x10;
                itemIndex = itemIndex + 0x10;
                distFromBase = distFromBase - setBits.u.Lower16;
            }
            else
            {
                // HOTS: 195A12F
                bitGroup = bitGroup >> 0x18;
                itemIndex = itemIndex + 0x18;
                distFromBase = distFromBase - setBits.u.Lower24;
            }
        }

        bitGroup = bitGroup & 0xFF;
        distFromBase = distFromBase << 0x08;

        // BUGBUG: Potential buffer overflow
        // Happens in Heroes of the Storm when index == 0x5B
        assert((bitGroup + distFromBase) < sizeof(table_1BA1818));
        return table_1BA1818[bitGroup + distFromBase] + itemIndex;
    }

#ifdef CASCLIB_DEBUG
    void Dump(FILE * fp)
    {
        size_t * ArrayNormal;
        size_t * ArrayInvert;
        size_t IndexNormal = 0;
        size_t IndexInvert = 0;

        // Output numbers of set bits for every 0x200-th item
        fprintf(fp, "Number of set bits for every 0x200-th index\n"
                    "========================================================\n"
                    "   Index    Base200h  +40  +80  +C0  +100 +140 +180 +1C0\n"
                    "--------------------------------------------------------\n");
        for(size_t i = 0; i < BaseVals.ItemCount; i++)
        {
            fprintf(fp, "[%08zX]: %08x  %04x %04x %04x %04x %04x %04x %04x\n", GROUP_TO_INDEX(i), BaseVals[i].BaseValue200,
                BaseVals[i].AddValue40,
                BaseVals[i].AddValue80,
                BaseVals[i].AddValueC0,
                BaseVals[i].AddValue100,
                BaseVals[i].AddValue140,
                BaseVals[i].AddValue180,
                BaseVals[i].AddValue1C0);
        }
        fprintf(fp, "\n");

        // Output values of Item1 and Item0 for every 0x200-th index
        fprintf(fp, "Item0 and Item1 for every 0x200-th index\n"
                    "========================================\n"
                    "   Index     Item0    Item1\n"
                    "-----------------------------\n");
        for(size_t i = 0; i < IndexToItem0.ItemCount; i++)
        {
            fprintf(fp, "[%08zX]: %08x %08x\n", GROUP_TO_INDEX(i), IndexToItem0[i], IndexToItem1[i]);
        }
        fprintf(fp, "\n");


        // Output values of Item1 and Item0 for every index
        ArrayNormal = new size_t[TotalItemCount];
        ArrayInvert = new size_t[TotalItemCount];
        if(ArrayNormal && ArrayInvert)
        {
            // Invalidate both arrays
            memset(ArrayNormal, 0xFF, TotalItemCount * sizeof(size_t));
            memset(ArrayInvert, 0xFF, TotalItemCount * sizeof(size_t));

            // Load the both arrays
            for(size_t i = 0; i < TotalItemCount; i++)
            {
                if(IsItemPresent(i))
                    ArrayNormal[IndexNormal++] = i;
                else
                    ArrayInvert[IndexInvert++] = i;
            }

            // Output both arrays
            fprintf(fp, "Item0 and Item1 for every index\n"
                        "========================================\n"
                        "   Index     Item0    Item1\n"
                        "-----------------------------\n");
            for(size_t i = 0; i < TotalItemCount; i++)
            {
                char NormalValue[0x20];
                char InvertValue[0x20];

                if(ArrayNormal[i] == MNDX_INVALID_SIZE_T && ArrayInvert[i] == MNDX_INVALID_SIZE_T)
                    break;
                fprintf(fp, "[%08zX]: %8s  %8s\n", i, DumpValue(InvertValue, _countof(InvertValue), ArrayInvert[i]), DumpValue(NormalValue, _countof(NormalValue), ArrayNormal[i]));
            }
            fprintf(fp, "\n");
        }

        // Free both arrays
        delete[] ArrayNormal;
        delete[] ArrayInvert;

        // Output array of all values
        fprintf(fp, "Item List: Index -> Value\n==========================\n");
        for(size_t i = 0; i < TotalItemCount; i++)
        {
            if(IsItemPresent(i))
            {
                fprintf(fp, "[%08zX]: %08x\n", i, GetItemValueAt(i));
            }
            else
            {
                fprintf(fp, "[%08zX]: NOT PRESENT\n", i);
            }
        }
        fprintf(fp, "\n");
    }

    char * DumpValue(char * szBuffer, size_t cchBuffer, size_t value)
    {
        CascStrPrintf(szBuffer, cchBuffer, (value != MNDX_INVALID_SIZE_T) ? "%08zX" : "   -    ", value);
        return szBuffer;
    }
#endif

    TGenericArray<DWORD> ItemBits;              // A bit array for each item. 1 if the item is present.
    size_t TotalItemCount;                      // Total number of items in the array
    size_t ValidItemCount;                      // Number of present items in the array
    TGenericArray<BASEVALS> BaseVals;           // For each 0x200-th item, this contains the number of set bits up to that 0x200-th item
    TGenericArray<DWORD> IndexToItem0;          // Mapping of index to invert item. An "invert" item is an item whose bit in "ItemBits" is zero.
    TGenericArray<DWORD> IndexToItem1;          // Mapping of index to normal item. An "normal" item is an item whose bit in "ItemBits" is set.
};

//-----------------------------------------------------------------------------
// TStruct40 functions

class TStruct40
{
    public:

    TStruct40()
    {
        NodeIndex   = 0;
        ItemCount   = 0;
        PathLength  = 0;
        SearchPhase = MNDX_SEARCH_INITIALIZING;
    }

    // HOTS: 19586B0
    void BeginSearch()
    {
        // HOTS: 19586BD
        PathBuffer.ItemCount = 0;
        PathBuffer.SetMaxItemsIf(0x40);

        // HOTS: 19586E1
        // Set the new item count
        PathStops.GrowArray(0);
        PathStops.SetMaxItemsIf(4);

        PathLength = 0;
        NodeIndex = 0;
        ItemCount = 0;
        SearchPhase = MNDX_SEARCH_SEARCHING;
    }

    DWORD CalcHashValue(const char * szPath)
    {
        return (BYTE)(szPath[PathLength]) ^ (NodeIndex << 0x05) ^ NodeIndex;
    }

    TGenericArray<TPathStop> PathStops;         // Array of path checkpoints
    TGenericArray<char> PathBuffer;             // Buffer for building a file name
    DWORD NodeIndex;                            // ID of a path node being searched; starting with 0
    DWORD PathLength;                           // Length of the path in the PathBuffer
    DWORD ItemCount;
    DWORD SearchPhase;                          // 0 = initializing, 2 = searching, 4 = finished
};

//-----------------------------------------------------------------------------
// Local functions - TMndxSearch

class TMndxSearch
{
    public:

    // HOTS: 01956EE0
    TMndxSearch()
    {
        szSearchMask = NULL;
        cchSearchMask = 0;
        szFoundPath = NULL;
        cchFoundPath = 0;
        nIndex = 0;
    }

    // HOTS: 01956F00
    ~TMndxSearch()
    {}

    // HOTS: 01956E70
    int SetSearchMask(
        const char * szNewSearchMask,
        size_t cchNewSearchMask)
    {
        if(szSearchMask == NULL && cchSearchMask != 0)
            return ERROR_INVALID_PARAMETER;

        Struct40.SearchPhase = MNDX_SEARCH_INITIALIZING;

        szSearchMask = szNewSearchMask;
        cchSearchMask = cchNewSearchMask;
        return ERROR_SUCCESS;
    }

    TStruct40 Struct40;
    const char * szSearchMask;          // Search mask without wildcards
    size_t cchSearchMask;               // Length of the search mask
    const char * szFoundPath;           // Found path name
    size_t cchFoundPath;                // Length of the found path name
    DWORD nIndex;                       // Index of the file name
};

//-----------------------------------------------------------------------------
// TPathFragmentTable class. This class implements table of the path fragments.
// These path fragments can either by terminated by zeros (ASCIIZ)
// or can be marked by the external "PathMarks" structure

class TPathFragmentTable
{
    public:

    // HOTS: 0195A290
    TPathFragmentTable()
    {}

    // HOTS: inlined
    ~TPathFragmentTable()
    {}

    // HOTS: 195A180
    bool ComparePathFragment(TMndxSearch * pSearch, size_t nFragmentOffset)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;

        // Do we have path fragment separators in an external structure?
        if(PathMarks.IsEmpty())
        {
            // Keep searching as long as the name matches with the fragment
            while(PathFragments[nFragmentOffset] == pSearch->szSearchMask[pStruct40->PathLength])
            {
                // Move to the next character
                pStruct40->PathLength++;
                nFragmentOffset++;

                // Is it the end of the fragment or end of the path?
                if(PathFragments[nFragmentOffset] == 0)
                    return true;
                if(pStruct40->PathLength >= pSearch->cchSearchMask)
                    return false;
            }

            return false;
        }
        else
        {
            // Keep searching as long as the name matches with the fragment
            while(PathFragments[nFragmentOffset] == pSearch->szSearchMask[pStruct40->PathLength])
            {
                // Move to the next character
                pStruct40->PathLength++;

                // Is it the end of the path fragment?
                if(PathMarks.IsItemPresent(nFragmentOffset++))
                    return true;
                if(nFragmentOffset >= pSearch->cchSearchMask)
                    return false;
            }

            return false;
        }
    }

    // HOTS: 195A3F0
    void CopyPathFragment(TMndxSearch * pSearch, size_t nFragmentOffset)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;

        // Do we have path fragment separators in an external structure?
        if(PathMarks.IsEmpty())
        {
            // HOTS: 195A40C
            while (PathFragments[nFragmentOffset] != 0)
            {
                // Insert the character to the path being built
                pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]);
            }
        }
        else
        {
            // HOTS: 195A4B3
            while(!PathMarks.IsItemPresent(nFragmentOffset))
            {
                // Insert the character to the path being built
                pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]);
            }
        }
    }

    // HOTS: 195A570
    bool CompareAndCopyPathFragment(TMndxSearch * pSearch, size_t nFragmentOffset)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;

        // Do we have path fragment separators in an external structure?
        if(PathMarks.IsEmpty())
        {
            // Keep copying as long as we don't reach the end of the search mask
            while(pStruct40->PathLength < pSearch->cchSearchMask)
            {
                // HOTS: 195A5A0
                if(PathFragments[nFragmentOffset] != pSearch->szSearchMask[pStruct40->PathLength])
                    return false;

                // HOTS: 195A5B7
                pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]);
                pStruct40->PathLength++;

                // If we found the end of the fragment, return success
                if(PathFragments[nFragmentOffset] == 0)
                    return true;
            }

            // HOTS: 195A660
            // Now we need to copy the rest of the fragment
            while(PathFragments[nFragmentOffset] != 0)
            {
                pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset]);
                nFragmentOffset++;
            }
        }
        else
        {
            // Keep copying as long as we don't reach the end of the search mask
            while(nFragmentOffset < pSearch->cchSearchMask)
            {
                if(PathFragments[nFragmentOffset] != pSearch->szSearchMask[pStruct40->PathLength])
                    return false;

                pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset]);
                pStruct40->PathLength++;

                // If we found the end of the fragment, return success
                if(PathMarks.IsItemPresent(nFragmentOffset++))
                    return true;
            }

            // Now we need to copy the rest of the fragment
            while(!PathMarks.IsItemPresent(nFragmentOffset))
            {
                // HOTS: 195A7A6
                pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]);
            }
        }

        return true;
    }

    // HOTS: 0195A820
    DWORD LoadFromStream(TByteStream & InStream)
    {
        DWORD dwErrCode;

        dwErrCode = PathFragments.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        return PathMarks.LoadFromStream(InStream);
    }

    TGenericArray<char> PathFragments;
    TSparseArray PathMarks;
};

//-----------------------------------------------------------------------------
// TStruct10 functions

class TStruct10
{
    public:

    TStruct10()
    {
        field_0 = 0x03;
        field_4 = 0x200;
        field_8 = 0x1000;
        field_C = 0x20000;
    }

    // HOTS: 1956FD0
    int sub_1956FD0(DWORD dwBitMask)
    {
        switch(dwBitMask & 0xF80)
        {
            case 0x00:
                field_4 = 0x200;
                return ERROR_SUCCESS;

            case 0x80:
                field_4 = 0x80;
                return ERROR_SUCCESS;

            case 0x100:
                field_4 = 0x100;
                return ERROR_SUCCESS;

            case 0x200:
                field_4 = 0x200;
                return ERROR_SUCCESS;

            case 0x400:
                field_4 = 0x400;
                return ERROR_SUCCESS;

            case 0x800:
                field_4 = 0x800;
                return ERROR_SUCCESS;
        }

        return ERROR_INVALID_PARAMETER;
    }

    // HOTS: 1957050
    int sub_1957050(DWORD dwBitMask)
    {
        switch(dwBitMask & 0xF0000)
        {
            case 0x00:
                field_C = 0x20000;
                return ERROR_SUCCESS;

            case 0x10000:
                field_C = 0x10000;
                return ERROR_SUCCESS;

            case 0x20000:
                field_C = 0x20000;
                return ERROR_SUCCESS;
        }

        return ERROR_INVALID_PARAMETER;
    }

    // HOTS: 19572E0
    DWORD sub_19572E0(DWORD dwBitMask)
    {
        DWORD dwSubMask;
        DWORD dwErrCode;

        if(dwBitMask & 0xFFF00000)
            return ERROR_INVALID_PARAMETER;

        dwSubMask = dwBitMask & 0x7F;
        if(dwSubMask)
            field_0 = dwSubMask;

        dwErrCode = sub_1956FD0(dwBitMask);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwSubMask = dwBitMask & 0xF000;
        if(dwSubMask == 0 || dwSubMask == 0x1000)
        {
            field_8 = 0x1000;
            return sub_1957050(dwBitMask);
        }

        if(dwSubMask == 0x2000)
        {
            field_8 = 0x2000;
            return sub_1957050(dwBitMask);
        }

        return ERROR_INVALID_PARAMETER;
    }

    // HOTS: 1957800
    int sub_1957800(DWORD dwBitMask)
    {
        return sub_19572E0(dwBitMask);
    }

    DWORD field_0;
    DWORD field_4;
    DWORD field_8;
    DWORD field_C;
};

//-----------------------------------------------------------------------------
// TFileNameDatabase interface/implementation

class TFileNameDatabase
{
    public:

    // HOTS: 01958730
    TFileNameDatabase()
    {
        HashTableMask = 0;
        field_214 = 0;
        pChildDB = NULL;
    }

    ~TFileNameDatabase()
    {
        delete pChildDB;
    }

    // Returns nonzero if the name fragment match is a single-char match
    bool IsPathFragmentSingleChar(HASH_ENTRY * pHashEntry)
    {
        return ((pHashEntry->FragmentOffset & 0xFFFFFF00) == 0xFFFFFF00);
    }

    // Returns true if the given collision path fragment is a string (aka more than 1 char)
    bool IsPathFragmentString(size_t index)
    {
        return CollisionHiBitsIndexes.IsItemPresent(index);
    }

    // HOTS: 1957350, inlined
    DWORD GetPathFragmentOffset1(DWORD index_lobits)
    {
        DWORD index_hibits = CollisionHiBitsIndexes.GetItemValueAt(index_lobits);

        return (HiBitsTable.GetItem(index_hibits) << 0x08) | LoBitsTable[index_lobits];
    }

    // Retrieves fragment_offset/subtable_index of the path fragment, with check for starting value
    DWORD GetPathFragmentOffset2(DWORD & index_hibits, DWORD index_lobits)
    {
        // If the hi-bits index is invalid, we need to get its starting value
        if(index_hibits == CASC_INVALID_INDEX)
        {
/*
            printf("\n");
            for(DWORD i = 0; i < CollisionHiBitsIndexes.TotalItemCount; i++)
            {
                if(CollisionHiBitsIndexes.IsItemPresent(i))
                    printf("[%02X] = %02X\n", i, CollisionHiBitsIndexes.GetIntValueAt(i));
                else
                    printf("[%02X] = NOT_PRESENT\n", i);
            }
*/
            index_hibits = CollisionHiBitsIndexes.GetItemValueAt(index_lobits);
        }
        else
        {
            index_hibits++;
        }

        // Now we use both NodeIndex and HiBits index for retrieving the path fragment index
        return (HiBitsTable.GetItem(index_hibits) << 0x08) | LoBitsTable[index_lobits];
    }

    // HOTS: 1956DA0
    DWORD Load(LPBYTE pbMarData, size_t cbMarData)
    {
        TByteStream ByteStream;
        DWORD dwSignature;
        DWORD dwErrCode;

        if(pbMarData == NULL && cbMarData != 0)
            return ERROR_INVALID_PARAMETER;

        dwErrCode = ByteStream.SetByteBuffer(pbMarData, cbMarData);
        if(dwErrCode == ERROR_SUCCESS)
        {
            // Get pointer to MAR signature
            dwErrCode = ByteStream.GetValue<DWORD>(dwSignature);
            if(dwErrCode != ERROR_SUCCESS)
                return dwErrCode;

            // Verify the signature
            if(dwSignature != MNDX_MAR_SIGNATURE)
                return ERROR_BAD_FORMAT;

            // HOTS: 1956E11
            dwErrCode = LoadFromStream(ByteStream);
        }

        return dwErrCode;
    }

    // HOTS: 19584B0
    int SetChildDatabase(TFileNameDatabase * pNewDB)
    {
        if(pNewDB != NULL && pChildDB == pNewDB)
            return ERROR_INVALID_PARAMETER;

        if(pChildDB != NULL)
            delete pChildDB;
        pChildDB = pNewDB;
        return ERROR_SUCCESS;
    }

    // HOTS: 1957970
    bool ComparePathFragment(TMndxSearch * pSearch)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;
        PHASH_ENTRY pHashEntry;
        DWORD ColTableIndex;
        DWORD HiBitsIndex;
        DWORD NodeIndex;

        // Calculate the item hash from the current char and fragment ID
        NodeIndex = pStruct40->CalcHashValue(pSearch->szSearchMask) & HashTableMask;
        pHashEntry = &HashTable[NodeIndex];

        // Does the hash value ID match?
        if(pHashEntry->NodeIndex == pStruct40->NodeIndex)
        {
            // Check if there is single character match
            if(!IsPathFragmentSingleChar(pHashEntry))
            {
                // Check if there is a name fragment match
                if(pChildDB != NULL)
                {
                    if(!pChildDB->ComparePathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex))
                        return false;
                }
                else
                {
                    if(!PathFragmentTable.ComparePathFragment(pSearch, pHashEntry->FragmentOffset))
                        return false;
                }
            }
            else
            {
                pStruct40->PathLength++;
            }

            pStruct40->NodeIndex = pHashEntry->NextIndex;
            return true;
        }

        //
        // Conflict: Multiple node IDs give the same table index
        //

        // HOTS: 1957A0E
        ColTableIndex = CollisionTable.GetItem0(pStruct40->NodeIndex) + 1;
        pStruct40->NodeIndex = (ColTableIndex - pStruct40->NodeIndex - 1);
        HiBitsIndex = CASC_INVALID_INDEX;

        // HOTS: 1957A41:
        while(CollisionTable.IsItemPresent(ColTableIndex))
        {
            // HOTS: 1957A41
            // Check if the low 8 bits if the fragment offset contain a single character
            // or an offset to a name fragment
            if(IsPathFragmentString(pStruct40->NodeIndex))
            {
                DWORD FragmentOffset = GetPathFragmentOffset2(HiBitsIndex, pStruct40->NodeIndex);
                DWORD SavePathLength = pStruct40->PathLength;       // HOTS: 1957A83

                // Do we have a child database?
                if(pChildDB != NULL)
                {
                    // HOTS: 1957AEC
                    if(pChildDB->ComparePathFragmentByIndex(pSearch, FragmentOffset))
                        return true;
                }
                else
                {
                    // HOTS: 1957AF7
                    if(PathFragmentTable.ComparePathFragment(pSearch, FragmentOffset))
                        return true;
                }

                // HOTS: 1957B0E
                // If there was partial match with the fragment, end the search
                if(pStruct40->PathLength != SavePathLength)
                    return false;
            }
            else
            {
                // HOTS: 1957B1C
                if(LoBitsTable[pStruct40->NodeIndex] == pSearch->szSearchMask[pStruct40->PathLength])
                {
                    pStruct40->PathLength++;
                    return true;
                }
            }

            // HOTS: 1957B32
            pStruct40->NodeIndex++;
            ColTableIndex++;
        }

        return false;
    }

    // HOTS: 1957B80
    bool ComparePathFragmentByIndex(TMndxSearch * pSearch, DWORD TableIndex)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;
        PHASH_ENTRY pHashEntry;
        DWORD eax;

        // HOTS: 1957B95
        for(;;)
        {
            // Get the hasn table item
            pHashEntry = &HashTable[TableIndex & HashTableMask];

            //
            if(TableIndex == pHashEntry->NextIndex)
            {
                // HOTS: 01957BB4
                if(!IsPathFragmentSingleChar(pHashEntry))
                {
                    // HOTS: 1957BC7
                    if(pChildDB != NULL)
                    {
                        // HOTS: 1957BD3
                        if(!pChildDB->ComparePathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex))
                            return false;
                    }
                    else
                    {
                        // HOTS: 1957BE0
                        if(!PathFragmentTable.ComparePathFragment(pSearch, pHashEntry->FragmentOffset))
                            return false;
                    }
                }
                else
                {
                    // HOTS: 1957BEE
                    if(pSearch->szSearchMask[pStruct40->PathLength] != pHashEntry->SingleChar)
                        return false;
                    pStruct40->PathLength++;
                }

                // HOTS: 1957C05
                TableIndex = pHashEntry->NodeIndex;
                if(TableIndex == 0)
                    return true;

                if(pStruct40->PathLength >= pSearch->cchSearchMask)
                    return false;
            }
            else
            {
                // HOTS: 1957C30
                if(IsPathFragmentString(TableIndex))
                {
                    DWORD FragmentOffset = GetPathFragmentOffset1(TableIndex);

                    // HOTS: 1957C4C
                    if(pChildDB != NULL)
                    {
                        // HOTS: 1957C58
                        if(!pChildDB->ComparePathFragmentByIndex(pSearch, FragmentOffset))
                            return false;
                    }
                    else
                    {
                        // HOTS: 1957350
                        if(!PathFragmentTable.ComparePathFragment(pSearch, FragmentOffset))
                            return false;
                    }
                }
                else
                {
                    // HOTS: 1957C8E
                    if(LoBitsTable[TableIndex] != pSearch->szSearchMask[pStruct40->PathLength])
                        return false;

                    pStruct40->PathLength++;
                }

                // HOTS: 1957CB2
                if(TableIndex <= field_214)
                    return true;

                if(pStruct40->PathLength >= pSearch->cchSearchMask)
                    return false;

                eax = CollisionTable.GetItem1(TableIndex);
                TableIndex = (eax - TableIndex - 1);
            }
        }
    }

    // HOTS: 1958D70
    void CopyPathFragmentByIndex(TMndxSearch * pSearch, DWORD TableIndex)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;
        PHASH_ENTRY pHashEntry;

        // HOTS: 1958D84
        for(;;)
        {
            pHashEntry = &HashTable[TableIndex & HashTableMask];
            if(TableIndex == pHashEntry->NextIndex)
            {
                // HOTS: 1958DA6
                if(!IsPathFragmentSingleChar(pHashEntry))
                {
                    // HOTS: 1958DBA
                    if(pChildDB != NULL)
                    {
                        pChildDB->CopyPathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex);
                    }
                    else
                    {
                        PathFragmentTable.CopyPathFragment(pSearch, pHashEntry->FragmentOffset);
                    }
                }
                else
                {
                    // HOTS: 1958DE7
                    // Insert the low 8 bits to the path being built
                    pStruct40->PathBuffer.Insert(pHashEntry->SingleChar);
                }

                // HOTS: 1958E71
                TableIndex = pHashEntry->NodeIndex;
                if(TableIndex == 0)
                    return;
            }
            else
            {
                // HOTS: 1958E8E
                if(IsPathFragmentString(TableIndex))
                {
                    DWORD FragmentOffset = GetPathFragmentOffset1(TableIndex);

                    // HOTS: 1958EAF
                    if(pChildDB != NULL)
                    {
                        pChildDB->CopyPathFragmentByIndex(pSearch, FragmentOffset);
                    }
                    else
                    {
                        PathFragmentTable.CopyPathFragment(pSearch, FragmentOffset);
                    }
                }
                else
                {
                    // HOTS: 1958F50
                    // Insert one character to the path being built
                    pStruct40->PathBuffer.Insert(LoBitsTable[TableIndex]);
                }

                // HOTS: 1958FDE
                if(TableIndex <= field_214)
                    return;

                TableIndex = 0xFFFFFFFF - TableIndex + CollisionTable.GetItem1(TableIndex);
            }
        }
    }

    // HOTS: 1958B00
    bool CompareAndCopyPathFragment(TMndxSearch * pSearch)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;
        PHASH_ENTRY pHashEntry;
        DWORD HiBitsIndex;
        DWORD ColTableIndex;
        DWORD TableIndex;
/*
        FILE * fp = fopen("E:\\PathFragmentTable.txt", "wt");
        if(fp != NULL)
        {
            for(DWORD i = 0; i < HashTable.ItemCount; i++)
            {
                FragOffs = HashTable[i].FragOffs;
                fprintf(fp, "%02x ('%c') %08X %08X %08X", i, (0x20 <= i && i < 0x80) ? i : 0x20, HashTable[i].ItemIndex, HashTable[i].NextIndex, FragOffs);

                if(FragOffs != 0x00800000)
                {
                    if((FragOffs & 0xFFFFFF00) == 0xFFFFFF00)
                        fprintf(fp, " '%c'", (char)(FragOffs & 0xFF));
                    else
                        fprintf(fp, " %s", &PathFragmentTable.PathFragments[FragOffs]);
                }
                fprintf(fp, "\n");
            }

            fclose(fp);
        }
*/
        // Calculate the item hash from the current char and fragment ID
        TableIndex = pStruct40->CalcHashValue(pSearch->szSearchMask) & HashTableMask;
        pHashEntry = &HashTable[TableIndex];

        // Does the hash value ID match?
        if(pStruct40->NodeIndex == pHashEntry->NodeIndex)
        {
            // If the higher 24 bits are set, then the fragment is just one letter,
            // contained directly in the table.
            if(!IsPathFragmentSingleChar(pHashEntry))
            {
                // HOTS: 1958B59
                if(pChildDB != NULL)
                {
                    if(!pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex))
                        return false;
                }
                else
                {
                    if(!PathFragmentTable.CompareAndCopyPathFragment(pSearch, pHashEntry->FragmentOffset))
                        return false;
                }
            }
            else
            {
                // HOTS: 1958B88
                pStruct40->PathBuffer.Insert(pHashEntry->SingleChar);
                pStruct40->PathLength++;
            }

            // HOTS: 1958BCA
            pStruct40->NodeIndex = pHashEntry->NextIndex;
            return true;
        }

        // HOTS: 1958BE5
        ColTableIndex = CollisionTable.GetItem0(pStruct40->NodeIndex) + 1;
        pStruct40->NodeIndex = (ColTableIndex - pStruct40->NodeIndex - 1);
        HiBitsIndex = CASC_INVALID_INDEX;

        // Keep searching while we have a valid collision table entry
        while(CollisionTable.IsItemPresent(ColTableIndex))
        {
            // If we have high bits in the the bit at NodeIndex is set, it means that there is fragment offset
            // If not, the byte in LoBitsTable is the character
            if(IsPathFragmentString(pStruct40->NodeIndex))
            {
                DWORD FragmentOffset = GetPathFragmentOffset2(HiBitsIndex, pStruct40->NodeIndex);
                DWORD SavePathLength = pStruct40->PathLength;   // HOTS: 1958C62

                // Do we have a child database?
                if(pChildDB != NULL)
                {
                    // HOTS: 1958CCB
                    if(pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, FragmentOffset))
                        return true;
                }
                else
                {
                    // HOTS: 1958CD6
                    if(PathFragmentTable.CompareAndCopyPathFragment(pSearch, FragmentOffset))
                        return true;
                }

                // HOTS: 1958CED
                if(SavePathLength != pStruct40->PathLength)
                    return false;
            }
            else
            {
                // HOTS: 1958CFB
                if(LoBitsTable[pStruct40->NodeIndex] == pSearch->szSearchMask[pStruct40->PathLength])
                {
                    // HOTS: 1958D11
                    pStruct40->PathBuffer.Insert(LoBitsTable[pStruct40->NodeIndex]);
                    pStruct40->PathLength++;
                    return true;
                }
            }

            // HOTS: 1958D11
            pStruct40->NodeIndex++;
            ColTableIndex++;
        }

        return false;
    }

    // HOTS: 1959010
    bool CompareAndCopyPathFragmentByIndex(TMndxSearch * pSearch, DWORD TableIndex)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;
        PHASH_ENTRY pHashEntry;

        // HOTS: 1959024
        for(;;)
        {
            pHashEntry = &HashTable[TableIndex & HashTableMask];
            if(TableIndex == pHashEntry->NextIndex)
            {
                // HOTS: 1959047
                if(!IsPathFragmentSingleChar(pHashEntry))
                {
                    // HOTS: 195905A
                    if(pChildDB != NULL)
                    {
                        if(!pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex))
                            return false;
                    }
                    else
                    {
                        if(!PathFragmentTable.CompareAndCopyPathFragment(pSearch, pHashEntry->FragmentOffset))
                            return false;
                    }
                }
                else
                {
                    // HOTS: 1959092
                    if(pHashEntry->SingleChar != pSearch->szSearchMask[pStruct40->PathLength])
                        return false;

                    // Insert the low 8 bits to the path being built
                    pStruct40->PathBuffer.Insert(pHashEntry->SingleChar);
                    pStruct40->PathLength++;
                }

                // HOTS: 195912E
                TableIndex = pHashEntry->NodeIndex;
                if(TableIndex == 0)
                    return true;
            }
            else
            {
                // HOTS: 1959147
                if(IsPathFragmentString(TableIndex))
                {
                    // HOTS: 195917C
                    DWORD FragmentOffset = GetPathFragmentOffset1(TableIndex);

                    if(pChildDB != NULL)
                    {
                        if(!pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, FragmentOffset))
                            return false;
                    }
                    else
                    {
                        if(!PathFragmentTable.CompareAndCopyPathFragment(pSearch, FragmentOffset))
                            return false;
                    }
                }
                else
                {
                    // HOTS: 195920E
                    if(LoBitsTable[TableIndex] != pSearch->szSearchMask[pStruct40->PathLength])
                        return false;

                    // Insert one character to the path being built
                    pStruct40->PathBuffer.Insert(LoBitsTable[TableIndex]);
                    pStruct40->PathLength++;
                }

                // HOTS: 19592B6
                if(TableIndex <= field_214)
                    return true;

                TableIndex = 0xFFFFFFFF - TableIndex + CollisionTable.GetItem1(TableIndex);
            }

            // HOTS: 19592D5
            if(pStruct40->PathLength >= pSearch->cchSearchMask)
                break;
        }

        CopyPathFragmentByIndex(pSearch, TableIndex);
        return true;
    }

    // HOTS: 1959460
    bool DoSearch(TMndxSearch * pSearch)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;
        TPathStop * pPathStop;
        DWORD edi;

        // Perform action based on the search phase
        switch(pStruct40->SearchPhase)
        {
            case MNDX_SEARCH_INITIALIZING:
            {
                // HOTS: 1959489
                pStruct40->BeginSearch();

                // If the caller passed a part of the search path, we need to find that one
                while (pStruct40->PathLength < pSearch->cchSearchMask)
                {
                    if(!CompareAndCopyPathFragment(pSearch))
                    {
                        pStruct40->SearchPhase = MNDX_SEARCH_FINISHED;
                        return false;
                    }
                }

                // HOTS: 19594b0
                TPathStop PathStop(pStruct40->NodeIndex, 0, pStruct40->PathBuffer.ItemCount);
                pStruct40->PathStops.Insert(PathStop);
                pStruct40->ItemCount = 1;

                if(FileNameIndexes.IsItemPresent(pStruct40->NodeIndex))
                {
                    pSearch->szFoundPath = &pStruct40->PathBuffer[0];
                    pSearch->cchFoundPath = pStruct40->PathBuffer.ItemCount;
                    pSearch->nIndex = FileNameIndexes.GetItemValueAt(pStruct40->NodeIndex);
                    return true;
                }
            }
            // No break here, go straight to the MNDX_SEARCH_SEARCHING

            case MNDX_SEARCH_SEARCHING:
            {
                // HOTS: 1959522
                for(;;)
                {
                    // HOTS: 1959530
                    if(pStruct40->ItemCount == pStruct40->PathStops.ItemCount)
                    {
                        TPathStop * pLastStop;
                        DWORD ColTableIndex;

                        pLastStop = &pStruct40->PathStops[pStruct40->PathStops.ItemCount - 1];

                        ColTableIndex = CollisionTable.GetItem0(pLastStop->LoBitsIndex) + 1;

                        // Insert a new structure
                        TPathStop PathStop(ColTableIndex - pLastStop->LoBitsIndex - 1, ColTableIndex, 0);
                        pStruct40->PathStops.Insert(PathStop);
                    }

                    // HOTS: 19595BD
                    pPathStop = &pStruct40->PathStops[pStruct40->ItemCount];

                    // HOTS: 19595CC
                    if(CollisionTable.IsItemPresent(pPathStop->field_4++))
                    {
                        // HOTS: 19595F2
                        pStruct40->ItemCount++;

                        if(IsPathFragmentString(pPathStop->LoBitsIndex))
                        {
                            DWORD FragmentOffset = GetPathFragmentOffset2(pPathStop->HiBitsIndex_PathFragment, pPathStop->LoBitsIndex);

                            // HOTS: 1959630
                            if(pChildDB != NULL)
                            {
                                // HOTS: 1959649
                                pChildDB->CopyPathFragmentByIndex(pSearch, FragmentOffset);
                            }
                            else
                            {
                                // HOTS: 1959654
                                PathFragmentTable.CopyPathFragment(pSearch, FragmentOffset);
                            }
                        }
                        else
                        {
                            // HOTS: 1959665
                            // Insert one character to the path being built
                            pStruct40->PathBuffer.Insert(LoBitsTable[pPathStop->LoBitsIndex]);
                        }

                        // HOTS: 19596AE
                        pPathStop->Count = pStruct40->PathBuffer.ItemCount;

                        // HOTS: 19596b6
                        if(FileNameIndexes.IsItemPresent(pPathStop->LoBitsIndex))
                        {
                            // HOTS: 19596D1
                            if(pPathStop->field_10 == 0xFFFFFFFF)
                            {
                                // HOTS: 19596D9
                                pPathStop->field_10 = FileNameIndexes.GetItemValueAt(pPathStop->LoBitsIndex);
                            }
                            else
                            {
                                pPathStop->field_10++;
                            }

                            // HOTS: 1959755
                            pSearch->szFoundPath = &pStruct40->PathBuffer[0];
                            pSearch->cchFoundPath = pStruct40->PathBuffer.ItemCount;
                            pSearch->nIndex = pPathStop->field_10;
                            return true;
                        }
                    }
                    else
                    {
                        // HOTS: 19596E9
                        if(pStruct40->ItemCount == 1)
                        {
                            pStruct40->SearchPhase = MNDX_SEARCH_FINISHED;
                            return false;
                        }

                        // HOTS: 19596F5
                        pStruct40->PathStops[pStruct40->ItemCount - 1].LoBitsIndex++;
                        edi = pStruct40->PathStops[pStruct40->ItemCount - 2].Count;
                        pStruct40->PathBuffer.SetMaxItemsIf(edi);

                        // HOTS: 1959749
                        pStruct40->PathBuffer.ItemCount = edi;
                        pStruct40->ItemCount--;
                    }
                }
            }

            case MNDX_SEARCH_FINISHED:
                break;
        }

        return false;
    }

    // HOTS: 1957EF0
    bool FindFileInDatabase(TMndxSearch * pSearch)
    {
        TStruct40 * pStruct40 = &pSearch->Struct40;

        pStruct40->NodeIndex = 0;
        pStruct40->PathLength = 0;
        pStruct40->SearchPhase = MNDX_SEARCH_INITIALIZING;

        if(pSearch->cchSearchMask > 0)
        {
            while(pStruct40->PathLength < pSearch->cchSearchMask)
            {
                // HOTS: 01957F12
                if(!ComparePathFragment(pSearch))
                    return false;
            }
        }

        // HOTS: 1957F26
        if(!FileNameIndexes.IsItemPresent(pStruct40->NodeIndex))
            return false;

        pSearch->szFoundPath   = pSearch->szSearchMask;
        pSearch->cchFoundPath  = pSearch->cchSearchMask;
        pSearch->nIndex = FileNameIndexes.GetItemValueAt(pStruct40->NodeIndex);
        return true;
    }

    // HOTS: 1959790
    DWORD LoadFromStream(TByteStream & InStream)
    {
        DWORD dwBitMask;
        DWORD dwErrCode;

        dwErrCode = CollisionTable.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = FileNameIndexes.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = CollisionHiBitsIndexes.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        // HOTS: 019597CD
        dwErrCode = LoBitsTable.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = HiBitsTable.LoadBitsFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        // HOTS: 019597F5
        dwErrCode = PathFragmentTable.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        // HOTS: 0195980A
        if(CollisionHiBitsIndexes.ValidItemCount != 0 && PathFragmentTable.PathFragments.ItemCount == 0)
        {
            TFileNameDatabase * pNewDB;

            pNewDB = new TFileNameDatabase;
            if(pNewDB == NULL)
                return ERROR_NOT_ENOUGH_MEMORY;

            dwErrCode = SetChildDatabase(pNewDB);
            if(dwErrCode != ERROR_SUCCESS)
                return dwErrCode;

            dwErrCode = pChildDB->LoadFromStream(InStream);
            if(dwErrCode != ERROR_SUCCESS)
                return dwErrCode;
        }

        // HOTS: 0195986B
        dwErrCode = HashTable.LoadFromStream(InStream);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        HashTableMask = HashTable.ItemCount - 1;

        dwErrCode = InStream.GetValue<DWORD>(field_214);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        dwErrCode = InStream.GetValue<DWORD>(dwBitMask);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        return Struct10.sub_1957800(dwBitMask);
    }

    TSparseArray CollisionTable;                // Table of valid collisions, indexed by NodeIndex
    TSparseArray FileNameIndexes;               // Array of file name indexes
    TSparseArray CollisionHiBitsIndexes;        // Table of indexes of high bits (above 8 bits) for collisions

    // This pair of arrays serves for fast conversion from node index to FragmentOffset / FragmentChar
    TGenericArray<BYTE> LoBitsTable;            // Array of lower 8 bits of name fragment offset
    TBitEntryArray      HiBitsTable;            // Array of upper x bits of name fragment offset

    TPathFragmentTable PathFragmentTable;
    TFileNameDatabase * pChildDB;

    TGenericArray<HASH_ENTRY> HashTable;         // Hash table for searching name fragments

    DWORD HashTableMask;                        // Mask to get hash table index from hash value
    DWORD field_214;
    TStruct10 Struct10;
};

//-----------------------------------------------------------------------------
// Local functions - MAR file

class TMndxMarFile
{
    public:

    TMndxMarFile()
    {
        pDatabase = NULL;
        pbMarData = NULL;
        cbMarData = 0;
    }

    ~TMndxMarFile()
    {
        if(pDatabase != NULL)
            delete pDatabase;
        CASC_FREE(pbMarData);
    }

    // HOTS: 00E94180
    int LoadRootData(FILE_MAR_INFO & MarInfo, LPBYTE pbRootFile, LPBYTE pbRootEnd)
    {
        // Allocate the MAR data
        pbMarData = CASC_ALLOC<BYTE>(MarInfo.MarDataSize);
        cbMarData = MarInfo.MarDataSize;
        if(pbMarData == NULL)
            return ERROR_NOT_ENOUGH_MEMORY;

        // Capture the MAR data
        if(!CaptureData(pbRootFile + MarInfo.MarDataOffset, pbRootEnd, pbMarData, cbMarData))
            return ERROR_FILE_CORRUPT;

        // Create the file name database
        pDatabase = new TFileNameDatabase();
        if(pDatabase == NULL)
            return ERROR_NOT_ENOUGH_MEMORY;

        return pDatabase->Load(pbMarData, cbMarData);
    }

    // HOTS: 1956C60
    DWORD SearchFile(TMndxSearch * pSearch)
    {
        DWORD dwErrCode = ERROR_SUCCESS;

        if(pDatabase == NULL)
            return ERROR_INVALID_PARAMETER;

        if(!pDatabase->FindFileInDatabase(pSearch))
            dwErrCode = ERROR_FILE_NOT_FOUND;

        return dwErrCode;
    }

    // HOTS: 1956CE0
    DWORD DoSearch(TMndxSearch * pSearch, bool * pbFindResult)
    {
        DWORD dwErrCode = ERROR_SUCCESS;

        if(pDatabase == NULL)
            return ERROR_INVALID_PARAMETER;

        *pbFindResult = pDatabase->DoSearch(pSearch);
        return dwErrCode;
    }

    // HOTS: 1956D20
    int GetFileNameCount(size_t * PtrFileNameCount)
    {
        if(pDatabase == NULL)
            return ERROR_INVALID_PARAMETER;

        PtrFileNameCount[0] = pDatabase->FileNameIndexes.ValidItemCount;
        return ERROR_SUCCESS;
    }

//  protected:
    TFileNameDatabase * pDatabase;
    LPBYTE pbMarData;
    size_t cbMarData;
};

//-----------------------------------------------------------------------------
// Implementation of root file functions

typedef struct _FILE_MNDX_INFO
{
    BYTE  RootFileName[MD5_HASH_SIZE];              // Name (aka MD5) of the root file
    DWORD HeaderVersion;                            // Must be <= 2
    DWORD FormatVersion;
    DWORD field_1C;
    DWORD field_20;
    DWORD MarInfoOffset;                            // Offset of the first MAR entry info
    DWORD MarInfoCount;                             // Number of the MAR info entries
    DWORD MarInfoSize;                              // Size of the MAR info entry
    DWORD CKeyEntriesOffset;                        // Offset of the CKey entries, relative to begin of the root file
    DWORD CKeyEntriesCount;                         // Number of CKeys (files) in the root file
    DWORD FileNameCount;                            // Number of unique file names. More files with the same name in the different packages can exist
    DWORD CKeyEntrySize;                            // Size of one CKey root entry
    TMndxMarFile * MarFiles[MAR_COUNT];             // File name list for the packages

} FILE_MNDX_INFO, *PFILE_MNDX_INFO;

struct TMndxHandler
{
    public:

    //
    //  Constructor and destructor
    //

    TMndxHandler()
    {
        memset(this, 0, sizeof(TMndxHandler));
    }

    ~TMndxHandler()
    {
        PMNDX_PACKAGE pPackage;
        size_t i;

        for(i = 0; i < MAR_COUNT; i++)
            delete MndxInfo.MarFiles[i];
        CASC_FREE(FileNameIndexToCKeyIndex);
        pCKeyEntries = NULL;

        for(i = 0; i < Packages.ItemCount(); i++)
        {
            pPackage = (PMNDX_PACKAGE)Packages.ItemAt(i);
            CASC_FREE(pPackage->szFileName);
        }
        Packages.Free();
    }

    //
    //  Helper functions
    //

    static LPBYTE CaptureRootHeader(FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
    {
        // Capture the root header
        pbRootPtr = CaptureData(pbRootPtr, pbRootEnd, &MndxHeader, sizeof(FILE_MNDX_HEADER));
        if(pbRootPtr == NULL)
            return NULL;

        // Check signature and version
        if(MndxHeader.Signature != CASC_MNDX_ROOT_SIGNATURE || MndxHeader.FormatVersion > 2 || MndxHeader.FormatVersion < 1)
            return NULL;

        // Passed
        return pbRootPtr + sizeof(FILE_MNDX_HEADER);
    }

    DWORD LoadPackageNames()
    {
        TMndxMarFile * pMarFile = MndxInfo.MarFiles[MAR_PACKAGE_NAMES];
        TMndxSearch Search;
        PMNDX_PACKAGE pPackage;
        size_t nPackageCount = 0x40;
        bool bFindResult = false;
        DWORD dwErrCode;

        // Prepare the file name search in the top level directory
        Search.SetSearchMask("", 0);

        // Allocate initial name list structure
        pMarFile->GetFileNameCount(&nPackageCount);
        dwErrCode = Packages.Create<MNDX_PACKAGE>(nPackageCount);
        if(dwErrCode != ERROR_SUCCESS)
            return dwErrCode;

        // Reset the package array
        Packages.Reset();

        // Keep searching as long as we find something
        while(pMarFile->DoSearch(&Search, &bFindResult) == ERROR_SUCCESS && bFindResult)
        {
            // Insert new package to the array
            assert(Search.nIndex < nPackageCount);
            pPackage = (PMNDX_PACKAGE)Packages.InsertAt(Search.nIndex);
            if(pPackage != NULL)
            {
                // The package mut not be initialized yet
                assert(pPackage->szFileName == NULL);

                // Allocate space for the file name
                pPackage->szFileName = CASC_ALLOC<char>(Search.cchFoundPath + 1);
                if(pPackage->szFileName == NULL)
                    return ERROR_NOT_ENOUGH_MEMORY;

                // Fill the package structure
                memcpy(pPackage->szFileName, Search.szFoundPath, Search.cchFoundPath);
                pPackage->szFileName[Search.cchFoundPath] = 0;
                pPackage->nLength = Search.cchFoundPath;
                pPackage->nIndex = Search.nIndex;
            }
        }

        // Give the packages to the caller
        return ERROR_SUCCESS;
    }

    DWORD Load(const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd)
    {
        TMndxMarFile * pMarFile;
        FILE_MAR_INFO MarInfo;
        size_t nFilePointer = 0;
        DWORD i;
        DWORD dwErrCode = ERROR_SUCCESS;

        // Copy the header into the MNDX info
        MndxInfo.HeaderVersion = MndxHeader.HeaderVersion;
        MndxInfo.FormatVersion = MndxHeader.FormatVersion;
        nFilePointer += sizeof(FILE_MNDX_HEADER);

        // Header version 2 has 2 extra fields that we need to load
        if(MndxInfo.HeaderVersion == 2)
        {
            if(!CaptureData(pbRootFile + nFilePointer, pbRootEnd, &MndxInfo.field_1C, sizeof(DWORD) + sizeof(DWORD)))
                return ERROR_FILE_CORRUPT;
            nFilePointer += sizeof(DWORD) + sizeof(DWORD);
        }

        // Load the rest of the file header
        if(!CaptureData(pbRootFile + nFilePointer, pbRootEnd, &MndxInfo.MarInfoOffset, 0x1C))
            return ERROR_FILE_CORRUPT;

        // Verify the structure
        if(MndxInfo.MarInfoCount > MAR_COUNT || MndxInfo.MarInfoSize != sizeof(FILE_MAR_INFO))
            return ERROR_FILE_CORRUPT;

        // Load all MAR infos
        for(i = 0; i < MndxInfo.MarInfoCount; i++)
        {
            // Capture the n-th MAR info
            nFilePointer = MndxInfo.MarInfoOffset + (MndxInfo.MarInfoSize * i);
            if(!CaptureData(pbRootFile + nFilePointer, pbRootEnd, &MarInfo, sizeof(FILE_MAR_INFO)))
                return ERROR_FILE_CORRUPT;

            // Allocate MAR_FILE structure
            pMarFile = new TMndxMarFile();
            if(pMarFile == NULL)
            {
                dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }

            // Create the database from the MAR data
            dwErrCode = pMarFile->LoadRootData(MarInfo, pbRootFile, pbRootEnd);
            if(dwErrCode != ERROR_SUCCESS)
                break;

            // Assign the MAR file to the MNDX info structure
            MndxInfo.MarFiles[i] = pMarFile;
        }

        // All three MAR files must be loaded
        // HOTS: 00E9503B
        if(dwErrCode == ERROR_SUCCESS)
        {
            if(MndxInfo.MarFiles[MAR_PACKAGE_NAMES] == NULL || MndxInfo.MarFiles[MAR_STRIPPED_NAMES] == NULL || MndxInfo.MarFiles[MAR_FULL_NAMES] == NULL)
                dwErrCode = ERROR_BAD_FORMAT;
            if(MndxInfo.CKeyEntrySize != sizeof(MNDX_CKEY_ENTRY))
                dwErrCode = ERROR_BAD_FORMAT;
        }

        // Load the array of Ckey entries. All present files are in the array,
        // the same names (differentiated by package ID) are groupped together
        if(dwErrCode == ERROR_SUCCESS)
        {
            size_t CKeyEntriesSize;
            size_t FileNameCount = 0;

            pMarFile = MndxInfo.MarFiles[MAR_STRIPPED_NAMES];
            dwErrCode = ERROR_FILE_CORRUPT;

            // Capture the array of CKey entries
            if(pMarFile->GetFileNameCount(&FileNameCount) == ERROR_SUCCESS && FileNameCount == MndxInfo.FileNameCount)
            {
                CKeyEntriesSize = MndxInfo.CKeyEntriesCount * MndxInfo.CKeyEntrySize;
                if((pbRootFile + MndxInfo.CKeyEntriesOffset + CKeyEntriesSize) <= pbRootEnd)
                {
                    pCKeyEntries = (PMNDX_CKEY_ENTRY)(pbRootFile + MndxInfo.CKeyEntriesOffset);
                    dwErrCode = ERROR_SUCCESS;
                }
            }
        }

        // Pick the CKey entries that are the first with a given name
        if(dwErrCode == ERROR_SUCCESS)
        {
            assert(MndxInfo.FileNameCount <= MndxInfo.CKeyEntriesCount);
            FileNameIndexToCKeyIndex = CASC_ALLOC<PMNDX_CKEY_ENTRY>(MndxInfo.FileNameCount + 1);
            if(FileNameIndexToCKeyIndex != NULL)
            {
                PMNDX_CKEY_ENTRY pRootEntry = pCKeyEntries;
                DWORD nFileNameIndex = 0;

                // The first entry is always beginning of a file name group
                FileNameIndexToCKeyIndex[nFileNameIndex++] = pRootEntry;

                // Get the remaining file name groups
                for(i = 0; i < MndxInfo.CKeyEntriesCount; i++, pRootEntry++)
                {
                    if(nFileNameIndex > MndxInfo.FileNameCount)
                        break;

                    if(pRootEntry->Flags & MNDX_LAST_CKEY_ENTRY)
                    {
                        FileNameIndexToCKeyIndex[nFileNameIndex++] = pRootEntry + 1;
                    }
                }

                // Verify the final number of file names
                if((nFileNameIndex - 1) != MndxInfo.FileNameCount)
                    dwErrCode = ERROR_BAD_FORMAT;
            }
            else
                dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
        }

        // Load the package names from the 0-th MAR file
        if(dwErrCode == ERROR_SUCCESS)
        {
            dwErrCode = LoadPackageNames();
        }

        return dwErrCode;
    }

    DWORD LoadFileNames(TCascStorage * hs, CASC_FILE_TREE & FileTree)
    {
        PCASC_CKEY_ENTRY pCKeyEntry;
        PMNDX_CKEY_ENTRY pRootEntry;
        PMNDX_CKEY_ENTRY pRootEnd = pCKeyEntries + MndxInfo.CKeyEntriesCount;
        PMNDX_PACKAGE pPackage;
        TMndxMarFile * pMarFile = MndxInfo.MarFiles[MAR_STRIPPED_NAMES];
        TMndxSearch Search;
        char szFileName[MAX_PATH];
        bool bFindResult = false;
        DWORD dwErrCode;

        // Setup the search mask
        Search.SetSearchMask("", 0);

        // Keep searching ad long as we found something
        while ((dwErrCode = pMarFile->DoSearch(&Search, &bFindResult)) == ERROR_SUCCESS && bFindResult)
        {
            // Sanity check
            assert(Search.cchFoundPath < MAX_PATH);

            // The found file name index must fall into range of file names
            if(Search.nIndex < MndxInfo.FileNameCount)
            {
                // Retrieve the first-in-group CKey entry of that name
                pRootEntry = FileNameIndexToCKeyIndex[Search.nIndex];

                // Now take all files of that name, prepend their package name and insert to file tree
                while(pRootEntry < pRootEnd)
                {
                    // Find the appropriate CKey entry in the central storage
                    pCKeyEntry = FindCKeyEntry_CKey(hs, pRootEntry->CKey);
                    if(pCKeyEntry != NULL)
                    {
                        size_t nPackageIndex = pRootEntry->Flags & 0x00FFFFFF;

                        // Retrieve the package for this entry
                        pPackage = (PMNDX_PACKAGE)Packages.ItemAt(nPackageIndex);
                        if(pPackage != NULL)
                        {
                            // Sanity check
                            assert(pPackage->nIndex == nPackageIndex);

                            // Merge the package name and file name
                            MakeFileName(szFileName, _countof(szFileName), pPackage, &Search);

                            // Insert the entry to the file tree
                            FileTree.InsertByName(pCKeyEntry, szFileName);
                        }
                    }

                    // Is this the last-in-group entry?
                    if(pRootEntry->Flags & MNDX_LAST_CKEY_ENTRY)
                        break;
                    pRootEntry++;
                }
            }
        }

        return dwErrCode;
    }

    //
    //  Helper functions
    //

    void MakeFileName(char * szBuffer, size_t cchBuffer, PMNDX_PACKAGE pPackage, TMndxSearch * pSearch)
    {
        char * szBufferEnd = szBuffer + cchBuffer - 1;

        // Buffer length check
        assert((pPackage->nLength + 1 + pSearch->cchFoundPath + 1) < cchBuffer);

        // Copy the package name
        if((szBuffer + pPackage->nLength) < szBufferEnd)
        {
            memcpy(szBuffer, pPackage->szFileName, pPackage->nLength);
            szBuffer += pPackage->nLength;
        }

        // Append slash
        if((szBuffer + 1) < szBufferEnd)
            *szBuffer++ = '/';

        // Append file name
        if((szBuffer + pSearch->cchFoundPath) < szBufferEnd)
        {
            memcpy(szBuffer, pSearch->szFoundPath, pSearch->cchFoundPath);
            szBuffer += pSearch->cchFoundPath;
        }

        szBuffer[0] = 0;
    }

    protected:

    FILE_MNDX_INFO MndxInfo;

    PMNDX_CKEY_ENTRY * FileNameIndexToCKeyIndex;
    PMNDX_CKEY_ENTRY pCKeyEntries;
    CASC_ARRAY Packages;                        // Linear list of present packages
};

//-----------------------------------------------------------------------------
// Handler definition for MNDX root file

struct TRootHandler_MNDX : public TFileTreeRoot
{
    public:

    TRootHandler_MNDX() : TFileTreeRoot(0)
    {
        // MNDX supports file names and CKeys
        dwFeatures |= CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY;
    }

    DWORD Load(TCascStorage * hs, const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd)
    {
        TMndxHandler Handler;
        DWORD dwErrCode;

        // Load and parse the entire MNDX structure
        dwErrCode = Handler.Load(MndxHeader, pbRootFile, pbRootEnd);
        if(dwErrCode == ERROR_SUCCESS)
        {
            // Search all file names and insert them into the file tree
            dwErrCode = Handler.LoadFileNames(hs, FileTree);
        }

        return dwErrCode;
    }
};

//-----------------------------------------------------------------------------
// Public functions - MNDX info

DWORD RootHandler_CreateMNDX(TCascStorage * hs, CASC_BLOB & RootFile)
{
    TRootHandler_MNDX * pRootHandler = NULL;
    FILE_MNDX_HEADER MndxHeader;
    LPBYTE pbRootFile = RootFile.pbData;
    LPBYTE pbRootEnd = RootFile.End();
    DWORD dwErrCode = ERROR_BAD_FORMAT;

    // Verify the header of the ROOT file
    if(TMndxHandler::CaptureRootHeader(MndxHeader, pbRootFile, pbRootEnd) != NULL)
    {
        // Allocate the root handler object
        pRootHandler = new TRootHandler_MNDX();
        if(pRootHandler != NULL)
        {
            // Load the root directory. If load failed, we free the object
            dwErrCode = pRootHandler->Load(hs, MndxHeader, pbRootFile, pbRootEnd);
            if(dwErrCode != ERROR_SUCCESS)
            {
                delete pRootHandler;
                pRootHandler = NULL;
            }
        }
    }

    // Assign the root directory (or NULL) and return error
    hs->pRootHandler = pRootHandler;
    return dwErrCode;
}
