// PeHandler.cpp

#include "StdAfx.h"

// #include <stdio.h>

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

#include "../../Common/DynamicBuffer.h"
#include "../../Common/ComTry.h"
#include "../../Common/IntToString.h"
#include "../../Common/StringConvert.h"

#include "../../Windows/PropVariantUtils.h"
#include "../../Windows/TimeUtils.h"

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

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

#define Get16(p) GetUi16(p)
#define Get32(p) GetUi32(p)
#define Get64(p) GetUi64(p)

#define G16(offs, v) v = Get16(p + (offs))
#define G32(offs, v) v = Get32(p + (offs))
#define G32_signed(offs, v) v = (Int32)Get32(p + (offs))
#define G64(offs, v) v = Get64(p + (offs))

#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; }

using namespace NWindows;

namespace NArchive {
namespace NPe {

static const UInt32 k_Signature32 = 0x00004550;

static HRESULT CalcCheckSum(ISequentialInStream *stream, UInt32 size, UInt32 excludePos, UInt32 &res)
{
  const UInt32 kBufSizeMax = (UInt32)1 << 15;
  UInt32 bufSize = kBufSizeMax;
  CByteBuffer buffer(bufSize);
  Byte *buf = buffer;
  UInt32 sum = 0;
  UInt32 pos = 0;
  for (;;)
  {
    UInt32 rem = size - pos;
    if (rem > bufSize)
      rem = bufSize;
    if (rem == 0)
      break;
    size_t processed = rem;
    RINOK(ReadStream(stream, buf, &processed))
    
    for (unsigned j = 0; j < 4; j++)
    {
      UInt32 e = excludePos + j;
      if (pos <= e)
      {
        e -= pos;
        if (e < processed)
          buf[e] = 0;
      }
    }

    const unsigned kStep = (1 << 4);
    {
      for (size_t i = processed; (i & (kStep - 1)) != 0; i++)
        buf[i] = 0;
    }
    {
      const Byte *buf2 = buf;
      const Byte *bufLimit = buf + processed;
      UInt64 sum2 = 0;
      for (; buf2 < bufLimit; buf2 += kStep)
      {
        UInt64 sum3 = (UInt64)Get32(buf2)
            + Get32(buf2 + 4)
            + Get32(buf2 + 8)
            + Get32(buf2 + 12);
        sum2 += sum3;
      }
      sum2 = (UInt32)(sum2) + (UInt64)(sum2 >> 32);
      UInt32 sum3 = ((UInt32)sum2 + (UInt32)(sum2 >> 32));
      sum += (sum3 & 0xFFFF) + (sum3 >> 16);
      sum = (sum & 0xFFFF) + (sum >> 16);
      sum = (sum & 0xFFFF) + (sum >> 16);
    }

    pos += (UInt32)processed;
    if (rem != processed)
      break;
  }
  res = sum + pos;
  return S_OK;
}


struct CVersion
{
  UInt16 Major;
  UInt16 Minor;

  void Parse(const Byte *p)
  {
    G16(0, Major);
    G16(2, Minor);
  }
  void ToProp(NCOM::CPropVariant &prop);
};

void CVersion::ToProp(NCOM::CPropVariant &prop)
{
  char sz[32];
  ConvertUInt32ToString(Major, sz);
  unsigned len = MyStringLen(sz);
  sz[len] = '.';
  ConvertUInt32ToString(Minor, sz + len + 1);
  prop = sz;
}

static const unsigned kCoffHeaderSize = 20;
static const unsigned kPeHeaderSize = 4 + kCoffHeaderSize;
static const unsigned k_OptHeader32_Size_MIN = 96;
static const unsigned k_OptHeader64_Size_MIN = 112;

static const UInt32 PE_IMAGE_FILE_DLL  = (1 << 13);

struct CHeader
{
  UInt16 Machine;
  UInt16 NumSections;
  UInt32 Time;
  UInt32 PointerToSymbolTable;
  UInt32 NumSymbols;
  UInt16 OptHeaderSize;
  UInt16 Flags;

  void ParseBase(const Byte *p);
  bool ParseCoff(const Byte *p);
  bool ParsePe(const Byte *p);
  bool IsDll() const { return (Flags & PE_IMAGE_FILE_DLL) != 0; }
};

void CHeader::ParseBase(const Byte *p)
{
  G16( 0, Machine);
  G16( 2, NumSections);
  G32( 4, Time);
  G32( 8, PointerToSymbolTable);
  G32(12, NumSymbols);
  G16(16, OptHeaderSize);
  G16(18, Flags);
}

bool CHeader::ParsePe(const Byte *p)
{
  if (Get32(p) != k_Signature32)
    return false;
  ParseBase(p + 4);
  return OptHeaderSize >= k_OptHeader32_Size_MIN;
}

struct CDirLink
{
  UInt32 Va;
  UInt32 Size;
  
  CDirLink(): Va(0), Size(0) {}
  void Parse(const Byte *p)
  {
    G32(0, Va);
    G32(4, Size);
  }
};

enum
{
  kDirLink_Certificate = 4,
  kDirLink_Debug = 6
};

static const UInt32 kNumDirItemsMax = 16;

struct CDebugEntry
{
  UInt32 Flags;
  UInt32 Time;
  CVersion Ver;
  UInt32 Type;
  UInt32 Size;
  UInt32 Va;
  UInt32 Pa;
  
  void Parse(const Byte *p)
  {
    G32(0, Flags);
    G32(4, Time);
    Ver.Parse(p + 8);
    G32(12, Type);
    G32(16, Size);
    G32(20, Va);
    G32(24, Pa);
  }
};

static const UInt32 k_CheckSum_Field_Offset = 64;

static const UInt32 PE_OptHeader_Magic_32 = 0x10B;
static const UInt32 PE_OptHeader_Magic_64 = 0x20B;

static const UInt32 k_SubSystems_EFI_First = 10;
static const UInt32 k_SubSystems_EFI_Last = 13;

struct COptHeader
{
  UInt16 Magic;
  Byte LinkerVerMajor;
  Byte LinkerVerMinor;

  UInt32 CodeSize;
  UInt32 InitDataSize;
  UInt32 UninitDataSize;
  
  // UInt32 AddressOfEntryPoint;
  // UInt32 BaseOfCode;
  // UInt32 BaseOfData32;
  UInt64 ImageBase;

  UInt32 SectAlign;
  UInt32 FileAlign;

  CVersion OsVer;
  CVersion ImageVer;
  CVersion SubsysVer;
  
  UInt32 ImageSize;
  UInt32 HeadersSize;
  UInt32 CheckSum;
  UInt16 SubSystem;
  UInt16 DllCharacts;

  UInt64 StackReserve;
  UInt64 StackCommit;
  UInt64 HeapReserve;
  UInt64 HeapCommit;

  UInt32 NumDirItems;
  CDirLink DirItems[kNumDirItemsMax];

  bool Is64Bit() const { return Magic == PE_OptHeader_Magic_64; }
  bool Parse(const Byte *p, UInt32 size);

  int GetNumFileAlignBits() const
  {
    for (unsigned i = 0; i < 32; i++)
      if (((UInt32)1 << i) == FileAlign)
        return (int)i;
    return -1;
  }

  bool IsSybSystem_EFI() const
  {
    return
        SubSystem >= k_SubSystems_EFI_First &&
        SubSystem <= k_SubSystems_EFI_Last;
  }
};

bool COptHeader::Parse(const Byte *p, UInt32 size)
{
  if (size < k_OptHeader32_Size_MIN)
    return false;
  Magic = Get16(p);
  switch (Magic)
  {
    case PE_OptHeader_Magic_32:
    case PE_OptHeader_Magic_64:
      break;
    default:
      return false;
  }
  LinkerVerMajor = p[2];
  LinkerVerMinor = p[3];
  
  G32( 4, CodeSize);
  G32( 8, InitDataSize);
  G32(12, UninitDataSize);
  // G32(16, AddressOfEntryPoint);
  // G32(20, BaseOfCode);
  
  G32(32, SectAlign);
  G32(36, FileAlign);

  OsVer.Parse(p + 40);
  ImageVer.Parse(p + 44);
  SubsysVer.Parse(p + 48);

  // reserved = Get32(p + 52);

  G32(56, ImageSize);
  G32(60, HeadersSize);
  G32(64, CheckSum);
  G16(68, SubSystem);
  G16(70, DllCharacts);

  UInt32 pos;
  if (Is64Bit())
  {
    if (size < k_OptHeader64_Size_MIN)
      return false;
    // BaseOfData32 = 0;
    G64(24, ImageBase);
    G64(72, StackReserve);
    G64(80, StackCommit);
    G64(88, HeapReserve);
    G64(96, HeapCommit);
    pos = 108;
  }
  else
  {
    // G32(24, BaseOfData32);
    G32(28, ImageBase);
    G32(72, StackReserve);
    G32(76, StackCommit);
    G32(80, HeapReserve);
    G32(84, HeapCommit);
    pos = 92;
  }

  G32(pos, NumDirItems);
  if (NumDirItems > (1 << 16))
    return false;
  pos += 4;
  if (pos + 8 * NumDirItems > size)
    return false;
  memset((void *)DirItems, 0, sizeof(DirItems));
  for (UInt32 i = 0; i < NumDirItems && i < kNumDirItemsMax; i++)
    DirItems[i].Parse(p + pos + i * 8);
  return true;
}

static const UInt32 kSectionSize = 40;

struct CSection
{
  AString Name;

  UInt32 VSize;
  UInt32 Va;
  UInt32 PSize;
  UInt32 Pa;
  UInt32 Flags;
  UInt32 Time;
  // UInt16 NumRelocs;
  bool IsRealSect;
  bool IsDebug;
  bool IsAdditionalSection;

  CSection(): IsRealSect(false), IsDebug(false), IsAdditionalSection(false) {}

  UInt32 GetSizeExtract() const { return PSize; }
  UInt32 GetSizeMin() const { return MyMin(PSize, VSize); }

  void UpdateTotalSize(UInt32 &totalSize) const
  {
    UInt32 t = Pa + PSize;
    if (totalSize < t)
      totalSize = t;
  }
  
  void Parse(const Byte *p);

