From a8831a132343c449a7316f26603c8c22a3812332 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Sat, 28 Sep 2024 15:48:28 +0200 Subject: [PATCH] Add better support for resource directory --- ..._name=NativeConsole2Win64.exe.verified.txt | 19 +- ...r_name=NativeConsoleWin64.exe.verified.txt | 19 +- ...r_name=NativeLibraryWin64.dll.verified.txt | 21 +- src/LibObjectFile/ObjectFileElement.cs | 2 +- src/LibObjectFile/ObjectFileReaderWriter.cs | 17 +- .../DataDirectory/PEArchitectureDirectory.cs | 4 +- .../PEBaseRelocationDirectory.cs | 12 +- .../DataDirectory/PEBoundImportDirectory.cs | 26 +- .../PEBoundImportDirectoryEntry.cs | 2 +- .../PE/DataDirectory/PEClrMetadata.cs | 4 +- .../DataDirectory/PECompositeSectionData.cs | 107 ++++++++ .../PE/DataDirectory/PEDataDirectory.cs | 72 +----- .../PE/DataDirectory/PEDebugDirectory.cs | 51 +++- .../PE/DataDirectory/PEDebugSectionData.cs | 2 +- .../DataDirectory/PEDelayImportDirectory.cs | 41 +++- .../PE/DataDirectory/PEDirectoryTable.cs | 17 ++ .../PE/DataDirectory/PEExceptionDirectory.cs | 47 +++- .../PE/DataDirectory/PEExportDirectory.cs | 18 +- .../PE/DataDirectory/PEExportNameTable.cs | 2 +- .../DataDirectory/PEGlobalPointerDirectory.cs | 4 +- .../PEImportAddressTableDirectory.cs | 8 +- .../PE/DataDirectory/PEImportDirectory.cs | 25 +- .../PE/DataDirectory/PERawDataDirectory.cs | 4 +- .../PE/DataDirectory/PEResourceData.cs | 17 ++ .../PE/DataDirectory/PEResourceDataEntry.cs | 115 +++++---- .../PE/DataDirectory/PEResourceDirectory.cs | 176 ++------------ .../DataDirectory/PEResourceDirectoryEntry.cs | 229 +++++++++--------- .../PEResourceDirectoryEntryById.cs | 12 + .../PEResourceDirectoryEntryByName.cs | 12 + .../PE/DataDirectory/PEResourceEntry.cs | 142 +---------- .../PE/Internal/RawImageDebugDirectory.cs | 2 +- .../PE/Internal/RawImageOptionalHeader32.cs | 2 +- .../PE/Internal/RawImageSectionHeader.cs | 2 +- .../PE/Internal/RawPEBoundImportDirectory.cs | 2 +- .../Internal/RawPEBoundImportForwarderRef.cs | 2 +- src/LibObjectFile/PE/PEFile.Read.cs | 85 ++++--- src/LibObjectFile/PE/PEFile.Write.cs | 142 ++++++++++- src/LibObjectFile/PE/PEImageWriter.cs | 2 +- src/LibObjectFile/PE/PEObjectBase.cs | 18 +- src/LibObjectFile/PE/PEPrinter.cs | 21 +- src/LibObjectFile/PE/PESection.cs | 30 ++- src/LibObjectFile/PE/PESectionData.cs | 3 +- src/LibObjectFile/PE/PESectionName.cs | 9 +- src/LibObjectFile/PE/PEStreamSectionData.cs | 41 +++- 44 files changed, 937 insertions(+), 651 deletions(-) create mode 100644 src/LibObjectFile/PE/DataDirectory/PECompositeSectionData.cs create mode 100644 src/LibObjectFile/PE/DataDirectory/PEResourceData.cs create mode 100644 src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryById.cs create mode 100644 src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryByName.cs diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt index 31bd030..b8a3e31 100644 --- a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsole2Win64.exe.verified.txt @@ -605,11 +605,20 @@ Sections [04] .rsrc PESection Position = 0x00003E00, Size = 0x00000200, RVA = 0x00007000, VirtualSize = 0x000001E0, Characteristics = 0x40000040 (ContainsInitializedData, MemRead) [00] PEResourceDirectory Position = 0x00003E00, Size = 0x000001E0, RVA = 0x00007000, VirtualSize = 0x000001E0 - > PEResourceDirectoryEntry { Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDirectoryEntry { Id = 0x18 (RT_MANIFEST), Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDirectoryEntry { Id = 0x1, Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDataEntry { Id = 0x409 (en-US), Data = Stream (381 bytes) } - [00] PEStreamSectionData Position = 0x00003FD8, Size = 0x00000008, RVA = 0x000071D8, VirtualSize = 0x00000008 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x18, Entry = PEResourceDirectoryEntry { RVA = 0x7018, VirtualSize = 0x18, Position = 0x3E18, Size = 0x18, ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0 } + [00] PEResourceDirectoryEntry Position = 0x00003E18, Size = 0x00000018, RVA = 0x00007018, VirtualSize = 0x00000018 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x1, Entry = PEResourceDirectoryEntry { RVA = 0x7030, VirtualSize = 0x18, Position = 0x3E30, Size = 0x18, ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0 } + + [01] PEResourceDirectoryEntry Position = 0x00003E30, Size = 0x00000018, RVA = 0x00007030, VirtualSize = 0x00000018 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x409, Entry = PEResourceDataEntry { RVA = 0x7048, VirtualSize = 0x10, Position = 0x3E48, Size = 0x10, CodePage = , Data = PEResourceData { RVA = 0x7060, VirtualSize = 0x17D, Position = 0x3E60, Size = 0x17D } } + + [02] PEResourceDataEntry Position = 0x00003E48, Size = 0x00000010, RVA = 0x00007048, VirtualSize = 0x00000010 + > CodePage = null, Data = PEResourceData { RVA = 0x7060, VirtualSize = 0x17D, Position = 0x3E60, Size = 0x17D } + + [03] PEResourceData Position = 0x00003E60, Size = 0x0000017D, RVA = 0x00007060, VirtualSize = 0x0000017D -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt index c62d765..87af661 100644 --- a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeConsoleWin64.exe.verified.txt @@ -512,11 +512,20 @@ Sections [04] .rsrc PESection Position = 0x00002E00, Size = 0x00000200, RVA = 0x00007000, VirtualSize = 0x000001E0, Characteristics = 0x40000040 (ContainsInitializedData, MemRead) [00] PEResourceDirectory Position = 0x00002E00, Size = 0x000001E0, RVA = 0x00007000, VirtualSize = 0x000001E0 - > PEResourceDirectoryEntry { Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDirectoryEntry { Id = 0x18 (RT_MANIFEST), Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDirectoryEntry { Id = 0x1, Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDataEntry { Id = 0x409 (en-US), Data = Stream (381 bytes) } - [00] PEStreamSectionData Position = 0x00002FD8, Size = 0x00000008, RVA = 0x000071D8, VirtualSize = 0x00000008 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x18, Entry = PEResourceDirectoryEntry { RVA = 0x7018, VirtualSize = 0x18, Position = 0x2E18, Size = 0x18, ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0 } + [00] PEResourceDirectoryEntry Position = 0x00002E18, Size = 0x00000018, RVA = 0x00007018, VirtualSize = 0x00000018 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x1, Entry = PEResourceDirectoryEntry { RVA = 0x7030, VirtualSize = 0x18, Position = 0x2E30, Size = 0x18, ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0 } + + [01] PEResourceDirectoryEntry Position = 0x00002E30, Size = 0x00000018, RVA = 0x00007030, VirtualSize = 0x00000018 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x409, Entry = PEResourceDataEntry { RVA = 0x7048, VirtualSize = 0x10, Position = 0x2E48, Size = 0x10, CodePage = , Data = PEResourceData { RVA = 0x7060, VirtualSize = 0x17D, Position = 0x2E60, Size = 0x17D } } + + [02] PEResourceDataEntry Position = 0x00002E48, Size = 0x00000010, RVA = 0x00007048, VirtualSize = 0x00000010 + > CodePage = null, Data = PEResourceData { RVA = 0x7060, VirtualSize = 0x17D, Position = 0x2E60, Size = 0x17D } + + [03] PEResourceData Position = 0x00002E60, Size = 0x0000017D, RVA = 0x00007060, VirtualSize = 0x0000017D -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt index 4d8d3d7..ba98b56 100644 --- a/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt +++ b/src/LibObjectFile.Tests/Verified/PEReaderTests.TestPrinter_name=NativeLibraryWin64.dll.verified.txt @@ -436,11 +436,22 @@ Sections [04] .rsrc PESection Position = 0x00002600, Size = 0x00000200, RVA = 0x00005000, VirtualSize = 0x000000F8, Characteristics = 0x40000040 (ContainsInitializedData, MemRead) [00] PEResourceDirectory Position = 0x00002600, Size = 0x000000F8, RVA = 0x00005000, VirtualSize = 0x000000F8 - > PEResourceDirectoryEntry { Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDirectoryEntry { Id = 0x18 (RT_MANIFEST), Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDirectoryEntry { Id = 0x2, Entries[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0} - > PEResourceDataEntry { Id = 0x409 (en-US), Data = Stream (145 bytes) } - [00] PEStreamSectionData Position = 0x000026EC, Size = 0x0000000C, RVA = 0x000050EC, VirtualSize = 0x0000000C + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x18, Entry = PEResourceDirectoryEntry { RVA = 0x5018, VirtualSize = 0x18, Position = 0x2618, Size = 0x18, ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0 } + [00] PEResourceDirectoryEntry Position = 0x00002618, Size = 0x00000018, RVA = 0x00005018, VirtualSize = 0x00000018 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x2, Entry = PEResourceDirectoryEntry { RVA = 0x5030, VirtualSize = 0x18, Position = 0x2630, Size = 0x18, ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, MajorVersion = 0, MinorVersion = 0 } + + [01] PEResourceDirectoryEntry Position = 0x00002630, Size = 0x00000018, RVA = 0x00005030, VirtualSize = 0x00000018 + > ByNames[0], ByIds[1] , TimeDateStamp = 01/01/1970 00:00:00, Version = 0.0 + [0] Id = 0x409, Entry = PEResourceDataEntry { RVA = 0x5048, VirtualSize = 0x10, Position = 0x2648, Size = 0x10, CodePage = , Data = PEResourceData { RVA = 0x5060, VirtualSize = 0x91, Position = 0x2660, Size = 0x91 } } + + [02] PEResourceDataEntry Position = 0x00002648, Size = 0x00000010, RVA = 0x00005048, VirtualSize = 0x00000010 + > CodePage = null, Data = PEResourceData { RVA = 0x5060, VirtualSize = 0x91, Position = 0x2660, Size = 0x91 } + + [03] PEResourceData Position = 0x00002660, Size = 0x00000091, RVA = 0x00005060, VirtualSize = 0x00000091 + + [04] PEStreamSectionData Position = 0x000026F4, Size = 0x00000004, RVA = 0x000050F4, VirtualSize = 0x00000004 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/LibObjectFile/ObjectFileElement.cs b/src/LibObjectFile/ObjectFileElement.cs index bb6603e..bd3cad8 100644 --- a/src/LibObjectFile/ObjectFileElement.cs +++ b/src/LibObjectFile/ObjectFileElement.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/ObjectFileReaderWriter.cs b/src/LibObjectFile/ObjectFileReaderWriter.cs index 32ce896..8c9424d 100644 --- a/src/LibObjectFile/ObjectFileReaderWriter.cs +++ b/src/LibObjectFile/ObjectFileReaderWriter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -18,6 +18,7 @@ namespace LibObjectFile; public abstract class ObjectFileReaderWriter : VisitorContextBase { private Stream _stream; + private readonly byte[] _zeroBuffer = new byte[1024]; internal ObjectFileReaderWriter(ObjectFileElement file, Stream stream) : this(file, stream, new DiagnosticBag()) { @@ -192,6 +193,20 @@ public void Write(byte[] buffer, int offset, int count) Stream.Write(buffer, offset, count); } + /// + /// Writes count bytes with zero. + /// + /// The number of bytes to write with zero. + public void WriteZero(int count) + { + while (count > 0) + { + var size = Math.Min(count, _zeroBuffer.Length); + Stream.Write(_zeroBuffer, 0, size); + count -= size; + } + } + /// /// Writes to the and current position from the specified buffer. /// diff --git a/src/LibObjectFile/PE/DataDirectory/PEArchitectureDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEArchitectureDirectory.cs index 8285b80..a6f782e 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEArchitectureDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEArchitectureDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -18,7 +18,7 @@ public PEArchitectureDirectory() : base(PEDataDirectoryKind.Architecture) { } - protected override uint ComputeHeaderSize(PEVisitorContext context) + protected override uint ComputeHeaderSize(PELayoutContext context) { return 0; } diff --git a/src/LibObjectFile/PE/DataDirectory/PEBaseRelocationDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEBaseRelocationDirectory.cs index 29fcd97..e034cc5 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEBaseRelocationDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEBaseRelocationDirectory.cs @@ -19,7 +19,7 @@ public PEBaseRelocationDirectory() : base(PEDataDirectoryKind.BaseRelocation) public List Blocks { get; } = new(); - protected override unsafe uint ComputeHeaderSize(PEVisitorContext context) + protected override unsafe uint ComputeHeaderSize(PELayoutContext context) { var size = 0U; foreach (var block in Blocks) @@ -44,8 +44,6 @@ public override unsafe void Read(PEImageReader reader) return; } - var allSectionData = reader.File.GetAllSectionData(); - int blockIndex = 0; while (buffer.Length > 0) { @@ -59,7 +57,6 @@ public override unsafe void Read(PEImageReader reader) } var sizeOfRelocations = (int)location.SizeOfBlock - sizeof(ImageBaseRelocation); - // Create a block var block = new PEBaseRelocationBlock(new PESectionLink(section, (uint)(location.PageRVA - section.RVA))) @@ -89,7 +86,12 @@ internal override void Bind(PEImageReader reader) public override void Write(PEImageWriter writer) { - throw new NotImplementedException(); + ImageBaseRelocation rawBlock = default; + foreach (var block in Blocks) + { + rawBlock.PageRVA = block.SectionLink.RVA(); + rawBlock.SizeOfBlock = block.CalculateSizeOf(); + } } protected override bool PrintMembers(StringBuilder builder) diff --git a/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectory.cs index 4d998aa..7ce6601 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -30,9 +30,10 @@ public PEBoundImportDirectory() : base(PEDataDirectoryKind.BoundImport) public List Entries { get; } /// - protected override uint ComputeHeaderSize(PEVisitorContext context) + protected override unsafe uint ComputeHeaderSize(PELayoutContext context) { - var size = 0u; + // Last raw entry is zero + var size = (uint)sizeof(RawPEBoundImportDirectory); var entries = CollectionsMarshal.AsSpan(Entries); foreach (var entry in entries) { @@ -156,6 +157,23 @@ internal override void Bind(PEImageReader reader) /// public override void Write(PEImageWriter writer) { - throw new NotImplementedException(); + RawPEBoundImportDirectory rawEntry = default; + RawPEBoundImportForwarderRef rawForwarderRef = default; + foreach (var entry in Entries) + { + rawEntry.OffsetModuleName = (ushort)entry.ModuleName.RVO; + rawEntry.NumberOfModuleForwarderRefs = (ushort)entry.ForwarderRefs.Count; + writer.Write(rawEntry); + + foreach (var forwarderRef in entry.ForwarderRefs) + { + rawForwarderRef.OffsetModuleName = (ushort)forwarderRef.ModuleName.RVO; + writer.Write(rawForwarderRef); + } + } + + // Last entry is null + rawEntry = default; + writer.Write(rawEntry); } } \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectoryEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectoryEntry.cs index adb076a..66edea8 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectoryEntry.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEBoundImportDirectoryEntry.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/DataDirectory/PEClrMetadata.cs b/src/LibObjectFile/PE/DataDirectory/PEClrMetadata.cs index 74fb09e..8964545 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEClrMetadata.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEClrMetadata.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -12,7 +12,7 @@ public PEClrMetadata() : base(PEDataDirectoryKind.ClrMetadata) { } - protected override uint ComputeHeaderSize(PEVisitorContext context) + protected override uint ComputeHeaderSize(PELayoutContext context) { return 0; } diff --git a/src/LibObjectFile/PE/DataDirectory/PECompositeSectionData.cs b/src/LibObjectFile/PE/DataDirectory/PECompositeSectionData.cs new file mode 100644 index 0000000..56895bc --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PECompositeSectionData.cs @@ -0,0 +1,107 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using LibObjectFile.Collections; +using LibObjectFile.Utils; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace LibObjectFile.PE; + +/// +/// A section data that contains a list of and an optional header of data. +/// +public abstract class PECompositeSectionData : PESectionData +{ + protected PECompositeSectionData() + { + Content = CreateObjectList(this); + } + + public sealed override bool HasChildren => true; + + internal uint HeaderSize { get; private protected set; } + + /// + /// Gets the content of this directory. + /// + public ObjectList Content { get; } + + public sealed override void UpdateLayout(PELayoutContext context) + { + var va = RVA; + + // We compute the size of the directory header + // Each directory have a specific layout, so we delegate the computation to the derived class + var headerSize = ComputeHeaderSize(context); + HeaderSize = headerSize; + va += headerSize; + ulong size = headerSize; + + // A directory could have a content in addition to the header + // So we update the VirtualAddress of each content and update the layout + var position = Position + headerSize; + foreach (var data in Content) + { + // Make sure we align the position and the virtual address + var alignment = data.GetRequiredPositionAlignment(context.File); + + if (alignment > 1) + { + var newPosition = AlignHelper.AlignUp(position, alignment); + size += (uint)newPosition - position; + position = newPosition; + va = AlignHelper.AlignUp(va, alignment); + } + + data.RVA = va; + + // Update layout will update virtual address + if (!context.UpdateSizeOnly) + { + data.Position = position; + } + data.UpdateLayout(context); + + var dataSize = AlignHelper.AlignUp((uint)data.Size, data.GetRequiredSizeAlignment(context.File)); + va += (uint)dataSize; + size += dataSize; + position += dataSize; + } + + Size = size; + } + + internal virtual IEnumerable CollectImplicitSectionDataList() => Enumerable.Empty(); + + internal virtual void Bind(PEImageReader reader) + { + } + + internal void WriteHeaderAndContent(PEImageWriter writer) + { + Write(writer); + + foreach (var table in Content) + { + table.Write(writer); + } + } + + protected abstract uint ComputeHeaderSize(PELayoutContext context); + + protected sealed override bool TryFindByRVAInChildren(RVA rva, out PEObject? result) + => Content.TryFindByRVA(rva, true, out result); + + protected sealed override void UpdateRVAInChildren() + { + var va = RVA; + foreach (var table in Content) + { + table.UpdateRVA(va); + va += (uint)table.Size; + } + } +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs index 8f10e87..a9bec2f 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDataDirectory.cs @@ -3,75 +3,18 @@ // See the license.txt file in the project root for more information. using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata.Ecma335; -using LibObjectFile.Collections; -using LibObjectFile.Diagnostics; namespace LibObjectFile.PE; -public abstract class PEDataDirectory : PESectionData +public abstract class PEDataDirectory : PECompositeSectionData { protected PEDataDirectory(PEDataDirectoryKind kind) { Kind = kind; - Content = CreateObjectList(this); } public PEDataDirectoryKind Kind { get; } - public override bool HasChildren => true; - - internal uint HeaderSize { get; private protected set; } - - /// - /// Gets the content of this directory. - /// - public ObjectList Content { get; } - - public sealed override void UpdateLayout(PELayoutContext context) - { - var va = RVA; - - // We compute the size of the directory header - // Each directory have a specific layout, so we delegate the computation to the derived class - var headerSize = ComputeHeaderSize(context); - HeaderSize = headerSize; - va += headerSize; - ulong size = headerSize; - - // A directory could have a content in addition to the header - // So we update the VirtualAddress of each content and update the layout - var position = Position + headerSize; - foreach (var subData in Content) - { - subData.RVA = va; - - // Update layout will update virtual address - if (!context.UpdateSizeOnly) - { - subData.Position = position; - } - - subData.UpdateLayout(context); - - va += (uint)subData.Size; - size += subData.Size; - position += subData.Size; - } - - Size = size; - } - - internal virtual IEnumerable CollectImplicitSectionDataList() => Enumerable.Empty(); - - internal virtual void Bind(PEImageReader reader) - { - } - - protected abstract uint ComputeHeaderSize(PEVisitorContext context); - protected override void ValidateParent(ObjectElement parent) { if (parent is not PESection) @@ -80,19 +23,6 @@ protected override void ValidateParent(ObjectElement parent) } } - protected override bool TryFindByRVAInChildren(RVA rva, out PEObject? result) - => Content.TryFindByRVA(rva, true, out result); - - protected override void UpdateRVAInChildren() - { - var va = RVA; - foreach (var table in Content) - { - table.UpdateRVA(va); - va += (uint)table.Size; - } - } - /// /// Factory method to create a new instance of based on the kind. /// diff --git a/src/LibObjectFile/PE/DataDirectory/PEDebugDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEDebugDirectory.cs index 0b75f2a..d30d140 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDebugDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDebugDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -27,10 +27,6 @@ public override unsafe void Read(PEImageReader reader) var entryCount = size / sizeof(RawImageDebugDirectory); - var positionBeforeFirstSection = reader.File.Sections.Count > 0 ? reader.File.Sections[0].Position : 0; - var positionAfterLastSection = reader.File.Sections.Count > 0 ? reader.File.Sections[^1].Position + reader.File.Sections[^1].Size : 0; - - var buffer = ArrayPool.Shared.Rent(size); try { @@ -137,12 +133,51 @@ internal override IEnumerable CollectImplicitSectionDataList() } } - public override void Write(PEImageWriter writer) + public override unsafe void Write(PEImageWriter writer) { - throw new NotImplementedException(); + var entries = CollectionsMarshal.AsSpan(Entries); + var rawBufferSize = sizeof(RawImageDebugDirectory) * entries.Length; + var rawBuffer = ArrayPool.Shared.Rent(rawBufferSize); + try + { + var buffer = new Span(rawBuffer, 0, rawBufferSize); + var rawEntries = MemoryMarshal.Cast(buffer); + + RawImageDebugDirectory rawEntry = default; + for (var i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + rawEntry.Characteristics = entry.Characteristics; + rawEntry.MajorVersion = entry.MajorVersion; + rawEntry.MinorVersion = entry.MinorVersion; + rawEntry.TimeDateStamp = entry.TimeDateStamp; + rawEntry.Type = entry.Type; + + if (entry.SectionData is not null) + { + rawEntry.SizeOfData = (uint)entry.SectionData.Size; + rawEntry.AddressOfRawData = (uint)entry.SectionData.RVA; + rawEntry.PointerToRawData = 0; + } + else if (entry.ExtraData is not null) + { + rawEntry.SizeOfData = (uint)entry.ExtraData.Size; + rawEntry.AddressOfRawData = 0; + rawEntry.PointerToRawData = (uint)entry.ExtraData.Position; + } + + rawEntries[i] = rawEntry; + } + + writer.Write(rawBuffer); + } + finally + { + ArrayPool.Shared.Return(rawBuffer); + } } - protected override unsafe uint ComputeHeaderSize(PEVisitorContext context) + protected override unsafe uint ComputeHeaderSize(PELayoutContext context) { return (uint)(Entries.Count * sizeof(RawImageDebugDirectory)); } diff --git a/src/LibObjectFile/PE/DataDirectory/PEDebugSectionData.cs b/src/LibObjectFile/PE/DataDirectory/PEDebugSectionData.cs index 97adf2b..d366726 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDebugSectionData.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDebugSectionData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/DataDirectory/PEDelayImportDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEDelayImportDirectory.cs index ed253ba..9e407f8 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDelayImportDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDelayImportDirectory.cs @@ -1,10 +1,11 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using LibObjectFile.Diagnostics; using LibObjectFile.PE.Internal; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -203,7 +204,7 @@ internal override void Bind(PEImageReader reader) } } - protected override uint ComputeHeaderSize(PEVisitorContext context) => CalculateSize(); + protected override uint ComputeHeaderSize(PELayoutContext context) => CalculateSize(); private unsafe uint CalculateSize() @@ -212,8 +213,40 @@ private unsafe uint CalculateSize() } - public override void Write(PEImageWriter writer) + public override unsafe void Write(PEImageWriter writer) { - throw new NotImplementedException(); + var entries = CollectionsMarshal.AsSpan(Entries); + var rawBufferSize = sizeof(RawDelayLoadDescriptor) * (entries.Length + 1); + var rawBuffer = ArrayPool.Shared.Rent((int)rawBufferSize); + try + { + var buffer = new Span(rawBuffer, 0, (int)rawBufferSize); + var rawEntries = MemoryMarshal.Cast(buffer); + + RawDelayLoadDescriptor rawEntry = default; + for (var i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + rawEntry.Attributes = entry.Attributes; + rawEntry.NameRVA = (uint)entry.DllName.RVA(); + rawEntry.ModuleHandleRVA = (uint)entry.ModuleHandle.RVA(); + rawEntry.DelayLoadImportAddressTableRVA = (uint)entry.DelayImportAddressTable.RVA; + rawEntry.DelayLoadImportNameTableRVA = (uint)entry.DelayImportNameTable.RVA; + rawEntry.BoundDelayLoadImportAddressTableRVA = entry.BoundImportAddressTable?.RVA ?? 0; + rawEntry.UnloadDelayLoadImportAddressTableRVA = entry.UnloadDelayInformationTable?.RVA ?? 0; + rawEntry.TimeDateStamp = 0; + + rawEntries[i] = rawEntry; + } + + // Write the null entry + rawEntries[entries.Length] = default; + + writer.Write(rawBuffer); + } + finally + { + ArrayPool.Shared.Return(rawBuffer); + } } } \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs b/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs index 7bfc095..2c8614a 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEDirectoryTable.cs @@ -161,6 +161,23 @@ internal int CalculateNumberOfEntries() return count; } + + internal unsafe void Write(PEImageWriter writer, ref uint position) + { + var numberOfEntries = CalculateNumberOfEntries(); + for (int i = 0; i < numberOfEntries; i++) + { + ImageDataDirectory rawDataDirectory = default; + var entry = _entries[i]; + if (entry is not null) + { + rawDataDirectory.RVA = entry is PEDataDirectory dataDirectory ? dataDirectory.RVA : (uint)entry.Position; + rawDataDirectory.Size = (uint)entry.Size; + } + } + + position += (uint)(numberOfEntries * sizeof(ImageDataDirectory)); + } [InlineArray(15)] private struct InternalArray diff --git a/src/LibObjectFile/PE/DataDirectory/PEExceptionDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEExceptionDirectory.cs index c974bdd..f248f82 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEExceptionDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEExceptionDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -32,7 +32,7 @@ public PEExceptionDirectory() : base(PEDataDirectoryKind.Exception) public List Entries { get; } /// - protected override unsafe uint ComputeHeaderSize(PEVisitorContext context) + protected override unsafe uint ComputeHeaderSize(PELayoutContext context) { var machine = context.File.CoffHeader.Machine; uint entrySize; @@ -227,7 +227,48 @@ internal override void Bind(PEImageReader reader) /// public override void Write(PEImageWriter writer) { - throw new NotImplementedException(); + var machine = writer.PEFile.CoffHeader.Machine; + switch (machine) + { + case Machine.Amd64: + case Machine.I386: + WriteX86(writer); + break; + case Machine.Arm: + case Machine.Arm64: + WriteArm(writer); + break; + default: + // We don't write the exception directory for other architectures + return; + } + } + + private void WriteX86(PEImageWriter writer) + { + foreach (var entry in Entries) + { + var entryX86 = (PEExceptionFunctionEntryX86)entry; + writer.Write(new RawExceptionFunctionEntryX86 + { + BeginAddress = (uint)entryX86.BeginAddress.RVA(), + EndAddress = (uint)entryX86.EndAddress.RVA(), + UnwindInfoAddress = (uint)entryX86.UnwindInfoAddress.RVA() + }); + } + } + + private void WriteArm(PEImageWriter writer) + { + foreach (var entry in Entries) + { + var entryARM = (PEExceptionFunctionEntryArm)entry; + writer.Write(new RawExceptionFunctionEntryARM + { + BeginAddress = (uint)entryARM.BeginAddress.RVA(), + UnwindData = entryARM.UnwindData + }); + } } private void ReadEntriesArm(Span rawEntries) diff --git a/src/LibObjectFile/PE/DataDirectory/PEExportDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEExportDirectory.cs index 22d4579..545d30f 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEExportDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEExportDirectory.cs @@ -32,7 +32,7 @@ public PEExportDirectory() : base(PEDataDirectoryKind.Export) public PEExportOrdinalTable? ExportOrdinalTable { get; set; } - protected override unsafe uint ComputeHeaderSize(PEVisitorContext context) + protected override unsafe uint ComputeHeaderSize(PELayoutContext context) { return (uint)sizeof(RawImageExportDirectory); } @@ -157,7 +157,21 @@ internal override void Bind(PEImageReader reader) public override void Write(PEImageWriter writer) { - throw new NotImplementedException(); + var exportDirectory = new RawImageExportDirectory + { + TimeDateStamp = (uint)(TimeStamp - DateTime.UnixEpoch).TotalSeconds, + MajorVersion = MajorVersion, + MinorVersion = MinorVersion, + Base = OrdinalBase, + Name = NameLink.RVA(), + NumberOfFunctions = (uint)ExportFunctionAddressTable!.Values.Count, + NumberOfNames = (uint)ExportNameTable!.Values.Count, + AddressOfFunctions = (RVA)(uint)(ExportFunctionAddressTable?.RVA ?? (RVA)0), + AddressOfNames = (RVA)(uint)(ExportNameTable?.RVA ?? 0), + AddressOfNameOrdinals = (RVA)(uint)(ExportOrdinalTable?.RVA ?? 0) + }; + + writer.Write(exportDirectory); } private struct RawImageExportDirectory diff --git a/src/LibObjectFile/PE/DataDirectory/PEExportNameTable.cs b/src/LibObjectFile/PE/DataDirectory/PEExportNameTable.cs index a9976d0..600bc21 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEExportNameTable.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEExportNameTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/DataDirectory/PEGlobalPointerDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEGlobalPointerDirectory.cs index 3018097..e0f4b9e 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEGlobalPointerDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEGlobalPointerDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -18,7 +18,7 @@ public PEGlobalPointerDirectory() : base(PEDataDirectoryKind.GlobalPointer) { } - protected override uint ComputeHeaderSize(PEVisitorContext context) + protected override uint ComputeHeaderSize(PELayoutContext context) { return 0; } diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs index a60c264..36b5629 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEImportAddressTableDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -18,7 +18,9 @@ public override void Read(PEImageReader reader) { } - public override void Write(PEImageWriter writer) => throw new NotSupportedException(); // Not called directly for this object, we are calling on tables directly + public override void Write(PEImageWriter writer) + { + } - protected override uint ComputeHeaderSize(PEVisitorContext context) => 0; + protected override uint ComputeHeaderSize(PELayoutContext context) => 0; } \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs index 96ca935..7dd5feb 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEImportDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -101,7 +101,7 @@ public override void Read(PEImageReader reader) } } - protected override unsafe uint ComputeHeaderSize(PEVisitorContext context) => CalculateSize(); + protected override unsafe uint ComputeHeaderSize(PELayoutContext context) => CalculateSize(); internal override IEnumerable CollectImplicitSectionDataList() { @@ -151,17 +151,22 @@ internal override void Bind(PEImageReader reader) private unsafe uint CalculateSize() { - return _entries.Count == 0 ? 0 : (uint)(((_entries.Count + 1) * sizeof(RawImportDirectoryEntry))); + return (uint)(((_entries.Count + 1) * sizeof(RawImportDirectoryEntry))); } public override void Write(PEImageWriter writer) { - throw new NotImplementedException(); - } + RawImportDirectoryEntry rawEntry = default; + foreach (var entry in Entries) + { + rawEntry.NameRVA = (uint)entry.ImportDllNameLink.RVA(); + rawEntry.ImportLookupTableRVA = (uint)entry.ImportLookupTable.RVA; + rawEntry.ImportAddressTableRVA = (uint)entry.ImportAddressTable.RVA; + writer.Write(rawEntry); + } - //private struct HintNameTableEntry - //{ - // public ushort Hint; - // public byte Name1stByte; - //} + // Null entry + rawEntry = default; + writer.Write(rawEntry); + } } \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PERawDataDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PERawDataDirectory.cs index 967fb00..8e06b9a 100644 --- a/src/LibObjectFile/PE/DataDirectory/PERawDataDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PERawDataDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -96,7 +96,7 @@ public override void Write(PEImageWriter writer) public override void WriteAt(uint offset, ReadOnlySpan source) => DataUtils.WriteAt(_rawData, offset, source); - protected override uint ComputeHeaderSize(PEVisitorContext context) + protected override uint ComputeHeaderSize(PELayoutContext context) // Size if the first field of the Load Configuration Directory => (uint)RawDataSize; } \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceData.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceData.cs new file mode 100644 index 0000000..643aa0f --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceData.cs @@ -0,0 +1,17 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.PE; + +/// +/// A section data that contains a resource data. +/// +public sealed class PEResourceData : PEStreamSectionData +{ + public PEResourceData() + { + RequiredPositionAlignment = 4; + RequiredSizeAlignment = 4; + } +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceDataEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceDataEntry.cs index dfbf466..d45c9da 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEResourceDataEntry.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceDataEntry.cs @@ -1,12 +1,11 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. -using System; -using System.Diagnostics; -using System.IO; using System.Text; +using LibObjectFile.Diagnostics; using LibObjectFile.PE.Internal; +using LibObjectFile.Utils; namespace LibObjectFile.PE; @@ -15,27 +14,28 @@ namespace LibObjectFile.PE; /// public sealed class PEResourceDataEntry : PEResourceEntry { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private object? _data; + internal PEResourceDataEntry() + { + Data = null!; + } /// - /// Initializes a new instance of the class with the specified name. + /// Initializes a new instance of the class. /// - /// The name of the resource data entry. - public PEResourceDataEntry(string name) : base(name) + public PEResourceDataEntry(PEResourceData data) { - Data = Stream.Null; + Data = data; } /// - /// Initializes a new instance of the class with the specified ID. + /// Initializes a new instance of the class. /// - /// The ID of the resource data entry. - public PEResourceDataEntry(PEResourceId id) : base(id) + public PEResourceDataEntry(Encoding? codePage, PEResourceData data) { - Data = Stream.Null; + CodePage = codePage; + Data = data; } - + /// /// Gets or sets the code page used for encoding the data. /// @@ -50,60 +50,69 @@ public PEResourceDataEntry(PEResourceId id) : base(id) /// /// The data can be a string, a stream, or a byte array. /// - public object? Data + public PEResourceData Data { get; set; } + + protected override bool PrintMembers(StringBuilder builder) { - get => _data; - set + if (base.PrintMembers(builder)) { - if (value is not string && value is not Stream && value is not byte[]) - { - throw new ArgumentException("Invalid data type. Expecting a string, a Stream or a byte[]"); - } - - _data = value; + builder.Append(", "); } + + builder.Append($"{nameof(CodePage)} = {CodePage?.EncodingName}, {nameof(Data)} = {Data}"); + return true; } - private protected override unsafe uint ComputeSize() + public override unsafe void UpdateLayout(PELayoutContext layoutContext) { - uint dataSize = 0; + Size = (uint)sizeof(RawImageResourceDataEntry); + } - if (Data is string text) - { - dataSize = (uint)(CodePage?.GetByteCount(text) ?? text.Length * 2); - } - else if (Data is Stream stream) + internal override unsafe void Read(in ReaderContext context) + { + var reader = context.Reader; + + reader.Position = Position; + Size = (uint)sizeof(RawImageResourceDataEntry); + + RawImageResourceDataEntry rawDataEntry; + if (!reader.TryReadData(sizeof(RawImageResourceDataEntry), out rawDataEntry)) { - dataSize = (uint)stream.Length; + reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource data entry at position {reader.Position}"); + return; } - else if (Data is byte[] buffer) + + CodePage = rawDataEntry.CodePage != 0 ? Encoding.GetEncoding((int)rawDataEntry.CodePage) : null; + + var peFile = context.Reader.File; + if (!peFile.TryFindSection(rawDataEntry.OffsetToData, out var section)) { - dataSize = (uint)buffer.Length; + reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntryRVAOffsetToData, $"Invalid resource data entry RVA OffsetToData {rawDataEntry.OffsetToData} at position {reader.Position}"); + return; } - return (uint)(sizeof(RawImageResourceDataEntry) + dataSize); - } + Data = new PEResourceData + { + Position = section.Position + rawDataEntry.OffsetToData - section.RVA, + Size = rawDataEntry.Size, + }; - protected override bool PrintMembers(StringBuilder builder) - { - if (base.PrintMembers(builder)) + // If we find that the position is not aligned on 4 bytes as we expect, reset it to 1 byte alignment + var checkPosition = AlignHelper.AlignUp(Data.Position, Data.RequiredPositionAlignment); + if (checkPosition != Data.Position) { - builder.Append(", "); + Data.RequiredPositionAlignment = 1; } - - switch (Data) + else if (context.ResourceDataList.Count == 0 && (Data.Position & 0xF) == 0) { - case string text: - builder.Append($"Data = {text}"); - break; - case Stream stream: - builder.Append($"Data = Stream ({stream.Length} bytes)"); - break; - case byte[] buffer: - builder.Append($"Data = byte[{buffer.Length}]"); - break; + // If we are the first resource data entry and the position is aligned on 16 bytes, we can assume this alignment + Data.RequiredPositionAlignment = 16; } + + // Read the data + Data.Read(reader); - return true; + // Register the list of data being loaded + context.ResourceDataList.Add(Data); } -} +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceDirectory.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectory.cs index 1cef981..434c314 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEResourceDirectory.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectory.cs @@ -1,25 +1,20 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System; -using System.Buffers; -using System.Collections; using System.Collections.Generic; -using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Text; -using LibObjectFile.Diagnostics; -using LibObjectFile.PE.Internal; -using LibObjectFile.Utils; namespace LibObjectFile.PE; /// /// Represents a resource directory in a Portable Executable (PE) file. /// -public sealed class PEResourceDirectory : PEDataDirectory, IEnumerable +public sealed class PEResourceDirectory : PEDataDirectory { + private List? _tempResourceDataList; + + /// /// Initializes a new instance of the class. /// @@ -35,173 +30,46 @@ public PEResourceDirectory() : base(PEDataDirectoryKind.Resource) /// Gets the root resource directory entry. /// public PEResourceDirectoryEntry Root { get; } - + /// - protected override uint ComputeHeaderSize(PEVisitorContext context) + protected override uint ComputeHeaderSize(PELayoutContext context) { - var size = Root.ComputeFullSize(); - size = (uint)AlignHelper.AlignUp(size, 4); - return size; + Root.UpdateLayout(context); + return (uint)Root.Size; } /// public override void Read(PEImageReader reader) { reader.Position = Position; - var currentDirectory = Root; - ReadDirectory(reader, currentDirectory); - - HeaderSize = ComputeHeaderSize(reader); - } - - private unsafe void ReadDirectory(PEImageReader reader, PEResourceDirectoryEntry currentDirectory) - { - if (!reader.TryReadData(sizeof(RawImageResourceDirectory), out var data)) - { - reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource directory at position {reader.Position}"); - return; - } - - currentDirectory.TimeDateStamp = DateTime.UnixEpoch.AddSeconds(data.TimeDateStamp); - currentDirectory.MajorVersion = data.MajorVersion; - currentDirectory.MinorVersion = data.MinorVersion; - var buffer = new byte[(data.NumberOfNamedEntries + data.NumberOfIdEntries) * sizeof(RawImageResourceDirectoryEntry)]; - var spanEntries = MemoryMarshal.Cast(buffer); - - int read = reader.Read(buffer); - if (read != buffer.Length) - { - reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource directory at position {reader.Position}"); - return; - } + Root.Position = Position; - for (int i = 0; i < data.NumberOfNamedEntries + data.NumberOfIdEntries; i++) - { - var entry = spanEntries[i]; - ReadEntry(reader, currentDirectory, entry); + _tempResourceDataList = new(); + var readerContext = new PEResourceEntry.ReaderContext(reader, this, _tempResourceDataList); + Root.Read(readerContext); - if (reader.Diagnostics.HasErrors) - { - return; - } - } + HeaderSize = ComputeHeaderSize(reader); } - private unsafe void ReadEntry(PEImageReader reader, PEResourceDirectoryEntry parent, RawImageResourceDirectoryEntry rawEntry) + internal override IEnumerable CollectImplicitSectionDataList() { - string? name = null; - int id = 0; - - if ((rawEntry.NameOrId & IMAGE_RESOURCE_NAME_IS_STRING) != 0) - { - // Read the string - var length = reader.ReadU16(); - var buffer = ArrayPool.Shared.Rent(length); - try - { - int readLength = reader.Read(buffer, 0, length); - if (readLength != length) - { - reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource directory string at position {reader.Position}"); - return; - } - name = Encoding.Unicode.GetString(buffer, 0, readLength); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - else - { - id = (int)(rawEntry.NameOrId & ~IMAGE_RESOURCE_NAME_IS_STRING); - } - - - bool isDirectory = (rawEntry.OffsetToDataOrDirectoryEntry & IMAGE_RESOURCE_DATA_IS_DIRECTORY) != 0; - var offset = rawEntry.OffsetToDataOrDirectoryEntry & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; - PEResourceEntry entry = isDirectory - ? (name is null) ? new PEResourceDirectoryEntry(new PEResourceId(id)) : new PEResourceDirectoryEntry(name) - : (name is null) ? new PEResourceDataEntry(new PEResourceId(id)) : new PEResourceDataEntry(name); - - parent.Entries.Add(entry); - - reader.Position = Position + offset; - - if (isDirectory) - { - var directory = (PEResourceDirectoryEntry)entry; - ReadDirectory(reader, directory); - } - else + if (_tempResourceDataList is not null) { - var dataEntry = (PEResourceDataEntry)entry; - RawImageResourceDataEntry rawDataEntry; - if (!reader.TryReadData(sizeof(RawImageResourceDataEntry), out rawDataEntry)) + foreach (var data in _tempResourceDataList) { - reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource data entry at position {reader.Position}"); - return; + yield return data; } - dataEntry.CodePage = rawDataEntry.CodePage != 0 ? Encoding.GetEncoding((int)rawDataEntry.CodePage) : null; - - if (!reader.File.TryFindSection(rawDataEntry.OffsetToData, out var section)) - { - reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntryRVAOffsetToData, $"Invalid resource data entry at position {reader.Position}. The RVA {rawDataEntry.OffsetToData} does not map to an existing section."); - return; - } - - var position = section.Position + rawDataEntry.OffsetToData - section.RVA; - reader.Position = position; - - if (dataEntry.CodePage != null) - { - var buffer = ArrayPool.Shared.Rent((int)rawDataEntry.Size); - try - { - int read = reader.Read(buffer, 0, (int)rawDataEntry.Size); - if (read != rawDataEntry.Size) - { - reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource data entry at position {reader.Position}"); - return; - } - dataEntry.Data = dataEntry.CodePage.GetString(buffer, 0, (int)rawDataEntry.Size); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - else - { - dataEntry.Data = reader.ReadAsStream(rawDataEntry.Size); - } + // We clear the list after being used - as this method is called once and we don't want to hold a reference + _tempResourceDataList.Clear(); + _tempResourceDataList = null; } } - + /// public override void Write(PEImageWriter writer) { throw new NotImplementedException(); } - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public List.Enumerator GetEnumerator() => Root.GetEnumerator(); - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return Root.GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)Root).GetEnumerator(); - } - - private const uint IMAGE_RESOURCE_NAME_IS_STRING = 0x80000000; - private const uint IMAGE_RESOURCE_DATA_IS_DIRECTORY = 0x80000000; } diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntry.cs index e04f6ff..54ed5db 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntry.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntry.cs @@ -1,16 +1,14 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using LibObjectFile.Diagnostics; +using LibObjectFile.PE.Internal; using System; -using System.Collections; +using System.Buffers; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; -using LibObjectFile.Collections; -using LibObjectFile.PE.Internal; namespace LibObjectFile.PE; @@ -21,39 +19,15 @@ namespace LibObjectFile.PE; /// This class provides functionality to manage a directory entry in the . /// It allows adding, removing, and updating resource entries within the directory. /// -public sealed class PEResourceDirectoryEntry : PEResourceEntry, IEnumerable +public sealed class PEResourceDirectoryEntry : PEResourceEntry { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Dictionary _nameToIndex = new(); - - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Dictionary _idToIndex = new(); - /// /// Initializes a new instance of the class. /// - internal PEResourceDirectoryEntry() - { - Entries = new ObjectList(this, adding: AddingEntry, removing: RemovingEntry, updating: UpdatingEntry); - } - - /// - /// Initializes a new instance of the class with the specified name. - /// - /// The name of the resource directory entry. - public PEResourceDirectoryEntry(string name) : base(name) - { - ArgumentNullException.ThrowIfNull(name); - Entries = new ObjectList(this, adding: AddingEntry, removing: RemovingEntry, updating: UpdatingEntry); - } - - /// - /// Initializes a new instance of the class with the specified ID. - /// - /// The ID of the resource directory entry. - public PEResourceDirectoryEntry(PEResourceId id) : base(id) + public PEResourceDirectoryEntry() { - Entries = new ObjectList(this); + ByNames = new(); + ByIds = new(); } /// @@ -74,120 +48,142 @@ public PEResourceDirectoryEntry(PEResourceId id) : base(id) /// /// Gets the list of resource entries within the directory. /// - public ObjectList Entries { get; } - - /// - /// Determines whether the directory contains a resource entry with the specified name. - /// - /// The name of the resource entry. - /// true if the directory contains a resource entry with the specified name; otherwise, false. - public bool Contains(string name) => _nameToIndex.ContainsKey(name); - - /// - /// Determines whether the directory contains a resource entry with the specified ID. - /// - /// The ID of the resource entry. - /// true if the directory contains a resource entry with the specified ID; otherwise, false. - public bool Contains(PEResourceId id) => _idToIndex.ContainsKey(id); + public List ByNames { get; } /// - /// Tries to get the resource entry with the specified name from the directory. + /// Gets the list of resource entries within the directory. /// - /// The name of the resource entry. - /// When this method returns, contains the resource entry with the specified name, if found; otherwise, null. - /// true if the resource entry with the specified name is found; otherwise, false. - public bool TryGetEntry(string name, out PEResourceEntry? entry) + public List ByIds { get; } + + internal override unsafe void Read(in ReaderContext context) { - if (_nameToIndex.TryGetValue(name, out var index)) + var reader = context.Reader; + var directory = context.Directory; + + reader.Position = Position; + if (!reader.TryReadData(sizeof(RawImageResourceDirectory), out var data)) { - entry = Entries[index]; - return true; + reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource directory at position {reader.Position}"); + return; } - entry = null; - return false; - } + TimeDateStamp = DateTime.UnixEpoch.AddSeconds(data.TimeDateStamp); + MajorVersion = data.MajorVersion; + MinorVersion = data.MinorVersion; - /// - /// Tries to get the resource entry with the specified ID from the directory. - /// - /// The ID of the resource entry. - /// When this method returns, contains the resource entry with the specified ID, if found; otherwise, null. - /// true if the resource entry with the specified ID is found; otherwise, false. - public bool TryGetEntry(PEResourceId id, out PEResourceEntry? entry) - { - if (_idToIndex.TryGetValue(id, out var index)) + var buffer = new byte[(data.NumberOfNamedEntries + data.NumberOfIdEntries) * sizeof(RawImageResourceDirectoryEntry)]; + var spanEntries = MemoryMarshal.Cast(buffer); + + int read = reader.Read(buffer); + if (read != buffer.Length) { - entry = Entries[index]; - return true; + reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource directory at position {reader.Position}"); + return; } - entry = null; - return false; - } + // Read all entries + for (int i = 0; i < data.NumberOfNamedEntries + data.NumberOfIdEntries; i++) + { + var entry = spanEntries[i]; + ReadEntry(reader, directory, entry); - /// - /// Adds the specified resource entry to the directory. - /// - /// The resource entry to add. - public void Add(PEResourceEntry entry) => Entries.Add(entry); + if (reader.Diagnostics.HasErrors) + { + return; + } + } - /// - /// Gets an enumerator that iterates through the resource entries in the directory. - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public List.Enumerator GetEnumerator() => Entries.GetEnumerator(); + // Update the size + Size = reader.Position - Position; - IEnumerator IEnumerable.GetEnumerator() - { - return Entries.GetEnumerator(); - } + var size = CalculateSize(); + if (Size != size) + { - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)Entries).GetEnumerator(); + } + + + // Process all the entries recursively + var byNames = CollectionsMarshal.AsSpan(ByNames); + foreach (ref var entry in byNames) + { + entry.Entry.Read(context); + } + + var byIds = CollectionsMarshal.AsSpan(ByIds); + foreach (ref var entry in byIds) + { + entry.Entry.Read(context); + } } - private static void AddingEntry(ObjectElement parent, int index, PEResourceEntry entry) + private void ReadEntry(PEImageReader reader, PEResourceDirectory directory, RawImageResourceDirectoryEntry rawEntry) { - var directory = (PEResourceDirectoryEntry)parent; - if (entry.Name != null) + string? name = null; + int id = 0; + + if ((rawEntry.NameOrId & IMAGE_RESOURCE_NAME_IS_STRING) != 0) { - directory._nameToIndex.Add(entry.Name, index); + // Read the string + var length = reader.ReadU16() * 2; + var buffer = ArrayPool.Shared.Rent(length); + try + { + int readLength = reader.Read(buffer, 0, length); + if (readLength != length) + { + reader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidResourceDirectoryEntry, $"Invalid resource directory string at position {reader.Position}"); + return; + } + name = Encoding.Unicode.GetString(buffer, 0, readLength); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } else { - directory._idToIndex.Add(entry.Id, index); + id = (int)(rawEntry.NameOrId & ~IMAGE_RESOURCE_NAME_IS_STRING); } - } + + bool isDirectory = (rawEntry.OffsetToDataOrDirectoryEntry & IMAGE_RESOURCE_DATA_IS_DIRECTORY) != 0; + var offset = rawEntry.OffsetToDataOrDirectoryEntry & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; - private static void RemovingEntry(ObjectElement parent, PEResourceEntry entry) - { - var directory = (PEResourceDirectoryEntry)parent; - if (entry.Name != null) + PEResourceEntry entry = isDirectory ? new PEResourceDirectoryEntry() : new PEResourceDataEntry(); + entry.Position = directory.Position + offset; + + if (name is not null) { - directory._nameToIndex.Remove(entry.Name); + ByNames.Add(new(name, entry)); } else { - directory._idToIndex.Remove(entry.Id); + ByIds.Add(new(new(id), entry)); } + + // Add the content to the directory (as we have the guarantee that the content belongs to the resource directory) + directory.Content.Add(entry); } - private static void UpdatingEntry(ObjectElement parent, int index, PEResourceEntry previousEntry, PEResourceEntry entry) + public override unsafe void UpdateLayout(PELayoutContext layoutContext) { - RemovingEntry(parent, previousEntry); - AddingEntry(parent, index, entry); + Size = CalculateSize(); } - private protected override unsafe uint ComputeSize() + private unsafe uint CalculateSize() { - var entries = CollectionsMarshal.AsSpan(Entries.UnsafeList); - uint size = (uint)sizeof(RawImageResourceDirectory); - foreach (var entry in entries) + var size = 0U; + size += (uint)sizeof(RawImageResourceDirectory); + size += (uint)(ByNames.Count + ByIds.Count) * (uint)sizeof(RawImageResourceDirectoryEntry); + + if (ByNames.Count > 0) { - size += entry.ComputeFullSize(); + var byNames = CollectionsMarshal.AsSpan(ByNames); + foreach (ref readonly var entry in byNames) + { + size += sizeof(ushort) + (uint)entry.Name.Length * 2; + } } return size; @@ -200,8 +196,11 @@ protected override bool PrintMembers(StringBuilder builder) builder.Append(", "); } - builder.Append($"Entries[{Entries.Count}] , TimeDateStamp = {TimeDateStamp}, MajorVersion = {MajorVersion}, MinorVersion = {MinorVersion}"); + builder.Append($"ByNames[{ByNames.Count}], ByIds[{ByIds.Count}] , TimeDateStamp = {TimeDateStamp}, MajorVersion = {MajorVersion}, MinorVersion = {MinorVersion}"); - return false; + return true; } -} + + private const uint IMAGE_RESOURCE_NAME_IS_STRING = 0x80000000; + private const uint IMAGE_RESOURCE_DATA_IS_DIRECTORY = 0x80000000; +} \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryById.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryById.cs new file mode 100644 index 0000000..67538ad --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryById.cs @@ -0,0 +1,12 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.PE; + +/// +/// Represents a resource directory entry with an ID in a PE file. +/// +/// The identifier of the resource directory entry. +/// The resource entry associated with the identifier. +public readonly record struct PEResourceDirectoryEntryById(PEResourceId Id, PEResourceEntry Entry); \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryByName.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryByName.cs new file mode 100644 index 0000000..59b0463 --- /dev/null +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceDirectoryEntryByName.cs @@ -0,0 +1,12 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.PE; + +/// +/// Represents a resource directory entry with a name in a PE file. +/// +/// The name of the resource directory entry. +/// The resource entry associated with the name. +public readonly record struct PEResourceDirectoryEntryByName(string Name, PEResourceEntry Entry); \ No newline at end of file diff --git a/src/LibObjectFile/PE/DataDirectory/PEResourceEntry.cs b/src/LibObjectFile/PE/DataDirectory/PEResourceEntry.cs index 8241942..64abb72 100644 --- a/src/LibObjectFile/PE/DataDirectory/PEResourceEntry.cs +++ b/src/LibObjectFile/PE/DataDirectory/PEResourceEntry.cs @@ -1,147 +1,19 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. -using System; -using System.Globalization; -using System.Text; -using LibObjectFile.PE.Internal; +using System.Collections.Generic; namespace LibObjectFile.PE; /// /// Represents an abstract base class for a PE resource entry. /// -public abstract class PEResourceEntry : ObjectElement +public abstract class PEResourceEntry : PESectionData { - /// - /// Initializes a new instance of the class with a default ID of -1. - /// - private protected PEResourceEntry() - { - Id = new(-1); - } + public sealed override bool HasChildren => false; + + internal abstract void Read(in ReaderContext context); - /// - /// Initializes a new instance of the class with the specified name. - /// - /// The name of the resource entry. - protected PEResourceEntry(string? name) - { - Name = name; - } - - /// - /// Initializes a new instance of the class with the specified ID. - /// - /// The ID of the resource entry. - protected PEResourceEntry(PEResourceId id) - { - ArgumentOutOfRangeException.ThrowIfLessThan(id.Value, 0, nameof(id)); - Id = id; - } - - /// - /// Gets the name of the resource entry. - /// - public string? Name { get; } - - /// - /// Gets the ID of the resource entry. - /// - public PEResourceId Id { get; } - - /// - /// Gets a value indicating whether the resource entry is the root entry. - /// - public bool IsRoot => Id.Value < 0; - - /// - /// Gets the level of the resource entry in the resource directory hierarchy. - /// - /// The level of the resource entry. - public int GetLevel() - { - var level = 0; - ObjectElement? parent = this; - while (parent is not null && parent is not PEResourceDirectory) - { - parent = parent.Parent; - level++; - } - - if (parent is PEResourceDirectory) - { - level--; - } - else - { - level = -1; - } - - return level; - } - - /// - /// Computes the full size of the resource entry. - /// - /// The full size of the resource entry. - internal unsafe uint ComputeFullSize() - { - var size = Name != null ? (uint)Name.Length * 2 + sizeof(ushort) : 0; - return (uint)(ComputeSize() + size + (IsRoot ? 0 : sizeof(RawImageResourceDirectoryEntry))); - } - - /// - /// Computes the size of the resource entry. - /// - /// The size of the resource entry. - private protected abstract uint ComputeSize(); - - - /// - protected override bool PrintMembers(StringBuilder builder) - { - if (!IsRoot) - { - if (Name != null) - { - builder.Append($"Name = {Name}"); - } - else - { - builder.Append($"Id = {Id}"); - var level = GetLevel(); - if (level >= 0) - { - switch (level) - { - case 1: - if (Id.TryGetWellKnownTypeName(out var name)) - { - builder.Append($" ({name})"); - } - break; - case 2: - break; - case 3: - try - { - var cultureInfo = CultureInfo.GetCultureInfo(Id.Value); - builder.Append($" ({cultureInfo.Name})"); - } - catch (CultureNotFoundException) - { - } - - break; - } - } - } - - return true; - } - - return false; - } + internal readonly record struct ReaderContext(PEImageReader Reader, PEResourceDirectory Directory, List ResourceDataList); } diff --git a/src/LibObjectFile/PE/Internal/RawImageDebugDirectory.cs b/src/LibObjectFile/PE/Internal/RawImageDebugDirectory.cs index f382ced..dc9d9d1 100644 --- a/src/LibObjectFile/PE/Internal/RawImageDebugDirectory.cs +++ b/src/LibObjectFile/PE/Internal/RawImageDebugDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs b/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs index f28135b..febb7a6 100644 --- a/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs +++ b/src/LibObjectFile/PE/Internal/RawImageOptionalHeader32.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/Internal/RawImageSectionHeader.cs b/src/LibObjectFile/PE/Internal/RawImageSectionHeader.cs index 67e7d6c..b703bf6 100644 --- a/src/LibObjectFile/PE/Internal/RawImageSectionHeader.cs +++ b/src/LibObjectFile/PE/Internal/RawImageSectionHeader.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/Internal/RawPEBoundImportDirectory.cs b/src/LibObjectFile/PE/Internal/RawPEBoundImportDirectory.cs index da3a990..5e64618 100644 --- a/src/LibObjectFile/PE/Internal/RawPEBoundImportDirectory.cs +++ b/src/LibObjectFile/PE/Internal/RawPEBoundImportDirectory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/Internal/RawPEBoundImportForwarderRef.cs b/src/LibObjectFile/PE/Internal/RawPEBoundImportForwarderRef.cs index 2385dd2..d141c1f 100644 --- a/src/LibObjectFile/PE/Internal/RawPEBoundImportForwarderRef.cs +++ b/src/LibObjectFile/PE/Internal/RawPEBoundImportForwarderRef.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/PEFile.Read.cs b/src/LibObjectFile/PE/PEFile.Read.cs index b6f873c..3690eba 100644 --- a/src/LibObjectFile/PE/PEFile.Read.cs +++ b/src/LibObjectFile/PE/PEFile.Read.cs @@ -370,7 +370,7 @@ private void ReadSectionsAndDirectories(PEImageReader reader, ReadOnlySpan dataParts, ulong startPosition, ulong totalSize) { var currentPosition = startPosition; + var endPosition = startPosition + totalSize; // We are working on position, while the list is ordered by VirtualAddress var listOrderedByPosition = dataParts.UnsafeList; + // Early exit if we don't have any data + if (totalSize == 0 && listOrderedByPosition.Count == 0) + { + return; + } + listOrderedByPosition.Sort((a, b) => a.Position.CompareTo(b.Position)); for (var i = 0; i < listOrderedByPosition.Count; i++) { var data = listOrderedByPosition[i]; data.Index = i; } - for (var i = 0; i < listOrderedByPosition.Count; i++) + + if (listOrderedByPosition.Count == 0) { - var data = listOrderedByPosition[i]; - if (currentPosition < data.Position) + var size = endPosition - currentPosition; + imageReader.Position = currentPosition; + var sectionData = new PEStreamSectionData(imageReader.ReadAsStream(size)) { - var size = data.Position - currentPosition; - imageReader.Position = currentPosition; + Position = currentPosition, + Parent = container, + }; - var sectionData = new PEStreamSectionData(imageReader.ReadAsStream(size)) + listOrderedByPosition.Add(sectionData); + currentPosition = endPosition; + } + else + { + for (var i = 0; i < listOrderedByPosition.Count; i++) + { + var data = listOrderedByPosition[i]; + + // Make sure we align the position to the required alignment of the next data + currentPosition = AlignHelper.AlignUp(currentPosition, data.GetRequiredPositionAlignment(imageReader.File)); + + if (currentPosition < data.Position) { - Position = currentPosition, - Parent = container, - }; + var size = data.Position - currentPosition; + imageReader.Position = currentPosition; - listOrderedByPosition.Insert(i, sectionData); - currentPosition = data.Position; - i++; - } - else if (currentPosition > data.Position) - { - imageReader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Invalid section data position {currentPosition} > {data.Position} in {container}"); - return; - } + var sectionData = new PEStreamSectionData(imageReader.ReadAsStream(size)) + { + Position = currentPosition, + Parent = container, + }; - currentPosition += data.Size; + listOrderedByPosition.Insert(i, sectionData); + currentPosition = data.Position; + i++; + } + else if (currentPosition > data.Position) + { + imageReader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Invalid section data position {currentPosition} > {data.Position} in {container}"); + return; + } + + var dataSize = AlignHelper.AlignUp(data.Size, data.GetRequiredSizeAlignment(imageReader.File)); + currentPosition += dataSize; + } } - if (currentPosition < startPosition + totalSize) + if (currentPosition < endPosition) { - var size = startPosition + totalSize - currentPosition; + var size = endPosition - currentPosition; imageReader.Position = currentPosition; var sectionData = new PEStreamSectionData(imageReader.ReadAsStream(size)) { @@ -520,9 +549,9 @@ private static void FillSectionDataWithMissingStreams(PEImageReader imageReader, listOrderedByPosition.Add(sectionData); } - else if (currentPosition > startPosition + totalSize) + else if (currentPosition > endPosition) { - imageReader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Invalid section data position {currentPosition} > {startPosition + totalSize} in {container}"); + imageReader.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Invalid section data position {currentPosition} > {endPosition} in {container}"); } // Make sure to update the indices after inserting the missing streams diff --git a/src/LibObjectFile/PE/PEFile.Write.cs b/src/LibObjectFile/PE/PEFile.Write.cs index 16bc19f..8fa31ef 100644 --- a/src/LibObjectFile/PE/PEFile.Write.cs +++ b/src/LibObjectFile/PE/PEFile.Write.cs @@ -1,10 +1,15 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System; using System.IO; +using System.Numerics; +using System.Reflection.PortableExecutable; +using System.Runtime.InteropServices; using LibObjectFile.Diagnostics; +using LibObjectFile.PE.Internal; +using LibObjectFile.Utils; namespace LibObjectFile.PE; @@ -54,8 +59,139 @@ public bool TryWrite(Stream stream, out DiagnosticBag diagnostics) return !diagnostics.HasErrors; } - public override void Write(PEImageWriter writer) + public override unsafe void Write(PEImageWriter writer) { - throw new NotImplementedException(); + var context = new PELayoutContext(this, writer.Diagnostics); + UpdateLayout(context); + + var position = 0U; + + // Update DOS header + writer.Write(DosHeader); + position += (uint)sizeof(ImageDosHeader); + + // Write DOS stub + writer.Write(_dosStub); + position += (uint)_dosStub.Length; + + // Write extra DOS stub + if (_dosStubExtra != null) + { + _dosStubExtra.CopyTo(writer.Stream); + position += (uint)_dosStubExtra.Length; + } + + var zeroSize = (int)((int)AlignHelper.AlignUp(position, 8) - (int)position); + writer.WriteZero((int)zeroSize); + position += (uint)zeroSize; + + // PE00 header + writer.Write(ImagePESignature.PE); + position += sizeof(ImagePESignature); // PE00 header + + // COFF header + writer.Write(CoffHeader); + position += (uint)sizeof(ImageCoffHeader); + + + if (IsPE32) + { + RawImageOptionalHeader32 header32; + header32.Common = OptionalHeader.OptionalHeaderCommonPart1; + header32.Base32 = OptionalHeader.OptionalHeaderBase32; + header32.Common2 = OptionalHeader.OptionalHeaderCommonPart2; + header32.Size32 = OptionalHeader.OptionalHeaderSize32; + header32.Common3 = OptionalHeader.OptionalHeaderCommonPart3; + writer.Write(header32); + position += (uint)sizeof(RawImageOptionalHeader32); + } + else + { + RawImageOptionalHeader64 header64; + header64.Common = OptionalHeader.OptionalHeaderCommonPart1; + header64.Base64 = OptionalHeader.OptionalHeaderBase64; + header64.Common2 = OptionalHeader.OptionalHeaderCommonPart2; + header64.Size64 = OptionalHeader.OptionalHeaderSize64; + header64.Common3 = OptionalHeader.OptionalHeaderCommonPart3; + writer.Write(header64); + position += (uint)sizeof(RawImageOptionalHeader64); + } + + + // Update directories + Directories.Write(writer, ref position); + + // Write Section Headers + RawImageSectionHeader sectionHeader = default; + foreach (var section in _sections) + { + section.Name.CopyTo(new Span(sectionHeader.Name, 8)); + sectionHeader.VirtualSize = section.VirtualSize; + sectionHeader.RVA = section.RVA; + sectionHeader.SizeOfRawData = (uint)section.Size; + sectionHeader.PointerToRawData = (uint)section.Position; + sectionHeader.Characteristics = section.Characteristics; + writer.Write(sectionHeader); + position += (uint)sizeof(RawImageSectionHeader); + } + + // Data before sections + foreach (var extraData in ExtraDataBeforeSections) + { + extraData.Write(writer); + position += (uint)extraData.Size; + } + + // Ensure that SectionAlignment is a multiple of FileAlignment + zeroSize = (int)(AlignHelper.AlignUp(position, OptionalHeader.FileAlignment) - position); + writer.WriteZero(zeroSize); + position += (uint)zeroSize; + + // Write sections + foreach (var section in _sections) + { + var span = CollectionsMarshal.AsSpan(section.Content.UnsafeList); + for (var i = 0; i < span.Length; i++) + { + var data = span[i]; + if (data.Position != position) + { + writer.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Current position {position} for data Section[{i}] in {section} does not match expecting position {data.Position}"); + return; + } + + if (data is PEDataDirectory directory) + { + directory.WriteHeaderAndContent(writer); + } + else + { + data.Write(writer); + } + + position += (uint)data.Size; + } + + zeroSize = (int)(AlignHelper.AlignUp(position, writer.PEFile.OptionalHeader.FileAlignment) - position); + writer.WriteZero(zeroSize); + } + + // Data after sections + foreach (var extraData in ExtraDataAfterSections) + { + if (extraData.Position != position) + { + writer.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Current position {position} doest not match expecting position {extraData.Position}"); + return; + } + + extraData.Write(writer); + position += (uint)extraData.Size; + } + + if (position != Size) + { + writer.Diagnostics.Error(DiagnosticId.PE_ERR_InvalidInternalState, $"Generated size {position} does not match expecting size {Size}"); + } } } \ No newline at end of file diff --git a/src/LibObjectFile/PE/PEImageWriter.cs b/src/LibObjectFile/PE/PEImageWriter.cs index 2f13d65..676bc1d 100644 --- a/src/LibObjectFile/PE/PEImageWriter.cs +++ b/src/LibObjectFile/PE/PEImageWriter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. diff --git a/src/LibObjectFile/PE/PEObjectBase.cs b/src/LibObjectFile/PE/PEObjectBase.cs index 3fd6f64..155b757 100644 --- a/src/LibObjectFile/PE/PEObjectBase.cs +++ b/src/LibObjectFile/PE/PEObjectBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. @@ -31,4 +31,20 @@ public virtual void WriteAt(uint offset, ReadOnlySpan source) { throw new NotSupportedException($"The write operation is not supported for {this.GetType().FullName}"); } + + /// + /// Gets the required alignment for this object. + /// + /// The PE file containing this object. + /// The required alignment for this object. + /// By default, this method returns 1. + public virtual uint GetRequiredPositionAlignment(PEFile file) => 1; + + /// + /// Gets the required size alignment for this object. + /// + /// The PE file containing this object. + /// The required size alignment for this object. + /// By default, this method returns 1. + public virtual uint GetRequiredSizeAlignment(PEFile file) => 1; } \ No newline at end of file diff --git a/src/LibObjectFile/PE/PEPrinter.cs b/src/LibObjectFile/PE/PEPrinter.cs index cb7d1f4..f8f4cde 100644 --- a/src/LibObjectFile/PE/PEPrinter.cs +++ b/src/LibObjectFile/PE/PEPrinter.cs @@ -5,6 +5,7 @@ using System; using System.IO; using LibObjectFile.IO; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace LibObjectFile.PE; @@ -269,6 +270,9 @@ private static void PrintSectionData(PEFile file, PESectionData data, ref TextWr case PEDebugSectionDataRSDS peDebugSectionDataRSDS: Print(peDebugSectionDataRSDS, ref writer); break; + case PEResourceEntry peResourceEntry: + Print(peResourceEntry, ref writer); + break; default: writer.WriteLine($"Unsupported section data {data}"); break; @@ -562,15 +566,24 @@ private static void Print(PEResourceEntry data, ref TextWriterIndenter writer) switch (data) { case PEResourceDataEntry resourceFile: - writer.WriteLine($"> {resourceFile}"); + writer.WriteLine($"> CodePage = {resourceFile.CodePage?.EncodingName ?? "null"}, Data = {resourceFile.Data}"); break; case PEResourceDirectoryEntry dir: - writer.WriteLine($"> {dir}"); + writer.WriteLine($"> ByNames[{dir.ByNames.Count}], ByIds[{dir.ByIds.Count}] , TimeDateStamp = {dir.TimeDateStamp}, Version = {dir.MajorVersion}.{dir.MinorVersion}"); writer.Indent(); - foreach (var entry in dir.Entries) + + for (var i = 0; i < dir.ByNames.Count; i++) + { + var entry = dir.ByNames[i]; + writer.WriteLine($"[{i}] Name = {entry.Name}, Entry = {entry.Entry}"); + } + + for (var i = 0; i < dir.ByIds.Count; i++) { - Print(entry, ref writer); + var entry = dir.ByIds[i]; + writer.WriteLine($"[{i}] Id = {entry.Id}, Entry = {entry.Entry}"); } + writer.Unindent(); break; default: diff --git a/src/LibObjectFile/PE/PESection.cs b/src/LibObjectFile/PE/PESection.cs index 1be9ee5..cc88a8f 100644 --- a/src/LibObjectFile/PE/PESection.cs +++ b/src/LibObjectFile/PE/PESection.cs @@ -6,11 +6,8 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Reflection.PortableExecutable; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using LibObjectFile.Collections; -using LibObjectFile.Diagnostics; using LibObjectFile.Utils; namespace LibObjectFile.PE; @@ -54,6 +51,12 @@ public PESection(PESectionName name, RVA rva, RVA virtualSize) /// Gets the list of data associated with this section. /// public ObjectList Content => _content; + + /// + public override uint GetRequiredPositionAlignment(PEFile file) => file.OptionalHeader.FileAlignment; + + /// + public override uint GetRequiredSizeAlignment(PEFile file) => file.OptionalHeader.FileAlignment; /// /// Tries to find the section data that contains the specified virtual address. @@ -73,14 +76,22 @@ public override void UpdateLayout(PELayoutContext context) { var peFile = context.File; - var sectionAlignment = peFile.OptionalHeader.SectionAlignment; - var fileAlignment = peFile.OptionalHeader.FileAlignment; - var va = RVA; - var position = Position; + var position = (uint)Position; var size = 0U; foreach (var data in Content) { + // Make sure we align the position and the virtual address + var alignment = data.GetRequiredPositionAlignment(context.File); + + if (alignment > 1) + { + var newPosition = AlignHelper.AlignUp(position, alignment); + size += newPosition - position; + position = newPosition; + va = AlignHelper.AlignUp(va, alignment); + } + data.RVA = va; if (!context.UpdateSizeOnly) @@ -90,13 +101,14 @@ public override void UpdateLayout(PELayoutContext context) data.UpdateLayout(context); - var dataSize = (uint)data.Size; + var dataSize = AlignHelper.AlignUp((uint)data.Size, data.GetRequiredSizeAlignment(peFile)); va += dataSize; position += dataSize; size += dataSize; } // The size of a section is the size of the content aligned on the file alignment + var fileAlignment = peFile.OptionalHeader.FileAlignment; Size = (Characteristics & SectionCharacteristics.ContainsUninitializedData) == 0 ? AlignHelper.AlignUp(size, fileAlignment) : (ulong)0; //if (Size > VirtualSize) @@ -117,6 +129,8 @@ public override void Write(PEImageWriter writer) throw new NotImplementedException(); } + + /// protected override bool PrintMembers(StringBuilder builder) { diff --git a/src/LibObjectFile/PE/PESectionData.cs b/src/LibObjectFile/PE/PESectionData.cs index 0af00ff..70993b9 100644 --- a/src/LibObjectFile/PE/PESectionData.cs +++ b/src/LibObjectFile/PE/PESectionData.cs @@ -1,7 +1,8 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using LibObjectFile.Utils; using System; using System.Runtime.CompilerServices; using System.Text; diff --git a/src/LibObjectFile/PE/PESectionName.cs b/src/LibObjectFile/PE/PESectionName.cs index 5a31c0a..56c5df1 100644 --- a/src/LibObjectFile/PE/PESectionName.cs +++ b/src/LibObjectFile/PE/PESectionName.cs @@ -1,9 +1,10 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System; using System.Text; +using System.Xml.Linq; namespace LibObjectFile.PE; @@ -40,6 +41,12 @@ internal PESectionName(string name, bool validate = true) /// public override string ToString() => Name; + internal void CopyTo(Span buffer) + { + var total = Encoding.ASCII.GetBytes(Name, buffer); + buffer.Slice(total).Fill(0); + } + /// /// Checks if the specified section name is a valid section name. /// diff --git a/src/LibObjectFile/PE/PEStreamSectionData.cs b/src/LibObjectFile/PE/PEStreamSectionData.cs index 2b7b491..800aaf7 100644 --- a/src/LibObjectFile/PE/PEStreamSectionData.cs +++ b/src/LibObjectFile/PE/PEStreamSectionData.cs @@ -1,9 +1,10 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System; using System.IO; +using LibObjectFile.Utils; namespace LibObjectFile.PE; @@ -13,16 +14,16 @@ namespace LibObjectFile.PE; public class PEStreamSectionData : PESectionData { private Stream _stream; + private uint _requiredPositionAlignment; + private uint _requiredSizeAlignment; internal static PEStreamSectionData Empty = new(); /// /// Initializes a new instance of the class. /// - public PEStreamSectionData() + public PEStreamSectionData() : this(System.IO.Stream.Null) { - _stream = Stream.Null; - Size = 0; } /// @@ -34,6 +35,8 @@ public PEStreamSectionData(Stream stream) ArgumentNullException.ThrowIfNull(stream); _stream = stream; Size = (ulong)stream.Length; + _requiredPositionAlignment = 1; + _requiredSizeAlignment = 1; } public override bool HasChildren => false; @@ -52,6 +55,32 @@ public Stream Stream } } + /// + /// Gets or sets the preferred position alignment for this section data. + /// + public uint RequiredPositionAlignment + { + get => _requiredPositionAlignment; + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 1U); + _requiredPositionAlignment = value; + } + } + + /// + /// Gets or sets the preferred size alignment for this section data. + /// + public uint RequiredSizeAlignment + { + get => _requiredSizeAlignment; + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 1U); + _requiredSizeAlignment = value; + } + } + public override void UpdateLayout(PELayoutContext layoutContext) { Size = (ulong)Stream.Length; @@ -80,4 +109,8 @@ public override void WriteAt(uint offset, ReadOnlySpan source) Stream.Position = offset; Stream.Write(source); } + + public override uint GetRequiredPositionAlignment(PEFile file) => _requiredPositionAlignment; + + public override uint GetRequiredSizeAlignment(PEFile file) => _requiredSizeAlignment; } \ No newline at end of file