text: /*****************************************************************************/
  /* SBaseCommon.cpp                        Copyright (c) Ladislav Zezula 2003 */
  /*---------------------------------------------------------------------------*/
  /* Common functions for StormLib, used by all SFile*** modules               */
  /*---------------------------------------------------------------------------*/
  /*   Date    Ver   Who  Comment                                              */
  /* --------  ----  ---  -------                                              */
  /* 24.03.03  1.00  Lad  The first version of SFileCommon.cpp                 */
  /* 19.11.03  1.01  Dan  Big endian handling                                  */
  /* 12.06.04  1.01  Lad  Renamed to SCommon.cpp                               */
  /* 06.09.10  1.01  Lad  Renamed to SBaseCommon.cpp                           */
  /*****************************************************************************/
  
  #define __STORMLIB_SELF__
  #include "StormLib.h"
  #include "StormCommon.h"
  
  char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014";
  
  //-----------------------------------------------------------------------------
  // Local variables
  
  DWORD g_dwMpqSignature = ID_MPQ;                // Marker for MPQ header
  DWORD g_dwHashTableKey = MPQ_KEY_HASH_TABLE;    // Key for hash table
  DWORD g_dwBlockTableKey = MPQ_KEY_BLOCK_TABLE;  // Key for block table
  LCID  g_lcFileLocale = LANG_NEUTRAL;            // File locale
  USHORT  wPlatform = 0;                          // File platform
  
  //-----------------------------------------------------------------------------
  // Conversion to uppercase/lowercase
  
  // Converts ASCII characters to lowercase
  // Converts slash (0x2F) to backslash (0x5C)
  unsigned char AsciiToLowerTable[256] =
  {
      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
      0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
      0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
      0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
      0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
      0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
      0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
      0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
      0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
      0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
      0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
      0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
      0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
  };
  
  // Converts ASCII characters to uppercase
  // Converts slash (0x2F) to backslash (0x5C)
  unsigned char AsciiToUpperTable[256] =
  {
      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
      0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
      0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
      0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
      0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
      0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
      0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
      0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
      0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
      0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
      0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
  };
  
  // Converts ASCII characters to uppercase
  // Does NOT convert slash (0x2F) to backslash (0x5C)
  unsigned char AsciiToUpperTable_Slash[256] =
  {
      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
      0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
      0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
      0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
      0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
      0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
      0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
      0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
      0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
      0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
      0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
  };
  
  //-----------------------------------------------------------------------------
  // Safe string functions (for ANSI builds)
  
  char * StringCopy(char * szTarget, size_t cchTarget, const char * szSource)
  {
      size_t cchSource = 0;
  
      if(cchTarget > 0)
      {
          cchSource = strlen(szSource);
  
          if(cchSource >= cchTarget)
              cchSource = cchTarget - 1;
  
          memcpy(szTarget, szSource, cchSource);
          szTarget[cchSource] = 0;
      }
  
      return szTarget + cchSource;
  }
  
  void StringCat(char * szTarget, size_t cchTargetMax, const char * szSource)
  {
      // Get the current length of the target
      size_t cchTarget = strlen(szTarget);
  
      // Copy the string to the target
      if(cchTarget < cchTargetMax)
      {
          StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource);
      }
  }
  
  void StringCreatePseudoFileName(char * szBuffer, size_t cchMaxChars, unsigned int nIndex, const char * szExtension)
  {
      char * szBufferEnd = szBuffer + cchMaxChars;
  
      // "File"
      szBuffer = StringCopy(szBuffer, (szBufferEnd - szBuffer), "File");
  
      // Number
      szBuffer = IntToString(szBuffer, szBufferEnd - szBuffer + 1, nIndex, 8);
  
      // Dot
      if(szBuffer < szBufferEnd)
          *szBuffer++ = '.';
  
      // Extension
      while(szExtension[0] == '.')
          szExtension++;
      StringCopy(szBuffer, (szBufferEnd - szBuffer), szExtension);
  }
  
  //-----------------------------------------------------------------------------
  // Utility functions (UNICODE) only exist in the ANSI version of the library
  // In ANSI builds, TCHAR = char, so we don't need these functions implemented
  
  #ifdef _UNICODE
  void StringCopy(TCHAR * szTarget, size_t cchTarget, const char * szSource)
  {
      if(cchTarget > 0)
      {
          size_t cchSource = strlen(szSource);
  
          if(cchSource >= cchTarget)
              cchSource = cchTarget - 1;
  
          mbstowcs(szTarget, szSource, cchSource);
          szTarget[cchSource] = 0;
      }
  }
  
  void StringCopy(char * szTarget, size_t cchTarget, const TCHAR * szSource)
  {
      if(cchTarget > 0)
      {
          size_t cchSource = _tcslen(szSource);
  
          if(cchSource >= cchTarget)
              cchSource = cchTarget - 1;
  
          wcstombs(szTarget, szSource, cchSource);
          szTarget[cchSource] = 0;
      }
  }
  
  void StringCopy(TCHAR * szTarget, size_t cchTarget, const TCHAR * szSource)
  {
      if(cchTarget > 0)
      {
          size_t cchSource = _tcslen(szSource);
  
          if(cchSource >= cchTarget)
              cchSource = cchTarget - 1;
  
          memcpy(szTarget, szSource, cchSource * sizeof(TCHAR));
          szTarget[cchSource] = 0;
      }
  }
  
  void StringCat(TCHAR * szTarget, size_t cchTargetMax, const TCHAR * szSource)
  {
      // Get the current length of the target
      size_t cchTarget = _tcslen(szTarget);
  
      // Copy the string to the target
      if(cchTarget < cchTargetMax)
      {
          StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource);
      }
  }
  #endif
  
  //-----------------------------------------------------------------------------
  // Storm hashing functions
  
  #define STORM_BUFFER_SIZE       0x500
  #define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0)
  
  static DWORD StormBuffer[STORM_BUFFER_SIZE];    // Buffer for the decryption engine
  static bool  bMpqCryptographyInitialized = false;
  
  void InitializeMpqCryptography()
  {
      DWORD dwSeed = 0x00100001;
      DWORD index1 = 0;
      DWORD index2 = 0;
      int   i;
  
      // Initialize the decryption buffer.
      // Do nothing if already done.
      if(bMpqCryptographyInitialized == false)
      {
          for(index1 = 0; index1 < 0x100; index1++)
          {
              for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
              {
                  DWORD temp1, temp2;
  
                  dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
                  temp1  = (dwSeed & 0xFFFF) << 0x10;
  
                  dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
                  temp2  = (dwSeed & 0xFFFF);
  
                  StormBuffer[index2] = (temp1 | temp2);
              }
          }
  
          // Also register both MD5 and SHA1 hash algorithms
          register_hash(&md5_desc);
          register_hash(&sha1_desc);
  
          // Use LibTomMath as support math library for LibTomCrypt
          ltc_mp = ltm_desc;
  
          // Don't do that again
          bMpqCryptographyInitialized = true;
      }
  }
  
  //
  // Note: Implementation of this function in WorldEdit.exe and storm.dll
  // incorrectly treats the character as signed, which leads to the
  // a buffer underflow if the character in the file name >= 0x80:
  // The following steps happen when *pbKey == 0xBF and dwHashType == 0x0000
  // (calculating hash index)
  //
  // 1) Result of AsciiToUpperTable_Slash[*pbKey++] is sign-extended to 0xffffffbf
  // 2) The "ch" is added to dwHashType (0xffffffbf + 0x0000 => 0xffffffbf)
  // 3) The result is used as index to the StormBuffer table,
  // thus dereferences a random value BEFORE the begin of StormBuffer.
  //
  // As result, MPQs containing files with non-ANSI characters will not work between
  // various game versions and localizations. Even WorldEdit, after importing a file
  // with Korean characters in the name, cannot open the file back.
  //
  DWORD HashString(const char * szFileName, DWORD dwHashType)
  {
      LPBYTE pbKey   = (BYTE *)szFileName;
      DWORD  dwSeed1 = 0x7FED7FED;
      DWORD  dwSeed2 = 0xEEEEEEEE;
      DWORD  ch;
  
      while(*pbKey != 0)
      {
          // Convert the input character to uppercase
          // Convert slash (0x2F) to backslash (0x5C)
          ch = AsciiToUpperTable[*pbKey++];
  
          dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
          dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
      }
  
      return dwSeed1;
  }
  
  DWORD HashStringSlash(const char * szFileName, DWORD dwHashType)
  {
      LPBYTE pbKey   = (BYTE *)szFileName;
      DWORD  dwSeed1 = 0x7FED7FED;
      DWORD  dwSeed2 = 0xEEEEEEEE;
      DWORD  ch;
  
      while(*pbKey != 0)
      {
          // Convert the input character to uppercase
          // DON'T convert slash (0x2F) to backslash (0x5C)
          ch = AsciiToUpperTable_Slash[*pbKey++];
  
          dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
          dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
      }
  
      return dwSeed1;
  }
  
  DWORD HashStringLower(const char * szFileName, DWORD dwHashType)
  {
      LPBYTE pbKey   = (BYTE *)szFileName;
      DWORD  dwSeed1 = 0x7FED7FED;
      DWORD  dwSeed2 = 0xEEEEEEEE;
      DWORD  ch;
  
      while(*pbKey != 0)
      {
          // Convert the input character to lower
          // DON'T convert slash (0x2F) to backslash (0x5C)
          ch = AsciiToLowerTable[*pbKey++];
  
          dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
          dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
      }
  
      return dwSeed1;
  }
  
  //-----------------------------------------------------------------------------
  // Calculates the hash table size for a given amount of files
  
  // Returns the nearest higher power of two.
  // If the value is already a power of two, returns the same value
  DWORD GetNearestPowerOfTwo(DWORD dwFileCount)
  {
      dwFileCount --;
  
      dwFileCount |= dwFileCount >> 1;
      dwFileCount |= dwFileCount >> 2;
      dwFileCount |= dwFileCount >> 4;
      dwFileCount |= dwFileCount >> 8;
      dwFileCount |= dwFileCount >> 16;
  
      return dwFileCount + 1;
  }
  /*
  DWORD GetNearestPowerOfTwo(DWORD dwFileCount)
  {
      DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN;
  
      // For zero files, there is no hash table needed
      if(dwFileCount == 0)
          return 0;
  
      // Round the hash table size up to the nearest power of two
      // Don't allow the hash table size go over allowed maximum
      while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount)
          dwPowerOfTwo <<= 1;
      return dwPowerOfTwo;
  }
  */
  //-----------------------------------------------------------------------------
  // Calculates a Jenkin's Encrypting and decrypting MPQ file data
  
  ULONGLONG HashStringJenkins(const char * szFileName)
  {
      LPBYTE pbFileName = (LPBYTE)szFileName;
      char szNameBuff[0x108];
      size_t nLength = 0;
      unsigned int primary_hash = 1;
      unsigned int secondary_hash = 2;
  
      // Normalize the file name - convert to uppercase, and convert "/" to "\\".
      if(pbFileName != NULL)
      {
          char * szNamePtr = szNameBuff;
          char * szNameEnd = szNamePtr + sizeof(szNameBuff);
  
          // Normalize the file name. Doesn't have to be zero terminated for hashing
          while(szNamePtr < szNameEnd && pbFileName[0] != 0)
              *szNamePtr++ = (char)AsciiToLowerTable[*pbFileName++];
          nLength = szNamePtr - szNameBuff;
      }
  
      // Thanks Quantam for finding out what the algorithm is.
      // I am really getting old for reversing large chunks of assembly
      // that does hashing :-)
      hashlittle2(szNameBuff, nLength, &secondary_hash, &primary_hash);
  
      // Combine those 2 together
      return ((ULONGLONG)primary_hash << 0x20) | (ULONGLONG)secondary_hash;
  }
  
  //-----------------------------------------------------------------------------
  // Default flags for (attributes) and (listfile)
  
  DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion)
  {
      // Fixed for format 1.0
      if(wFormatVersion == MPQ_FORMAT_VERSION_1)
          return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY;
  
      // Size-dependent for formats 2.0-4.0
      return (dwFileSize > 0x4000) ? (MPQ_FILE_COMPRESS | MPQ_FILE_SECTOR_CRC) : (MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT);
  }
  
  
  //-----------------------------------------------------------------------------
  // Encrypting/Decrypting MPQ data block
  
  static DWORD EncryptUInt32Unaligned(LPDWORD DataPointer, DWORD i, DWORD dwXorKey)
  {
      LPBYTE pbDataPointer = (LPBYTE)(DataPointer + i);
      LPBYTE pbXorKey = (LPBYTE)(&dwXorKey);
      DWORD dwValue32;
  
      // Retrieve the value
      dwValue32 = ((DWORD)pbDataPointer[0] << 0x00) |
                  ((DWORD)pbDataPointer[1] << 0x08) |
                  ((DWORD)pbDataPointer[2] << 0x10) |
                  ((DWORD)pbDataPointer[3] << 0x18);
  
      // Perform unaligned XOR
      pbDataPointer[0] = (pbDataPointer[0] ^ pbXorKey[0]);
      pbDataPointer[1] = (pbDataPointer[1] ^ pbXorKey[1]);
      pbDataPointer[2] = (pbDataPointer[2] ^ pbXorKey[2]);
      pbDataPointer[3] = (pbDataPointer[3] ^ pbXorKey[3]);
      return dwValue32;
  }
  
  void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
  {
      LPDWORD DataPointer = (LPDWORD)pvDataBlock;
      DWORD dwValue32;
      DWORD dwKey2 = 0xEEEEEEEE;
  
      // Round to DWORDs
      dwLength >>= 2;
  
      // We need different approach on non-aligned buffers
      if(STORMLIB_DWORD_ALIGNED(DataPointer))
      {
          for(DWORD i = 0; i < dwLength; i++)
          {
              // Modify the second key
              dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
  
              // We can use 32-bit approach, when the buffer is aligned
              DataPointer[i] = (dwValue32 = DataPointer[i]) ^ (dwKey1 + dwKey2);
  
              dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
              dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
          }
      }
      else
      {
          for(DWORD i = 0; i < dwLength; i++)
          {
              // Modify the second key
              dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
  
              // The data are unaligned. Make sure we don't cause data misalignment error
              dwValue32 = EncryptUInt32Unaligned(DataPointer, i, (dwKey1 + dwKey2));
  
              dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
              dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
          }
      }
  }
  
  static DWORD DecryptUInt32Unaligned(LPDWORD DataPointer, DWORD i, DWORD dwXorKey)
  {
      LPBYTE pbDataPointer = (LPBYTE)(DataPointer + i);
      LPBYTE pbXorKey = (LPBYTE)(&dwXorKey);
  
      // Perform unaligned XOR
      pbDataPointer[0] = (pbDataPointer[0] ^ pbXorKey[0]);
      pbDataPointer[1] = (pbDataPointer[1] ^ pbXorKey[1]);
      pbDataPointer[2] = (pbDataPointer[2] ^ pbXorKey[2]);
      pbDataPointer[3] = (pbDataPointer[3] ^ pbXorKey[3]);
  
      // Retrieve the value
      return ((DWORD)pbDataPointer[0] << 0x00) |
             ((DWORD)pbDataPointer[1] << 0x08) |
             ((DWORD)pbDataPointer[2] << 0x10) |
             ((DWORD)pbDataPointer[3] << 0x18);
  }
  
  void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
  {
      LPDWORD DataPointer = (LPDWORD)pvDataBlock;
      DWORD dwValue32;
      DWORD dwKey2 = 0xEEEEEEEE;
  
      // Round to DWORDs
      dwLength >>= 2;
  
      // We need different approach on non-aligned buffers
      if(STORMLIB_DWORD_ALIGNED(DataPointer))
      {
          for(DWORD i = 0; i < dwLength; i++)
          {
              // Modify the second key
              dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
  
              // We can use 32-bit approach, when the buffer is aligned
              DataPointer[i] = dwValue32 = DataPointer[i] ^ (dwKey1 + dwKey2);
  
              dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
              dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
          }
      }
      else
      {
          for(DWORD i = 0; i < dwLength; i++)
          {
              // Modify the second key
              dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
  
              // The data are unaligned. Make sure we don't cause data misalignment error
              dwValue32 = DecryptUInt32Unaligned(DataPointer, i, (dwKey1 + dwKey2));
  
              dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
              dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
          }
      }
  }
  
  /**
   * Functions tries to get file decryption key. This comes from these facts
   *
   * - We know the decrypted value of the first DWORD in the encrypted data
   * - We know the decrypted value of the second DWORD (at least aproximately)
   * - There is only 256 variants of how the second key is modified
   *
   *  The first iteration of dwKey1 and dwKey2 is this:
   *
   *  dwKey2 = 0xEEEEEEEE + StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]
   *  dwDecrypted0 = DataBlock[0] ^ (dwKey1 + dwKey2);
   *
   *  This means:
   *
   *  (dwKey1 + dwKey2) = DataBlock[0] ^ dwDecrypted0;
   *
   */
  
  DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwDecrypted0)
  {
      // We must have at least 2 DWORDs there to be able to decrypt something
      if(dwSectorSize >= 0x08)
      {
          // Also try subsequent three values. This is because the value of the sector offset[0]
          // could be higher than the total size of the sector table.
          // Example MPQ: MPQ_2021_v1_CantExtractCHK.scx
          for(DWORD dwDecrypted4 = dwDecrypted0 + 4; dwDecrypted0 < dwDecrypted4; dwDecrypted0++)
          {
              DWORD dwDecrypted1Max = dwSectorSize + dwDecrypted0;
              DWORD dwKey1PlusKey2;
              DWORD DataBlock[2];
  
              // Get the value of the combined encryption key
              dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
  
              // Try all 256 combinations of dwKey1
              for(DWORD i = 0; i < 0x100; i++)
              {
                  DWORD dwSaveKey1;
                  DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
                  DWORD dwKey2 = 0xEEEEEEEE;
  
                  // Modify the second key and decrypt the first DWORD
                  dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
                  DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
  
                  // Did we obtain the same value like dwDecrypted0?
                  if(DataBlock[0] == dwDecrypted0)
                  {
                      // Save this key value. Increment by one because
                      // we are decrypting sector offset table
                      dwSaveKey1 = dwKey1 + 1;
  
                      // Rotate both keys
                      dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
                      dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
  
                      // Modify the second key again and decrypt the second DWORD
                      dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
                      DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
  
                      // Now compare the results
                      if(DataBlock[1] <= dwDecrypted1Max)
                          return dwSaveKey1;
                  }
              }
          }
      }
  
      // Key not found
      return 0;
  }
  
  // Function tries to detect file encryption key based on expected file content
  // It is the same function like before, except that we know the value of the second DWORD
  DWORD DetectFileKeyByKnownContent(void * pvEncryptedData, DWORD dwDecrypted0, DWORD dwDecrypted1)
  {
      LPDWORD EncryptedData = (LPDWORD)pvEncryptedData;
      DWORD dwKey1PlusKey2;
      DWORD DataBlock[2];
  
      // Get the value of the combined encryption key
      dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
  
      // Try all 256 combinations of dwKey1
      for(DWORD i = 0; i < 0x100; i++)
      {
          DWORD dwSaveKey1;
          DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
          DWORD dwKey2 = 0xEEEEEEEE;
  
          // Modify the second key and decrypt the first DWORD
          dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
          DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
  
          // Did we obtain the same value like dwDecrypted0?
          if(DataBlock[0] == dwDecrypted0)
          {
              // Save this key value
              dwSaveKey1 = dwKey1;
  
              // Rotate both keys
              dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
              dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
  
              // Modify the second key again and decrypt the second DWORD
              dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
              DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
  
              // Now compare the results
              if(DataBlock[1] == dwDecrypted1)
                  return dwSaveKey1;
          }
      }
  
      // Key not found
      return 0;
  }
  
  DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize)
  {
      DWORD dwFileKey;
  
      // Try to break the file encryption key as if it was a WAVE file
      if(dwSectorSize >= 0x0C)
      {
          dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x46464952, dwFileSize - 8);
          if(dwFileKey != 0)
              return dwFileKey;
      }
  
      // Try to break the encryption key as if it was an EXE file
      if(dwSectorSize > 0x40)
      {
          dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x00905A4D, 0x00000003);
          if(dwFileKey != 0)
              return dwFileKey;
      }
  
      // Try to break the encryption key as if it was a XML file
      if(dwSectorSize > 0x04)
      {
          dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x6D783F3C, 0x6576206C);
          if(dwFileKey != 0)
              return dwFileKey;
      }
  
      // Not detected, sorry
      return 0;
  }
  
  DWORD DecryptFileKey(
      const char * szFileName,
      ULONGLONG MpqPos,
      DWORD dwFileSize,
      DWORD dwFlags)
  {
      DWORD dwFileKey;
      DWORD dwMpqPos = (DWORD)MpqPos;
  
      // File key is calculated from plain name
      szFileName = GetPlainFileName(szFileName);
      dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY);
  
      // Fix the key, if needed
      if(dwFlags & MPQ_FILE_FIX_KEY)
          dwFileKey = (dwFileKey + dwMpqPos) ^ dwFileSize;
  
      // Return the key
      return dwFileKey;
  }
  
  //-----------------------------------------------------------------------------
  // Handle validation functions
  
  TMPQArchive * IsValidMpqHandle(HANDLE hMpq)
  {
      TMPQArchive * ha = (TMPQArchive *)hMpq;
  
      return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == g_dwMpqSignature) ? ha : NULL;
  }
  
  TMPQFile * IsValidFileHandle(HANDLE hFile)
  {
      TMPQFile * hf = (TMPQFile *)hFile;
  
      // Must not be NULL
      if(hf != NULL && hf->dwMagic == ID_MPQ_FILE)
      {
          // Local file handle?
          if(hf->pStream != NULL)
              return hf;
  
          // Also verify the MPQ handle within the file handle
          if(IsValidMpqHandle(hf->ha))
              return hf;
      }
  
      return NULL;
  }
  
  //-----------------------------------------------------------------------------
  // Hash table and block table manipulation
  
  // Attempts to search a free hash entry, or an entry whose names and locale matches
  TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale)
  {
      TMPQHash * pDeletedEntry = NULL;            // If a deleted entry was found in the continuous hash range
      TMPQHash * pFreeEntry = NULL;               // If a free entry was found in the continuous hash range
      DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
      DWORD dwIndex;
  
      // Set the initial index
      dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
  
      // Search the hash table and return the found entries in the following priority:
      // 1) <MATCHING_ENTRY>
      // 2) <DELETED-ENTRY>
      // 3) <FREE-ENTRY>
      // 4) NULL
      for(;;)
      {
          TMPQHash * pHash = ha->pHashTable + dwIndex;
  
          // If we found a matching entry, return that one
          if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == lcLocale)
              return pHash;
  
          // If we found a deleted entry, remember it but keep searching
          if(pHash->dwBlockIndex == HASH_ENTRY_DELETED && pDeletedEntry == NULL)
              pDeletedEntry = pHash;
  
          // If we found a free entry, we need to stop searching
          if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
          {
              pFreeEntry = pHash;
              break;
          }
  
          // Move to the next hash entry.
          // If we reached the starting entry, it's failure.
          dwIndex = (dwIndex + 1) & dwHashIndexMask;
          if(dwIndex == dwStartIndex)
              break;
      }
  
      // If we found a deleted entry, return that one preferentially
      return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry;
  }
  
  // Retrieves the first hash entry for the given file.
  // Every locale version of a file has its own hash entry
  TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName)
  {
      DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
      DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX);
      DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A);
      DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B);
      DWORD dwIndex;
  
      // Set the initial index
      dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
  
      // Search the hash table
      for(;;)
      {
          TMPQHash * pHash = ha->pHashTable + dwIndex;
  
          // If the entry matches, we found it.
          if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
              return pHash;
  
          // If that hash entry is a free entry, it means we haven't found the file
          if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
              return NULL;
  
          // Move to the next hash entry. Stop searching
          // if we got reached the original hash entry
          dwIndex = (dwIndex + 1) & dwHashIndexMask;
          if(dwIndex == dwStartIndex)
              return NULL;
      }
  }
  
  TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
  {
      DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
      DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
      DWORD dwName1 = pHash->dwName1;
      DWORD dwName2 = pHash->dwName2;
      DWORD dwIndex = (DWORD)(pHash - ha->pHashTable);
  
      // Now go for any next entry that follows the pHash,
      // until either free hash entry was found, or the start entry was reached
      for(;;)
      {
          // Move to the next hash entry. Stop searching
          // if we got reached the original hash entry
          dwIndex = (dwIndex + 1) & dwHashIndexMask;
          if(dwIndex == dwStartIndex)
              return NULL;
          pHash = ha->pHashTable + dwIndex;
  
          // If the entry matches, we found it.
          if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
              return pHash;
  
          // If that hash entry is a free entry, it means we haven't found the file
          if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
              return NULL;
      }
  }
  
  // Allocates an entry in the hash table
  TMPQHash * AllocateHashEntry(
      TMPQArchive * ha,
      TFileEntry * pFileEntry,
      LCID lcLocale)
  {
      TMPQHash * pHash;
      DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX);
      DWORD dwName1 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_A);
      DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B);
  
      // Attempt to find a free hash entry
      pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, lcLocale);
      if(pHash != NULL)
      {
          // Fill the free hash entry
          pHash->dwName1      = dwName1;
          pHash->dwName2      = dwName2;
          pHash->lcLocale     = (USHORT)lcLocale;
          pHash->Platform     = 0;
          pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
      }
  
      return pHash;
  }
  
  // Finds a free space in the MPQ where to store next data
  // The free space begins beyond the file that is stored at the fuhrtest
  // position in the MPQ. (listfile), (attributes) and (signature) are ignored,
  // unless the MPQ is being flushed.
  ULONGLONG FindFreeMpqSpace(TMPQArchive * ha)
  {
      TMPQHeader * pHeader = ha->pHeader;
      TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
      TFileEntry * pFileEntry;
      ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize;
      DWORD dwChunkCount;
  
      // Parse the entire block table
      for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
      {
          // Only take existing files with nonzero size
          if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0))
          {
              // If we are not saving MPQ tables, ignore internal MPQ files
              if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0 && IsInternalMpqFileName(pFileEntry->szFileName))
                  continue;
  
              // If the end of the file is bigger than current MPQ table pos, update it
              if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos)
              {
                  // Get the end of the file data
                  FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize;
  
                  // Add the MD5 chunks, if present
                  if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0)
                  {
                      dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1;
                      FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE;
                  }
              }
          }
      }
  
      // Give the free space position to the caller
      return FreeSpacePos;
  }
  
  //-----------------------------------------------------------------------------
  // Common functions - MPQ File
  
  TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry)
  {
      TMPQFile * hf;
  
      // Allocate space for TMPQFile
      hf = STORM_ALLOC(TMPQFile, 1);
      if(hf != NULL)
      {
          // Fill the file structure
          memset(hf, 0, sizeof(TMPQFile));
          hf->dwMagic = ID_MPQ_FILE;
          hf->pStream = NULL;
          hf->ha = ha;
  
          // If the called entered a file entry, we also copy informations from the file entry
          if(ha != NULL && pFileEntry != NULL)
          {
              // Set the raw position and MPQ position
              hf->RawFilePos = FileOffsetFromMpqOffset(ha, pFileEntry->ByteOffset);
              hf->MpqFilePos = pFileEntry->ByteOffset;
  
              // Set the data size
              hf->dwDataSize = pFileEntry->dwFileSize;
              hf->pFileEntry = pFileEntry;
          }
      }
  
      return hf;
  }
  
  TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize)
  {
      ULONGLONG FreeMpqSpace;
      ULONGLONG TempPos;
      TMPQFile * hf;
  
      // We need to find the position in the MPQ where we save the file data
      FreeMpqSpace = FindFreeMpqSpace(ha);
  
      // When format V1, the size of the archive cannot exceed 4 GB
      if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
      {
          TempPos  = FreeMpqSpace +
                     dwFileSize +
                    (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)) +
                    (ha->dwFileTableSize * sizeof(TMPQBlock));
          if((TempPos >> 32) != 0)
          {
              SetLastError(ERROR_DISK_FULL);
              return NULL;
          }
      }
  
      // Allocate the file handle
      hf = CreateFileHandle(ha, NULL);
      if(hf == NULL)
      {
          SetLastError(ERROR_NOT_ENOUGH_MEMORY);
          return NULL;
      }
  
      // We need to find the position in the MPQ where we save the file data
      hf->MpqFilePos = FreeMpqSpace;
      hf->bIsWriteHandle = true;
      return hf;
  }
  
  // Loads a table from MPQ.
  // Can be used for hash table, block table, sector offset table or sector checksum table
  void * LoadMpqTable(
      TMPQArchive * ha,
      ULONGLONG ByteOffset,
      LPBYTE pbTableHash,
      DWORD dwCompressedSize,
      DWORD dwTableSize,
      DWORD dwKey,
      bool * pbTableIsCut)
  {
      ULONGLONG FileSize = 0;
      LPBYTE pbCompressed = NULL;
      LPBYTE pbMpqTable;
      LPBYTE pbToRead;
      DWORD dwBytesToRead = dwCompressedSize;
      DWORD dwErrCode = ERROR_SUCCESS;
  
      // Allocate the MPQ table
      pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize);
      if(pbMpqTable != NULL)
      {
          // Check if the MPQ table is encrypted
          if(dwCompressedSize < dwTableSize)
          {
              // Allocate temporary buffer for holding compressed data
              pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
              if(pbCompressed == NULL)
              {
                  STORM_FREE(pbMpqTable);
                  return NULL;
              }
          }
  
          // Get the file offset from which we will read the table
          // Note: According to Storm.dll from Warcraft III (version 2002),
          // if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
          // and the table is loaded from the current file offset
          if(ByteOffset == SFILE_INVALID_POS)
              FileStream_GetPos(ha->pStream, &ByteOffset);
  
          // On archives v 1.0, hash table and block table can go beyond EOF.
          // Storm.dll reads as much as possible, then fills the missing part with zeros.
          // Abused by Spazzler map protector which sets hash table size to 0x00100000
          // Abused by NP_Protect in MPQs v4 as well
          if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
          {
              // Cut the table size
              FileStream_GetSize(ha->pStream, &FileSize);
              if((ByteOffset + dwBytesToRead) > FileSize)
              {
                  // Fill the extra data with zeros
                  dwBytesToRead = (DWORD)(FileSize - ByteOffset);
                  memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
  
                  // Give the caller information that the table was cut
                  if(pbTableIsCut != NULL)
                      pbTableIsCut[0] = true;
              }
          }
  
          // If everything succeeded, read the raw table from the MPQ
          if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
          {
              // Verify the MD5 of the table, if present
              if(!VerifyDataBlockHash(pbToRead, dwBytesToRead, pbTableHash))
              {
                  dwErrCode = ERROR_FILE_CORRUPT;
              }
          }
          else
          {
              dwErrCode = GetLastError();
          }
  
          if(dwErrCode == ERROR_SUCCESS)
          {
              // First of all, decrypt the table
              if(dwKey != 0)
              {
                  BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
                  DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
                  BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
              }
  
              // If the table is compressed, decompress it
              if(dwCompressedSize < dwTableSize)
              {
                  int cbOutBuffer = (int)dwTableSize;
                  int cbInBuffer = (int)dwCompressedSize;
  
                  if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer))
                      dwErrCode = GetLastError();
              }
  
              // Make sure that the table is properly byte-swapped
              BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
          }
  
          // If read failed, free the table and return
          if(dwErrCode != ERROR_SUCCESS)
          {
              STORM_FREE(pbMpqTable);
              pbMpqTable = NULL;
          }
  
          // Free the compression buffer, if any
          if(pbCompressed != NULL)
              STORM_FREE(pbCompressed);
      }
  
      // Return the MPQ table
      return pbMpqTable;
  }
  
  unsigned char * AllocateMd5Buffer(
      DWORD dwRawDataSize,
      DWORD dwChunkSize,
      LPDWORD pcbMd5Size)
  {
      unsigned char * md5_array;
      DWORD cbMd5Size;
  
      // Sanity check
      assert(dwRawDataSize != 0);
      assert(dwChunkSize != 0);
  
      // Calculate how many MD5's we will calculate
      cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE;
  
      // Allocate space for array or MD5s
      md5_array = STORM_ALLOC(BYTE, cbMd5Size);
  
      // Give the size of the MD5 array
      if(pcbMd5Size != NULL)
          *pcbMd5Size = cbMd5Size;
      return md5_array;
  }
  
  // Allocates sector buffer and sector offset table
  DWORD AllocateSectorBuffer(TMPQFile * hf)
  {
      TMPQArchive * ha = hf->ha;
  
      // Caller of AllocateSectorBuffer must ensure these
      assert(hf->pbFileSector == NULL);
      assert(hf->pFileEntry != NULL);
      assert(hf->ha != NULL);
  
      // Don't allocate anything if the file has zero size
      if(hf->pFileEntry->dwFileSize == 0 || hf->dwDataSize == 0)
          return ERROR_SUCCESS;
  
      // Determine the file sector size and allocate buffer for it
      hf->dwSectorSize = (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) ? hf->dwDataSize : ha->dwSectorSize;
      hf->pbFileSector = STORM_ALLOC(BYTE, hf->dwSectorSize);
      hf->dwSectorOffs = SFILE_INVALID_POS;
  
      // Return result
      return (hf->pbFileSector != NULL) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
  }
  
  // Allocates sector offset table
  DWORD AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile)
  {
      TMPQArchive * ha = hf->ha;
      DWORD dwLength = sizeof(TPatchInfo);
  
      // The following conditions must be true
      assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
      assert(hf->pPatchInfo == NULL);
  
  __AllocateAndLoadPatchInfo:
  
      // Allocate space for patch header. Start with default size,
      // and if its size if bigger, then we reload them
      hf->pPatchInfo = STORM_ALLOC(TPatchInfo, 1);
      if(hf->pPatchInfo == NULL)
          return ERROR_NOT_ENOUGH_MEMORY;
  
      // Do we have to load the patch header from the file ?
      if(bLoadFromFile)
      {
          // Load the patch header
          if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength))
          {
              // Free the patch info
              STORM_FREE(hf->pPatchInfo);
              hf->pPatchInfo = NULL;
              return GetLastError();
          }
  
          // Perform necessary swapping
          hf->pPatchInfo->dwLength = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwLength);
          hf->pPatchInfo->dwFlags = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwFlags);
          hf->pPatchInfo->dwDataSize = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwDataSize);
  
          // Verify the size of the patch header
          // If it's not default size, we have to reload them
          if(hf->pPatchInfo->dwLength > dwLength)
          {
              // Free the patch info
              dwLength = hf->pPatchInfo->dwLength;
              STORM_FREE(hf->pPatchInfo);
              hf->pPatchInfo = NULL;
  
              // If the length is out of all possible ranges, fail the operation
              if(dwLength > 0x400)
                  return ERROR_FILE_CORRUPT;
              goto __AllocateAndLoadPatchInfo;
          }
  
          // Patch file data size according to the patch header
          hf->dwDataSize = hf->pPatchInfo->dwDataSize;
      }
      else
      {
          memset(hf->pPatchInfo, 0, dwLength);
      }
  
      // Save the final length to the patch header
      hf->pPatchInfo->dwLength = dwLength;
      hf->pPatchInfo->dwFlags  = 0x80000000;
      return ERROR_SUCCESS;
  }
  
  // Allocates sector offset table
  DWORD AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile)
  {
      TMPQArchive * ha = hf->ha;
      TFileEntry * pFileEntry = hf->pFileEntry;
      DWORD dwSectorOffsLen;
      bool bSectorOffsetTableCorrupt = false;
  
      // Caller of AllocateSectorOffsets must ensure these
      assert(hf->SectorOffsets == NULL);
      assert(hf->pFileEntry != NULL);
      assert(hf->dwDataSize != 0);
      assert(hf->ha != NULL);
  
      // If the file is stored as single unit, just set number of sectors to 1
      if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
      {
          hf->dwSectorCount = 1;
          return ERROR_SUCCESS;
      }
  
      // Calculate the number of data sectors
      // Note that this doesn't work if the file size is zero
      hf->dwSectorCount = ((hf->dwDataSize - 1) / hf->dwSectorSize) + 1;
  
      // Calculate the number of file sectors
      dwSectorOffsLen = (hf->dwSectorCount + 1) * sizeof(DWORD);
  
      // If MPQ_FILE_SECTOR_CRC flag is set, there will either be extra DWORD
      // or an array of MD5's. Either way, we read at least 4 bytes more
      // in order to save additional read from the file.
      if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
          dwSectorOffsLen += sizeof(DWORD);
  
      // Only allocate and load the table if the file is compressed
      if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
      {
          __LoadSectorOffsets:
  
          // Allocate the sector offset table
          hf->SectorOffsets = STORM_ALLOC(DWORD, (dwSectorOffsLen / sizeof(DWORD)));
          if(hf->SectorOffsets == NULL)
              return ERROR_NOT_ENOUGH_MEMORY;
  
          // Only read from the file if we are supposed to do so
          if(bLoadFromFile)
          {
              ULONGLONG RawFilePos = hf->RawFilePos;
  
              // Append the length of the patch info, if any
              if(hf->pPatchInfo != NULL)
              {
                  if((RawFilePos + hf->pPatchInfo->dwLength) < RawFilePos)
                      return ERROR_FILE_CORRUPT;
                  RawFilePos += hf->pPatchInfo->dwLength;
              }
  
              // Load the sector offsets from the file
              if(!FileStream_Read(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
              {
                  // Free the sector offsets
                  STORM_FREE(hf->SectorOffsets);
                  hf->SectorOffsets = NULL;
                  return GetLastError();
              }
  
              // Swap the sector positions
              BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
  
              // Decrypt loaded sector positions if necessary
              if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
              {
                  // If we don't know the file key, try to find it.
                  if(hf->dwFileKey == 0)
                  {
                      hf->dwFileKey = DetectFileKeyBySectorSize(hf->SectorOffsets, ha->dwSectorSize, dwSectorOffsLen);
                      if(hf->dwFileKey == 0)
                      {
                          STORM_FREE(hf->SectorOffsets);
                          hf->SectorOffsets = NULL;
                          return ERROR_UNKNOWN_FILE_KEY;
                      }
                  }
  
                  // Decrypt sector positions
                  DecryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
              }
  
              //
              // Validate the sector offset table
              //
              // Note: Some MPQ protectors put the actual file data before the sector offset table.
              // In this case, the sector offsets are negative (> 0x80000000).
              //
  
              for(DWORD i = 0; i < hf->dwSectorCount; i++)
              {
                  DWORD dwSectorOffset1 = hf->SectorOffsets[i+1];
                  DWORD dwSectorOffset0 = hf->SectorOffsets[i];
  
                  // Every following sector offset must be bigger than the previous one
                  if(dwSectorOffset1 < dwSectorOffset0)
                  {
                      bSectorOffsetTableCorrupt = true;
                      break;
                  }
  
                  // The sector size must not be bigger than compressed file size
                  // Edit: Yes, but apparently, in original Storm.dll, the compressed
                  // size is not checked anywhere. However, we need to do this check
                  // in order to sector offset table malformed by MPQ protectors
                  if((dwSectorOffset1 - dwSectorOffset0) > ha->dwSectorSize)
                  {
                      bSectorOffsetTableCorrupt = true;
                      break;
                  }
              }
  
              // If data corruption detected, free the sector offset table
              if(bSectorOffsetTableCorrupt)
              {
                  STORM_FREE(hf->SectorOffsets);
                  hf->SectorOffsets = NULL;
                  return ERROR_FILE_CORRUPT;
              }
  
              //
              // There may be various extra DWORDs loaded after the sector offset table.
              // They are mostly empty on WoW release MPQs, but on MPQs from PTR,
              // they contain random non-zero data. Their meaning is unknown.
              //
              // These extra values are, however, included in the dwCmpSize in the file
              // table. We cannot ignore them, because compacting archive would fail
              //
  
              // Clear the lower 2 bits in order to make sure that the value is aligned to 4 bytes
              if((hf->SectorOffsets[0] & 0xFFFFFFFC) > dwSectorOffsLen)
              {
                  // MPQ protectors put some ridiculous values there. We must limit the extra bytes
                  if(hf->SectorOffsets[0] > (dwSectorOffsLen + 0x400))
                      return ERROR_FILE_CORRUPT;
  
                  // Free the old sector offset table
                  dwSectorOffsLen = hf->SectorOffsets[0];
                  STORM_FREE(hf->SectorOffsets);
                  goto __LoadSectorOffsets;
              }
          }
          else
          {
              memset(hf->SectorOffsets, 0, dwSectorOffsLen);
              hf->SectorOffsets[0] = dwSectorOffsLen;
          }
      }
  
      return ERROR_SUCCESS;
  }
  
  DWORD AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
  {
      TMPQArchive * ha = hf->ha;
      TFileEntry * pFileEntry = hf->pFileEntry;
      ULONGLONG RawFilePos;
      DWORD dwCompressedSize = 0;
      DWORD dwExpectedSize;
      DWORD dwCrcOffset;                      // Offset of the CRC table, relative to file offset in the MPQ
      DWORD dwCrcSize;
  
      // Caller of AllocateSectorChecksums must ensure these
      assert(hf->SectorChksums == NULL);
      assert(hf->SectorOffsets != NULL);
      assert(hf->pFileEntry != NULL);
      assert(hf->ha != NULL);
  
      // Single unit files don't have sector checksums
      if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
          return ERROR_SUCCESS;
  
      // Caller must ensure that we are only called when we have sector checksums
      assert(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
  
      //
      // Older MPQs store an array of CRC32's after
      // the raw file data in the MPQ.
      //
      // In newer MPQs, the (since Cataclysm BETA) the (attributes) file
      // contains additional 32-bit values beyond the sector table.
      // Their number depends on size of the (attributes), but their
      // meaning is unknown. They are usually zeroed in retail game files,
      // but contain some sort of checksum in BETA MPQs
      //
  
      // Does the size of the file table match with the CRC32-based checksums?
      dwExpectedSize = (hf->dwSectorCount + 2) * sizeof(DWORD);
      if(hf->SectorOffsets[0] != 0 && hf->SectorOffsets[0] == dwExpectedSize)
      {
          // If we are not loading from the MPQ file, we just allocate the sector table
          // In that case, do not check any sizes
          if(bLoadFromFile == false)
          {
              hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount);
              if(hf->SectorChksums == NULL)
                  return ERROR_NOT_ENOUGH_MEMORY;
  
              // Fill the checksum table with zeros
              memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD));
              return ERROR_SUCCESS;
          }
          else
          {
              // Is there valid size of the sector checksums?
              if(hf->SectorOffsets[hf->dwSectorCount + 1] >= hf->SectorOffsets[hf->dwSectorCount])
                  dwCompressedSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
  
              // Ignore cases when the length is too small or too big.
              if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize)
                  return ERROR_SUCCESS;
  
              // Calculate offset of the CRC table
              dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
              dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount];
              RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
  
              // Now read the table from the MPQ
              hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, NULL, dwCompressedSize, dwCrcSize, 0, NULL);
              if(hf->SectorChksums == NULL)
                  return ERROR_NOT_ENOUGH_MEMORY;
          }
      }
  
      // If the size doesn't match, we ignore sector checksums
  //  assert(false);
      return ERROR_SUCCESS;
  }
  
  DWORD WritePatchInfo(TMPQFile * hf)
  {
      TMPQArchive * ha = hf->ha;
      TPatchInfo * pPatchInfo = hf->pPatchInfo;
  
      // The caller must make sure that this function is only called
      // when the following is true.
      assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
      assert(pPatchInfo != NULL);
  
      BSWAP_ARRAY32_UNSIGNED(pPatchInfo, 3 * sizeof(DWORD));
      if(!FileStream_Write(ha->pStream, &hf->RawFilePos, pPatchInfo, sizeof(TPatchInfo)))
          return GetLastError();
  
      return ERROR_SUCCESS;
  }
  
  DWORD WriteSectorOffsets(TMPQFile * hf)
  {
      TMPQArchive * ha = hf->ha;
      TFileEntry * pFileEntry = hf->pFileEntry;
      ULONGLONG RawFilePos = hf->RawFilePos;
      DWORD dwSectorOffsLen;
  
      // The caller must make sure that this function is only called
      // when the following is true.
      assert(hf->pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
      assert(hf->SectorOffsets != NULL);
      dwSectorOffsLen = hf->SectorOffsets[0];
  
      // If file is encrypted, sector positions are also encrypted
      if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
          EncryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
      BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
  
      // Adjust sector offset table position, if we also have patch info
      if(hf->pPatchInfo != NULL)
          RawFilePos += hf->pPatchInfo->dwLength;
  
      // Write sector offsets to the archive
      if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
          return GetLastError();
  
      // Not necessary, as the sector checksums
      // are going to be freed when this is done.
  //  BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
      return ERROR_SUCCESS;
  }
  
  DWORD WriteSectorChecksums(TMPQFile * hf)
  {
      TMPQArchive * ha = hf->ha;
      ULONGLONG RawFilePos;
      TFileEntry * pFileEntry = hf->pFileEntry;
      LPBYTE pbCompressed;
      DWORD dwCompressedSize = 0;
      DWORD dwErrCode = ERROR_SUCCESS;
      DWORD dwCrcSize;
      int nOutSize;
  
      // The caller must make sure that this function is only called
      // when the following is true.
      assert(hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
      assert(hf->SectorOffsets != NULL);
      assert(hf->SectorChksums != NULL);
  
      // If the MPQ has MD5 of each raw data chunk,
      // we leave sector offsets empty
      if(ha->pHeader->dwRawChunkSize != 0)
      {
          hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount];
          return ERROR_SUCCESS;
      }
  
      // Calculate size of the checksum array
      dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
  
      // Allocate buffer for compressed sector CRCs.
      pbCompressed = STORM_ALLOC(BYTE, dwCrcSize);
      if(pbCompressed == NULL)
          return ERROR_NOT_ENOUGH_MEMORY;
  
      // Perform the compression
      BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
  
      nOutSize = (int)dwCrcSize;
      SCompCompress(pbCompressed, &nOutSize, hf->SectorChksums, (int)dwCrcSize, MPQ_COMPRESSION_ZLIB, 0, 0);
      dwCompressedSize = (DWORD)nOutSize;
  
      // Write the sector CRCs to the archive
      RawFilePos = hf->RawFilePos + hf->SectorOffsets[hf->dwSectorCount];
      if(hf->pPatchInfo != NULL)
          RawFilePos += hf->pPatchInfo->dwLength;
      if(!FileStream_Write(ha->pStream, &RawFilePos, pbCompressed, dwCompressedSize))
          dwErrCode = GetLastError();
  
      // Not necessary, as the sector checksums
      // are going to be freed when this is done.
  //  BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
  
      // Store the sector CRCs
      hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount] + dwCompressedSize;
      pFileEntry->dwCmpSize += dwCompressedSize;
      STORM_FREE(pbCompressed);
      return dwErrCode;
  }
  
  DWORD WriteMemDataMD5(
      TFileStream * pStream,
      ULONGLONG RawDataOffs,
      void * pvRawData,
      DWORD dwRawDataSize,
      DWORD dwChunkSize,
      LPDWORD pcbTotalSize)
  {
      unsigned char * md5_array;
      unsigned char * md5;
      LPBYTE pbRawData = (LPBYTE)pvRawData;
      DWORD dwBytesRemaining = dwRawDataSize;
      DWORD dwMd5ArraySize = 0;
      DWORD dwErrCode = ERROR_SUCCESS;
  
      // Allocate buffer for array of MD5
      md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
      if(md5_array == NULL)
          return ERROR_NOT_ENOUGH_MEMORY;
  
      // For every file chunk, calculate MD5
      while(dwBytesRemaining != 0)
      {
          // Get the remaining number of bytes to read
          dwChunkSize = STORMLIB_MIN(dwBytesRemaining, dwChunkSize);
  
          // Calculate MD5
          CalculateDataBlockHash(pbRawData, dwChunkSize, md5);
          md5 += MD5_DIGEST_SIZE;
  
          // Move offset and size
          dwBytesRemaining -= dwChunkSize;
          pbRawData += dwChunkSize;
      }
  
      // Write the array od MD5's to the file
      RawDataOffs += dwRawDataSize;
      if(!FileStream_Write(pStream, &RawDataOffs, md5_array, dwMd5ArraySize))
          dwErrCode = GetLastError();
  
      // Give the caller the size of the MD5 array
      if(pcbTotalSize != NULL)
          *pcbTotalSize = dwRawDataSize + dwMd5ArraySize;
  
      // Free buffers and exit
      STORM_FREE(md5_array);
      return dwErrCode;
  }
  
  
  // Writes the MD5 for each chunk of the raw file data
  DWORD WriteMpqDataMD5(
      TFileStream * pStream,
      ULONGLONG RawDataOffs,
      DWORD dwRawDataSize,
      DWORD dwChunkSize)
  {
      unsigned char * md5_array;
      unsigned char * md5;
      LPBYTE pbFileChunk;
      DWORD dwMd5ArraySize = 0;
      DWORD dwToRead = dwRawDataSize;
      DWORD dwErrCode = ERROR_SUCCESS;
  
      // Allocate buffer for array of MD5
      md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
      if(md5_array == NULL)
          return ERROR_NOT_ENOUGH_MEMORY;
  
      // Allocate space for file chunk
      pbFileChunk = STORM_ALLOC(BYTE, dwChunkSize);
      if(pbFileChunk == NULL)
      {
          STORM_FREE(md5_array);
          return ERROR_NOT_ENOUGH_MEMORY;
      }
  
      // For every file chunk, calculate MD5
      while(dwRawDataSize != 0)
      {
          // Get the remaining number of bytes to read
          dwToRead = STORMLIB_MIN(dwRawDataSize, dwChunkSize);
  
          // Read the chunk
          if(!FileStream_Read(pStream, &RawDataOffs, pbFileChunk, dwToRead))
          {
              dwErrCode = GetLastError();
              break;
          }
  
          // Calculate MD5
          CalculateDataBlockHash(pbFileChunk, dwToRead, md5);
          md5 += MD5_DIGEST_SIZE;
  
          // Move offset and size
          RawDataOffs += dwToRead;
          dwRawDataSize -= dwToRead;
      }
  
      // Write the array od MD5's to the file
      if(dwErrCode == ERROR_SUCCESS)
      {
          if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize))
              dwErrCode = GetLastError();
      }
  
      // Free buffers and exit
      STORM_FREE(pbFileChunk);
      STORM_FREE(md5_array);
      return dwErrCode;
  }
  
  // Frees the structure for MPQ file
  void FreeFileHandle(TMPQFile *& hf)
  {
      if(hf != NULL)
      {
          // If we have patch file attached to this one, free it first
          if(hf->hfPatch != NULL)
              FreeFileHandle(hf->hfPatch);
  
          // Then free all buffers allocated in the file structure
          if(hf->pbFileData != NULL)
              STORM_FREE(hf->pbFileData);
          if(hf->pPatchInfo != NULL)
              STORM_FREE(hf->pPatchInfo);
          if(hf->SectorOffsets != NULL)
              STORM_FREE(hf->SectorOffsets);
          if(hf->SectorChksums != NULL)
              STORM_FREE(hf->SectorChksums);
          if(hf->pbFileSector != NULL)
              STORM_FREE(hf->pbFileSector);
          if(hf->pStream != NULL)
              FileStream_Close(hf->pStream);
          STORM_FREE(hf);
          hf = NULL;
      }
  }
  
  // Frees the MPQ archive
  void FreeArchiveHandle(TMPQArchive *& ha)
  {
      if(ha != NULL)
      {
          // First of all, free the patch archive, if any
          if(ha->haPatch != NULL)
              FreeArchiveHandle(ha->haPatch);
  
          // Free the patch prefix, if any
          if(ha->pPatchPrefix != NULL)
              STORM_FREE(ha->pPatchPrefix);
  
          // Close the file stream
          FileStream_Close(ha->pStream);
          ha->pStream = NULL;
  
          // Free the file names from the file table
          if(ha->pFileTable != NULL)
          {
              for(DWORD i = 0; i < ha->dwFileTableSize; i++)
              {
                  if(ha->pFileTable[i].szFileName != NULL)
                      STORM_FREE(ha->pFileTable[i].szFileName);
                  ha->pFileTable[i].szFileName = NULL;
              }
  
              // Then free all buffers allocated in the archive structure
              STORM_FREE(ha->pFileTable);
          }
  
          if(ha->pHashTable != NULL)
              STORM_FREE(ha->pHashTable);
          if(ha->pHetTable != NULL)
              FreeHetTable(ha->pHetTable);
          STORM_FREE(ha);
          ha = NULL;
      }
  }
  
  bool IsInternalMpqFileName(const char * szFileName)
  {
      if(szFileName != NULL && szFileName[0] == '(')
      {
          if(!_stricmp(szFileName, LISTFILE_NAME) ||
             !_stricmp(szFileName, ATTRIBUTES_NAME) ||
             !_stricmp(szFileName, SIGNATURE_NAME))
          {
              return true;
          }
      }
  
      return false;
  }
  
  // Verifies if the file name is a pseudo-name
  bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex)
  {
      DWORD dwFileIndex = 0;
  
      if(szFileName != NULL)
      {
          // Must be "File########.ext"
          if(!_strnicmp(szFileName, "File", 4))
          {
              // Check 8 digits
              for(int i = 4; i < 4+8; i++)
              {
                  if(szFileName[i] < '0' || szFileName[i] > '9')
                      return false;
                  dwFileIndex = (dwFileIndex * 10) + (szFileName[i] - '0');
              }
  
              // An extension must follow
              if(szFileName[12] == '.')
              {
                  if(pdwFileIndex != NULL)
                      *pdwFileIndex = dwFileIndex;
                  return true;
              }
          }
      }
  
      // Not a pseudo-name
      return false;
  }
  
  //-----------------------------------------------------------------------------
  // Functions calculating and verifying the MD5 signature
  
  bool IsValidMD5(LPBYTE pbMd5)
  {
      LPDWORD Md5 = (LPDWORD)pbMd5;
  
      return ((Md5 != NULL) && (Md5[0] | Md5[1] | Md5[2] | Md5[3])) ? true : false;
  }
  
  bool IsValidSignature(LPBYTE pbSignature)
  {
      LPDWORD Signature = (LPDWORD)pbSignature;
      DWORD SigValid = 0;
  
      for(int i = 0; i < MPQ_WEAK_SIGNATURE_SIZE / sizeof(DWORD); i++)
          SigValid |= Signature[i];
  
      return (SigValid != 0) ? true : false;
  }
  
  
  bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
  {
      hash_state md5_state;
      BYTE md5_digest[MD5_DIGEST_SIZE];
      bool bResult = true;
  
      // Don't verify the block if the MD5 is not valid.
      if(IsValidMD5(expected_md5))
      {
          // Calculate the MD5 of the data block
          md5_init(&md5_state);
          md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
          md5_done(&md5_state, md5_digest);
  
          // Does the MD5's match?
          bResult = (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
      }
  
      return bResult;
  }
  
  void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
  {
      hash_state md5_state;
  
      md5_init(&md5_state);
      md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
      md5_done(&md5_state, md5_hash);
  }
  
  
  //-----------------------------------------------------------------------------
  // Swapping functions
  
  #ifndef STORMLIB_LITTLE_ENDIAN
  
  // Swaps a signed 16-bit integer
  int16_t SwapInt16(uint16_t val)
  {
      return (val << 8) | ((val >> 8) & 0xFF);
  }
  
  // Swaps an unsigned 16-bit integer
  uint16_t SwapUInt16(uint16_t val)
  {
      return (val << 8) | (val >> 8 );
  }
  
  // Swaps a signed 32-bit integer
  int32_t SwapInt32(uint32_t val)
  {
      val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF );
      return (val << 16) | ((val >> 16) & 0xFFFF);
  }
  
  // Swaps an unsigned 32-bit integer
  uint32_t SwapUInt32(uint32_t val)
  {
      val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF );
      return (val << 16) | (val >> 16);
  }
  
  // Swaps a signed 64-bit integer
  int64_t SwapInt64(uint64_t val)
  {
      val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
      val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
      return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
  }
  
  // Swaps an unsigned 64-bit integer
  uint64_t SwapUInt64(uint64_t val)
  {
      val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
      val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
      return (val << 32) | (val >> 32);
  }
  
  // Swaps array of unsigned 16-bit integers
  void ConvertUInt16Buffer(void * ptr, size_t length)
  {
      uint16_t * buffer = (uint16_t *)ptr;
      uint32_t nElements = (uint32_t)(length / sizeof(uint16_t));
  
      while(nElements-- > 0)
  	{
  		*buffer = SwapUInt16(*buffer);
  		buffer++;
  	}
  }
  
  // Swaps array of unsigned 32-bit integers
  void ConvertUInt32Buffer(void * ptr, size_t length)
  {
      uint32_t * buffer = (uint32_t *)ptr;
      uint32_t nElements = (uint32_t)(length / sizeof(uint32_t));
  
  	while(nElements-- > 0)
  	{
  		*buffer = SwapUInt32(*buffer);
  		buffer++;
  	}
  }
  
  // Swaps array of unsigned 64-bit integers
  void ConvertUInt64Buffer(void * ptr, size_t length)
  {
      uint64_t * buffer = (uint64_t *)ptr;
      uint32_t nElements = (uint32_t)(length / sizeof(uint64_t));
  
  	while(nElements-- > 0)
  	{
  		*buffer = SwapUInt64(*buffer);
  		buffer++;
  	}
  }
  
  // Swaps the TMPQHeader structure
  void ConvertTMPQHeader(void *header, uint16_t version)
  {
  	TMPQHeader * theHeader = (TMPQHeader *)header;
  
      // Swap header part version 1
      if(version == MPQ_FORMAT_VERSION_1)
      {
  	    theHeader->dwID = SwapUInt32(theHeader->dwID);
  	    theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize);
  	    theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize);
  	    theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion);
  	    theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize);
  	    theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos);
  	    theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos);
  	    theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize);
  	    theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize);
      }
  
  	if(version == MPQ_FORMAT_VERSION_2)
  	{
  		theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
          theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
  		theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
      }
  
      if(version == MPQ_FORMAT_VERSION_3)
      {
          theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
          theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
          theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
      }
  
      if(version == MPQ_FORMAT_VERSION_4)
  	{
          theHeader->HashTableSize64    = SwapUInt64(theHeader->HashTableSize64);
          theHeader->BlockTableSize64   = SwapUInt64(theHeader->BlockTableSize64);
          theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64);
          theHeader->HetTableSize64     = SwapUInt64(theHeader->HetTableSize64);
          theHeader->BetTableSize64     = SwapUInt64(theHeader->BetTableSize64);
      }
  }
  
  #endif  // STORMLIB_LITTLE_ENDIAN
  