/** *****************************************************************************
 * This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *	 Peter Smith
 ****************************************************************************** */
/** ****************************************************************************
 * This is a copy of https://github.com/kichik/pecoff4j/blob/2137804a7c1f1aa5f4272a9623bad452f7aab0ad/java/src/org/boris/pecoff4j/io/PEParser.java#L1
 * Added a logger and more forgiving error handling while reading files per https://github.com/jeremylong/DependencyCheck/issues/2601
 ***************************************************************************** */
package org.owasp.dependencycheck.utils;

import org.boris.pecoff4j.*;
import org.boris.pecoff4j.constant.ImageDataDirectoryType;
import org.boris.pecoff4j.util.IntMap;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.boris.pecoff4j.io.ByteArrayDataReader;
import org.boris.pecoff4j.io.DataEntry;
import org.boris.pecoff4j.io.DataReader;
import org.boris.pecoff4j.io.IDataReader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PEParser {

    /**
     * Logger
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(PEParser.class);

    public static PE parse(InputStream is) throws IOException {
        return read(new DataReader(is));
    }

    public static PE parse(String filename) throws IOException {
        return parse(new File(filename));
    }

    public static PE parse(File file) throws IOException {
        return read(new DataReader(new FileInputStream(file)), file);
    }

    public static PE read(IDataReader dr) throws IOException {
        return read(dr, null);
    }

    public static PE read(IDataReader dr, File file) throws IOException {
        PE pe = new PE();
        pe.setDosHeader(readDos(dr));

        // Check if we have an old file type
        if (pe.getDosHeader().getAddressOfNewExeHeader() == 0
                || pe.getDosHeader().getAddressOfNewExeHeader() > 8192) {
            return pe;
        }

        pe.setStub(readStub(pe.getDosHeader(), dr));
        pe.setSignature(readSignature(dr));

        // Check signature to ensure we have a pe/coff file
        if (!pe.getSignature().isValid()) {
            return pe;
        }

        pe.setCoffHeader(readCOFF(dr));
        pe.setOptionalHeader(readOptional(dr));
        pe.setSectionTable(readSectionHeaders(pe, dr));

        pe.set64(pe.getOptionalHeader().isPE32plus());

        // Now read the rest of the file
        DataEntry entry = null;
        while ((entry = findNextEntry(pe, dr.getPosition())) != null) {
            if (entry.isSection) {
                readSection(pe, entry, dr);
            } else if (entry.isDebugRawData) {
                try {
                    readDebugRawData(pe, entry, dr);
                } catch (java.io.EOFException eofEx) {
                    if (file != null) {
                        LOGGER.debug("Error reading debug raw data: " + file.getPath(), eofEx);
                    }
                }

            } else {
                try {
                    readImageData(pe, entry, dr);
                } catch (java.io.EOFException eofEx) {
                    if (file != null) {
                        LOGGER.debug("Error reading image data: " + file.getPath(), eofEx);
                    }
                }

            }
        }

        // Read any trailing data
        try {
            byte[] tb = dr.readAll();
            if (tb.length > 0) {
                pe.getImageData().setTrailingData(tb);
            }
        } catch (java.io.EOFException eofEx) {
            if (file != null) {
                LOGGER.debug("Error reading trailing data: " + file.getPath(), eofEx);
            }
        }

        return pe;
    }

    public static DOSHeader readDos(IDataReader dr) throws IOException {
        DOSHeader dh = new DOSHeader();
        dh.setMagic(dr.readWord());
        dh.setUsedBytesInLastPage(dr.readWord());
        dh.setFileSizeInPages(dr.readWord());
        dh.setNumRelocationItems(dr.readWord());
        dh.setHeaderSizeInParagraphs(dr.readWord());
        dh.setMinExtraParagraphs(dr.readWord());
        dh.setMaxExtraParagraphs(dr.readWord());
        dh.setInitialSS(dr.readWord());
        dh.setInitialSP(dr.readWord());
        dh.setChecksum(dr.readWord());
        dh.setInitialIP(dr.readWord());
        dh.setInitialRelativeCS(dr.readWord());
        dh.setAddressOfRelocationTable(dr.readWord());
        dh.setOverlayNumber(dr.readWord());
        int[] reserved = new int[4];
        for (int i = 0; i < reserved.length; i++) {
            reserved[i] = dr.readWord();
        }
        dh.setReserved(reserved);
        dh.setOemId(dr.readWord());
        dh.setOemInfo(dr.readWord());
        int[] reserved2 = new int[10];
        for (int i = 0; i < reserved2.length; i++) {
            reserved2[i] = dr.readWord();
        }
        dh.setReserved2(reserved2);
        dh.setAddressOfNewExeHeader(dr.readDoubleWord());

        // calc stub size
        int stubSize = dh.getFileSizeInPages() * 512
                - (512 - dh.getUsedBytesInLastPage());
        if (stubSize > dh.getAddressOfNewExeHeader()) {
            stubSize = dh.getAddressOfNewExeHeader();
        }
        stubSize -= dh.getHeaderSizeInParagraphs() * 16;
        dh.setStubSize(stubSize);

        return dh;
    }

    public static DOSStub readStub(DOSHeader header, IDataReader dr)
            throws IOException {
        DOSStub ds = new DOSStub();
        int pos = dr.getPosition();
        int add = header.getAddressOfNewExeHeader();
        byte[] stub = new byte[add - pos];
        dr.read(stub);
        ds.setStub(stub);
        return ds;
    }

    public static PESignature readSignature(IDataReader dr) throws IOException {
        PESignature ps = new PESignature();
        byte[] signature = new byte[4];
        dr.read(signature);
        ps.setSignature(signature);
        return ps;
    }

    public static COFFHeader readCOFF(IDataReader dr) throws IOException {
        COFFHeader h = new COFFHeader();
        h.setMachine(dr.readWord());
        h.setNumberOfSections(dr.readWord());
        h.setTimeDateStamp(dr.readDoubleWord());
        h.setPointerToSymbolTable(dr.readDoubleWord());
        h.setNumberOfSymbols(dr.readDoubleWord());
        h.setSizeOfOptionalHeader(dr.readWord());
        h.setCharacteristics(dr.readWord());
        return h;
    }

    public static OptionalHeader readOptional(IDataReader dr)
            throws IOException {
        OptionalHeader oh = new OptionalHeader();
        oh.setMagic(dr.readWord());
        boolean is64 = oh.isPE32plus();
        oh.setMajorLinkerVersion(dr.readByte());
        oh.setMinorLinkerVersion(dr.readByte());
        oh.setSizeOfCode(dr.readDoubleWord());
        oh.setSizeOfInitializedData(dr.readDoubleWord());
        oh.setSizeOfUninitializedData(dr.readDoubleWord());
        oh.setAddressOfEntryPoint(dr.readDoubleWord());
        oh.setBaseOfCode(dr.readDoubleWord());

        if (!is64) {
            oh.setBaseOfData(dr.readDoubleWord());
        }

        // NT additional fields.
        oh.setImageBase(is64 ? dr.readLong() : dr.readDoubleWord());
        oh.setSectionAlignment(dr.readDoubleWord());
        oh.setFileAlignment(dr.readDoubleWord());
        oh.setMajorOperatingSystemVersion(dr.readWord());
        oh.setMinorOperatingSystemVersion(dr.readWord());
        oh.setMajorImageVersion(dr.readWord());
        oh.setMinorImageVersion(dr.readWord());
        oh.setMajorSubsystemVersion(dr.readWord());
        oh.setMinorSubsystemVersion(dr.readWord());
        oh.setWin32VersionValue(dr.readDoubleWord());
        oh.setSizeOfImage(dr.readDoubleWord());
        oh.setSizeOfHeaders(dr.readDoubleWord());
        oh.setCheckSum(dr.readDoubleWord());
        oh.setSubsystem(dr.readWord());
        oh.setDllCharacteristics(dr.readWord());
        oh.setSizeOfStackReserve(is64 ? dr.readLong() : dr.readDoubleWord());
        oh.setSizeOfStackCommit(is64 ? dr.readLong() : dr.readDoubleWord());
        oh.setSizeOfHeapReserve(is64 ? dr.readLong() : dr.readDoubleWord());
        oh.setSizeOfHeapCommit(is64 ? dr.readLong() : dr.readDoubleWord());
        oh.setLoaderFlags(dr.readDoubleWord());
        oh.setNumberOfRvaAndSizes(dr.readDoubleWord());

        // Data directories
        ImageDataDirectory[] dds = new ImageDataDirectory[16];
        for (int i = 0; i < dds.length; i++) {
            dds[i] = readImageDD(dr);
        }
        oh.setDataDirectories(dds);

        return oh;
    }

    public static ImageDataDirectory readImageDD(IDataReader dr)
            throws IOException {
        ImageDataDirectory idd = new ImageDataDirectory();
        idd.setVirtualAddress(dr.readDoubleWord());
        idd.setSize(dr.readDoubleWord());
        return idd;
    }

    public static SectionTable readSectionHeaders(PE pe, IDataReader dr)
            throws IOException {
        SectionTable st = new SectionTable();
        int ns = pe.getCoffHeader().getNumberOfSections();
        for (int i = 0; i < ns; i++) {
            st.add(readSectionHeader(dr));
        }

        SectionHeader[] sorted = st.getHeadersPointerSorted();
        int[] virtualAddress = new int[sorted.length];
        int[] pointerToRawData = new int[sorted.length];
        for (int i = 0; i < sorted.length; i++) {
            virtualAddress[i] = sorted[i].getVirtualAddress();
            pointerToRawData[i] = sorted[i].getPointerToRawData();
        }

        st.setRvaConverter(new RVAConverter(virtualAddress, pointerToRawData));
        return st;
    }

    public static SectionHeader readSectionHeader(IDataReader dr)
            throws IOException {
        SectionHeader sh = new SectionHeader();
        sh.setName(dr.readUtf(8));
        sh.setVirtualSize(dr.readDoubleWord());
        sh.setVirtualAddress(dr.readDoubleWord());
        sh.setSizeOfRawData(dr.readDoubleWord());
        sh.setPointerToRawData(dr.readDoubleWord());
        sh.setPointerToRelocations(dr.readDoubleWord());
        sh.setPointerToLineNumbers(dr.readDoubleWord());
        sh.setNumberOfRelocations(dr.readWord());
        sh.setNumberOfLineNumbers(dr.readWord());
        sh.setCharacteristics(dr.readDoubleWord());
        return sh;
    }

    public static DataEntry findNextEntry(PE pe, int pos) {
        DataEntry de = new DataEntry();

        // Check sections first
        int ns = pe.getCoffHeader().getNumberOfSections();
        for (int i = 0; i < ns; i++) {
            SectionHeader sh = pe.getSectionTable().getHeader(i);
            if (sh.getSizeOfRawData() > 0
                    && sh.getPointerToRawData() >= pos
                    && (de.pointer == 0 || sh.getPointerToRawData() < de.pointer)) {
                de.pointer = sh.getPointerToRawData();
                de.index = i;
                de.isSection = true;
            }
        }

        // Now check image data directories
        RVAConverter rvc = pe.getSectionTable().getRVAConverter();
        int dc = pe.getOptionalHeader().getDataDirectoryCount();
        for (int i = 0; i < dc; i++) {
            ImageDataDirectory idd = pe.getOptionalHeader().getDataDirectory(i);
            if (idd.getSize() > 0) {
                int prd = idd.getVirtualAddress();
                // Assume certificate live outside section ?
                if (i != ImageDataDirectoryType.CERTIFICATE_TABLE
                        && isInsideSection(pe, idd)) {
                    prd = rvc.convertVirtualAddressToRawDataPointer(idd
                            .getVirtualAddress());
                }
                if (prd >= pos && (de.pointer == 0 || prd < de.pointer)) {
                    de.pointer = prd;
                    de.index = i;
                    de.isSection = false;
                }
            }
        }

        // Check debug
        ImageData id = pe.getImageData();
        DebugDirectory dd = null;
        if (id != null) {
            dd = id.getDebug();
        }
        if (dd != null) {
            int prd = dd.getPointerToRawData();
            if (prd >= pos && (de.pointer == 0 || prd < de.pointer)) {
                de.pointer = prd;
                de.index = -1;
                de.isDebugRawData = true;
                de.isSection = false;
                de.baseAddress = prd;
            }
        }

        if (de.pointer == 0) {
            return null;
        }

        return de;
    }

    private static boolean isInsideSection(PE pe, ImageDataDirectory idd) {
        int prd = idd.getVirtualAddress();
        int pex = prd + idd.getSize();
        SectionTable st = pe.getSectionTable();
        int ns = st.getNumberOfSections();
        for (int i = 0; i < ns; i++) {
            SectionHeader sh = st.getHeader(i);
            int vad = sh.getVirtualAddress();
            int vex = vad + sh.getVirtualSize();
            if (prd >= vad && prd < vex && pex <= vex) {
                return true;
            }
        }
        return false;
    }

    private static void readImageData(PE pe, DataEntry entry, IDataReader dr)
            throws IOException {

        // Read any preamble data
        ImageData id = pe.getImageData();
        byte[] pa = readPreambleData(entry.pointer, dr);
        if (pa != null) {
            id.put(entry.index, pa);
        }

        // Read the image data
        ImageDataDirectory idd = pe.getOptionalHeader().getDataDirectory(
                entry.index);
        byte[] b = new byte[idd.getSize()];
        dr.read(b);

        switch (entry.index) {
            case ImageDataDirectoryType.EXPORT_TABLE:
                id.setExportTable(readExportDirectory(b));
                break;
            case ImageDataDirectoryType.IMPORT_TABLE:
                id.setImportTable(readImportDirectory(b, entry.baseAddress));
                break;
            case ImageDataDirectoryType.RESOURCE_TABLE:
                id.setResourceTable(readResourceDirectory(b, entry.baseAddress));
                break;
            case ImageDataDirectoryType.EXCEPTION_TABLE:
                id.setExceptionTable(b);
                break;
            case ImageDataDirectoryType.CERTIFICATE_TABLE:
                id.setCertificateTable(readAttributeCertificateTable(b));
                break;
            case ImageDataDirectoryType.BASE_RELOCATION_TABLE:
                id.setBaseRelocationTable(b);
                break;
            case ImageDataDirectoryType.DEBUG:
                id.setDebug(readDebugDirectory(b));
                break;
            case ImageDataDirectoryType.ARCHITECTURE:
                id.setArchitecture(b);
                break;
            case ImageDataDirectoryType.GLOBAL_PTR:
                id.setGlobalPtr(b);
                break;
            case ImageDataDirectoryType.TLS_TABLE:
                id.setTlsTable(b);
                break;
            case ImageDataDirectoryType.LOAD_CONFIG_TABLE:
                id.setLoadConfigTable(readLoadConfigDirectory(pe, b));
                break;
            case ImageDataDirectoryType.BOUND_IMPORT:
                id.setBoundImports(readBoundImportDirectoryTable(b));
                break;
            case ImageDataDirectoryType.IAT:
                id.setIat(b);
                break;
            case ImageDataDirectoryType.DELAY_IMPORT_DESCRIPTOR:
                id.setDelayImportDescriptor(b);
                break;
            case ImageDataDirectoryType.CLR_RUNTIME_HEADER:
                id.setClrRuntimeHeader(b);
                break;
            case ImageDataDirectoryType.RESERVED:
                id.setReserved(b);
                break;
            default:
                break;
        }
    }

    private static byte[] readPreambleData(int pointer, IDataReader dr)
            throws IOException {
        if (pointer > dr.getPosition()) {
            byte[] pa = new byte[pointer - dr.getPosition()];
            dr.read(pa);
            boolean zeroes = true;
            for (byte b : pa) {
                if (b != 0) {
                    zeroes = false;
                    break;
                }
            }
            if (!zeroes) {
                return pa;
            }
        }

        return null;
    }

    private static void readDebugRawData(PE pe, DataEntry entry, IDataReader dr)
            throws IOException {
        // Read any preamble data
        ImageData id = pe.getImageData();
        byte[] pa = readPreambleData(entry.pointer, dr);
        if (pa != null) {
            id.setDebugRawDataPreamble(pa);
        }
        DebugDirectory dd = id.getDebug();
        byte[] b = new byte[dd.getSizeOfData()];
        dr.read(b);
        id.setDebugRawData(b);
    }

    private static void readSection(PE pe, DataEntry entry, IDataReader dr)
            throws IOException {
        SectionTable st = pe.getSectionTable();
        SectionHeader sh = st.getHeader(entry.index);
        SectionData sd = new SectionData();

        // Read any preamble - store if non-zero
        byte[] pa = readPreambleData(sh.getPointerToRawData(), dr);
        if (pa != null) {
            sd.setPreamble(pa);
        }

        // Read in the raw data block
        dr.jumpTo(sh.getPointerToRawData());
        byte[] b = new byte[sh.getSizeOfRawData()];
        dr.read(b);
        sd.setData(b);
        st.put(entry.index, sd);

        // Check for an image directory within this section
        int ddc = pe.getOptionalHeader().getDataDirectoryCount();
        for (int i = 0; i < ddc; i++) {
            if (i == ImageDataDirectoryType.CERTIFICATE_TABLE) {
                continue;
            }
            ImageDataDirectory idd = pe.getOptionalHeader().getDataDirectory(i);
            if (idd.getSize() > 0) {
                int vad = sh.getVirtualAddress();
                int vex = vad + sh.getVirtualSize();
                int dad = idd.getVirtualAddress();
                if (dad >= vad && dad < vex) {
                    int off = dad - vad;
                    IDataReader idr = new ByteArrayDataReader(b, off,
                            idd.getSize());
                    DataEntry de = new DataEntry(i, 0);
                    de.baseAddress = sh.getVirtualAddress();
                    readImageData(pe, de, idr);
                }
            }
        }
    }

    private static BoundImportDirectoryTable readBoundImportDirectoryTable(
            byte[] b) throws IOException {
        DataReader dr = new DataReader(b);
        BoundImportDirectoryTable bidt = new BoundImportDirectoryTable();
        List<BoundImport> imports = new ArrayList<>();
        BoundImport bi = null;
        while ((bi = readBoundImport(dr)) != null) {
            bidt.add(bi);
            imports.add(bi);
        }
        imports.sort((o1, o2) -> o1.getOffsetToModuleName() - o2.getOffsetToModuleName());
        IntMap names = new IntMap();
        for (BoundImport anImport : imports) {
            bi = anImport;
            int offset = bi.getOffsetToModuleName();
            String n = (String) names.get(offset);
            if (n == null) {
                dr.jumpTo(offset);
                n = dr.readUtf();
                names.put(offset, n);
            }
            bi.setModuleName(n);
        }
        return bidt;
    }

    private static BoundImport readBoundImport(IDataReader dr)
            throws IOException {
        BoundImport bi = new BoundImport();
        bi.setTimestamp(dr.readDoubleWord());
        bi.setOffsetToModuleName(dr.readWord());
        bi.setNumberOfModuleForwarderRefs(dr.readWord());

        if (bi.getTimestamp() == 0 && bi.getOffsetToModuleName() == 0
                && bi.getNumberOfModuleForwarderRefs() == 0) {
            return null;
        }

        return bi;
    }

    public static ImportDirectory readImportDirectory(byte[] b, int baseAddress)
            throws IOException {
        DataReader dr = new DataReader(b);
        ImportDirectory id = new ImportDirectory();
        ImportDirectoryEntry ide = null;
        while ((ide = readImportDirectoryEntry(dr)) != null) {
            id.add(ide);
        }

        /*
		 * FIXME - name table refer to data outside image directory for (int i =
		 * 0; i < id.size(); i++) { ImportDirectoryEntry e = id.getEntry(i);
		 * dr.jumpTo(e.getNameRVA() - baseAddress); String name = dr.readUtf();
		 * dr.jumpTo(e.getImportLookupTableRVA() - baseAddress);
		 * ImportDirectoryTable nt = readImportDirectoryTable(dr, baseAddress);
		 * dr.jumpTo(e.getImportAddressTableRVA() - baseAddress);
		 * ImportDirectoryTable at = null; // readImportDirectoryTable(dr, //
		 * baseAddress); id.add(name, nt, at); }
         */
        return id;
    }

    public static ImportDirectoryEntry readImportDirectoryEntry(IDataReader dr)
            throws IOException {
        ImportDirectoryEntry id = new ImportDirectoryEntry();
        id.setImportLookupTableRVA(dr.readDoubleWord());
        id.setTimeDateStamp(dr.readDoubleWord());
        id.setForwarderChain(dr.readDoubleWord());
        id.setNameRVA(dr.readDoubleWord());
        id.setImportAddressTableRVA(dr.readDoubleWord());

        // The last entry is null
        if (id.getImportLookupTableRVA() == 0) {
            return null;
        }

        return id;
    }

    public static ImportDirectoryTable readImportDirectoryTable(IDataReader dr,
            int baseAddress) throws IOException {
        ImportDirectoryTable idt = new ImportDirectoryTable();
        ImportEntry ie = null;
        while ((ie = readImportEntry(dr)) != null) {
            idt.add(ie);
        }

        for (int i = 0; i < idt.size(); i++) {
            ImportEntry iee = idt.getEntry(i);
            if ((iee.getVal() & 0x80000000) != 0) {
                iee.setOrdinal(iee.getVal() & 0x7fffffff);
            } else {
                dr.jumpTo(iee.getVal() - baseAddress);
                dr.readWord(); // FIXME this is an index into the export table
                iee.setName(dr.readUtf());
            }
        }
        return idt;
    }

    public static ImportEntry readImportEntry(IDataReader dr)
            throws IOException {
        ImportEntry ie = new ImportEntry();
        ie.setVal(dr.readDoubleWord());
        if (ie.getVal() == 0) {
            return null;
        }

        return ie;
    }

    public static ExportDirectory readExportDirectory(byte[] b)
            throws IOException {
        DataReader dr = new DataReader(b);
        ExportDirectory edt = new ExportDirectory();
        edt.set(b);
        edt.setExportFlags(dr.readDoubleWord());
        edt.setTimeDateStamp(dr.readDoubleWord());
        edt.setMajorVersion(dr.readWord());
        edt.setMinorVersion(dr.readWord());
        edt.setNameRVA(dr.readDoubleWord());
        edt.setOrdinalBase(dr.readDoubleWord());
        edt.setAddressTableEntries(dr.readDoubleWord());
        edt.setNumberOfNamePointers(dr.readDoubleWord());
        edt.setExportAddressTableRVA(dr.readDoubleWord());
        edt.setNamePointerRVA(dr.readDoubleWord());
        edt.setOrdinalTableRVA(dr.readDoubleWord());
        return edt;
    }

    public static LoadConfigDirectory readLoadConfigDirectory(PE pe, byte[] b)
            throws IOException {
        DataReader dr = new DataReader(b);
        LoadConfigDirectory lcd = new LoadConfigDirectory();
        lcd.set(b);
        lcd.setSize(dr.readDoubleWord());
        lcd.setTimeDateStamp(dr.readDoubleWord());
        lcd.setMajorVersion(dr.readWord());
        lcd.setMinorVersion(dr.readWord());
        lcd.setGlobalFlagsClear(dr.readDoubleWord());
        lcd.setGlobalFlagsSet(dr.readDoubleWord());
        lcd.setCriticalSectionDefaultTimeout(dr.readDoubleWord());
        lcd.setDeCommitFreeBlockThreshold(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        lcd.setDeCommitTotalFreeThreshold(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        lcd.setLockPrefixTable(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        lcd.setMaximumAllocationSize(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        lcd.setVirtualMemoryThreshold(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        lcd.setProcessAffinityMask(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        lcd.setProcessHeapFlags(dr.readDoubleWord());
        lcd.setCsdVersion(dr.readWord());
        lcd.setReserved(dr.readWord());
        lcd.setEditList(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        if (dr.hasMore()) // optional
        {
            lcd.setSecurityCookie(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        }
        if (dr.hasMore()) // optional
        {
            lcd.setSeHandlerTable(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        }
        if (dr.hasMore()) // optional
        {
            lcd.setSeHandlerCount(pe.is64() ? dr.readLong() : dr.readDoubleWord());
        }

        return lcd;
    }

    public static DebugDirectory readDebugDirectory(byte[] b)
            throws IOException {
        return readDebugDirectory(b, new DataReader(b));
    }

    public static DebugDirectory readDebugDirectory(byte[] b, IDataReader dr)
            throws IOException {
        DebugDirectory dd = new DebugDirectory();
        dd.set(b);
        dd.setCharacteristics(dr.readDoubleWord());
        dd.setTimeDateStamp(dr.readDoubleWord());
        dd.setMajorVersion(dr.readWord());
        dd.setMajorVersion(dr.readWord());
        dd.setType(dr.readDoubleWord());
        dd.setSizeOfData(dr.readDoubleWord());
        dd.setAddressOfRawData(dr.readDoubleWord());
        dd.setPointerToRawData(dr.readDoubleWord());
        return dd;
    }

    private static ResourceDirectory readResourceDirectory(byte[] b,
            int baseAddress) throws IOException {
        IDataReader dr = new ByteArrayDataReader(b);
        return readResourceDirectory(dr, baseAddress);
    }

    private static ResourceDirectory readResourceDirectory(IDataReader dr,
            int baseAddress) throws IOException {
        ResourceDirectory d = new ResourceDirectory();
        d.setTable(readResourceDirectoryTable(dr));
        int ne = d.getTable().getNumNameEntries()
                + d.getTable().getNumIdEntries();
        for (int i = 0; i < ne; i++) {
            d.add(readResourceEntry(dr, baseAddress));
        }

        return d;
    }

    private static ResourceEntry readResourceEntry(IDataReader dr,
            int baseAddress) throws IOException {
        ResourceEntry re = new ResourceEntry();
        int id = dr.readDoubleWord();
        int offset = dr.readDoubleWord();
        re.setOffset(offset);
        int pos = dr.getPosition();
        if ((id & 0x80000000) != 0) {
            dr.jumpTo(id & 0x7fffffff);
            re.setName(dr.readUnicode(dr.readWord()));
        } else {
            re.setId(id);
        }
        if ((offset & 0x80000000) != 0) {
            dr.jumpTo(offset & 0x7fffffff);
            re.setDirectory(readResourceDirectory(dr, baseAddress));
        } else {
            dr.jumpTo(offset);
            int rva = dr.readDoubleWord();
            int size = dr.readDoubleWord();
            int cp = dr.readDoubleWord();
            int res = dr.readDoubleWord();
            re.setDataRVA(rva);
            re.setCodePage(cp);
            re.setReserved(res);
            dr.jumpTo(rva - baseAddress);
            byte[] b = new byte[size];
            dr.read(b);
            re.setData(b);
        }
        dr.jumpTo(pos);
        return re;
    }

    private static ResourceDirectoryTable readResourceDirectoryTable(
            IDataReader dr) throws IOException {
        ResourceDirectoryTable t = new ResourceDirectoryTable();
        t.setCharacteristics(dr.readDoubleWord());
        t.setTimeDateStamp(dr.readDoubleWord());
        t.setMajorVersion(dr.readWord());
        t.setMinVersion(dr.readWord());
        t.setNumNameEntries(dr.readWord());
        t.setNumIdEntries(dr.readWord());

        return t;
    }

    public static AttributeCertificateTable readAttributeCertificateTable(byte[] b)
            throws IOException {
        return readAttributeCertificateTable(b, new DataReader(b));
    }

    public static AttributeCertificateTable readAttributeCertificateTable(byte[] b, IDataReader dr)
            throws IOException {
        AttributeCertificateTable dd = new AttributeCertificateTable();
        dd.set(b);
        dd.setLength(dr.readDoubleWord());
        dd.setRevision(dr.readWord());
        dd.setCertificateType(dr.readWord());
        byte[] certificate = new byte[dd.getLength() - 8];
        dr.read(certificate);
        dd.setCertificate(certificate);
        return dd;
    }

}
