text: /*****************************************************************************/
  /* CascOpenFile.cpp                       Copyright (c) Ladislav Zezula 2014 */
  /*---------------------------------------------------------------------------*/
  /* System-dependent directory functions for CascLib                          */
  /*---------------------------------------------------------------------------*/
  /*   Date    Ver   Who  Comment                                              */
  /* --------  ----  ---  -------                                              */
  /* 01.05.14  1.00  Lad  The first version of CascOpenFile.cpp                */
  /*****************************************************************************/
  
  #define __CASCLIB_SELF__
  #include "CascLib.h"
  #include "CascCommon.h"
  
  //-----------------------------------------------------------------------------
  // Local structures
  
  typedef struct _BLTE_FRAME
  {
      BYTE CompressedSize[4];                     // Compressed file size as big endian
      BYTE FrameSize[4];                          // File size as big endian
      BYTE md5[MD5_HASH_SIZE];                    // Hash of the compressed frame
  
  } BLTE_FRAME, *PBLTE_FRAME;
  
  //-----------------------------------------------------------------------------
  // Local functions
  
  TCascFile * IsValidFileHandle(HANDLE hFile);        // In CascOpenFile.cpp
  
  static int EnsureDataStreamIsOpen(TCascFile * hf)
  {
      TCascStorage * hs = hf->hs;
      TFileStream * pStream = NULL;
      TCHAR * szDataFile;
      TCHAR szPlainName[0x40];
  
      // If the file is not open yet, do it
      if(hs->DataFileArray[hf->ArchiveIndex] == NULL)
      {
          // Prepare the name of the data file
          _stprintf(szPlainName, _T("data.%03u"), hf->ArchiveIndex);
          szDataFile = CombinePath(hs->szIndexPath, szPlainName);
  
          // Open the data file
          if(szDataFile != NULL)
          {
              // Open the stream
              pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
              hs->DataFileArray[hf->ArchiveIndex] = pStream;
              CASC_FREE(szDataFile);
          }
      }
  
      // Return error or success
      hf->pStream = hs->DataFileArray[hf->ArchiveIndex];
      return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
  }
  
  static int LoadFileFrames(TCascFile * hf)
  {
      PBLTE_FRAME pFileFrames;
      PBLTE_FRAME pFileFrame;
      ULONGLONG ArchiveFileOffset;
      DWORD FrameOffset = 0;
      DWORD FileSize = 0;
      int nError = ERROR_SUCCESS;
  
      assert(hf != NULL);
      assert(hf->pStream != NULL);
      assert(hf->pFrames != NULL);
  
      // Allocate frame array
      pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, hf->FrameCount);
      if(pFileFrames != NULL)
      {
          // Load the frame array
          ArchiveFileOffset = hf->FramesOffset;
          if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, hf->FrameCount * sizeof(BLTE_FRAME)))
          {
              // Move the raw archive offset
              ArchiveFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME));
  
              // Copy the frames to the file structure
              for(DWORD i = 0; i < hf->FrameCount; i++, pFileFrame++)
              {
                  hf->pFrames[i].FrameArchiveOffset = (DWORD)ArchiveFileOffset;
                  hf->pFrames[i].FrameFileOffset = FrameOffset;
                  hf->pFrames[i].CompressedSize = ConvertBytesToInteger_4(pFileFrame->CompressedSize);
                  hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize);
                  memcpy(hf->pFrames[i].md5, pFileFrame->md5, MD5_HASH_SIZE);
  
                  ArchiveFileOffset += hf->pFrames[i].CompressedSize;
                  FrameOffset += hf->pFrames[i].FrameSize;
                  FileSize += hf->pFrames[i].FrameSize;
              }
          }
          else
              nError = GetLastError();
  
          // Note: Do not take the FileSize from the sum of frames.
          // This value is invalid when loading the ENCODING file.
  //      hf->FileSize = FileSize;
  
  #ifdef CASCLIB_TEST
          hf->FileSize_FrameSum = FileSize;
  #endif
  
          // Free the array
          CASC_FREE(pFileFrames);
      }
      else
          nError = ERROR_NOT_ENOUGH_MEMORY;
  
      return nError;
  }
  
  static int EnsureHeaderAreaIsLoaded(TCascFile * hf)
  {
      TCascStorage * hs = hf->hs;
      ULONGLONG FileOffset = hf->HeaderOffset;
      LPBYTE pbHeaderArea;
      DWORD FileSignature;
      DWORD FileSize;
      BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
      int nError;
  
      // We need the data file to be open
      nError = EnsureDataStreamIsOpen(hf);
      if(nError != ERROR_SUCCESS)
          return nError;
  
      // Make sure that we already know the shift
      // to the begin of file data.
      // Note that older builds of Heroes of the Storm have entries pointing
      // to the beginning of the header area.
      // Newer versions of HOTS have encoding entries pointing directly to
      // the BLTE header
      if(hs->dwFileBeginDelta == 0xFFFFFFFF)
      {
          FileSignature = 0;
          FileOffset = hf->HeaderOffset;
          if(!FileStream_Read(hf->pStream, &FileOffset, &FileSignature, sizeof(DWORD)))
              return ERROR_FILE_CORRUPT;
  
          hs->dwFileBeginDelta = (FileSignature == BLTE_HEADER_SIGNATURE) ? BLTE_HEADER_DELTA : 0;
      }
             
      // If the file size is not loaded yet, do it
      if(hf->FrameCount == 0)
      {
          // Load the part before BLTE header + header itself
          FileOffset = hf->HeaderOffset - hs->dwFileBeginDelta;
          if(!FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea)))
              return ERROR_FILE_CORRUPT;
  
          // Copy the MD5 hash of the frame array
          memcpy(hf->FrameArrayHash, HeaderArea, MD5_HASH_SIZE);
          pbHeaderArea = HeaderArea + MD5_HASH_SIZE;
  
          // Copy the file size
          FileSize = ConvertBytesToInteger_4_LE(pbHeaderArea);
          pbHeaderArea += 0x0E;
  
          // Verify the BLTE signature
          if(ConvertBytesToInteger_4_LE(pbHeaderArea) != BLTE_HEADER_SIGNATURE)
              return ERROR_BAD_FORMAT;
          pbHeaderArea += sizeof(DWORD);
  
          // Load the size of the frame headers
          hf->HeaderSize = ConvertBytesToInteger_4(pbHeaderArea);
          if(hf->HeaderSize & 0x80000000)
              return ERROR_BAD_FORMAT;
          pbHeaderArea += sizeof(DWORD);
  
          // Read the header size
          assert(hs->dwFileBeginDelta <= BLTE_HEADER_DELTA);
          hf->HeaderOffset += (BLTE_HEADER_DELTA - hs->dwFileBeginDelta);
          hf->FrameCount = 1;
  
          // Retrieve the frame count, if different from 1
          if(hf->HeaderSize != 0)
          {
              // The next byte must be 0x0F
              if(pbHeaderArea[0] != 0x0F)
                  return ERROR_BAD_FORMAT;
              pbHeaderArea++;
  
              // Next three bytes form number of frames
              hf->FrameCount = ConvertBytesToInteger_3(pbHeaderArea);
          }
  
  #ifdef CASCLIB_TEST
          hf->FileSize_HdrArea = FileSize;
  #endif
      }
  
      return ERROR_SUCCESS;
  }
  
  static int EnsureFrameHeadersLoaded(TCascFile * hf)
  {
      int nError;
  
      // Make sure we have header area loaded
      nError = EnsureHeaderAreaIsLoaded(hf);
      if(nError != ERROR_SUCCESS)
          return nError;
  
      // If the frame headers are not loaded yet, do it
      if(hf->pFrames == NULL)
      {
          // Allocate the frame array
          hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount);
          if(hf->pFrames != NULL)
          {
              // Either load the frames from the file or supply them on our own
              if(hf->HeaderSize != 0)
              {
                  hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD) + sizeof(DWORD);
                  nError = LoadFileFrames(hf);
              }
              else
              {
                  // Offset of the first frame is right after the file frames
                  hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD);
                  
                  hf->pFrames[0].FrameArchiveOffset = hf->FramesOffset;
                  hf->pFrames[0].FrameFileOffset = 0;
                  hf->pFrames[0].CompressedSize = hf->CompressedSize;
                  hf->pFrames[0].FrameSize      = hf->FileSize;
                  memset(hf->pFrames[0].md5, 0, MD5_HASH_SIZE);
              }
          }
  
          // Return result
          return (hf->pFrames != NULL) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
      }
  
      return ERROR_SUCCESS;
  }
  
  static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer)
  {
      PCASC_FILE_FRAME pFrame = hf->pFrames;
      DWORD FrameBegin;
      DWORD FrameEnd;
  
      // Sanity checks
      assert(hf->pFrames != NULL);
      assert(hf->FrameCount != 0);
  
      // Find the frame where to read from
      for(DWORD i = 0; i < hf->FrameCount; i++, pFrame++)
      {
          // Does the read request fit into the current frame?
          FrameBegin = pFrame->FrameFileOffset;
          FrameEnd = FrameBegin + pFrame->FrameSize;
          if(FrameBegin <= FilePointer && FilePointer < FrameEnd)
              return pFrame;
      }
  
      // Not found, sorry
      return NULL;
  }
  
  //-----------------------------------------------------------------------------
  // Public functions
  
  //
  // THE FILE SIZE PROBLEM
  //
  // There are members called "FileSize" in many CASC-related structure
  // For various files, these variables have different meaning.
  //
  // Storage      FileName  FileSize     FrameSum    HdrArea     IdxEntry    EncEntry    RootEntry
  // -----------  --------  ----------   --------    --------    --------    --------    ---------
  // HotS(29049)  ENCODING  0x0024BA45 - 0x0024b98a  0x0024BA45  0x0024BA45  n/a         n/a
  // HotS(29049)  ROOT      0x00193340 - 0x00193340  0x0010db65  0x0010db65  0x00193340  n/a
  // HotS(29049)  (other)   0x00001080 - 0x00001080  0x000008eb  0x000008eb  0x00001080  0x00001080
  //
  // WoW(18888)   ENCODING  0x030d487b - 0x030dee79  0x030d487b  0x030d487b  n/a         n/a
  // WoW(18888)   ROOT      0x016a9800 - n/a         0x0131313d  0x0131313d  0x016a9800  n/a
  // WoW(18888)   (other)   0x000007d0 - 0x000007d0  0x00000397  0x00000397  0x000007d0  n/a
  //
  
  DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
  {
      TCascFile * hf;
      int nError;
  
      CASCLIB_UNUSED(pdwFileSizeHigh);
  
      // Validate the file handle
      if((hf = IsValidFileHandle(hFile)) == NULL)
      {
          SetLastError(ERROR_INVALID_HANDLE);
          return CASC_INVALID_SIZE;
      }
  
      // Make sure that the file header area is loaded
      nError = EnsureHeaderAreaIsLoaded(hf);
      if(nError != ERROR_SUCCESS)
      {
          SetLastError(nError);
          return CASC_INVALID_SIZE;
      }
  
      // Give the file size to the caller
      if(pdwFileSizeHigh != NULL)
          *pdwFileSizeHigh = 0;
      return hf->FileSize;
  }
  
  DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod)
  {
      TCascFile * hf;
      ULONGLONG FilePosition;
      ULONGLONG MoveOffset;
      DWORD dwFilePosHi;
  
      // If the hFile is not a valid file handle, return an error.
      hf = IsValidFileHandle(hFile);
      if(hf == NULL)
      {
          SetLastError(ERROR_INVALID_HANDLE);
          return CASC_INVALID_POS;
      }
  
      // Get the relative point where to move from
      switch(dwMoveMethod)
      {
          case FILE_BEGIN:
              FilePosition = 0;
              break;
  
          case FILE_CURRENT:
              FilePosition = hf->FilePointer;
              break;
  
          case FILE_END:
              FilePosition = hf->FileSize;
              break;
  
          default:
              SetLastError(ERROR_INVALID_PARAMETER);
              return CASC_INVALID_POS;
      }
  
      // Now get the move offset. Note that both values form
      // a signed 64-bit value (a file pointer can be moved backwards)
      if(plFilePosHigh != NULL)
          dwFilePosHi = *plFilePosHigh;
      else
          dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0;
      MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos);
  
      // Now calculate the new file pointer
      // Do not allow the file pointer to overflow
      FilePosition = ((FilePosition + MoveOffset) >= FilePosition) ? (FilePosition + MoveOffset) : 0;
  
      // CASC files can't be bigger than 4 GB.
      // We don't allow to go past 4 GB
      if(FilePosition >> 32)
      {
          SetLastError(ERROR_INVALID_PARAMETER);
          return CASC_INVALID_POS;
      }
  
      // Change the file position
      hf->FilePointer = (DWORD)FilePosition;
  
      // Return the new file position
      if(plFilePosHigh != NULL)
          *plFilePosHigh = 0;
      return hf->FilePointer;
  }
  
  bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead)
  {
      PCASC_FILE_FRAME pFrame = NULL;
      ULONGLONG StreamSize;
      ULONGLONG FileOffset;
      TCascFile * hf;
      LPBYTE pbBuffer = (LPBYTE)pvBuffer;
      DWORD dwStartPointer = 0;
      DWORD dwFilePointer = 0;
      DWORD dwEndPointer = 0;
      DWORD dwFrameSize;
      DWORD cbOutBuffer;
      bool bReadResult;
      int nError = ERROR_SUCCESS;
  
      // The buffer must be valid
      if(pvBuffer == NULL)
      {
          SetLastError(ERROR_INVALID_PARAMETER);
          return false;
      }
  
      // Validate the file handle
      if((hf = IsValidFileHandle(hFile)) == NULL)
      {
          SetLastError(ERROR_INVALID_HANDLE);
          return false;
      }
  
      // If the file frames are not loaded yet, do it now
      if(nError == ERROR_SUCCESS)
      {
          nError = EnsureFrameHeadersLoaded(hf);
      }
  
      // If the file position is at or beyond end of file, do nothing
      if(nError == ERROR_SUCCESS && hf->FilePointer >= hf->FileSize)
      {
          *pdwBytesRead = 0;
          return ERROR_SUCCESS;
      }
  
      // Find the file frame where to read from
      if(nError == ERROR_SUCCESS)
      {
          // Get the frame
          pFrame = FindFileFrame(hf, hf->FilePointer);
          if(pFrame == NULL)
              nError = ERROR_FILE_CORRUPT;
      }
  
      // Perform the read
      if(nError == ERROR_SUCCESS)
      {
          // If not enough bytes in the file remaining, cut them
          dwStartPointer = dwFilePointer = hf->FilePointer;
          dwEndPointer = dwStartPointer + dwBytesToRead;
          if(dwEndPointer > hf->FileSize)
              dwEndPointer = hf->FileSize;
  
          // Perform block read from each file frame
          while(dwFilePointer < dwEndPointer)
          {
              LPBYTE pbRawData = NULL;
              DWORD dwFrameStart = pFrame->FrameFileOffset;
              DWORD dwFrameEnd = pFrame->FrameFileOffset + pFrame->FrameSize;
  
              // Shall we populate the cache with a new data?
              if(dwFrameStart != hf->CacheStart || hf->CacheEnd != dwFrameEnd)
              {
                  // Shall we reallocate the cache buffer?
                  if(pFrame->FrameSize > hf->cbFileCache)
                  {
                      if(hf->pbFileCache != NULL)
                          CASC_FREE(hf->pbFileCache);
                      
                      hf->pbFileCache = CASC_ALLOC(BYTE, pFrame->FrameSize);
                      hf->cbFileCache = pFrame->FrameSize;
                  }
  
                  // We also need to allocate buffer for the raw data
                  pbRawData = CASC_ALLOC(BYTE, pFrame->CompressedSize);
                  if(pbRawData == NULL)
                  {
                      nError = ERROR_NOT_ENOUGH_MEMORY;
                      break;
                  }
  
                  // Load the raw file data to memory
                  FileOffset = pFrame->FrameArchiveOffset;
                  bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbRawData, pFrame->CompressedSize);
                  
                  // Note: The raw file data size could be less than expected
                  // Happened in WoW build 19342 with the ROOT file. MD5 in the frame header
                  // is zeroed, which means it should not be checked
                  // Frame File: data.029
                  // Frame Offs: 0x013ED9F0 size 0x01325B32
                  // Frame End:  0x02713522
                  // File Size:  0x027134FC
                  if(bReadResult == false && GetLastError() == ERROR_HANDLE_EOF && !IsValidMD5(pFrame->md5))
                  {
                      // Get the size of the remaining file
                      FileStream_GetSize(hf->pStream, &StreamSize);
                      dwFrameSize = (DWORD)(StreamSize - FileOffset);
  
                      // If the frame offset is before EOF and frame end is beyond EOF, correct it
                      if(FileOffset < StreamSize && dwFrameSize < pFrame->CompressedSize)
                      {
                          memset(pbRawData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize));
                          bReadResult = true;
                      }
                  }
  
                  // If the read result failed, we cannot finish reading it
                  if(bReadResult == false)
                  {
                      CASC_FREE(pbRawData);
                      nError = GetLastError();
                      break;
                  }
  
                  // Verify the block MD5
                  if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
                  {
                      CASC_FREE(pbRawData);
                      nError = ERROR_FILE_CORRUPT;
                      break;
                  }
  
                  // Decompress the file frame
                  cbOutBuffer = pFrame->FrameSize;
                  nError = CascDecompress(hf->pbFileCache, &cbOutBuffer, pbRawData, pFrame->CompressedSize);
                  if(nError != ERROR_SUCCESS || cbOutBuffer != pFrame->FrameSize)
                  {
                      CASC_FREE(pbRawData);
                      nError = ERROR_FILE_CORRUPT;
                      break;
                  }
  
                  // Set the start and end of the cache
                  hf->CacheStart = dwFrameStart;
                  hf->CacheEnd = dwFrameEnd;
  
                  // Free the decompress buffer, if needed
                  CASC_FREE(pbRawData);
              }
  
              // Copy the decompressed data
              if(dwFrameEnd > dwEndPointer)
                  dwFrameEnd = dwEndPointer;
              memcpy(pbBuffer, hf->pbFileCache + (dwFilePointer - dwFrameStart), (dwFrameEnd - dwFilePointer));
              pbBuffer += (dwFrameEnd - dwFilePointer);
  
              // Move pointers
              dwFilePointer = dwFrameEnd;
              pFrame++;
          }
      }
  
      // Update the file position
      if(nError == ERROR_SUCCESS)
      {
          if(pdwBytesRead != NULL)
              *pdwBytesRead = (dwFilePointer - dwStartPointer);
          hf->FilePointer = dwFilePointer;
      }
  
      if(nError != ERROR_SUCCESS)
          SetLastError(nError);
      return (nError == ERROR_SUCCESS);
  }
  
  