text: // IhexHandler.cpp
  
  
  
  #include "../../../C/CpuArch.h"
  
  #include "../../Common/ComTry.h"
  #include "../../Common/DynamicBuffer.h"
  #include "../../Common/IntToString.h"
  #include "../../Common/MyVector.h"
  
  #include "../../Windows/PropVariant.h"
  
  #include "../Common/ProgressUtils.h"
  #include "../Common/RegisterArc.h"
  #include "../Common/StreamUtils.h"
  #include "../Common/InBuffer.h"
  
  namespace NArchive {
  namespace NIhex {
  
  /* We still don't support files with custom record types: 20, 22: used by Samsung */
  
  struct CBlock
  {
    CByteDynamicBuffer Data;
    UInt32 Offset;
  };
  
  class CHandler:
    public IInArchive,
    public CMyUnknownImp
  {
    bool _isArc;
    bool _needMoreInput;
    bool _dataError;
    
    UInt64 _phySize;
  
    CObjectVector<CBlock> _blocks;
  public:
    MY_UNKNOWN_IMP1(IInArchive)
    INTERFACE_IInArchive(;)
  };
  
  static const Byte kProps[] =
  {
    kpidPath,
    kpidSize,
    kpidVa
  };
  
  IMP_IInArchive_Props
  IMP_IInArchive_ArcProps_NO_Table
  
  STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
  {
    *numItems = _blocks.Size();
    return S_OK;
  }
  
  STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
  {
    NWindows::NCOM::CPropVariant prop;
    switch (propID)
    {
      case kpidPhySize: if (_phySize != 0) prop = _phySize; break;
      case kpidErrorFlags:
      {
        UInt32 v = 0;
        if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;;
        if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd;
        if (_dataError) v |= kpv_ErrorFlags_DataError;
        prop = v;
      }
    }
    prop.Detach(value);
    return S_OK;
  }
  
  STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
  {
    COM_TRY_BEGIN
    NWindows::NCOM::CPropVariant prop;
    const CBlock &block = _blocks[index];
    switch (propID)
    {
      case kpidSize: prop = (UInt64)block.Data.GetPos(); break;
      case kpidVa: prop = block.Offset; break;
      case kpidPath:
      {
        if (_blocks.Size() != 1)
        {
          char s[16];
          ConvertUInt32ToString(index, s);
          prop = s;
        }
        break;
      }
    }
    prop.Detach(value);
    return S_OK;
    COM_TRY_END
  }
  
  static inline int HexToByte(unsigned c)
  {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    return -1;
  }
  
  static int Parse(const Byte *p)
  {
    int c1 = HexToByte(p[0]); if (c1 < 0) return -1;
    int c2 = HexToByte(p[1]); if (c2 < 0) return -1;
    return (c1 << 4) | c2;
  }
    
  #define kType_Data 0
  #define kType_Eof  1
  #define kType_Seg  2
  #define kType_CsIp 3
  #define kType_High 4
  #define kType_Ip32 5
  
  #define kType_MAX  5
  
  #define IS_LINE_DELIMITER(c) ((c) == 0 || (c) == 10 || (c) == 13)
  
  API_FUNC_static_IsArc IsArc_Ihex(const Byte *p, size_t size)
  {
    if (size < 1)
      return k_IsArc_Res_NEED_MORE;
    if (p[0] != ':')
      return k_IsArc_Res_NO;
    p++;
    size--;
  
    const unsigned kNumLinesToCheck = 3; // 1 line is OK also, but we don't want false detection
  
    for (unsigned j = 0; j < kNumLinesToCheck; j++)
    {
      if (size < 4 * 2)
        return k_IsArc_Res_NEED_MORE;
      
      int num = Parse(p);
      if (num < 0)
        return k_IsArc_Res_NO;
      
      int type = Parse(p + 6);
      if (type < 0 || type > kType_MAX)
        return k_IsArc_Res_NO;
      
      unsigned numChars = ((unsigned)num + 5) * 2;
      unsigned sum = 0;
      
      for (unsigned i = 0; i < numChars; i += 2)
      {
        if (i + 2 > size)
          return k_IsArc_Res_NEED_MORE;
        int v = Parse(p + i);
        if (v < 0)
          return k_IsArc_Res_NO;
        sum += (unsigned)v;
      }
      
      if ((sum & 0xFF) != 0)
        return k_IsArc_Res_NO;
  
      if (type == kType_Data)
      {
        // we don't want to open :0000000000 files
        if (num == 0)
          return k_IsArc_Res_NO;
      }
      else
      {
        if (type == kType_Eof)
        {
          if (num != 0)
            return k_IsArc_Res_NO;
          return k_IsArc_Res_YES;
        }
        if (p[2] != 0 ||
            p[3] != 0 ||
            p[4] != 0 ||
            p[5] != 0)
          return k_IsArc_Res_NO;
        if (type == kType_Seg || type == kType_High)
        {
          if (num != 2)
            return k_IsArc_Res_NO;
        }
        else
        {
          if (num != 4)
            return k_IsArc_Res_NO;
        }
      }
      
      p += numChars;
      size -= numChars;
      
      for (;;)
      {
        if (size == 0)
          return k_IsArc_Res_NEED_MORE;
        char b = *p++;
        size--;
        if (IS_LINE_DELIMITER(b))
          continue;
        if (b == ':')
          break;
        return k_IsArc_Res_NO;
      }
    }
    
    return k_IsArc_Res_YES;
  }
  }
  
  STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *)
  {
    COM_TRY_BEGIN
    {
    Close();
    try
    {
      const unsigned kStartSize = (2 + (256 + 5) + 2) * 2;
      Byte temp[kStartSize];
      {
        size_t size = kStartSize;
        RINOK(ReadStream(stream, temp, &size));
        UInt32 isArcRes = IsArc_Ihex(temp, size);
        if (isArcRes == k_IsArc_Res_NO)
          return S_FALSE;
        if (isArcRes == k_IsArc_Res_NEED_MORE && size != kStartSize)
          return S_FALSE;
      }
      _isArc = true;
  
      RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
      CInBuffer s;
      if (!s.Create(1 << 15))
        return E_OUTOFMEMORY;
      s.SetStream(stream);
      s.Init();
  
      {
        Byte b;
        if (!s.ReadByte(b))
        {
          _needMoreInput = true;
          return S_FALSE;
        }
        if (b != ':')
        {
          _dataError = true;
          return S_FALSE;
        }
      }
  
      UInt32 globalOffset = 0;
  
      for (;;)
      {
        if (s.ReadBytes(temp, 2) != 2)
        {
          _needMoreInput = true;
          return S_FALSE;
        }
        int num = Parse(temp);
        if (num < 0)
        {
          _dataError = true;
          return S_FALSE;
        }
        
        {
          size_t numPairs = ((unsigned)num + 4);
          size_t numBytes = numPairs * 2;
          if (s.ReadBytes(temp, numBytes) != numBytes)
          {
            _needMoreInput = true;
            return S_FALSE;
          }
          
          unsigned sum = num;
          for (size_t i = 0; i < numPairs; i++)
          {
            int a = Parse(temp + i * 2);
            if (a < 0)
            {
              _dataError = true;
              return S_FALSE;
            }
            temp[i] = (Byte)a;
            sum += a;
          }
          if ((sum & 0xFF) != 0)
          {
            _dataError = true;
            return S_FALSE;
          }
        }
  
        unsigned type = temp[2];
        if (type > kType_MAX)
        {
          _dataError = true;
          return S_FALSE;
        }
  
        UInt32 a = GetBe16(temp);
  
        if (type == kType_Data)
        {
          if (num == 0)
          {
            // we don't want to open :0000000000 files
            // maybe it can mean EOF in old-style files?
            _dataError = true;
            return S_FALSE;
          }
          // if (num != 0)
          {
            UInt32 offs = globalOffset + a;
            CBlock *block = NULL;
            if (!_blocks.IsEmpty())
            {
              block = &_blocks.Back();
              if (block->Offset + block->Data.GetPos() != offs)
                block = NULL;
            }
            if (!block)
            {
              block = &_blocks.AddNew();
              block->Offset = offs;
            }
            block->Data.AddData(temp + 3, (unsigned)num);
          }
        }
        else if (type == kType_Eof)
        {
          _phySize = s.GetProcessedSize();
          {
            Byte b;
            if (s.ReadByte(b))
            {
              if (b == 10)
                _phySize++;
              else if (b == 13)
              {
                _phySize++;
                if (s.ReadByte(b))
                {
                  if (b == 10)
                    _phySize++;
                }
              }
            }
          }
          return S_OK;
        }
        else
        {
          if (a != 0)
          {
            _dataError = true;
            return S_FALSE;
          }
          if (type == kType_Seg || type == kType_High)
          {
            if (num != 2)
            {
              _dataError = true;
              return S_FALSE;
            }
            UInt32 d = GetBe16(temp + 3);
            globalOffset = d << (type == kType_Seg ? 4 : 16);
          }
          else
          {
            if (num != 4)
            {
              _dataError = true;
              return S_FALSE;
            }
          }
        }
  
        for (;;)
        {
          Byte b;
          if (!s.ReadByte(b))
          {
            _needMoreInput = true;
            return S_FALSE;
          }
          if (IS_LINE_DELIMITER(b))
            continue;
          if (b == ':')
            break;
          _dataError = true;
          return S_FALSE;
        }
      }
    }
    catch(const CInBufferException &e) { return e.ErrorCode; }
    }
    COM_TRY_END
  }
  
  STDMETHODIMP CHandler::Close()
  {
    _phySize = 0;
    
    _isArc = false;
    _needMoreInput = false;
    _dataError = false;
  
    _blocks.Clear();
    return S_OK;
  }
  
  
  STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
      Int32 testMode, IArchiveExtractCallback *extractCallback)
  {
    COM_TRY_BEGIN
    bool allFilesMode = (numItems == (UInt32)(Int32)-1);
    if (allFilesMode)
      numItems = _blocks.Size();
    if (numItems == 0)
      return S_OK;
  
    UInt64 totalSize = 0;
    UInt32 i;
    for (i = 0; i < numItems; i++)
      totalSize += _blocks[allFilesMode ? i : indices[i]].Data.GetPos();
    extractCallback->SetTotal(totalSize);
  
    UInt64 currentTotalSize = 0;
    UInt64 currentItemSize;
  
    CLocalProgress *lps = new CLocalProgress;
    CMyComPtr<ICompressProgressInfo> progress = lps;
    lps->Init(extractCallback, false);
  
    for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize)
    {
      currentItemSize = 0;
      lps->InSize = lps->OutSize = currentTotalSize;
      RINOK(lps->SetCur());
  
      UInt32 index = allFilesMode ? i : indices[i];
      const CByteDynamicBuffer &data = _blocks[index].Data;
      currentItemSize = data.GetPos();
      
      CMyComPtr<ISequentialOutStream> realOutStream;
      Int32 askMode = testMode ?
          NExtract::NAskMode::kTest :
          NExtract::NAskMode::kExtract;
      
      RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
      
      if (!testMode && !realOutStream)
        continue;
      
      extractCallback->PrepareOperation(askMode);
      
      if (realOutStream)
      {
        RINOK(WriteStream(realOutStream, (const Byte *)data, data.GetPos()));
      }
    
      realOutStream.Release();
      RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
    }
    
    lps->InSize = lps->OutSize = currentTotalSize;
    return lps->SetCur();
  
    COM_TRY_END
  }
  
  // k_Signature: { ':', '1' }
  
  REGISTER_ARC_I_NO_SIG(
    "IHex", "ihex", 0, 0xCD,
    0,
    NArcInfoFlags::kStartOpen,
    IsArc_Ihex)
  
  }}
  