Newer
Older
alicedbg / src / adbg / object / format / pe.d
/// Microsoft Portable Executable format.
///
/// PE32 format for both images (executables) and objects (mscoff object files).
///
/// Sources:
/// - Windows Kits\10\Include\10.0.17763.0\um\winnt.h
/// - Microsoft Corporation, Microsoft Portable Executable and Common Object File Format Specification, Revision 6.0 - February 1999
/// - Microsoft Corporation, Microsoft Portable Executable and Common Object File Format Specification, Revision 8.3 – February 6, 2013
/// - Microsoft Corporation, PE Format, 2019-08-26
/// - https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
module adbg.object.format.pe;

import core.stdc.stdlib;
import adbg.error;
import adbg.object.server;
import adbg.machines : AdbgMachine;
import adbg.utils.uid : UID;
import adbg.utils.bit;

// NOTE: Avoid the Windows base types as they are not defined outside "version (Windows)"
// NOTE: Microsoft loader limits sections to 96 maximum
// NOTE: Load Configuration depends on Linker version, Windows depend on that to load PE32 images

extern (C):

/// Magic number for PE32 object files.
enum MAGIC_PE32 = CHAR32!"PE\0\0";

private enum {
	/// Minimum file size for PE32.
	/// See: https://stackoverflow.com/a/47311684
	MINIMUM_SIZE = 97,
	/// Specifications limit number of sections to 96.
	MAXIMUM_SECTIONS = 96,
}

enum : ushort { // PE_HEADER.Machine, likely all little-endian
	PE_MACHINE_UNKNOWN	= 0,	/// Any machine
	PE_MACHINE_ALPHAOLD	= 0x183,	/// Alpha (old value), unused
	PE_MACHINE_ALPHA	= 0x184,	/// Alpha AXP
	PE_MACHINE_ALPHA64	= 0x284,	/// Alpha AXP 64-bit
	PE_MACHINE_AM33	= 0x1d3,	/// Matsushita AM33
	PE_MACHINE_AMD64	= 0x8664,	/// x86-64
	PE_MACHINE_ARM	= 0x1c0,	/// ARM little endian
	PE_MACHINE_ARMNT	= 0x1c4,	/// arm_a32 (ARMv7+ with thumb2)
	PE_MACHINE_ARM64	= 0xaa64,	/// arm_a64 (AArch64)
	PE_MACHINE_EBC	= 0xebc,	/// EFI Byte-Code
	PE_MACHINE_I386	= 0x14c,	/// x86
	PE_MACHINE_IA64	= 0x200,	/// Itanium
	PE_MACHINE_LOONGARCH32	= 0x6232,	/// LoongArch32
	PE_MACHINE_LOONGARCH64	= 0x6264,	/// LoongArch64
	PE_MACHINE_M32R	= 0x9041,	/// Mitsubishi M32R LSB
	PE_MACHINE_MIPS16	= 0x266,	/// 
	PE_MACHINE_MIPSFPU	= 0x366,	/// 
	PE_MACHINE_MIPSFPU16	= 0x466,	/// 
	PE_MACHINE_POWERPC	= 0x1f0,	/// 
	PE_MACHINE_POWERPCFP	= 0x1f1,	/// 
	PE_MACHINE_R3000	= 0x162,	/// MIPS I
	PE_MACHINE_R4000	= 0x166,	/// MIPS II
	PE_MACHINE_R10000	= 0x168,	/// MIPS III
	PE_MACHINE_RISCV32	= 0x5032,	/// RISC-V (32-bit)
	PE_MACHINE_RISCV64	= 0x5064,	/// RISC-V (64-bit)
	PE_MACHINE_RISCV128	= 0x5128,	/// RISC-V (128-bit)
	PE_MACHINE_SH3	= 0x1a2,	/// SuperH
	PE_MACHINE_SH3DSP	= 0x1a3,	/// SuperH + DSP
	PE_MACHINE_SH4	= 0x1a6,	/// SuperH 4
	PE_MACHINE_SH5	= 0x1a8,	/// SuperH 5
	PE_MACHINE_THUMB	= 0x1c2,	/// arm_t32
	PE_MACHINE_WCEMIPSV2	= 0x169,	/// MIPS WCE
	PE_MACHINE_CHPE_X86	= 0x3a64,	/// ARM64X, source: SystemInformer
	// https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files
	PE_MACHINE_CLR	= 0xC0EE,	/// Pure MSIL. aka COM+ EE?
}

enum : ushort { // PE_HEADER.Characteristics flags
	PE_CHARACTERISTIC_RELOCS_STRIPPED	= 0x0001,
	PE_CHARACTERISTIC_EXECUTABLE_IMAGE	= 0x0002,
	PE_CHARACTERISTIC_LINE_NUMS_STRIPPED	= 0x0004,
	PE_CHARACTERISTIC_LOCAL_SYMS_STRIPPED	= 0x0008,
	PE_CHARACTERISTIC_AGGRESSIVE_WS_TRIM	= 0x0010, // obsolete
	PE_CHARACTERISTIC_LARGE_ADDRESS_AWARE	= 0x0020,
	PE_CHARACTERISTIC_16BIT_MACHINE	= 0x0040,
	PE_CHARACTERISTIC_BYTES_REVERSED_LO	= 0x0080, // obsolete
	PE_CHARACTERISTIC_32BIT_MACHINE	= 0x0100,
	PE_CHARACTERISTIC_DEBUG_STRIPPED	= 0x0200,
	PE_CHARACTERISTIC_REMOVABLE_RUN_FROM_SWAP	= 0x0400,
	PE_CHARACTERISTIC_NET_RUN_FROM_SWAP	= 0x0800,
	PE_CHARACTERISTIC_SYSTEM	= 0x1000,
	PE_CHARACTERISTIC_DLL	= 0x2000,
	PE_CHARACTERISTIC_UP_SYSTEM_ONLY	= 0x4000,
	PE_CHARACTERISTIC_BYTES_REVERSED_HI	= 0x8000 // obsolete
}

enum : ushort { // PE_OPTIONAL_HEADER.DllCharacteristics flags
	PE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA	= 0x0020,
	PE_DLLCHARACTERISTICS_DYNAMIC_BASE	= 0x0040,
	PE_DLLCHARACTERISTICS_FORCE_INTEGRITY	= 0x0080,
	PE_DLLCHARACTERISTICS_NX_COMPAT	= 0x0100,
	PE_DLLCHARACTERISTICS_NO_ISOLATION	= 0x0200,
	PE_DLLCHARACTERISTICS_NO_SEH	= 0x0400,
	PE_DLLCHARACTERISTICS_NO_BIND	= 0x0800,
	PE_DLLCHARACTERISTICS_APPCONTAINER	= 0x1000,
	PE_DLLCHARACTERISTICS_WDM_DRIVER	= 0x2000,
	PE_DLLCHARACTERISTICS_GUARD_CF	= 0x4000,
	PE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE	= 0x8000,
}

/// To be used with SECTION_CHARACTERISTIC_ALIGN fields.
enum PE_SECTION_CHARACTERISTIC_ALIGN_MASK = 0x00F00000;

enum { // PE_SECTION_ENTRY.Characteristics flags
	PE_SECTION_CHARACTERISTIC_TYPE_DSECT	= 0x00000001,	/// Reserved, undocumented
	PE_SECTION_CHARACTERISTIC_TYPE_NOLOAD	= 0x00000002,	/// Reserved, undocumented
	PE_SECTION_CHARACTERISTIC_TYPE_GROUP	= 0x00000004,	/// Reserved, undocumented
	PE_SECTION_CHARACTERISTIC_NO_PAD	= 0x00000008,
	PE_SECTION_CHARACTERISTIC_TYPE_COPY	= 0x00000010,	/// Reserved, undocumented
	PE_SECTION_CHARACTERISTIC_CODE	= 0x00000020,
	PE_SECTION_CHARACTERISTIC_INITIALIZED_DATA	= 0x00000040,
	PE_SECTION_CHARACTERISTIC_UNINITIALIZED_DATA	= 0x00000080,
	PE_SECTION_CHARACTERISTIC_LNK_OTHER	= 0x00000100,	/// Reserved
	PE_SECTION_CHARACTERISTIC_LNK_INFO	= 0x00000200,
	PE_SECTION_CHARACTERISTIC_LNK_REMOVE	= 0x00000800,
	PE_SECTION_CHARACTERISTIC_LNK_COMDAT	= 0x00001000,
	PE_SECTION_CHARACTERISTIC_MEM_PROTECTED	= 0x00004000,	/// Reserved, undocumented
	PE_SECTION_CHARACTERISTIC_GPREL	= 0x00008000,
	PE_SECTION_CHARACTERISTIC_MEM_PURGEABLE	= 0x00010000,	/// Reserved, aka SYSHEAP
	PE_SECTION_CHARACTERISTIC_MEM_16BIT	= 0x00020000,	/// Reserved
	PE_SECTION_CHARACTERISTIC_MEM_LOCKED	= 0x00040000,	/// Reserved
	PE_SECTION_CHARACTERISTIC_PRELOAD	= 0x00080000,	/// Reserved
	PE_SECTION_CHARACTERISTIC_ALIGN_1BYTES	= 0x00100000,
	PE_SECTION_CHARACTERISTIC_ALIGN_2BYTES	= 0x00200000,
	PE_SECTION_CHARACTERISTIC_ALIGN_4BYTES	= 0x00300000,
	PE_SECTION_CHARACTERISTIC_ALIGN_8BYTES	= 0x00400000,
	PE_SECTION_CHARACTERISTIC_ALIGN_16BYTES	= 0x00500000,
	PE_SECTION_CHARACTERISTIC_ALIGN_32BYTES	= 0x00600000,
	PE_SECTION_CHARACTERISTIC_ALIGN_64BYTES	= 0x00700000,
	PE_SECTION_CHARACTERISTIC_ALIGN_128BYTES	= 0x00800000,
	PE_SECTION_CHARACTERISTIC_ALIGN_256BYTES	= 0x00900000,
	PE_SECTION_CHARACTERISTIC_ALIGN_512BYTES	= 0x00A00000,
	PE_SECTION_CHARACTERISTIC_ALIGN_1024BYTES	= 0x00B00000,
	PE_SECTION_CHARACTERISTIC_ALIGN_2048BYTES	= 0x00C00000,
	PE_SECTION_CHARACTERISTIC_ALIGN_4096BYTES	= 0x00D00000,
	PE_SECTION_CHARACTERISTIC_ALIGN_8192BYTES	= 0x00E00000,
	PE_SECTION_CHARACTERISTIC_LNK_NRELOC_OVFL	= 0x01000000,
	PE_SECTION_CHARACTERISTIC_MEM_DISCARDABLE	= 0x02000000,
	PE_SECTION_CHARACTERISTIC_MEM_NOT_CACHED	= 0x04000000,
	PE_SECTION_CHARACTERISTIC_MEM_NOT_PAGED	= 0x08000000,
	PE_SECTION_CHARACTERISTIC_MEM_SHARED	= 0x10000000,
	PE_SECTION_CHARACTERISTIC_MEM_EXECUTE	= 0x20000000,
	PE_SECTION_CHARACTERISTIC_MEM_READ	= 0x40000000,
	PE_SECTION_CHARACTERISTIC_MEM_WRITE	= 0x80000000,
}

