// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using dnlib.IO;
using dnlib.PE;
namespace dnlib.DotNet.MD {
///
/// Low level access to a .NET file's metadata
///
public static class MetadataFactory {
enum MetadataType {
Unknown,
Compressed, // #~ (normal)
ENC, // #- (edit and continue)
}
internal static MetadataBase Load(string fileName, CLRRuntimeReaderKind runtime) {
IPEImage peImage = null;
try {
return Load(peImage = new PEImage(fileName), runtime);
}
catch {
if (peImage is not null)
peImage.Dispose();
throw;
}
}
internal static MetadataBase Load(byte[] data, CLRRuntimeReaderKind runtime) {
IPEImage peImage = null;
try {
return Load(peImage = new PEImage(data), runtime);
}
catch {
if (peImage is not null)
peImage.Dispose();
throw;
}
}
internal static MetadataBase Load(IntPtr addr, CLRRuntimeReaderKind runtime) {
IPEImage peImage = null;
// We don't know what layout it is. Memory is more common so try that first.
try {
return Load(peImage = new PEImage(addr, ImageLayout.Memory, true), runtime);
}
catch {
if (peImage is not null)
peImage.Dispose();
peImage = null;
}
try {
return Load(peImage = new PEImage(addr, ImageLayout.File, true), runtime);
}
catch {
if (peImage is not null)
peImage.Dispose();
throw;
}
}
internal static MetadataBase Load(IntPtr addr, ImageLayout imageLayout, CLRRuntimeReaderKind runtime) {
IPEImage peImage = null;
try {
return Load(peImage = new PEImage(addr, imageLayout, true), runtime);
}
catch {
if (peImage is not null)
peImage.Dispose();
throw;
}
}
internal static MetadataBase Load(IPEImage peImage, CLRRuntimeReaderKind runtime) => Create(peImage, runtime, true);
///
/// Create a instance
///
/// The PE image
/// A new instance
public static Metadata CreateMetadata(IPEImage peImage) => CreateMetadata(peImage, CLRRuntimeReaderKind.CLR);
///
/// Create a instance
///
/// The PE image
/// Runtime reader kind
/// A new instance
public static Metadata CreateMetadata(IPEImage peImage, CLRRuntimeReaderKind runtime) => Create(peImage, runtime, true);
///
/// Create a instance
///
/// The PE image
/// true if we should verify that it's a .NET PE file
/// A new instance
public static Metadata CreateMetadata(IPEImage peImage, bool verify) => CreateMetadata(peImage, CLRRuntimeReaderKind.CLR, verify);
///
/// Create a instance
///
/// The PE image
/// Runtime reader kind
/// true if we should verify that it's a .NET PE file
/// A new instance
public static Metadata CreateMetadata(IPEImage peImage, CLRRuntimeReaderKind runtime, bool verify) => Create(peImage, runtime, verify);
///
/// Create a instance
///
/// The PE image
/// Runtime reader kind
/// true if we should verify that it's a .NET PE file
/// A new instance
static MetadataBase Create(IPEImage peImage, CLRRuntimeReaderKind runtime, bool verify) {
MetadataBase md = null;
try {
var dotNetDir = peImage.ImageNTHeaders.OptionalHeader.DataDirectories[14];
// Mono doesn't check that the Size field is >= 0x48
if (dotNetDir.VirtualAddress == 0)
throw new BadImageFormatException(".NET data directory RVA is 0");
var cor20HeaderReader = peImage.CreateReader(dotNetDir.VirtualAddress, 0x48);
var cor20Header = new ImageCor20Header(ref cor20HeaderReader, verify && runtime == CLRRuntimeReaderKind.CLR);
if (cor20Header.Metadata.VirtualAddress == 0)
throw new BadImageFormatException(".NET metadata RVA is 0");
var mdRva = cor20Header.Metadata.VirtualAddress;
// Don't use the size field, Mono ignores it. Create a reader that can read to EOF.
var mdHeaderReader = peImage.CreateReader(mdRva);
var mdHeader = new MetadataHeader(ref mdHeaderReader, runtime, verify);
if (verify) {
foreach (var sh in mdHeader.StreamHeaders) {
if ((ulong)sh.Offset + sh.StreamSize > mdHeaderReader.EndOffset)
throw new BadImageFormatException("Invalid stream header");
}
}
md = GetMetadataType(mdHeader.StreamHeaders, runtime) switch {
MetadataType.Compressed => new CompressedMetadata(peImage, cor20Header, mdHeader, runtime),
MetadataType.ENC => new ENCMetadata(peImage, cor20Header, mdHeader, runtime),
_ => throw new BadImageFormatException("No #~ or #- stream found"),
};
md.Initialize(null);
return md;
}
catch {
if (md is not null)
md.Dispose();
throw;
}
}
///
/// Create a standalone portable PDB instance
///
/// Metadata stream
/// true if we should verify that it's a .NET PE file
/// A new instance
internal static MetadataBase CreateStandalonePortablePDB(DataReaderFactory mdReaderFactory, bool verify) {
const CLRRuntimeReaderKind runtime = CLRRuntimeReaderKind.CLR;
MetadataBase md = null;
try {
var reader = mdReaderFactory.CreateReader();
var mdHeader = new MetadataHeader(ref reader, runtime, verify);
if (verify) {
foreach (var sh in mdHeader.StreamHeaders) {
if (sh.Offset + sh.StreamSize < sh.Offset || sh.Offset + sh.StreamSize > reader.Length)
throw new BadImageFormatException("Invalid stream header");
}
}
md = GetMetadataType(mdHeader.StreamHeaders, runtime) switch {
MetadataType.Compressed => new CompressedMetadata(mdHeader, true, runtime),
MetadataType.ENC => new ENCMetadata(mdHeader, true, runtime),
_ => throw new BadImageFormatException("No #~ or #- stream found"),
};
md.Initialize(mdReaderFactory);
return md;
}
catch {
md?.Dispose();
throw;
}
}
static MetadataType GetMetadataType(IList streamHeaders, CLRRuntimeReaderKind runtime) {
MetadataType? mdType = null;
if (runtime == CLRRuntimeReaderKind.CLR) {
foreach (var sh in streamHeaders) {
if (mdType is null) {
if (sh.Name == "#~")
mdType = MetadataType.Compressed;
else if (sh.Name == "#-")
mdType = MetadataType.ENC;
}
if (sh.Name == "#Schema")
mdType = MetadataType.ENC;
}
}
else if (runtime == CLRRuntimeReaderKind.Mono) {
foreach (var sh in streamHeaders) {
if (sh.Name == "#~")
mdType = MetadataType.Compressed;
else if (sh.Name == "#-") {
mdType = MetadataType.ENC;
break;
}
}
}
else
throw new ArgumentOutOfRangeException(nameof(runtime));
if (mdType is null)
return MetadataType.Unknown;
return mdType.Value;
}
}
}