  int Compare(const CSection &s) const
  {
    RINOZ(MyCompare(Pa, s.Pa))
    UInt32 size1 = GetSizeExtract();
    UInt32 size2 = s.GetSizeExtract();
    return MyCompare(size1, size2);
  }
};

static const unsigned kNameSize = 8;

static void GetName(const Byte *name, AString &res)
{
  res.SetFrom_CalcLen((const char *)name, kNameSize);
}

void CSection::Parse(const Byte *p)
{
  GetName(p, Name);
  G32( 8, VSize);
  G32(12, Va);
  G32(16, PSize);
  G32(20, Pa);
  // G16(32, NumRelocs);
  G32(36, Flags);
}



// IMAGE_FILE_*

static const CUInt32PCharPair g_HeaderCharacts[] =
{
  {  1, "Executable" },
  { 13, "DLL" },
  {  8, "32-bit" },
  {  5, "LargeAddress" },
  {  0, "NoRelocs" },
  {  2, "NoLineNums" },
  {  3, "NoLocalSyms" },
  {  4, "AggressiveWsTrim" },
  {  9, "NoDebugInfo" },
  { 10, "RemovableRun" },
  { 11, "NetRun" },
  { 12, "System" },
  { 14, "UniCPU" },
  {  7, "Little-Endian" },
  { 15, "Big-Endian" }
};

// IMAGE_DLLCHARACTERISTICS_*

static const char * const g_DllCharacts[] =
{
    NULL
  , NULL
  , NULL
  , NULL
  , NULL
  , "HighEntropyVA"
  , "Relocated"
  , "Integrity"
  , "NX-Compatible"
  , "NoIsolation"
  , "NoSEH"
  , "NoBind"
  , "AppContainer"
  , "WDM"
  , "GuardCF"
  , "TerminalServerAware"
};


// IMAGE_SCN_* constants:

static const char * const g_SectFlags[] =
{
    NULL
  , NULL
  , NULL
  , "NoPad"
  , NULL
  , "Code"
  , "InitializedData"
  , "UninitializedData"
  , "Other"
  , "Comments"
  , NULL // OVER
  , "Remove"
  , "COMDAT"
  , NULL
  , "NO_DEFER_SPEC_EXC"
  , "GP" // MEM_FARDATA
  , NULL // SYSHEAP
  , "PURGEABLE" // 16BIT
  , "LOCKED"
  , "PRELOAD"
  , NULL
  , NULL
  , NULL
  , NULL
  , "ExtendedRelocations"
  , "Discardable"
  , "NotCached"
  , "NotPaged"
  , "Shared"
  , "Execute"
  , "Read"
  , "Write"
};

static const CUInt32PCharPair g_MachinePairs[] =
{
  { 0x014C, "x86" },
  { 0x014D, "I860" },
  { 0x0162, "MIPS-R3000" },
  { 0x0166, "MIPS-R4000" },
  { 0x0168, "MIPS-R10000" },
  { 0x0169, "MIPS-V2" },
  { 0x0184, "Alpha" },
  { 0x01A2, "SH3" },
  { 0x01A3, "SH3-DSP" },
  { 0x01A4, "SH3E" },
  { 0x01A6, "SH4" },
  { 0x01A8, "SH5" },
  { 0x01C0, "ARM" },
  { 0x01C2, "ARM-Thumb" },
  { 0x01C4, "ARM-NT" },
  { 0x01D3, "AM33" },
  { 0x01F0, "PPC" },
  { 0x01F1, "PPC-FP" },
  { 0x0200, "IA-64" },
  { 0x0266, "MIPS-16" },
  { 0x0284, "Alpha-64" },
  { 0x0366, "MIPS-FPU" },
  { 0x0466, "MIPS-FPU16" },
  { 0x0520, "TriCore" },
  { 0x0CEF, "CEF" },
  { 0x0EBC, "EFI" },
  { 0x5032, "RISCV32" },
  { 0x5064, "RISCV64" },
//  { 0x5128, "RISCV128" },
  { 0x6232, "LOONGARCH32" },
  { 0x6264, "LOONGARCH64" },
  { 0x8664, "x64" },
  { 0x9041, "M32R" },
  { 0xA641, "ARM64EC" },
  { 0xA64e, "ARM64X" },
  { 0xAA64, "ARM64" },
  { 0xC0EE, "CEE" }
};

static const char * const g_SubSystems[] =
{
    "Unknown"
  , "Native"
  , "Windows GUI"
  , "Windows CUI"
  , NULL // "Old Windows CE"
  , "OS2"
  , NULL
  , "Posix"
  , "Win9x"
  , "Windows CE"
  , "EFI"
  , "EFI Boot"
  , "EFI Runtime"
  , "EFI ROM"
  , "XBOX"
  , NULL
  , "Windows Boot"
  , "XBOX Catalog" // 17
};

static const char * const g_ResTypes[] =
{
    NULL
  , "CURSOR"
  , "BITMAP"
  , "ICON"
  , "MENU"
  , "DIALOG"
  , "STRING"
  , "FONTDIR"
  , "FONT"
  , "ACCELERATOR"
  , "RCDATA"
  , "MESSAGETABLE"
  , "GROUP_CURSOR"
  , NULL
  , "GROUP_ICON"
  , NULL
  , "VERSION"
  , "DLGINCLUDE"
  , NULL
  , "PLUGPLAY"
  , "VXD"
  , "ANICURSOR"
  , "ANIICON"
  , "HTML"
  , "MANIFEST"
};

static const UInt32 kFlag = (UInt32)1 << 31;
static const UInt32 kMask = ~kFlag;

struct CTableItem
{
  UInt32 Offset;
  UInt32 ID;
};


static const UInt32 kBmpHeaderSize = 14;
static const UInt32 kIconHeaderSize = 22;

struct CResItem
{
  UInt32 Type;
  UInt32 ID;
  UInt32 Lang;

  UInt32 Size;
  UInt32 Offset;

  UInt32 HeaderSize;
  Byte Header[kIconHeaderSize]; // it must be enough for max size header.
  bool Enabled;

  bool IsNameEqual(const CResItem &item) const { return Lang == item.Lang; }
  UInt32 GetSize() const { return Size + HeaderSize; }
  bool IsBmp() const { return Type == 2; }
  bool IsIcon() const { return Type == 3; }
  bool IsString() const { return Type == 6; }
  bool IsRcData() const { return Type == 10; }
  bool IsVersion() const { return Type == 16; }
  bool IsRcDataOrUnknown() const { return IsRcData() || Type > 64; }
};

struct CTextFile
{
  CByteDynamicBuffer Buf;

  size_t FinalSize() const { return Buf.GetPos(); }

  void AddChar(char c);
  void AddWChar(UInt16 c);
  void AddWChar_Smart(UInt16 c);
  void NewLine();
  void AddString(const char *s);
  void AddSpaces(int num);
  void AddBytes(const Byte *p, size_t size)
  {
    Buf.AddData(p, size);
  }
  
  void OpenBlock(int num)
  {
    AddSpaces(num);
    AddChar('{');
    NewLine();
  }
  void CloseBlock(int num)
  {
    AddSpaces(num);
    AddChar('}');
    NewLine();
  }
};

void CTextFile::AddChar(char c)
{
  Byte *p = Buf.GetCurPtrAndGrow(2);
  p[0] = (Byte)c;
  p[1] = 0;
}

void CTextFile::AddWChar(UInt16 c)
{
  Byte *p = Buf.GetCurPtrAndGrow(2);
  SetUi16(p, c)
}

void CTextFile::AddWChar_Smart(UInt16 c)
{
  if (c == '\n')
  {
    AddChar('\\');
    c = 'n';
  }
  AddWChar(c);
}

void CTextFile::NewLine()
{
  AddChar(0x0D);
  AddChar(0x0A);
}

void CTextFile::AddString(const char *s)
{
  for (;; s++)
  {
    char c = *s;
    if (c == 0)
      return;
    AddChar(c);
  }
}

void CTextFile::AddSpaces(int num)
{
  for (int i = 0; i < num; i++)
    AddChar(' ');
}

struct CStringItem: public CTextFile
{
  UInt32 Lang;
};

struct CByteBuffer_WithLang: public CByteBuffer
{
  UInt32 Lang;
};


struct CMixItem
{
  int SectionIndex;
  int ResourceIndex;
  int StringIndex;
  int VersionIndex;

  CMixItem(): SectionIndex(-1), ResourceIndex(-1), StringIndex(-1), VersionIndex(-1) {}
  bool IsSectionItem() const { return ResourceIndex < 0 && StringIndex < 0 && VersionIndex < 0; }
};

struct CUsedBitmap
{
  CByteBuffer Buf;
public:
  void Alloc(size_t size)
  {
    size = (size + 7) / 8;
    Buf.Alloc(size);
    memset(Buf, 0, size);
  }
  
  void Free()
  {
    Buf.Free();
  }
  
  bool SetRange(size_t from, unsigned size)
  {
    for (unsigned i = 0; i < size; i++)
    {
      size_t pos = (from + i) >> 3;
      Byte mask = (Byte)(1 << ((from + i) & 7));
      Byte b = Buf[pos];
      if ((b & mask) != 0)
        return false;
      Buf[pos] = (Byte)(b | mask);
    }
    return true;
  }
};
 
struct CStringKeyValue
{
  UString Key;
  UString Value;
};


Z7_CLASS_IMP_CHandler_IInArchive_2(
  IInArchiveGetStream,
  IArchiveAllowTail
)
  CMyComPtr<IInStream> _stream;
  CObjectVector<CSection> _sections;
  CHeader _header;
  UInt32 _totalSize;
  Int32 _mainSubfile;

  CRecordVector<CMixItem> _mixItems;
  CRecordVector<CResItem> _items;
  CObjectVector<CStringItem> _strings;
  CObjectVector<CByteBuffer_WithLang> _versionFiles;
  UString _versionFullString;
  UString _versionShortString;
  UString _originalFilename;
  CObjectVector<CStringKeyValue> _versionKeys;

  CByteBuffer _buf;
  bool _oneLang;
  UString _resourcesPrefix;
  CUsedBitmap _usedRes;
  // bool _parseResources;
  bool _checksumError;
  bool _sectionsError;

  bool IsOpt() const { return _header.OptHeaderSize != 0; }

  COptHeader _optHeader;

  bool _coffMode;
  bool _allowTail;

  HRESULT LoadDebugSections(IInStream *stream, bool &thereIsSection);
  HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback);

  void AddResNameToString(UString &s, UInt32 id) const;
  void AddLangPrefix(UString &s, UInt32 lang) const;
  HRESULT ReadString(UInt32 offset, UString &dest) const;
  HRESULT ReadTable(UInt32 offset, CRecordVector<CTableItem> &items);
  bool ParseStringRes(UInt32 id, UInt32 lang, const Byte *src, UInt32 size);
  HRESULT OpenResources(unsigned sectIndex, IInStream *stream, IArchiveOpenCallback *callback);
  void CloseResources();


  bool CheckItem(const CSection &sect, const CResItem &item, size_t offset) const
  {
    return item.Offset >= sect.Va && offset <= _buf.Size() && _buf.Size() - offset >= item.Size;
  }

public:
  CHandler(bool coffMode = false):
        _coffMode(coffMode),
        _allowTail(coffMode)
        {}
};


enum
{
  kpidSectAlign = kpidUserDefined,
  kpidFileAlign,
  kpidLinkerVer,
  kpidOsVer,
  kpidImageVer,
  kpidSubsysVer,
  kpidCodeSize,
  kpidImageSize,
  kpidInitDataSize,
  kpidUnInitDataSize,
  kpidHeadersSizeUnInitDataSize,
  kpidSubSystem,
  kpidDllCharacts,
  kpidStackReserve,
  kpidStackCommit,
  kpidHeapReserve,
  kpidHeapCommit,
  kpidImageBase
  // kpidAddressOfEntryPoint,
  // kpidBaseOfCode,
  // kpidBaseOfData32,
};

static const CStatProp kArcProps[] =
{
  // { NULL, kpidWarning, VT_BSTR},
  { NULL, kpidCpu, VT_BSTR},
  { NULL, kpidBit64, VT_BOOL},
  { NULL, kpidCharacts, VT_BSTR},
  { NULL, kpidCTime, VT_FILETIME},
  { NULL, kpidHeadersSize, VT_UI4},
  { NULL, kpidChecksum, VT_UI4},
  { NULL, kpidName, VT_BSTR},

  { "Image Size", kpidImageSize, VT_UI4},
  { "Section Alignment", kpidSectAlign, VT_UI4},
  { "File Alignment", kpidFileAlign, VT_UI4},
  { "Code Size", kpidCodeSize, VT_UI4},
  { "Initialized Data Size", kpidInitDataSize, VT_UI4},
  { "Uninitialized Data Size", kpidUnInitDataSize, VT_UI4},
  { "Linker Version", kpidLinkerVer, VT_BSTR},
  { "OS Version", kpidOsVer, VT_BSTR},
  { "Image Version", kpidImageVer, VT_BSTR},
  { "Subsystem Version", kpidSubsysVer, VT_BSTR},
  { "Subsystem", kpidSubSystem, VT_BSTR},
  { "DLL Characteristics", kpidDllCharacts, VT_BSTR},
  { "Stack Reserve", kpidStackReserve, VT_UI8},
  { "Stack Commit", kpidStackCommit, VT_UI8},
  { "Heap Reserve", kpidHeapReserve, VT_UI8},
  { "Heap Commit", kpidHeapCommit, VT_UI8},
  { "Image Base", kpidImageBase, VT_UI8},
  { NULL, kpidComment, VT_BSTR},
  
  // { "Address Of Entry Point", kpidAddressOfEntryPoint, VT_UI8},
  // { "Base Of Code", kpidBaseOfCode, VT_UI8},
  // { "Base Of Data", kpidBaseOfData32, VT_UI8},
};