enum : ushort { // PE image format/magic
	PE_CLASS_ROM	= 0x0107,	// No longer used? Docs no longer have it
	PE_CLASS_32	= 0x010B,	/// PE32
	PE_CLASS_64	= 0x020B,	/// PE32+
}

enum : ushort { // PE_HEADER
	PE_SUBSYSTEM_NATIVE	= 1,
	PE_SUBSYSTEM_WINDOWS_GUI	= 2,
	PE_SUBSYSTEM_WINDOWS_CUI	= 3,
	PE_SUBSYSTEM_OS2_CUI	= 5,
	PE_SUBSYSTEM_POSIX_CUI	= 7,
	PE_SUBSYSTEM_NATIVE_WINDOWS	= 8,
	PE_SUBSYSTEM_WINDOWS_CE_GUI	= 9,
	PE_SUBSYSTEM_EFI_APPLICATION	= 10,
	PE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER	= 11,
	PE_SUBSYSTEM_EFI_RUNTIME_DRIVER	= 12,
	PE_SUBSYSTEM_EFI_ROM	= 13,
	PE_SUBSYSTEM_XBOX	= 14,
	PE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION	= 16,
	PE_SUBSYSTEM_XBOX_CODE_CATALOG	= 17,
}

/// COFF file header (object and image)
struct pe_header_t { align(1):
	union {
		ubyte[4] Signature;
		uint   Signature32;
	}
	ushort Machine;
	ushort NumberOfSections;
	uint TimeDateStamp; // C time_t
	uint PointerToSymbolTable;
	uint NumberOfSymbols;
	ushort SizeOfOptionalHeader;
	ushort Characteristics;
}
alias PE_HEADER = pe_header_t;

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx
// Image only
struct pe_optional_header_t { align(1):
	ushort Magic; // "Format"
	ubyte  MajorLinkerVersion;
	ubyte  MinorLinkerVersion;
	uint SizeOfCode;
	uint SizeOfInitializedData;
	uint SizeOfUninitializedData;
	uint AddressOfEntryPoint;
	uint BaseOfCode;
	uint BaseOfData;
	uint ImageBase;
	uint SectionAlignment;
	uint FileAlignment;
	ushort MajorOperatingSystemVersion;
	ushort MinorOperatingSystemVersion;
	ushort MajorImageVersion;
	ushort MinorImageVersion;
	ushort MajorSubsystemVersion;
	ushort MinorSubsystemVersion;
	uint Win32VersionValue;
	uint SizeOfImage;
	uint SizeOfHeaders;
	uint CheckSum;
	ushort Subsystem;
	ushort DllCharacteristics;
	uint SizeOfStackReserve;
	uint SizeOfStackCommit;
	uint SizeOfHeapReserve;
	uint SizeOfHeapCommit;
	uint LoaderFlags;	/// Obsolete
	uint NumberOfRvaAndSizes;
}
alias PE_OPTIONAL_HEADER = pe_optional_header_t;

struct pe_optional_header64_t { align(1):
	ushort Magic; // "Format"
	ubyte  MajorLinkerVersion;
	ubyte  MinorLinkerVersion;
	uint SizeOfCode;
	uint SizeOfInitializedData;
	uint SizeOfUninitializedData;
	uint AddressOfEntryPoint;
	uint BaseOfCode;
	ulong ImageBase;
	uint SectionAlignment;
	uint FileAlignment;
	ushort MajorOperatingSystemVersion;
	ushort MinorOperatingSystemVersion;
	ushort MajorImageVersion;
	ushort MinorImageVersion;
	ushort MajorSubsystemVersion;
	ushort MinorSubsystemVersion;
	uint Win32VersionValue;
	uint SizeOfImage;
	uint SizeOfHeaders;
	uint CheckSum;
	ushort Subsystem;
	ushort DllCharacteristics;
	ulong SizeOfStackReserve;
	ulong SizeOfStackCommit;
	ulong SizeOfHeapReserve;
	ulong SizeOfHeapCommit;
	uint LoaderFlags; // Obsolete
	uint NumberOfRvaAndSizes;
}
alias PE_OPTIONAL_HEADER64 = pe_optional_header64_t;

struct pe_optional_headerrom_t {
	ushort Magic;
	ubyte  MajorLinkerVersion;
	ubyte  MinorLinkerVersion;
	uint SizeOfCode;
	uint SizeOfInitializedData;
	uint SizeOfUninitializedData;
	uint AddressOfEntryPoint;
	uint BaseOfCode;
	uint BaseOfData;
	uint BaseOfBss;
	uint GprMask;
	uint[4] CprMask;
	uint GpValue;
}
alias PE_OPTIONAL_HEADERROM = pe_optional_headerrom_t;

struct pe_directory_entry_t { align(1):
	uint rva;	/// Relative Virtual Address
	uint size;	/// Size in bytes
}
alias PE_DIRECTORY_ENTRY = pe_directory_entry_t;

// IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16
// MS recommends checking NumberOfRvaAndSizes but it always been 16
struct pe_image_data_directory_t { align(1):
	pe_directory_entry_t ExportTable;
	pe_directory_entry_t ImportTable;
	pe_directory_entry_t ResourceTable;
	pe_directory_entry_t ExceptionTable;
	pe_directory_entry_t CertificateTable;	// File Pointer (instead of RVA)
	pe_directory_entry_t BaseRelocationTable;
	pe_directory_entry_t DebugDirectory;
	pe_directory_entry_t ArchitectureData;
	pe_directory_entry_t GlobalPtr;
	pe_directory_entry_t TLSTable;
	pe_directory_entry_t LoadConfigurationTable;
	pe_directory_entry_t BoundImportTable;
	pe_directory_entry_t ImportAddressTable;
	pe_directory_entry_t DelayImport;
	pe_directory_entry_t CLRHeader;	// Used to be (or alias to) COM+ Runtime Header
	pe_directory_entry_t Reserved;
}
alias PE_IMAGE_DATA_DIRECTORY = pe_image_data_directory_t;

//
// ANCHOR Directory structures
//

struct pe_export_descriptor_t { align(1):
	uint ExportFlags;
	uint Timestamp;
	ushort MajorVersion;
	ushort MinorVersion;
	uint Name;	/// RVA
	uint OrdinalBase;
	uint AddressTableEntries;	/// Number of export entries
	uint NumberOfNamePointers;	/// Same amount for ordinal
	uint ExportAddressTable;	/// RVA
	uint NamePointer;	/// RVA, "The address of the export name pointer table"
	uint OrdinalTable;	/// RVA
}
alias PE_EXPORT_DESCRIPTOR = pe_export_descriptor_t;

union pe_export_entry_t { align(1):
	uint Export;	/// RVA
	uint Forwarder;	/// RVA
}
alias PE_EXPORT_ENTRY = pe_export_entry_t;

// IMAGE_IMPORT_DESCRIPTOR
struct pe_import_descriptor_t { align(1):
	uint Characteristics; // used in WINNT.H but no longer descriptive
	uint TimeDateStamp; // time_t
	uint ForwarderChain;
	uint Name;
	uint FirstThunk;
}
alias PE_IMPORT_DESCRIPTOR = pe_import_descriptor_t;

/// Import Lookup Table entry structure
struct pe_import_entry32_t { align(1):
	union {
		uint ordinal;	/// Ordinal/Name Flag
		ushort number;	/// Ordinal Number (val[31] is set)
		uint rva;	/// Hint/Name Table RVA (val[31] is clear)
	}
}
alias PE_IMPORT_ENTRY32 = pe_import_entry32_t;

/// Import Lookup Table entry structure
struct pe_import_entry64_t { align(1):
	union {
		ulong ordinal;	/// Ordinal/Name Flag
		ushort number;	/// Ordinal Number (val2[31] is set)
		uint rva;	/// Hint/Name Table RVA (val2[31] is clear)
	}
}
alias PE_IMPORT_ENTRY64 = pe_import_entry64_t;

struct pe_debug_directory_entry_t { align(1):
	uint Characteristics;	/// reserved, must be zero
	uint TimeDateStamp;	/// time and date that the debug data was created
	ushort MajorVersion;	/// The major version number of the debug data format
	ushort MinorVersion;	/// The minor version number of the debug data format
	uint Type;	/// The format of debugging information
	uint SizeOfData;	/// The size of the debug data (not including the debug directory itself)
	uint AddressOfRawData;	/// The address of the debug data relative to the image base
	uint PointerToRawData;	/// The file pointer to the debug data
}
alias PE_DEBUG_DIRECTORY = pe_debug_directory_entry_t;

// Debug Types
enum : uint {
	/// An unknown value that is ignored by all tools
	PE_IMAGE_DEBUG_TYPE_UNKNOWN	= 0,
	/// The COFF debug information (line numbers, symbol table, and string table).
	/// This type of debug information is also pointed to by fields in the file headers.
	PE_IMAGE_DEBUG_TYPE_COFF	= 1,
	/// The Visual C++ debug information
	PE_IMAGE_DEBUG_TYPE_CODEVIEW	= 2,
	/// The frame pointer omission (FPO) information. This information tells the
	/// debugger how to interpret nonstandard stack frames, which use the EBP
	/// register for a purpose other than as a frame pointer.
	PE_IMAGE_DEBUG_TYPE_FPO	= 3,
	/// The location of DBG file.
	PE_IMAGE_DEBUG_TYPE_MISC	= 4,
	/// A copy of .pdata section.
	PE_IMAGE_DEBUG_TYPE_EXCEPTION	= 5,
	/// Reserved.
	PE_IMAGE_DEBUG_TYPE_FIXUP	= 6,
	/// The mapping from an RVA in image to an RVA in source image.
	PE_IMAGE_DEBUG_TYPE_OMAP_TO_SRC	= 7,
	/// The mapping from an RVA in source image to an RVA in image.
	PE_IMAGE_DEBUG_TYPE_OMAP_FROM_SRC	= 8,
	/// Reserved for Borland.
	PE_IMAGE_DEBUG_TYPE_BORLAND	= 9,
	/// Reserved.
	PE_IMAGE_DEBUG_TYPE_RESERVED10	= 10,
	/// Reserved.
	PE_IMAGE_DEBUG_TYPE_CLSID	= 11,
	/// Visual C++ features
	PE_IMAGE_DEBUG_TYPE_VC_FEATURE	= 12,
	/// Profile Guided Optimization.
	/// See: https://devblogs.microsoft.com/cppblog/pogo/
	PE_IMAGE_DEBUG_TYPE_POGO	= 13,
	/// Incremental Link Time Code Generation.
	/// See: https://devblogs.microsoft.com/cppblog/speeding-up-the-incremental-developer-build-scenario/
	PE_IMAGE_DEBUG_TYPE_ILTCG	= 14,
	/// Uses Intel MPX
	PE_IMAGE_DEBUG_TYPE_MPX	= 15,
	/// PE determinism or reproducibility.
	PE_IMAGE_DEBUG_TYPE_REPRO	= 16,
	/// Embedded Portable PDB Debug Directory Entry
	PE_IMAGE_DEBUG_TYPE_EMBEDDED	= 17,
	/// SPGo debug types
	PE_IMAGE_DEBUG_TYPE_SPGO	= 18,
	/// Crypto hash of the content of the symbol file the PE/COFF file was built with.
	PE_IMAGE_DEBUG_TYPE_HASH	= 19,
	/// Extended DLL characteristics bits.
	PE_IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS	= 20,
	/// R2R PerfMap Debug Directory Entry
	PE_IMAGE_DEBUG_TYPE_R2R_PERFMAP	= 21,
}

