diff --git a/Bytes/PortableExecutable.Deserializer.cs b/Bytes/PortableExecutable.Deserializer.cs new file mode 100644 index 00000000..811708e7 --- /dev/null +++ b/Bytes/PortableExecutable.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.PortableExecutable; + +namespace SabreTools.Serialization.Bytes +{ + public partial class PortableExecutable : IByteSerializer + { + /// +#if NET48 + public Executable Deserialize(byte[] data, int offset) +#else + public Executable? Deserialize(byte[]? data, int offset) +#endif + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return new Streams.PortableExecutable().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/PortableExecutable.Deserializer.cs b/Streams/PortableExecutable.Deserializer.cs new file mode 100644 index 00000000..5072535f --- /dev/null +++ b/Streams/PortableExecutable.Deserializer.cs @@ -0,0 +1,1312 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.PortableExecutable; +using static SabreTools.Models.PortableExecutable.Constants; + +namespace SabreTools.Serialization.Streams +{ + public partial class PortableExecutable : IStreamSerializer + { + /// +#if NET48 + public Executable Deserialize(Stream data) +#else + public Executable? Deserialize(Stream? data) +#endif + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + int initialOffset = (int)data.Position; + + // Create a new executable to fill + var executable = new Executable(); + + #region MS-DOS Stub + + // Parse the MS-DOS stub + var stub = new MSDOS().Deserialize(data); + if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0) + return null; + + // Set the MS-DOS stub + executable.Stub = stub; + + #endregion + + #region Signature + + data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin); + byte[] signature = data.ReadBytes(4); + executable.Signature = Encoding.ASCII.GetString(signature); + if (executable.Signature != SignatureString) + return null; + + #endregion + + #region COFF File Header + + // Try to parse the COFF file header + var coffFileHeader = ParseCOFFFileHeader(data); + if (coffFileHeader == null) + return null; + + // Set the COFF file header + executable.COFFFileHeader = coffFileHeader; + + #endregion + + #region Optional Header + + // Try to parse the optional header + var optionalHeader = ParseOptionalHeader(data, coffFileHeader.SizeOfOptionalHeader); + if (optionalHeader == null) + return null; + + // Set the optional header + executable.OptionalHeader = optionalHeader; + + #endregion + + #region Section Table + + // Try to parse the section table + var sectionTable = ParseSectionTable(data, coffFileHeader.NumberOfSections); + if (sectionTable == null) + return null; + + // Set the section table + executable.SectionTable = sectionTable; + + #endregion + + #region COFF Symbol Table and COFF String Table + + // TODO: Validate that this is correct with an "old" PE + if (coffFileHeader.PointerToSymbolTable.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the COFF symbol table doesn't exist + int symbolTableAddress = initialOffset + + (int)coffFileHeader.PointerToSymbolTable.ConvertVirtualAddress(executable.SectionTable); + if (symbolTableAddress >= data.Length) + return executable; + + // Try to parse the COFF symbol table + data.Seek(symbolTableAddress, SeekOrigin.Begin); + var coffSymbolTable = ParseCOFFSymbolTable(data, coffFileHeader.NumberOfSymbols); + if (coffSymbolTable == null) + return null; + + // Set the COFF symbol table + executable.COFFSymbolTable = coffSymbolTable; + + // Try to parse the COFF string table + var coffStringTable = ParseCOFFStringTable(data); + if (coffStringTable == null) + return null; + + // Set the COFF string table + executable.COFFStringTable = coffStringTable; + } + + #endregion + + #region Attribute Certificate Table + + if (optionalHeader.CertificateTable != null && optionalHeader.CertificateTable.VirtualAddress != 0) + { + // If the offset for the attribute certificate table doesn't exist + int certificateTableAddress = initialOffset + + (int)optionalHeader.CertificateTable.VirtualAddress; + if (certificateTableAddress >= data.Length) + return executable; + + // Try to parse the attribute certificate table + data.Seek(certificateTableAddress, SeekOrigin.Begin); + int endOffset = (int)(certificateTableAddress + optionalHeader.CertificateTable.Size); + var attributeCertificateTable = ParseAttributeCertificateTable(data, endOffset); + if (attributeCertificateTable == null) + return null; + + // Set the attribute certificate table + executable.AttributeCertificateTable = attributeCertificateTable; + } + + #endregion + + #region Delay-Load Directory Table + + if (optionalHeader.DelayImportDescriptor != null && optionalHeader.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the delay-load directory table doesn't exist + int delayLoadDirectoryTableAddress = initialOffset + + (int)optionalHeader.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (delayLoadDirectoryTableAddress >= data.Length) + return executable; + + // Try to parse the delay-load directory table + data.Seek(delayLoadDirectoryTableAddress, SeekOrigin.Begin); + var delayLoadDirectoryTable = ParseDelayLoadDirectoryTable(data); + if (delayLoadDirectoryTable == null) + return null; + + // Set the delay-load directory table + executable.DelayLoadDirectoryTable = delayLoadDirectoryTable; + } + + #endregion + + #region Base Relocation Table + + // Should also be in a '.reloc' section + if (optionalHeader.BaseRelocationTable != null && optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the base relocation table doesn't exist + int baseRelocationTableAddress = initialOffset + + (int)optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (baseRelocationTableAddress >= data.Length) + return executable; + + // Try to parse the base relocation table + data.Seek(baseRelocationTableAddress, SeekOrigin.Begin); + int endOffset = (int)(baseRelocationTableAddress + optionalHeader.BaseRelocationTable.Size); + var baseRelocationTable = ParseBaseRelocationTable(data, endOffset, executable.SectionTable); + if (baseRelocationTable == null) + return null; + + // Set the base relocation table + executable.BaseRelocationTable = baseRelocationTable; + } + + #endregion + + #region Debug Table + + // Should also be in a '.debug' section + if (optionalHeader.Debug != null && optionalHeader.Debug.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the debug table doesn't exist + int debugTableAddress = initialOffset + + (int)optionalHeader.Debug.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (debugTableAddress >= data.Length) + return executable; + + // Try to parse the debug table + data.Seek(debugTableAddress, SeekOrigin.Begin); + int endOffset = (int)(debugTableAddress + optionalHeader.Debug.Size); + var debugTable = ParseDebugTable(data, endOffset, executable.SectionTable); + if (debugTable == null) + return null; + + // Set the debug table + executable.DebugTable = debugTable; + } + + #endregion + + #region Export Table + + // Should also be in a '.edata' section + if (optionalHeader.ExportTable != null && optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the export table doesn't exist + int exportTableAddress = initialOffset + + (int)optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (exportTableAddress >= data.Length) + return executable; + + // Try to parse the export table + data.Seek(exportTableAddress, SeekOrigin.Begin); + var exportTable = ParseExportTable(data, executable.SectionTable); + if (exportTable == null) + return null; + + // Set the export table + executable.ExportTable = exportTable; + } + + #endregion + + #region Import Table + + // Should also be in a '.idata' section + if (optionalHeader.ImportTable != null && optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the import table doesn't exist + int importTableAddress = initialOffset + + (int)optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (importTableAddress >= data.Length) + return executable; + + // Try to parse the import table + data.Seek(importTableAddress, SeekOrigin.Begin); + var importTable = ParseImportTable(data, optionalHeader.Magic, executable.SectionTable); + if (importTable == null) + return null; + + // Set the import table + executable.ImportTable = importTable; + } + + #endregion + + #region Resource Directory Table + + // Should also be in a '.rsrc' section + if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0) + { + // If the offset for the resource directory table doesn't exist + int resourceTableAddress = initialOffset + + (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable); + if (resourceTableAddress >= data.Length) + return executable; + + // Try to parse the resource directory table + data.Seek(resourceTableAddress, SeekOrigin.Begin); + var resourceDirectoryTable = ParseResourceDirectoryTable(data, data.Position, executable.SectionTable, true); + if (resourceDirectoryTable == null) + return null; + + // Set the resource directory table + executable.ResourceDirectoryTable = resourceDirectoryTable; + } + + #endregion + + // TODO: Finish implementing PE parsing + return executable; + } + + /// + /// Parse a Stream into a Portable Executable COFF file header + /// + /// Stream to parse + /// Filled executable header on success, null on error + private static COFFFileHeader ParseCOFFFileHeader(Stream data) + { + // TODO: Use marshalling here instead of building + var fileHeader = new COFFFileHeader(); + + fileHeader.Machine = (MachineType)data.ReadUInt16(); + fileHeader.NumberOfSections = data.ReadUInt16(); + fileHeader.TimeDateStamp = data.ReadUInt32(); + fileHeader.PointerToSymbolTable = data.ReadUInt32(); + fileHeader.NumberOfSymbols = data.ReadUInt32(); + fileHeader.SizeOfOptionalHeader = data.ReadUInt16(); + fileHeader.Characteristics = (Characteristics)data.ReadUInt16(); + + return fileHeader; + } + + /// + /// Parse a Stream into an optional header + /// + /// Stream to parse + /// Size of the optional header + /// Filled optional header on success, null on error + private static OptionalHeader ParseOptionalHeader(Stream data, int optionalSize) + { + long initialOffset = data.Position; + + // TODO: Use marshalling here instead of building + var optionalHeader = new OptionalHeader(); + + #region Standard Fields + + optionalHeader.Magic = (OptionalHeaderMagicNumber)data.ReadUInt16(); + optionalHeader.MajorLinkerVersion = data.ReadByteValue(); + optionalHeader.MinorLinkerVersion = data.ReadByteValue(); + optionalHeader.SizeOfCode = data.ReadUInt32(); + optionalHeader.SizeOfInitializedData = data.ReadUInt32(); + optionalHeader.SizeOfUninitializedData = data.ReadUInt32(); + optionalHeader.AddressOfEntryPoint = data.ReadUInt32(); + optionalHeader.BaseOfCode = data.ReadUInt32(); + + if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) + optionalHeader.BaseOfData = data.ReadUInt32(); + + #endregion + + #region Windows-Specific Fields + + if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) + optionalHeader.ImageBase_PE32 = data.ReadUInt32(); + else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) + optionalHeader.ImageBase_PE32Plus = data.ReadUInt64(); + optionalHeader.SectionAlignment = data.ReadUInt32(); + optionalHeader.FileAlignment = data.ReadUInt32(); + optionalHeader.MajorOperatingSystemVersion = data.ReadUInt16(); + optionalHeader.MinorOperatingSystemVersion = data.ReadUInt16(); + optionalHeader.MajorImageVersion = data.ReadUInt16(); + optionalHeader.MinorImageVersion = data.ReadUInt16(); + optionalHeader.MajorSubsystemVersion = data.ReadUInt16(); + optionalHeader.MinorSubsystemVersion = data.ReadUInt16(); + optionalHeader.Win32VersionValue = data.ReadUInt32(); + optionalHeader.SizeOfImage = data.ReadUInt32(); + optionalHeader.SizeOfHeaders = data.ReadUInt32(); + optionalHeader.CheckSum = data.ReadUInt32(); + optionalHeader.Subsystem = (WindowsSubsystem)data.ReadUInt16(); + optionalHeader.DllCharacteristics = (DllCharacteristics)data.ReadUInt16(); + if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) + optionalHeader.SizeOfStackReserve_PE32 = data.ReadUInt32(); + else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) + optionalHeader.SizeOfStackReserve_PE32Plus = data.ReadUInt64(); + if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) + optionalHeader.SizeOfStackCommit_PE32 = data.ReadUInt32(); + else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) + optionalHeader.SizeOfStackCommit_PE32Plus = data.ReadUInt64(); + if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) + optionalHeader.SizeOfHeapReserve_PE32 = data.ReadUInt32(); + else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) + optionalHeader.SizeOfHeapReserve_PE32Plus = data.ReadUInt64(); + if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32) + optionalHeader.SizeOfHeapCommit_PE32 = data.ReadUInt32(); + else if (optionalHeader.Magic == OptionalHeaderMagicNumber.PE32Plus) + optionalHeader.SizeOfHeapCommit_PE32Plus = data.ReadUInt64(); + optionalHeader.LoaderFlags = data.ReadUInt32(); + optionalHeader.NumberOfRvaAndSizes = data.ReadUInt32(); + + #endregion + + #region Data Directories + + if (optionalHeader.NumberOfRvaAndSizes >= 1 && data.Position - initialOffset < optionalSize) + { + optionalHeader.ExportTable = new DataDirectory(); + optionalHeader.ExportTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.ExportTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 2 && data.Position - initialOffset < optionalSize) + { + optionalHeader.ImportTable = new DataDirectory(); + optionalHeader.ImportTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.ImportTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 3 && data.Position - initialOffset < optionalSize) + { + optionalHeader.ResourceTable = new DataDirectory(); + optionalHeader.ResourceTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.ResourceTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 4 && data.Position - initialOffset < optionalSize) + { + optionalHeader.ExceptionTable = new DataDirectory(); + optionalHeader.ExceptionTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.ExceptionTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 5 && data.Position - initialOffset < optionalSize) + { + optionalHeader.CertificateTable = new DataDirectory(); + optionalHeader.CertificateTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.CertificateTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 6 && data.Position - initialOffset < optionalSize) + { + optionalHeader.BaseRelocationTable = new DataDirectory(); + optionalHeader.BaseRelocationTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.BaseRelocationTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 7 && data.Position - initialOffset < optionalSize) + { + optionalHeader.Debug = new DataDirectory(); + optionalHeader.Debug.VirtualAddress = data.ReadUInt32(); + optionalHeader.Debug.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 8 && data.Position - initialOffset < optionalSize) + { + optionalHeader.Architecture = data.ReadUInt64(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 9 && data.Position - initialOffset < optionalSize) + { + optionalHeader.GlobalPtr = new DataDirectory(); + optionalHeader.GlobalPtr.VirtualAddress = data.ReadUInt32(); + optionalHeader.GlobalPtr.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 10 && data.Position - initialOffset < optionalSize) + { + optionalHeader.ThreadLocalStorageTable = new DataDirectory(); + optionalHeader.ThreadLocalStorageTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.ThreadLocalStorageTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 11 && data.Position - initialOffset < optionalSize) + { + optionalHeader.LoadConfigTable = new DataDirectory(); + optionalHeader.LoadConfigTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.LoadConfigTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 12 && data.Position - initialOffset < optionalSize) + { + optionalHeader.BoundImport = new DataDirectory(); + optionalHeader.BoundImport.VirtualAddress = data.ReadUInt32(); + optionalHeader.BoundImport.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 13 && data.Position - initialOffset < optionalSize) + { + optionalHeader.ImportAddressTable = new DataDirectory(); + optionalHeader.ImportAddressTable.VirtualAddress = data.ReadUInt32(); + optionalHeader.ImportAddressTable.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 14 && data.Position - initialOffset < optionalSize) + { + optionalHeader.DelayImportDescriptor = new DataDirectory(); + optionalHeader.DelayImportDescriptor.VirtualAddress = data.ReadUInt32(); + optionalHeader.DelayImportDescriptor.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 15 && data.Position - initialOffset < optionalSize) + { + optionalHeader.CLRRuntimeHeader = new DataDirectory(); + optionalHeader.CLRRuntimeHeader.VirtualAddress = data.ReadUInt32(); + optionalHeader.CLRRuntimeHeader.Size = data.ReadUInt32(); + } + if (optionalHeader.NumberOfRvaAndSizes >= 16 && data.Position - initialOffset < optionalSize) + { + optionalHeader.Reserved = data.ReadUInt64(); + } + + #endregion + + return optionalHeader; + } + + /// + /// Parse a Stream into a section table + /// + /// Stream to parse + /// Number of section table entries to read + /// Filled section table on success, null on error + private static SectionHeader[] ParseSectionTable(Stream data, int count) + { + // TODO: Use marshalling here instead of building + var sectionTable = new SectionHeader[count]; + + for (int i = 0; i < count; i++) + { + var entry = new SectionHeader(); + entry.Name = data.ReadBytes(8); + entry.VirtualSize = data.ReadUInt32(); + entry.VirtualAddress = data.ReadUInt32(); + entry.SizeOfRawData = data.ReadUInt32(); + entry.PointerToRawData = data.ReadUInt32(); + entry.PointerToRelocations = data.ReadUInt32(); + entry.PointerToLinenumbers = data.ReadUInt32(); + entry.NumberOfRelocations = data.ReadUInt16(); + entry.NumberOfLinenumbers = data.ReadUInt16(); + entry.Characteristics = (SectionFlags)data.ReadUInt32(); + entry.COFFRelocations = new COFFRelocation[entry.NumberOfRelocations]; + for (int j = 0; j < entry.NumberOfRelocations; j++) + { + // TODO: Seek to correct location and read data + } + entry.COFFLineNumbers = new COFFLineNumber[entry.NumberOfLinenumbers]; + for (int j = 0; j < entry.NumberOfLinenumbers; j++) + { + // TODO: Seek to correct location and read data + } + sectionTable[i] = entry; + } + + return sectionTable; + } + + /// + /// Parse a Stream into a COFF symbol table + /// + /// Stream to parse + /// Number of COFF symbol table entries to read + /// Filled COFF symbol table on success, null on error + private static COFFSymbolTableEntry[] ParseCOFFSymbolTable(Stream data, uint count) + { + // TODO: Use marshalling here instead of building + var coffSymbolTable = new COFFSymbolTableEntry[count]; + + int auxSymbolsRemaining = 0; + int currentSymbolType = 0; + + for (int i = 0; i < count; i++) + { + // Standard COFF Symbol Table Entry + if (currentSymbolType == 0) + { + var entry = new COFFSymbolTableEntry(); + entry.ShortName = data.ReadBytes(8); + entry.Zeroes = BitConverter.ToUInt32(entry.ShortName, 0); + if (entry.Zeroes == 0) + { + entry.Offset = BitConverter.ToUInt32(entry.ShortName, 4); + entry.ShortName = null; + } + entry.Value = data.ReadUInt32(); + entry.SectionNumber = data.ReadUInt16(); + entry.SymbolType = (SymbolType)data.ReadUInt16(); + entry.StorageClass = (StorageClass)data.ReadByte(); + entry.NumberOfAuxSymbols = data.ReadByteValue(); + coffSymbolTable[i] = entry; + + auxSymbolsRemaining = entry.NumberOfAuxSymbols; + if (auxSymbolsRemaining == 0) + continue; + + if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC + && entry.SectionNumber > 0) + { + currentSymbolType = 1; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION + && entry.ShortName != null + && ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf + || (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef + { + currentSymbolType = 2; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED + && entry.Value == 0) + { + currentSymbolType = 3; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE) + { + // TODO: Symbol name should be ".file" + currentSymbolType = 4; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC) + { + // TODO: Should have the name of a section (like ".text") + currentSymbolType = 5; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN) + { + currentSymbolType = 6; + } + } + + // Auxiliary Format 1: Function Definitions + else if (currentSymbolType == 1) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat1TagIndex = data.ReadUInt32(); + entry.AuxFormat1TotalSize = data.ReadUInt32(); + entry.AuxFormat1PointerToLinenumber = data.ReadUInt32(); + entry.AuxFormat1PointerToNextFunction = data.ReadUInt32(); + entry.AuxFormat1Unused = data.ReadUInt16(); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 2: .bf and .ef Symbols + else if (currentSymbolType == 2) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat2Unused1 = data.ReadUInt32(); + entry.AuxFormat2Linenumber = data.ReadUInt16(); + entry.AuxFormat2Unused2 = data.ReadBytes(6); + entry.AuxFormat2PointerToNextFunction = data.ReadUInt32(); + entry.AuxFormat2Unused3 = data.ReadUInt16(); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 3: Weak Externals + else if (currentSymbolType == 3) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat3TagIndex = data.ReadUInt32(); + entry.AuxFormat3Characteristics = data.ReadUInt32(); + entry.AuxFormat3Unused = data.ReadBytes(10); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 4: Files + else if (currentSymbolType == 4) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat4FileName = data.ReadBytes(18); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 5: Section Definitions + else if (currentSymbolType == 5) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat5Length = data.ReadUInt32(); + entry.AuxFormat5NumberOfRelocations = data.ReadUInt16(); + entry.AuxFormat5NumberOfLinenumbers = data.ReadUInt16(); + entry.AuxFormat5CheckSum = data.ReadUInt32(); + entry.AuxFormat5Number = data.ReadUInt16(); + entry.AuxFormat5Selection = data.ReadByteValue(); + entry.AuxFormat5Unused = data.ReadBytes(3); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 6: CLR Token Definition + else if (currentSymbolType == 6) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat6AuxType = data.ReadByteValue(); + entry.AuxFormat6Reserved1 = data.ReadByteValue(); + entry.AuxFormat6SymbolTableIndex = data.ReadUInt32(); + entry.AuxFormat6Reserved2 = data.ReadBytes(12); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // If we hit the last aux symbol, go back to normal format + if (auxSymbolsRemaining == 0) + currentSymbolType = 0; + } + + return coffSymbolTable; + } + + /// + /// Parse a Stream into a COFF string table + /// + /// Stream to parse + /// Filled COFF string table on success, null on error + private static COFFStringTable ParseCOFFStringTable(Stream data) + { + // TODO: Use marshalling here instead of building + var coffStringTable = new COFFStringTable(); + + coffStringTable.TotalSize = data.ReadUInt32(); + if (coffStringTable.TotalSize <= 4) + return coffStringTable; + + var strings = new List(); + + uint totalSize = coffStringTable.TotalSize; + while (totalSize > 0 && data.Position < data.Length) + { + long initialPosition = data.Position; + string str = data.ReadString(); + strings.Add(str); + totalSize -= (uint)(data.Position - initialPosition); + } + + coffStringTable.Strings = strings.ToArray(); + + return coffStringTable; + } + + /// + /// Parse a Stream into an attribute certificate table + /// + /// Stream to parse + /// First address not part of the attribute certificate table + /// Filled attribute certificate on success, null on error + private static AttributeCertificateTableEntry[] ParseAttributeCertificateTable(Stream data, int endOffset) + { + var attributeCertificateTable = new List(); + + while (data.Position < endOffset && data.Position != data.Length) + { + var entry = new AttributeCertificateTableEntry(); + + entry.Length = data.ReadUInt32(); + entry.Revision = (WindowsCertificateRevision)data.ReadUInt16(); + entry.CertificateType = (WindowsCertificateType)data.ReadUInt16(); + + int certificateDataLength = (int)(entry.Length - 8); + if (certificateDataLength > 0) + entry.Certificate = data.ReadBytes(certificateDataLength); + + attributeCertificateTable.Add(entry); + + // Align to the 8-byte boundary + while ((data.Position % 8) != 0 && data.Position < endOffset && data.Position != data.Length) + _ = data.ReadByteValue(); + } + + return attributeCertificateTable.ToArray(); + } + + /// + /// Parse a byte array into a delay-load directory table + /// + /// Stream to parse + /// Filled delay-load directory table on success, null on error + private static DelayLoadDirectoryTable ParseDelayLoadDirectoryTable(Stream data) + { + // TODO: Use marshalling here instead of building + var delayLoadDirectoryTable = new DelayLoadDirectoryTable(); + + delayLoadDirectoryTable.Attributes = data.ReadUInt32(); + delayLoadDirectoryTable.Name = data.ReadUInt32(); + delayLoadDirectoryTable.ModuleHandle = data.ReadUInt32(); + delayLoadDirectoryTable.DelayImportAddressTable = data.ReadUInt32(); + delayLoadDirectoryTable.DelayImportNameTable = data.ReadUInt32(); + delayLoadDirectoryTable.BoundDelayImportTable = data.ReadUInt32(); + delayLoadDirectoryTable.UnloadDelayImportTable = data.ReadUInt32(); + delayLoadDirectoryTable.TimeStamp = data.ReadUInt32(); + + return delayLoadDirectoryTable; + } + + /// + /// Parse a Stream into a base relocation table + /// + /// Stream to parse + /// First address not part of the base relocation table + /// Section table to use for virtual address translation + /// Filled base relocation table on success, null on error + private static BaseRelocationBlock[] ParseBaseRelocationTable(Stream data, int endOffset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var baseRelocationTable = new List(); + + while (data.Position < endOffset) + { + var baseRelocationBlock = new BaseRelocationBlock(); + + baseRelocationBlock.PageRVA = data.ReadUInt32(); + baseRelocationBlock.BlockSize = data.ReadUInt32(); + + var typeOffsetFieldEntries = new List(); + int totalSize = 8; + while (totalSize < baseRelocationBlock.BlockSize && data.Position < data.Length) + { + var baseRelocationTypeOffsetFieldEntry = new BaseRelocationTypeOffsetFieldEntry(); + + ushort typeAndOffsetField = data.ReadUInt16(); + baseRelocationTypeOffsetFieldEntry.BaseRelocationType = (BaseRelocationTypes)(typeAndOffsetField >> 12); + baseRelocationTypeOffsetFieldEntry.Offset = (ushort)(typeAndOffsetField & 0x0FFF); + + typeOffsetFieldEntries.Add(baseRelocationTypeOffsetFieldEntry); + totalSize += 2; + } + + baseRelocationBlock.TypeOffsetFieldEntries = typeOffsetFieldEntries.ToArray(); + + baseRelocationTable.Add(baseRelocationBlock); + } + + return baseRelocationTable.ToArray(); + } + + /// + /// Parse a Stream into a debug table + /// + /// Stream to parse + /// First address not part of the debug table + /// Section table to use for virtual address translation + /// Filled debug table on success, null on error + private static DebugTable ParseDebugTable(Stream data, int endOffset, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var debugTable = new DebugTable(); + + var debugDirectoryTable = new List(); + + while (data.Position < endOffset) + { + var debugDirectoryEntry = new DebugDirectoryEntry(); + + debugDirectoryEntry.Characteristics = data.ReadUInt32(); + debugDirectoryEntry.TimeDateStamp = data.ReadUInt32(); + debugDirectoryEntry.MajorVersion = data.ReadUInt16(); + debugDirectoryEntry.MinorVersion = data.ReadUInt16(); + debugDirectoryEntry.DebugType = (DebugType)data.ReadUInt32(); + debugDirectoryEntry.SizeOfData = data.ReadUInt32(); + debugDirectoryEntry.AddressOfRawData = data.ReadUInt32(); + debugDirectoryEntry.PointerToRawData = data.ReadUInt32(); + + debugDirectoryTable.Add(debugDirectoryEntry); + } + + debugTable.DebugDirectoryTable = debugDirectoryTable.ToArray(); + + // TODO: Should we read the debug data in? Most of it is unformatted or undocumented + // TODO: Implement .debug$F (Object Only) / IMAGE_DEBUG_TYPE_FPO + + return debugTable; + } + + /// + /// Parse a Stream into a export table + /// + /// Stream to parse + /// Section table to use for virtual address translation + /// Filled export table on success, null on error + private static ExportTable ParseExportTable(Stream data, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var exportTable = new ExportTable(); + + var exportDirectoryTable = new ExportDirectoryTable(); + + exportDirectoryTable.ExportFlags = data.ReadUInt32(); + exportDirectoryTable.TimeDateStamp = data.ReadUInt32(); + exportDirectoryTable.MajorVersion = data.ReadUInt16(); + exportDirectoryTable.MinorVersion = data.ReadUInt16(); + exportDirectoryTable.NameRVA = data.ReadUInt32(); + exportDirectoryTable.OrdinalBase = data.ReadUInt32(); + exportDirectoryTable.AddressTableEntries = data.ReadUInt32(); + exportDirectoryTable.NumberOfNamePointers = data.ReadUInt32(); + exportDirectoryTable.ExportAddressTableRVA = data.ReadUInt32(); + exportDirectoryTable.NamePointerRVA = data.ReadUInt32(); + exportDirectoryTable.OrdinalTableRVA = data.ReadUInt32(); + + exportTable.ExportDirectoryTable = exportDirectoryTable; + + // Name + if (exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections) != 0) + { + uint nameAddress = exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections); + data.Seek(nameAddress, SeekOrigin.Begin); + + string name = data.ReadString(Encoding.ASCII); + exportDirectoryTable.Name = name; + } + + // Address table + if (exportDirectoryTable.AddressTableEntries != 0 && exportDirectoryTable.ExportAddressTableRVA.ConvertVirtualAddress(sections) != 0) + { + uint exportAddressTableAddress = exportDirectoryTable.ExportAddressTableRVA.ConvertVirtualAddress(sections); + data.Seek(exportAddressTableAddress, SeekOrigin.Begin); + + var exportAddressTable = new ExportAddressTableEntry[exportDirectoryTable.AddressTableEntries]; + + for (int i = 0; i < exportDirectoryTable.AddressTableEntries; i++) + { + var addressTableEntry = new ExportAddressTableEntry(); + + // TODO: Use the optional header address and length to determine if export or forwarder + addressTableEntry.ExportRVA = data.ReadUInt32(); + addressTableEntry.ForwarderRVA = addressTableEntry.ExportRVA; + + exportAddressTable[i] = addressTableEntry; + } + + exportTable.ExportAddressTable = exportAddressTable; + } + + // Name pointer table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.NamePointerRVA.ConvertVirtualAddress(sections) != 0) + { + uint namePointerTableAddress = exportDirectoryTable.NamePointerRVA.ConvertVirtualAddress(sections); + data.Seek(namePointerTableAddress, SeekOrigin.Begin); + + var namePointerTable = new ExportNamePointerTable(); + + namePointerTable.Pointers = new uint[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + uint pointer = data.ReadUInt32(); + namePointerTable.Pointers[i] = pointer; + } + + exportTable.NamePointerTable = namePointerTable; + } + + // Ordinal table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.OrdinalTableRVA.ConvertVirtualAddress(sections) != 0) + { + uint ordinalTableAddress = exportDirectoryTable.OrdinalTableRVA.ConvertVirtualAddress(sections); + data.Seek(ordinalTableAddress, SeekOrigin.Begin); + + var exportOrdinalTable = new ExportOrdinalTable(); + + exportOrdinalTable.Indexes = new ushort[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + ushort pointer = data.ReadUInt16(); + exportOrdinalTable.Indexes[i] = pointer; + } + + exportTable.OrdinalTable = exportOrdinalTable; + } + + // Name table + if (exportDirectoryTable.NumberOfNamePointers != 0 && exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections) != 0) + { + uint nameTableAddress = exportDirectoryTable.NameRVA.ConvertVirtualAddress(sections); + data.Seek(nameTableAddress, SeekOrigin.Begin); + + var exportNameTable = new ExportNameTable(); + + exportNameTable.Strings = new string[exportDirectoryTable.NumberOfNamePointers]; + for (int i = 0; i < exportDirectoryTable.NumberOfNamePointers; i++) + { + string str = data.ReadString(Encoding.ASCII); + exportNameTable.Strings[i] = str; + } + + exportTable.ExportNameTable = exportNameTable; + } + + return exportTable; + } + + /// + /// Parse a Stream into a import table + /// + /// Stream to parse + /// Optional header magic number indicating PE32 or PE32+ + /// Section table to use for virtual address translation + /// Filled import table on success, null on error + private static ImportTable ParseImportTable(Stream data, OptionalHeaderMagicNumber magic, SectionHeader[] sections) + { + // TODO: Use marshalling here instead of building + var importTable = new ImportTable(); + + // Import directory table + var importDirectoryTable = new List(); + + // Loop until the last item (all nulls) are found + while (true) + { + var importDirectoryTableEntry = new ImportDirectoryTableEntry(); + + importDirectoryTableEntry.ImportLookupTableRVA = data.ReadUInt32(); + importDirectoryTableEntry.TimeDateStamp = data.ReadUInt32(); + importDirectoryTableEntry.ForwarderChain = data.ReadUInt32(); + importDirectoryTableEntry.NameRVA = data.ReadUInt32(); + importDirectoryTableEntry.ImportAddressTableRVA = data.ReadUInt32(); + + importDirectoryTable.Add(importDirectoryTableEntry); + + // All zero values means the last entry + if (importDirectoryTableEntry.ImportLookupTableRVA == 0 + && importDirectoryTableEntry.TimeDateStamp == 0 + && importDirectoryTableEntry.ForwarderChain == 0 + && importDirectoryTableEntry.NameRVA == 0 + && importDirectoryTableEntry.ImportAddressTableRVA == 0) + break; + } + + importTable.ImportDirectoryTable = importDirectoryTable.ToArray(); + + // Names + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.NameRVA.ConvertVirtualAddress(sections) == 0) + continue; + + uint nameAddress = importDirectoryTableEntry.NameRVA.ConvertVirtualAddress(sections); + data.Seek(nameAddress, SeekOrigin.Begin); + + string name = data.ReadString(Encoding.ASCII); + importDirectoryTableEntry.Name = name; + } + + // Lookup tables + var importLookupTables = new Dictionary(); + + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.ImportLookupTableRVA.ConvertVirtualAddress(sections) == 0) + continue; + + uint tableAddress = importDirectoryTableEntry.ImportLookupTableRVA.ConvertVirtualAddress(sections); + data.Seek(tableAddress, SeekOrigin.Begin); + + var entryLookupTable = new List(); + + while (true) + { + var entryLookupTableEntry = new ImportLookupTableEntry(); + + if (magic == OptionalHeaderMagicNumber.PE32) + { + uint entryValue = data.ReadUInt32(); + entryLookupTableEntry.OrdinalNameFlag = (entryValue & 0x80000000) != 0; + if (entryLookupTableEntry.OrdinalNameFlag) + entryLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x80000000); + else + entryLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x80000000); + } + else if (magic == OptionalHeaderMagicNumber.PE32Plus) + { + ulong entryValue = data.ReadUInt64(); + entryLookupTableEntry.OrdinalNameFlag = (entryValue & 0x8000000000000000) != 0; + if (entryLookupTableEntry.OrdinalNameFlag) + entryLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x8000000000000000); + else + entryLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x8000000000000000); + } + + entryLookupTable.Add(entryLookupTableEntry); + + // All zero values means the last entry + if (entryLookupTableEntry.OrdinalNameFlag == false + && entryLookupTableEntry.OrdinalNumber == 0 + && entryLookupTableEntry.HintNameTableRVA == 0) + break; + } + + importLookupTables[i] = entryLookupTable.ToArray(); + } + + importTable.ImportLookupTables = importLookupTables; + + // Address tables + var importAddressTables = new Dictionary(); + + for (int i = 0; i < importTable.ImportDirectoryTable.Length; i++) + { + var importDirectoryTableEntry = importTable.ImportDirectoryTable[i]; + if (importDirectoryTableEntry.ImportAddressTableRVA.ConvertVirtualAddress(sections) == 0) + continue; + + uint tableAddress = importDirectoryTableEntry.ImportAddressTableRVA.ConvertVirtualAddress(sections); + data.Seek(tableAddress, SeekOrigin.Begin); + + var addressLookupTable = new List(); + + while (true) + { + var addressLookupTableEntry = new ImportAddressTableEntry(); + + if (magic == OptionalHeaderMagicNumber.PE32) + { + uint entryValue = data.ReadUInt32(); + addressLookupTableEntry.OrdinalNameFlag = (entryValue & 0x80000000) != 0; + if (addressLookupTableEntry.OrdinalNameFlag) + addressLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x80000000); + else + addressLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x80000000); + } + else if (magic == OptionalHeaderMagicNumber.PE32Plus) + { + ulong entryValue = data.ReadUInt64(); + addressLookupTableEntry.OrdinalNameFlag = (entryValue & 0x8000000000000000) != 0; + if (addressLookupTableEntry.OrdinalNameFlag) + addressLookupTableEntry.OrdinalNumber = (ushort)(entryValue & ~0x8000000000000000); + else + addressLookupTableEntry.HintNameTableRVA = (uint)(entryValue & ~0x8000000000000000); + } + + addressLookupTable.Add(addressLookupTableEntry); + + // All zero values means the last entry + if (addressLookupTableEntry.OrdinalNameFlag == false + && addressLookupTableEntry.OrdinalNumber == 0 + && addressLookupTableEntry.HintNameTableRVA == 0) + break; + } + + importAddressTables[i] = addressLookupTable.ToArray(); + } + + importTable.ImportAddressTables = importAddressTables; + + // Hint/Name table + var importHintNameTable = new List(); + + if ((importTable.ImportLookupTables != null && importTable.ImportLookupTables.Count > 0) + || importTable.ImportAddressTables != null && importTable.ImportAddressTables.Count > 0) + { + // Get the addresses of the hint/name table entries + List hintNameTableEntryAddresses = new List(); + + // If we have import lookup tables + if (importTable.ImportLookupTables != null && importLookupTables.Count > 0) + { + var addresses = importTable.ImportLookupTables + .SelectMany(kvp => kvp.Value) + .Select(ilte => (int)ilte.HintNameTableRVA.ConvertVirtualAddress(sections)); + hintNameTableEntryAddresses.AddRange(addresses); + } + + // If we have import address tables + if (importTable.ImportAddressTables != null && importTable.ImportAddressTables.Count > 0) + { + var addresses = importTable.ImportAddressTables + .SelectMany(kvp => kvp.Value) + .Select(iate => (int)iate.HintNameTableRVA.ConvertVirtualAddress(sections)); + hintNameTableEntryAddresses.AddRange(addresses); + } + + // Sanitize the addresses + hintNameTableEntryAddresses = hintNameTableEntryAddresses.Where(addr => addr != 0) + .Distinct() + .OrderBy(a => a) + .ToList(); + + // If we have any addresses, add them to the table + if (hintNameTableEntryAddresses.Any()) + { + for (int i = 0; i < hintNameTableEntryAddresses.Count; i++) + { + int hintNameTableEntryAddress = hintNameTableEntryAddresses[i]; + data.Seek(hintNameTableEntryAddress, SeekOrigin.Begin); + + var hintNameTableEntry = new HintNameTableEntry(); + + hintNameTableEntry.Hint = data.ReadUInt16(); + hintNameTableEntry.Name = data.ReadString(Encoding.ASCII); + + importHintNameTable.Add(hintNameTableEntry); + } + } + } + + importTable.HintNameTable = importHintNameTable.ToArray(); + + return importTable; + } + + /// + /// Parse a Stream into a resource directory table + /// + /// Stream to parse + /// Initial offset to use in address comparisons + /// Section table to use for virtual address translation + /// Indicates if this is the top level or not + /// Filled resource directory table on success, null on error + private static ResourceDirectoryTable ParseResourceDirectoryTable(Stream data, long initialOffset, SectionHeader[] sections, bool topLevel = false) + { + // TODO: Use marshalling here instead of building + var resourceDirectoryTable = new ResourceDirectoryTable(); + + resourceDirectoryTable.Characteristics = data.ReadUInt32(); + if (resourceDirectoryTable.Characteristics != 0) + return null; + + resourceDirectoryTable.TimeDateStamp = data.ReadUInt32(); + resourceDirectoryTable.MajorVersion = data.ReadUInt16(); + resourceDirectoryTable.MinorVersion = data.ReadUInt16(); + resourceDirectoryTable.NumberOfNameEntries = data.ReadUInt16(); + resourceDirectoryTable.NumberOfIDEntries = data.ReadUInt16(); + + // If we have no entries + int totalEntryCount = resourceDirectoryTable.NumberOfNameEntries + resourceDirectoryTable.NumberOfIDEntries; + if (totalEntryCount == 0) + return resourceDirectoryTable; + + // Perform top-level pass of data + resourceDirectoryTable.Entries = new ResourceDirectoryEntry[totalEntryCount]; + for (int i = 0; i < totalEntryCount; i++) + { + var entry = new ResourceDirectoryEntry(); + uint offset = data.ReadUInt32(); + if ((offset & 0x80000000) != 0) + entry.NameOffset = offset & ~0x80000000; + else + entry.IntegerID = offset; + + offset = data.ReadUInt32(); + if ((offset & 0x80000000) != 0) + entry.SubdirectoryOffset = offset & ~0x80000000; + else + entry.DataEntryOffset = offset; + + // Read the name from the offset, if needed + if (entry.NameOffset > 0) + { + long currentOffset = data.Position; + offset = entry.NameOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + + var resourceDirectoryString = new ResourceDirectoryString(); + + resourceDirectoryString.Length = data.ReadUInt16(); + if (resourceDirectoryString.Length > 0) + resourceDirectoryString.UnicodeString = data.ReadBytes(resourceDirectoryString.Length * 2); + + entry.Name = resourceDirectoryString; + + data.Seek(currentOffset, SeekOrigin.Begin); + } + + resourceDirectoryTable.Entries[i] = entry; + } + + // Loop through and process the entries + foreach (var entry in resourceDirectoryTable.Entries) + { + if (entry.DataEntryOffset > 0) + { + uint offset = entry.DataEntryOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + + var resourceDataEntry = new ResourceDataEntry(); + resourceDataEntry.DataRVA = data.ReadUInt32(); + resourceDataEntry.Size = data.ReadUInt32(); + resourceDataEntry.Codepage = data.ReadUInt32(); + resourceDataEntry.Reserved = data.ReadUInt32(); + + // Read the data from the offset + offset = resourceDataEntry.DataRVA.ConvertVirtualAddress(sections); + if (offset > 0 && resourceDataEntry.Size > 0) + { + data.Seek(offset, SeekOrigin.Begin); + resourceDataEntry.Data = data.ReadBytes((int)resourceDataEntry.Size); + } + + entry.DataEntry = resourceDataEntry; + } + else if (entry.SubdirectoryOffset > 0) + { + uint offset = entry.SubdirectoryOffset + (uint)initialOffset; + data.Seek(offset, SeekOrigin.Begin); + + entry.Subdirectory = ParseResourceDirectoryTable(data, initialOffset, sections); + } + } + + // If we are not at the top level + if (!topLevel) + return resourceDirectoryTable; + + // If we're not aligned to a section + if (!sections.Any(s => s.PointerToRawData == initialOffset)) + return resourceDirectoryTable; + + // Get the section size + int size = (int)sections.First(s => s.PointerToRawData == initialOffset).SizeOfRawData; + + // Align to the 512-byte boundary, we find the start of an MS-DOS header, or the end of the file + while (data.Position - initialOffset < size && data.Position % 0x200 != 0 && data.Position < data.Length - 1) + { + // If we find the start of an MS-DOS header + if (data.ReadUInt16() == Models.MSDOS.Constants.SignatureUInt16) + { + data.Seek(-2, origin: SeekOrigin.Current); + break; + } + + // Otherwise + data.Seek(-1, origin: SeekOrigin.Current); + } + + // If we have not used up the full size, parse the remaining chunk as a single resource + if (data.Position - initialOffset < size) + { + Array.Resize(ref resourceDirectoryTable.Entries, totalEntryCount + 1); + int length = (int)(size - (data.Position - initialOffset)); + + resourceDirectoryTable.Entries[totalEntryCount] = new ResourceDirectoryEntry + { + Name = new ResourceDirectoryString { UnicodeString = Encoding.ASCII.GetBytes("HIDDEN RESOURCE") }, + IntegerID = uint.MaxValue, + DataEntryOffset = (uint)data.Position, + DataEntry = new ResourceDataEntry + { + Size = (uint)length, + Data = data.ReadBytes(length), + Codepage = (uint)Encoding.Unicode.CodePage, + }, + }; + } + + return resourceDirectoryTable; + } + } +} \ No newline at end of file diff --git a/Streams/PortableExecutable.Serializer.cs b/Streams/PortableExecutable.Serializer.cs new file mode 100644 index 00000000..9a833987 --- /dev/null +++ b/Streams/PortableExecutable.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.PortableExecutable; + +namespace SabreTools.Serialization.Streams +{ + public partial class PortableExecutable : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Executable obj) => throw new NotImplementedException(); +#else + public Stream? Serialize(Executable? obj) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file