static const Byte kProps[] =
{
  kpidPath,
  kpidSize,
  kpidPackSize,
  kpidVirtualSize,
  kpidCharacts,
  kpidOffset,
  kpidVa,
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps_WITH_NAME

static void TimeToProp(UInt32 unixTime, NCOM::CPropVariant &prop)
{
  if (unixTime != 0)
    PropVariant_SetFrom_UnixTime(prop, unixTime);
}

Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  switch (propID)
  {
    case kpidPhySize: prop = _totalSize; break;
    case kpidComment: if (!_versionFullString.IsEmpty()) prop = _versionFullString; break;
    case kpidShortComment:
      if (!_versionShortString.IsEmpty())
        prop = _versionShortString;
      else
      {
        PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop);
      }
      break;

    case kpidName: if (!_originalFilename.IsEmpty()) prop = _originalFilename; break;
      
    // case kpidIsSelfExe: prop = !_header.IsDll(); break;
    // case kpidError:
    case kpidWarning: if (_checksumError) prop = "Checksum error"; break;

    case kpidWarningFlags:
    {
      UInt32 v = 0;
      if (_sectionsError) v |= kpv_ErrorFlags_HeadersError;
      if (v != 0)
        prop = v;
      break;
    }

    case kpidCpu: PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); break;
    case kpidMTime:
    case kpidCTime: TimeToProp(_header.Time, prop); break;
    case kpidCharacts: FLAGS_TO_PROP(g_HeaderCharacts, _header.Flags, prop); break;
    case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break;
    
    default:
    if (IsOpt())
    switch (propID)
    {

    case kpidSectAlign: prop = _optHeader.SectAlign; break;
    case kpidFileAlign: prop = _optHeader.FileAlign; break;
    case kpidLinkerVer:
    {
      CVersion v = { _optHeader.LinkerVerMajor, _optHeader.LinkerVerMinor };
      v.ToProp(prop);
      break;
    }
  
    case kpidOsVer: _optHeader.OsVer.ToProp(prop); break;
    case kpidImageVer: _optHeader.ImageVer.ToProp(prop); break;
    case kpidSubsysVer: _optHeader.SubsysVer.ToProp(prop); break;
    case kpidCodeSize: prop = _optHeader.CodeSize; break;
    case kpidInitDataSize: prop = _optHeader.InitDataSize; break;
    case kpidUnInitDataSize: prop = _optHeader.UninitDataSize; break;
    case kpidImageSize: prop = _optHeader.ImageSize; break;
    case kpidHeadersSize: prop = _optHeader.HeadersSize; break;
    case kpidChecksum: prop = _optHeader.CheckSum; break;

    case kpidExtension:
      if (_header.IsDll())
        prop = "dll";
      else if (_optHeader.IsSybSystem_EFI())
        prop = "efi";
      break;

    case kpidBit64: if (_optHeader.Is64Bit()) prop = true; break;
    case kpidSubSystem: TYPE_TO_PROP(g_SubSystems, _optHeader.SubSystem, prop); break;

    case kpidDllCharacts: FLAGS_TO_PROP(g_DllCharacts, _optHeader.DllCharacts, prop); break;
    case kpidStackReserve: prop = _optHeader.StackReserve; break;
    case kpidStackCommit: prop = _optHeader.StackCommit; break;
    case kpidHeapReserve: prop = _optHeader.HeapReserve; break;
    case kpidHeapCommit: prop = _optHeader.HeapCommit; break;

    case kpidImageBase: prop = _optHeader.ImageBase; break;
    // case kpidAddressOfEntryPoint: prop = _optHeader.AddressOfEntryPoint; break;
    // case kpidBaseOfCode: prop = _optHeader.BaseOfCode; break;
    // case kpidBaseOfData32: if (!_optHeader.Is64Bit()) prop = _optHeader.BaseOfData32; break;
  }
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

HRESULT CHandler::ReadString(UInt32 offset, UString &dest) const
{
  if ((offset & 1) != 0 || offset >= _buf.Size())
    return S_FALSE;
  size_t rem = _buf.Size() - offset;
  if (rem < 2)
    return S_FALSE;
  unsigned len = Get16(_buf + offset);
  if ((rem - 2) / 2 < len)
    return S_FALSE;
  dest.Empty();
  wchar_t *destBuf = dest.GetBuf(len);
  offset += 2;
  const Byte *src = _buf + offset;
  unsigned i;
  for (i = 0; i < len; i++)
  {
    wchar_t c = (wchar_t)Get16(src + i * 2);
    if (c == 0)
      break;
    destBuf[i] = c;
  }
  destBuf[i] = 0;
  dest.ReleaseBuf_SetLen(i);
  return S_OK;
}

void CHandler::AddResNameToString(UString &s, UInt32 id) const
{
  if ((id & kFlag) != 0)
  {
    UString name;
    if (ReadString(id & kMask, name) == S_OK)
    {
      const wchar_t *str = L"[]";
      if (name.Len() > 1 && name[0] == '"' && name.Back() == '"')
      {
        if (name.Len() != 2)
        {
          name.DeleteBack();
          str = name.Ptr(1);
        }
      }
      else if (!name.IsEmpty())
        str = name;
      s += str;
      return;
    }
  }
  s.Add_UInt32(id);
}

