mux2.4/game/data/
mux2.4/src/tools/
// svdhash.cpp -- CHashPage, CHashFile, CHashTable modules.
//
// $Id: svdhash.cpp,v 1.37 2005/10/13 15:18:28 sdennis Exp $
//
// MUX 2.4
// Copyright (C) 1998 through 2004 Solid Vertical Domains, Ltd. All
// rights not explicitly given are reserved.
//
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#define DO_COMMIT

int cs_writes   = 0;    // total writes
int cs_reads    = 0;    // total reads
int cs_dels     = 0;    // total deletes
int cs_fails    = 0;    // attempts to grab nonexistent
int cs_syncs    = 0;    // total cache syncs
int cs_dbreads  = 0;    // total read-throughs
int cs_dbwrites = 0;    // total write-throughs
int cs_whits    = 0;    // writes into cached pages
int cs_rhits    = 0;    // read from cached pages

static const UINT32 CRC32_Table[256] =
{
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
    0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
    0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
    0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
    0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
    0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
    0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
    0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
    0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
    0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
    0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
    0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
    0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
    0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
    0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
    0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
    0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
    0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
    0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
    0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
    0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
    0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
    0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
    0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
    0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
    0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
    0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
    0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
    0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
    0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
    0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
    0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
    0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
    0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
    0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
    0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
    0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
    0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
    0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
    0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
    0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
    0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
    0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

// Portable CRC-32 routine. These slower routines are less compiler and
// platform dependent and still get the job done.
//
UINT32 CRC32_ProcessBuffer
(
    UINT32         ulCrc,
    const void    *arg_pBuffer,
    unsigned int   nBuffer
)
{
    UINT8 *pBuffer = (UINT8 *)arg_pBuffer;

    ulCrc = ~ulCrc;
    while (nBuffer--)
    {
        ulCrc  = CRC32_Table[((UINT8)*pBuffer++) ^ (UINT8)ulCrc] ^ (ulCrc >> 8);
    }
    return ~ulCrc;
}

UINT32 CRC32_ProcessInteger(UINT32 nInteger)
{
    UINT32 ulCrc;
    ulCrc  = ~nInteger;
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    return ~ulCrc;
}

UINT32 CRC32_ProcessInteger2(UINT32 nInteger1, UINT32 nInteger2)
{
    UINT32 ulCrc;
    ulCrc  = ~nInteger1;
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc ^= nInteger2;
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    ulCrc  = CRC32_Table[(UINT8)ulCrc] ^ (ulCrc >> 8);
    return ~ulCrc;
}

#define DO1(buf,i)  {s1 += buf[i]; s2 += s1;}
#define DO2(buf,i)  DO1(buf,i); DO1(buf,i+1);
#define DO4(buf,i)  DO2(buf,i); DO2(buf,i+2);
#define DO8(buf,i)  DO4(buf,i); DO4(buf,i+4);
#define DO16(buf)   DO8(buf,0); DO8(buf,8);

UINT32 HASH_ProcessBuffer
(
    UINT32       ulHash,
    const void  *arg_pBuffer,
    size_t       nBuffer
)
{
    UINT8 *pBuffer = (UINT8 *)arg_pBuffer;
    ulHash = ~ulHash;

    if (nBuffer <= 16)
    {
        pBuffer -= 16 - nBuffer;
        switch (nBuffer)
        {
        case 16: ulHash  = CRC32_Table[pBuffer[0] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 15: ulHash  = CRC32_Table[pBuffer[1] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 14: ulHash  = CRC32_Table[pBuffer[2] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 13: ulHash  = CRC32_Table[pBuffer[3] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 12: ulHash  = CRC32_Table[pBuffer[4] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 11: ulHash  = CRC32_Table[pBuffer[5] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 10: ulHash  = CRC32_Table[pBuffer[6] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 9:  ulHash  = CRC32_Table[pBuffer[7] ^ (UINT8)ulHash] ^ (ulHash >> 8);
#if defined(UNALIGNED32) && defined(WORDS_LITTLEENDIAN)
        case 8:  ulHash ^= *(UINT32 *)(pBuffer + 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash ^= *(UINT32 *)(pBuffer + 12);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 return ~ulHash;
#else
        case 8:  ulHash  = CRC32_Table[pBuffer[8] ^ (UINT8)ulHash] ^ (ulHash >> 8);
#endif

        case 7:  ulHash  = CRC32_Table[pBuffer[9] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 6:  ulHash  = CRC32_Table[pBuffer[10] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 5:  ulHash  = CRC32_Table[pBuffer[11] ^ (UINT8)ulHash] ^ (ulHash >> 8);
#if defined(UNALIGNED32) && defined(WORDS_LITTLEENDIAN)
        case 4:  ulHash ^= *(UINT32 *)(pBuffer + 12);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
                 return ~ulHash;
#else
        case 4:  ulHash  = CRC32_Table[pBuffer[12] ^ (UINT8)ulHash] ^ (ulHash >> 8);
#endif

        case 3:  ulHash  = CRC32_Table[pBuffer[13] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 2:  ulHash  = CRC32_Table[pBuffer[14] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 1:  ulHash  = CRC32_Table[pBuffer[15] ^ (UINT8)ulHash] ^ (ulHash >> 8);
        case 0:  return ~ulHash;
        }
    }

    size_t nSmall  = nBuffer & 15;
    size_t nMedium = (nBuffer >> 4) & 255;
    size_t nLarge  = nBuffer >> 12;

    UINT32 s1 = ulHash & 0xFFFF;
    UINT32 s2 = (ulHash >> 16) & 0xFFFF;

    while (nLarge--)
    {
        int k = 256;
        while (k)
        {
            DO16(pBuffer);
            pBuffer += 16;
            k--;
        }
        ulHash  = ~s1;
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash ^= s2;
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
        ulHash = ~ulHash;
        s1 = ulHash & 0xFFFF;
        s2 = (ulHash >> 16) & 0xFFFF;
    }

    while (nMedium--)
    {
        DO16(pBuffer);
        pBuffer += 16;
    }

    pBuffer -= 15 - nSmall;
    switch (nSmall)
    {
    case 15: s1 += pBuffer[0];  s2 += s1;
    case 14: s1 += pBuffer[1];  s2 += s1;
    case 13: s1 += pBuffer[2];  s2 += s1;
    case 12: s1 += pBuffer[3];  s2 += s1;
    case 11: s1 += pBuffer[4];  s2 += s1;
    case 10: s1 += pBuffer[5];  s2 += s1;
    case 9:  s1 += pBuffer[6];  s2 += s1;
    case 8:  s1 += pBuffer[7];  s2 += s1;
    case 7:  s1 += pBuffer[8];  s2 += s1;
    case 6:  s1 += pBuffer[9];  s2 += s1;
    case 5:  s1 += pBuffer[10]; s2 += s1;
    case 4:  s1 += pBuffer[11]; s2 += s1;
    case 3:  s1 += pBuffer[12]; s2 += s1;
    case 2:  s1 += pBuffer[13]; s2 += s1;
    case 1:  s1 += pBuffer[14]; s2 += s1;
    case 0:  break;
    }

    ulHash  = ~s1;
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash ^= s2;
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    ulHash  = CRC32_Table[(UINT8)ulHash] ^ (ulHash >> 8);
    return ~ulHash;
}

#define NUMBER_OF_PRIMES 177
const int Primes[NUMBER_OF_PRIMES] =
{

    1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
    71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
    157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239,
    241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337,
    347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433,
    439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
    547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641,
    643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743,
    751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857,
    859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971,
    977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0
};

void ChoosePrimes(int TableSize, HP_HEAPOFFSET HashPrimes[16])
{
    int LargestPrime = TableSize/2;
    if (LargestPrime > Primes[NUMBER_OF_PRIMES-2])
    {
        LargestPrime = Primes[NUMBER_OF_PRIMES-2];
    }
    int Spacing = LargestPrime/16;

    // Pick a set primes that are evenly spaced from (0 to LargestPrime)
    // We divide this interval into 16 equal sized zones. We want to find
    // one prime number that best represents that zone.
    //
    int iZone, iPrime;
    for (iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing)
    {
        // Search for a prime number that is less than the target zone
        // number given by iZone.
        //
        int Lower = Primes[0];
        for (int jPrime = 0; Primes[jPrime] != 0; jPrime++)
        {
            if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue;
            int Upper = Primes[jPrime];
            if (Lower <= iZone && iZone <= Upper)
            {
                // Choose the closest lower prime number.
                //
                if (iZone - Lower <= Upper - iZone)
                {
                    HashPrimes[iPrime++] = Lower;
                }
                else
                {
                    HashPrimes[iPrime++] = Upper;
                }
                break;
            }
            Lower = Upper;
        }
    }

    // Alternate negative and positive numbers
    //
    for (iPrime = 0; iPrime < 16; iPrime += 2)
    {
        HashPrimes[iPrime] = TableSize-HashPrimes[iPrime];
    }

    // Shuffle the set of primes to reduce correlation with bits in
    // hash key.
    //
    for (iPrime = 0; iPrime < 16-1; iPrime++)
    {
        int Pick = (int)RandomINT32(0, 15-iPrime);
        HP_HEAPOFFSET Temp = HashPrimes[Pick];
        HashPrimes[Pick] = HashPrimes[15-iPrime];
        HashPrimes[15-iPrime] = Temp;
    }
}

static const UINT32 anGroupMask[33] =
{
    0x00000000U,
    0x80000000U, 0xC0000000U, 0xE0000000U, 0xF0000000U,
    0xF8000000U, 0xFC000000U, 0xFE000000U, 0xFF000000U,
    0xFF800000U, 0xFFC00000U, 0xFFE00000U, 0xFFF00000U,
    0xFFF80000U, 0xFFFC0000U, 0xFFFE0000U, 0xFFFF0000U,
    0xFFFF8000U, 0xFFFFC000U, 0xFFFFE000U, 0xFFFFF000U,
    0xFFFFF800U, 0xFFFFFC00U, 0xFFFFFE00U, 0xFFFFFF00U,
    0xFFFFFF80U, 0xFFFFFFC0U, 0xFFFFFFE0U, 0xFFFFFFF0U,
    0xFFFFFFF8U, 0xFFFFFFFCU, 0xFFFFFFFEU, 0xFFFFFFFFU
};

bool CHashPage::Allocate(unsigned int nPageSize)
{
    if (m_nPageSize) return false;

    m_nPageSize = nPageSize;
    m_pPage = new unsigned char[nPageSize];
    if (m_pPage)
    {
        return true;
    }
    return false;
}

CHashPage::CHashPage(void)
{
    m_nPageSize = 0;
    m_pPage = 0;
}

CHashPage::~CHashPage(void)
{
    if (m_pPage)
    {
        delete m_pPage;
        m_pPage = 0;
    }
}

// GetStats
//
// This functions returns the number of records in this hash page and the
// number of bytes that these records would take up in a fresh page.
//
// It also tries to leave room for a record of size nExtra.
//
// This function is useful for reallocating the page
//
void CHashPage::GetStats
(
    HP_HEAPLENGTH nExtra,
    int *pnRecords,
    HP_HEAPLENGTH *pnAllocatedSize,
    int *pnGoodDirSize
)
{
    unsigned nSize = 0;
    unsigned nCount = 0;
    unsigned nGoodDirSize = 100;

    // Count and measure all the records in this page.
    //
    for (UINT32 iDir = 0; iDir < m_pHeader->m_nDirSize; iDir++)
    {
        if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
        {
            nCount++;
            HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[iDir]);
            HP_HEAPLENGTH nRequired = EXPAND_TO_BOUNDARY(
                HP_SIZEOF_HEAPNODE + pNode->u.s.nRecordSize);

            if (nRequired < HP_MIN_HEAP_ALLOC)
            {
                nRequired = HP_MIN_HEAP_ALLOC;
            }
            nSize += nRequired;
        }
    }
    *pnRecords = nCount;
    *pnAllocatedSize = nSize;

    // If we have records to talk about, or even if we are trying to reserve
    // space, then do the math.
    //
    if (  nExtra != 0
       || nCount != 0)
    {
        UINT32 nSpace = (UINT32)( ((unsigned char *)m_pTrailer)
                                - ((unsigned char *)m_pDirectory));
        UINT32 nMinDirSize = nCount;
        UINT32 nMaxDirSize = (nSpace - nSize)/sizeof(HP_HEAPOFFSET);

        if (nExtra)
        {
            nExtra += HP_SIZEOF_HEAPNODE;
            if (nExtra < HP_MIN_HEAP_ALLOC)
            {
                nExtra = HP_MIN_HEAP_ALLOC;
            }
            nExtra = EXPAND_TO_BOUNDARY(nExtra);
            nCount++;
            nSize += nExtra;
        }

#define FILL_FACTOR 1
        UINT32 nAverageSize = (nSize + nCount/2)/nCount;
        UINT32 nHeapGoal = (nSpace * nAverageSize)/(nAverageSize + sizeof(HP_HEAPOFFSET) + FILL_FACTOR);
        nGoodDirSize = (UINT32)((nSpace - nHeapGoal + sizeof(HP_HEAPOFFSET)/2)/sizeof(HP_HEAPOFFSET));
        if (nGoodDirSize < nMinDirSize)
        {
            nGoodDirSize = nMinDirSize;
        }
        else if (nGoodDirSize > nMaxDirSize)
        {
            nGoodDirSize = nMaxDirSize;
        }
    }
    *pnGoodDirSize = nGoodDirSize;
}


void CHashPage::SetFixedPointers(void)
{
    m_pHeader = (HP_PHEADER)m_pPage;
    m_pDirectory = (HP_PHEAPOFFSET)(m_pHeader+1);
    m_pTrailer = (HP_PTRAILER)(m_pPage + m_nPageSize - sizeof(HP_TRAILER));
}

void CHashPage::Empty(HP_DIRINDEX arg_nDepth, UINT32 arg_nHashGroup, HP_DIRINDEX arg_nDirSize)
{
    memset(m_pPage, 0, m_nPageSize);

    SetFixedPointers();

    m_pHeader->m_nDepth = arg_nDepth;
    m_pHeader->m_nDirSize = arg_nDirSize;
    m_pHeader->m_nHashGroup = arg_nHashGroup;
    m_pHeader->m_nTotalInsert = 0;
    m_pHeader->m_nDirEmptyLeft = arg_nDirSize;  // Number of entries marked HP_DIR_EMPTY.
    if (arg_nDirSize > 0)
    {
        ChoosePrimes(arg_nDirSize, m_pHeader->m_Primes);
        for (UINT32 iDir = 0; iDir < arg_nDirSize; iDir++)
        {
            m_pDirectory[iDir] = HP_DIR_EMPTY;
        }
    }
    SetVariablePointers();

    // Setup initial free list.
    //
    HP_PHEAPNODE pNode = (HP_PHEAPNODE)m_pHeapStart;
    pNode->nBlockSize = (HP_HEAPLENGTH)(m_pHeapEnd - m_pHeapStart);
    pNode->u.oNext = HP_NIL_OFFSET;
    m_pHeader->m_oFreeList = 0; // This is intentionally zero (i.e., m_pHeapStart - m_pHeapStart).
}

#ifdef HP_PROTECTION
void CHashPage::Protection(void)
{
    UINT32 ul = HASH_ProcessBuffer(0, m_pPage, m_nPageSize-sizeof(HP_TRAILER));
    m_pTrailer->m_checksum = ul;
}

bool CHashPage::Validate(void)
{
    UINT32 ul = HASH_ProcessBuffer(0, m_pPage, m_nPageSize-sizeof(HP_TRAILER));
    if (ul != m_pTrailer->m_checksum)
    {
        return false;
    }
    return true;
}

// ValidateBlock.
//
// This function validates a block associated with a particular
// Dir entry and blows that entry away if it's suspect.
//
bool CHashPage::ValidateAllocatedBlock(UINT32 iDir)
{
    if (iDir >= m_pHeader->m_nDirSize) return false;
    if (m_pDirectory[iDir] >= HP_DIR_DELETED)
        return false;

    // Use directory entry to go find heap node. The record itself follows.
    //
    unsigned char *pBlockStart = m_pHeapStart + m_pDirectory[iDir];
    unsigned char *pBlockEnd = pBlockStart + HP_MIN_HEAP_ALLOC;
    if (pBlockStart < m_pHeapStart || m_pHeapEnd <= pBlockEnd)
    {
        // Wow. We have a problem here. There is no good way of
        // finding this record anymore, so just mark it as
        // deleted. A sweep of the heap will reclaim any lost
        // free space.
        //
        m_pDirectory[iDir] = HP_DIR_DELETED;
    }
    else
    {
        HP_PHEAPNODE pNode = (HP_PHEAPNODE)pBlockStart;
        pBlockEnd = pBlockStart + pNode->nBlockSize;
        if (m_pHeapEnd < pBlockEnd || pNode->u.s.nRecordSize > pNode->nBlockSize)
        {
            // Wow. Record hangs off the end of the heap space, or the record
            // is larger than the block that holds it.
            //
            m_pDirectory[iDir] = HP_DIR_DELETED;
        }
        else
        {
            return true;
        }
    }
    return false;
}

bool CHashPage::ValidateFreeBlock(HP_HEAPOFFSET oBlock)
{
    // If the free list is empty, then this can't be a valid free block.
    //
    if (m_pHeader->m_oFreeList == HP_NIL_OFFSET)
    {
        return false;
    }

    // Go find heap node. The record itself follows.
    //
    unsigned char *pBlockStart = m_pHeapStart + oBlock;
    unsigned char *pBlockEnd = pBlockStart + HP_MIN_HEAP_ALLOC;
    if (pBlockStart < m_pHeapStart || m_pHeapEnd < pBlockEnd)
    {
        // Wow. We have a problem here. There is no good way of
        // finding this record anymore, so just empty the free list
        // and hope to either rehash the page into a new page, or
        // sweep the heap and re-establish the free list.
        //
        m_pHeader->m_oFreeList = HP_NIL_OFFSET;
    }
    else
    {
        HP_PHEAPNODE pNode = (HP_PHEAPNODE)pBlockStart;
        pBlockEnd = pBlockStart + pNode->nBlockSize;
        if (m_pHeapEnd < pBlockEnd)
        {
            // Wow. Record hangs off the end of the heap space.
            //
            m_pHeader->m_oFreeList = HP_NIL_OFFSET;
        }
        else
        {
            return true;
        }
    }
    return false;
}

// ValidateFreeList - Checks the validity of the free list
//
bool CHashPage::ValidateFreeList(void)
{
    HP_HEAPOFFSET oCurrent = m_pHeader->m_oFreeList;
    while (oCurrent != HP_NIL_OFFSET)
    {
        if (ValidateFreeBlock(oCurrent))
        {
            HP_PHEAPNODE pCurrent = (HP_PHEAPNODE)(m_pHeapStart + oCurrent);
            if (oCurrent >= pCurrent->u.oNext)
            {
                Log.WriteString("CHashPage::ValidateFreeList - Free list is corrupt." ENDLINE);
                m_pHeader->m_oFreeList = HP_NIL_OFFSET;
                return false;
            }
            oCurrent = pCurrent->u.oNext;
        }
        else
        {
            Log.WriteString("CHashPage::ValidateFreeList - Free list is corrupt." ENDLINE);
            m_pHeader->m_oFreeList = HP_NIL_OFFSET;
            return false;
        }
    }
    return true;
}
#endif // HP_PROTECTION

// Insert - Inserts a new record if there is room.
//
int CHashPage::Insert(HP_HEAPLENGTH nRecord, UINT32 nHash, void *pRecord)
{
    int ret = HP_INSERT_SUCCESS;
    m_pHeader->m_nTotalInsert++;
    for (int nTries = 0; nTries < 2; nTries++)
    {
#ifdef HP_PROTECTION
        // First, is this page dealing with keys like this at all?
        //
        HP_DIRINDEX nDepth = m_pHeader->m_nDepth;
        if ((nHash & anGroupMask[nDepth]) != m_pHeader->m_nHashGroup)
        {
            Log.WriteString("CHashPage::Insert - Inserting into the wrong page." ENDLINE);
            return HP_INSERT_ERROR_ILLEGAL;
        }
#endif // HP_PROTECTION

        // Where do we begin our first probe?
        //
        int di   = m_pHeader->m_Primes[nHash & 15];
        int iDir = (nHash >> 4) % (m_pHeader->m_nDirSize);
        m_nProbesLeft = m_pHeader->m_nDirSize;
        while (m_nProbesLeft-- && (m_pDirectory[iDir] < HP_DIR_DELETED))
        {
            iDir += di;
            if (iDir >= (m_pHeader->m_nDirSize))
            {
                iDir -= (m_pHeader->m_nDirSize);
            }
        }
        if (m_nProbesLeft >= 0)
        {
            if (m_pHeader->m_nDirEmptyLeft < m_nDirEmptyTrigger)
            {
                if (!Defrag(nRecord))
                {
                    return HP_INSERT_ERROR_FULL;
                }
                ret = HP_INSERT_SUCCESS_DEFRAG;
                continue;
            }
            if (HeapAlloc(iDir, nRecord, nHash, pRecord))
            {
                return ret;
            }
        }
        if (!Defrag(nRecord))
        {
            return HP_INSERT_ERROR_FULL;
        }
        ret = HP_INSERT_SUCCESS_DEFRAG;
    }
    return HP_INSERT_ERROR_FULL;
}

// Find - Finds the first record with the given hash key and returns its
//        directory index or HP_DIR_EMPTY if no hash keys are found.
//
//        Call iDir = FindFirstKey(hash) the first time, and then call
//        iDir = FindNextKey(iDir, hash) every time after than until iDir == HP_DIR_EMPTY
//        to interate through all the records with the desired hash key.
//
HP_DIRINDEX CHashPage::FindFirstKey(UINT32 nHash, unsigned int *numchecks)
{
#ifdef HP_PROTECTION
    // First, is this page dealing with keys like this at all?
    //
    HP_DIRINDEX nDepth = m_pHeader->m_nDepth;
    if ((nHash & anGroupMask[nDepth]) != m_pHeader->m_nHashGroup)
        return HP_DIR_EMPTY;
#endif // HP_PROTECTION

    int nDirSize = m_pHeader->m_nDirSize;

    // Where do we begin our first probe?
    //
    int di = m_pHeader->m_Primes[nHash & 15];
    int iDir = (nHash >> 4) % nDirSize;
    m_nProbesLeft = nDirSize;
    int sOffset = m_pDirectory[iDir];
    while (sOffset != HP_DIR_EMPTY)
    {
        m_nProbesLeft--;
        if (sOffset != HP_DIR_DELETED)
        {
            HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + sOffset);
            if (pNode->u.s.nHash == nHash)
            {
                *numchecks = nDirSize - m_nProbesLeft;
                return iDir;
            }
        }

        if (!m_nProbesLeft) break;

        iDir += di;
        if (iDir >= nDirSize)
        {
            iDir -= nDirSize;
        }
        sOffset = m_pDirectory[iDir];
    }
    *numchecks = nDirSize - m_nProbesLeft;
    return HP_DIR_EMPTY;
}

// Find - Finds the next record with the given hash key and returns its
//        directory index or HP_DIR_EMPTY if no hash keys are found.
//
//
HP_DIRINDEX CHashPage::FindNextKey(HP_DIRINDEX iDir, UINT32 nHash, unsigned int *numchecks)
{
    *numchecks = 0;

#ifdef HP_PROTECTION
    // First, is this page dealing with keys like this at all?
    //
    HP_DIRINDEX nDepth = m_pHeader->m_nDepth;
    if ((nHash & anGroupMask[nDepth]) != m_pHeader->m_nHashGroup)
        return HP_DIR_EMPTY;
#endif // HP_PROTECTION

    int nDirSize = m_pHeader->m_nDirSize;

    // Where do we begin our first probe? If this is the first call, i will be HP_DIR_EMPTY.
    // On calls after that, it will be what we returned on the previous call.
    //
    int di = m_pHeader->m_Primes[nHash & 15];
    iDir += di;
    if (iDir >= nDirSize)
    {
        iDir -= nDirSize;
    }
    while (m_nProbesLeft && (m_pDirectory[iDir] != HP_DIR_EMPTY))
    {
        m_nProbesLeft--;
        (*numchecks)++;
        if (m_pDirectory[iDir] != HP_DIR_DELETED)
        {
            if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
            {
                HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[iDir]);
                if (pNode->u.s.nHash == nHash) return iDir;
            }
        }
        iDir += di;
        if (iDir >= nDirSize)
        {
            iDir -= nDirSize;
        }
    }
    return HP_DIR_EMPTY;
}

// HeapAlloc - Return true if there was enough room to copy the record into the heap, otherwise,
//             it returns false.
//
bool CHashPage::HeapAlloc(HP_DIRINDEX iDir, HP_HEAPLENGTH nRecord, UINT32 nHash, void *pRecord)
{
    //ValidateFreeList();
    if (m_pDirectory[iDir] < HP_DIR_DELETED)
    {
        return false;
    }

    // How much space do we need?
    //
    HP_HEAPLENGTH nRequired = EXPAND_TO_BOUNDARY(HP_SIZEOF_HEAPNODE + nRecord);
    if (nRequired < HP_MIN_HEAP_ALLOC)
    {
        nRequired = HP_MIN_HEAP_ALLOC;
    }

    // Search through the free list for something of the right size.
    //
    HP_HEAPOFFSET oNext = m_pHeader->m_oFreeList;
    HP_PHEAPOFFSET poPrev = &(m_pHeader->m_oFreeList);
    while (oNext != HP_NIL_OFFSET)
    {
#if 0
        if (!ValidateFreeBlock(oPrevious))
        {
            ValidateFreeList();
            return false;
        }
#endif // 0
        unsigned char *pBlockStart = m_pHeapStart + oNext;
        HP_PHEAPNODE pNode = (HP_PHEAPNODE)pBlockStart;
        if (pNode->nBlockSize >= nRequired)
        {
            // We found something of the correct size.
            //
            // Do we cut it into two blocks or take the whole thing?
            //
            HP_HEAPLENGTH nNewBlockSize = pNode->nBlockSize - nRequired;
            if (nNewBlockSize >= EXPAND_TO_BOUNDARY(HP_MIN_HEAP_ALLOC+1))
            {
                // There is enough for leftovers, split it.
                //
                HP_PHEAPNODE pNewNode = (HP_PHEAPNODE)(pBlockStart + nRequired);
                pNewNode->nBlockSize = nNewBlockSize;
                pNewNode->u.oNext = pNode->u.oNext;

                // Update current node.
                //
                pNode->nBlockSize = nRequired;
                pNode->u.s.nHash = nHash;
                pNode->u.s.nRecordSize = nRecord;

                // Update Free list pointer.
                //
                *poPrev += nRequired;
            }
            else
            {
                // Take the whole thing.
                //
                *poPrev = pNode->u.oNext;
                pNode->u.s.nHash = nHash;
                pNode->u.s.nRecordSize = nRecord;
            }
            memcpy(pNode+1, pRecord, nRecord);
            if (m_pDirectory[iDir] == HP_DIR_EMPTY)
            {
                m_pHeader->m_nDirEmptyLeft--;
            }
            m_pDirectory[iDir] = (HP_HEAPOFFSET)(pBlockStart - m_pHeapStart);
            return true;
        }
        poPrev = &(pNode->u.oNext);
        oNext = pNode->u.oNext;
    }
    return false;
}

// HeapFree - Returns to the heap the space for the record associated with iDir. It
//            always succeeds even if there wasn't a record there to delete.
//
void CHashPage::HeapFree(HP_DIRINDEX iDir)
{
    //ValidateFreeList();
    if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
    {
        HP_HEAPOFFSET oBlock = m_pDirectory[iDir];
        HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + oBlock);

        // Clear it. The reason for clearing is that it makes debugging easier,
        // and also, if the file is compressed by the file system, a string
        // of zeros will yield a smaller result.
        //
        HP_HEAPLENGTH nBlockSize = pNode->nBlockSize;
        memset(pNode, 0, nBlockSize);
        pNode->nBlockSize = nBlockSize;

        // Push it onto the free list.
        //
        pNode->u.oNext = m_pHeader->m_oFreeList;
        m_pHeader->m_oFreeList = oBlock;
        m_pDirectory[iDir] = HP_DIR_DELETED;
    }
}

void CHashPage::HeapCopy(HP_DIRINDEX iDir, HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    if (pnRecord == 0 || pRecord == 0) return;

    if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
    {
        HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[iDir]);

        // Copy the record.
        //
        *pnRecord = pNode->u.s.nRecordSize;
        memcpy(pRecord, pNode+1, pNode->u.s.nRecordSize);
    }
}

void CHashPage::HeapUpdate(HP_DIRINDEX iDir, HP_HEAPLENGTH nRecord, void *pRecord)
{
    if (nRecord == 0 || pRecord == 0) return;

    if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
    {
        HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[iDir]);

        if (pNode->u.s.nRecordSize != nRecord) return;
        memcpy(pNode+1, pRecord, nRecord);
    }
}

bool CHashPage::Split(CHashPage &hp0, CHashPage &hp1)
{
    // Figure out what a good directory size is given the actual records in this page.
    //
    int   nRecords;
    HP_HEAPLENGTH nAllocatedSize;
    int   nGoodDirSize;
    GetStats(0, &nRecords, &nAllocatedSize, &nGoodDirSize);
    if (nRecords == 0)
    {
        Log.WriteString("Why are we splitting a page with no records in it?" ENDLINE);
        return false;
    }

    // Initialize that type of HashPage and copy records over.
    //
    int   nNewDepth = m_pHeader->m_nDepth + 1;
    UINT32 nBitMask = 1 << (32-nNewDepth);
    UINT32 nHashGroup0 = m_pHeader->m_nHashGroup & (~nBitMask);
    UINT32 nHashGroup1 = nHashGroup0 | nBitMask;
    hp0.Empty(nNewDepth, nHashGroup0, nGoodDirSize);
    hp1.Empty(nNewDepth, nHashGroup1, nGoodDirSize);
    for (int iDir = 0; iDir < m_pHeader->m_nDirSize; iDir++)
    {
        if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
        {
            HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[iDir]);
            UINT32 nHash = pNode->u.s.nHash;
            if ((nHash & anGroupMask[nNewDepth]) == (nHashGroup0 & anGroupMask[nNewDepth]))
            {
                if (!IS_HP_SUCCESS(hp0.Insert(pNode->u.s.nRecordSize, nHash, pNode+1)))
                {
                    Log.WriteString("CHashPage::Split - Ran out of room." ENDLINE);
                    return false;
                }
            }
            else if ((nHash & anGroupMask[nNewDepth]) == (nHashGroup1 & anGroupMask[nNewDepth]))
            {
                if (!IS_HP_SUCCESS(hp1.Insert(pNode->u.s.nRecordSize, nHash, pNode+1)))
                {
                    Log.WriteString("CHashPage::Split - Ran out of room." ENDLINE);
                    return false;
                }
            }
            else
            {
                Log.WriteString("CHashPage::Split - This record fits in neither page...lost." ENDLINE);
                return false;
            }
        }
    }
#if 0
    int nRecords0, nRecords1;
    HP_HEAPLENGTH nAllocatedSize0, nAllocatedSize1;
    int    temp;
    hp0.GetStats(0, &nRecords0, &nAllocatedSize0, &temp);
    hp1.GetStats(0, &nRecords1, &nAllocatedSize1, &temp);
    Log.tinyprintf("Split (%d %d) page into (%d %d) and (%d %d)" ENDLINE,
        nRecords, nAllocatedSize, nRecords0, nAllocatedSize0, nRecords1,
        nAllocatedSize1);
    if (nRecords0 + nRecords1 != nRecords)
    {
        Log.WriteString("Lost something" ENDLINE);
        return false;
    }
#endif // 0
    return true;
}

void CHashPage::GetRange
(
    UINT32 arg_nDirDepth,
    UINT32 &nStart,
    UINT32 &nEnd
)
{
    UINT32 nBase = 0;
    int nShift = 32 - arg_nDirDepth;
    if (arg_nDirDepth > 0)
    {
        nBase = m_pHeader->m_nHashGroup >> nShift;
    }
    UINT32 ulMask = anGroupMask[nShift + m_pHeader->m_nDepth];
    nStart = nBase & ulMask;
    nEnd   = nBase | ~ulMask;
}

#ifdef WIN32
bool CHashPage::WritePage(HANDLE hFile, HF_FILEOFFSET oWhere)
{
    cs_dbwrites++;
    for ( ; ; MuxAlarm.Sleep(time_250ms))
    {
        if (SetFilePointer(hFile, oWhere, 0, FILE_BEGIN) == 0xFFFFFFFFUL)
        {
            Log.tinyprintf("CHashPage::Write - SetFilePointer error %u." ENDLINE, GetLastError());
            continue;
        }
        DWORD nWritten;
        if (!WriteFile(hFile, m_pPage, m_nPageSize, &nWritten, 0) || nWritten != m_nPageSize)
        {
            UINT32 cc = GetLastError();
            if (cc != ERROR_LOCK_VIOLATION)
            {
                Log.tinyprintf("CHashPage::Write - WriteFile error %u." ENDLINE, cc);
            }
            continue;
        }
        return true;
    }
}

bool CHashPage::ReadPage(HANDLE hFile, HF_FILEOFFSET oWhere)
{
    cs_dbreads++;
    SetFixedPointers();
    for ( ; ; MuxAlarm.Sleep(time_250ms))
    {
        if (SetFilePointer(hFile, oWhere, 0, FILE_BEGIN) == 0xFFFFFFFFUL)
        {
            Log.tinyprintf("CHashPage::Read - SetFilePointer error %u." ENDLINE, GetLastError());
            continue;
        }
        DWORD nRead;
        if (!ReadFile(hFile, m_pPage, m_nPageSize, &nRead, 0) || nRead != m_nPageSize)
        {
            UINT32 cc = GetLastError();
            if (cc != ERROR_LOCK_VIOLATION)
            {
                Log.tinyprintf("CHashPage::Read - ReadFile error %u." ENDLINE, cc);
            }
            continue;
        }
        SetVariablePointers();
        return true;
    }
}

#else // WIN32
bool CHashPage::WritePage(HANDLE hFile, HF_FILEOFFSET oWhere)
{
    cs_dbwrites++;
    int cnt = 60;
    for ( ; cnt; MuxAlarm.Sleep(time_1s), cnt--)
    {
#ifdef HAVE_PWRITE
        int cc = pwrite(hFile, m_pPage, m_nPageSize, oWhere);
#else
        if (lseek(hFile, oWhere, SEEK_SET) == (off_t)-1)
        {
            Log.tinyprintf("CHashPage::Write - lseek error %u." ENDLINE, errno);
            continue;
        }
        int cc = write(hFile, m_pPage, m_nPageSize);
#endif // HAVE_PWRITE
        if ((int)m_nPageSize != cc)
        {
            if (cc == -1)
            {
                Log.tinyprintf("CHashPage::Write - write error %u." ENDLINE, errno);
            }
            else
            {
                // Our write request was only partially filled. The disk is
                // probably full.
                //
                Log.tinyprintf("CHashPage::Write - partial write." ENDLINE);
            }
        }
        return true;
    }

    // Don't struggle further.  You'll just make it worse.
    //
    mudstate.shutdown_flag = true;
    return false;
}

bool CHashPage::ReadPage(HANDLE hFile, HF_FILEOFFSET oWhere)
{
    cs_dbreads++;
    SetFixedPointers();
    int cnt = 60;
    for ( ; cnt; MuxAlarm.Sleep(time_1s), cnt--)
    {
#ifdef HAVE_PREAD
        int cc = pread(hFile, m_pPage, m_nPageSize, oWhere);
#else
        if (lseek(hFile, oWhere, SEEK_SET) == (off_t)-1)
        {
            Log.tinyprintf("CHashPage::Read - lseek error %u." ENDLINE, errno);
            continue;
        }
        int cc = read(hFile, m_pPage, m_nPageSize);
#endif // HAVE_PREAD
        if ((int)m_nPageSize != cc)
        {
            if (cc == -1)
            {
                Log.tinyprintf("CHashPage::Read - read error %u." ENDLINE, errno);
            }
            else
            {
                // Our read request was only partially filled. Surrender.
                //
                Log.tinyprintf("CHashPage::Read - partial read." ENDLINE);
            }
            continue;
        }
        SetVariablePointers();
        return true;
    }

    // Don't struggle further.  You'll just make it worse.
    //
    mudstate.shutdown_flag = true;
    return false;
}
#endif // WIN32

HP_DIRINDEX CHashPage::GetDepth(void)
{
    return m_pHeader->m_nDepth;
}

// Defrag
//
// Moves all the records together, and re-establishes a single-element free list at the end.
//
bool CHashPage::Defrag(HP_HEAPLENGTH nExtra)
{
    CHashPage *hpNew = new CHashPage;
    if (!hpNew) return false;
    if (!hpNew->Allocate(m_nPageSize))
    {
        delete hpNew;
        return false;
    }

    // Figure out what a good directory size is given the actual records in this page.
    //
    int   nRecords;
    HP_HEAPLENGTH nAllocatedSize;
    int   nGoodDirSize;
    GetStats(nExtra, &nRecords, &nAllocatedSize, &nGoodDirSize);

    // Initialize that type of HashPage and copy records over.
    //
    hpNew->Empty(m_pHeader->m_nDepth, m_pHeader->m_nHashGroup, nGoodDirSize);
    int errInserted = HP_INSERT_SUCCESS;
    for (int iDir = 0; iDir < m_pHeader->m_nDirSize && IS_HP_SUCCESS(errInserted); iDir++)
    {
        if (m_pDirectory[iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
        {
            HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[iDir]);
            errInserted = hpNew->Insert(pNode->u.s.nRecordSize, pNode->u.s.nHash, pNode+1);
        }
    }
    if (IS_HP_SUCCESS(errInserted))
    {
        // Swap buffers.
        //
        unsigned char *tmp;
        tmp = hpNew->m_pPage;
        hpNew->m_pPage = m_pPage;
        m_pPage = tmp;

        SetFixedPointers();
        SetVariablePointers();
        delete hpNew;
        return true;
    }
    delete hpNew;
    return false;
}

void CHashPage::SetVariablePointers(void)
{
    m_pHeapStart = (unsigned char *)(m_pDirectory + m_pHeader->m_nDirSize);
    m_pHeapEnd = (unsigned char *)(m_pTrailer);

    // If less than 14.29% of the entries are empty, then do another Defrag.
    //
    m_nDirEmptyTrigger = (m_pHeader->m_nDirSize)/7;
}

HP_DIRINDEX CHashPage::FindFirst(HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    for (m_iDir = 0; m_iDir < m_pHeader->m_nDirSize; m_iDir++)
    {
        if (m_pDirectory[m_iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
        {
            HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[m_iDir]);
            *pnRecord = pNode->u.s.nRecordSize;
            memcpy(pRecord, pNode+1, pNode->u.s.nRecordSize);
            return m_iDir;
        }
    }
    return HP_DIR_EMPTY;
}

HP_DIRINDEX CHashPage::FindNext(HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    for ( m_iDir++; m_iDir < m_pHeader->m_nDirSize; m_iDir++)
    {
        if (m_pDirectory[m_iDir] < HP_DIR_DELETED) // ValidateAllocatedBlock(iDir))
        {
            HP_PHEAPNODE pNode = (HP_PHEAPNODE)(m_pHeapStart + m_pDirectory[m_iDir]);
            *pnRecord = pNode->u.s.nRecordSize;
            memcpy(pRecord, pNode+1, pNode->u.s.nRecordSize);
            return m_iDir;
        }
    }
    return HP_DIR_EMPTY;
}

CHashFile::CHashFile(void)
{
    SeedRandomNumberGenerator();
    m_Cache = NULL;
    m_nCache = 0;
    Init();
}

void CHashFile::Init(void)
{
    m_hDirFile = INVALID_HANDLE_VALUE;
    m_hPageFile = INVALID_HANDLE_VALUE;
    m_nDir = 0;
    m_nDirDepth = 0;
    m_pDir = NULL;
    m_hpCacheLookup = NULL;
    iCache = 0;
    m_iLastFlushed = 0;
}

#ifdef WIN32
void CHashFile::WriteDirectory(void)
{
    if (m_hDirFile == INVALID_HANDLE_VALUE) return;

    SetFilePointer(m_hDirFile, 0, 0, FILE_BEGIN);
    DWORD nWritten;
    WriteFile(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir, &nWritten, 0);
    SetEndOfFile(m_hDirFile);
#ifdef DO_COMMIT
    if (!mudstate.bStandAlone)
    {
        FlushFileBuffers(m_hDirFile);
    }
#endif // DO_COMMIT
}
#else // WIN32
void CHashFile::WriteDirectory(void)
{
    if (m_hDirFile == INVALID_HANDLE_VALUE) return;

#ifdef HAVE_PWRITE
    pwrite(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir, 0);
#else
    lseek(m_hDirFile, 0, SEEK_SET);
    write(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir);
#endif // HAVE_PWRITE
    //SetEndOfFile(m_hDirFile);
#ifdef DO_COMMIT
    if (!mudstate.bStandAlone)
    {
        fsync(m_hDirFile);
    }
#endif // DO_COMMIT
}
#endif // WIN32

bool CHashFile::InitializeDirectory(unsigned int n)
{
    if (m_pDir)
    {
        MEMFREE(m_pDir);
        m_pDir = NULL;
    }
    if (m_hpCacheLookup)
    {
        MEMFREE(m_hpCacheLookup);
        m_hpCacheLookup = NULL;
    }

    m_nDir = n;
    m_nDirDepth = 0;
    n >>= 1;
    while (n)
    {
        m_nDirDepth++;
        n >>= 1;
    }

    m_pDir = (HF_FILEOFFSET *)MEMALLOC(sizeof(HF_FILEOFFSET)*m_nDir);
    ISOUTOFMEMORY(m_pDir);
    m_hpCacheLookup = new int[m_nDir];
    ISOUTOFMEMORY(m_hpCacheLookup);

    for (int i = 0; i < m_nDir; i++)
    {
        m_pDir[i] = 0xFFFFFFFFUL;
        m_hpCacheLookup[i] = -1;
    }

    return true;
}

bool CHashFile::CreateFileSet(const char *szDirFile, const char *szPageFile)
{
    CloseAll();
#ifdef WIN32
    m_hPageFile = CreateFile(szPageFile, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ, 0, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL + FILE_FLAG_RANDOM_ACCESS, NULL);
#else // WIN32
    m_hPageFile = open(szPageFile, O_RDWR|O_BINARY|O_CREAT|O_TRUNC, 0600);
#endif // WIN32

    if (m_hPageFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }

#ifdef WIN32
    m_hDirFile = CreateFile(szDirFile, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ, 0, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#else // WIN32
    m_hDirFile = open(szDirFile, O_RDWR|O_BINARY|O_CREAT|O_TRUNC, 0600);
#endif // WIN32

    if (m_hPageFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }

    // Create empty structures in memory and write them out.
    //
    if (!InitializeDirectory(2))
    {
        return false;
    }

    iCache = AllocateEmptyPage(0, 0);
    if (iCache < 0)
    {
        return false;
    }
    m_Cache[iCache].m_hp.Empty(0, 0UL, 100);
    m_Cache[iCache].m_o = 0UL;

    m_pDir[0] = m_pDir[1] = m_Cache[iCache].m_o;
    m_hpCacheLookup[0] = m_hpCacheLookup[1] = iCache;

    m_Cache[iCache].m_iState = HF_CACHE_UNPROTECTED;

    oEndOfFile = HF_SIZEOF_PAGE;

#ifdef DO_COMMIT
    FlushCache(iCache);
#endif // DO_COMMIT
    WriteDirectory();
    return true;
}

bool CHashFile::RebuildDirectory(void)
{
    // Initialize in-memory page directory
    //
    if (!InitializeDirectory(2))
    {
        return false;
    }

    // Re-build the directory from CHashPages.
    //
    int Hits = 0;
    for (UINT32 oPage = 0; oPage < oEndOfFile; oPage += HF_SIZEOF_PAGE)
    {
        int iCache;
        if ((iCache = AllocateEmptyPage(0, NULL)) < 0)
        {
            Log.WriteString("CHashFile::RebuildDirectory.  AllocateEmptyPage failed. DB DAMAGE." ENDLINE);
            return false;
        }

        if (m_Cache[iCache].m_hp.ReadPage(m_hPageFile, oPage))
        {
            m_Cache[iCache].m_o = oPage;
            m_Cache[iCache].m_iState = HF_CACHE_CLEAN;
            ResetAge(iCache);
        }
        else
        {
            Log.WriteString("CHashFile::RebuildDirectory.  ReadPage failed to get the page. DB DAMAGE." ENDLINE);
        }

        UINT32 nPageDepth = m_Cache[iCache].m_hp.GetDepth();
        while (m_nDirDepth < nPageDepth)
        {
            if (!DoubleDirectory())
            {
                return false;
            }
        }
        UINT32 nStart, nEnd;
        m_Cache[iCache].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
        for ( ; nStart <= nEnd; nStart++)
        {
            if (m_pDir[nStart] != 0xFFFFFFFFUL)
            {
                Log.WriteString("CHashFile::Open - The keyspace of pages in Page File overlap." ENDLINE);
                return false;
            }
            m_pDir[nStart] = oPage;
            m_hpCacheLookup[nStart] = iCache;
        }
    }

    // Validate that the directory does not have holes.
    //
    for (UINT32 iFileDir = 0; iFileDir < m_nDir; iFileDir++)
    {
        if (m_pDir[iFileDir] == 0xFFFFFFFFUL)
        {
            Log.WriteString("CHashFile::Open - Page File is incomplete." ENDLINE);
            return false;
        }
    }
    WriteDirectory();
    return true;
}

bool CHashFile::ReadDirectory(void)
{
#ifdef WIN32
    UINT32 cc = SetFilePointer(m_hDirFile, 0, 0, FILE_END);
#else // WIN32
    UINT32 cc = lseek(m_hDirFile, 0, SEEK_END);
#endif // WIN32
    if (cc == 0xFFFFFFFFUL)
    {
        return false;
    }

    InitializeDirectory(cc / HF_SIZEOF_FILEOFFSET);

#ifdef WIN32
    cc = SetFilePointer(m_hDirFile, 0, 0, FILE_BEGIN);
    DWORD nRead;
    ReadFile(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir, &nRead, 0);
#else // WIN32
#ifdef HAVE_PREAD
    pread(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir, 0);
#else
    lseek(m_hDirFile, 0, SEEK_SET);
    read(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir);
#endif // HAVE_PREAD
#endif // WIN32
    return true;
}

void CHashFile::InitCache(int nCachePages)
{
    if (m_Cache)
    {
        return;
    }

    // Allocate hash page cache.
    //
    m_nCache = nCachePages;
    m_Cache = new HF_CACHE[m_nCache];
    ISOUTOFMEMORY(m_Cache);
    for (int i = 0; i < m_nCache; i++)
    {
        m_Cache[i].m_hp.Allocate(HF_SIZEOF_PAGE);
        m_Cache[i].m_iState = HF_CACHE_EMPTY;
        m_Cache[i].m_o = 0UL;
        m_Cache[i].m_iYounger = i-1;
        m_Cache[i].m_iOlder   = i+1;
    }
    m_Cache[0].m_iYounger = m_nCache-1;
    m_Cache[m_nCache-1].m_iOlder = 0;
    m_iOldest = 0;
}

int CHashFile::Open(const char *szDirFile, const char *szPageFile, int nCachePages)
{
    CloseAll();
    FinalCache();
    InitCache(nCachePages);

    // First let's try to open the page file. This is the more important file.
    //
#ifdef WIN32
    m_hPageFile = CreateFile(szPageFile, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ, 0, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL + FILE_FLAG_RANDOM_ACCESS, NULL);
#else // WIN32
    m_hPageFile = open(szPageFile, O_RDWR|O_BINARY);
#endif // WIN32
    if (m_hPageFile == INVALID_HANDLE_VALUE)
    {
        // The PageFile doesn't exist, so we have'ta create both of them.
        //
        if (!CreateFileSet(szDirFile, szPageFile))
        {
            CloseAll();
            return HF_OPEN_STATUS_ERROR;
        }
        return HF_OPEN_STATUS_NEW;
    }

    // We have a page file open. Let's check how big it is. If it's
    // zero-length, the page file is useless to use, and we need to go through
    // the standard creation process. If the size is not a multiple of
    // HF_SIZEOF_PAGE, we need to fail.
    //
#ifdef WIN32
    oEndOfFile = SetFilePointer(m_hPageFile, 0, 0, FILE_END);
#else // WIN32
    oEndOfFile = lseek(m_hPageFile, 0, SEEK_END);
#endif // WIN32
    if (oEndOfFile == 0xFFFFFFFFUL)
    {
        CloseAll();
        return HF_OPEN_STATUS_ERROR;
    }
    else if (oEndOfFile == 0UL)
    {
        // The PageFile exists, but it's zero-length, so we have'ta create
        // both of them.
        //
        if (!CreateFileSet(szDirFile, szPageFile))
        {
            CloseAll();
            return HF_OPEN_STATUS_ERROR;
        }
        return HF_OPEN_STATUS_NEW;
    }
    else if ((oEndOfFile % HF_SIZEOF_PAGE) != 0)
    {
        // This is not a mulitple of HP_SIZEOF_PAGE. Weird unknown format.
        //
        CloseAll();
        return HF_OPEN_STATUS_ERROR;
    }

    // Now that the page file appears valid so far, let's see if the directory
    // file is there. This file is not strictly necessary, we can rebuild it.
    // However, having it helps us to open faster.
    //
#ifdef WIN32
    m_hDirFile = CreateFile(szDirFile, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ, 0, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#else // WIN32
    m_hDirFile = open(szDirFile, O_RDWR|O_BINARY);
#endif // WIN32
    if (m_hDirFile == INVALID_HANDLE_VALUE)
    {
        // The Directory doesn't exist, so we create it anew, and rebuild the
        // index.
#ifdef WIN32
        m_hDirFile = CreateFile(szDirFile, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ, 0, CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#else // WIN32
        m_hDirFile = open(szDirFile, O_RDWR|O_BINARY|O_CREAT|O_TRUNC, 0600);
#endif // WIN32

        if (m_hPageFile == INVALID_HANDLE_VALUE)
        {
            CloseAll();
            return HF_OPEN_STATUS_ERROR;
        }

        if (!RebuildDirectory())
        {
            CloseAll();
            return HF_OPEN_STATUS_ERROR;
        }
        return HF_OPEN_STATUS_OLD;
    }

    // Read in the directory.
    //
    if (!ReadDirectory())
    {
        CloseAll();
        return HF_OPEN_STATUS_ERROR;
    }
    return HF_OPEN_STATUS_OLD;
}

void CHashFile::Sync(void)
{
    if (m_hPageFile != INVALID_HANDLE_VALUE)
    {
        cs_syncs++;
        bool bAllFlushed = true;
        for (int i = 0; i < m_nCache; i++)
        {
            if (!FlushCache(i))
            {
                bAllFlushed = false;
            }
        }
        if (!bAllFlushed)
        {
            Log.WriteString("CHashFile::Sync. Could not flush all the pages. DB DAMAGE." ENDLINE);
        }

#ifdef DO_COMMIT
        if (!mudstate.bStandAlone)
        {
#ifdef WIN32
            FlushFileBuffers(m_hPageFile);
#else // WIN32
            fsync(m_hPageFile);
#endif // WIN32
        }
#endif // DO_COMMIT
    }
#ifdef DO_COMMIT
    if (  m_hDirFile != INVALID_HANDLE_VALUE
       && !mudstate.bStandAlone)
    {
#ifdef WIN32
        FlushFileBuffers(m_hDirFile);
#else // WIN32
        fsync(m_hDirFile);
#endif // WIN32
    }
#endif // DO_COMMIT
}

void CHashFile::CloseAll(void)
{
    if (m_hPageFile != INVALID_HANDLE_VALUE)
    {
        Sync();
        if (m_pDir)
        {
            MEMFREE(m_pDir);
            m_pDir = NULL;
        }
        if (m_hpCacheLookup)
        {
            delete [] m_hpCacheLookup;
            m_hpCacheLookup = NULL;
        }

#ifdef WIN32
        CloseHandle(m_hPageFile);
#else // WIN32
        close(m_hPageFile);
#endif // WIN32
    }
    if (m_hDirFile != INVALID_HANDLE_VALUE)
    {
#ifdef WIN32
        CloseHandle(m_hDirFile);
#else // WIN32
        close(m_hDirFile);
#endif // WIN32
    }
    Init();
}

void CHashFile::FinalCache(void)
{
    if (m_Cache)
    {
        delete [] m_Cache;
        m_Cache = NULL;
        m_nCache = 0;
    }
}

CHashFile::~CHashFile(void)
{
    FinalCache();
    CloseAll();
}

bool CHashFile::Insert(HP_HEAPLENGTH nRecord, UINT32 nHash, void *pRecord)
{
    cs_writes++;
    for (;;)
    {
        UINT32 iFileDir = nHash >> (32-m_nDirDepth);
        if (iFileDir >= m_nDir)
        {
            Log.WriteString("CHashFile::Insert - iFileDir out of range." ENDLINE);
            return false;
        }
        iCache = ReadCache(iFileDir, &cs_whits);
        if (iCache < 0)
        {
            Log.WriteString("CHashFile::Insert - Page wasn't valid." ENDLINE);
            return false;
        }

        UINT32 nStart, nEnd;
        m_Cache[iCache].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
        if (iFileDir < nStart || nEnd < iFileDir)
        {
            Log.tinyprintf("CHashFile::Insert - Directory entry (0x%08X) points to the wrong page (0x%08X-0x%08X)." ENDLINE,
                iFileDir, nStart, nEnd);
            return false;
        }
        int errInserted = m_Cache[iCache].m_hp.Insert(nRecord, nHash, pRecord);
        if (IS_HP_SUCCESS(errInserted))
        {
            // The record was inserted successfully, so the page is dirty.
            //
            m_Cache[iCache].m_iState = HF_CACHE_UNPROTECTED;
            break;
        }
        else if (HP_INSERT_ERROR_ILLEGAL == errInserted)
        {
            return false;
        }

#ifndef WIN32
        // First, if we are @dumping, then we have a @forked process
        // that is also reading from the file. We must pause and let
        // this reader process finish.
        //
        if (  !mudstate.bStandAlone
           && mudstate.dumping)
        {
            STARTLOG(LOG_DBSAVES, "DMP", "DUMP");
            log_text("Waiting on previously-forked child before page-splitting... ");
            ENDLOG;
            do
            {
                // We have a forked dump in progress, so we will wait until the
                // child exits.
                //
                MuxAlarm.Sleep(time_1s);
            } while (mudstate.dumping);
        }
#endif // !WIN32

        // If the depth of this page is already as deep as the directory
        // depth,then we must increase depth of the directory, first.
        //
        if (m_nDirDepth == m_Cache[iCache].m_hp.GetDepth())
        {
            if (!DoubleDirectory())
            {
                return false;
            }
        }

        // Split this page into two pages. We become a new one, and we
        // are given a pointer to the other one.
        //
        int Safe[2];
        int nSafe = 0;
        int iEmpty0, iEmpty1;

        Safe[nSafe++] = iCache;
        iEmpty0 = AllocateEmptyPage(nSafe, Safe);
        if (iEmpty0 < 0) return false;

        if (iCache == iEmpty0)
        {
            Log.WriteString("CHashFile::Split - iCache == iEmpty0" ENDLINE);
            return false;
        }

        Safe[nSafe++] = iEmpty0;
        iEmpty1 = AllocateEmptyPage(nSafe, Safe);
        if (iEmpty1 < 0) return false;

        if (iCache == iEmpty1)
        {
            Log.WriteString("CHashFile::Split - iCache == iEmpty1" ENDLINE);
            return false;
        }

        if (iEmpty0 == iEmpty1)
        {
            Log.WriteString("CHashFile::Split - iEmpty0 == iEmpty1" ENDLINE);
            return false;
        }

        if (!m_Cache[iCache].m_hp.Split(m_Cache[iEmpty0].m_hp, m_Cache[iEmpty1].m_hp))
        {
            return false;
        }

        // Tack another page onto the end of the .pag file.
        //
        long oNew = oEndOfFile;
        oEndOfFile += HF_SIZEOF_PAGE;

        // iEmpty0 => iCache. iEmpty1 => end of file
        //
        m_Cache[iCache].m_iState = HF_CACHE_EMPTY;
        m_Cache[iEmpty0].m_o = m_Cache[iCache].m_o;
        m_Cache[iEmpty1].m_o = oNew;
        m_Cache[iEmpty0].m_iState = HF_CACHE_UNPROTECTED;
        m_Cache[iEmpty1].m_iState = HF_CACHE_UNPROTECTED;

        // Update the directory.
        //
        m_Cache[iEmpty0].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
        for ( ; nStart <= nEnd; nStart++)
        {
            m_hpCacheLookup[nStart] = iEmpty0;
        }
        m_Cache[iEmpty1].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
        for ( ; nStart <= nEnd; nStart++)
        {
            m_pDir[nStart] = oNew;
            m_hpCacheLookup[nStart] = iEmpty1;
        }

        // Flush the pages out.
        //
        FlushCache(iEmpty1);
        FlushCache(iEmpty0);

#ifdef DO_COMMIT
        if (!mudstate.bStandAlone)
        {
#ifdef WIN32
            FlushFileBuffers(m_hPageFile);
#else // WIN32
            fsync(m_hPageFile);
#endif // WIN32
        }
#endif // DO_COMMIT

        // Write Directory
        //
#ifdef WIN32
        SetFilePointer(m_hDirFile, 0, 0, FILE_BEGIN);
        DWORD nWritten;
        WriteFile(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir, &nWritten, 0);
#else // WIN32
#ifdef HAVE_PWRITE
        pwrite(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir, 0);
#else
        lseek(m_hDirFile, 0, SEEK_SET);
        write(m_hDirFile, m_pDir, sizeof(HF_FILEOFFSET)*m_nDir);
#endif // HAVE_PWRITE
#endif // WIN32

#ifdef DO_COMMIT
        if (!mudstate.bStandAlone)
        {
#ifdef WIN32
            FlushFileBuffers(m_hDirFile);
#else // WIN32
            fsync(m_hDirFile);
#endif // WIN32
        }
#endif // DO_COMMIT
    }
    return true;
}

bool CHashFile::DoubleDirectory(void)
{
    unsigned int nNewDir      = 2 * m_nDir;
    HP_DIRINDEX nNewDirDepth = m_nDirDepth + 1;

    HF_PFILEOFFSET pNewDir = (HF_PFILEOFFSET)MEMALLOC(sizeof(HF_FILEOFFSET)*nNewDir);
    ISOUTOFMEMORY(pNewDir);

    int *pNewCacheLookup = new int[nNewDir];
    ISOUTOFMEMORY(pNewCacheLookup);

    unsigned int iNewDir = 0;
    for (unsigned int iDir = 0; iDir < m_nDir; iDir++)
    {
        pNewDir[iNewDir]   = m_pDir[iDir];
        pNewDir[iNewDir+1] = m_pDir[iDir];
        pNewCacheLookup[iNewDir]   = m_hpCacheLookup[iDir];
        pNewCacheLookup[iNewDir+1] = m_hpCacheLookup[iDir];
        iNewDir += 2;
    }

    // Write out the new directory. It's always larger than
    // the previous one.
    //
    WriteDirectory();

    MEMFREE(m_pDir);
    m_pDir = pNewDir;

    MEMFREE(m_hpCacheLookup);
    m_hpCacheLookup = pNewCacheLookup;

    m_nDirDepth = nNewDirDepth;
    m_nDir = nNewDir;
    return true;
}

HP_DIRINDEX CHashFile::FindFirstKey(UINT32 nHash)
{
    cs_reads++;

    UINT32 iFileDir = nHash >> (32-m_nDirDepth);
    if (iFileDir >= m_nDir)
    {
        Log.WriteString("CHashFile::Insert - iFileDir out of range." ENDLINE);
        cs_fails++;
        return HF_FIND_END;
    }
    iCache = ReadCache(iFileDir, &cs_rhits);
    if (iCache < 0)
    {
        cs_fails++;
        return HF_FIND_END;
    }
    UINT32 nStart, nEnd;
    m_Cache[iCache].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
    if (iFileDir < nStart || nEnd < iFileDir)
    {
        Log.tinyprintf("CHashFile::Find - Directory entry (0x%08X) points to the wrong page (0x%08X-0x%08X)." ENDLINE,
            iFileDir, nStart, nEnd);
        return HF_FIND_END;
    }

    unsigned int numchecks;
    HP_DIRINDEX iDir = m_Cache[iCache].m_hp.FindFirstKey(nHash, &numchecks);

    if (iDir == HP_DIR_EMPTY)
    {
        cs_fails++;
        return HF_FIND_END;
    }
    return iDir;
}

HP_DIRINDEX CHashFile::FindNextKey(HP_DIRINDEX iDir, UINT32 nHash)
{
    cs_reads++;

    unsigned int numchecks;

    iDir = m_Cache[iCache].m_hp.FindNextKey(iDir, nHash, &numchecks);

    if (iDir == HP_DIR_EMPTY)
    {
        cs_fails++;
        return HF_FIND_END;
    }
    return iDir;
}

void CHashFile::Copy(HP_DIRINDEX iDir, HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    m_Cache[iCache].m_hp.HeapCopy(iDir, pnRecord, pRecord);
}

void CHashFile::Remove(HP_DIRINDEX iDir)
{
    cs_dels++;
    m_Cache[iCache].m_hp.HeapFree(iDir);
    m_Cache[iCache].m_iState = HF_CACHE_UNPROTECTED;
}

bool CHashFile::FlushCache(int iCache)
{
    switch (m_Cache[iCache].m_iState)
    {
    case HF_CACHE_UNPROTECTED:
#ifdef HP_PROTECTION
        m_Cache[iCache].m_hp.Protection();
#endif // HP_PROTECTION

    case HF_CACHE_UNWRITTEN:
        if (m_Cache[iCache].m_hp.WritePage(m_hPageFile, m_Cache[iCache].m_o))
        {
            m_Cache[iCache].m_iState = HF_CACHE_CLEAN;
        }
        else
        {
            return false;
        }
    }
    return true;
}

int CHashFile::AllocateEmptyPage(int nSafe, int Safe[])
{
    int cnt = m_nCache;
    while (cnt--)
    {
        int i = m_iOldest;

        bool bExclude = false;
        for (int j = 0; j < nSafe; j++)
        {
            if (Safe[j] == i)
            {
                bExclude = true;
                break;
            }
        }

        ResetAge(i);

        if (  !bExclude
           && FlushCache(i))
        {
            if (HF_CACHE_EMPTY != m_Cache[i].m_iState)
            {
                UINT32 nStart, nEnd;
                m_Cache[i].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
                for ( ; nStart <= nEnd; nStart++)
                {
                    m_hpCacheLookup[nStart] = -1;
                }
                m_Cache[i].m_iState = HF_CACHE_EMPTY;
            }
            return i;
        }
    }
    return -1;
}

void CHashFile::ResetAge(int iEntry)
{
    if (iEntry == m_iOldest)
    {
        // Rotate the doubly-linked list to make the oldest entry the
        // youngest.
        //
        m_iOldest = m_Cache[m_iOldest].m_iYounger;
    }
    else if (iEntry == m_Cache[m_iOldest].m_iOlder)
    {
        // This is already the youngest entry.
        //
    }
    else
    {
        // Unlink this entry.
        //
        int iYounger = m_Cache[iEntry].m_iYounger;
        int iOlder   = m_Cache[iEntry].m_iOlder;
        m_Cache[iYounger].m_iOlder = iOlder;
        m_Cache[iOlder].m_iYounger = iYounger;

        // Re-link at the young end of the queue.
        //
        iYounger = m_iOldest;
        iOlder   = m_Cache[iYounger].m_iOlder;
        m_Cache[iEntry].m_iOlder   = iOlder;
        m_Cache[iEntry].m_iYounger = iYounger;
        m_Cache[iYounger].m_iOlder = iEntry;
        m_Cache[iOlder].m_iYounger = iEntry;
    }
}

void CHashFile::Tick(void)
{
    int nCycle = mudconf.check_interval;
    if (mudconf.dump_interval < nCycle)
    {
        nCycle = mudconf.dump_interval;
    }

    CLinearTimeDelta ltdCycle;
    ltdCycle.SetSeconds(nCycle);

    int n = (mudconf.cache_tick_period*m_nCache)/ltdCycle;
    if (n < 1)
    {
        n = 1;
    }

    for (int i = 0; i < n; i++)
    {
        // Go ahead and flush a cache entry...just to keep the sync load
        // down a bit. This gives the cache time to age, and yet, pushes
        // the pages off to the disk eventually and gradually.
        //
        FlushCache(m_iLastFlushed++);
        if (m_iLastFlushed >= m_nCache)
        {
            m_iLastFlushed = 0;
        }
    }
}

int CHashFile::ReadCache(UINT32 iFileDir, int *phits)
{
    int iCache = m_hpCacheLookup[iFileDir];
    HF_FILEOFFSET oPage = m_pDir[iFileDir];

    if (  iCache != -1
       && m_Cache[iCache].m_iState != HF_CACHE_EMPTY
       && m_Cache[iCache].m_o == oPage)
    {
        ResetAge(iCache);
        (*phits)++;
        return iCache;
    }

    if ((iCache = AllocateEmptyPage(0, NULL)) >= 0)
    {
        if (m_Cache[iCache].m_hp.ReadPage(m_hPageFile, oPage))
        {
            //if (m_Cache[i].m_hp.Validate())
            //{
                m_Cache[iCache].m_o = oPage;
                m_Cache[iCache].m_iState = HF_CACHE_CLEAN;
                ResetAge(iCache);

                UINT32 nStart, nEnd;
                m_Cache[iCache].m_hp.GetRange(m_nDirDepth, nStart, nEnd);
                for ( ; nStart <= nEnd; nStart++)
                {
                    m_hpCacheLookup[nStart] = iCache;
                }

                return iCache;
            //}
        }
        else
        {
            Log.WriteString("CHashFile::ReadCache.  ReadPage failed to get the page. DB DAMAGE." ENDLINE);
        }
    }
    return -1;
}

CHashTable::CHashTable(void)
{
    SeedRandomNumberGenerator();
    Init();
}

void CHashTable::Init(void)
{
    m_nDir = 2;
    m_nDirDepth = 1;
    m_pDir = new pCHashPage[m_nDir];
    if (m_pDir)
    {
        m_pDir[1] = m_pDir[0] = new CHashPage;
        if (m_pDir[0])
        {
            if (m_pDir[0]->Allocate(HT_SIZEOF_PAGE))
            {
                m_pDir[0]->Empty(0, 0UL, 100);
            }
            else
            {
                delete m_pDir[0];
                m_pDir[0] = NULL;
            }
        }
    }

    m_nPages = 1;
    m_nEntries = 0;
    m_nDeletions = 0;
    m_nScans = 0;
    m_nHits = 0;
    m_nChecks = 0;
    m_nMaxScan = 0;
}

void CHashTable::ResetStats(void)
{
    m_nScans = 0;
    m_nHits = 0;
    m_nChecks = 0;
}

bool CHashTable::Insert(HP_HEAPLENGTH nRecord, UINT32  nHash, void *pRecord)
{
    for (;;)
    {
        UINT32  iTableDir = nHash >> (32 - m_nDirDepth);
#ifdef HP_PROTECTION
        if (iTableDir >= m_nDir)
        {
            Log.WriteString("CHashTable::Insert - iTableDir out of range." ENDLINE);
            return false;
        }
#endif // HP_PROTECTION
        m_hpLast = m_pDir[iTableDir];
        if (!m_hpLast)
        {
            Log.WriteString("CHashTable::Insert - Page wasn't valid." ENDLINE);
            return false;
        }
        UINT32  nStart, nEnd;
#ifdef HP_PROTECTION
        m_hpLast->GetRange(m_nDirDepth, nStart, nEnd);
        if (iTableDir < nStart || nEnd < iTableDir)
        {
            Log.WriteString("CHashTable::Insert - Directory points to the wrong page." ENDLINE);
            return false;
        }
#endif // HP_PROTECTION
        int errInserted = m_hpLast->Insert(nRecord, nHash, pRecord);
        if (IS_HP_SUCCESS(errInserted))
        {
            if (errInserted == HP_INSERT_SUCCESS_DEFRAG)
            {
                // Otherwise, this value will be over inflated.
                //
                m_nMaxScan = 0;
            }
            break;
        }
        if (errInserted == HP_INSERT_ERROR_ILLEGAL)
        {
            return false;
        }

        // If the depth of this page is already as deep as the directory
        // depth,then we must increase depth of the directory, first.
        //
        if (m_nDirDepth == m_hpLast->GetDepth())
        {
            if (!DoubleDirectory())
            {
                return false;
            }
        }

        // Split this page into two pages. We become a new one, and we
        // are given a pointer to the other one.
        //
        CHashPage *hpEmpty0 = new CHashPage;
        if (!hpEmpty0) return false;
        if (!hpEmpty0->Allocate(HT_SIZEOF_PAGE))
        {
            delete hpEmpty0;
            return false;
        }

        CHashPage *hpEmpty1 = new CHashPage;
        if (!hpEmpty1) return false;
        if (!hpEmpty1->Allocate(HT_SIZEOF_PAGE))
        {
            delete hpEmpty0;
            delete hpEmpty1;
            return false;
        }

        if (!m_hpLast->Split(*hpEmpty0, *hpEmpty1))
        {
            return false;
        }

        // Otherwise, this value will be over inflated.
        //
        m_nMaxScan = 0;

        // Now, update the directory.
        //
        hpEmpty0->GetRange(m_nDirDepth, nStart, nEnd);
        for ( ; nStart <= nEnd; nStart++)
        {
            m_pDir[nStart] = hpEmpty0;
        }

        hpEmpty1->GetRange(m_nDirDepth, nStart, nEnd);
        for ( ; nStart <= nEnd; nStart++)
        {
            m_pDir[nStart] = hpEmpty1;
        }
        m_nPages++;

        delete m_hpLast;
        m_hpLast = 0;
    }
    m_nEntries++;
    return true;
}

bool CHashTable::DoubleDirectory(void)
{
    unsigned int nNewDir      = 2 * m_nDir;
    unsigned int nNewDirDepth = m_nDirDepth + 1;

    pCHashPage *pNewDir = new pCHashPage[nNewDir];
    if (pNewDir)
    {
        unsigned int iNewDir = 0;
        for (unsigned int iDir = 0; iDir < m_nDir; iDir++)
        {
            pNewDir[iNewDir++] = m_pDir[iDir];
            pNewDir[iNewDir++] = m_pDir[iDir];
        }

        delete m_pDir;

        m_pDir = pNewDir;
        m_nDirDepth = nNewDirDepth;
        m_nDir = nNewDir;
        return true;
    }
    return false;
}

HP_DIRINDEX CHashTable::FindFirstKey(UINT32  nHash)
{
    m_nScans++;
    UINT32  iTableDir = nHash >> (32-m_nDirDepth);
#ifdef HP_PROTECTION
    if (iTableDir >= m_nDir)
    {
        Log.WriteString("CHashTable::Insert - iTableDir out of range." ENDLINE);
        return HF_FIND_END;
    }
#endif // HP_PROTECTION
    m_hpLast = m_pDir[iTableDir];
    if (!m_hpLast)
    {
        Log.WriteString("CHashTable::Insert - Page wasn't valid." ENDLINE);
        return HF_FIND_END;
    }
#ifdef HP_PROTECTION
    UINT32  nStart, nEnd;
    m_hpLast->GetRange(m_nDirDepth, nStart, nEnd);
    if (iTableDir < nStart || nEnd < iTableDir)
    {
        Log.WriteString("CHashTable::Find - Directory points to the wrong page." ENDLINE);
        return HF_FIND_END;
    }
#endif // HP_PROTECTION
    unsigned int numchecks;

    HP_DIRINDEX iDir = m_hpLast->FindFirstKey(nHash, &numchecks);

    m_nChecks += numchecks;
    if (numchecks > m_nMaxScan)
    {
        m_nMaxScan = numchecks;
    }
    if (iDir == HP_DIR_EMPTY)
    {
        return HF_FIND_END;
    }
    m_nHits++;
    return iDir;
}

HP_DIRINDEX CHashTable::FindNextKey(HP_DIRINDEX iDir, UINT32  nHash)
{
    m_nScans++;
    unsigned int numchecks;

    iDir = m_hpLast->FindNextKey(iDir, nHash, &numchecks);

    m_nChecks += numchecks;
    if (numchecks > m_nMaxScan)
    {
        m_nMaxScan = numchecks;
    }
    if (iDir == HP_DIR_EMPTY)
    {
        return HF_FIND_END;
    }
    m_nHits++;
    return iDir;
}

void CHashTable::Copy(HP_DIRINDEX iDir, HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    m_hpLast->HeapCopy(iDir, pnRecord, pRecord);
}

void CHashTable::Remove(HP_DIRINDEX iDir)
{
    m_nEntries--;
    m_nDeletions++;
    m_hpLast->HeapFree(iDir);
}

void CHashTable::Update(HP_DIRINDEX iDir, HP_HEAPLENGTH nRecord, void *pRecord)
{
    m_hpLast->HeapUpdate(iDir, nRecord, pRecord);
}

CHashTable::~CHashTable(void)
{
    Final();
}

void CHashTable::Final(void)
{
    if (m_pDir)
    {
        m_hpLast = 0;
        for (unsigned int i = 0; i < m_nDir; i++)
        {
            CHashPage *hp = m_pDir[i];

            if (hp != m_hpLast && hp)
            {
                delete hp;
                m_hpLast = hp;
            }
        }
        delete m_pDir;
        m_pDir = NULL;
    }
}

void CHashTable::Reset(void)
{
    Final();
    Init();
}

HP_DIRINDEX CHashTable::FindFirst(HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    m_hpLast = 0;
    for (m_iPage = 0; m_iPage < m_nDir; m_iPage++)
    {
        if (m_pDir[m_iPage] == m_hpLast) continue;
        m_hpLast = m_pDir[m_iPage];
        if (m_hpLast)
        {
            HP_DIRINDEX iDir = m_hpLast->FindFirst(pnRecord, pRecord);
            if (iDir != HP_DIR_EMPTY)
            {
                return iDir;
            }
        }
    }
    return HF_FIND_END;
}

HP_DIRINDEX CHashTable::FindNext(HP_PHEAPLENGTH pnRecord, void *pRecord)
{
    if (m_hpLast)
    {
        HP_DIRINDEX iDir = m_hpLast->FindNext(pnRecord, pRecord);
        if (iDir != HP_DIR_EMPTY)
        {
            return iDir;
        }
    }

    // Move on to the next page.
    //
    for ( ; m_iPage < m_nDir; m_iPage++)
    {
        // Move on to the next page.
        //
        if (m_pDir[m_iPage] == m_hpLast) continue;
        m_hpLast = m_pDir[m_iPage];
        if (m_hpLast)
        {
            HP_DIRINDEX iDir = m_hpLast->FindFirst(pnRecord, pRecord);
            if (iDir != HP_DIR_EMPTY)
            {
                return iDir;
            }
        }
    }
    return HF_FIND_END;
}

INT64 CHashTable::GetEntryCount()
{
    return m_nEntries;
}

void CHashTable::GetStats
(
    unsigned int *hashsize,
    int *entries,
    INT64 *deletes,
    INT64 *scans,
    INT64 *hits,
    INT64 *checks,
    int *max_scan
)
{
    *hashsize = m_nPages;
    *entries = m_nEntries;
    *deletes = m_nDeletions;
    *scans = m_nScans;
    *hits = m_nHits;
    *checks = m_nChecks;
    *max_scan = m_nMaxScan;
}

CLogFile Log;
void CLogFile::WriteInteger(int iNumber)
{
    char aTempBuffer[20];
    size_t nTempBuffer = mux_ltoa(iNumber, aTempBuffer);
    WriteBuffer(nTempBuffer, aTempBuffer);
}

void CLogFile::WriteBuffer(size_t nString, const char *pString)
{
    if (!bEnabled)
    {
        return;
    }

#ifdef WIN32
    EnterCriticalSection(&csLog);
#endif // WIN32

    while (nString > 0)
    {
        size_t nAvailable = SIZEOF_LOG_BUFFER - m_nBuffer;
        if (nAvailable == 0)
        {
            Flush();
            continue;
        }

        size_t nToMove = nAvailable;
        if (nString < nToMove)
        {
            nToMove = nString;
        }

        // Move nToMove bytes from pString to aBuffer+nBuffer
        //
        memcpy(m_aBuffer+m_nBuffer, pString, nToMove);
        pString += nToMove;
        nString -= nToMove;
        m_nBuffer += nToMove;
    }
    Flush();

#ifdef WIN32
    LeaveCriticalSection(&csLog);
#endif // WIN32
}

void CLogFile::WriteString(const char *pString)
{
    size_t nString = strlen(pString);
    WriteBuffer(nString, pString);
}

void DCL_CDECL CLogFile::tinyprintf(char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    char aTempBuffer[SIZEOF_LOG_BUFFER];
    int nString = mux_vsnprintf(aTempBuffer, SIZEOF_LOG_BUFFER, fmt, ap);
    va_end(ap);
    WriteBuffer(nString, aTempBuffer);
}

void MakeLogName
(
    const char *pBasename,
    const char *szPrefix,
    CLinearTimeAbsolute lta,
    char *szLogName
)
{
    char szTimeStamp[18];
    lta.ReturnUniqueString(szTimeStamp);
    if (  pBasename
       && pBasename[0] != '\0')
    {
        sprintf(szLogName, "%s/%s-%s.log",
            pBasename,
            szPrefix,
            szTimeStamp);
    }
    else
    {
        sprintf(szLogName, "%s-%s.log",
            szPrefix,
            szTimeStamp);
    }
}

void CLogFile::CreateLogFile(void)
{
    CloseLogFile();

    m_nSize = 0;
#ifdef WIN32
    m_hFile = CreateFile(m_szFilename, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ, 0, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#else // WIN32
    m_hFile = open(m_szFilename, O_RDWR|O_BINARY|O_CREAT|O_TRUNC, 0600);
#endif // WIN32
}

void CLogFile::AppendLogFile(void)
{
    CloseLogFile();

#ifdef WIN32
    m_hFile = CreateFile(m_szFilename, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ, 0, OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL + FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#else // WIN32
    m_hFile = open(m_szFilename, O_RDWR|O_BINARY, 0600);
#endif // WIN32

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
#ifdef WIN32
        SetFilePointer(m_hFile, 0, 0, FILE_END);
#else // WIN32
        lseek(m_hFile, 0, SEEK_SET);
#endif // WIN32
    }
}

void CLogFile::CloseLogFile(void)
{
    if (m_hFile != INVALID_HANDLE_VALUE)
    {
#ifdef WIN32
        CloseHandle(m_hFile);
#else // WIN32
        close(m_hFile);
#endif // WIN32
        m_hFile = INVALID_HANDLE_VALUE;
    }
}

#define FILE_SIZE_TRIGGER (512*1024UL)

void CLogFile::Flush(void)
{
    if (  m_nBuffer <= 0
       || !bEnabled)
    {
        return;
    }
    if (bUseStderr)
    {
        fwrite(m_aBuffer, m_nBuffer, 1, stderr);
    }
    else
    {
        m_nSize += m_nBuffer;
#ifdef WIN32
        unsigned long nWritten;
        WriteFile(m_hFile, m_aBuffer, (DWORD)m_nBuffer, &nWritten, NULL);
#else // WIN32
        write(m_hFile, m_aBuffer, m_nBuffer);
#endif // WIN32

        if (m_nSize > FILE_SIZE_TRIGGER)
        {
            CloseLogFile();

            m_ltaStarted.GetLocal();
            MakeLogName(m_pBasename, m_szPrefix, m_ltaStarted, m_szFilename);

            CreateLogFile();
        }
    }
    m_nBuffer = 0;
}

void CLogFile::SetPrefix(const char *szPrefix)
{
    if (  !bUseStderr
       && strcmp(szPrefix, m_szPrefix) != 0)
    {
        if (bEnabled)
        {
            CloseLogFile();
        }

        char szNewName[SIZEOF_PATHNAME];
        MakeLogName(m_pBasename, szPrefix, m_ltaStarted, szNewName);
        if (bEnabled)
        {
            ReplaceFile(m_szFilename, szNewName);
        }
        strcpy(m_szPrefix, szPrefix);
        strcpy(m_szFilename, szNewName);

        if (bEnabled)
        {
            AppendLogFile();
        }
    }
}

void CLogFile::SetBasename(const char *pBasename)
{
    if (m_pBasename)
    {
        MEMFREE(m_pBasename);
        m_pBasename = NULL;
    }
    if (  pBasename
       && strcmp(pBasename, "-") == 0)
    {
        bUseStderr = true;
    }
    else
    {
        bUseStderr = false;
        if (pBasename)
        {
            m_pBasename = StringClone(pBasename);
        }
        else
        {
            m_pBasename = StringClone("");
        }
    }
}

CLogFile::CLogFile(void)
{
#ifdef WIN32
    InitializeCriticalSection(&csLog);
#endif // WIN32

    m_ltaStarted.GetLocal();
    m_hFile = INVALID_HANDLE_VALUE;
    m_nSize = 0;
    m_nBuffer = 0;
    bEnabled = false;
    bUseStderr = true;
    m_pBasename = NULL;
    m_szPrefix[0] = '\0';
    m_szFilename[0] = '\0';
}

void CLogFile::StartLogging()
{
    if (!bUseStderr)
    {
        m_ltaStarted.GetLocal();
        MakeLogName(m_pBasename, m_szPrefix, m_ltaStarted, m_szFilename);
        CreateLogFile();
    }
    bEnabled = true;
}

void CLogFile::StopLogging(void)
{
    Flush();
    bEnabled = false;
    if (!bUseStderr)
    {
        CloseLogFile();
        m_szPrefix[0] = '\0';
        m_szFilename[0] = '\0';
        SetBasename(NULL);
    }
}

CLogFile::~CLogFile(void)
{
    StopLogging();
#ifdef WIN32
    DeleteCriticalSection(&csLog);
#endif // WIN32
}

#ifdef MEMORY_ACCOUNTING

#pragma pack(1)
typedef struct
{
    void  *address;
    int    size;
    UINT32 fileline;
} AllocDataRec;

typedef struct
{
    size_t nSize;
    int    line;
    char   filename[1];
} IdentDataRec;
#pragma pack()

bool bMemAccountingInitialized = false;
CHashFile hfAllocData;
CHashFile hfIdentData;
extern long DebugTotalMemory;

UINT32 HashPointer(void *vp)
{
    UINT32 nHash1 = HASH_ProcessBuffer(0, &vp, sizeof(void *));
    UINT32 nHash2 = CRC32_ProcessBuffer(0, &vp, sizeof(void *));
    mux_assert(nHash1 == nHash2);
    return nHash1;
}

unsigned long HashFileLine(const char *fn, int line)
{
    UINT32 nHash1 = CRC32_ProcessInteger(line);
    nHash1 = HASH_ProcessBuffer(nHash1, fn, strlen(fn)+1);
    UINT32 nHash2 = CRC32_ProcessInteger(line);
    nHash2 = CRC32_ProcessBuffer(nHash2, fn, strlen(fn)+1);
    mux_assert(nHash1 == nHash2);
    return nHash1;
}

bool SubtractSpaceFromFileLine
(
    UINT32  nHash,
    int     size,
    size_t *pnSpace,
    int    *pAllocLine,
    char   *file
)
{
    *pnSpace = 0;
    *pAllocLine = -1;
    HP_DIRINDEX iDir = hfIdentData.FindFirstKey(nHash);
    if (iDir != HF_FIND_END)
    {
        HP_HEAPLENGTH nIdent;
        char Buffer[1024];
        hfIdentData.Copy(iDir, &nIdent, Buffer);
        hfIdentData.Remove(iDir);

        IdentDataRec *idr = (IdentDataRec *)Buffer;
        *pAllocLine = idr->line;
        strcpy(file, idr->filename);
        idr->nSize -= size;
        *pnSpace = idr->nSize;

        hfIdentData.Insert(nIdent, nHash, Buffer);
    }
    return true;
}

unsigned long AddSpaceToFileLine
(
    const char *file,
    int     line,
    size_t  nSpace,
    size_t *pnTotalSpace
)
{
    *pnTotalSpace = 0;

    HP_HEAPLENGTH nIdent;
    char Buffer[1024];
    IdentDataRec *idr = (IdentDataRec *)Buffer;
    UINT32 nHash = HashFileLine(file, line);
    bool bFound = false;
again:
    HP_DIRINDEX iDir = hfIdentData.FindFirstKey(nHash);
    while (iDir != HF_FIND_END)
    {
        hfIdentData.Copy(iDir, &nIdent, Buffer);
        if (  line == idr->line
           && strcmp(idr->filename, file) == 0)
        {
            hfIdentData.Remove(iDir);
            nSpace += idr->nSize;
            bFound = true;
            goto again;
        }
        iDir = hfIdentData.FindNextKey(iDir, nHash);
    }
    if (!bFound)
    {
        idr->line = line;
        strcpy(idr->filename, file);

        char *p = idr->filename + strlen(idr->filename) + 1;
        nIdent = p - Buffer;
    }
    idr->nSize = nSpace;
    hfIdentData.Insert(nIdent, nHash, Buffer);
    *pnTotalSpace = nSpace;
    return nHash;
}

void AccountForAllocation(void *pointer, size_t size, const char *file, int line)
{
    DebugTotalMemory += size;

    AllocDataRec adr;
    adr.address = pointer;
    adr.size = size;

    unsigned long nHash = HashPointer(pointer);
again:
    HP_DIRINDEX iDir = hfAllocData.FindFirstKey(nHash);

    while (iDir != HF_FIND_END)
    {
        AllocDataRec adr2;
        HP_HEAPLENGTH nRecord;
        hfAllocData.Copy(iDir, &nRecord, &adr2);
        if (adr2.address == adr.address)
        {
            // Whoa! We've been told this new pointer, but it's not.
            //
            fprintf(stderr, "The heap is giving us unfreed pointers (0x%08X). Weird.\n", adr.address);
            hfAllocData.Remove(iDir);
            goto again;
        }
        iDir = hfAllocData.FindNextKey(iDir, nHash);
    }

    size_t TotalSpace;
    adr.fileline = AddSpaceToFileLine(file, line, size, &TotalSpace);
    hfAllocData.Insert(sizeof(adr), nHash, &adr);
    fprintf(stderr, "malloc %d bytes %s, %d\n  bringing total to %d bytes\n",
        size, file, line, TotalSpace);
}

void AccountForFree(void *pointer, const char *file, int line)
{
    unsigned long nHash = HashPointer(pointer);

    bool bFound = false;
again:
    HP_DIRINDEX iDir = hfAllocData.FindFirstKey(nHash);
    while (iDir != HF_FIND_END)
    {
        // We found it.
        //
        HP_HEAPLENGTH nRecord;
        AllocDataRec adr;
        hfAllocData.Copy(iDir, &nRecord, &adr);
        if (adr.address == pointer)
        {
            bFound = true;
            hfAllocData.Remove(iDir);

            DebugTotalMemory -= adr.size;
            size_t nSpace;
            int AllocLine;
            char filename[1024];
            if (SubtractSpaceFromFileLine(adr.fileline, adr.size, &nSpace, &AllocLine, filename))
            {
                fprintf(stderr, "free %d bytes on %s, %d ...\n  allocated on %s, %d...\n  leaving total of %d bytes\n",
                    adr.size, file, line, filename, AllocLine, nSpace);
            }
            else
            {
                fprintf(stderr, "free %d bytes on %s, %d ...\n  allocated on UNKNOWN\n", adr.size, file, line);
            }
            goto again;
        }
        iDir = hfAllocData.FindNextKey(iDir, nHash);
    }

    if (!bFound)
    {
        // Problems.
        //
        fprintf(stderr, "We are freeing unallocated pointers (0x%08X) on %s, %d\n", pointer, file, line);
    }
}

int iRecursion = 0;

void *MemAllocate(size_t size, const char *file, int line)
{
    void *vp = malloc(size);
    if (vp)
    {
        if (bMemAccountingInitialized)
        {
            if (iRecursion == 0)
            {
                iRecursion++;
                AccountForAllocation(vp, size, file, line);
                iRecursion--;
            }
        }
        else
        {
            fprintf(stderr, "malloc not intitalized on %s, %d.\n", file, line);
        }
    }
    else
    {
        fprintf(stderr, "malloc ran out of memory on %s, %d.\n", file, line);
    }
    return vp;
}

void MemFree(void *pointer, const char *file, int line)
{
    if (pointer)
    {
        if (bMemAccountingInitialized)
        {
            if (iRecursion == 0)
            {
                iRecursion++;
                AccountForFree(pointer, file, line);
                iRecursion--;
            }
        }
        else
        {
            fprintf(stderr, "free called on %s, %d before initialized with 0x%08X\n", file, line, pointer);
        }
        free(pointer);
    }
    else
    {
        fprintf(stderr, "We tried to free(0) on %s, %d\n", file, line);
    }
}

void *MemRealloc(void *pointer, size_t size, const char *file, int line)
{
    if (pointer)
    {
        AccountForFree(pointer, file, line);
    }
    void * vp = realloc(pointer, size);
    if (vp)
    {
        AccountForAllocation(vp, size, file, line);
    }
    return vp;
}

#endif // MEMORY_ACCOUNTING