// ElfHandler.cpp

#include "StdAfx.h"

#include "../../../C/CpuArch.h"

#include "Common/Buffer.h"
#include "Common/ComTry.h"
#include "Common/IntToString.h"

#include "Windows/PropVariantUtils.h"

#include "../Common/LimitedStreams.h"
#include "../Common/ProgressUtils.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamUtils.h"

#include "../Compress/CopyCoder.h"

static UInt16 Get16(const Byte *p, int be) { if (be) return GetBe16(p); return GetUi16(p); }
static UInt32 Get32(const Byte *p, int be) { if (be) return GetBe32(p); return GetUi32(p); }
static UInt64 Get64(const Byte *p, int be) { if (be) return GetBe64(p); return GetUi64(p); }

using namespace NWindows;

namespace NArchive {
namespace NElf {

#define ELF_CLASS_32 1
#define ELF_CLASS_64 2

#define ELF_DATA_2LSB 1
#define ELF_DATA_2MSB 2

#define NUM_SCAN_SECTIONS_MAX (1 << 6)

struct CHeader
{
  bool Mode64;
  bool Be;
  Byte Os;
  Byte AbiVer;

  UInt16 Type;
  UInt16 Machine;
  // UInt32 Version;

  // UInt64 EntryVa;
  UInt64 ProgOffset;
  UInt64 SectOffset;
  UInt32 Flags;
  UInt16 ElfHeaderSize;
  UInt16 SegmentEntrySize;
  UInt16 NumSegments;
  UInt16 SectEntrySize;
  UInt16 NumSections;
  // UInt16 SectNameStringTableIndex;

  bool Parse(const Byte *buf);

  bool CheckSegmentEntrySize() const
  {
    return (Mode64 && SegmentEntrySize == 0x38) || (!Mode64 && SegmentEntrySize == 0x20);
  };

  UInt64 GetHeadersSize() const
    { return ElfHeaderSize +
      (UInt64)SegmentEntrySize * NumSegments +
      (UInt64)SectEntrySize * NumSections; }
    
};

bool CHeader::Parse(const Byte *p)
{
  switch(p[4])
  {
    case ELF_CLASS_32: Mode64 = false; break;
    case ELF_CLASS_64: Mode64 = true; break;
    default: return false;
  }
  bool be;
  switch(p[5])
  {
    case ELF_DATA_2LSB: be = false; break;
    case ELF_DATA_2MSB: be = true; break;
    default: return false;
  }
  Be = be;
  if (p[6] != 1) // Version
    return false;
  Os = p[7];
  AbiVer = p[8];
  for (int i = 9; i < 16; i++)
    if (p[i] != 0)
      return false;

  Type = Get16(p + 0x10, be);
  Machine = Get16(p + 0x12, be);
  if (Get32(p + 0x14, be) != 1) // Version
    return false;

  if (Mode64)
  {
    // EntryVa = Get64(p + 0x18, be);
    ProgOffset = Get64(p + 0x20, be);
    SectOffset = Get64(p + 0x28, be);
    p += 0x30;
  }
  else
  {
    // EntryVa = Get32(p + 0x18, be);
    ProgOffset = Get32(p + 0x1C, be);
    SectOffset = Get32(p + 0x20, be);
    p += 0x24;
  }

  Flags = Get32(p + 0, be);
  ElfHeaderSize = Get16(p + 4, be);
  SegmentEntrySize = Get16(p + 6, be);
  NumSegments = Get16(p + 8, be);
  SectEntrySize = Get16(p + 10, be);
  NumSections = Get16(p + 12, be);
  // SectNameStringTableIndex = Get16(p + 14, be);
  return CheckSegmentEntrySize();
}

struct CSegment
{
  UInt32 Type;
  UInt32 Flags;
  UInt64 Offset;
  UInt64 Va;
  // UInt64 Pa;
  UInt64 PSize;
  UInt64 VSize;
  // UInt64 Align;