void CHandler::AddLangPrefix(UString &s, UInt32 lang) const
{
  if (!_oneLang)
  {
    AddResNameToString(s, lang);
    s.Add_PathSepar();
  }
}

Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  const CMixItem &mixItem = _mixItems[index];
  if (mixItem.StringIndex >= 0)
  {
    const CStringItem &item = _strings[mixItem.StringIndex];
    switch (propID)
    {
      case kpidPath:
      {
        UString s = _resourcesPrefix;
        AddLangPrefix(s, item.Lang);
        s += "string.txt";
        prop = s;
        break;
      }
      case kpidSize:
      case kpidPackSize:
        prop = (UInt64)item.FinalSize(); break;
    }
  }
  else if (mixItem.VersionIndex >= 0)
  {
    const CByteBuffer_WithLang &item = _versionFiles[mixItem.VersionIndex];
    switch (propID)
    {
      case kpidPath:
      {
        UString s = _resourcesPrefix;
        AddLangPrefix(s, item.Lang);
        s += "version.txt";
        prop = s;
        break;
      }
      case kpidSize:
      case kpidPackSize:
        prop = (UInt64)item.Size(); break;
    }
  }
  else if (mixItem.ResourceIndex >= 0)
  {
    const CResItem &item = _items[mixItem.ResourceIndex];
    switch (propID)
    {
      case kpidPath:
      {
        UString s = _resourcesPrefix;
        AddLangPrefix(s, item.Lang);
        {
          const char *p = NULL;
          if (item.Type < Z7_ARRAY_SIZE(g_ResTypes))
            p = g_ResTypes[item.Type];
          if (p)
            s += p;
          else
            AddResNameToString(s, item.Type);
        }
        s.Add_PathSepar();
        AddResNameToString(s, item.ID);
        if (item.HeaderSize != 0)
        {
          if (item.IsBmp())
            s += ".bmp";
          else if (item.IsIcon())
            s += ".ico";
        }
        prop = s;
        break;
      }
      case kpidSize: prop = (UInt64)item.GetSize(); break;
      case kpidPackSize: prop = (UInt64)item.Size; break;
    }
  }
  else
  {
    const CSection &item = _sections[mixItem.SectionIndex];
    switch (propID)
    {
      case kpidPath:
      {
        AString s = item.Name;
        s.Replace('/', '_');
        s.Replace('\\', '_');
        prop = MultiByteToUnicodeString(s);
        break;
      }
      case kpidSize: prop = (UInt64)item.PSize; break;
      case kpidPackSize: prop = (UInt64)item.PSize; break;
      case kpidVirtualSize: prop = (UInt64)item.VSize; break;
      case kpidOffset: prop = item.Pa; break;
      case kpidVa: if (item.IsRealSect) prop = item.Va; break;
      case kpidMTime:
      case kpidCTime:
        TimeToProp(item.IsDebug ? item.Time : _header.Time, prop); break;
      case kpidCharacts:
       if (item.IsRealSect)
       {
         UInt32 flags = item.Flags;
         const UInt32 MY_IMAGE_SCN_ALIGN_MASK = 0x00F00000;
         AString s = FlagsToString(g_SectFlags, Z7_ARRAY_SIZE(g_SectFlags), item.Flags & ~MY_IMAGE_SCN_ALIGN_MASK);
         const UInt32 align = ((flags >> 20) & 0xF);
         if (align != 0)
         {
           char sz[32];
           ConvertUInt32ToString(1 << (align - 1), sz);
           s.Add_Space();
           s += "align_";
           s += sz;
         }
         prop = s;
       }
       break;
      case kpidZerosTailIsAllowed: if (!item.IsRealSect) prop = true; break;
    }
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

HRESULT CHandler::LoadDebugSections(IInStream *stream, bool &thereIsSection)
{
  thereIsSection = false;
  const CDirLink &debugLink = _optHeader.DirItems[kDirLink_Debug];
  if (debugLink.Size == 0)
    return S_OK;
  const unsigned kEntrySize = 28;
  UInt32 numItems = debugLink.Size / kEntrySize;
  if (numItems > 16)
    return S_FALSE;

  // MAC's EFI file: numItems can be incorrect. Only first CDebugEntry entry is correct.
  // debugLink.Size = kEntrySize + some_data, pointed by entry[0].
  if (numItems * kEntrySize != debugLink.Size)
  {
    // return S_FALSE;
    if (numItems > 1)
      numItems = 1;
  }
  
  UInt64 pa = 0;
  unsigned i;
  for (i = 0; i < _sections.Size(); i++)
  {
    const CSection &sect = _sections[i];
    if (sect.Va <= debugLink.Va && debugLink.Va + debugLink.Size <= sect.Va + sect.PSize)
    {
      pa = sect.Pa + (debugLink.Va - sect.Va);
      break;
    }
  }
  if (i == _sections.Size())
  {
    // Exe for ARM requires S_OK
    // return S_FALSE;
    return S_OK;
  }
  
  CByteBuffer buffer(debugLink.Size);
  Byte *buf = buffer;
  
  RINOK(InStream_SeekSet(stream, pa))
  RINOK(ReadStream_FALSE(stream, buf, debugLink.Size))

  for (i = 0; i < numItems; i++)
  {
    CDebugEntry de;
    de.Parse(buf);

    if (de.Size == 0)
      continue;
    
    UInt32 totalSize = de.Pa + de.Size;
    if (totalSize > _totalSize)
    {
      _totalSize = totalSize;
      thereIsSection = true;

      CSection &sect = _sections.AddNew();
      sect.Name = ".debug";
      sect.Name.Add_UInt32(i);
      sect.IsDebug = true;
      sect.Time = de.Time;
      sect.Va = de.Va;
      sect.Pa = de.Pa;
      sect.PSize = sect.VSize = de.Size;
    }
    buf += kEntrySize;
  }

  return S_OK;
}

HRESULT CHandler::ReadTable(UInt32 offset, CRecordVector<CTableItem> &items)
{
  if ((offset & 3) != 0 || offset >= _buf.Size())
    return S_FALSE;
  size_t rem = _buf.Size() - offset;
  if (rem < 16)
    return S_FALSE;
  unsigned numNameItems = Get16(_buf + offset + 12);
  unsigned numIdItems = Get16(_buf + offset + 14);
  unsigned numItems = numNameItems + numIdItems;
  if ((rem - 16) / 8 < numItems)
    return S_FALSE;
  if (!_usedRes.SetRange(offset, 16 + numItems * 8))
    return S_FALSE;
  offset += 16;
  items.ClearAndReserve(numItems);
  for (unsigned i = 0; i < numItems; i++, offset += 8)
  {
    const Byte *buf = _buf + offset;
    CTableItem item;
    item.ID = Get32(buf + 0);
    if ((bool)((item.ID & kFlag) != 0) != (bool)(i < numNameItems))
      return S_FALSE;
    item.Offset = Get32(buf + 4);
    items.AddInReserved(item);
  }
  return S_OK;
}

static const UInt32 kFileSizeMax = (UInt32)1 << 31;
static const unsigned kNumResItemsMax = (unsigned)1 << 23;
static const unsigned kNumStringLangsMax = 256;

// BITMAPINFOHEADER
struct CBitmapInfoHeader
{
  // UInt32 HeaderSize;
  UInt32 XSize;
  Int32 YSize;
  UInt16 Planes;
  UInt16 BitCount;
  UInt32 Compression;
  UInt32 SizeImage;

  bool Parse(const Byte *p, size_t size);
};

static const UInt32 kBitmapInfoHeader_Size = 0x28;

bool CBitmapInfoHeader::Parse(const Byte *p, size_t size)
{
  if (size < kBitmapInfoHeader_Size || Get32(p) != kBitmapInfoHeader_Size)
    return false;
  G32( 4, XSize);
  G32_signed( 8, YSize);
  G16(12, Planes);
  G16(14, BitCount);
  G32(16, Compression);
  G32(20, SizeImage);
  return true;
}

static UInt32 GetImageSize(UInt32 xSize, UInt32 ySize, UInt32 bitCount)
{
  return ((xSize * bitCount + 7) / 8 + 3) / 4 * 4 * ySize;
}
  
static UInt32 SetBitmapHeader(Byte *dest, const Byte *src, UInt32 size)
{
  CBitmapInfoHeader h;
  if (!h.Parse(src, size))
    return 0;
  if (h.YSize < 0)
    h.YSize = -h.YSize;
  if (h.XSize > (1 << 26)
      || h.YSize > (1 << 26)
      || h.YSize < 0
      || h.Planes != 1 || h.BitCount > 32)
    return 0;
  if (h.SizeImage == 0)
  {
    if (h.Compression != 0) // BI_RGB
      return 0;
    h.SizeImage = GetImageSize(h.XSize, (UInt32)h.YSize, h.BitCount);
  }
  UInt32 totalSize = kBmpHeaderSize + size;
  UInt32 offBits = totalSize - h.SizeImage;
  // BITMAPFILEHEADER
  SetUi16(dest, 0x4D42)
  SetUi32(dest + 2, totalSize)
  SetUi32(dest + 6, 0)
  SetUi32(dest + 10, offBits)
  return kBmpHeaderSize;
}

static UInt32 SetIconHeader(Byte *dest, const Byte *src, UInt32 size)
{
  CBitmapInfoHeader h;
  if (!h.Parse(src, size))
    return 0;
  if (h.YSize < 0)
    h.YSize = -h.YSize;
  if (h.XSize > (1 << 26)
      || h.YSize > (1 << 26)
      || h.YSize < 0
      || h.Planes != 1
      || h.Compression != 0) // BI_RGB
    return 0;

  const UInt32 numBitCount = h.BitCount;
  if (numBitCount != 1 &&
      numBitCount != 4 &&
      numBitCount != 8 &&
      numBitCount != 24 &&
      numBitCount != 32)
    return 0;

  if ((h.YSize & 1) != 0)
    return 0;
  h.YSize /= 2;
  if (h.XSize > 0x100 || h.YSize > 0x100)
    return 0;

  UInt32 imageSize;
  // imageSize is not correct if AND mask array contains zeros
  // in this case it is equal image1Size

  // UInt32 imageSize = h.SizeImage;
  // if (imageSize == 0)
  // {
    const UInt32 image1Size = GetImageSize(h.XSize, (UInt32)h.YSize, h.BitCount);
    const UInt32 image2Size = GetImageSize(h.XSize, (UInt32)h.YSize, 1);
    imageSize = image1Size + image2Size;
  // }
  UInt32 numColors = 0;
  if (numBitCount < 16)
    numColors = 1 << numBitCount;

  SetUi16(dest, 0) // Reserved
  SetUi16(dest + 2, 1) // RES_ICON
  SetUi16(dest + 4, 1) // ResCount

  dest[6] = (Byte)h.XSize; // Width
  dest[7] = (Byte)h.YSize; // Height
  dest[8] = (Byte)numColors; // ColorCount
  dest[9] = 0; // Reserved
  
  SetUi32(dest + 10, 0) // Reserved1 / Reserved2

  UInt32 numQuadsBytes = numColors * 4;
  UInt32 BytesInRes = kBitmapInfoHeader_Size + numQuadsBytes + imageSize;
  SetUi32(dest + 14, BytesInRes)
  SetUi32(dest + 18, kIconHeaderSize)

  /*
  Description = DWORDToString(xSize) +
      kDelimiterChar + DWORDToString(ySize) +
      kDelimiterChar + DWORDToString(numBitCount);
  */
  return kIconHeaderSize;
}

bool CHandler::ParseStringRes(UInt32 id, UInt32 lang, const Byte *src, UInt32 size)
{
  if ((size & 1) != 0)
    return false;

  unsigned i;
  for (i = 0; i < _strings.Size(); i++)
    if (_strings[i].Lang == lang)
      break;
  if (i == _strings.Size())
  {
    if (_strings.Size() >= kNumStringLangsMax)
      return false;
    CStringItem &item = _strings.AddNew();
    item.Lang = lang;
  }
  
  CStringItem &item = _strings[i];
  id = (id - 1) << 4;
  UInt32 pos = 0;
  for (i = 0; i < 16; i++)
  {
    if (size - pos < 2)
      return false;
    UInt32 len = Get16(src + pos);
    pos += 2;
    if (len != 0)
    {
      if (size - pos < len * 2)
        return false;
      char temp[32];
      ConvertUInt32ToString(id + i, temp);
      size_t tempLen = strlen(temp);
      size_t j;
      for (j = 0; j < tempLen; j++)
        item.AddChar(temp[j]);
      item.AddChar('\t');
      for (j = 0; j < len; j++, pos += 2)
        item.AddWChar_Smart(Get16(src + pos));
      item.NewLine();
    }
  }
  if (size == pos)
    return true;
  
  // Some rare case files have additional ZERO.
  if (size == pos + 2 && Get16(src + pos) == 0)
    return true;
  
  return false;
}


// ---------- VERSION ----------

static const UInt32 kMy_VS_FFI_SIGNATURE = 0xFEEF04BD;

struct CMy_VS_FIXEDFILEINFO
{
  // UInt32 Signature;
  // UInt32 StrucVersion;
  UInt32 VersionMS;
  UInt32 VersionLS;
  UInt32 ProductVersionMS;
  UInt32 ProductVersionLS;
  UInt32 FlagsMask;
  UInt32 Flags;
  UInt32 OS;
  UInt32 Type;
  UInt32 Subtype;
  UInt32 DateMS;
  UInt32 DateLS;

  bool Parse(const Byte *p);
  void PrintToTextFile(CTextFile &f, CObjectVector<CStringKeyValue> &keys);
};

bool CMy_VS_FIXEDFILEINFO::Parse(const Byte *p)
{
  if (Get32(p) != kMy_VS_FFI_SIGNATURE) // signature;
    return false;
  // G32(0x04, StrucVersion);
  G32(0x08, VersionMS);
  G32(0x0C, VersionLS);
  G32(0x10, ProductVersionMS);
  G32(0x14, ProductVersionLS);
  G32(0x18, FlagsMask);
  G32(0x1C, Flags);
  G32(0x20, OS);
  G32(0x24, Type);
  G32(0x28, Subtype);
  G32(0x2C, DateMS);
  G32(0x40, DateLS);
  return true;
}

static void PrintUInt32(CTextFile &f, UInt32 v)
{
  char s[16];
  ConvertUInt32ToString(v, s);
  f.AddString(s);
}

static inline void PrintUInt32(UString &dest, UInt32 v)
{
  dest.Add_UInt32(v);
}

static void PrintHex(CTextFile &f, UInt32 val)
{
  char temp[16];
  temp[0] = '0';
  temp[1] = 'x';
  ConvertUInt32ToHex(val, temp + 2);
  f.AddString(temp);
}

static void PrintVersion(CTextFile &f, UInt32 ms, UInt32 ls)
{
  PrintUInt32(f, HIWORD(ms));  f.AddChar(',');
  PrintUInt32(f, LOWORD(ms));  f.AddChar(',');
  PrintUInt32(f, HIWORD(ls));  f.AddChar(',');
  PrintUInt32(f, LOWORD(ls));
}

static void PrintVersion(UString &s, UInt32 ms, UInt32 ls)
{
  PrintUInt32(s, HIWORD(ms));  s.Add_Dot();
  PrintUInt32(s, LOWORD(ms));  s.Add_Dot();
  PrintUInt32(s, HIWORD(ls));  s.Add_Dot();
  PrintUInt32(s, LOWORD(ls));
}

static const char * const k_VS_FileFlags[] =
{
    "DEBUG"
  , "PRERELEASE"
  , "PATCHED"
  , "PRIVATEBUILD"
  , "INFOINFERRED"
  , "SPECIALBUILD"
};

static const CUInt32PCharPair k_VS_FileOS[] =
{
  {  0x10001, "VOS_DOS_WINDOWS16" },
  {  0x10004, "VOS_DOS_WINDOWS32" },
  {  0x20002, "VOS_OS216_PM16" },
  {  0x30003, "VOS_OS232_PM32" },
  {  0x40004, "VOS_NT_WINDOWS32" }
};

static const char * const k_VS_FileOS_High[] =
{
    "VOS_UNKNOWN"
  , "VOS_DOS"
  , "VOS_OS216"
  , "VOS_OS232"
  , "VOS_NT"
  , "VOS_WINCE"
};

static const UInt32 kMY_VFT_DRV  = 3;
static const UInt32 kMY_VFT_FONT = 4;

static const char * const k_VS_FileOS_Low[] =
{
    "VOS__BASE"
  , "VOS__WINDOWS16"
  , "VOS__PM16"
  , "VOS__PM32"
  , "VOS__WINDOWS32"
};

static const char * const k_VS_FileType[] =
{
    "VFT_UNKNOWN"
  , "VFT_APP"
  , "VFT_DLL"
  , "VFT_DRV"
  , "VFT_FONT"
  , "VFT_VXD"
  , "0x6"
  , "VFT_STATIC_LIB"
};

// Subtype for VFT_DRV Type
static const char * const k_VS_FileSubType_DRV[] =
{
    "0"
  , "PRINTER"
  , "KEYBOARD"
  , "LANGUAGE"
  , "DISPLAY"
  , "MOUSE"
  , "NETWORK"
  , "SYSTEM"
  , "INSTALLABLE"
  , "SOUND"
  , "COMM"
  , "INPUTMETHOD"
  , "VERSIONED_PRINTER"
};

// Subtype for VFT_FONT Type
static const char * const k_VS_FileSubType_FONT[] =
{
    "0"
  , "VFT2_FONT_RASTER"
  , "VFT2_FONT_VECTOR"
  , "VFT2_FONT_TRUETYPE"
};

static int FindKey(CObjectVector<CStringKeyValue> &v, const char *key)
{
  FOR_VECTOR (i, v)
    if (v[i].Key.IsEqualTo(key))
      return (int)i;
  return -1;
}

static void AddToUniqueUStringVector(CObjectVector<CStringKeyValue> &v, const UString &key, const UString &value)
{
  bool needInsert = false;
  unsigned i;
  for (i = 0; i < v.Size(); i++)
  {
    if (v[i].Key == key)
    {
      if (v[i].Value == value)
        return;
      needInsert = true;
    }
    else if (needInsert)
      break;
  }
  CStringKeyValue &pair = v.InsertNew(i);
  pair.Key = key;
  pair.Value = value;
}

void CMy_VS_FIXEDFILEINFO::PrintToTextFile(CTextFile &f, CObjectVector<CStringKeyValue> &keys)
{
  f.AddString("FILEVERSION    ");
  PrintVersion(f, VersionMS, VersionLS);
  f.NewLine();

  f.AddString("PRODUCTVERSION ");
  PrintVersion(f, ProductVersionMS, ProductVersionLS);
  f.NewLine();

  {
    UString s;
    PrintVersion(s, VersionMS, VersionLS);
    AddToUniqueUStringVector(keys, L"FileVersion", s);
  }
  {
    UString s;
    PrintVersion(s, ProductVersionMS, ProductVersionLS);
    AddToUniqueUStringVector(keys, L"ProductVersion", s);
  }
 
  f.AddString("FILEFLAGSMASK  ");
  PrintHex(f, FlagsMask);
  f.NewLine();

  f.AddString("FILEFLAGS      ");
  {
    bool wasPrinted = false;
    for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_VS_FileFlags); i++)
    {
      if ((Flags & ((UInt32)1 << i)) != 0)
      {
        if (wasPrinted)
          f.AddString(" | ");
        f.AddString("VS_FF_");
        f.AddString(k_VS_FileFlags[i]);
        wasPrinted = true;
      }
    }
    UInt32 v = Flags & ~(((UInt32)1 << Z7_ARRAY_SIZE(k_VS_FileFlags)) - 1);
    if (v != 0 || !wasPrinted)
    {
      if (wasPrinted)
        f.AddString(" | ");
      PrintHex(f, v);
    }
  }
  f.NewLine();

  // OS = 0x111230;
  f.AddString("FILEOS         ");
  unsigned i;
  for (i = 0; i < Z7_ARRAY_SIZE(k_VS_FileOS); i++)
  {
    const CUInt32PCharPair &pair = k_VS_FileOS[i];
    if (OS == pair.Value)
    {
      // continue;
      // f.AddString("VOS_");
      f.AddString(pair.Name);
      break;
    }
  }
  if (i == Z7_ARRAY_SIZE(k_VS_FileOS))
  {
    UInt32 high = OS >> 16;
    if (high < Z7_ARRAY_SIZE(k_VS_FileOS_High))
      f.AddString(k_VS_FileOS_High[high]);
    else
      PrintHex(f, high << 16);
    UInt32 low = OS & 0xFFFF;
    if (low != 0)
    {
      f.AddString(" | ");
      if (low < Z7_ARRAY_SIZE(k_VS_FileOS_Low))
        f.AddString(k_VS_FileOS_Low[low]);
      else
        PrintHex(f, low);
    }
  }
  f.NewLine();

  f.AddString("FILETYPE       ");
  if (Type < Z7_ARRAY_SIZE(k_VS_FileType))
    f.AddString(k_VS_FileType[Type]);
  else
    PrintHex(f, Type);
  f.NewLine();

  f.AddString("FILESUBTYPE    ");
  bool needPrintSubType = true;
  if (Type == kMY_VFT_DRV)
  {
    if (Subtype != 0 && Subtype < Z7_ARRAY_SIZE(k_VS_FileSubType_DRV))
    {
      f.AddString("VFT2_DRV_");
      f.AddString(k_VS_FileSubType_DRV[Subtype]);
      needPrintSubType = false;
    }
  }
  else if (Type == kMY_VFT_FONT)
  {
    if (Subtype != 0 && Subtype < Z7_ARRAY_SIZE(k_VS_FileSubType_FONT))
    {
      f.AddString(k_VS_FileSubType_FONT[Subtype]);
      needPrintSubType = false;
    }
  }
  if (needPrintSubType)
    PrintHex(f, Subtype);
  f.NewLine();
}

