obfuz/Plugins/dnlib/DotNet/Writer/ModuleWriterBase.cs

1281 lines
47 KiB
C#

// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using System.IO;
using dnlib.IO;
using dnlib.DotNet.Pdb;
using dnlib.PE;
using dnlib.W32Resources;
using dnlib.DotNet.MD;
using System.Diagnostics;
using dnlib.DotNet.Pdb.WindowsPdb;
using System.Text;
using System.IO.Compression;
using System.Runtime.InteropServices;
namespace dnlib.DotNet.Writer {
/// <summary>
/// Module writer event args
/// </summary>
public readonly struct ModuleWriterEventArgs {
/// <summary>
/// Gets the writer (<see cref="ModuleWriter"/> or <see cref="NativeModuleWriter"/>)
/// </summary>
public ModuleWriterBase Writer { get; }
/// <summary>
/// Gets the event
/// </summary>
public ModuleWriterEvent Event { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="writer">Writer</param>
/// <param name="event">Event</param>
public ModuleWriterEventArgs(ModuleWriterBase writer, ModuleWriterEvent @event) {
Writer = writer ?? throw new ArgumentNullException(nameof(writer));
Event = @event;
}
}
/// <summary>
/// Module writer progress event args
/// </summary>
public readonly struct ModuleWriterProgressEventArgs {
/// <summary>
/// Gets the writer (<see cref="ModuleWriter"/> or <see cref="NativeModuleWriter"/>)
/// </summary>
public ModuleWriterBase Writer { get; }
/// <summary>
/// Gets the progress, 0.0 - 1.0
/// </summary>
public double Progress { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="writer">Writer</param>
/// <param name="progress">Progress, 0.0 - 1.0</param>
public ModuleWriterProgressEventArgs(ModuleWriterBase writer, double progress) {
if (progress < 0 || progress > 1)
throw new ArgumentOutOfRangeException(nameof(progress));
Writer = writer ?? throw new ArgumentNullException(nameof(writer));
Progress = progress;
}
}
/// <summary>
/// Content ID
/// </summary>
public readonly struct ContentId {
/// <summary>
/// Gets the GUID
/// </summary>
public readonly Guid Guid;
/// <summary>
/// Gets the timestamp
/// </summary>
public readonly uint Timestamp;
/// <summary>
/// Constructor
/// </summary>
/// <param name="guid">Guid</param>
/// <param name="timestamp">Timestamp</param>
public ContentId(Guid guid, uint timestamp) {
Guid = guid;
Timestamp = timestamp;
}
}
/// <summary>
/// Event handler
/// </summary>
/// <typeparam name="TEventArgs">Event args type</typeparam>
/// <param name="sender">Sender</param>
/// <param name="e">Event args</param>
public delegate void EventHandler2<TEventArgs>(object sender, TEventArgs e);
/// <summary>
/// PDB writer options
/// </summary>
[Flags]
public enum PdbWriterOptions {
/// <summary>
/// No bit is set
/// </summary>
None = 0,
/// <summary>
/// Don't use Microsoft.DiaSymReader.Native. This is a NuGet package with an updated Windows PDB reader/writer implementation,
/// and if it's available at runtime, dnlib will try to use it. If this option is set, dnlib won't use it.
/// You have to add a reference to the NuGet package if you want to use it, dnlib has no reference to the NuGet package.
///
/// This is only used if it's a Windows PDB file.
/// </summary>
NoDiaSymReader = 0x00000001,
/// <summary>
/// Don't use diasymreader.dll's PDB writer that is shipped with .NET Framework.
///
/// This is only used if it's a Windows PDB file.
/// </summary>
NoOldDiaSymReader = 0x00000002,
/// <summary>
/// Create a deterministic PDB file and add a <see cref="ImageDebugType.Reproducible"/> debug directory entry to the PE file.
///
/// It's ignored if the PDB writer doesn't support it.
/// </summary>
Deterministic = 0x00000004,
/// <summary>
/// Hash the PDB file and add a PDB checksum debug directory entry to the PE file.
///
/// It's ignored if the PDB writer doesn't support it.
/// </summary>
PdbChecksum = 0x00000008,
}
/// <summary>
/// Common module writer options base class
/// </summary>
public class ModuleWriterOptionsBase {
PEHeadersOptions peHeadersOptions;
Cor20HeaderOptions cor20HeaderOptions;
MetadataOptions metadataOptions;
ILogger logger;
ILogger metadataLogger;
bool noWin32Resources;
Win32Resources win32Resources;
StrongNameKey strongNameKey;
StrongNamePublicKey strongNamePublicKey;
bool delaySign;
/// <summary>
/// Raised at various times when writing the file. The listener has a chance to modify
/// the file, eg. add extra metadata, encrypt methods, etc.
/// </summary>
public event EventHandler2<ModuleWriterEventArgs> WriterEvent;
internal void RaiseEvent(object sender, ModuleWriterEventArgs e) => WriterEvent?.Invoke(sender, e);
/// <summary>
/// Raised when the progress is updated
/// </summary>
public event EventHandler2<ModuleWriterProgressEventArgs> ProgressUpdated;
internal void RaiseEvent(object sender, ModuleWriterProgressEventArgs e) => ProgressUpdated?.Invoke(sender, e);
/// <summary>
/// Gets/sets the logger. If this is <c>null</c>, any errors result in a
/// <see cref="ModuleWriterException"/> being thrown. To disable this behavior, either
/// create your own logger or use <see cref="DummyLogger.NoThrowInstance"/>.
/// </summary>
public ILogger Logger {
get => logger;
set => logger = value;
}
/// <summary>
/// Gets/sets the <see cref="Metadata"/> writer logger. If this is <c>null</c>, use
/// <see cref="Logger"/>.
/// </summary>
public ILogger MetadataLogger {
get => metadataLogger;
set => metadataLogger = value;
}
/// <summary>
/// Gets/sets the <see cref="PEHeaders"/> options. This is never <c>null</c>.
/// </summary>
public PEHeadersOptions PEHeadersOptions {
get => peHeadersOptions ??= new PEHeadersOptions();
set => peHeadersOptions = value;
}
/// <summary>
/// Gets/sets the <see cref="ImageCor20Header"/> options. This is never <c>null</c>.
/// </summary>
public Cor20HeaderOptions Cor20HeaderOptions {
get => cor20HeaderOptions ??= new Cor20HeaderOptions();
set => cor20HeaderOptions = value;
}
/// <summary>
/// Gets/sets the <see cref="Metadata"/> options. This is never <c>null</c>.
/// </summary>
public MetadataOptions MetadataOptions {
get => metadataOptions ??= new MetadataOptions();
set => metadataOptions = value;
}
/// <summary>
/// If <c>true</c>, Win32 resources aren't written to the output
/// </summary>
public bool NoWin32Resources {
get => noWin32Resources;
set => noWin32Resources = value;
}
/// <summary>
/// Gets/sets the Win32 resources. If this is <c>null</c>, use the module's
/// Win32 resources if any.
/// </summary>
public Win32Resources Win32Resources {
get => win32Resources;
set => win32Resources = value;
}
/// <summary>
/// true to delay sign the assembly. Initialize <see cref="StrongNamePublicKey"/> to the
/// public key to use, and don't initialize <see cref="StrongNameKey"/>. To generate the
/// public key from your strong name key file, execute <c>sn -p mykey.snk mypublickey.snk</c>
/// </summary>
public bool DelaySign {
get => delaySign;
set => delaySign = value;
}
/// <summary>
/// Gets/sets the strong name key. When you enhance strong name sign an assembly,
/// this instance's HashAlgorithm must be initialized to its public key's HashAlgorithm.
/// You should call <see cref="InitializeStrongNameSigning(ModuleDef,StrongNameKey)"/>
/// to initialize this property if you use normal strong name signing.
/// You should call <see cref="InitializeEnhancedStrongNameSigning(ModuleDef,StrongNameKey,StrongNamePublicKey)"/>
/// or <see cref="InitializeEnhancedStrongNameSigning(ModuleDef,StrongNameKey,StrongNamePublicKey,StrongNameKey,StrongNamePublicKey)"/>
/// to initialize this property if you use enhanced strong name signing.
/// </summary>
public StrongNameKey StrongNameKey {
get => strongNameKey;
set => strongNameKey = value;
}
/// <summary>
/// Gets/sets the new public key that should be used. If this is <c>null</c>, use
/// the public key generated from <see cref="StrongNameKey"/>. If it is also <c>null</c>,
/// use the module's Assembly's public key.
/// You should call <see cref="InitializeEnhancedStrongNameSigning(ModuleDef,StrongNameKey,StrongNamePublicKey)"/>
/// or <see cref="InitializeEnhancedStrongNameSigning(ModuleDef,StrongNameKey,StrongNamePublicKey,StrongNameKey,StrongNamePublicKey)"/>
/// to initialize this property if you use enhanced strong name signing.
/// </summary>
public StrongNamePublicKey StrongNamePublicKey {
get => strongNamePublicKey;
set => strongNamePublicKey = value;
}
/// <summary>
/// <c>true</c> if method bodies can be shared (two or more method bodies can share the
/// same RVA), <c>false</c> if method bodies can't be shared. Don't enable it if there
/// must be a 1:1 relationship with method bodies and their RVAs.
/// This is enabled by default and results in smaller files.
/// </summary>
public bool ShareMethodBodies { get; set; }
/// <summary>
/// <c>true</c> if the PE header CheckSum field should be updated, <c>false</c> if the
/// CheckSum field isn't updated.
/// </summary>
public bool AddCheckSum { get; set; }
/// <summary>
/// <c>true</c> if it's a 64-bit module, <c>false</c> if it's a 32-bit or AnyCPU module.
/// </summary>
public bool Is64Bit {
get {
if (!PEHeadersOptions.Machine.HasValue)
return false;
return PEHeadersOptions.Machine.Value.Is64Bit();
}
}
/// <summary>
/// Gets/sets the module kind
/// </summary>
public ModuleKind ModuleKind { get; set; }
/// <summary>
/// <c>true</c> if it should be written as an EXE file, <c>false</c> if it should be
/// written as a DLL file.
/// </summary>
public bool IsExeFile => ModuleKind != ModuleKind.Dll && ModuleKind != ModuleKind.NetModule;
/// <summary>
/// Set it to <c>true</c> to enable writing a PDB file. Default is <c>false</c> (a PDB file
/// won't be written to disk).
/// </summary>
public bool WritePdb { get; set; }
/// <summary>
/// PDB writer options. This property is ignored if <see cref="WritePdb"/> is false.
/// </summary>
public PdbWriterOptions PdbOptions { get; set; }
/// <summary>
/// PDB file name. If it's <c>null</c> a PDB file with the same name as the output assembly
/// will be created but with a PDB extension. <see cref="WritePdb"/> must be <c>true</c> or
/// this property is ignored.
/// </summary>
public string PdbFileName { get; set; }
/// <summary>
/// PDB file name stored in the debug directory, or null to use <see cref="PdbFileName"/>
/// </summary>
public string PdbFileNameInDebugDirectory { get; set; }
/// <summary>
/// PDB stream. If this is initialized, then you should also set <see cref="PdbFileName"/>
/// to the name of the PDB file since the file name must be written to the PE debug directory.
/// <see cref="WritePdb"/> must be <c>true</c> or this property is ignored.
/// </summary>
public Stream PdbStream { get; set; }
/// <summary>
/// Gets the PDB content id (portable PDBs). The <see cref="Stream"/> argument is the PDB stream with the PDB ID zeroed out,
/// and the 2nd <see cref="uint"/> argument is the default timestamp.
/// This property is ignored if a deterministic PDB file is created or if the PDB checksum is calculated.
/// </summary>
public Func<Stream, uint, ContentId> GetPdbContentId { get; set; }
const ChecksumAlgorithm DefaultPdbChecksumAlgorithm = ChecksumAlgorithm.SHA256;
/// <summary>
/// PDB checksum algorithm
/// </summary>
public ChecksumAlgorithm PdbChecksumAlgorithm { get; set; } = DefaultPdbChecksumAlgorithm;
/// <summary>
/// true if an <c>.mvid</c> section should be added to the assembly. Not used by native module writer.
/// </summary>
public bool AddMvidSection { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="module">The module</param>
protected ModuleWriterOptionsBase(ModuleDef module) {
ShareMethodBodies = true;
MetadataOptions.MetadataHeaderOptions.VersionString = module.RuntimeVersion;
ModuleKind = module.Kind;
PEHeadersOptions.Machine = module.Machine;
PEHeadersOptions.Characteristics = module.Characteristics;
PEHeadersOptions.DllCharacteristics = module.DllCharacteristics;
if (module.Kind == ModuleKind.Windows)
PEHeadersOptions.Subsystem = Subsystem.WindowsGui;
else
PEHeadersOptions.Subsystem = Subsystem.WindowsCui;
PEHeadersOptions.NumberOfRvaAndSizes = 0x10;
Cor20HeaderOptions.Flags = module.Cor20HeaderFlags;
if (module.Assembly is not null && !PublicKeyBase.IsNullOrEmpty2(module.Assembly.PublicKey))
Cor20HeaderOptions.Flags |= ComImageFlags.StrongNameSigned;
if (module.Cor20HeaderRuntimeVersion is not null) {
Cor20HeaderOptions.MajorRuntimeVersion = (ushort)(module.Cor20HeaderRuntimeVersion.Value >> 16);
Cor20HeaderOptions.MinorRuntimeVersion = (ushort)module.Cor20HeaderRuntimeVersion.Value;
}
else if (module.IsClr1x) {
Cor20HeaderOptions.MajorRuntimeVersion = 2;
Cor20HeaderOptions.MinorRuntimeVersion = 0;
}
else {
Cor20HeaderOptions.MajorRuntimeVersion = 2;
Cor20HeaderOptions.MinorRuntimeVersion = 5;
}
if (module.TablesHeaderVersion is not null) {
MetadataOptions.TablesHeapOptions.MajorVersion = (byte)(module.TablesHeaderVersion.Value >> 8);
MetadataOptions.TablesHeapOptions.MinorVersion = (byte)module.TablesHeaderVersion.Value;
}
else if (module.IsClr1x) {
// Generics aren't supported
MetadataOptions.TablesHeapOptions.MajorVersion = 1;
MetadataOptions.TablesHeapOptions.MinorVersion = 0;
}
else {
// Generics are supported
MetadataOptions.TablesHeapOptions.MajorVersion = 2;
MetadataOptions.TablesHeapOptions.MinorVersion = 0;
}
// Some tools crash if #GUID is missing so always create it by default
MetadataOptions.Flags |= MetadataFlags.AlwaysCreateGuidHeap;
var modDefMD = module as ModuleDefMD;
if (modDefMD is not null) {
var ntHeaders = modDefMD.Metadata.PEImage.ImageNTHeaders;
PEHeadersOptions.TimeDateStamp = ntHeaders.FileHeader.TimeDateStamp;
PEHeadersOptions.MajorLinkerVersion = ntHeaders.OptionalHeader.MajorLinkerVersion;
PEHeadersOptions.MinorLinkerVersion = ntHeaders.OptionalHeader.MinorLinkerVersion;
PEHeadersOptions.ImageBase = ntHeaders.OptionalHeader.ImageBase;
PEHeadersOptions.MajorOperatingSystemVersion = ntHeaders.OptionalHeader.MajorOperatingSystemVersion;
PEHeadersOptions.MinorOperatingSystemVersion = ntHeaders.OptionalHeader.MinorOperatingSystemVersion;
PEHeadersOptions.MajorImageVersion = ntHeaders.OptionalHeader.MajorImageVersion;
PEHeadersOptions.MinorImageVersion = ntHeaders.OptionalHeader.MinorImageVersion;
PEHeadersOptions.MajorSubsystemVersion = ntHeaders.OptionalHeader.MajorSubsystemVersion;
PEHeadersOptions.MinorSubsystemVersion = ntHeaders.OptionalHeader.MinorSubsystemVersion;
PEHeadersOptions.Win32VersionValue = ntHeaders.OptionalHeader.Win32VersionValue;
AddCheckSum = ntHeaders.OptionalHeader.CheckSum != 0;
AddMvidSection = HasMvidSection(modDefMD.Metadata.PEImage.ImageSectionHeaders);
if (HasDebugDirectoryEntry(modDefMD.Metadata.PEImage.ImageDebugDirectories, ImageDebugType.Reproducible))
PdbOptions |= PdbWriterOptions.Deterministic;
if (HasDebugDirectoryEntry(modDefMD.Metadata.PEImage.ImageDebugDirectories, ImageDebugType.PdbChecksum))
PdbOptions |= PdbWriterOptions.PdbChecksum;
if (TryGetPdbChecksumAlgorithm(modDefMD.Metadata.PEImage, modDefMD.Metadata.PEImage.ImageDebugDirectories, out var pdbChecksumAlgorithm))
PdbChecksumAlgorithm = pdbChecksumAlgorithm;
MetadataOptions.TablesHeapOptions.Log2Rid = modDefMD.TablesStream.Log2Rid;
}
if (Is64Bit) {
PEHeadersOptions.Characteristics &= ~Characteristics.Bit32Machine;
PEHeadersOptions.Characteristics |= Characteristics.LargeAddressAware;
}
else if (modDefMD is null)
PEHeadersOptions.Characteristics |= Characteristics.Bit32Machine;
}
static bool HasMvidSection(IList<ImageSectionHeader> sections) {
int count = sections.Count;
for (int i = 0; i < count; i++) {
var section = sections[i];
if (section.VirtualSize != 16)
continue;
var name = section.Name;
// Roslyn ignores the last 2 bytes
if (name[0] == '.' && name[1] == 'm' && name[2] == 'v' && name[3] == 'i' && name[4] == 'd' && name[5] == 0)
return true;
}
return false;
}
static bool HasDebugDirectoryEntry(IList<ImageDebugDirectory> debugDirs, ImageDebugType type) {
int count = debugDirs.Count;
for (int i = 0; i < count; i++) {
if (debugDirs[i].Type == type)
return true;
}
return false;
}
static bool TryGetPdbChecksumAlgorithm(IPEImage peImage, IList<ImageDebugDirectory> debugDirs, out ChecksumAlgorithm pdbChecksumAlgorithm) {
int count = debugDirs.Count;
for (int i = 0; i < count; i++) {
var dir = debugDirs[i];
if (dir.Type == ImageDebugType.PdbChecksum) {
var reader = peImage.CreateReader(dir.AddressOfRawData, dir.SizeOfData);
if (TryGetPdbChecksumAlgorithm(ref reader, out pdbChecksumAlgorithm))
return true;
}
}
pdbChecksumAlgorithm = DefaultPdbChecksumAlgorithm;
return false;
}
static bool TryGetPdbChecksumAlgorithm(ref DataReader reader, out ChecksumAlgorithm pdbChecksumAlgorithm) {
try {
var checksumName = reader.TryReadZeroTerminatedUtf8String();
if (Hasher.TryGetChecksumAlgorithm(checksumName, out pdbChecksumAlgorithm, out int checksumSize) && (uint)checksumSize == reader.BytesLeft)
return true;
}
catch (IOException) {
}
catch (ArgumentException) {
}
pdbChecksumAlgorithm = DefaultPdbChecksumAlgorithm;
return false;
}
/// <summary>
/// Initializes <see cref="StrongNameKey"/> and <see cref="StrongNamePublicKey"/>
/// for normal strong name signing.
/// </summary>
/// <param name="module">Module</param>
/// <param name="signatureKey">Signature strong name key pair</param>
public void InitializeStrongNameSigning(ModuleDef module, StrongNameKey signatureKey) {
StrongNameKey = signatureKey;
StrongNamePublicKey = null;
if (module.Assembly is not null)
module.Assembly.CustomAttributes.RemoveAll("System.Reflection.AssemblySignatureKeyAttribute");
}
/// <summary>
/// Initializes <see cref="StrongNameKey"/> and <see cref="StrongNamePublicKey"/>
/// for enhanced strong name signing (without key migration). See
/// http://msdn.microsoft.com/en-us/library/hh415055.aspx
/// </summary>
/// <param name="module">Module</param>
/// <param name="signatureKey">Signature strong name key pair</param>
/// <param name="signaturePubKey">Signature public key</param>
public void InitializeEnhancedStrongNameSigning(ModuleDef module, StrongNameKey signatureKey, StrongNamePublicKey signaturePubKey) {
InitializeStrongNameSigning(module, signatureKey);
StrongNameKey = StrongNameKey.WithHashAlgorithm(signaturePubKey.HashAlgorithm);
}
/// <summary>
/// Initializes <see cref="StrongNameKey"/> and <see cref="StrongNamePublicKey"/>
/// for enhanced strong name signing (with key migration). See
/// http://msdn.microsoft.com/en-us/library/hh415055.aspx
/// </summary>
/// <param name="module">Module</param>
/// <param name="signatureKey">Signature strong name key pair</param>
/// <param name="signaturePubKey">Signature public key</param>
/// <param name="identityKey">Identity strong name key pair</param>
/// <param name="identityPubKey">Identity public key</param>
public void InitializeEnhancedStrongNameSigning(ModuleDef module, StrongNameKey signatureKey, StrongNamePublicKey signaturePubKey, StrongNameKey identityKey, StrongNamePublicKey identityPubKey) {
StrongNameKey = signatureKey.WithHashAlgorithm(signaturePubKey.HashAlgorithm);
StrongNamePublicKey = identityPubKey;
if (module.Assembly is not null)
module.Assembly.UpdateOrCreateAssemblySignatureKeyAttribute(identityPubKey, identityKey, signaturePubKey);
}
}
/// <summary>
/// Module writer base class
/// </summary>
public abstract class ModuleWriterBase : ILogger {
/// <summary>Default alignment of all constants</summary>
protected internal const uint DEFAULT_CONSTANTS_ALIGNMENT = 8;
/// <summary>Default alignment of all method bodies</summary>
protected const uint DEFAULT_METHODBODIES_ALIGNMENT = 4;
/// <summary>Default alignment of all .NET resources</summary>
protected const uint DEFAULT_NETRESOURCES_ALIGNMENT = 4;
/// <summary>Default alignment of the .NET metadata</summary>
protected const uint DEFAULT_METADATA_ALIGNMENT = 4;
/// <summary>Default Win32 resources alignment</summary>
protected internal const uint DEFAULT_WIN32_RESOURCES_ALIGNMENT = 8;
/// <summary>Default strong name signature alignment</summary>
protected const uint DEFAULT_STRONGNAMESIG_ALIGNMENT = 4;
/// <summary>Default COR20 header alignment</summary>
protected const uint DEFAULT_COR20HEADER_ALIGNMENT = 4;
/// <summary>See <see cref="DestinationStream"/></summary>
protected Stream destStream;
/// <summary>See <see cref="Constants"/></summary>
protected UniqueChunkList<ByteArrayChunk> constants;
/// <summary>See <see cref="MethodBodies"/></summary>
protected MethodBodyChunks methodBodies;
/// <summary>See <see cref="NetResources"/></summary>
protected NetResources netResources;
/// <summary>See <see cref="Metadata"/></summary>
protected Metadata metadata;
/// <summary>See <see cref="Win32Resources"/></summary>
protected Win32ResourcesChunk win32Resources;
/// <summary>Offset where the module is written. Usually 0.</summary>
protected long destStreamBaseOffset;
/// <summary>Debug directory</summary>
protected DebugDirectory debugDirectory;
string createdPdbFileName;
/// <summary>
/// Strong name signature
/// </summary>
protected StrongNameSignature strongNameSignature;
/// <summary>
/// Returns the module writer options
/// </summary>
public abstract ModuleWriterOptionsBase TheOptions { get; }
/// <summary>
/// Gets the destination stream
/// </summary>
public Stream DestinationStream => destStream;
/// <summary>
/// Gets the constants
/// </summary>
public UniqueChunkList<ByteArrayChunk> Constants => constants;
/// <summary>
/// Gets the method bodies
/// </summary>
public MethodBodyChunks MethodBodies => methodBodies;
/// <summary>
/// Gets the .NET resources
/// </summary>
public NetResources NetResources => netResources;
/// <summary>
/// Gets the .NET metadata
/// </summary>
public Metadata Metadata => metadata;
/// <summary>
/// Gets the Win32 resources or <c>null</c> if there's none
/// </summary>
public Win32ResourcesChunk Win32Resources => win32Resources;
/// <summary>
/// Gets the strong name signature or <c>null</c> if there's none
/// </summary>
public StrongNameSignature StrongNameSignature => strongNameSignature;
/// <summary>
/// Gets all <see cref="PESection"/>s. The reloc section must be the last section, so use <see cref="AddSection(PESection)"/> if you need to append a section
/// </summary>
public abstract List<PESection> Sections { get; }
/// <summary>
/// Adds <paramref name="section"/> to the sections list, but before the reloc section which must be last
/// </summary>
/// <param name="section">New section to add to the list</param>
public virtual void AddSection(PESection section) => Sections.Add(section);
/// <summary>
/// Gets the <c>.text</c> section
/// </summary>
public abstract PESection TextSection { get; }
/// <summary>
/// Gets the <c>.rsrc</c> section or <c>null</c> if there's none
/// </summary>
public abstract PESection RsrcSection { get; }
/// <summary>
/// Gets the debug directory or <c>null</c> if there's none
/// </summary>
public DebugDirectory DebugDirectory => debugDirectory;
/// <summary>
/// <c>true</c> if <c>this</c> is a <see cref="NativeModuleWriter"/>, <c>false</c> if
/// <c>this</c> is a <see cref="ModuleWriter"/>.
/// </summary>
public bool IsNativeWriter => this is NativeModuleWriter;
/// <summary>
/// null if we're not writing a PDB
/// </summary>
PdbState pdbState;
/// <summary>
/// Writes the module to a file
/// </summary>
/// <param name="fileName">File name. The file will be truncated if it exists.</param>
public void Write(string fileName) {
using (var dest = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) {
dest.SetLength(0);
try {
Write(dest);
}
catch {
// Writing failed. Delete the file since it's useless.
dest.Close();
DeleteFileNoThrow(fileName);
throw;
}
}
}
static void DeleteFileNoThrow(string fileName) {
if (string.IsNullOrEmpty(fileName))
return;
try {
File.Delete(fileName);
}
catch {
}
}
/// <summary>
/// Writes the module to a <see cref="Stream"/>
/// </summary>
/// <param name="dest">Destination stream</param>
public void Write(Stream dest) {
pdbState = TheOptions.WritePdb && Module.PdbState is not null ? Module.PdbState : null;
if (TheOptions.DelaySign) {
Debug.Assert(TheOptions.StrongNamePublicKey is not null, "Options.StrongNamePublicKey must be initialized when delay signing the assembly");
Debug.Assert(TheOptions.StrongNameKey is null, "Options.StrongNameKey must be null when delay signing the assembly");
TheOptions.Cor20HeaderOptions.Flags &= ~ComImageFlags.StrongNameSigned;
}
else if (TheOptions.StrongNameKey is not null || TheOptions.StrongNamePublicKey is not null)
TheOptions.Cor20HeaderOptions.Flags |= ComImageFlags.StrongNameSigned;
destStream = dest;
destStreamBaseOffset = destStream.Position;
OnWriterEvent(ModuleWriterEvent.Begin);
var imageLength = WriteImpl();
destStream.Position = destStreamBaseOffset + imageLength;
OnWriterEvent(ModuleWriterEvent.End);
}
/// <summary>
/// Returns the module that is written
/// </summary>
public abstract ModuleDef Module { get; }
/// <summary>
/// Writes the module to <see cref="destStream"/>. Event listeners and
/// <see cref="destStream"/> have been initialized when this method is called.
/// </summary>
/// <returns>Number of bytes written</returns>
protected abstract long WriteImpl();
/// <summary>
/// Creates the strong name signature if the module has one of the strong name flags
/// set or wants to sign the assembly.
/// </summary>
protected void CreateStrongNameSignature() {
if (TheOptions.DelaySign && TheOptions.StrongNamePublicKey is not null) {
int len = TheOptions.StrongNamePublicKey.CreatePublicKey().Length - 0x20;
strongNameSignature = new StrongNameSignature(len > 0 ? len : 0x80);
}
else if (TheOptions.StrongNameKey is not null)
strongNameSignature = new StrongNameSignature(TheOptions.StrongNameKey.SignatureSize);
else if (Module.Assembly is not null && !PublicKeyBase.IsNullOrEmpty2(Module.Assembly.PublicKey)) {
int len = Module.Assembly.PublicKey.Data.Length - 0x20;
strongNameSignature = new StrongNameSignature(len > 0 ? len : 0x80);
}
else if (((TheOptions.Cor20HeaderOptions.Flags ?? Module.Cor20HeaderFlags) & ComImageFlags.StrongNameSigned) != 0)
strongNameSignature = new StrongNameSignature(0x80);
}
/// <summary>
/// Creates the .NET metadata chunks (constants, method bodies, .NET resources,
/// the metadata, and Win32 resources)
/// </summary>
/// <param name="module"></param>
protected void CreateMetadataChunks(ModuleDef module) {
constants = new UniqueChunkList<ByteArrayChunk>();
methodBodies = new MethodBodyChunks(TheOptions.ShareMethodBodies);
netResources = new NetResources(DEFAULT_NETRESOURCES_ALIGNMENT);
DebugMetadataKind debugKind;
if (pdbState is not null && (pdbState.PdbFileKind == PdbFileKind.PortablePDB || pdbState.PdbFileKind == PdbFileKind.EmbeddedPortablePDB))
debugKind = DebugMetadataKind.Standalone;
else
debugKind = DebugMetadataKind.None;
metadata = Metadata.Create(module, constants, methodBodies, netResources, TheOptions.MetadataOptions, debugKind);
metadata.Logger = TheOptions.MetadataLogger ?? this;
metadata.MetadataEvent += Metadata_MetadataEvent;
metadata.ProgressUpdated += Metadata_ProgressUpdated;
// StrongNamePublicKey is used if the user wants to override the assembly's
// public key or when enhanced strong naming the assembly.
var pk = TheOptions.StrongNamePublicKey;
if (pk is not null)
metadata.AssemblyPublicKey = pk.CreatePublicKey();
else if (TheOptions.StrongNameKey is not null)
metadata.AssemblyPublicKey = TheOptions.StrongNameKey.PublicKey;
var w32Resources = GetWin32Resources();
if (w32Resources is not null)
win32Resources = new Win32ResourcesChunk(w32Resources);
}
/// <summary>
/// Gets the Win32 resources that should be written to the new image or <c>null</c> if none
/// </summary>
protected abstract Win32Resources GetWin32Resources();
/// <summary>
/// Calculates <see cref="RVA"/> and <see cref="FileOffset"/> of all <see cref="IChunk"/>s
/// </summary>
/// <param name="chunks">All chunks</param>
/// <param name="offset">Starting file offset</param>
/// <param name="rva">Starting RVA</param>
/// <param name="fileAlignment">File alignment</param>
/// <param name="sectionAlignment">Section alignment</param>
protected void CalculateRvasAndFileOffsets(List<IChunk> chunks, FileOffset offset, RVA rva, uint fileAlignment, uint sectionAlignment) {
int count = chunks.Count;
var maxAlignment = Math.Min(fileAlignment, sectionAlignment);
for (int i = 0; i < count; i++) {
var chunk = chunks[i];
// We don't need to align to result of CalculateAlignment as all the chunks in `chunks` either
// don't need a specific alignment or align themselves.
var alignment = chunk.CalculateAlignment();
if (alignment > maxAlignment)
Error("Chunk alignment is too big. Chunk: {0}, alignment: {1:X4}", chunk, alignment);
chunk.SetOffset(offset, rva);
// If it has zero size, it's not present in the file (eg. a section that wasn't needed)
if (chunk.GetVirtualSize() != 0) {
offset += chunk.GetFileLength();
rva += chunk.GetVirtualSize();
offset = offset.AlignUp(fileAlignment);
rva = rva.AlignUp(sectionAlignment);
}
}
}
/// <summary>
/// Writes all chunks to <paramref name="writer"/>
/// </summary>
/// <param name="writer">The writer</param>
/// <param name="chunks">All chunks</param>
/// <param name="offset">File offset of first chunk</param>
/// <param name="fileAlignment">File alignment</param>
protected void WriteChunks(DataWriter writer, List<IChunk> chunks, FileOffset offset, uint fileAlignment) {
int count = chunks.Count;
for (int i = 0; i < count; i++) {
var chunk = chunks[i];
chunk.VerifyWriteTo(writer);
// If it has zero size, it's not present in the file (eg. a section that wasn't needed)
if (chunk.GetVirtualSize() != 0) {
offset += chunk.GetFileLength();
var newOffset = offset.AlignUp(fileAlignment);
writer.WriteZeroes((int)(newOffset - offset));
offset = newOffset;
}
}
}
/// <summary>
/// Strong name sign the assembly
/// </summary>
/// <param name="snSigOffset">Strong name signature offset</param>
protected void StrongNameSign(long snSigOffset) {
var snSigner = new StrongNameSigner(destStream, destStreamBaseOffset);
snSigner.WriteSignature(TheOptions.StrongNameKey, snSigOffset);
}
bool CanWritePdb() => pdbState is not null;
/// <summary>
/// Creates the debug directory if a PDB file should be written
/// </summary>
protected void CreateDebugDirectory() {
if (CanWritePdb())
debugDirectory = new DebugDirectory();
}
/// <summary>
/// Write the PDB file. The caller should send the PDB events before and after calling this
/// method.
/// </summary>
protected void WritePdbFile() {
if (!CanWritePdb())
return;
if (debugDirectory is null)
throw new InvalidOperationException("debugDirectory is null but WritePdb is true");
if (pdbState is null) {
Error("TheOptions.WritePdb is true but module has no PdbState");
return;
}
try {
switch (pdbState.PdbFileKind) {
case PdbFileKind.WindowsPDB:
WriteWindowsPdb(pdbState);
break;
case PdbFileKind.PortablePDB:
WritePortablePdb(pdbState, false);
break;
case PdbFileKind.EmbeddedPortablePDB:
WritePortablePdb(pdbState, true);
break;
default:
Error("Invalid PDB file kind {0}", pdbState.PdbFileKind);
break;
}
}
catch {
DeleteFileNoThrow(createdPdbFileName);
throw;
}
}
void AddReproduciblePdbDebugDirectoryEntry() =>
debugDirectory.Add(Array2.Empty<byte>(), type: ImageDebugType.Reproducible, majorVersion: 0, minorVersion: 0, timeDateStamp: 0);
void AddPdbChecksumDebugDirectoryEntry(byte[] checksumBytes, ChecksumAlgorithm checksumAlgorithm) {
var stream = new MemoryStream();
var writer = new DataWriter(stream);
var checksumName = Hasher.GetChecksumName(checksumAlgorithm);
writer.WriteBytes(Encoding.UTF8.GetBytes(checksumName));
writer.WriteByte(0);
writer.WriteBytes(checksumBytes);
var blob = stream.ToArray();
debugDirectory.Add(blob, ImageDebugType.PdbChecksum, majorVersion: 1, minorVersion: 0, timeDateStamp: 0);
}
const uint PdbAge = 1;
void WriteWindowsPdb(PdbState pdbState) {
bool addPdbChecksumDebugDirectoryEntry = (TheOptions.PdbOptions & PdbWriterOptions.PdbChecksum) != 0;
addPdbChecksumDebugDirectoryEntry = false;//TODO: If this is true, get the checksum from the PDB writer
var symWriter = GetWindowsPdbSymbolWriter(TheOptions.PdbOptions, out var pdbFilename);
if (symWriter is null) {
Error("Could not create a PDB symbol writer. A Windows OS might be required.");
return;
}
using (var pdbWriter = new WindowsPdbWriter(symWriter, pdbState, metadata)) {
pdbWriter.Logger = TheOptions.Logger;
pdbWriter.Write();
var pdbAge = PdbAge;
bool hasContentId = pdbWriter.GetDebugInfo(TheOptions.PdbChecksumAlgorithm, ref pdbAge, out var pdbGuid, out uint stamp, out var idd, out var codeViewData);
if (hasContentId) {
debugDirectory.Add(GetCodeViewData(pdbGuid, pdbAge, TheOptions.PdbFileNameInDebugDirectory ?? pdbFilename),
type: ImageDebugType.CodeView,
majorVersion: 0,
minorVersion: 0,
timeDateStamp: stamp);
}
else {
Debug.Fail("Failed to get the PDB content ID");
if (codeViewData is null)
throw new InvalidOperationException();
var entry = debugDirectory.Add(codeViewData);
entry.DebugDirectory = idd;
entry.DebugDirectory.TimeDateStamp = GetTimeDateStamp();
}
//TODO: Only do this if symWriter supports PDB checksums
if (addPdbChecksumDebugDirectoryEntry)
{}//TODO: AddPdbChecksumDebugDirectoryEntry(checksumBytes, TheOptions.PdbChecksumAlgorithm);, and verify that the order of the debug dir entries is the same as Roslyn created binaries
if (symWriter.IsDeterministic)
AddReproduciblePdbDebugDirectoryEntry();
}
}
/// <summary>
/// Gets the timestamp stored in the PE header
/// </summary>
/// <returns></returns>
protected uint GetTimeDateStamp() {
var td = TheOptions.PEHeadersOptions.TimeDateStamp;
if (td.HasValue)
return (uint)td;
TheOptions.PEHeadersOptions.TimeDateStamp = PEHeadersOptions.CreateNewTimeDateStamp();
return (uint)TheOptions.PEHeadersOptions.TimeDateStamp;
}
SymbolWriter GetWindowsPdbSymbolWriter(PdbWriterOptions options, out string pdbFilename) {
#if NETSTANDARD || NETCOREAPP
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
pdbFilename = null;
return null;
}
#endif
if (TheOptions.PdbStream is not null) {
return Pdb.Dss.SymbolReaderWriterFactory.Create(options, TheOptions.PdbStream,
pdbFilename = TheOptions.PdbFileName ??
GetStreamName(TheOptions.PdbStream) ??
GetDefaultPdbFileName());
}
if (!string.IsNullOrEmpty(TheOptions.PdbFileName)) {
createdPdbFileName = pdbFilename = TheOptions.PdbFileName;
return Pdb.Dss.SymbolReaderWriterFactory.Create(options, createdPdbFileName);
}
createdPdbFileName = pdbFilename = GetDefaultPdbFileName();
if (createdPdbFileName is null)
return null;
return Pdb.Dss.SymbolReaderWriterFactory.Create(options, createdPdbFileName);
}
static string GetStreamName(Stream stream) => (stream as FileStream)?.Name;
static string GetModuleName(ModuleDef module) {
var name = module.Name ?? string.Empty;
if (string.IsNullOrEmpty(name))
return null;
if (name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".netmodule", StringComparison.OrdinalIgnoreCase))
return name;
return name + ".pdb";
}
string GetDefaultPdbFileName() {
var destFileName = GetStreamName(destStream) ?? GetModuleName(Module);
if (string.IsNullOrEmpty(destFileName)) {
Error("TheOptions.WritePdb is true but it's not possible to guess the default PDB file name. Set PdbFileName to the name of the PDB file.");
return null;
}
return Path.ChangeExtension(destFileName, "pdb");
}
void WritePortablePdb(PdbState pdbState, bool isEmbeddedPortablePdb) {
bool ownsStream = false;
Stream pdbStream = null;
try {
MemoryStream embeddedMemoryStream = null;
if (isEmbeddedPortablePdb) {
pdbStream = embeddedMemoryStream = new MemoryStream();
ownsStream = true;
}
else
pdbStream = GetStandalonePortablePdbStream(out ownsStream);
if (pdbStream is null)
throw new ModuleWriterException("Couldn't create a PDB stream");
var pdbFilename = TheOptions.PdbFileName ?? GetStreamName(pdbStream) ?? GetDefaultPdbFileName();
if (isEmbeddedPortablePdb)
pdbFilename = Path.GetFileName(pdbFilename);
uint entryPointToken;
if (pdbState.UserEntryPoint is null)
entryPointToken = 0;
else
entryPointToken = new MDToken(Table.Method, metadata.GetRid(pdbState.UserEntryPoint)).Raw;
metadata.WritePortablePdb(pdbStream, entryPointToken, out var pdbIdOffset);
Guid pdbGuid;
var pdbId = new byte[20];
var pdbIdWriter = new ArrayWriter(pdbId);
uint codeViewTimestamp;
byte[] checksumBytes;
if ((TheOptions.PdbOptions & PdbWriterOptions.Deterministic) != 0 ||
(TheOptions.PdbOptions & PdbWriterOptions.PdbChecksum) != 0 ||
TheOptions.GetPdbContentId is null) {
pdbStream.Position = 0;
checksumBytes = Hasher.Hash(TheOptions.PdbChecksumAlgorithm, pdbStream, pdbStream.Length);
if (checksumBytes.Length < 20)
throw new ModuleWriterException("Checksum bytes length < 20");
RoslynContentIdProvider.GetContentId(checksumBytes, out pdbGuid, out codeViewTimestamp);
}
else {
var contentId = TheOptions.GetPdbContentId(pdbStream, GetTimeDateStamp());
codeViewTimestamp = contentId.Timestamp;
pdbGuid = contentId.Guid;
checksumBytes = null;
}
pdbIdWriter.WriteBytes(pdbGuid.ToByteArray());
pdbIdWriter.WriteUInt32(codeViewTimestamp);
Debug.Assert(pdbIdWriter.Position == pdbId.Length);
pdbStream.Position = pdbIdOffset;
pdbStream.Write(pdbId, 0, pdbId.Length);
// NOTE: We add these directory entries in the same order as Roslyn seems to do:
// - CodeView
// - PdbChecksum
// - Reproducible
// - EmbeddedPortablePdb
debugDirectory.Add(GetCodeViewData(pdbGuid, PdbAge, TheOptions.PdbFileNameInDebugDirectory ?? pdbFilename),
type: ImageDebugType.CodeView,
majorVersion: PortablePdbConstants.FormatVersion,
minorVersion: PortablePdbConstants.PortableCodeViewVersionMagic,
timeDateStamp: codeViewTimestamp);
if (checksumBytes is not null)
AddPdbChecksumDebugDirectoryEntry(checksumBytes, TheOptions.PdbChecksumAlgorithm);
if ((TheOptions.PdbOptions & PdbWriterOptions.Deterministic) != 0)
AddReproduciblePdbDebugDirectoryEntry();
if (isEmbeddedPortablePdb) {
Debug.Assert(embeddedMemoryStream is not null);
debugDirectory.Add(CreateEmbeddedPortablePdbBlob(embeddedMemoryStream),
type: ImageDebugType.EmbeddedPortablePdb,
majorVersion: PortablePdbConstants.FormatVersion,
minorVersion: PortablePdbConstants.EmbeddedVersion,
timeDateStamp: 0);
}
}
finally {
if (ownsStream && pdbStream is not null)
pdbStream.Dispose();
}
}
static byte[] CreateEmbeddedPortablePdbBlob(MemoryStream portablePdbStream) {
var compressedData = Compress(portablePdbStream);
var data = new byte[4 + 4 + compressedData.Length];
var stream = new MemoryStream(data);
var writer = new DataWriter(stream);
writer.WriteInt32(0x4244504D);//"MPDB"
writer.WriteUInt32((uint)portablePdbStream.Length);
writer.WriteBytes(compressedData);
Debug.Assert(stream.Position == data.Length);
return data;
}
static byte[] Compress(MemoryStream sourceStream) {
sourceStream.Position = 0;
var destStream = new MemoryStream();
using (var deflate = new DeflateStream(destStream, CompressionMode.Compress)) {
var source = sourceStream.ToArray();
deflate.Write(source, 0, source.Length);
}
return destStream.ToArray();
}
static byte[] GetCodeViewData(Guid guid, uint age, string filename) {
var stream = new MemoryStream();
var writer = new DataWriter(stream);
writer.WriteInt32(0x53445352);
writer.WriteBytes(guid.ToByteArray());
writer.WriteUInt32(age);
writer.WriteBytes(Encoding.UTF8.GetBytes(filename));
writer.WriteByte(0);
return stream.ToArray();
}
Stream GetStandalonePortablePdbStream(out bool ownsStream) {
if (TheOptions.PdbStream is not null) {
ownsStream = false;
return TheOptions.PdbStream;
}
if (!string.IsNullOrEmpty(TheOptions.PdbFileName))
createdPdbFileName = TheOptions.PdbFileName;
else
createdPdbFileName = GetDefaultPdbFileName();
if (createdPdbFileName is null) {
ownsStream = false;
return null;
}
ownsStream = true;
return File.Create(createdPdbFileName);
}
void Metadata_MetadataEvent(object sender, MetadataWriterEventArgs e) {
switch (e.Event) {
case MetadataEvent.BeginCreateTables:
OnWriterEvent(ModuleWriterEvent.MDBeginCreateTables);
break;
case MetadataEvent.AllocateTypeDefRids:
OnWriterEvent(ModuleWriterEvent.MDAllocateTypeDefRids);
break;
case MetadataEvent.AllocateMemberDefRids:
OnWriterEvent(ModuleWriterEvent.MDAllocateMemberDefRids);
break;
case MetadataEvent.MemberDefRidsAllocated:
OnWriterEvent(ModuleWriterEvent.MDMemberDefRidsAllocated);
break;
case MetadataEvent.MemberDefsInitialized:
OnWriterEvent(ModuleWriterEvent.MDMemberDefsInitialized);
break;
case MetadataEvent.BeforeSortTables:
OnWriterEvent(ModuleWriterEvent.MDBeforeSortTables);
break;
case MetadataEvent.MostTablesSorted:
OnWriterEvent(ModuleWriterEvent.MDMostTablesSorted);
break;
case MetadataEvent.MemberDefCustomAttributesWritten:
OnWriterEvent(ModuleWriterEvent.MDMemberDefCustomAttributesWritten);
break;
case MetadataEvent.BeginAddResources:
OnWriterEvent(ModuleWriterEvent.MDBeginAddResources);
break;
case MetadataEvent.EndAddResources:
OnWriterEvent(ModuleWriterEvent.MDEndAddResources);
break;
case MetadataEvent.BeginWriteMethodBodies:
OnWriterEvent(ModuleWriterEvent.MDBeginWriteMethodBodies);
break;
case MetadataEvent.EndWriteMethodBodies:
OnWriterEvent(ModuleWriterEvent.MDEndWriteMethodBodies);
break;
case MetadataEvent.OnAllTablesSorted:
OnWriterEvent(ModuleWriterEvent.MDOnAllTablesSorted);
break;
case MetadataEvent.EndCreateTables:
OnWriterEvent(ModuleWriterEvent.MDEndCreateTables);
break;
default:
Debug.Fail($"Unknown MD event: {e.Event}");
break;
}
}
void Metadata_ProgressUpdated(object sender, MetadataProgressEventArgs e) =>
RaiseProgress(ModuleWriterEvent.MDBeginCreateTables, ModuleWriterEvent.MDEndCreateTables + 1, e.Progress);
/// <summary>
/// Raises a writer event
/// </summary>
/// <param name="evt">Event</param>
protected void OnWriterEvent(ModuleWriterEvent evt) {
RaiseProgress(evt, 0);
TheOptions.RaiseEvent(this, new ModuleWriterEventArgs(this, evt));
}
static readonly double[] eventToProgress = new double[(int)ModuleWriterEvent.End - (int)ModuleWriterEvent.Begin + 1 + 1] {
0, // Begin
0.00128048488389907,// PESectionsCreated
0.0524625293056615, // ChunksCreated
0.0531036610555682, // ChunksAddedToSections
0.0535679983835939, // MDBeginCreateTables
0.0547784058004697, // MDAllocateTypeDefRids
0.0558606342971218, // MDAllocateMemberDefRids
0.120553993799033, // MDMemberDefRidsAllocated
0.226210300699921, // MDMemberDefsInitialized
0.236002648477671, // MDBeforeSortTables
0.291089703426468, // MDMostTablesSorted
0.449919748849947, // MDMemberDefCustomAttributesWritten
0.449919985998736, // MDBeginAddResources
0.452716444513587, // MDEndAddResources
0.452716681662375, // MDBeginWriteMethodBodies
0.924922132195272, // MDEndWriteMethodBodies
0.931410404476231, // MDOnAllTablesSorted
0.931425463424305, // MDEndCreateTables
0.932072998191503, // BeginWritePdb
0.932175327893773, // EndWritePdb
0.932175446468167, // BeginCalculateRvasAndFileOffsets
0.954646479929387, // EndCalculateRvasAndFileOffsets
0.95492263969368, // BeginWriteChunks
0.980563166714175, // EndWriteChunks
0.980563403862964, // BeginStrongNameSign
0.980563403862964, // EndStrongNameSign
0.980563522437358, // BeginWritePEChecksum
0.999975573674777, // EndWritePEChecksum
1, // End
1,// An extra one so we can get the next base progress without checking the index
};
void RaiseProgress(ModuleWriterEvent evt, double subProgress) => RaiseProgress(evt, evt + 1, subProgress);
void RaiseProgress(ModuleWriterEvent evt, ModuleWriterEvent nextEvt, double subProgress) {
subProgress = Math.Min(1, Math.Max(0, subProgress));
var baseProgress = eventToProgress[(int)evt];
var nextProgress = eventToProgress[(int)nextEvt];
var progress = baseProgress + (nextProgress - baseProgress) * subProgress;
progress = Math.Min(1, Math.Max(0, progress));
TheOptions.RaiseEvent(this, new ModuleWriterProgressEventArgs(this, progress));
}
ILogger GetLogger() => TheOptions.Logger ?? DummyLogger.ThrowModuleWriterExceptionOnErrorInstance;
/// <inheritdoc/>
void ILogger.Log(object sender, LoggerEvent loggerEvent, string format, params object[] args) =>
GetLogger().Log(this, loggerEvent, format, args);
/// <inheritdoc/>
bool ILogger.IgnoresEvent(LoggerEvent loggerEvent) => GetLogger().IgnoresEvent(loggerEvent);
/// <summary>
/// Logs an error message
/// </summary>
/// <param name="format">Format</param>
/// <param name="args">Format args</param>
protected void Error(string format, params object[] args) =>
GetLogger().Log(this, LoggerEvent.Error, format, args);
/// <summary>
/// Logs a warning message
/// </summary>
/// <param name="format">Format</param>
/// <param name="args">Format args</param>
protected void Warning(string format, params object[] args) =>
GetLogger().Log(this, LoggerEvent.Warning, format, args);
}
}