using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lomont.Utils;
// TODO - 
// 1. abstract out reading/writing to allow in memory work
// 2. 
// 3. rewrite - make all entries Properties with same format as NT headers so after parsing all is laid out nicely

namespace Lomont.Binary.FileFormat
    {
    /// <summary>
    /// Represent a PE file format. Used to parse 32 and 64 bit PE files on disk
    /// </summary>
    public class PEFormat : Messager
        {
        /// <summary>
        /// Keep a copy of the file image
        /// </summary>
        List<byte> fileImage;

        /// <summary>
        /// The unpacked image, like what is in memory
        /// </summary>
        List<byte> unpackedImage;

        /// Most of the code follows the article http://msdn.microsoft.com/en-us/magazine/cc301805.aspx
        /// and http://msdn.microsoft.com/en-us/magazine/cc301808.aspx.
        /// and the internals of the WinNT.h file

        /// <summary>
        /// Represent the section headers in the file
        /// </summary>
        public class ImageSectionHeader
            {
            /// <summary>
            /// Name of the header, at most 8 characters.
            /// Common sections are
            /// .text  = code
            /// .idata = import data and functions. All DLL calls from .text patched through here.
            /// .edata = export data and functions.
            /// .data  = initialized data section.
            /// .bss   = uninitialized global and static variables
            /// .crt   = C-Runtime variables
            /// .rsrc  = all resources for the module.
            /// .reloc = base table relocations
            /// .debug = debug information (older, now unused?)
            /// .rdata
            /// .debug$S
            /// .debug$T
            /// .drective
            /// </summary>
            public string Name { get; private set; }
            /// <summary>
            /// The total size when loaded into memory
            /// </summary>
            public uint VirtualSize { get; private set; }
            /// <summary>
            /// Address relative to base when loaded into memory
            /// </summary>
            public uint VirtualAddress { get; private set; }
            /// <summary>
            /// The size on disk of the section
            /// </summary>
            public uint SizeOfRawData { get; private set; }
            /// <summary>
            /// The file pointer to the first page of the section
            /// </summary>
            public uint PointerToRawData { get; private set; }
            /// <summary>
            /// The relocation pointers. Set to 0 for executable images
            /// </summary>
            public uint PointerToRelocations { get; private set; }
            /// <summary>
            /// Line number entries for COFF images
            /// </summary>
            public uint PointerToLineNumbers { get; private set; }
            /// <summary>
            /// Set to 0 for executable images
            /// </summary>
            public ushort NumberOfRelocations { get; private set; }
            /// <summary>
            /// Number of line numbers in the COFF image. Set to 0 for executables.
            /// </summary>
            public ushort NumberOfLineNumbers { get; private set; }
            /// <summary>
            /// Flags describing the image
            /// </summary>
            public SectionCharacteristics Characteristics { get; private set; }

            /// <summary>
            /// Parse data into this object.
            /// </summary>
            /// <param name="image"></param>
            /// <param name="offset">The offset into the image to start parse</param>
            public void Parse(List<byte> image, ref int offset)
                {
                Name = BitUtils.ReadASCII(image, ref offset, 8);
                VirtualSize = BitUtils.Read4(image, ref offset);
                VirtualAddress = BitUtils.Read4(image, ref offset);
                SizeOfRawData = BitUtils.Read4(image, ref offset);
                PointerToRawData = BitUtils.Read4(image, ref offset);
                PointerToRelocations = BitUtils.Read4(image, ref offset);
                PointerToLineNumbers = BitUtils.Read4(image, ref offset);
                NumberOfRelocations = BitUtils.Read2(image, ref offset);
                NumberOfLineNumbers = BitUtils.Read2(image, ref offset);
                Characteristics = (SectionCharacteristics)BitUtils.Read4(image, ref offset);
                }
            }

        /// <summary>
        /// The section headers in the image
        /// </summary>
        public List<ImageSectionHeader> SectionHeaders { get; private set; }

        /// <summary>
        /// Bit depth of image file
        /// </summary>
        public enum BitDepths
            {
            Unknown,
            Depth32,
            Depth64
            }
        /// <summary>
        /// is this 32 or 64 bit image?
        /// </summary>
        public BitDepths BitDepth { get; private set; }

        /// <summary>
        /// The offset from load of the entry point.
        /// </summary>
        public uint EntryPointRVA { get { return OptionalHeader.AddressOfEntryPoint; } }

        /// <summary>
        /// A RelativeVirtualAddress (RVA) represents the offset from the
        /// image base once loaded in memory.
        /// TODO use throughout?
        /// </summary>
        //public class RelativeVirtualAddress

        /// <summary>
        /// Offset in file where sections start, past all the header stuff.
        /// </summary>
        long SectionFileOffset = 0;

        /// <summary>
        /// Parse the PEFormat with the given file image.
        /// This only parses the intro headers, allowing checking a small amount
        /// of the file to see if it seems likely to be a PE file. 
        /// Call <see cref="Parse"/> to parse the entire file.
        /// Return true if successful, else false.
        /// Call Message events while parsing for info.
        /// </summary>
        /// <param name="image"></param>
        public bool ParseSmall(List<byte> image)
            {
            fileImage = image; // keep local copy

            int index = 0;
            ushort sig = BitUtils.Read2(image, ref index);
            if (sig != DOSSignature)
                {
                OnError("File signature 0x{0:X4}, not 0x{1:X4}='MZ'", sig, DOSSignature);
                return false;
                }
            index = 30 * 2; // 30 WORDs (29 more) into the header is the 32 bit offset to the IMAGE_NT_HEADERS, usually 0x200
            ulong NTStart = BitUtils.Read4(image, ref index);
            if (NTStart > (ulong)(image.Count - 4))
                {
                OnError("NTStart not valid");
                return false;
                }
            OnInfo("IMAGE_NT_HEADERS start 0x{0:X8}", NTStart);
            index = (int)NTStart;

            ushort numberOfSections;
            if (!ReadHeaders(ref index, out numberOfSections))
                return false; // failed
            OnInfo("Number of sections {0}", numberOfSections);
            return true;
            }


        /// <summary>
        /// Parse the PEFormat with the given file image.
        /// Return true if successful, else false.
        /// Call Message events while parsing for info.
        /// </summary>
        /// <param name="image"></param>
        public bool Parse(List<byte> image)
            {
            fileImage = image; // keep local copy

            int index = 0;
            ushort sig = BitUtils.Read2(image, ref index);
            if (sig != DOSSignature)
                {
                OnError("File signature 0x{0:X4}, not 0x{1:X4}='MZ'", sig, DOSSignature);
                return false;
                }
            index = 30 * 2; // 30 WORDs (29 more) into the header is the 32 bit offset to the IMAGE_NT_HEADERS, usually 0x200
            ulong NTStart = BitUtils.Read4(image, ref index);
            if (NTStart > (ulong)(image.Count - 4))
                {
                OnError("NTStart not valid");
                return false;
                }
            OnInfo("IMAGE_NT_HEADERS start 0x{0:X8}", NTStart);
            index = (int)NTStart;

            ushort numberOfSections;
            if (!ReadHeaders(ref index, out numberOfSections))
                return false; // failed
            OnInfo("Number of sections {0}", numberOfSections);

            SectionFileOffset = index;

            if (!ReadSections(ref index, numberOfSections))
                return false; // failed

            if (!UnpackSections())
                return false; // failed

            // get exports and prepare info
            if (!ParseExports())
                return false; // failed

            // get imports and prepare info
            if (!ParseImports(image))
                return false; // failed

            return true;
            }

        /// <summary>
        /// Get the total size of all segments marked as code
        /// </summary>
        /// <returns></returns>
        public ulong CodeSegmentSizeTotal()
            {
            ulong total = 0;

            var flag = SectionCharacteristics.IMAGE_SCN_CNT_CODE;
            foreach (var s in SectionHeaders)
                {
                if ((s.Characteristics & flag) == flag)
                    total += s.SizeOfRawData;
                }
            return total;
            }

        /// <summary>
        /// Unpack the packed PE file into a memory image.
        /// </summary>
        bool UnpackSections()
            {
            // algorithm. 
            // 1. Allocate memory for image size from header
            // 2. Place header in first, round up size to SectionAlignment from header
            // 3. for each section:
            //    A. If section not mapped, continue
            //    B. Copy in section, use RAW data size from section definition
            //    C. Pad to VirtualSize in section definition
            //    D. Pad to SectionAlignment

            // helper function. Copy from file image [start, end), 
            Action<uint,uint,uint> Copy = (start, end, virtualSize) =>
            {
                // copy
                uint i = start;
                for (; i < end; ++i)
                    unpackedImage.Add(fileImage[(int)i]);
                // expand to virtual size
                while (i++ < virtualSize)
                    unpackedImage.Add(0);
                // expand to section size
                while ((unpackedImage.Count % OptionalHeader.SectionAlignment) != 0)
                    unpackedImage.Add(0);
            };

            // 1. Create space
            unpackedImage = new List<byte>();
            //for (int i = 0; i < OptionalHeader.SizeOfImage; ++i)
            //    unpackedImage.Add(0);

            // copy in header
            Copy(0, OptionalHeader.SizeOfHeaders, OptionalHeader.SectionAlignment);

            // copy in sections
            foreach (var section in SectionHeaders)
                {
                Copy(
                    section.PointerToRawData,
                    section.PointerToRawData + section.SizeOfRawData,
                    section.VirtualSize);
                }
            return true;
            }

        /// <summary>
        /// Represent IMAGE_EXPORT_DIRECTORY from WinNT.h.
        /// One of these exists for the entire file, listing exports.
        /// </summary>
        public class ImageExportDirectory
            {
            public uint Characteristics { get; set; }
            public uint TimeStamp { get; set; }
            public ushort MajorVersion { get; set; }
            public ushort MinorVersion { get; set; }
            /// <summary>
            /// RVA of name of DLL
            /// </summary>
            public uint NameRVA { get; set; }
            /// <summary>
            /// Name of DLL, pulled from RVA
            /// </summary>
            public string Name { get; set; }
            /// <summary>
            /// Starting number for ordinals for this image
            /// Usually set to 1.
            /// </summary>
            public uint Base { get; set; }
            /// <summary>
            /// number of entries in the Export Address Table (EAT)
            /// </summary>
            public uint NumberOfFunctions { get; set; }
            /// <summary>
            /// Number of names in name pointer table
            /// </summary>
            public uint NumberOfNames { get; set; }
            /// <summary>
            /// Array of RVA of function addresses
            /// </summary>
            public uint AddressOfFunctions { get; set; }
            /// <summary>
            /// RVA of names of functions, null terminated ASCII, sorted in ascending ASCII order
            /// </summary>
            public uint AddressOfNames { get; set; }
            /// <summary>
            /// RVA of ordinals
            /// </summary>
            public uint AddressOfNameOrdinals { get; set; }
            }

        /// <summary>
        /// Names and symbols exported from this PE file
        /// </summary>
        public ImageExportDirectory ExportDirectory { get; set; }

        /// <summary>
        /// Represent an exported function from the PE file
        /// </summary>
        public class ExportEntry
            {
            /// <summary>
            /// RVA of the Entry point
            /// </summary>
            public uint AddressRVA { get; set; }
            /// <summary>
            /// The name of the exported symbol
            /// </summary>
            public string Name { get; set; }
            /// <summary>
            /// The ordinal of the export.
            /// </summary>
            public uint Ordinal { get; set; }
            }

        /// <summary>
        /// The exported symbols from this PE file
        /// </summary>
        public List<ExportEntry> Exports { get; set; }

        /// <summary>
        /// Parse the exports. 
        /// Assumes the ExportDirectory has been filled in.
        /// </summary>
        /// <param name="image"></param>
        bool ParseExports()
            {
            // create structures
            ExportDirectory = new ImageExportDirectory();
            Exports = new List<ExportEntry>();

            int offset = (int)ExportDirectoryEntry.VirtualAddress; // RVA of item?
            if (offset == 0)
                return true; // no exports to find
            var e = ExportDirectory;
            // read fields
            e.Characteristics = BitUtils.Read4(unpackedImage, ref offset);
            e.TimeStamp = BitUtils.Read4(unpackedImage, ref offset);
            e.MajorVersion = BitUtils.Read2(unpackedImage, ref offset);
            e.MinorVersion = BitUtils.Read2(unpackedImage, ref offset);
            e.NameRVA = BitUtils.Read4(unpackedImage, ref offset);
            int nameOfs = (int)e.NameRVA;
            e.Name = BitUtils.ReadNullTerminatedASCII(unpackedImage, ref nameOfs);
            e.Base = BitUtils.Read4(unpackedImage, ref offset);
            e.NumberOfFunctions = BitUtils.Read4(unpackedImage, ref offset);
            e.NumberOfNames = BitUtils.Read4(unpackedImage, ref offset);
            e.AddressOfFunctions = BitUtils.Read4(unpackedImage, ref offset);
            e.AddressOfNames = BitUtils.Read4(unpackedImage, ref offset);
            e.AddressOfNameOrdinals = BitUtils.Read4(unpackedImage, ref offset);

            // now parse them
            for (int i = 0; i < e.NumberOfNames; ++i)
                {
                ExportEntry exp = new ExportEntry();
                // get name of export (alphabetical)
                int name = (int)e.AddressOfNames + 4 * i;
                int nameRva = (int)BitUtils.Read4(unpackedImage, ref name);
                exp.Name = BitUtils.ReadNullTerminatedASCII(unpackedImage, ref nameRva);

                // get corresponding ordinal
                int ordRva = (int)e.AddressOfNameOrdinals + 2 * i;
                exp.Ordinal = BitUtils.Read2(unpackedImage, ref ordRva) + e.Base;

                // ordinal gives the offset to an address
                int rva = (int)(e.AddressOfFunctions + (exp.Ordinal - e.Base) * 4);
                exp.AddressRVA = BitUtils.Read4(unpackedImage, ref rva);
                Exports.Add(exp);
                }

            // finally, sort on ordinal
            Exports.Sort((a, b) => a.Ordinal.CompareTo(b.Ordinal));

            // todo - tag for EXPORT Forwarding? see article
            return true;
            }



        /// <summary>
        /// Represent the IMAGE_IMPORT_DESCRIPTORS, one for each imported DLL.
        /// </summary>
        public class ImageImportDescriptor
            {
            /// <summary>
            /// Set to 0 for end of list
            /// </summary>
            public uint Characteristics { get; set; }
            /// <summary>
            /// RVA for original unbound IAT
            /// </summary>
            public uint OriginalFirstThunk { get; set; }

            /// <summary>
            /// 0 if not bound, -1 if bound, and real time stamp in
            /// ImageDirectoryEntryBoundImport
            /// </summary>
            public uint TimeStamp { get; set; }

            /// <summary>
            /// -1 if no forwarders
            /// </summary>
            public uint ForwarderChain { get; set; }

            /// <summary>
            /// Name RVA
            /// </summary>
            public uint Name { get; set; }

            /// <summary>
            /// RVA to IAT. If bound, this IAT has actual address. 
            /// </summary>
            public uint FirstThunk { get; set; }
            }


        /// <summary>
        /// Read the Imports table
        /// </summary>
        /// <param name="image"></param>
        /// <returns></returns>
        bool ParseImports(List<byte> image)
            {
            return true;
            }

        /// <summary>
        /// After PE is parsed, this obtains a continuous section of all code
        /// sections and all offsets into the code section that are listed as
        /// entry points through the main PE entry point and through exports.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="entryPoints"></param>
        public void GetCodeAndEntryPoints(out List<byte> data, out List<uint> entryPoints)
            {
            data = new List<byte>();
            entryPoints = new List<uint>();
            // todo - make smaller, cleaner?

            // copy memory image to data array
            foreach (var b in unpackedImage)
                data.Add(b);
            // copy out RVAs
            entryPoints.Add(OptionalHeader.AddressOfEntryPoint);
            foreach (var export in Exports)
                entryPoints.Add(export.AddressRVA);
            } // GetCodeAndEntryPoints

        /// <summary>
        /// Read the IMAGE_SECTION_HEADERS. Sections contain code, data, and other info.
        /// </summary>
        /// <param name="fileImage"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        bool ReadSections(ref int index, ushort numberOfSections)
            {
            SectionHeaders = new List<ImageSectionHeader>();

            // characteristics test
            SectionCharacteristics characteristics = SectionCharacteristics.IMAGE_SCN_CNT_CODE;
            Func<SectionCharacteristics, bool> test = c => (c & characteristics) == c;

            for (int s = 0; s < numberOfSections; ++s)
                {
                ImageSectionHeader header = new ImageSectionHeader();
                header.Parse(fileImage, ref index);
                characteristics = header.Characteristics;
                StringBuilder characteristicString = new StringBuilder();
                if (test(SectionCharacteristics.IMAGE_SCN_CNT_CODE))
                    characteristicString.Append("CODE, ");
                if (test(SectionCharacteristics.IMAGE_SCN_MEM_EXECUTE))
                    characteristicString.Append("EXECUTABLE, ");
                if (test(SectionCharacteristics.IMAGE_SCN_MEM_NOT_PAGED))
                    characteristicString.Append("NOT PAGED, ");
                if (test(SectionCharacteristics.IMAGE_SCN_MEM_NOT_CACHED))
                    characteristicString.Append("NOT CACHED, ");
                if (test(SectionCharacteristics.IMAGE_SCN_MEM_SHARED))
                    characteristicString.Append("SHARED, ");
                if (test(SectionCharacteristics.IMAGE_SCN_MEM_READ))
                    characteristicString.Append("READABLE, ");
                if (test(SectionCharacteristics.IMAGE_SCN_MEM_WRITE))
                    characteristicString.Append("WRITEABLE, ");
                if (test(SectionCharacteristics.IMAGE_SCN_CNT_INITIALIZED_DATA))
                    characteristicString.Append("INITIALIZED DATA, ");
                if (test(SectionCharacteristics.IMAGE_SCN_CNT_UNINITIALIZED_DATA))
                    characteristicString.Append("UNINITIALIZED DATA, ");
                // alignment
                if ((characteristics & SectionCharacteristics.IMAGE_SCN_ALIGN_MASK) != 0)
                    { // has alignment mask
                    SectionCharacteristics [] alignments = {
                        SectionCharacteristics.IMAGE_SCN_ALIGN_1BYTES 	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_2BYTES 	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_4BYTES 	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_8BYTES 	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_16BYTES	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_32BYTES	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_64BYTES	   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_128BYTES    ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_256BYTES    ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_512BYTES    ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_1024BYTES   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_2048BYTES   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_4096BYTES   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_8192BYTES   ,
                        SectionCharacteristics.IMAGE_SCN_ALIGN_MASK
                                 };
                    int alignmentLength = 1;
                    foreach (var alignment in alignments)
                        {

                        if (test(SectionCharacteristics.IMAGE_SCN_ALIGN_1BYTES))
                            characteristicString.Append(String.Format("ALIGN({0})", alignmentLength));
                        alignmentLength <<= 1;
                        }
                    }
                else
                    {
                    characteristicString.Append("ALIGN_DEFAULT(16)");
                    }


                OnMessage("Section {0}: {1} Virtual Size 0x{2:X8}, Virtual Address 0x{3:X8}: \n\tFile Address 0x{4:X8}\n\t {5} ",
                    s + 1, header.Name,
                    header.VirtualSize,
                    header.VirtualAddress,
                    header.PointerToRawData,
                    characteristicString.ToString());
                SectionHeaders.Add(header);
                }
            return true;
            }

        /// <summary>
        /// Holds the IMAGE_OPTIONAL_HEADERxx structure where
        /// xx is 32 or 64.
        /// </summary>
        public class OptionalHeaderXX
            {
            public ushort Magic { get; set; }
            public byte MajorLinkerVersion { get; set; }
            public byte MinorLinkerVersion { get; set; }
            public uint SizeOfCode { get; set; }
            public uint SizeofInitializedData { get; set; }
            public uint SizeOfUninitializedData { get; set; }
            /// <summary>
            /// RVA of entry point
            /// </summary>
            public uint AddressOfEntryPoint { get; set; }
            /// <summary>
            /// File base of code
            /// </summary>
            public uint BaseOfCode { get; set; }

            /// <summary>
            /// Only valid in 32 bit images, not 64
            /// </summary>
            public uint BaseOfData { get; set; }

            /// <summary>
            /// Preferred base address of image. 32 bits in x32, 64 bits in x64.
            /// </summary>
            public ulong ImageBase { get; set; }

            public uint SectionAlignment { get; set; }
            public uint FileAlignment { get; set; }

            public ushort MajorOperatingSystemVersion { get; set; }
            public ushort MinorOperatingSystemVersion { get; set; }
            public ushort MajorImageSystemVersion { get; set; }
            public ushort MinorImageSystemVersion { get; set; }
            public ushort MajorSubsystemVersion { get; set; }
            public ushort MinorSubsystemVersion { get; set; }

            public uint Win32VersionValue { get; set; }

            /// <summary>
            /// Size reserved for loading and unpacking PE file
            /// </summary>
            public uint SizeOfImage { get; set; }
            public uint SizeOfHeaders { get; set; }

            public uint Checksum { get; set; }
            public ushort Subsystem { get; set; }
            public ushort DllCharcteristics { get; set; }

            /// <summary>
            /// Stack reserve. 32 or 64 bit, depending on image bit depth.
            /// </summary>
            public ulong SizeOfStackReserve { get; set; }
            /// <summary>
            /// Stack commit. 32 or 64 bit, depending on image bit depth.
            /// </summary>
            public ulong SizeOfStackCommit { get; set; }
            /// <summary>
            /// Heap reserve. 32 or 64 bit, depending on image bit depth.
            /// </summary>
            public ulong SizeOfHeapReserve { get; set; }
            /// <summary>
            /// Heap commit. 32 or 64 bit, depending on image bit depth.
            /// </summary>
            public ulong SizeOfHeapCommit { get; set; }

            public uint LoaderFlags { get; set; }
            public uint NumberOfRvaAndSizes { get; set; }
            }

        public OptionalHeaderXX OptionalHeader;

        /// <summary>
        /// Read IMAGE_NT_HEADERS, return true if success, else false
        /// Update the start location index.
        /// </summary>
        /// <param name="fileImage"></param>
        /// <param name="index"></param>
        bool ReadHeaders(ref int index, out ushort numberOfSections)
            {
            numberOfSections = 0;
            uint sig = BitUtils.Read4(fileImage, ref index);
            if (sig != PESignature)
                {
                OnError("IMAGE_NT_HEADERS signature 0x{0:X8}, not 0x{1:X8}='PE00'", sig, PESignature);
                return false;
                }
            // read IMAGE_FILE_HEADER, same for 32 and 64 bit.
            ushort machine = BitUtils.Read2(fileImage, ref index);
            if ((machine != MACHINE_I386) && (machine != IMAGE_FILE_MACHINE_AMD64))
                {
                OnError("Image not for 386 or x64 machine");
                return false;
                }
            numberOfSections = BitUtils.Read2(fileImage, ref index);
            index += 3 * 4; // skip timestamp, symbol table pointer, number of symbols


            // optional header bitSize usually 224 for 32 bit, 240 for 64 bit.
            // reads IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 
            ushort sizeOfOptionalHeader = BitUtils.Read2(fileImage, ref index);
            if ((sizeOfOptionalHeader != 224) && (sizeOfOptionalHeader != 240))
                {
                OnError("Unknown IMAGE_OPTIONAL_HEADER size");
                return false;
                }

            // flags
            ImageCharacteristics characteristics = (ImageCharacteristics)BitUtils.Read2(fileImage, ref index);
            // some testing
            Func<ImageCharacteristics,bool> test = e => (characteristics & e) == e;
            // this next test fails on 64-bit DLLs, which we want to allow.
            //if (!test(ImageCharacteristics.IMAGE_FILE_32BIT_MACHINE))
            //{
            //    // note this test is valid for 32 or 64 bit PE files on Intel/Windows
            //    OnError("Image characteristics not 32 bit machine");
            //    return false;
            //}
            bool isDll = test(ImageCharacteristics.IMAGE_FILE_DLL);
            bool isExe = test(ImageCharacteristics.IMAGE_FILE_EXECUTABLE_IMAGE);
            if (!isDll && !isExe) // Note: DLL is also tagged as exe
                {
                OnError("Image is not EXE or DLL");
                return false;
                }
            if (isDll)
                OnInfo("Image is DLL");
            if (isExe)
                OnInfo("Image is EXE");

            // read IMAGE_OPTIONAL_HEADER
            OptionalHeader = new OptionalHeaderXX();
            var hd = OptionalHeader; // alias for cleaner code.

            hd.Magic = BitUtils.Read2(fileImage, ref index);

            // see if 32 bit or 64 bit image
            if (hd.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
                {
                BitDepth = BitDepths.Depth64;
                }
            else if (hd.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
                {
                BitDepth = BitDepths.Depth32;
                }
            else
                {
                OnError("Unknown code type: not 32 or 64 bit.");
                return false;
                }

            // major,minor linker
            hd.MajorLinkerVersion = fileImage[index++];
            hd.MinorLinkerVersion = fileImage[index++];

            // some fields...
            hd.SizeOfCode = BitUtils.Read4(fileImage, ref index);
            hd.SizeOfUninitializedData = BitUtils.Read4(fileImage, ref index);
            hd.SizeofInitializedData = BitUtils.Read4(fileImage, ref index);
            // RVA of first code byte to be executed
            hd.AddressOfEntryPoint = BitUtils.Read4(fileImage, ref index);
            hd.BaseOfCode = BitUtils.Read4(fileImage, ref index);

            if (BitDepth == BitDepths.Depth32)
                {
                hd.BaseOfData = BitUtils.Read4(fileImage, ref index);
                hd.ImageBase = BitUtils.Read4(fileImage, ref index);
                OnMessage("Image base 0x{0:X8}", hd.ImageBase);
                }
            else if (BitDepth == BitDepths.Depth64)
                {
                hd.ImageBase = BitUtils.Read8(fileImage, ref index);
                OnMessage("Image base 0x{0:X16}", hd.ImageBase);
                }

            hd.SectionAlignment = BitUtils.Read4(fileImage, ref index);
            OnMessage("Section Alignment 0x{0:X8}", hd.SectionAlignment);
            hd.FileAlignment = BitUtils.Read4(fileImage, ref index);
            hd.MajorOperatingSystemVersion = BitUtils.Read2(fileImage, ref index);
            hd.MinorOperatingSystemVersion = BitUtils.Read2(fileImage, ref index);
            hd.MajorImageSystemVersion = BitUtils.Read2(fileImage, ref index);
            hd.MinorImageSystemVersion = BitUtils.Read2(fileImage, ref index);
            hd.MajorSubsystemVersion = BitUtils.Read2(fileImage, ref index);
            hd.MinorSubsystemVersion = BitUtils.Read2(fileImage, ref index);
            hd.Win32VersionValue = BitUtils.Read4(fileImage, ref index);

            // get bitSize loader needs to reserve
            hd.SizeOfImage = BitUtils.Read4(fileImage, ref index);
            OnMessage("Image size 0x{0:X8}", hd.SizeOfImage);

            // bitSize of all headers before sections start, rounded up to multiple of file bitSize alignment
            hd.SizeOfHeaders = BitUtils.Read4(fileImage, ref index);
            hd.Checksum = BitUtils.Read4(fileImage, ref index);

            hd.Subsystem = BitUtils.Read2(fileImage, ref index);
            switch (hd.Subsystem)
                {
                case IMAGE_SUBSYSTEM_NATIVE:
                    OnMessage("Native subsystem");
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_CUI:
                    OnMessage("Command line subsystem");
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_GUI:
                    OnMessage("GUI subsystem");
                    break;
                default:
                    OnError("Unsupported subsystem");
                    return false;
                }

            hd.DllCharcteristics = BitUtils.Read2(fileImage, ref index);

            if (BitDepth == BitDepths.Depth32)
                {
                hd.SizeOfStackReserve = BitUtils.Read4(fileImage, ref index);
                hd.SizeOfStackCommit = BitUtils.Read4(fileImage, ref index);
                hd.SizeOfHeapReserve = BitUtils.Read4(fileImage, ref index);
                hd.SizeOfHeapCommit = BitUtils.Read4(fileImage, ref index);
                }
            else
                {
                hd.SizeOfStackReserve = BitUtils.Read8(fileImage, ref index);
                hd.SizeOfStackCommit = BitUtils.Read8(fileImage, ref index);
                hd.SizeOfHeapReserve = BitUtils.Read8(fileImage, ref index);
                hd.SizeOfHeapCommit = BitUtils.Read8(fileImage, ref index);
                }

            hd.LoaderFlags = BitUtils.Read4(fileImage, ref index);
            hd.NumberOfRvaAndSizes = BitUtils.Read4(fileImage, ref index);

            if (hd.NumberOfRvaAndSizes != 16)
                {
                OnError("Weird number of RVA entries {0} not 16", hd.NumberOfRvaAndSizes);
                return false;
                }


            // IMAGE_DATA_DIRECTORY is here
            // Directory Entries
            //IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
            //IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
            //IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
            //IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
            //IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
            //IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
            //IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
            //Unused? IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
            //IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
            //IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
            //IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
            //IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
            //IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
            //IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
            //IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
            //IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

            // read a few data directories, skip a few
            ExportDirectoryEntry = ReadDataDirectory(ref index);
            ImportDirectoryEntry = ReadDataDirectory(ref index);
            index += 8 * 10; // each entry is 8 bytes, skip indices 1-11
            IATDirectory = ReadDataDirectory(ref index); // number 12
            // skip remaining
            index += (int)((hd.NumberOfRvaAndSizes - 13) * 8); // each entry is DWORD VirtualAddress, DWORD Size
            return true;
            }

        /// <summary>
        /// Create a data directory structure, reading from the image.
        /// </summary>
        /// <param name="fileImage"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        DataDirectoryEntry ReadDataDirectory(ref int index)
            {
            DataDirectoryEntry dir = new DataDirectoryEntry();
            dir.VirtualAddress = BitUtils.Read4(fileImage, ref index);
            dir.Size = BitUtils.Read4(fileImage, ref index);
            return dir;
            }

        /// <summary>
        /// Represent an IMAGE_DIRECTORY_ENTRY_xxx
        /// </summary>
        public class DataDirectoryEntry
            {
            /// <summary>
            /// RVA or file offset of the item
            /// </summary>
            public uint VirtualAddress { get; set; }
            /// <summary>
            /// Size of entry.
            /// </summary>
            public uint Size { get; set; }
            }

        /// <summary>
        /// File export directory.
        /// </summary>
        public DataDirectoryEntry ExportDirectoryEntry { get; set; }
        /// <summary>
        /// File import directory.
        /// </summary>
        public DataDirectoryEntry ImportDirectoryEntry { get; set; }

        /// <summary>
        /// Import Address Table (IAT) Directory
        /// </summary>
        public DataDirectoryEntry IATDirectory { get; set; }


        #region Constants
        // most from the Windows header WINNT.H

        const ushort DOSSignature = 0x5A4D; // letters 'MZ' to start every PE file
        const uint PESignature = 0x00004550; // ASCII for PE00
        const ushort MACHINE_I386 = 0x14C; // intel 386 (includes x86-64)
        const ushort MACHINE_IA64 = 0x200; // Intel IA64 (not x86-64!)
        const ushort IMAGE_FILE_MACHINE_AMD64 = 0x8664;  // AMD64 (K8) x64 code
        const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; // is 32 bit code
        const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; // is 64 bit code

        const ushort IMAGE_SUBSYSTEM_NATIVE = 1;   // Image doesn't require a subsystem.
        const ushort IMAGE_SUBSYSTEM_WINDOWS_GUI = 2;   // Image runs in the Windows GUI subsystem.
        const ushort IMAGE_SUBSYSTEM_WINDOWS_CUI = 3;   // Image runs in the Windows character subsystem.



        [Flags]
        enum ImageCharacteristics
            {
            IMAGE_FILE_RELOCS_STRIPPED = 0x0001,  // Relocation info stripped from file.
            IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002,  // File is executable  (i.e. no unresolved external references).
            IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004,  // Line numbers stripped from file.
            IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008,  // Local symbols stripped from file.
            IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010,  // Aggressively trim working set
            IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020,  // App can handle >2gb addresses
            IMAGE_FILE_BYTES_REVERSED_LO = 0x0080,  // Bytes of machine word are reversed.
            IMAGE_FILE_32BIT_MACHINE = 0x0100,  // 32 bit word machine.
            IMAGE_FILE_DEBUG_STRIPPED = 0x0200,  // Debugging info stripped from file in .DBG file
            IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400,  // If Image is on removable media, copy and run from the swap file.
            IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800,  // If Image is on Net, copy and run from the swap file.
            IMAGE_FILE_SYSTEM = 0x1000,  // System File.
            IMAGE_FILE_DLL = 0x2000,  // File is a DLL.
            IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000,  // File should only be run on a UP machine
            IMAGE_FILE_BYTES_REVERSED_HI = 0x8000  // Bytes of machine word are reversed.
            }

        [Flags]
        public enum SectionCharacteristics : uint
            {
            // Reserved.
            IMAGE_SCN_TYPE_NO_PAD = 0x00000008,
            // Section contains code.
            IMAGE_SCN_CNT_CODE = 0x00000020,
            // Section contains initialized data.
            IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040,
            // Section contains uninitialized data.
            IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080,
            // Reserved.
            IMAGE_SCN_LNK_OTHER = 0x00000100,
            // Section contains comments or some other type of information.
            IMAGE_SCN_LNK_INFO = 0x00000200,
            // Section contents will not become part of image.
            IMAGE_SCN_LNK_REMOVE = 0x00000800,
            // Section contents comdat.
            IMAGE_SCN_LNK_COMDAT = 0x00001000,
            // Reset speculative exceptions handling bits in the TLB entries for this section.
            IMAGE_SCN_NO_DEFER_SPEC_EXC = 0x00004000,
            IMAGE_SCN_GPREL = 0x00008000,
            // Section content can be accessed relative to GP
            IMAGE_SCN_MEM_FARDATA = 0x00008000,
            IMAGE_SCN_MEM_PURGEABLE = 0x00020000,
            IMAGE_SCN_MEM_16BIT = 0x00020000,
            IMAGE_SCN_MEM_LOCKED = 0x00040000,
            IMAGE_SCN_MEM_PRELOAD = 0x00080000,
            IMAGE_SCN_ALIGN_1BYTES = 0x00100000,  //
            IMAGE_SCN_ALIGN_2BYTES = 0x00200000,  //
            IMAGE_SCN_ALIGN_4BYTES = 0x00300000,  //
            IMAGE_SCN_ALIGN_8BYTES = 0x00400000,  //
            IMAGE_SCN_ALIGN_16BYTES = 0x00500000,  // Default alignment if no others are specified.
            IMAGE_SCN_ALIGN_32BYTES = 0x00600000,  //
            IMAGE_SCN_ALIGN_64BYTES = 0x00700000,  //
            IMAGE_SCN_ALIGN_128BYTES = 0x00800000,  //
            IMAGE_SCN_ALIGN_256BYTES = 0x00900000,  //
            IMAGE_SCN_ALIGN_512BYTES = 0x00A00000,  //
            IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000,  //
            IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000,  //
            IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000,  //
            IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000,  //
            IMAGE_SCN_ALIGN_MASK = 0x00F00000,

            // Section contains extended relocations.
            IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000,
            // Section can be discarded.
            IMAGE_SCN_MEM_DISCARDABLE = 0x02000000,
            // Section is not cacheable.
            IMAGE_SCN_MEM_NOT_CACHED = 0x04000000,
            // Section is not pageable.
            IMAGE_SCN_MEM_NOT_PAGED = 0x08000000,
            // Section is shareable.
            IMAGE_SCN_MEM_SHARED = 0x10000000,
            // Section is executable.
            IMAGE_SCN_MEM_EXECUTE = 0x20000000,
            // Section is readable.
            IMAGE_SCN_MEM_READ = 0x40000000,
            // Section is writeable.		
            IMAGE_SCN_MEM_WRITE = 0x80000000
            }
        #endregion

        }
    }