static void CopyToUString(const Byte *p, UString &s)
{
  for (;;)
  {
    wchar_t c = (wchar_t)Get16(p);
    p += 2;
    if (c == 0)
      return;
    s += c;
  }
}

static bool CompareWStrStrings(const Byte *p, const char *s)
{
  unsigned pos = 0;
  for (;;)
  {
    const Byte c = (Byte)*s++;
    if (Get16(p + pos) != c)
      return false;
    pos += 2;
    if (c == 0)
      return true;
  }
}

struct CVersionBlock
{
  UInt32 TotalLen;
  UInt32 ValueLen;
  bool IsTextValue;
  unsigned StrSize;

  bool Parse(const Byte *p, UInt32 size);
};

static int Get_Utf16Str_Len_InBytes(const Byte *p, size_t size)
{
  unsigned pos = 0;
  for (;;)
  {
    if (pos + 1 >= size)
      return -1;
    if (Get16(p + pos) == 0)
      return (int)pos;
    pos += 2;
  }
}

static const unsigned k_ResoureBlockHeader_Size = 6;

bool CVersionBlock::Parse(const Byte *p, UInt32 size)
{
  if (size < k_ResoureBlockHeader_Size)
    return false;
  TotalLen = Get16(p);
  ValueLen = Get16(p + 2);
  if (TotalLen < k_ResoureBlockHeader_Size || TotalLen > size)
    return false;
  switch (Get16(p + 4))
  {
    case 0: IsTextValue = false; break;
    case 1: IsTextValue = true; break;
    default: return false;
  }
  StrSize = 0;
  const int t = Get_Utf16Str_Len_InBytes(p + k_ResoureBlockHeader_Size, TotalLen - k_ResoureBlockHeader_Size);
  if (t < 0)
    return false;
  StrSize = (unsigned)t;
  return true;
}

static void AddParamString(CTextFile &f, const Byte *p, size_t sLen)
{
  f.AddChar(' ');
  f.AddChar('\"');
  f.AddBytes(p, sLen);
  f.AddChar('\"');
}

static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector<CStringKeyValue> &keys)
{
  UInt32 pos;
  {
    const unsigned k_sizeof_VS_FIXEDFILEINFO = 13 * 4;

    CVersionBlock vb;
    if (!vb.Parse(p, size))
      return false;
    if (vb.ValueLen != k_sizeof_VS_FIXEDFILEINFO) // maybe 0 is allowed here?
      return false;
    if (vb.IsTextValue)
      return false;
    pos = k_ResoureBlockHeader_Size;
    if (!CompareWStrStrings(p + pos, "VS_VERSION_INFO"))
      return false;
    pos += vb.StrSize + 2;
    pos += (4 - pos) & 3;
    if (pos + vb.ValueLen > vb.TotalLen)
      return false;
    /* sometimes resource contains zeros in remainder.
       So we don't check that size != vb.TotalLen
    // if (size != vb.TotalLen) return false;
    */
    if (size > vb.TotalLen)
      size = vb.TotalLen;
    CMy_VS_FIXEDFILEINFO FixedFileInfo;
    if (!FixedFileInfo.Parse(p + pos))
      return false;
    FixedFileInfo.PrintToTextFile(f, keys);
    pos += vb.ValueLen;
  }
  
  f.OpenBlock(0);
  
  for (;;)
  {
    pos += (4 - pos) & 3;
    if (pos >= size)
      break;
    
    CVersionBlock vb;
    if (!vb.Parse(p + pos, size - pos))
      return false;
    if (vb.ValueLen != 0)
      return false;
    UInt32 endPos = pos + vb.TotalLen;
    pos += k_ResoureBlockHeader_Size;
    
    f.AddSpaces(2);
    f.AddString("BLOCK");
    AddParamString(f, p + pos, vb.StrSize);
    
    f.NewLine();
    f.OpenBlock(2);
    
    if (CompareWStrStrings(p + pos, "VarFileInfo"))
    {
      pos += vb.StrSize + 2;
      for (;;)
      {
        pos += (4 - pos) & 3;
        if (pos >= endPos)
          break;
        CVersionBlock vb2;
        if (!vb2.Parse(p + pos, endPos - pos))
          return false;
        UInt32 endPos2 = pos + vb2.TotalLen;
        if (vb2.IsTextValue)
          return false;
        pos += k_ResoureBlockHeader_Size;
        f.AddSpaces(4);
        f.AddString("VALUE");
        AddParamString(f, p + pos, vb2.StrSize);
        if (!CompareWStrStrings(p + pos, "Translation"))
          return false;
        pos += vb2.StrSize + 2;
        pos += (4 - pos) & 3;
        if (pos + vb2.ValueLen != endPos2)
          return false;
        if ((vb2.ValueLen & 3) != 0)
          return false;
        UInt32 num = (vb2.ValueLen >> 2);
        for (; num != 0; num--, pos += 4)
        {
          UInt32 dw = Get32(p + pos);
          UInt32 lang = LOWORD(dw);
          UInt32 codePage = HIWORD(dw);

          f.AddString(", ");
          PrintHex(f, lang);
          f.AddString(", ");
          PrintUInt32(f, codePage);
        }
        f.NewLine();
      }
    }
    else
    {
      if (!CompareWStrStrings(p + pos, "StringFileInfo"))
        return false;
      pos += vb.StrSize + 2;
  
      for (;;)
      {
        pos += (4 - pos) & 3;
        if (pos >= endPos)
          break;
        CVersionBlock vb2;
        if (!vb2.Parse(p + pos, endPos - pos))
          return false;
        UInt32 endPos2 = pos + vb2.TotalLen;
        if (vb2.ValueLen != 0)
          return false;
        pos += k_ResoureBlockHeader_Size;

        f.AddSpaces(4);
        f.AddString("BLOCK");
        AddParamString(f, p + pos, vb2.StrSize);
        pos += vb2.StrSize + 2;

        f.NewLine();
        f.OpenBlock(4);

        for (;;)
        {
          pos += (4 - pos) & 3;
          if (pos >= endPos2)
            break;

          CVersionBlock vb3;
          if (!vb3.Parse(p + pos, endPos2 - pos))
            return false;
          // ValueLen sometimes is a number of characters (not bytes)?
          // So we don't use it.
          UInt32 endPos3 = pos + vb3.TotalLen;
          pos += k_ResoureBlockHeader_Size;

          // we don't write string if it's not text
          if (vb3.IsTextValue)
          {
            f.AddSpaces(6);
            f.AddString("VALUE");
            AddParamString(f, p + pos, vb3.StrSize);
            UString key;
            UString value;
            CopyToUString(p + pos, key);
            pos += vb3.StrSize + 2;

            pos += (4 - pos) & 3;
            if (vb3.ValueLen > 0 && pos + 2 <= endPos3)
            {
              f.AddChar(',');
              f.AddSpaces((34 - (int)vb3.StrSize) / 2);
              const int sLen = Get_Utf16Str_Len_InBytes(p + pos, endPos3 - pos);
              if (sLen < 0)
                return false;
              AddParamString(f, p + pos, (unsigned)sLen);
              CopyToUString(p + pos, value);
              pos += (unsigned)sLen + 2;
            }
            AddToUniqueUStringVector(keys, key, value);
          }
          pos = endPos3;
          f.NewLine();
        }
        f.CloseBlock(4);
      }
    }
    f.CloseBlock(2);
  }

  f.CloseBlock(0);
  return true;
}


HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchiveOpenCallback *callback)
{
  const CSection &sect = _sections[sectionIndex];
  size_t fileSize = sect.PSize;
  {
    size_t fileSizeMin = sect.PSize;
    
    if (sect.VSize < sect.PSize)
    {
      fileSize = fileSizeMin = sect.VSize;
      const int numBits = _optHeader.GetNumFileAlignBits();
      if (numBits > 0)
      {
        const UInt32 mask = ((UInt32)1 << numBits) - 1;
        const size_t end = (size_t)((sect.VSize + mask) & (UInt32)~mask);
        if (end > sect.VSize)
        {
          if (end <= sect.PSize)
            fileSize = end;
          else
            fileSize = sect.PSize;
        }
      }
    }

    if (fileSize > kFileSizeMax)
      return S_FALSE;

    {
      const UInt64 fileSize64 = fileSize;
      if (callback)
        RINOK(callback->SetTotal(NULL, &fileSize64))
    }
    
    RINOK(InStream_SeekSet(stream, sect.Pa))
    
    _buf.Alloc(fileSize);
    
    size_t pos;
    
    for (pos = 0; pos < fileSize;)
    {
      {
        const UInt64 offset64 = pos;
        if (callback)
          RINOK(callback->SetCompleted(NULL, &offset64))
      }
      size_t rem = MyMin(fileSize - pos, (size_t)(1 << 22));
      RINOK(ReadStream(stream, _buf + pos, &rem))
      if (rem == 0)
      {
        if (pos < fileSizeMin)
          return S_FALSE;
        break;
      }
      pos += rem;
    }
    
    if (pos < fileSize)
      memset(_buf + pos, 0, fileSize - pos);
  }
  
  _usedRes.Alloc(fileSize);
  CRecordVector<CTableItem> specItems;
  RINOK(ReadTable(0, specItems))

  _oneLang = true;
  bool stringsOk = true;
  size_t maxOffset = 0;
  
  FOR_VECTOR (i, specItems)
  {
    const CTableItem &item1 = specItems[i];
    if ((item1.Offset & kFlag) == 0)
      return S_FALSE;

    CRecordVector<CTableItem> specItems2;
    RINOK(ReadTable(item1.Offset & kMask, specItems2))

    FOR_VECTOR (j, specItems2)
    {
      const CTableItem &item2 = specItems2[j];
      if ((item2.Offset & kFlag) == 0)
        return S_FALSE;
      
      CRecordVector<CTableItem> specItems3;
      RINOK(ReadTable(item2.Offset & kMask, specItems3))
      
      CResItem item;
      item.Type = item1.ID;
      item.ID = item2.ID;
      
      FOR_VECTOR (k, specItems3)
      {
        if (_items.Size() >= kNumResItemsMax)
          return S_FALSE;
        const CTableItem &item3 = specItems3[k];
        if ((item3.Offset & kFlag) != 0)
          return S_FALSE;
        if (item3.Offset >= _buf.Size() || _buf.Size() - item3.Offset < 16)
          return S_FALSE;
        const Byte *buf = _buf + item3.Offset;
        item.Lang = item3.ID;
        item.Offset = Get32(buf + 0);
        item.Size = Get32(buf + 4);
        // UInt32 codePage = Get32(buf + 8);
        if (Get32(buf + 12) != 0)
          return S_FALSE;
        if (!_items.IsEmpty() && _oneLang && !item.IsNameEqual(_items.Back()))
          _oneLang = false;

        item.HeaderSize = 0;
      
        size_t offset = item.Offset - sect.Va;
        if (offset > maxOffset)
          maxOffset = offset;
        if (offset + item.Size > maxOffset)
          maxOffset = offset + item.Size;

        if (CheckItem(sect, item, offset))
        {
          const Byte *data = _buf + offset;
          if (item.IsBmp())
            item.HeaderSize = SetBitmapHeader(item.Header, data, item.Size);
          else if (item.IsIcon())
            item.HeaderSize = SetIconHeader(item.Header, data, item.Size);
          else if (item.IsString())
          {
            if (stringsOk)
              stringsOk = ParseStringRes(item.ID, item.Lang, data, item.Size);
          }
        }

        if (item.IsVersion())
        {
          if (offset > _buf.Size() || _buf.Size() - offset < item.Size)
            continue;
          CTextFile f;
          if (ParseVersion((const Byte *)_buf + offset, item.Size, f, _versionKeys))
          {
            CMixItem mixItem;
            mixItem.VersionIndex = (int)_versionFiles.Size();
            mixItem.SectionIndex = (int)sectionIndex; // check it !!!!
            CByteBuffer_WithLang &vf = _versionFiles.AddNew();
            vf.Lang = item.Lang;
            vf.CopyFrom(f.Buf, f.Buf.GetPos());
            _mixItems.Add(mixItem);
            continue;
          }
          // PrintError("ver.Parse error");
        }
  
        item.Enabled = true;
        _items.Add(item);
      }
    }
  }
  
  if (stringsOk && !_strings.IsEmpty())
  {
    unsigned i;
    for (i = 0; i < _items.Size(); i++)
    {
      CResItem &item = _items[i];
      if (item.IsString())
        item.Enabled = false;
    }
    for (i = 0; i < _strings.Size(); i++)
    {
      if (_strings[i].FinalSize() == 0)
        continue;
      CMixItem mixItem;
      mixItem.StringIndex = (int)i;
      mixItem.SectionIndex = (int)sectionIndex;
      _mixItems.Add(mixItem);
    }
  }

  _usedRes.Free();

  {
    // PSize can be much larger than VSize in some exe installers.
    // it contains archive data after PE resources.
    // So we need to use PSize here!
    if (maxOffset < sect.PSize)
    {
      size_t end = fileSize;

      // we skip Zeros to start of aligned block
      size_t i;
      for (i = maxOffset; i < end; i++)
        if (_buf[i] != 0)
          break;
      if (i == end)
        maxOffset = end;
      
      CSection sect2;
      sect2.Flags = 0;
      sect2.Pa = sect.Pa + (UInt32)maxOffset;
      sect2.Va = sect.Va + (UInt32)maxOffset;

      // 9.29: we use sect.PSize instead of sect.VSize to support some CAB-SFX
      // the code for .rsrc_2 is commented.
      sect2.PSize = sect.PSize - (UInt32)maxOffset;

      if (sect2.PSize != 0)
      {
        sect2.VSize = sect2.PSize;
        sect2.Name = ".rsrc_1";
        sect2.Time = 0;
        sect2.IsAdditionalSection = true;
        _sections.Add(sect2);
      }
    }
  }

  return S_OK;
}


bool CHeader::ParseCoff(const Byte *p)
{
  ParseBase(p);
  if (PointerToSymbolTable < kCoffHeaderSize)
    return false;
  if (NumSymbols >= (1 << 24))
    return false;
  if (OptHeaderSize != 0 && OptHeaderSize < k_OptHeader32_Size_MIN)
    return false;

  // 18.04: we reduce false detections
  if (NumSections == 0 && OptHeaderSize == 0)
    return false;

  for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_MachinePairs); i++)
    if (Machine == g_MachinePairs[i].Value)
      return true;
  if (Machine == 0)
    return true;

  return false;
}


static inline bool CheckPeOffset(UInt32 pe)
{
  // ((pe & 7) == 0) is for most PE files. But there is unusual EFI-PE file that uses unaligned pe value.
  return pe >= 0x40 && pe <= 0x1000 /* && (pe & 7) == 0 */ ;
}

static const unsigned kStartSize = 0x40;

API_FUNC_static_IsArc IsArc_Pe(const Byte *p, size_t size)
{
  if (size < 2)
    return k_IsArc_Res_NEED_MORE;
  if (p[0] != 'M' || p[1] != 'Z')
    return k_IsArc_Res_NO;
  if (size < kStartSize)
    return k_IsArc_Res_NEED_MORE;
  UInt32 pe = Get32(p + 0x3C);
  if (!CheckPeOffset(pe))
    return k_IsArc_Res_NO;
  if (pe + kPeHeaderSize > size)
    return k_IsArc_Res_NEED_MORE;
  CHeader header;
  if (!header.ParsePe(p + pe))
    return k_IsArc_Res_NO;
  return k_IsArc_Res_YES;
}
}

HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback)
{
  UInt32 coffOffset = 0;
  if (_coffMode)
  {
    Byte h[kCoffHeaderSize];
    RINOK(ReadStream_FALSE(stream, h, kCoffHeaderSize))
    if (!_header.ParseCoff(h))
      return S_FALSE;
  }
  else
  {
    UInt32 _peOffset;
    {
      Byte h[kStartSize];
      RINOK(ReadStream_FALSE(stream, h, kStartSize))
      if (h[0] != 'M' || h[1] != 'Z')
        return S_FALSE;
        /* most of PE files contain 0x0090 at offset 2.
        But some rare PE files contain another values. So we don't use that check.
      if (Get16(h + 2) != 0x90) return false; */
      _peOffset = Get32(h + 0x3C);
      if (!CheckPeOffset(_peOffset))
        return S_FALSE;
      coffOffset = _peOffset + 4;
    }
    {
      Byte h[kPeHeaderSize];
      RINOK(InStream_SeekSet(stream, _peOffset))
      RINOK(ReadStream_FALSE(stream, h, kPeHeaderSize))
      if (!_header.ParsePe(h))
        return S_FALSE;
    }
  }

  const UInt32 optStart = coffOffset + kCoffHeaderSize;
  const UInt32 bufSize = _header.OptHeaderSize + (UInt32)_header.NumSections * kSectionSize;
  _totalSize = optStart + bufSize;
  CByteBuffer buffer(bufSize);

  RINOK(ReadStream_FALSE(stream, buffer, bufSize))
  
  // memset((void *)&_optHeader, 0, sizeof(_optHeader));
  if (_header.OptHeaderSize != 0)
  if (!_optHeader.Parse(buffer, _header.OptHeaderSize))
    return S_FALSE;

  UInt32 pos = _header.OptHeaderSize;
  unsigned i;
  for (i = 0; i < _header.NumSections; i++, pos += kSectionSize)
  {
    CSection &sect = _sections.AddNew();
    sect.Parse(buffer + pos);
    sect.IsRealSect = true;
    
    /* PE pre-file in .hxs file has errors:
         PSize of resource is larger than real size.
         So it overlaps next ".its" section.
       7-zip before 22.02: we corrected it.

       22.02: another bad case is possible in incorrect pe (exe) file:
         PSize in .rsrc section is correct,
         but next .reloc section has incorrect (Pa) that overlaps with .rsrc.
    */

    if (i != 0)
    {
      const CSection &prev = _sections[i - 1];
      if (prev.Pa < sect.Pa
          && prev.Pa + prev.PSize > sect.Pa
          && sect.PSize != 0
          && prev.PSize != 0)
      {
        _sectionsError = true;
        // PRF(printf("\n !!!! Section correction: %s\n ", prev.Name));

        /* we corrected that case in 7-zip before 22.02: */
        // prev.PSize = sect.Pa - prev.Pa;

        /* 22.02: here we can try to change bad section position to expected postion.
           but original Windows code probably will not do same things. */
        // if (prev.PSize <= sect.Va - prev.Va) sect.Pa = prev.Pa + prev.PSize;
      }
    }
    /* last ".its" section in hxs file has incorrect sect.PSize.
       7-zip before 22.02: we reduced section to real sect.VSize */
    /*
    if (sect.VSize == 24 && sect.PSize == 512 && i == (unsigned)_header.NumSections - 1)
      sect.PSize = sect.VSize;
    */
  }

  for (i = 0; i < _sections.Size(); i++)
    _sections[i].UpdateTotalSize(_totalSize);

  bool thereISDebug = false;
  if (IsOpt())
  {
  RINOK(LoadDebugSections(stream, thereISDebug))

  const CDirLink &certLink = _optHeader.DirItems[kDirLink_Certificate];
  if (certLink.Size != 0)
  {
    CSection &sect = _sections.AddNew();
    sect.Name = "CERTIFICATE";
    sect.Va = 0;
    sect.Pa = certLink.Va;
    sect.PSize = sect.VSize = certLink.Size;
    sect.UpdateTotalSize(_totalSize);
  }

  if (thereISDebug)
  {
    /* sometime there is some data after debug section.
       We don't see any reference in exe file to that data.
       But we suppose that it's part of EXE file */

    const UInt32 kAlign = 1 << 12;
    UInt32 alignPos = _totalSize & (kAlign - 1);
    if (alignPos != 0)
    {
      UInt32 size = kAlign - alignPos;
      RINOK(InStream_SeekSet(stream, _totalSize))
      buffer.Alloc(kAlign);
      Byte *buf = buffer;
      size_t processed = size;
      RINOK(ReadStream(stream, buf, &processed))

      /*
      if (processed != 0)
      {
        printf("\ndata after debug %d, %d \n", (int)size, (int)processed);
        fflush(stdout);
      }
      */

      size_t k;
      for (k = 0; k < processed; k++)
        if (buf[k] != 0)
          break;
      if (processed < size && processed < 100)
        _totalSize += (UInt32)processed;
      else if (((_totalSize + k) & 0x1FF) == 0 || processed < size)
        _totalSize += (UInt32)k;
    }
  }
  }

  if (_header.NumSymbols > 0 && _header.PointerToSymbolTable >= optStart)
  {
    if (_header.NumSymbols >= (1 << 24))
      return S_FALSE;
    UInt32 size = _header.NumSymbols * 18;
    RINOK(InStream_SeekSet(stream, (UInt64)_header.PointerToSymbolTable + size))
    Byte buf[4];
    RINOK(ReadStream_FALSE(stream, buf, 4))
    UInt32 size2 = Get32(buf);
    if (size2 >= (1 << 28))
      return S_FALSE;
    size += size2;

    CSection &sect = _sections.AddNew();
    sect.Name = "COFF_SYMBOLS";
    sect.Va = 0;
    sect.Pa = _header.PointerToSymbolTable;
    sect.PSize = sect.VSize = size;
    sect.UpdateTotalSize(_totalSize);
  }

  {
    CObjectVector<CSection> sections = _sections;
    sections.Sort();
    UInt32 limit = (1 << 12);
    unsigned num = 0;
    FOR_VECTOR (k, sections)
    {
      const CSection &s = sections[k];
      if (s.Pa > limit)
      {
        CSection &s2 = _sections.AddNew();
        s2.Pa = s2.Va = limit;
        s2.PSize = s2.VSize = s.Pa - limit;
        s2.IsAdditionalSection = true;
        s2.Name = '[';
        s2.Name.Add_UInt32(num++);
        s2.Name += ']';
        limit = s.Pa;
      }
      UInt32 next = s.Pa + s.PSize;
      if (next < s.Pa)
        break;
      if (next >= limit)
        limit = next;
    }
  }


  if (IsOpt())
  if (_optHeader.CheckSum != 0)
  {
    RINOK(InStream_SeekToBegin(stream))
    UInt32 checkSum = 0;
    RINOK(CalcCheckSum(stream, _totalSize, optStart + k_CheckSum_Field_Offset, checkSum))
    _checksumError = (checkSum != _optHeader.CheckSum);
  }


  if (!_allowTail)
  {
    UInt64 fileSize;
    RINOK(InStream_GetSize_SeekToEnd(stream, fileSize))
    if (fileSize > _totalSize)
      return S_FALSE;
  }

  bool _parseResources = true;
  // _parseResources = false; // for debug

  UInt64 mainSize = 0, mainSize2 = 0;

  for (i = 0; i < _sections.Size(); i++)
  {
    const CSection &sect = _sections[i];
    if (IsOpt())
    if (_parseResources && sect.Name == ".rsrc")
    {
      // 20.01: we try to parse only first copy of .rsrc section.
      _parseResources = false;
      const unsigned numMixItems = _mixItems.Size();
      HRESULT res = OpenResources(i, stream, callback);
      if (res == S_OK)
      {
        _resourcesPrefix = sect.Name.Ptr();
        _resourcesPrefix.Add_PathSepar();
        FOR_VECTOR (j, _items)
        {
          const CResItem &item = _items[j];
          if (item.Enabled)
          {
            CMixItem mixItem;
            mixItem.SectionIndex = (int)i;
            mixItem.ResourceIndex = (int)j;
            if (item.IsRcDataOrUnknown())
            {
              if (item.Size >= mainSize)
              {
                mainSize2 = mainSize;
                mainSize = item.Size;
                _mainSubfile = (Int32)(int)_mixItems.Size();
              }
              else if (item.Size >= mainSize2)
                mainSize2 = item.Size;
            }
            _mixItems.Add(mixItem);
          }
        }
        // 9.29: .rsrc_2 code was commented.
        // .rsrc_1 now must include that .rsrc_2 block.
        /*
        if (sect.PSize > sect.VSize)
        {
          int numBits = _optHeader.GetNumFileAlignBits();
          if (numBits >= 0)
          {
            UInt32 mask = (1 << numBits) - 1;
            UInt32 end = ((sect.VSize + mask) & ~mask);

            if (sect.PSize > end)
            {
              CSection &sect2 = _sections.AddNew();
              sect2.Flags = 0;
              sect2.Pa = sect.Pa + end;
              sect2.Va = sect.Va + end;
              sect2.PSize = sect.PSize - end;
              sect2.VSize = sect2.PSize;
              sect2.Name = ".rsrc_2";
              sect2.Time = 0;
              sect2.IsAdditionalSection = true;
            }
          }
        }
        */
        continue;
      }
      if (res != S_FALSE)
        return res;
      _mixItems.DeleteFrom(numMixItems);
      CloseResources();
    }

    if (sect.IsAdditionalSection)
    {
      if (sect.PSize >= mainSize)
      {
        mainSize2 = mainSize;
        mainSize = sect.PSize;
        _mainSubfile = (Int32)(int)_mixItems.Size();
      }
      else if (sect.PSize >= mainSize2)
        mainSize2 = sect.PSize;
    }
    
    CMixItem mixItem;
    mixItem.SectionIndex = (int)i;
    _mixItems.Add(mixItem);
  }
  
  if (mainSize2 >= (1 << 20) && mainSize < mainSize2 * 2)
    _mainSubfile = -1;

  for (i = 0; i < _mixItems.Size(); i++)
  {
    const CMixItem &mixItem = _mixItems[i];
    if (mixItem.StringIndex < 0 && mixItem.ResourceIndex < 0 && _sections[mixItem.SectionIndex].Name == "_winzip_")
    {
      _mainSubfile = (Int32)(int)i;
      break;
    }
  }

  for (i = 0; i < _versionKeys.Size(); i++)
  {
    if (i != 0)
      _versionFullString.Add_LF();
    const CStringKeyValue &k = _versionKeys[i];
    _versionFullString += k.Key;
    _versionFullString += ": ";
    _versionFullString += k.Value;
  }

  {
    int keyIndex = FindKey(_versionKeys, "OriginalFilename");
    if (keyIndex >= 0)
      _originalFilename = _versionKeys[keyIndex].Value;
  }
  {
    int keyIndex = FindKey(_versionKeys, "FileDescription");
    if (keyIndex >= 0)
      _versionShortString = _versionKeys[keyIndex].Value;
  }
  {
    int keyIndex = FindKey(_versionKeys, "FileVersion");
    if (keyIndex >= 0)
    {
      _versionShortString.Add_Space();
      _versionShortString += _versionKeys[keyIndex].Value;
    }
  }

  return S_OK;
}

Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback))
{
  COM_TRY_BEGIN
  Close();
  RINOK(Open2(inStream, callback))
  _stream = inStream;
  return S_OK;
  COM_TRY_END
}

void CHandler::CloseResources()
{
  _usedRes.Free();
  _items.Clear();
  _strings.Clear();
  _versionFiles.Clear();
  _buf.Free();
  _versionFullString.Empty();
  _versionShortString.Empty();
  _originalFilename.Empty();
  _versionKeys.Clear();
}

Z7_COM7F_IMF(CHandler::Close())
{
  _totalSize = 0;
  _checksumError = false;
  _sectionsError = false;
  _mainSubfile = -1;

  _stream.Release();
  _sections.Clear();
  _mixItems.Clear();
  CloseResources();
  return S_OK;
}

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

Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
    Int32 testMode, IArchiveExtractCallback *extractCallback))
{
  COM_TRY_BEGIN
  const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
  if (allFilesMode)
    numItems = _mixItems.Size();
  if (numItems == 0)
    return S_OK;
  UInt64 totalSize = 0;
  UInt32 i;
  for (i = 0; i < numItems; i++)
  {
    const CMixItem &mixItem = _mixItems[allFilesMode ? i : indices[i]];
    UInt64 size;
    if (mixItem.StringIndex >= 0)
      size = _strings[mixItem.StringIndex].FinalSize();
    else if (mixItem.VersionIndex >= 0)
      size = _versionFiles[mixItem.VersionIndex].Size();
    else if (mixItem.ResourceIndex >= 0)
      size = _items[mixItem.ResourceIndex].GetSize();
    else
      size = _sections[mixItem.SectionIndex].GetSizeExtract();
    totalSize += size;
  }
  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(_stream);

  for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize)
  {
    lps->InSize = lps->OutSize = currentTotalSize;
    RINOK(lps->SetCur())
    const Int32 askMode = testMode ?
        NExtract::NAskMode::kTest :
        NExtract::NAskMode::kExtract;
    const UInt32 index = allFilesMode ? i : indices[i];

    CMyComPtr<ISequentialOutStream> outStream;
    RINOK(extractCallback->GetStream(index, &outStream, askMode))
    const CMixItem &mixItem = _mixItems[index];

    const CSection &sect = _sections[mixItem.SectionIndex];
    bool isOk = true;
    if (mixItem.StringIndex >= 0)
    {
      const CStringItem &item = _strings[mixItem.StringIndex];
      currentItemSize = item.FinalSize();
      if (!testMode && !outStream)
        continue;

      RINOK(extractCallback->PrepareOperation(askMode))
      if (outStream)
        RINOK(WriteStream(outStream, item.Buf, item.FinalSize()))
    }
    else if (mixItem.VersionIndex >= 0)
    {
      const CByteBuffer &item = _versionFiles[mixItem.VersionIndex];
      currentItemSize = item.Size();
      if (!testMode && !outStream)
        continue;

      RINOK(extractCallback->PrepareOperation(askMode))
      if (outStream)
        RINOK(WriteStream(outStream, item, item.Size()))
    }
    else if (mixItem.ResourceIndex >= 0)
    {
      const CResItem &item = _items[mixItem.ResourceIndex];
      currentItemSize = item.GetSize();
      if (!testMode && !outStream)
        continue;

      RINOK(extractCallback->PrepareOperation(askMode))
      size_t offset = item.Offset - sect.Va;
      if (!CheckItem(sect, item, offset))
        isOk = false;
      else if (outStream)
      {
        if (item.HeaderSize != 0)
          RINOK(WriteStream(outStream, item.Header, item.HeaderSize))
        RINOK(WriteStream(outStream, _buf + offset, item.Size))
      }
    }
    else
    {
      currentItemSize = sect.GetSizeExtract();
      if (!testMode && !outStream)
        continue;
      
      RINOK(extractCallback->PrepareOperation(askMode))
      RINOK(InStream_SeekSet(_stream, sect.Pa))
      streamSpec->Init(currentItemSize);
      RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress))
      isOk = (copyCoderSpec->TotalSize == currentItemSize);
    }
    
    outStream.Release();
    RINOK(extractCallback->SetOperationResult(isOk ?
        NExtract::NOperationResult::kOK :
        NExtract::NOperationResult::kDataError))
  }
  return S_OK;
  COM_TRY_END
}

Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
{
  COM_TRY_BEGIN
  *stream = NULL;

  const CMixItem &mixItem = _mixItems[index];
  const CSection &sect = _sections[mixItem.SectionIndex];
  if (mixItem.IsSectionItem())
    return CreateLimitedInStream(_stream, sect.Pa, sect.PSize, stream);

  CBufInStream *inStreamSpec = new CBufInStream;
  CMyComPtr<ISequentialInStream> streamTemp = inStreamSpec;
  CReferenceBuf *referenceBuf = new CReferenceBuf;
  CMyComPtr<IUnknown> ref = referenceBuf;
  if (mixItem.StringIndex >= 0)
  {
    const CStringItem &item = _strings[mixItem.StringIndex];
    referenceBuf->Buf.CopyFrom(item.Buf, item.FinalSize());
  }
  else if (mixItem.VersionIndex >= 0)
  {
    const CByteBuffer &item = _versionFiles[mixItem.VersionIndex];
    referenceBuf->Buf.CopyFrom(item, item.Size());
  }
  else
  {
    const CResItem &item = _items[mixItem.ResourceIndex];
    size_t offset = item.Offset - sect.Va;
    if (!CheckItem(sect, item, offset))
      return S_FALSE;
    if (item.HeaderSize == 0)
    {
      CBufInStream *streamSpec = new CBufInStream;
      CMyComPtr<IInStream> streamTemp2 = streamSpec;
      streamSpec->Init(_buf + offset, item.Size, (IInArchive *)this);
      *stream = streamTemp2.Detach();
      return S_OK;
    }
    referenceBuf->Buf.Alloc(item.HeaderSize + item.Size);
    memcpy(referenceBuf->Buf, item.Header, item.HeaderSize);
    if (item.Size != 0)
      memcpy(referenceBuf->Buf + item.HeaderSize, _buf + offset, item.Size);
  }
  inStreamSpec->Init(referenceBuf);

  *stream = streamTemp.Detach();
  return S_OK;
  COM_TRY_END
}

