1051 lines
38 KiB
C#
1051 lines
38 KiB
C#
// dnlib: See LICENSE.txt for more info
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using dnlib.Utils;
|
|
using dnlib.DotNet.MD;
|
|
using dnlib.DotNet.Writer;
|
|
using System.Text.RegularExpressions;
|
|
using dnlib.DotNet.Pdb;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
|
|
namespace dnlib.DotNet {
|
|
/// <summary>
|
|
/// A high-level representation of a row in the Assembly table
|
|
/// </summary>
|
|
public abstract class AssemblyDef : IHasCustomAttribute, IHasDeclSecurity, IHasCustomDebugInformation, IAssembly, IListListener<ModuleDef>, ITypeDefFinder, IDnlibDef {
|
|
/// <summary>
|
|
/// The row id in its table
|
|
/// </summary>
|
|
protected uint rid;
|
|
|
|
/// <inheritdoc/>
|
|
public MDToken MDToken => new MDToken(Table.Assembly, rid);
|
|
|
|
/// <inheritdoc/>
|
|
public uint Rid {
|
|
get => rid;
|
|
set => rid = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public int HasCustomAttributeTag => 14;
|
|
|
|
/// <inheritdoc/>
|
|
public int HasDeclSecurityTag => 2;
|
|
|
|
/// <summary>
|
|
/// From column Assembly.HashAlgId
|
|
/// </summary>
|
|
public AssemblyHashAlgorithm HashAlgorithm {
|
|
get => hashAlgorithm;
|
|
set => hashAlgorithm = value;
|
|
}
|
|
/// <summary/>
|
|
protected AssemblyHashAlgorithm hashAlgorithm;
|
|
|
|
/// <summary>
|
|
/// From columns Assembly.MajorVersion, Assembly.MinorVersion, Assembly.BuildNumber,
|
|
/// Assembly.RevisionNumber.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="value"/> is <c>null</c></exception>
|
|
public Version Version {
|
|
get => version;
|
|
set => version = value ?? throw new ArgumentNullException(nameof(value));
|
|
}
|
|
/// <summary/>
|
|
protected Version version;
|
|
|
|
/// <summary>
|
|
/// From column Assembly.Flags
|
|
/// </summary>
|
|
public AssemblyAttributes Attributes {
|
|
get => (AssemblyAttributes)attributes;
|
|
set => attributes = (int)value;
|
|
}
|
|
/// <summary>Attributes</summary>
|
|
protected int attributes;
|
|
|
|
/// <summary>
|
|
/// From column Assembly.PublicKey
|
|
/// </summary>
|
|
/// <remarks>An empty <see cref="PublicKey"/> is created if the caller writes <c>null</c></remarks>
|
|
public PublicKey PublicKey {
|
|
get => publicKey;
|
|
set => publicKey = value ?? new PublicKey();
|
|
}
|
|
/// <summary/>
|
|
protected PublicKey publicKey;
|
|
|
|
/// <summary>
|
|
/// Gets the public key token which is calculated from <see cref="PublicKey"/>
|
|
/// </summary>
|
|
public PublicKeyToken PublicKeyToken => publicKey.Token;
|
|
|
|
/// <summary>
|
|
/// From column Assembly.Name
|
|
/// </summary>
|
|
public UTF8String Name {
|
|
get => name;
|
|
set => name = value;
|
|
}
|
|
/// <summary>Name</summary>
|
|
protected UTF8String name;
|
|
|
|
/// <summary>
|
|
/// From column Assembly.Locale
|
|
/// </summary>
|
|
public UTF8String Culture {
|
|
get => culture;
|
|
set => culture = value;
|
|
}
|
|
/// <summary>Name</summary>
|
|
protected UTF8String culture;
|
|
|
|
/// <inheritdoc/>
|
|
public IList<DeclSecurity> DeclSecurities {
|
|
get {
|
|
if (declSecurities is null)
|
|
InitializeDeclSecurities();
|
|
return declSecurities;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected IList<DeclSecurity> declSecurities;
|
|
/// <summary>Initializes <see cref="declSecurities"/></summary>
|
|
protected virtual void InitializeDeclSecurities() =>
|
|
Interlocked.CompareExchange(ref declSecurities, new List<DeclSecurity>(), null);
|
|
|
|
/// <inheritdoc/>
|
|
public PublicKeyBase PublicKeyOrToken => publicKey;
|
|
|
|
/// <inheritdoc/>
|
|
public string FullName => GetFullNameWithPublicKeyToken();
|
|
|
|
/// <inheritdoc/>
|
|
public string FullNameToken => GetFullNameWithPublicKeyToken();
|
|
|
|
/// <summary>
|
|
/// Gets all modules. The first module is always the <see cref="ManifestModule"/>.
|
|
/// </summary>
|
|
public IList<ModuleDef> Modules {
|
|
get {
|
|
if (modules is null)
|
|
InitializeModules();
|
|
return modules;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected LazyList<ModuleDef> modules;
|
|
/// <summary>Initializes <see cref="modules"/></summary>
|
|
protected virtual void InitializeModules() =>
|
|
Interlocked.CompareExchange(ref modules, new LazyList<ModuleDef>(this), null);
|
|
|
|
/// <summary>
|
|
/// Gets all custom attributes
|
|
/// </summary>
|
|
public CustomAttributeCollection CustomAttributes {
|
|
get {
|
|
if (customAttributes is null)
|
|
InitializeCustomAttributes();
|
|
return customAttributes;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected CustomAttributeCollection customAttributes;
|
|
/// <summary>Initializes <see cref="customAttributes"/></summary>
|
|
protected virtual void InitializeCustomAttributes() =>
|
|
Interlocked.CompareExchange(ref customAttributes, new CustomAttributeCollection(), null);
|
|
|
|
/// <inheritdoc/>
|
|
public bool HasCustomAttributes => CustomAttributes.Count > 0;
|
|
|
|
|
|
/// <inheritdoc/>
|
|
public int HasCustomDebugInformationTag => 14;
|
|
|
|
/// <inheritdoc/>
|
|
public bool HasCustomDebugInfos => CustomDebugInfos.Count > 0;
|
|
|
|
/// <summary>
|
|
/// Gets all custom debug infos
|
|
/// </summary>
|
|
public IList<PdbCustomDebugInfo> CustomDebugInfos {
|
|
get {
|
|
if (customDebugInfos is null)
|
|
InitializeCustomDebugInfos();
|
|
return customDebugInfos;
|
|
}
|
|
}
|
|
/// <summary/>
|
|
protected IList<PdbCustomDebugInfo> customDebugInfos;
|
|
/// <summary>Initializes <see cref="customDebugInfos"/></summary>
|
|
protected virtual void InitializeCustomDebugInfos() =>
|
|
Interlocked.CompareExchange(ref customDebugInfos, new List<PdbCustomDebugInfo>(), null);
|
|
/// <inheritdoc/>
|
|
public bool HasDeclSecurities => DeclSecurities.Count > 0;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if <see cref="Modules"/> is not empty
|
|
/// </summary>
|
|
public bool HasModules => Modules.Count > 0;
|
|
|
|
/// <summary>
|
|
/// Gets the manifest (main) module. This is always the first module in <see cref="Modules"/>.
|
|
/// <c>null</c> is returned if <see cref="Modules"/> is empty.
|
|
/// </summary>
|
|
public ModuleDef ManifestModule => Modules.Count == 0 ? null : Modules[0];
|
|
|
|
/// <summary>
|
|
/// Modify <see cref="attributes"/> property: <see cref="attributes"/> =
|
|
/// (<see cref="attributes"/> & <paramref name="andMask"/>) | <paramref name="orMask"/>.
|
|
/// </summary>
|
|
/// <param name="andMask">Value to <c>AND</c></param>
|
|
/// <param name="orMask">Value to OR</param>
|
|
void ModifyAttributes(AssemblyAttributes andMask, AssemblyAttributes orMask) =>
|
|
attributes = (attributes & (int)andMask) | (int)orMask;
|
|
|
|
/// <summary>
|
|
/// Set or clear flags in <see cref="attributes"/>
|
|
/// </summary>
|
|
/// <param name="set"><c>true</c> if flags should be set, <c>false</c> if flags should
|
|
/// be cleared</param>
|
|
/// <param name="flags">Flags to set or clear</param>
|
|
void ModifyAttributes(bool set, AssemblyAttributes flags) {
|
|
if (set)
|
|
attributes |= (int)flags;
|
|
else
|
|
attributes &= ~(int)flags;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="AssemblyAttributes.PublicKey"/> bit
|
|
/// </summary>
|
|
public bool HasPublicKey {
|
|
get => ((AssemblyAttributes)attributes & AssemblyAttributes.PublicKey) != 0;
|
|
set => ModifyAttributes(value, AssemblyAttributes.PublicKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the processor architecture
|
|
/// </summary>
|
|
public AssemblyAttributes ProcessorArchitecture {
|
|
get => (AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask;
|
|
set => ModifyAttributes(~AssemblyAttributes.PA_Mask, value & AssemblyAttributes.PA_Mask);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the processor architecture
|
|
/// </summary>
|
|
public AssemblyAttributes ProcessorArchitectureFull {
|
|
get => (AssemblyAttributes)attributes & AssemblyAttributes.PA_FullMask;
|
|
set => ModifyAttributes(~AssemblyAttributes.PA_FullMask, value & AssemblyAttributes.PA_FullMask);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if unspecified processor architecture
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureNone => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_None;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if neutral (PE32) architecture
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureMSIL => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_MSIL;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if x86 (PE32) architecture
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureX86 => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_x86;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if IA-64 (PE32+) architecture
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureIA64 => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_IA64;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if x64 (PE32+) architecture
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureX64 => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_AMD64;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if ARM (PE32) architecture
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureARM => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_ARM;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if eg. reference assembly (not runnable)
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureNoPlatform => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Mask) == AssemblyAttributes.PA_NoPlatform;
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="AssemblyAttributes.PA_Specified"/> bit
|
|
/// </summary>
|
|
public bool IsProcessorArchitectureSpecified {
|
|
get => ((AssemblyAttributes)attributes & AssemblyAttributes.PA_Specified) != 0;
|
|
set => ModifyAttributes(value, AssemblyAttributes.PA_Specified);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="AssemblyAttributes.EnableJITcompileTracking"/> bit
|
|
/// </summary>
|
|
public bool EnableJITcompileTracking {
|
|
get => ((AssemblyAttributes)attributes & AssemblyAttributes.EnableJITcompileTracking) != 0;
|
|
set => ModifyAttributes(value, AssemblyAttributes.EnableJITcompileTracking);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="AssemblyAttributes.DisableJITcompileOptimizer"/> bit
|
|
/// </summary>
|
|
public bool DisableJITcompileOptimizer {
|
|
get => ((AssemblyAttributes)attributes & AssemblyAttributes.DisableJITcompileOptimizer) != 0;
|
|
set => ModifyAttributes(value, AssemblyAttributes.DisableJITcompileOptimizer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the <see cref="AssemblyAttributes.Retargetable"/> bit
|
|
/// </summary>
|
|
public bool IsRetargetable {
|
|
get => ((AssemblyAttributes)attributes & AssemblyAttributes.Retargetable) != 0;
|
|
set => ModifyAttributes(value, AssemblyAttributes.Retargetable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the content type
|
|
/// </summary>
|
|
public AssemblyAttributes ContentType {
|
|
get => (AssemblyAttributes)attributes & AssemblyAttributes.ContentType_Mask;
|
|
set => ModifyAttributes(~AssemblyAttributes.ContentType_Mask, value & AssemblyAttributes.ContentType_Mask);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if content type is <c>Default</c>
|
|
/// </summary>
|
|
public bool IsContentTypeDefault => ((AssemblyAttributes)attributes & AssemblyAttributes.ContentType_Mask) == AssemblyAttributes.ContentType_Default;
|
|
|
|
/// <summary>
|
|
/// <c>true</c> if content type is <c>WindowsRuntime</c>
|
|
/// </summary>
|
|
public bool IsContentTypeWindowsRuntime => ((AssemblyAttributes)attributes & AssemblyAttributes.ContentType_Mask) == AssemblyAttributes.ContentType_WindowsRuntime;
|
|
|
|
/// <summary>
|
|
/// Finds a module in this assembly
|
|
/// </summary>
|
|
/// <param name="name">Name of module</param>
|
|
/// <returns>A <see cref="ModuleDef"/> instance or <c>null</c> if it wasn't found.</returns>
|
|
public ModuleDef FindModule(UTF8String name) {
|
|
var modules = Modules;
|
|
int count = modules.Count;
|
|
for (int i = 0; i < count; i++) {
|
|
var module = modules[i];
|
|
if (module is null)
|
|
continue;
|
|
if (UTF8String.CaseInsensitiveEquals(module.Name, name))
|
|
return module;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a file
|
|
/// </summary>
|
|
/// <param name="fileName">File name of an existing .NET assembly</param>
|
|
/// <param name="context">Module context or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="fileName"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(string fileName, ModuleContext context) =>
|
|
Load(fileName, new ModuleCreationOptions(context));
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a file
|
|
/// </summary>
|
|
/// <param name="fileName">File name of an existing .NET assembly</param>
|
|
/// <param name="options">Module creation options or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="fileName"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(string fileName, ModuleCreationOptions options = null) {
|
|
if (fileName is null)
|
|
throw new ArgumentNullException(nameof(fileName));
|
|
ModuleDef module = null;
|
|
try {
|
|
module = ModuleDefMD.Load(fileName, options);
|
|
var asm = module.Assembly;
|
|
if (asm is null)
|
|
throw new BadImageFormatException($"{fileName} is only a .NET module, not a .NET assembly. Use ModuleDef.Load().");
|
|
return asm;
|
|
}
|
|
catch {
|
|
if (module is not null)
|
|
module.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a byte[]
|
|
/// </summary>
|
|
/// <param name="data">Contents of a .NET assembly</param>
|
|
/// <param name="context">Module context or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="data"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(byte[] data, ModuleContext context) =>
|
|
Load(data, new ModuleCreationOptions(context));
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a byte[]
|
|
/// </summary>
|
|
/// <param name="data">Contents of a .NET assembly</param>
|
|
/// <param name="options">Module creation options or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="data"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(byte[] data, ModuleCreationOptions options = null) {
|
|
if (data is null)
|
|
throw new ArgumentNullException(nameof(data));
|
|
ModuleDef module = null;
|
|
try {
|
|
module = ModuleDefMD.Load(data, options);
|
|
var asm = module.Assembly;
|
|
if (asm is null)
|
|
throw new BadImageFormatException($"{module.ToString()} is only a .NET module, not a .NET assembly. Use ModuleDef.Load().");
|
|
return asm;
|
|
}
|
|
catch {
|
|
if (module is not null)
|
|
module.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a memory location
|
|
/// </summary>
|
|
/// <param name="addr">Address of a .NET assembly</param>
|
|
/// <param name="context">Module context or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="addr"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(IntPtr addr, ModuleContext context) =>
|
|
Load(addr, new ModuleCreationOptions(context));
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a memory location
|
|
/// </summary>
|
|
/// <param name="addr">Address of a .NET assembly</param>
|
|
/// <param name="options">Module creation options or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="addr"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(IntPtr addr, ModuleCreationOptions options = null) {
|
|
if (addr == IntPtr.Zero)
|
|
throw new ArgumentNullException(nameof(addr));
|
|
ModuleDef module = null;
|
|
try {
|
|
module = ModuleDefMD.Load(addr, options);
|
|
var asm = module.Assembly;
|
|
if (asm is null)
|
|
throw new BadImageFormatException($"{module.ToString()} (addr: {addr.ToInt64():X8}) is only a .NET module, not a .NET assembly. Use ModuleDef.Load().");
|
|
return asm;
|
|
}
|
|
catch {
|
|
if (module is not null)
|
|
module.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a stream
|
|
/// </summary>
|
|
/// <remarks>This will read all bytes from the stream and call <see cref="Load(byte[],ModuleContext)"/>.
|
|
/// It's better to use one of the other Load() methods.</remarks>
|
|
/// <param name="stream">The stream</param>
|
|
/// <param name="context">Module context or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(Stream stream, ModuleContext context) =>
|
|
Load(stream, new ModuleCreationOptions(context));
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="AssemblyDef"/> instance from a stream
|
|
/// </summary>
|
|
/// <remarks>This will read all bytes from the stream and call <see cref="Load(byte[],ModuleContext)"/>.
|
|
/// It's better to use one of the other Load() methods.</remarks>
|
|
/// <param name="stream">The stream</param>
|
|
/// <param name="options">Module creation options or <c>null</c></param>
|
|
/// <returns>A new <see cref="AssemblyDef"/> instance</returns>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <c>null</c></exception>
|
|
/// <exception cref="BadImageFormatException">If it's not a .NET assembly (eg. not a .NET file or only a .NET module)</exception>
|
|
public static AssemblyDef Load(Stream stream, ModuleCreationOptions options = null) {
|
|
if (stream is null)
|
|
throw new ArgumentNullException(nameof(stream));
|
|
ModuleDef module = null;
|
|
try {
|
|
module = ModuleDefMD.Load(stream, options);
|
|
var asm = module.Assembly;
|
|
if (asm is null)
|
|
throw new BadImageFormatException($"{module.ToString()} is only a .NET module, not a .NET assembly. Use ModuleDef.Load().");
|
|
return asm;
|
|
}
|
|
catch {
|
|
if (module is not null)
|
|
module.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the assembly name with the public key
|
|
/// </summary>
|
|
public string GetFullNameWithPublicKey() => FullNameFactory.AssemblyFullName(this, false);
|
|
|
|
/// <summary>
|
|
/// Gets the assembly name with the public key token
|
|
/// </summary>
|
|
public string GetFullNameWithPublicKeyToken() => FullNameFactory.AssemblyFullName(this, true);
|
|
|
|
/// <summary>
|
|
/// Finds a <see cref="TypeDef"/>. For speed, enable <see cref="ModuleDef.EnableTypeDefFindCache"/>
|
|
/// if possible (read the documentation first).
|
|
/// </summary>
|
|
/// <param name="fullName">Full name of the type (no assembly information)</param>
|
|
/// <param name="isReflectionName"><c>true</c> if it's a reflection name, and nested
|
|
/// type names are separated by a <c>+</c> character. If <c>false</c>, nested type names
|
|
/// are separated by a <c>/</c> character.</param>
|
|
/// <returns>An existing <see cref="TypeDef"/> or <c>null</c> if it wasn't found.</returns>
|
|
public TypeDef Find(string fullName, bool isReflectionName) {
|
|
var modules = Modules;
|
|
int count = modules.Count;
|
|
for (int i = 0; i < count; i++) {
|
|
var module = modules[i];
|
|
if (module is null)
|
|
continue;
|
|
var type = module.Find(fullName, isReflectionName);
|
|
if (type is not null)
|
|
return type;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds a <see cref="TypeDef"/>. Its scope (i.e., module or assembly) is ignored when
|
|
/// looking up the type. For speed, enable <see cref="ModuleDef.EnableTypeDefFindCache"/>
|
|
/// if possible (read the documentation first).
|
|
/// </summary>
|
|
/// <param name="typeRef">The type ref</param>
|
|
/// <returns>An existing <see cref="TypeDef"/> or <c>null</c> if it wasn't found.</returns>
|
|
public TypeDef Find(TypeRef typeRef) {
|
|
var modules = Modules;
|
|
int count = modules.Count;
|
|
for (int i = 0; i < count; i++) {
|
|
var module = modules[i];
|
|
if (module is null)
|
|
continue;
|
|
var type = module.Find(typeRef);
|
|
if (type is not null)
|
|
return type;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the assembly to a file on disk. If the file exists, it will be truncated.
|
|
/// </summary>
|
|
/// <param name="filename">Filename</param>
|
|
/// <param name="options">Writer options</param>
|
|
public void Write(string filename, ModuleWriterOptions options = null) =>
|
|
ManifestModule.Write(filename, options);
|
|
|
|
/// <summary>
|
|
/// Writes the assembly to a stream.
|
|
/// </summary>
|
|
/// <param name="dest">Destination stream</param>
|
|
/// <param name="options">Writer options</param>
|
|
public void Write(Stream dest, ModuleWriterOptions options = null) =>
|
|
ManifestModule.Write(dest, options);
|
|
|
|
/// <summary>
|
|
/// Checks whether this assembly is a friend assembly of <paramref name="targetAsm"/>
|
|
/// </summary>
|
|
/// <param name="targetAsm">Target assembly</param>
|
|
public bool IsFriendAssemblyOf(AssemblyDef targetAsm) {
|
|
if (targetAsm is null)
|
|
return false;
|
|
if (this == targetAsm)
|
|
return true;
|
|
|
|
// Both must be unsigned or both must be signed according to the
|
|
// InternalsVisibleToAttribute documentation.
|
|
if (PublicKeyBase.IsNullOrEmpty2(publicKey) != PublicKeyBase.IsNullOrEmpty2(targetAsm.PublicKey))
|
|
return false;
|
|
|
|
foreach (var ca in targetAsm.CustomAttributes.FindAll("System.Runtime.CompilerServices.InternalsVisibleToAttribute")) {
|
|
if (ca.ConstructorArguments.Count != 1)
|
|
continue;
|
|
var arg = ca.ConstructorArguments.Count == 0 ? default : ca.ConstructorArguments[0];
|
|
if (arg.Type.GetElementType() != ElementType.String)
|
|
continue;
|
|
var asmName = arg.Value as UTF8String;
|
|
if (UTF8String.IsNull(asmName))
|
|
continue;
|
|
|
|
var asmInfo = new AssemblyNameInfo(asmName);
|
|
if (asmInfo.Name != name)
|
|
continue;
|
|
if (!PublicKeyBase.IsNullOrEmpty2(publicKey)) {
|
|
if (!PublicKey.Equals(asmInfo.PublicKeyOrToken as PublicKey))
|
|
continue;
|
|
}
|
|
else if (!PublicKeyBase.IsNullOrEmpty2(asmInfo.PublicKeyOrToken))
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds or updates an existing <c>System.Reflection.AssemblySignatureKeyAttribute</c>
|
|
/// attribute. This attribute is used in enhanced strong naming with key migration.
|
|
/// See http://msdn.microsoft.com/en-us/library/hh415055.aspx
|
|
/// </summary>
|
|
/// <param name="identityPubKey">Identity public key</param>
|
|
/// <param name="identityKey">Identity strong name key pair</param>
|
|
/// <param name="signaturePubKey">Signature public key</param>
|
|
public void UpdateOrCreateAssemblySignatureKeyAttribute(StrongNamePublicKey identityPubKey, StrongNameKey identityKey, StrongNamePublicKey signaturePubKey) {
|
|
var manifestModule = ManifestModule;
|
|
if (manifestModule is null)
|
|
return;
|
|
|
|
// Remove all existing attributes
|
|
CustomAttribute ca = null;
|
|
for (int i = 0; i < CustomAttributes.Count; i++) {
|
|
var caTmp = CustomAttributes[i];
|
|
if (caTmp.TypeFullName != "System.Reflection.AssemblySignatureKeyAttribute")
|
|
continue;
|
|
CustomAttributes.RemoveAt(i);
|
|
i--;
|
|
if (ca is null)
|
|
ca = caTmp;
|
|
}
|
|
|
|
if (IsValidAssemblySignatureKeyAttribute(ca))
|
|
ca.NamedArguments.Clear();
|
|
else
|
|
ca = CreateAssemblySignatureKeyAttribute();
|
|
|
|
var counterSig = StrongNameKey.CreateCounterSignatureAsString(identityPubKey, identityKey, signaturePubKey);
|
|
ca.ConstructorArguments[0] = new CAArgument(manifestModule.CorLibTypes.String, new UTF8String(signaturePubKey.ToString()));
|
|
ca.ConstructorArguments[1] = new CAArgument(manifestModule.CorLibTypes.String, new UTF8String(counterSig));
|
|
CustomAttributes.Add(ca);
|
|
}
|
|
|
|
bool IsValidAssemblySignatureKeyAttribute(CustomAttribute ca) {
|
|
if (dnlib.Settings.IsThreadSafe)
|
|
return false;
|
|
if (ca is null)
|
|
return false;
|
|
var ctor = ca.Constructor;
|
|
if (ctor is null)
|
|
return false;
|
|
var sig = ctor.MethodSig;
|
|
if (sig is null || sig.Params.Count != 2)
|
|
return false;
|
|
if (sig.Params[0].GetElementType() != ElementType.String)
|
|
return false;
|
|
if (sig.Params[1].GetElementType() != ElementType.String)
|
|
return false;
|
|
if (ca.ConstructorArguments.Count != 2)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
CustomAttribute CreateAssemblySignatureKeyAttribute() {
|
|
var manifestModule = ManifestModule;
|
|
var owner = manifestModule.UpdateRowId(new TypeRefUser(manifestModule, "System.Reflection", "AssemblySignatureKeyAttribute", manifestModule.CorLibTypes.AssemblyRef));
|
|
var methodSig = MethodSig.CreateInstance(manifestModule.CorLibTypes.Void, manifestModule.CorLibTypes.String, manifestModule.CorLibTypes.String);
|
|
var ctor = manifestModule.UpdateRowId(new MemberRefUser(manifestModule, MethodDef.InstanceConstructorName, methodSig, owner));
|
|
var ca = new CustomAttribute(ctor);
|
|
ca.ConstructorArguments.Add(new CAArgument(manifestModule.CorLibTypes.String, UTF8String.Empty));
|
|
ca.ConstructorArguments.Add(new CAArgument(manifestModule.CorLibTypes.String, UTF8String.Empty));
|
|
return ca;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the original <c>System.Runtime.Versioning.TargetFrameworkAttribute</c> custom attribute information if possible.
|
|
/// It reads this from the original metadata and doesn't use <see cref="CustomAttributes"/>.
|
|
/// Returns false if the custom attribute isn't present or if it is invalid.
|
|
/// </summary>
|
|
/// <param name="framework">Framework name</param>
|
|
/// <param name="version">Version</param>
|
|
/// <param name="profile">Profile</param>
|
|
/// <returns></returns>
|
|
public virtual bool TryGetOriginalTargetFrameworkAttribute(out string framework, out Version version, out string profile) {
|
|
framework = null;
|
|
version = null;
|
|
profile = null;
|
|
return false;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<ModuleDef>.OnLazyAdd(int index, ref ModuleDef module) {
|
|
if (module is null)
|
|
return;
|
|
#if DEBUG
|
|
if (module.Assembly is null)
|
|
throw new InvalidOperationException("Module.Assembly is null");
|
|
#endif
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<ModuleDef>.OnAdd(int index, ModuleDef module) {
|
|
if (module is null)
|
|
return;
|
|
if (module.Assembly is not null)
|
|
throw new InvalidOperationException("Module already has an assembly. Remove it from that assembly before adding it to this assembly.");
|
|
module.Assembly = this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<ModuleDef>.OnRemove(int index, ModuleDef module) {
|
|
if (module is not null)
|
|
module.Assembly = null;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<ModuleDef>.OnResize(int index) {
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IListListener<ModuleDef>.OnClear() {
|
|
foreach (var module in modules.GetEnumerable_NoLock()) {
|
|
if (module is not null)
|
|
module.Assembly = null;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override string ToString() => FullName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// An Assembly row created by the user and not present in the original .NET file
|
|
/// </summary>
|
|
public class AssemblyDefUser : AssemblyDef {
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public AssemblyDefUser()
|
|
: this(UTF8String.Empty, new Version(0, 0, 0, 0)) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="name">Simple name</param>
|
|
/// <exception cref="ArgumentNullException">If any of the args is invalid</exception>
|
|
public AssemblyDefUser(UTF8String name)
|
|
: this(name, new Version(0, 0, 0, 0), new PublicKey()) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="name">Simple name</param>
|
|
/// <param name="version">Version</param>
|
|
/// <exception cref="ArgumentNullException">If any of the args is invalid</exception>
|
|
public AssemblyDefUser(UTF8String name, Version version)
|
|
: this(name, version, new PublicKey()) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="name">Simple name</param>
|
|
/// <param name="version">Version</param>
|
|
/// <param name="publicKey">Public key</param>
|
|
/// <exception cref="ArgumentNullException">If any of the args is invalid</exception>
|
|
public AssemblyDefUser(UTF8String name, Version version, PublicKey publicKey)
|
|
: this(name, version, publicKey, UTF8String.Empty) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="name">Simple name</param>
|
|
/// <param name="version">Version</param>
|
|
/// <param name="publicKey">Public key</param>
|
|
/// <param name="locale">Locale</param>
|
|
/// <exception cref="ArgumentNullException">If any of the args is invalid</exception>
|
|
public AssemblyDefUser(UTF8String name, Version version, PublicKey publicKey, UTF8String locale) {
|
|
if (name is null)
|
|
throw new ArgumentNullException(nameof(name));
|
|
if (locale is null)
|
|
throw new ArgumentNullException(nameof(locale));
|
|
modules = new LazyList<ModuleDef>(this);
|
|
this.name = name;
|
|
this.version = version ?? throw new ArgumentNullException(nameof(version));
|
|
this.publicKey = publicKey ?? new PublicKey();
|
|
culture = locale;
|
|
attributes = (int)AssemblyAttributes.None;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="asmName">Assembly name info</param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="asmName"/> is <c>null</c></exception>
|
|
public AssemblyDefUser(AssemblyName asmName)
|
|
: this(new AssemblyNameInfo(asmName)) {
|
|
hashAlgorithm = (AssemblyHashAlgorithm)asmName.HashAlgorithm;
|
|
attributes = (int)asmName.Flags;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="asmName">Assembly name info</param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="asmName"/> is <c>null</c></exception>
|
|
public AssemblyDefUser(IAssembly asmName) {
|
|
if (asmName is null)
|
|
throw new ArgumentNullException(nameof(asmName));
|
|
modules = new LazyList<ModuleDef>(this);
|
|
name = asmName.Name;
|
|
version = asmName.Version ?? new Version(0, 0, 0, 0);
|
|
publicKey = asmName.PublicKeyOrToken as PublicKey ?? new PublicKey();
|
|
culture = asmName.Culture;
|
|
attributes = (int)AssemblyAttributes.None;
|
|
hashAlgorithm = AssemblyHashAlgorithm.SHA1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Created from a row in the Assembly table
|
|
/// </summary>
|
|
sealed class AssemblyDefMD : AssemblyDef, IMDTokenProviderMD {
|
|
/// <summary>The module where this instance is located</summary>
|
|
readonly ModuleDefMD readerModule;
|
|
|
|
readonly uint origRid;
|
|
|
|
/// <inheritdoc/>
|
|
public uint OrigRid => origRid;
|
|
|
|
/// <inheritdoc/>
|
|
protected override void InitializeDeclSecurities() {
|
|
var list = readerModule.Metadata.GetDeclSecurityRidList(Table.Assembly, origRid);
|
|
var tmp = new LazyList<DeclSecurity, RidList>(list.Count, list, (list2, index) => readerModule.ResolveDeclSecurity(list2[index]));
|
|
Interlocked.CompareExchange(ref declSecurities, tmp, null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void InitializeModules() {
|
|
var list = readerModule.GetModuleRidList();
|
|
var tmp = new LazyList<ModuleDef, RidList>(list.Count + 1, this, list, (list2, index) => {
|
|
ModuleDef module;
|
|
if (index == 0)
|
|
module = readerModule;
|
|
else
|
|
module = readerModule.ReadModule(list2[index - 1], this);
|
|
if (module is null)
|
|
module = new ModuleDefUser("INVALID", Guid.NewGuid());
|
|
module.Assembly = this;
|
|
return module;
|
|
});
|
|
Interlocked.CompareExchange(ref modules, tmp, null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void InitializeCustomAttributes() {
|
|
var list = readerModule.Metadata.GetCustomAttributeRidList(Table.Assembly, origRid);
|
|
var tmp = new CustomAttributeCollection(list.Count, list, (list2, index) => readerModule.ReadCustomAttribute(list[index]));
|
|
Interlocked.CompareExchange(ref customAttributes, tmp, null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void InitializeCustomDebugInfos() {
|
|
var list = new List<PdbCustomDebugInfo>();
|
|
readerModule.InitializeCustomDebugInfos(new MDToken(MDToken.Table, origRid), new GenericParamContext(), list);
|
|
Interlocked.CompareExchange(ref customDebugInfos, list, null);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override bool TryGetOriginalTargetFrameworkAttribute(out string framework, out Version version, out string profile) {
|
|
if (!hasInitdTFA)
|
|
InitializeTargetFrameworkAttribute();
|
|
framework = tfaFramework;
|
|
version = tfaVersion;
|
|
profile = tfaProfile;
|
|
return tfaReturnValue;
|
|
}
|
|
volatile bool hasInitdTFA;
|
|
string tfaFramework;
|
|
Version tfaVersion;
|
|
string tfaProfile;
|
|
bool tfaReturnValue;
|
|
|
|
void InitializeTargetFrameworkAttribute() {
|
|
if (hasInitdTFA)
|
|
return;
|
|
|
|
var list = readerModule.Metadata.GetCustomAttributeRidList(Table.Assembly, origRid);
|
|
var gpContext = new GenericParamContext();
|
|
for (int i = 0; i < list.Count; i++) {
|
|
var caRid = list[i];
|
|
if (!readerModule.TablesStream.TryReadCustomAttributeRow(caRid, out var caRow))
|
|
continue;
|
|
var caType = readerModule.ResolveCustomAttributeType(caRow.Type, gpContext);
|
|
if (!TryGetName(caType, out var ns, out var name))
|
|
continue;
|
|
if (ns != nameSystemRuntimeVersioning || name != nameTargetFrameworkAttribute)
|
|
continue;
|
|
var ca = CustomAttributeReader.Read(readerModule, caType, caRow.Value, gpContext);
|
|
if (ca is null || ca.ConstructorArguments.Count != 1)
|
|
continue;
|
|
var s = ca.ConstructorArguments[0].Value as UTF8String;
|
|
if (s is null)
|
|
continue;
|
|
if (TryCreateTargetFrameworkInfo(s, out var tmpFramework, out var tmpVersion, out var tmpProfile)) {
|
|
tfaFramework = tmpFramework;
|
|
tfaVersion = tmpVersion;
|
|
tfaProfile = tmpProfile;
|
|
tfaReturnValue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hasInitdTFA = true;
|
|
}
|
|
static readonly UTF8String nameSystemRuntimeVersioning = new UTF8String("System.Runtime.Versioning");
|
|
static readonly UTF8String nameTargetFrameworkAttribute = new UTF8String("TargetFrameworkAttribute");
|
|
|
|
static bool TryGetName(ICustomAttributeType caType, out UTF8String ns, out UTF8String name) {
|
|
ITypeDefOrRef type;
|
|
if (caType is MemberRef mr)
|
|
type = mr.DeclaringType;
|
|
else
|
|
type = (caType as MethodDef)?.DeclaringType;
|
|
if (type is TypeRef tr) {
|
|
ns = tr.Namespace;
|
|
name = tr.Name;
|
|
return true;
|
|
}
|
|
if (type is TypeDef td) {
|
|
ns = td.Namespace;
|
|
name = td.Name;
|
|
return true;
|
|
}
|
|
ns = null;
|
|
name = null;
|
|
return false;
|
|
}
|
|
|
|
static bool TryCreateTargetFrameworkInfo(string attrString, out string framework, out Version version, out string profile) {
|
|
framework = null;
|
|
version = null;
|
|
profile = null;
|
|
|
|
// See corclr/src/mscorlib/src/System/Runtime/Versioning/BinaryCompatibility.cs
|
|
var values = attrString.Split(new char[] { ',' });
|
|
if (values.Length < 2 || values.Length > 3)
|
|
return false;
|
|
var frameworkRes = values[0].Trim();
|
|
if (frameworkRes.Length == 0)
|
|
return false;
|
|
|
|
Version versionRes = null;
|
|
string profileRes = null;
|
|
for (int i = 1; i < values.Length; i++) {
|
|
var kvp = values[i].Split('=');
|
|
if (kvp.Length != 2)
|
|
return false;
|
|
|
|
var key = kvp[0].Trim();
|
|
var value = kvp[1].Trim();
|
|
|
|
if (key.Equals("Version", StringComparison.OrdinalIgnoreCase)) {
|
|
if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
|
|
value = value.Substring(1);
|
|
if (!TryParse(value, out versionRes))
|
|
return false;
|
|
versionRes = new Version(versionRes.Major, versionRes.Minor, versionRes.Build == -1 ? 0 : versionRes.Build, 0);
|
|
}
|
|
else if (key.Equals("Profile", StringComparison.OrdinalIgnoreCase)) {
|
|
if (!string.IsNullOrEmpty(value))
|
|
profileRes = value;
|
|
}
|
|
}
|
|
if (versionRes is null)
|
|
return false;
|
|
|
|
framework = frameworkRes;
|
|
version = versionRes;
|
|
profile = profileRes;
|
|
return true;
|
|
}
|
|
|
|
static int ParseInt32(string s) => int.TryParse(s, out int res) ? res : 0;
|
|
|
|
static bool TryParse(string s, out Version version) {
|
|
Match m;
|
|
|
|
m = Regex.Match(s, @"^(\d+)\.(\d+)$");
|
|
if (m.Groups.Count == 3) {
|
|
version = new Version(ParseInt32(m.Groups[1].Value), ParseInt32(m.Groups[2].Value));
|
|
return true;
|
|
}
|
|
|
|
m = Regex.Match(s, @"^(\d+)\.(\d+)\.(\d+)$");
|
|
if (m.Groups.Count == 4) {
|
|
version = new Version(ParseInt32(m.Groups[1].Value), ParseInt32(m.Groups[2].Value), ParseInt32(m.Groups[3].Value));
|
|
return true;
|
|
}
|
|
|
|
m = Regex.Match(s, @"^(\d+)\.(\d+)\.(\d+)\.(\d+)$");
|
|
if (m.Groups.Count == 5) {
|
|
version = new Version(ParseInt32(m.Groups[1].Value), ParseInt32(m.Groups[2].Value), ParseInt32(m.Groups[3].Value), ParseInt32(m.Groups[4].Value));
|
|
return true;
|
|
}
|
|
|
|
version = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="readerModule">The module which contains this <c>Assembly</c> row</param>
|
|
/// <param name="rid">Row ID</param>
|
|
/// <exception cref="ArgumentNullException">If <paramref name="readerModule"/> is <c>null</c></exception>
|
|
/// <exception cref="ArgumentException">If <paramref name="rid"/> is invalid</exception>
|
|
public AssemblyDefMD(ModuleDefMD readerModule, uint rid) {
|
|
#if DEBUG
|
|
if (readerModule is null)
|
|
throw new ArgumentNullException("readerModule");
|
|
if (readerModule.TablesStream.AssemblyTable.IsInvalidRID(rid))
|
|
throw new BadImageFormatException($"Assembly rid {rid} does not exist");
|
|
#endif
|
|
origRid = rid;
|
|
this.rid = rid;
|
|
this.readerModule = readerModule;
|
|
if (rid != 1)
|
|
modules = new LazyList<ModuleDef>(this);
|
|
bool b = readerModule.TablesStream.TryReadAssemblyRow(origRid, out var row);
|
|
Debug.Assert(b);
|
|
hashAlgorithm = (AssemblyHashAlgorithm)row.HashAlgId;
|
|
version = new Version(row.MajorVersion, row.MinorVersion, row.BuildNumber, row.RevisionNumber);
|
|
attributes = (int)row.Flags;
|
|
name = readerModule.StringsStream.ReadNoNull(row.Name);
|
|
culture = readerModule.StringsStream.ReadNoNull(row.Locale);
|
|
publicKey = new PublicKey(readerModule.BlobStream.Read(row.PublicKey));
|
|
}
|
|
}
|
|
}
|