// Debug entry 2: CodeView/PDB stuff

// Magics for debug structures
enum : uint {
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_LINK510	= CHAR32!"NB02", /// MS LINK 5.10
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_LINK520	= CHAR32!"NB05", /// MS LINK 5.20
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_QUICKC	= CHAR32!"NB07", /// Quick C for Windows 1.0
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_CV400	= CHAR32!"NB08", /// PDB 2.0+ / CodeView 4.00-4.05
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_CV410	= CHAR32!"NB09", /// PDB 2.0+ / CodeView 4.10
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_PDB20PLUS	= CHAR32!"NB10", /// PDB 2.0+ / MS C/C++ PDB 2.0
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_CV500	= CHAR32!"NB11", /// PDB 2.0+ / CodeView 5.0
	PE_IMAGE_DEBUG_MAGIC_CODEVIEW_CV700	= CHAR32!"RSDS", /// PDB 7.0 / CodeView 7.0
	// Mono source has it set as 0x4244504d
	PE_IMAGE_DEBUG_MAGIC_EMBEDDED_PPDB	= CHAR32!"MPDB", /// Portable PDB
	PE_IMAGE_DEBUG_MAGIC_PPDB	= CHAR32!"BSJB", /// Portable PDB
	// Source: SystemInformer, except for full names
	PE_IMAGE_DEBUG_MAGIC_POGO_LTCG	= CHAR32!"LTCG",  /// Link-Time Code Generation
	PE_IMAGE_DEBUG_MAGIC_POGO_PGU	= CHAR32!"PGU\0", /// Profile Guided Update (/LTCG:PGUPDATE)
}

/// PDB 2.0 and above
struct pe_debug_data_codeview_pdb20_t { align(1):
	// Old PE32 doc mentions "NB05" -- CodeView 4.0 or earlier?
	char[4] Signature;	/// Magic: "NB09"/"NB10"/"NB11" bytes
	/// Offset to the start of the actual debug information from the
	/// beginning of the CodeView data. Zero if it's another file.
	uint Offset;
	uint Timestamp;	///
	uint Age;	/// incremented each time the executable is remade by the linker
	char[1] Path;	/// Path to PDB (0-terminated)
}
alias PE_DEBUG_DATA_CODEVIEW_PDB20 = pe_debug_data_codeview_pdb20_t;

/// PDB 7.0
struct pe_debug_data_codeview_pdb70_t { align(1):
	char[4] Signature;	/// Magic: "RSDS" bytes
	UID Guid;	/// GUID of PDB file, matches with PDB file
	uint Age;	/// incremented each time the executable is remade by the linker
	char[1] Path;	/// Path to PDB (0-terminated UTF-8)
}
alias PE_DEBUG_DATA_CODEVIEW_PDB70 = pe_debug_data_codeview_pdb70_t;

// Debug entry 3: The frame pointer omission (FPO) information.

// This information tells the debugger how to interpret nonstandard stack frames,
// which use the EBP register for a purpose other than as a frame pointer. 
enum FRAME_FPO  = 0;
enum FRAME_TRAP = 1;
enum FRAME_TSS  = 2; // i286 Task Switch
struct pe_debug_data_fpo_t { // struct _FPO_DATA
	uint ulOffStart; // offset 1st byte of function code
	uint cbProcSize; // # bytes in function
	uint cdwLocals; // # bytes in locals/4
	ushort cdwParams; // # bytes in params/4
	ushort Flags;
	//WORD cbProlog : 8; // # bytes in prolog
	//WORD cbRegs : 3; // # regs saved
	//WORD fHasSEH : 1; // TRUE if SEH in func
	//WORD fUseBP : 1; // TRUE if EBP has been allocated
	//WORD reserved : 1; // reserved for future use
	//WORD cbFrame : 2; // frame type
}

// Debug entry 4: The location of a DBG file.

struct pe_debug_data_misc_t { align(1):
	union {
		char[4] Signature;	/// 
		uint Signature32;
	}
	uint DataType;	/// Must be 1
	uint Length;	/// Multiple of four; Total length of data block
	bool Unicode;	/// If true, Unicode string
	byte[3] Reserved;
	byte[1] Data;
}
alias PE_DEBUG_DATA_MISC = pe_debug_data_misc_t;

// Debug entry 12: VC Features

/// VC Featured data
struct pe_debug_data_vc_feat_t { align(1):
	uint PreVC11;	/// Pre-VC11
	uint CCpp;	/// C/C++
	uint GS;	/// /GS
	uint SDL;	/// /SDL
	uint GuardN;	/// guardN
}
alias PE_DEBUG_DATA_VC_FEAT = pe_debug_data_vc_feat_t;

// Debug entry 13: POGO

/// POGO Entry containing filename. Should be ending with .PGD (Profile-Guided Database).
struct pe_debug_data_pogo_entry_t {
	uint Magic;
	uint Rva;
	uint Size;
	char[1] Name;
}
alias PE_DEBUG_POGO_ENTRY = pe_debug_data_pogo_entry_t;

// Debug entry 17: Embedded PDB

/// Declares that debugging information is embedded in the PE file at location
/// specified by PointerToRawData.
// Version Major=any, Minor=0x0100 of the data format:
struct pe_debug_data_embedded_t { align(1):
	char[4] Signature;	/// Magic: "MPDB"
	uint UncompressedSize;
	// SizeOfData - 8: PortablePdbImage
	//                 Portable PDB image compressed using Deflate algorithm
	ubyte[1] PortablePdbImage;
}
alias PE_DEBUG_DATA_EMBEDDED = pe_debug_data_embedded_t;

// aka MetadataRootHeader
struct pe_debug_data_ppdb_t {
	/// Magic signature for physical metadata : 0x424A5342.
	// or "BSJB"
	char[4] Signature;
	/// Major version, 1 (ignore on read)
	ushort MajorVersion;
	/// Minor version, 1 (ignore on read)
	ushort MinorVersion;
	/// Reserved, always 0.
	uint Reserved;
	/// Length of version string, multi-byte.
	uint Length;
	/// UTF-8 "Version" string.
	// 4-Byte aligned, maximum 255 (?).
	// Values:
	// - "PDB v1.00" with a value of 12 (.NET 6)
	// - "Standard CLI 2002" (17 chars, so rounded to 20 chars)
	char[1] Version;
}
alias PE_DEBUG_DATA_PPDB = pe_debug_data_ppdb_t;

// After MetadataRootHeader + Version string
struct pe_debug_data_ppdb_flags_t {
	ushort Flags;
	ushort Streams;
}
alias PE_DEBUG_DATA_PPDB_FLAGS = pe_debug_data_ppdb_flags_t;

struct pe_debug_data_ppdb_stream_t {
	uint Offset;
	uint Size;
	char[1] Name;
}
alias PE_DEBUG_DATA_PPDB_STREAM = pe_debug_data_ppdb_stream_t;

// Debug type 21: R2R PerfMap

/// Declares that the image has an associated PerfMap file containing a table
/// mapping symbols to offsets for ready to run compilations.
// Version Major=0x0001, Minor=0x0000 of the entry data format is following:
struct pe_debug_data_r2r_perfmap_t { align(1):
	union {
		char[4] Magic;	/// "R2RM"
		uint Magic32;
	}
	/// Byte sequence uniquely identifying the associated PerfMap.
	UID Signature; // Used to be ubyte[16]
	/// Version number of the PerfMap. Currently only version 1 is supported.
	uint Version;
	/// UTF-8 NUL-terminated path to the associated .r2rmap file.
	char[1] Path;
}
alias PE_DEBUG_DATA_R2R_PERFMAP = pe_debug_data_r2r_perfmap_t;

//
// Load configuration directory
//

struct pe_load_config_code_integrity_t { align(1):
	ushort Flags;	// Flags to indicate if CI information is available, etc.
	ushort Catalog;	// 0xFFFF means not available
	uint CatalogOffset;
	uint Reserved;	// Additional bitmask to be defined later
}
alias PE_LOAD_CONFIG_CODE_INTEGRITY = pe_load_config_code_integrity_t;

/// IMAGE_LOAD_CONFIG_DIRECTORY32
//TODO: Map sizes to WindowsNT versions
//      Or very likely MSVC linker versions
struct pe_load_config_dir32_t { align(1):
	// Windows XP and after
	uint Size; // Doc: Characteristics, header: Size, Windows XP=64
	uint TimeDateStamp; // time_t
	ushort MajorVersion;
	ushort MinorVersion;
	uint GlobalFlagsClear;
	uint GlobalFlagsSet;
	uint CriticalSectionDefaultTimeout;
	uint DeCommitFreeBlockThreshold;
	uint DeCommitTotalBlockThreshold;
	uint LockPrefixTable;
	uint MaximumAllocationSize;
	uint VirtualMemoryThreshold;
	uint ProcessHeapFlags;
	uint ProcessAffinityMask;
	ushort CSDVersion;
	ushort Reserved1;
	uint EditList;
	// Windows 7 and later
	uint SecurityCookie;
	uint SEHandlerTable;
	uint SEHandlerCount;
	uint GuardCFCheckFunctionPointer; // Control Flow
	uint GuardCFDispatchFunctionPointer;
	uint GuardCFFunctionTable;
	uint GuardCFFunctionCount;
	// Windows 8 and later?
	uint GuardFlags;
	PE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity;
	uint GuardAddressTakenIatEntryTable;
	uint GuardAddressTakenIatEntryCount;
	uint GuardLongJumpTargetTable;
	uint GuardLongJumpTargetCount; // Windows 8's limit?
	// Windows 10 and later?
	uint DynamicValueRelocTable;	// VA
	uint CHPEMetadataPointer;
	uint GuardRFFailureRoutine;	// VA
	uint GuardRFFailureRoutineFunctionPointer;	// VA
	uint DynamicValueRelocTableOffset;
	ushort DynamicValueRelocTableSection;
	ushort Reserved2;
	uint GuardRFVerifyStackPointerFunctionPointer;	// VA
	uint HotPatchTableOffset;
	uint Reserved3;
	uint EnclaveConfigurationPointer;	// VA
	uint VolatileMetadataPointer;	// VA
	// 10.0.2261.0
	uint GuardEHContinuationTable;	// VA
	uint GuardEHContinuationCount;
	uint GuardXFGCheckFunctionPointer;	// VA
	uint GuardXFGDispatchFunctionPointer;	// VA
	uint GuardXFGTableDispatchFunctionPointer;	// VA
	uint CastGuardOsDeterminedFailureMode;	// VA
	uint GuardMemcpyFunctionPointer;	// VA
}
alias PE_LOAD_CONFIG_DIR32 = pe_load_config_dir32_t;