Z7_COM7F_IMF(CHandler::AllowTail(Int32 allowTail))
{
  _allowTail = IntToBool(allowTail);
  return S_OK;
}

static const Byte k_Signature[] = { 'M', 'Z' };

REGISTER_ARC_I(
  "PE", "exe dll sys", NULL, 0xDD,
  k_Signature,
  0,
  NArcInfoFlags::kPreArc,
  IsArc_Pe)

}

namespace NCoff {

API_FUNC_static_IsArc IsArc_Coff(const Byte *p, size_t size)
{
  if (size < NPe::kCoffHeaderSize)
    return k_IsArc_Res_NEED_MORE;
  NPe::CHeader header;
  if (!header.ParseCoff(p))
    return k_IsArc_Res_NO;
  return k_IsArc_Res_YES;
}
}

/*
static const Byte k_Signature[] =
{
    2, 0x4C, 0x01, // x86
    2, 0x64, 0x86, // x64
    2, 0x64, 0xAA  // ARM64
};
REGISTER_ARC_I_CLS(
*/

REGISTER_ARC_I_CLS_NO_SIG(
  NPe::CHandler(true),
  "COFF", "obj", NULL, 0xC6,
  // k_Signature,
  0,
  // NArcInfoFlags::kMultiSignature |
  NArcInfoFlags::kStartOpen,
  IsArc_Coff)
}


namespace NTe {

// Terse Executable (TE) image

struct CDataDir
{
  UInt32 Va;
  UInt32 Size;

  void Parse(const Byte *p)
  {
    G32(0, Va);
    G32(4, Size);
  }
};

static const UInt32 kHeaderSize = 40;

static bool FindValue(const CUInt32PCharPair *pairs, unsigned num, UInt32 value)
{
  for (unsigned i = 0; i < num; i++)
    if (pairs[i].Value == value)
      return true;
  return false;
}

#define MY_FIND_VALUE(pairs, val) FindValue(pairs, Z7_ARRAY_SIZE(pairs), val)
#define MY_FIND_VALUE_2(strings, val) (val < Z7_ARRAY_SIZE(strings) && strings[val])
 
static const UInt32 kNumSection_MAX = 32;

struct CHeader
{
  UInt16 Machine;
  Byte NumSections;
  Byte SubSystem;
  UInt16 StrippedSize;
  /*
  UInt32 AddressOfEntryPoint;
  UInt32 BaseOfCode;
  UInt64 ImageBase;
  */
  CDataDir DataDir[2]; // base relocation and debug directory

  bool ConvertPa(UInt32 &pa) const
  {
    if (pa < StrippedSize)
      return false;
    pa = pa - StrippedSize + kHeaderSize;
    return true;
  }
  bool Parse(const Byte *p);
};

bool CHeader::Parse(const Byte *p)
{
  NumSections = p[4];
  if (NumSections > kNumSection_MAX)
    return false;
  SubSystem = p[5];
  G16(2, Machine);
  G16(6, StrippedSize);
  /*
  G32(8, AddressOfEntryPoint);
  G32(12, BaseOfCode);
  G64(16, ImageBase);
  */
  for (int i = 0; i < 2; i++)
  {
    CDataDir &dd = DataDir[i];
    dd.Parse(p + 24 + i * 8);
    if (dd.Size >= ((UInt32)1 << 28))
      return false;
  }
  return
      MY_FIND_VALUE(NPe::g_MachinePairs, Machine) &&
      MY_FIND_VALUE_2(NPe::g_SubSystems, SubSystem);
}

API_FUNC_static_IsArc IsArc_Te(const Byte *p, size_t size)
{
  if (size < 2)
    return k_IsArc_Res_NEED_MORE;
  if (p[0] != 'V' || p[1] != 'Z')
    return k_IsArc_Res_NO;
  if (size < kHeaderSize)
    return k_IsArc_Res_NEED_MORE;
  
  CHeader h;
  if (!h.Parse(p))
    return k_IsArc_Res_NO;
  return k_IsArc_Res_YES;
}
}


struct CSection
{
  Byte Name[NPe::kNameSize];

  UInt32 VSize;
  UInt32 Va;
  UInt32 PSize;
  UInt32 Pa;
  UInt32 Flags;
  // UInt16 NumRelocs;

  void Parse(const Byte *p)
  {
    memcpy(Name, p, NPe::kNameSize);
    G32(8, VSize);
    G32(12, Va);
    G32(16, PSize);
    G32(20, Pa);
    // G32(p + 32, NumRelocs);
    G32(36, Flags);
  }

  bool Check() const
  {
    return
        Pa <= ((UInt32)1 << 30) &&
        PSize <= ((UInt32)1 << 30);
  }

  void UpdateTotalSize(UInt32 &totalSize)
  {
    UInt32 t = Pa + PSize;
    if (t > totalSize)
      totalSize = t;
  }
};


Z7_CLASS_IMP_CHandler_IInArchive_2(
  IInArchiveGetStream,
  IArchiveAllowTail
)
  CRecordVector<CSection> _items;
  CMyComPtr<IInStream> _stream;
  UInt32 _totalSize;
  bool _allowTail;
  CHeader _h;
  
  HRESULT Open2(IInStream *stream);
public:
  CHandler(): _allowTail(false) {}
};

static const Byte kProps[] =
{
  kpidPath,
  kpidSize,
  kpidVirtualSize,
  kpidCharacts,
  kpidOffset,
  kpidVa
};

enum
{
  kpidSubSystem = kpidUserDefined
  // , kpidImageBase
};

static const CStatProp kArcProps[] =
{
  // { NULL, kpidHeadersSize, VT_UI4 },
  { NULL, kpidCpu, VT_BSTR},
  { "Subsystem", kpidSubSystem, VT_BSTR },
  // { "Image Base", kpidImageBase, VT_UI8 }
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps_WITH_NAME

Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  switch (propID)
  {
    case kpidPhySize: prop = _totalSize; break;
    case kpidCpu: PAIR_TO_PROP(NPe::g_MachinePairs, _h.Machine, prop); break;
    case kpidSubSystem: TYPE_TO_PROP(NPe::g_SubSystems, _h.SubSystem, prop); break;
    /*
    case kpidImageBase: prop = _h.ImageBase; break;
    case kpidAddressOfEntryPoint: prop = _h.AddressOfEntryPoint; break;
    case kpidBaseOfCode: prop = _h.BaseOfCode; break;
    */
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  {
    const CSection &item = _items[index];
    switch (propID)
    {
      case kpidPath:
      {
        AString name;
        NPe::GetName(item.Name, name);
        prop = MultiByteToUnicodeString(name);
        break;
      }
      case kpidSize:
      case kpidPackSize: prop = (UInt64)item.PSize; break;
      case kpidVirtualSize: prop = (UInt64)item.VSize; break;
      case kpidOffset: prop = item.Pa; break;
      case kpidVa: prop = item.Va; break;
      case kpidCharacts: FLAGS_TO_PROP(NPe::g_SectFlags, item.Flags, prop); break;
    }
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

HRESULT CHandler::Open2(IInStream *stream)
{
  Byte h[kHeaderSize];
  RINOK(ReadStream_FALSE(stream, h, kHeaderSize))
  if (h[0] != 'V' || h[1] != 'Z')
    return S_FALSE;
  if (!_h.Parse(h))
    return S_FALSE;

  UInt32 headerSize = NPe::kSectionSize * (UInt32)_h.NumSections;
  CByteArr buf(headerSize);
  RINOK(ReadStream_FALSE(stream, buf, headerSize))
  headerSize += kHeaderSize;

  _totalSize = headerSize;
  _items.ClearAndReserve(_h.NumSections);
  for (UInt32 i = 0; i < _h.NumSections; i++)
  {
    CSection sect;
    sect.Parse(buf + i * NPe::kSectionSize);
    if (!_h.ConvertPa(sect.Pa))
      return S_FALSE;
    if (sect.Pa < headerSize)
      return S_FALSE;
    if (!sect.Check())
      return S_FALSE;
    _items.AddInReserved(sect);
    sect.UpdateTotalSize(_totalSize);
  }

  if (!_allowTail)
  {
    UInt64 fileSize;
    RINOK(InStream_GetSize_SeekToEnd(stream, fileSize))
    if (fileSize > _totalSize)
      return S_FALSE;
  }
  
  return S_OK;
}

Z7_COM7F_IMF(CHandler::Open(IInStream *inStream,
    const UInt64 * /* maxCheckStartPosition */,
    IArchiveOpenCallback * /* openArchiveCallback */))
{
  COM_TRY_BEGIN
  Close();
  try
  {
    if (Open2(inStream) != S_OK)
      return S_FALSE;
    _stream = inStream;
  }
  catch(...) { return S_FALSE; }
  return S_OK;
  COM_TRY_END
}

Z7_COM7F_IMF(CHandler::Close())
{
  _totalSize = 0;
  _stream.Release();
  _items.Clear();
  return S_OK;
}

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

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

  UInt64 currentTotalSize = 0;
  
  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(_stream);

  for (i = 0; i < numItems; i++)
  {
    lps->InSize = lps->OutSize = currentTotalSize;
    RINOK(lps->SetCur())
    CMyComPtr<ISequentialOutStream> realOutStream;
    const Int32 askMode = testMode ?
        NExtract::NAskMode::kTest :
        NExtract::NAskMode::kExtract;
    const UInt32 index = allFilesMode ? i : indices[i];
    const CSection &item = _items[index];
    RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
    currentTotalSize += item.PSize;
    
    if (!testMode && !realOutStream)
      continue;
    RINOK(extractCallback->PrepareOperation(askMode))
    int res = NExtract::NOperationResult::kDataError;

    RINOK(InStream_SeekSet(_stream, item.Pa))
    streamSpec->Init(item.PSize);
    RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress))
    if (copyCoderSpec->TotalSize == item.PSize)
      res = NExtract::NOperationResult::kOK;

    realOutStream.Release();
    RINOK(extractCallback->SetOperationResult(res))
  }
  return S_OK;
  COM_TRY_END
}

Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
{
  COM_TRY_BEGIN
  const CSection &item = _items[index];
  return CreateLimitedInStream(_stream, item.Pa, item.PSize, stream);
  COM_TRY_END
}

Z7_COM7F_IMF(CHandler::AllowTail(Int32 allowTail))
{
  _allowTail = IntToBool(allowTail);
  return S_OK;
}

static const Byte k_Signature[] = { 'V', 'Z' };

REGISTER_ARC_I(
  "TE", "te", NULL, 0xCF,
  k_Signature,
  0,
  NArcInfoFlags::kPreArc,
  IsArc_Te)

}
}