  void UpdateTotalSize(UInt64 &totalSize)
  {
    UInt64 t = Offset + PSize;
    if (t > totalSize)
      totalSize = t;
  }
  void Parse(const Byte *p, bool mode64, bool be);
};

void CSegment::Parse(const Byte *p, bool mode64, bool be)
{
  Type = Get32(p, be);
  if (mode64)
  {
    Flags = Get32(p + 4, be);
    Offset = Get64(p + 8, be);
    Va = Get64(p + 0x10, be);
    // Pa = Get64(p + 0x18, be);
    PSize = Get64(p + 0x20, be);
    VSize = Get64(p + 0x28, be);
    // Align = Get64(p + 0x30, be);
  }
  else
  {
    Offset = Get32(p + 4, be);
    Va = Get32(p + 8, be);
    // Pa = Get32(p + 12, be);
    PSize = Get32(p + 16, be);
    VSize = Get32(p + 20, be);
    Flags = Get32(p + 24, be);
    // Align = Get32(p + 28, be);
  }
}

static const CUInt32PCharPair g_MachinePairs[] =
{
  { 0, "None" },
  { 1, "AT&T WE 32100" },
  { 2, "SPARC" },
  { 3, "Intel 386" },
  { 4, "Motorola 68000" },
  { 5, "Motorola 88000" },
  { 6, "Intel 486" },
  { 7, "Intel i860" },
  { 8, "MIPS" },
  { 9, "IBM S/370" },
  { 10, "MIPS RS3000 LE" },
  { 11, "RS6000" },

  { 15, "PA-RISC" },
  { 16, "nCUBE" },
  { 17, "Fujitsu VPP500" },
  { 18, "SPARC 32+" },
  { 19, "Intel i960" },
  { 20, "PowerPC" },
  { 21, "PowerPC 64-bit" },
  { 22, "IBM S/390" },

  { 36, "NEX v800" },
  { 37, "Fujitsu FR20" },
  { 38, "TRW RH-32" },
  { 39, "Motorola RCE" },
  { 40, "ARM" },
  { 41, "Alpha" },
  { 42, "Hitachi SH" },
  { 43, "SPARC-V9" },
  { 44, "Siemens Tricore" },
  { 45, "ARC" },
  { 46, "H8/300" },
  { 47, "H8/300H" },
  { 48, "H8S" },
  { 49, "H8/500" },
  { 50, "IA-64" },
  { 51, "Stanford MIPS-X" },
  { 52, "Motorola ColdFire" },
  { 53, "M68HC12" },
  { 54, "Fujitsu MMA" },
  { 55, "Siemens PCP" },
  { 56, "Sony nCPU" },
  { 57, "Denso NDR1" },
  { 58, "Motorola StarCore" },
  { 59, "Toyota ME16" },
  { 60, "ST100" },
  { 61, "Advanced Logic TinyJ" },
  { 62, "AMD64" },
  { 63, "Sony DSP" },

  { 66, "Siemens FX66" },
  { 67, "ST9+" },
  { 68, "ST7" },
  { 69, "MC68HC16" },
  { 70, "MC68HC11" },
  { 71, "MC68HC08" },
  { 72, "MC68HC05" },
  { 73, "Silicon Graphics SVx" },
  { 74, "ST19" },
  { 75, "Digital VAX" },
  { 76, "Axis CRIS" },
  { 77, "Infineon JAVELIN" },
  { 78, "Element 14 FirePath" },
  { 79, "LSI ZSP" },
  { 80, "MMIX" },
  { 81, "HUANY" },
  { 82, "SiTera Prism" },
  { 83, "Atmel AVR" },
  { 84, "Fujitsu FR30" },
  { 85, "Mitsubishi D10V" },
  { 86, "Mitsubishi D30V" },
  { 87, "NEC v850" },
  { 88, "Mitsubishi M32R" },
  { 89, "Matsushita MN10300" },
  { 90, "Matsushita MN10200" },
  { 91, "picoJava" },
  { 92, "OpenRISC" },
  { 93, "ARC Tangent-A5" },
  { 94, "Tensilica Xtensa" },
  { 0x9026, "Alpha" }
};

static const CUInt32PCharPair g_AbiOS[] =
{
  { 0, "None" },
  { 1, "HP-UX" },
  { 2, "NetBSD" },
  { 3, "Linux" },

  { 6, "Solaris" },
  { 7, "AIX" },
  { 8, "IRIX" },
  { 9, "FreeBSD" },
  { 10, "TRU64" },
  { 11, "Novell Modesto" },
  { 12, "OpenBSD" },
  { 13, "OpenVMS" },
  { 14, "HP NSK" },
  { 15, "AROS" },
  { 97, "ARM" },
  { 255, "Standalone" }
};

static const CUInt32PCharPair g_SegmentFlags[] =
{
  { 0, "Execute" },
  { 1, "Write" },
  { 2, "Read" }
};

static const char *g_Types[] =
{
  "None",
  "Relocatable file",
  "Executable file",
  "Shared object file",
  "Core file"
};

static const char *g_SegnmentTypes[] =
{
  "Unused",
  "Loadable segment",
  "Dynamic linking tables",
  "Program interpreter path name",
  "Note section",
  "SHLIB",
  "Program header table",
  "TLS"
};

class CHandler:
  public IInArchive,
  public CMyUnknownImp
{
  CMyComPtr<IInStream> _inStream;
  CObjectVector<CSegment> _sections;
  UInt32 _peOffset;
  CHeader _header;
  UInt64 _totalSize;
  HRESULT Open2(IInStream *stream);
  bool Parse(const Byte *buf, UInt32 size);
public:
  MY_UNKNOWN_IMP1(IInArchive)
  INTERFACE_IInArchive(;)
};

#define ELF_PT_PHDR 6

bool CHandler::Parse(const Byte *buf, UInt32 size)
{
  if (size < 64)
    return false;
  if (!_header.Parse(buf))
    return false;
  if (_header.ProgOffset > size ||
      _header.ProgOffset + (UInt64)_header.SegmentEntrySize * _header.NumSegments > size ||
      _header.NumSegments > NUM_SCAN_SECTIONS_MAX)
    return false;
  const Byte *p = buf + _header.ProgOffset;
  _totalSize = _header.ProgOffset;
  
  for (int i = 0; i < _header.NumSegments; i++, p += _header.SegmentEntrySize)
  {
    CSegment sect;
    sect.Parse(p, _header.Mode64, _header.Be);
    sect.UpdateTotalSize(_totalSize);
    if (sect.Type != ELF_PT_PHDR)
      _sections.Add(sect);
  }
  UInt64 total2 = _header.SectOffset + (UInt64)_header.SectEntrySize * _header.NumSections;
  if (total2 > _totalSize)
    _totalSize = total2;
  return true;
}

STATPROPSTG kArcProps[] =
{
  { NULL, kpidCpu, VT_BSTR},
  { NULL, kpidBit64, VT_BOOL},
  { NULL, kpidBigEndian, VT_BOOL},
  { NULL, kpidHostOS, VT_BSTR},
  { NULL, kpidCharacts, VT_BSTR},
  { NULL, kpidPhySize, VT_UI8},
  { NULL, kpidHeadersSize, VT_UI8}
 };

STATPROPSTG kProps[] =
{
  { NULL, kpidPath, VT_BSTR},
  { NULL, kpidSize, VT_UI8},
  { NULL, kpidPackSize, VT_UI8},
  { NULL, kpidType, VT_BSTR},
  { NULL, kpidCharacts, VT_BSTR},
  { NULL, kpidOffset, VT_UI8},
  { NULL, kpidVa, VT_UI8}
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps

STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  switch(propID)
  {
    case kpidPhySize:  prop = _totalSize; break;
    case kpidHeadersSize:  prop = _header.GetHeadersSize(); break;
    case kpidBit64:  if (_header.Mode64) prop = _header.Mode64; break;
    case kpidBigEndian:  if (_header.Be) prop = _header.Be; break;
    case kpidCpu:  PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); break;
    case kpidHostOS:  PAIR_TO_PROP(g_AbiOS, _header.Os, prop); break;
    case kpidCharacts:  TYPE_TO_PROP(g_Types, _header.Type, prop); break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  const CSegment &item = _sections[index];
  switch(propID)
  {
    case kpidPath:
    {
      wchar_t sz[32];
      ConvertUInt64ToString(index, sz);
      prop = sz;
      break;
    }
    case kpidSize:  prop = (UInt64)item.VSize; break;
    case kpidPackSize:  prop = (UInt64)item.PSize; break;
    case kpidOffset:  prop = item.Offset; break;
    case kpidVa:  prop = item.Va; break;
    case kpidType:  TYPE_TO_PROP(g_SegnmentTypes, item.Type, prop); break;
    case kpidCharacts:  FLAGS_TO_PROP(g_SegmentFlags, item.Flags, prop); break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

HRESULT CHandler::Open2(IInStream *stream)
{
  const UInt32 kBufSize = 1 << 18;
  const UInt32 kSigSize = 4;

  CByteBuffer buffer;
  buffer.SetCapacity(kBufSize);
  Byte *buf = buffer;

  size_t processed = kSigSize;
  RINOK(ReadStream_FALSE(stream, buf, processed));
  if (buf[0] != 0x7F || buf[1] != 'E' || buf[2] != 'L' || buf[3] != 'F')
    return S_FALSE;
  processed = kBufSize - kSigSize;
  RINOK(ReadStream(stream, buf + kSigSize, &processed));
  processed += kSigSize;
  if (!Parse(buf, (UInt32)processed))
    return S_FALSE;
  UInt64 fileSize;
  RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize));
  return (fileSize == _totalSize) ? S_OK : S_FALSE;
}

STDMETHODIMP CHandler::Open(IInStream *inStream,
    const UInt64 * /* maxCheckStartPosition */,
    IArchiveOpenCallback * /* openArchiveCallback */)
{
  COM_TRY_BEGIN
  Close();
  RINOK(Open2(inStream));
  _inStream = inStream;
  return S_OK;
  COM_TRY_END
}

STDMETHODIMP CHandler::Close()
{
  _inStream.Release();
  _sections.Clear();
  return S_OK;
}

STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
  *numItems = _sections.Size();
  return S_OK;
}

STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
    Int32 testMode, IArchiveExtractCallback *extractCallback)
{
  COM_TRY_BEGIN
  bool allFilesMode = (numItems == (UInt32)-1);
  if (allFilesMode)
    numItems = _sections.Size();
  if (numItems == 0)
    return S_OK;
  UInt64 totalSize = 0;
  UInt32 i;
  for (i = 0; i < numItems; i++)
    totalSize += _sections[allFilesMode ? i : indices[i]].PSize;
  extractCallback->SetTotal(totalSize);

  UInt64 currentTotalSize = 0;
  UInt64 currentItemSize;
  
  NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
  CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;

  CLocalProgress *lps = new CLocalProgress;
  CMyComPtr<ICompressProgressInfo> progress = lps;
  lps->Init(extractCallback, false);

  CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
  CMyComPtr<ISequentialInStream> inStream(streamSpec);
  streamSpec->SetStream(_inStream);

  for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize)
  {
    lps->InSize = lps->OutSize = currentTotalSize;
    RINOK(lps->SetCur());
    Int32 askMode = testMode ?
        NExtract::NAskMode::kTest :
        NExtract::NAskMode::kExtract;
    UInt32 index = allFilesMode ? i : indices[i];
    const CSegment &item = _sections[index];
    currentItemSize = item.PSize;
    
    CMyComPtr<ISequentialOutStream> outStream;
    RINOK(extractCallback->GetStream(index, &outStream, askMode));
    if (!testMode && !outStream)
      continue;
      
    RINOK(extractCallback->PrepareOperation(askMode));
    RINOK(_inStream->Seek(item.Offset, STREAM_SEEK_SET, NULL));
    streamSpec->Init(currentItemSize);
    RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress));
    outStream.Release();
    RINOK(extractCallback->SetOperationResult(copyCoderSpec->TotalSize == currentItemSize ?
        NExtract::NOperationResult::kOK:
        NExtract::NOperationResult::kDataError));
  }
  return S_OK;
  COM_TRY_END
}

static IInArchive *CreateArc() { return new CHandler; }

static CArcInfo g_ArcInfo =
  { L"ELF", L"", 0, 0xDE, { 0 }, 0, false, CreateArc, 0 };

REGISTER_ARC(Elf)

}}