/// IMAGE_LOAD_CONFIG_DIRECTORY64
//TODO: Map sizes to WindowsNT versions
//      Or MSVC linker versions
struct pe_load_config_dir64_t { align(1):
	uint Size; // Characteristics
	uint TimeDateStamp; // time_t
	ushort MajorVersion;
	ushort MinorVersion;
	uint GlobalFlagsClear;
	uint GlobalFlagsSet;
	uint CriticalSectionDefaultTimeout;
	ulong DeCommitFreeBlockThreshold;
	ulong DeCommitTotalBlockThreshold;
	ulong LockPrefixTable;
	ulong MaximumAllocationSize;
	ulong VirtualMemoryThreshold;
	ulong ProcessAffinityMask;
	uint ProcessHeapFlags;
	ushort CSDVersion;
	ushort Reserved1;
	ulong EditList;
	// Windows 7 and later
	ulong SecurityCookie;
	ulong SEHandlerTable;
	ulong SEHandlerCount;
	ulong GuardCFCheckFunctionPointer; // Control Flow
	ulong GuardCFDispatchFunctionPointer;
	ulong GuardCFFunctionTable;
	ulong GuardCFFunctionCount;
	uint GuardFlags;
	// Windows 8 and later?
	PE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity;
	ulong GuardAddressTakenIatEntryTable;
	ulong GuardAddressTakenIatEntryCount;
	ulong GuardLongJumpTargetTable;
	ulong GuardLongJumpTargetCount;
	// Windows 10 and later?
	ulong DynamicValueRelocTable;         // VA
	ulong CHPEMetadataPointer;            // VA
	ulong GuardRFFailureRoutine;          // VA
	ulong GuardRFFailureRoutineFunctionPointer; // VA
	uint DynamicValueRelocTableOffset;
	ushort DynamicValueRelocTableSection;
	ushort Reserved2;
	ulong GuardRFVerifyStackPointerFunctionPointer; // VA
	uint HotPatchTableOffset;
	uint Reserved3;
	ulong EnclaveConfigurationPointer;     // VA
	ulong VolatileMetadataPointer;         // VA
	// 10.0.22621.0
	ulong GuardEHContinuationTable;	// VA
	ulong GuardEHContinuationCount;
	ulong GuardXFGCheckFunctionPointer;	// VA
	ulong GuardXFGDispatchFunctionPointer;	// VA
	ulong GuardXFGTableDispatchFunctionPointer;	// VA
	ulong CastGuardOsDeterminedFailureMode;	// VA
	ulong GuardMemcpyFunctionPointer;	// VA
}
alias PE_LOAD_CONFIG_DIR64 = pe_load_config_dir64_t;

struct pe_section_entry_t { align(1):
	/// An 8-byte, null-padded UTF-8 encoded string. If
	/// the string is exactly 8 characters long, there is
	/// no terminating null. For longer names, this field
	/// contains a slash (/) that is followed by an ASCII
	/// representation of a decimal number that is an
	/// offset into the string table. Executable images
	/// do not use a string table and do not support
	/// section names longer than 8 characters. Long
	/// names in object files are truncated if they are
	/// emitted to an executable file.
	char[8] Name;
	/// The total size of the section when loaded into
	/// memory. If this value is greater than
	/// SizeOfRawData, the section is zero-padded. This
	/// field is valid only for executable images and
	/// should be set to zero for object files.
	uint VirtualSize;
	/// For executable images, the address of the first
	/// byte of the section relative to the image base
	/// when the section is loaded into memory. For
	/// object files, this field is the address of the first
	/// byte before relocation is applied; for simplicity,
	/// compilers should set this to zero. Otherwise, it
	/// is an arbitrary value that is subtracted from
	/// offsets during relocation.
	uint VirtualAddress;
	/// The size of the section (for object files) or the
	/// size of the initialized data on disk (for image
	/// files). For executable images, this must be a
	/// multiple of FileAlignment from the optional
	/// header. If this is less than VirtualSize, the
	/// remainder of the section is zero-filled. Because
	/// the SizeOfRawData field is rounded but the
	/// VirtualSize field is not, it is possible for
	/// SizeOfRawData to be greater than VirtualSize as
	/// well. When a section contains only uninitialized
	/// data, this field should be zero.
	uint SizeOfRawData;
	/// The file pointer to the first page of the section
	/// within the COFF file. For executable images, this
	/// must be a multiple of FileAlignment from the
	/// optional header. For object files, the value
	/// should be aligned on a 4-byte boundary for best
	/// performance. When a section contains only
	/// uninitialized data, this field should be zero.
	uint PointerToRawData;
	/// The file pointer to the beginning of relocation
	/// entries for the section. This is set to zero for
	/// executable images or if there are no
	/// relocations.
	uint PointerToRelocations;
	/// The file pointer to the beginning of line-number
	/// entries for the section. This is set to zero if
	/// there are no COFF line numbers. This value
	/// should be zero for an image because COFF
	/// debugging information is deprecated.
	uint PointerToLinenumbers;
	/// The number of relocation entries for the
	/// section. This is set to zero for executable
	/// images.
	ushort NumberOfRelocations;
	/// The number of line-number entries for the
	/// section. This value should be zero for an image
	/// because COFF debugging information is
	/// deprecated.
	ushort NumberOfLinenumbers;
	/// The flags that describe the characteristics of the
	/// section.
	uint Characteristics;
}
alias PE_SECTION_ENTRY = pe_section_entry_t;

private
struct internal_pe_t {
	pe_header_t header;
	union {
		pe_optional_header_t optheader;
		pe_optional_header64_t optheader64;
		pe_optional_headerrom_t optheaderrom;
	}
	pe_image_data_directory_t directory;
	uint locsections;
	pe_section_entry_t *sections;
	bool *r_sections;
	// export directory
	pe_export_descriptor_t *export_directory;
	bool *r_export_entries;
	// import directory
	pe_import_descriptor_t *import_directory; // not allocated
	pe_section_entry_t *import_section; // associated section, not allocated
	void *import_buffer; // section buffer
	//bool *r_import_desc;
	//bool *r_import_entries; // Current only, cleared when selecting other descriptor
	// debug directory
	pe_debug_directory_entry_t *debug_directory; // not allocated
	pe_section_entry_t *debug_section;
	void *debug_buffer; // section buffer
	bool *r_debug_entries;
	// load configuration directory
	union {
		pe_load_config_dir32_t *load32_directory;
		pe_load_config_dir64_t *load64_directory;
	}
	bool r_loaddir;
}

/// (Internal) Called by the server to preload a PE object.
/// Params:
/// 	o = Object instance.
/// 	e_lfanew = Location of PE COFF Header.
/// Returns: Error code.
int adbg_object_pe_load(adbg_object_t *o, uint e_lfanew) {
	o.format = AdbgObject.pe;
	o.internal = calloc(1, internal_pe_t.sizeof);
	if (o.internal == null)
		return adbg_oops(AdbgError.crt);
	if (adbg_object_read_at(o, e_lfanew, o.internal, pe_header_t.sizeof)) {
		free(o.internal);
		o.internal = null;
		return adbg_errno();
	}
	
	pe_header_t *header = cast(pe_header_t*)o.internal;
	with (header)
	if (o.status & AdbgObjectInternalFlags.reversed) {
		Signature32	= adbg_bswap32(Signature32);
		Machine	= adbg_bswap16(Machine);
		NumberOfSections	= adbg_bswap16(NumberOfSections);
		TimeDateStamp	= adbg_bswap32(TimeDateStamp);
		PointerToSymbolTable	= adbg_bswap32(PointerToSymbolTable);
		NumberOfSymbols	= adbg_bswap32(NumberOfSymbols);
		SizeOfOptionalHeader	= adbg_bswap16(SizeOfOptionalHeader);
		Characteristics	= adbg_bswap16(Characteristics);
	}
	
	e_lfanew += pe_header_t.sizeof; // adjust to optional header
	ushort optmagic = void;
	if (adbg_object_read_at(o, e_lfanew, &optmagic, ushort.sizeof)) {
		free(o.internal);
		o.internal = null;
		return adbg_errno();
	}
	
	internal_pe_t* internal = cast(internal_pe_t*)o.internal;
	switch (optmagic) {
	case PE_CLASS_32:
		if (adbg_object_read_at(o, e_lfanew, &internal.optheader, pe_optional_header_t.sizeof)) {
			free(o.internal);
			o.internal = null;
			return adbg_errno();
		}
		
		e_lfanew += pe_optional_header_t.sizeof; // adjust to directory
		if (adbg_object_read_at(o, e_lfanew, &internal.directory, pe_image_data_directory_t.sizeof)) {
			free(o.internal);
			o.internal = null;
			return adbg_errno();
		}
		
		e_lfanew += pe_image_data_directory_t.sizeof; // adjust to sections
		
		if (o.status & AdbgObjectInternalFlags.reversed) with (internal.optheader) {
			SizeOfCode	= adbg_bswap32(SizeOfCode);
			SizeOfInitializedData	= adbg_bswap32(SizeOfInitializedData);
			SizeOfUninitializedData	= adbg_bswap32(SizeOfUninitializedData);
			AddressOfEntryPoint	= adbg_bswap32(AddressOfEntryPoint);
			BaseOfCode	= adbg_bswap32(BaseOfCode);
			BaseOfData	= adbg_bswap32(BaseOfData);
			ImageBase	= adbg_bswap32(ImageBase);
			SectionAlignment	= adbg_bswap32(SectionAlignment);
			FileAlignment	= adbg_bswap32(FileAlignment);
			MajorOperatingSystemVersion	= adbg_bswap16(MajorOperatingSystemVersion);
			MinorOperatingSystemVersion	= adbg_bswap16(MinorOperatingSystemVersion);
			MajorImageVersion	= adbg_bswap16(MajorImageVersion);
			MinorImageVersion	= adbg_bswap16(MinorImageVersion);
			MajorSubsystemVersion	= adbg_bswap16(MajorSubsystemVersion);
			MinorSubsystemVersion	= adbg_bswap16(MinorSubsystemVersion);
			Win32VersionValue	= adbg_bswap32(Win32VersionValue);
			SizeOfImage	= adbg_bswap32(SizeOfImage);
			SizeOfHeaders	= adbg_bswap32(SizeOfHeaders);
			CheckSum	= adbg_bswap32(CheckSum);
			Subsystem	= adbg_bswap16(Subsystem);
			DllCharacteristics	= adbg_bswap16(DllCharacteristics);
			SizeOfStackReserve	= adbg_bswap32(SizeOfStackReserve);
			SizeOfStackCommit	= adbg_bswap32(SizeOfStackCommit);
			SizeOfHeapReserve	= adbg_bswap32(SizeOfHeapReserve);
			SizeOfHeapCommit	= adbg_bswap32(SizeOfHeapCommit);
			LoaderFlags	= adbg_bswap32(LoaderFlags);
			NumberOfRvaAndSizes	= adbg_bswap32(NumberOfRvaAndSizes);
		}
		break;
	case PE_CLASS_64:
		if (adbg_object_read_at(o, e_lfanew, &internal.optheader64, pe_optional_header64_t.sizeof)) {
			free(o.internal);
			o.internal = null;
			return adbg_errno();
		}
		
		e_lfanew += pe_optional_header64_t.sizeof; // adjust to directory
		if (adbg_object_read_at(o, e_lfanew, &internal.directory, pe_image_data_directory_t.sizeof)) {
			free(o.internal);
			o.internal = null;
			return adbg_errno();
		}
		
		e_lfanew += pe_image_data_directory_t.sizeof; // adjust to sections
		
		if (o.status & AdbgObjectInternalFlags.reversed) with (internal.optheader64) {
			SizeOfCode	= adbg_bswap32(SizeOfCode);
			SizeOfInitializedData	= adbg_bswap32(SizeOfInitializedData);
			SizeOfUninitializedData	= adbg_bswap32(SizeOfUninitializedData);
			AddressOfEntryPoint	= adbg_bswap32(AddressOfEntryPoint);
			BaseOfCode	= adbg_bswap32(BaseOfCode);
			ImageBase	= adbg_bswap64(ImageBase);
			SectionAlignment	= adbg_bswap32(SectionAlignment);
			FileAlignment	= adbg_bswap32(FileAlignment);
			MajorOperatingSystemVersion	= adbg_bswap16(MajorOperatingSystemVersion);
			MinorOperatingSystemVersion	= adbg_bswap16(MinorOperatingSystemVersion);
			MajorImageVersion	= adbg_bswap16(MajorImageVersion);
			MinorImageVersion	= adbg_bswap16(MinorImageVersion);
			MajorSubsystemVersion	= adbg_bswap16(MajorSubsystemVersion);
			MinorSubsystemVersion	= adbg_bswap16(MinorSubsystemVersion);
			Win32VersionValue	= adbg_bswap32(Win32VersionValue);
			SizeOfImage	= adbg_bswap32(SizeOfImage);
			SizeOfHeaders	= adbg_bswap32(SizeOfHeaders);
			CheckSum	= adbg_bswap32(CheckSum);
			Subsystem	= adbg_bswap16(Subsystem);
			DllCharacteristics	= adbg_bswap16(DllCharacteristics);
			SizeOfStackReserve	= adbg_bswap64(SizeOfStackReserve);
			SizeOfStackCommit	= adbg_bswap64(SizeOfStackCommit);
			SizeOfHeapReserve	= adbg_bswap64(SizeOfHeapReserve);
			SizeOfHeapCommit	= adbg_bswap64(SizeOfHeapCommit);
			LoaderFlags	= adbg_bswap32(LoaderFlags);
			NumberOfRvaAndSizes	= adbg_bswap32(NumberOfRvaAndSizes);
		}
		break;
	case PE_CLASS_ROM: // NOTE: ROM have no optional header and directories
		if (adbg_object_read_at(o, e_lfanew, &internal.optheaderrom, pe_optional_headerrom_t.sizeof)) {
			free(o.internal);
			o.internal = null;
			return adbg_errno();
		}
		e_lfanew += pe_optional_headerrom_t.sizeof; // adjust to sections, no directories
		
		if (o.status & AdbgObjectInternalFlags.reversed) with (internal.optheaderrom) {
			SizeOfCode	= adbg_bswap32(SizeOfCode);
			SizeOfInitializedData	= adbg_bswap32(SizeOfInitializedData);
			SizeOfUninitializedData	= adbg_bswap32(SizeOfUninitializedData);
			AddressOfEntryPoint	= adbg_bswap32(AddressOfEntryPoint);
			BaseOfCode	= adbg_bswap32(BaseOfCode);
			BaseOfData	= adbg_bswap32(BaseOfData);
			BaseOfBss	= adbg_bswap32(BaseOfBss);
			GprMask	= adbg_bswap32(GprMask);
			CprMask[0]	= adbg_bswap32(CprMask[0]);
			CprMask[1]	= adbg_bswap32(CprMask[1]);
			CprMask[2]	= adbg_bswap32(CprMask[2]);
			CprMask[3]	= adbg_bswap32(CprMask[3]);
			GpValue	= adbg_bswap32(GpValue);
		}
		return 0;
	default:
		return adbg_oops(AdbgError.objectInvalidClass);
	}
	
	internal.locsections = e_lfanew; // updated to point at section headers
	
	// If reversed and it's not a "rom" image, swap dictionary entries
	if (o.status & AdbgObjectInternalFlags.reversed && optmagic != PE_CLASS_ROM) with (internal.directory) {
		ExportTable.rva	= adbg_bswap32(ExportTable.rva);
		ExportTable.size	= adbg_bswap32(ExportTable.size);
		ImportTable.rva	= adbg_bswap32(ImportTable.rva);
		ImportTable.size	= adbg_bswap32(ImportTable.size);
		ResourceTable.rva	= adbg_bswap32(ResourceTable.rva);
		ResourceTable.size	= adbg_bswap32(ResourceTable.size);
		ExceptionTable.rva	= adbg_bswap32(ExceptionTable.rva);
		ExceptionTable.size	= adbg_bswap32(ExceptionTable.size);
		CertificateTable.rva	= adbg_bswap32(CertificateTable.rva);
		CertificateTable.size	= adbg_bswap32(CertificateTable.size);
		BaseRelocationTable.rva	= adbg_bswap32(BaseRelocationTable.rva);
		BaseRelocationTable.size	= adbg_bswap32(BaseRelocationTable.size);
		DebugDirectory.rva	= adbg_bswap32(DebugDirectory.rva);
		DebugDirectory.size	= adbg_bswap32(DebugDirectory.size);
		ArchitectureData.rva	= adbg_bswap32(ArchitectureData.rva);
		ArchitectureData.size	= adbg_bswap32(ArchitectureData.size);
		GlobalPtr.rva	= adbg_bswap32(GlobalPtr.rva);
		GlobalPtr.size	= adbg_bswap32(GlobalPtr.size);
		TLSTable.rva	= adbg_bswap32(TLSTable.rva);
		TLSTable.size	= adbg_bswap32(TLSTable.size);
		LoadConfigurationTable.rva	= adbg_bswap32(LoadConfigurationTable.rva);
		LoadConfigurationTable.size	= adbg_bswap32(LoadConfigurationTable.size);
		BoundImportTable.rva	= adbg_bswap32(BoundImportTable.rva);
		BoundImportTable.size	= adbg_bswap32(BoundImportTable.size);
		ImportAddressTable.rva	= adbg_bswap32(ImportAddressTable.rva);
		ImportAddressTable.size	= adbg_bswap32(ImportAddressTable.size);
		DelayImport.rva	= adbg_bswap32(DelayImport.rva);
		DelayImport.size	= adbg_bswap32(DelayImport.size);
		CLRHeader.rva	= adbg_bswap32(CLRHeader.rva);
		CLRHeader.size	= adbg_bswap32(CLRHeader.size);
		Reserved.rva	= adbg_bswap32(Reserved.rva);
		Reserved.size	= adbg_bswap32(Reserved.size);
	}
	
	return 0;
}

void adbg_object_pe_unload(adbg_object_t *o) {
	if (o == null) return;
	if (o.internal == null) return;
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	
	if (internal.sections) free(internal.sections);
	if (internal.r_sections) free(internal.r_sections);
	
	if (internal.export_directory) free(internal.export_directory);
	if (internal.r_export_entries) free(internal.r_export_entries);
	
	if (internal.import_buffer) free(internal.import_buffer);
	
	if (internal.debug_buffer) free(internal.debug_buffer);
	if (internal.r_debug_entries) free(internal.r_debug_entries);
	
	if (internal.load32_directory) free(internal.load32_directory);
	
	free(o.internal);
}

// NOTE: Mapping directory RVAs to file offsets
//       1. Given the Directory RVA, map it to a section
//       2. Calculate the Section RVA with the Directory RVA

// Given the directory RVA, map it to a section
private
pe_section_entry_t* adbg_object_pe_directory_section(adbg_object_t *o, uint rva) {
	version (Trace) trace("o=%p rva=%#x", o, rva);
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	
	internal_pe_t* internal = cast(internal_pe_t*)o.internal;
	ushort seccnt = internal.header.NumberOfSections;
	for (ushort i; i < seccnt; ++i) {
		// Function sets error
		pe_section_entry_t *section = adbg_object_pe_section(o, i);
		if (section == null) return null;
		
		// If RVA is outside section's VA and range
		with (section) if (rva < VirtualAddress || rva > VirtualAddress + SizeOfRawData)
			continue;
		
		return section;
	}
	
	adbg_oops(AdbgError.unfindable);
	return null;
}

// Given a section and directory RVA, return absolute file offset
private
uint adbg_object_pe_directory_offset_section(pe_section_entry_t *section, uint dirrva) {
	version (Trace) trace("dir_rva=%#x", dirrva);
	if (section == null) return 0;
	with (section) return PointerToRawData + (dirrva - VirtualAddress);
}

// Given a directory RVA, return absolute file offset
private
uint adbg_object_pe_directory_offset(adbg_object_t *o, uint dirrva) {
	version (Trace) trace("dir_rva=%#x", dirrva);
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return 0;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return 0;
	}
	// Function checks null
	return adbg_object_pe_directory_offset_section(
		adbg_object_pe_directory_section(o, dirrva), dirrva);
}

pe_header_t* adbg_object_pe_header(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	return cast(pe_header_t*)o.internal;
}

// void* to force a pointer cast
// NOTE: then shouldn't there be a function to return the type of optional header?
void* adbg_object_pe_optional_header(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	internal_pe_t* internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	return &internal.optheader;
}

pe_image_data_directory_t* adbg_object_pe_directories(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	internal_pe_t* internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	return &internal.directory;
}

pe_section_entry_t* adbg_object_pe_section(adbg_object_t *o, size_t index) {
	version (Trace) trace("o=%p index=%u", o, cast(uint)index);
	
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	if (index >= MAXIMUM_SECTIONS) {
		adbg_oops(AdbgError.indexBounds);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	
	ushort count = internal.header.NumberOfSections;
	if (index >= count || index >= MAXIMUM_SECTIONS) {
		adbg_oops(AdbgError.indexBounds);
		return null;
	}
	
	// Otherwise, load section headers
	if (internal.sections == null) {
		size_t totsize = count * pe_section_entry_t.sizeof;
		internal.sections = cast(pe_section_entry_t*)malloc(totsize);
		if (internal.sections == null) {
			adbg_oops(AdbgError.crt);
			return null;
		}
		if (adbg_object_read_at(o, internal.locsections, internal.sections, totsize)) { // sets error
			free(internal.sections);
			internal.sections = null;
			return null;
		}
		
		// Init swapping stuff if required
		if (o.status & AdbgObjectInternalFlags.reversed) {
			internal.r_sections = cast(bool*)malloc(count);
			if (internal.r_sections == null) {
				adbg_oops(AdbgError.crt);
				free(internal.sections);
				internal.sections = null;
				return null;
			}
		}
	}
	
	pe_section_entry_t *section = &internal.sections[index];
	
	// If needs to be swapped
	if (o.status & AdbgObjectInternalFlags.reversed && internal.r_sections[index] == false) with (section) {
		VirtualSize	= adbg_bswap32(VirtualSize);
		VirtualAddress	= adbg_bswap32(VirtualAddress);
		SizeOfRawData	= adbg_bswap32(SizeOfRawData);
		PointerToRawData	= adbg_bswap32(PointerToRawData);
		PointerToRelocations	= adbg_bswap32(PointerToRelocations);
		PointerToLinenumbers	= adbg_bswap32(PointerToLinenumbers);
		NumberOfRelocations	= adbg_bswap16(NumberOfRelocations);
		NumberOfLinenumbers	= adbg_bswap16(NumberOfLinenumbers);
		Characteristics	= adbg_bswap32(Characteristics);
		internal.r_sections[index] = true;
	}
	
	return section;
}

// TODO:
// - [x] ExportTable: Size includes everything
// - [x] ImportTable: Size only includes descriptor tables
// - [ ] ResourceTable
// - [ ] ExceptionTable
// - [ ] CertificateTable
// - [ ] BaseRelocationTable
// - [x] DebugDirectory
// - [ ] ArchitectureData
// - [ ] GlobalPtr
// - [ ] TLSTable: Size includes everything
// - [ ] LoadConfigurationTable: Size includes everything
// - [ ] BoundImportTable
// - [ ] ImportAddressTable
// - [ ] DelayImport
// - [ ] CLRHeader

//
// Export directory functions
//

// One descriptor table, multiple entries, because one module emits one table.
//   Name -> Name of the module
//   ExportAddressTable -> raw address to RVAs
//     AddressTableEntries for count
//     RVA -> hint + entry
pe_export_descriptor_t* adbg_object_pe_export(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ExportTable.size == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	// If not already loaded
	if (internal.export_directory == null) {
		// Function sets error
		uint offset = adbg_object_pe_directory_offset(o, internal.directory.ExportTable.rva);
		if (offset == 0)
			return null;
		
		// Load exports in memory
		uint size = internal.directory.ExportTable.size;
		internal.export_directory = cast(pe_export_descriptor_t*)malloc(size);
		if (internal.export_directory == null) {
			adbg_oops(AdbgError.crt);
			return null;
		}
		if (adbg_object_read_at(o, offset, internal.export_directory, size)) // sets error
			return null;
		
		// If need to be swapped
		if (o.status & AdbgObjectInternalFlags.reversed) with (internal.export_directory) {
			ExportFlags	= adbg_bswap32(ExportFlags);
			Timestamp	= adbg_bswap32(Timestamp);
			MajorVersion	= adbg_bswap16(MajorVersion);
			MinorVersion	= adbg_bswap16(MinorVersion);
			Name	= adbg_bswap32(Name);
			OrdinalBase	= adbg_bswap32(OrdinalBase);
			AddressTableEntries	= adbg_bswap32(AddressTableEntries);
			NumberOfNamePointers	= adbg_bswap32(NumberOfNamePointers);
			ExportAddressTable	= adbg_bswap32(ExportAddressTable);
			NamePointer	= adbg_bswap32(NamePointer);
			OrdinalTable	= adbg_bswap32(OrdinalTable);
		}
	}
	
	// ExportFlags must be zero
	if (internal.export_directory.ExportFlags != 0) {
		adbg_oops(AdbgError.unavailable);
		free(internal.export_directory);
		internal.export_directory = null;
		return null;
	}
	
	return internal.export_directory;
}

const(char)* adbg_object_pe_export_module_name(adbg_object_t *o, pe_export_descriptor_t *export_) {
	if (o == null || export_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ExportTable.size == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	// directory_exports (offset) - ExportTable.rva + export.Name
	// or try: directory_exports + sizeof(export_descriptor_t) ?
	void* base = cast(void*)export_ -
		internal.directory.ExportTable.rva +
		export_.Name;
	if (adbg_bits_ptrbounds(base, 2, export_, internal.directory.ExportTable.size)) {
		adbg_oops(AdbgError.offsetBounds); // or assertion?
		return null;
	}
	
	return cast(const(char)*)base;
}

pe_export_entry_t* adbg_object_pe_export_entry_name(adbg_object_t *o, PE_EXPORT_DESCRIPTOR *export_, size_t index) {
	if (o == null || export_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	if (index >= export_.NumberOfNamePointers) {
		adbg_oops(AdbgError.indexBounds);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ExportTable.size == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	// Entry table
	void* base = cast(void*)export_ -
		internal.directory.ExportTable.rva +
		export_.NamePointer;
	if (adbg_bits_ptrbounds(base, pe_export_entry_t.sizeof, export_, internal.directory.ExportTable.size)) {
		adbg_oops(AdbgError.offsetBounds); // or assertion?
		return null;
	}
	
	// Check bounds with name pointer and requested index
	pe_export_entry_t *entry = cast(pe_export_entry_t*)base + index;
	if (adbg_bits_ptrbounds(entry, pe_export_entry_t.sizeof, export_, internal.directory.ExportTable.size)) {
		adbg_oops(AdbgError.offsetBounds); // or assertion?
		return null;
	}
	
	if (o.status & AdbgObjectInternalFlags.reversed && internal.r_export_entries[index] == false) with (entry) {
		entry.Export = adbg_bswap32(entry.Export);
		internal.r_export_entries[index] = true;
	}
	
	return entry;
}

const(char)* adbg_object_pe_export_name_string(adbg_object_t *o,
	pe_export_descriptor_t *export_, pe_export_entry_t *entry) {
	if (o == null || export_ == null || entry == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ExportTable.size == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	// NOTE: Export Table bounds
	//       If the address specified is not within the export section (as
	//       defined by the address and length that are indicated in the
	//       optional header), the field is an Export RVA: an actual
	//       address in code or data. Otherwise, the field is a Forwarder
	//       RVA, which names a symbol in another DLL.
	
	//TODO: Forwarder check
	//if (entry.Export >= o.i.pe.directory.ExportTable.size)
	//	return null;
	
	void *base = cast(void*)export_ -
		internal.directory.ExportTable.rva +
		entry.Export;
	if (adbg_bits_ptrbounds(base, 2, export_, internal.directory.ExportTable.size)) {
		adbg_oops(AdbgError.offsetBounds); // or assertion?
		return null;
	}
	
	return cast(const(char)*)base;
}

//
// Import directory functions
//

// NOTE: Import directory handling
//       Because the import directory is not self-contained (its size only reflects headers),
//       the entire section is loaded in memory, hoping that nothing 
// Multiple tables, multiple entries per table
pe_import_descriptor_t* adbg_object_pe_import(adbg_object_t *o, size_t index) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	// NOTE: The last directory entry is empty (filled with null values),
	//       which indicates the end of the directory table.
	// NOTE: In theory, the entire set of import tables and names should be in the same section
	//       But, name RVA *could* point outside of it as Windows loads the entire image in memory
	
	// If zero, or just "one" entry
	if (internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	size_t count = (internal.directory.ImportTable.size / pe_import_descriptor_t.sizeof) - 1;
	version (Trace) trace("count=%zu", count);
	if (index >= count) {
		adbg_oops(AdbgError.indexBounds);
		return null;
	}
	
	// Load the section associated with the import directory
	if (internal.import_directory == null) {
		internal.import_section = adbg_object_pe_directory_section(o, internal.directory.ImportTable.rva);
		if (internal.import_section == null) {
			adbg_oops(AdbgError.unavailable);
			return null;
		}
		
		uint secsize = internal.import_section.SizeOfRawData;
		uint secoffs = internal.import_section.PointerToRawData;
		
		// Get file offset of import descriptors
		uint offset = adbg_object_pe_directory_offset_section(internal.import_section, internal.directory.ImportTable.rva);
		
		//TODO: Check if section already loaded
		//      While Load Config and TLS tables might be in the same section,
		//      they also could not be. So let's just hope this fuckery is only for imports.
		// Load the section because import table only contains descriptors
		internal.import_buffer = malloc(secsize);
		if (internal.import_buffer == null) {
			adbg_oops(AdbgError.crt);
			return null;
		}
		if (adbg_object_read_at(o, secoffs, internal.import_buffer, secsize)) {
			free(internal.import_buffer);
			internal.import_buffer = null;
			return null;
		}
		
		// Adjust offset to point from base of section to import descritor tables
		internal.import_directory = cast(pe_import_descriptor_t*)(internal.import_buffer + (offset - secoffs));
		if (adbg_bits_ptrbounds(internal.import_directory, pe_import_descriptor_t.sizeof,
			internal.import_buffer, secsize)) {
			adbg_oops(AdbgError.offsetBounds);
			free(internal.import_buffer);
			internal.import_buffer = null;
			return null;
		}
	}
	
	// Select descriptor
	pe_import_descriptor_t *import_ = internal.import_directory + index;
	if (adbg_bits_ptrbounds(import_, pe_import_descriptor_t.sizeof,
		internal.import_buffer, internal.import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	if (import_.Characteristics == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	//TODO: swap import directory entries
	/*if (o.status & AdbgObjectInternalFlags.reversed && internal.r_import_desc[index] == false) with (import_) {
		Characteristics	= adbg_bswap32(Characteristics);
		TimeDateStamp	= adbg_bswap32(TimeDateStamp);
		ForwarderChain	= adbg_bswap32(ForwarderChain);
		Name	= adbg_bswap32(Name);
		FirstThunk	= adbg_bswap32(FirstThunk);
		internal.r_import_desc[index] = true;
	}*/
	
	return import_;
}

// get module name out of import descriptor
const(char)* adbg_object_pe_import_module_name(adbg_object_t *o, pe_import_descriptor_t *import_) {
	if (o == null || import_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	void* name = cast(void*)internal.import_directory -
		internal.directory.ImportTable.rva +
		import_.Name;
	with (internal)
	if (adbg_bits_ptrbounds(name, 2, import_buffer, import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	return cast(const(char)*)name;
}

//TODO: Byte-swap import look-up table entries

pe_import_entry32_t* adbg_object_pe_import_entry32(adbg_object_t *o, pe_import_descriptor_t *import_, size_t index) {
	if (o == null || import_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	pe_import_entry32_t* entry = cast(pe_import_entry32_t*)(
		cast(void*)internal.import_directory +
		(import_.Characteristics - internal.directory.ImportTable.rva)) +
		index;
	with (internal)
	if (adbg_bits_ptrbounds(entry, pe_import_entry32_t.sizeof, import_buffer, import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	//TODO: Swap import entry
	/*if (o.status & AdbgObjectInternalFlags.reversed && internal.r_import_entries[index] == false) {
		entry.ordinal = adbg_bswap32(entry.ordinal);
		internal.r_import_entries[index] = true;
	}*/
	
	// Not supported
	if (entry.ordinal == 0) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	return entry;
}

/*ushort* adbg_object_pe_import_entry32_hint(adbg_object_t *o, PE_IMPORT_DESCRIPTOR *import_, PE_IMPORT_ENTRY32 *im32) {
	if (o == null || import_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	void* base =
		internal.import_directory -
		internal.directory.ImportTable.rva +
		im32.rva;
	with (internal)
	if (adbg_bits_ptr_outside(entry, import_buffer, import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	return cast(ushort*)base;
}*/

pe_import_entry64_t* adbg_object_pe_import_entry64(adbg_object_t *o, pe_import_descriptor_t *import_, size_t index) {
	if (o == null || import_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	pe_import_entry64_t* entry = cast(pe_import_entry64_t*)(
		cast(void*)internal.import_directory +
		(import_.Characteristics - internal.directory.ImportTable.rva)) +
		index;
	with (internal)
	if (adbg_bits_ptrbounds(entry, pe_import_entry64_t.sizeof, import_buffer, import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	//TODO: Swap import entry
	
	// Not supported
	if (entry.ordinal == 0) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	return entry;
}

/*ushort* adbg_object_pe_import_entry64_hint(adbg_object_t *o, PE_IMPORT_DESCRIPTOR *import_, PE_IMPORT_ENTRY64 *im64) {
	if (o == null || import_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.i.pe.directory_imports == null) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	ushort* base = cast(ushort*)
		((cast(char*)o.i.pe.directory_imports - o.i.pe.directory.ImportTable.rva) + im64.rva);
	
	version (Trace) trace("base=%p fs=%zx", base, o.file_size);
	
	if (adbg_object_outboundp(o, base)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	return base;
}*/

// Classless functions
// TODO: Optimize these, maybe cache the last result in internals

void* adbg_object_pe_import_entry(adbg_object_t *o, pe_import_descriptor_t *import_, size_t index) {
	if (o == null || import_ == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	switch (internal.optheader.Magic) {
	case PE_CLASS_32:
		return adbg_object_pe_import_entry32(o, import_, index);
	case PE_CLASS_64:
		return adbg_object_pe_import_entry64(o, import_, index);
	default:
		adbg_oops(AdbgError.objectInvalidClass);
		return null;
	}
}

uint adbg_object_pe_import_entry_rva(adbg_object_t *o, pe_import_descriptor_t *import_, void *entry) {
	if (o == null || import_ == null || entry == null) {
		adbg_oops(AdbgError.invalidArgument);
		return 0;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return 0;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return 0;
	}
	
	switch (internal.optheader.Magic) {
	case PE_CLASS_32:
		return (cast(pe_import_entry32_t*)entry).rva;
	case PE_CLASS_64:
		return (cast(pe_import_entry64_t*)entry).rva;
	default:
		adbg_oops(AdbgError.objectInvalidClass);
		return 0;
	}
}

ushort adbg_object_pe_import_entry_hint(adbg_object_t *o, pe_import_descriptor_t *import_, void *entry) {
	if (o == null || import_ == null || entry == null) {
		adbg_oops(AdbgError.invalidArgument);
		return 0;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return 0;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return 0;
	}
	
	ushort *hint = void;
	switch (internal.optheader.Magic) {
	case PE_CLASS_32:
		pe_import_entry32_t *entry32 = cast(pe_import_entry32_t*)entry;
		
		// By ordinal
		if (entry32.ordinal >= 0x8000_0000) {
			adbg_oops(AdbgError.unavailable);
			return 0;
		}
		
		// By RVA
		hint = cast(ushort*)(
			cast(void*)internal.import_directory -
			internal.directory.ImportTable.rva +
			entry32.rva);
		break;
	case PE_CLASS_64:
		pe_import_entry64_t *entry64 = cast(pe_import_entry64_t*)entry;
		
		// By ordinal
		if (entry64.ordinal >= 0x8000_0000) {
			adbg_oops(AdbgError.unavailable);
			return 0;
		}
		
		// By RVA
		hint = cast(ushort*)(
			cast(void*)internal.import_directory -
			internal.directory.ImportTable.rva +
			entry64.rva);
		break;
	default:
		adbg_oops(AdbgError.objectInvalidClass);
		return 0;
	}
	
	with (internal)
	if (adbg_bits_ptrbounds(hint, ushort.sizeof, import_buffer, import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return 0;
	}
	
	return *hint;
}

const(char)* adbg_object_pe_import_entry_string(adbg_object_t *o, pe_import_descriptor_t *import_, void *entry) {
	if (o == null || import_ == null || entry == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.unimplemented);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.ImportTable.size <= pe_import_descriptor_t.sizeof) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	ushort *hint = void;
	switch (internal.optheader.Magic) {
	case PE_CLASS_32:
		pe_import_entry32_t *entry32 = cast(pe_import_entry32_t*)entry;
		
		// By ordinal
		if (entry32.ordinal >= 0x8000_0000) {
			adbg_oops(AdbgError.unavailable);
			return null;
		}
		
		// By RVA
		hint = cast(ushort*)(
			cast(void*)internal.import_directory -
			internal.directory.ImportTable.rva +
			entry32.rva);
		break;
	case PE_CLASS_64:
		pe_import_entry64_t *entry64 = cast(pe_import_entry64_t*)entry;
		
		// By ordinal
		if (entry64.ordinal >= 0x8000_0000_0000_0000L) {
			adbg_oops(AdbgError.unavailable);
			return null;
		}
		
		// By RVA
		hint = cast(ushort*)(
			cast(void*)internal.import_directory -
			internal.directory.ImportTable.rva +
			entry64.rva);
		break;
	default:
		adbg_oops(AdbgError.objectInvalidClass);
		return null;
	}
	
	hint++;
	
	with (internal)
	if (adbg_bits_ptrbounds(hint, 2, import_buffer, import_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	return cast(const(char)*)hint;
}
//
// Debug directory functions
//

pe_debug_directory_entry_t* adbg_object_pe_debug_directory(adbg_object_t *o, size_t index) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.DebugDirectory.size == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	size_t count = internal.directory.DebugDirectory.size / PE_DEBUG_DIRECTORY.sizeof;
	if (index >= count) {
		adbg_oops(AdbgError.indexBounds);
		return null;
	}
	
	// Load debug directory
	if (internal.debug_directory == null) {
		// Same as imports, load the section
		internal.debug_section = adbg_object_pe_directory_section(o, internal.directory.DebugDirectory.rva);
		if (internal.debug_section == null) {
			adbg_oops(AdbgError.unavailable);
			return null;
		}
		
		uint secsize = internal.debug_section.SizeOfRawData;
		uint secoffs = internal.debug_section.PointerToRawData;
		
		// Get file offset of debug descriptors
		uint offset = adbg_object_pe_directory_offset_section(internal.debug_section, internal.directory.DebugDirectory.rva);
		
		version (Trace) trace("ssize=%u soff=%u off=%u", secsize, secoffs, offset);
		
		// Load the section because debug table only contains descriptors
		internal.debug_buffer = malloc(secsize);
		if (internal.debug_buffer == null) {
			adbg_oops(AdbgError.crt);
			return null;
		}
		if (adbg_object_read_at(o, secoffs, internal.debug_buffer, secsize)) {
			free(internal.debug_buffer);
			internal.debug_buffer = null;
			return null;
		}
		
		// Adjust offset to point from base of section to import descritor tables
		internal.debug_directory =
			cast(pe_debug_directory_entry_t*)(cast(void*)internal.debug_buffer + (offset - secoffs));
		if (adbg_bits_ptrbounds(internal.debug_directory, pe_debug_directory_entry_t.sizeof,
			internal.debug_buffer, secsize)) {
			adbg_oops(AdbgError.offsetBounds);
			free(internal.debug_buffer);
			internal.debug_buffer = null;
			return null;
		}
		
		// Allocate reverse status bools
		internal.r_debug_entries = cast(bool*)malloc(count);
		if (internal.r_debug_entries == null) {
			adbg_oops(AdbgError.crt);
			free(internal.debug_buffer);
			internal.debug_buffer = null;
			return null;
		}
	}
	
	// Select directory entry
	pe_debug_directory_entry_t *debug_ = internal.debug_directory + index;
	if (adbg_bits_ptrbounds(debug_, pe_debug_directory_entry_t.sizeof,
		internal.debug_buffer, internal.debug_section.SizeOfRawData)) {
		adbg_oops(AdbgError.offsetBounds);
		return null;
	}
	
	if (o.status & AdbgObjectInternalFlags.reversed && internal.r_debug_entries[index] == false) with (debug_) {
		Characteristics	= adbg_bswap32(Characteristics);
		TimeDateStamp	= adbg_bswap32(TimeDateStamp);
		MajorVersion	= adbg_bswap16(MajorVersion);
		MinorVersion	= adbg_bswap16(MinorVersion);
		Type	= adbg_bswap32(Type);
		SizeOfData	= adbg_bswap32(SizeOfData);
		AddressOfRawData	= adbg_bswap32(AddressOfRawData);
		PointerToRawData	= adbg_bswap32(PointerToRawData);
		internal.r_debug_entries[index] = true;
	}
	return debug_;
}

void* adbg_object_pe_debug_directory_data(adbg_object_t *o, pe_debug_directory_entry_t *entry) {
	if (o == null || entry == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	if (internal.header.SizeOfOptionalHeader == 0 ||
		internal.directory.DebugDirectory.size == 0) {
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	//TODO: Type size checking (minimum fulfillment)?
	void* data = malloc(entry.SizeOfData);
	if (data == null) {
		adbg_oops(AdbgError.crt);
		return null;
	}
	if (adbg_object_read_at(o, entry.PointerToRawData, data, entry.SizeOfData))
		return null;
	
	return data;
}
void adbg_object_pe_debug_directory_data_close(void* entry) {
	if (entry) free(entry);
}

//
// Other helpers
//

AdbgMachine adbg_object_pe_machine(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return AdbgMachine.unknown;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return AdbgMachine.unknown;
	}
	pe_header_t* header = cast(pe_header_t*)o.internal;
	switch (header.Machine) {
	case PE_MACHINE_I386:	return AdbgMachine.i386;
	case PE_MACHINE_AMD64:	return AdbgMachine.amd64;
	case PE_MACHINE_ALPHAOLD, PE_MACHINE_ALPHA:	return AdbgMachine.alpha;
	case PE_MACHINE_ALPHA64:	return AdbgMachine.alpha64;
	case PE_MACHINE_AM33:	return AdbgMachine.am33;
	case PE_MACHINE_ARM:
	case PE_MACHINE_ARMNT:	return AdbgMachine.arm;
	case PE_MACHINE_ARM64:	return AdbgMachine.aarch64;
	case PE_MACHINE_EBC:	return AdbgMachine.ebc;
	case PE_MACHINE_IA64:	return AdbgMachine.ia64;
	case PE_MACHINE_LOONGARCH32:	return AdbgMachine.loongarch32;
	case PE_MACHINE_LOONGARCH64:	return AdbgMachine.loongarch64;
	case PE_MACHINE_M32R:	return AdbgMachine.m32r;
	case PE_MACHINE_MIPS16:	return AdbgMachine.mips16;
	case PE_MACHINE_MIPSFPU:	return AdbgMachine.mipsfpu;
	case PE_MACHINE_MIPSFPU16:	return AdbgMachine.mips16fpu;
	case PE_MACHINE_POWERPC:	return AdbgMachine.ppc;
	case PE_MACHINE_POWERPCFP:	return AdbgMachine.ppcfpu;
	case PE_MACHINE_R3000:	return AdbgMachine.mips;
	case PE_MACHINE_R4000:	return AdbgMachine.mipsii;
	case PE_MACHINE_R10000:	return AdbgMachine.mipsiv;
	case PE_MACHINE_RISCV32:	return AdbgMachine.riscv32;
	case PE_MACHINE_RISCV64:	return AdbgMachine.riscv64;
	case PE_MACHINE_RISCV128:	return AdbgMachine.riscv128;
	case PE_MACHINE_SH3:	return AdbgMachine.sh3;
	case PE_MACHINE_SH3DSP:	return AdbgMachine.sh3dsp;
	case PE_MACHINE_SH4:	return AdbgMachine.sh4;
	case PE_MACHINE_SH5:	return AdbgMachine.sh5;
	case PE_MACHINE_THUMB:	return AdbgMachine.thumb;
	case PE_MACHINE_WCEMIPSV2:	return AdbgMachine.mipswcele;
	case PE_MACHINE_CLR:	return AdbgMachine.clr;
	default:	return AdbgMachine.unknown;
	}
}

const(char)* adbg_object_pe_machine_value_string(ushort Machine) {
	switch (Machine) {
	case PE_MACHINE_ALPHA:	return "DEC Alpha";
	case PE_MACHINE_ALPHA64:	return "DEC Alpha (64-bit)";
	case PE_MACHINE_AM33:	return "Mitsubishi MN10300 (AM33)";
	case PE_MACHINE_AMD64:	return "x86-64";
	case PE_MACHINE_ARM:	return "ARM (32-bit)";
	case PE_MACHINE_ARMNT:	return "ARM Thumb-2 (32-bit)";
	case PE_MACHINE_ARM64:	return "ARM (64-bit)";
	case PE_MACHINE_EBC:	return "EFI Byte Code";
	case PE_MACHINE_I386:	return "Intel x86";
	case PE_MACHINE_IA64:	return "Intel Itanium Architecture 64";
	case PE_MACHINE_LOONGARCH32:	return "LoongArch32";
	case PE_MACHINE_LOONGARCH64:	return "LoongArch64";
	case PE_MACHINE_M32R:	return "Mitsubishi M32R";
	case PE_MACHINE_MIPS16:	return "MIPS16";
	case PE_MACHINE_MIPSFPU:	return "MIPS I with FPU";
	case PE_MACHINE_MIPSFPU16:	return "MIPS16 with FPU";
	case PE_MACHINE_POWERPC:	return "PowerPC";
	case PE_MACHINE_POWERPCFP:	return "PowerPC with FPU";
	case PE_MACHINE_R3000:	return "MIPS I (RS3000) Little-Endian";
	case PE_MACHINE_R4000:	return "MIPS III (R4000)";
	case PE_MACHINE_R10000:	return "MIPS IV (R10000)";
	case PE_MACHINE_RISCV32:	return "RISC-V (32-bit)";
	case PE_MACHINE_RISCV64:	return "RISC-V (64-bit)";
	case PE_MACHINE_RISCV128:	return "RISC-V (128-bit)";
	case PE_MACHINE_SH3:	return "Hitachi SuperH 3";
	case PE_MACHINE_SH3DSP:	return "Hitachi SuperH 3 DSP";
	case PE_MACHINE_SH4:	return "Hitachi SuperH 4";
	case PE_MACHINE_SH5:	return "Hitachi SuperH 5";
	case PE_MACHINE_THUMB:	return "ARM Thumb";
	case PE_MACHINE_WCEMIPSV2:	return "MIPS little-endian WCE v2";
	case PE_MACHINE_CLR:	return "Common Language Runtime";
	default:
		adbg_oops(AdbgError.objectInvalidMachine);
		return null;
	}
}

const(char)* adbg_object_pe_machine_string(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	return adbg_object_pe_machine_value_string((cast(pe_header_t*)o.internal).Machine);
}

const(char)* adbg_object_pe_magic_string(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	pe_optional_header_t* opthdr = &(cast(internal_pe_t*)o.internal).optheader;
	switch (opthdr.Magic) {
	case PE_CLASS_32:	return "PE32";
	case PE_CLASS_64:	return "PE32+";
	case PE_CLASS_ROM:	return "PE-ROM";
	default:
		adbg_oops(AdbgError.objectMalformed);
		return null;
	}
}

const(char)* adbg_object_pe_subsys_string(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (o.internal == null) {
		adbg_oops(AdbgError.uninitiated);
		return null;
	}
	pe_optional_header_t* opthdr = &(cast(internal_pe_t*)o.internal).optheader;
	ushort subsystem = void;
	switch (opthdr.Magic) {
	case PE_CLASS_32: subsystem = opthdr.Subsystem; break;
	case PE_CLASS_64: subsystem = (cast(pe_optional_header64_t*)opthdr).Subsystem; break;
	default:
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	switch (subsystem) {
	case PE_SUBSYSTEM_NATIVE:	return "Native";
	case PE_SUBSYSTEM_WINDOWS_GUI:	return "Windows GUI";
	case PE_SUBSYSTEM_WINDOWS_CUI:	return "Windows Console";
	case PE_SUBSYSTEM_POSIX_CUI:	return "Posix Console";
	case PE_SUBSYSTEM_NATIVE_WINDOWS:	return "Native Windows 9x Driver";
	case PE_SUBSYSTEM_WINDOWS_CE_GUI:	return "Windows CE GUI";
	case PE_SUBSYSTEM_EFI_APPLICATION:	return "EFI";
	case PE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:	return "EFI Boot Service Driver";
	case PE_SUBSYSTEM_EFI_RUNTIME_DRIVER:	return "EFI Runtime Driver";
	case PE_SUBSYSTEM_EFI_ROM:	return "EFI ROM";
	case PE_SUBSYSTEM_XBOX:	return "XBOX";
	case PE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION:	return "Windows Boot";
	case PE_SUBSYSTEM_XBOX_CODE_CATALOG:	return "XBOX Code Catalog";
	default:
		adbg_oops(AdbgError.objectInvalidType);
		return null;
	}
}

const(char)* adbg_object_pe_debug_type_string(uint type) {
	switch (type) {
	case PE_IMAGE_DEBUG_TYPE_UNKNOWN:	return "Unknown";
	case PE_IMAGE_DEBUG_TYPE_COFF:	return "COFF";
	case PE_IMAGE_DEBUG_TYPE_CODEVIEW:	return "CodeView / VC++";
	case PE_IMAGE_DEBUG_TYPE_FPO:	return "FPO (Frame Pointer Omission) Information";
	case PE_IMAGE_DEBUG_TYPE_MISC:	return "DBG File Location";
	case PE_IMAGE_DEBUG_TYPE_EXCEPTION:	return "Exception";
	case PE_IMAGE_DEBUG_TYPE_FIXUP:	return "FIXUP";
	case PE_IMAGE_DEBUG_TYPE_OMAP_TO_SRC:	return "Map RVA to source image RVA";
	case PE_IMAGE_DEBUG_TYPE_OMAP_FROM_SRC:	return "Map source image RVA to RVA";
	case PE_IMAGE_DEBUG_TYPE_BORLAND:	return "Borland";
	case PE_IMAGE_DEBUG_TYPE_RESERVED10:	return "RESERVED10";
	case PE_IMAGE_DEBUG_TYPE_CLSID:	return "CLSID";
	case PE_IMAGE_DEBUG_TYPE_VC_FEATURE:	return "VC FEATURE";
	case PE_IMAGE_DEBUG_TYPE_POGO:	return "Profile Guided Optimization (POGO)";
	case PE_IMAGE_DEBUG_TYPE_ILTCG:	return "Incremental Link Time Code Generation (ILTCG)";
	case PE_IMAGE_DEBUG_TYPE_MPX:	return "Memory protection (Intel MPX)";
	case PE_IMAGE_DEBUG_TYPE_REPRO:	return "PE Reproducibility";
	case PE_IMAGE_DEBUG_TYPE_EMBEDDED:	return "Embedded Portable PDB Debug Directory Entry";
	case PE_IMAGE_DEBUG_TYPE_HASH:	return "PDB Hash";
	case PE_IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS:	return "DLL characteristics";
	case PE_IMAGE_DEBUG_TYPE_R2R_PERFMAP:	return "R2R PerfMap Debug Directory Entry";
	default:	return null;
	}
}

const(char)* adbg_object_pe_kind_string(adbg_object_t *o) {
	if (o == null) return null;
	if (o.internal == null) return null;
	internal_pe_t *internal = cast(internal_pe_t*)o.internal;
	return internal.header.Characteristics & PE_CHARACTERISTIC_DLL ?
		`Dynamically Linked Library` : `Executable`;
}

private:

//TODO: Map linker version with load configuration sizes
// Exec        Version   Size
// putty-x86      0.73     92
// putty-amd64    0.73    148