// dnlib: See LICENSE.txt for more info
using dnlib.IO;
using dnlib.PE;
using dnlib.DotNet.MD;
using System;
using System.Collections.Generic;
using dnlib.Protection;
using dnlib.Utils;
namespace dnlib.DotNet.Writer {
///
/// options
///
public sealed class TablesHeapOptions {
///
/// Should be 0
///
public uint? Reserved1;
///
/// Major version number. Default is 2. Valid versions are v1.0 (no generics),
/// v1.1 (generics are supported), or v2.0 (recommended).
///
public byte? MajorVersion;
///
/// Minor version number. Default is 0.
///
public byte? MinorVersion;
///
/// Force #- or #~ stream. Default value is null and recommended because the correct
/// tables stream will be used. true will force #- (Edit N' Continue)
/// stream, and false will force #~ (normal compressed) stream.
///
public bool? UseENC;
///
/// All columns that can be 2 or 4 bytes are forced to be 4 bytes.
/// Set this to true if you add a #JTD heap and (if CLR) a #- tables heap is used
/// or (if Mono/Unity) a #~ or #- tables heap is used.
/// dnlib won't try to auto detect this from your added heaps since the CLR/CoreCLR vs Mono/Unity behavior
/// is a little bit different. You may need to set to true if you target CLR/CoreCLR.
///
public bool? ForceBigColumns;
///
/// Extra data to write
///
public uint? ExtraData;
///
/// Log2Rid to write
///
public byte? Log2Rid;
///
/// true if there are deleted s, s,
/// s, s, s and/or
/// s.
///
public bool? HasDeletedRows;
///
/// Creates portable PDB v1.0 options
///
///
public static TablesHeapOptions CreatePortablePdbV1_0() =>
new TablesHeapOptions {
Reserved1 = 0,
MajorVersion = 2,
MinorVersion = 0,
UseENC = null,
ExtraData = null,
Log2Rid = null,
HasDeletedRows = null,
};
}
///
/// Contains all .NET tables
///
public sealed class TablesHeap : IHeap {
uint length;
byte majorVersion;
byte minorVersion;
bool bigStrings;
bool bigGuid;
bool bigBlob;
bool hasDeletedRows;
readonly Metadata metadata;
readonly TablesHeapOptions options;
FileOffset offset;
RVA rva;
///
public FileOffset FileOffset => offset;
///
public RVA RVA => rva;
#pragma warning disable 1591 // XML doc comment
public readonly MDTable ModuleTable = new MDTable(Table.Module, RawRowEqualityComparer.Instance);
public readonly MDTable TypeRefTable = new MDTable(Table.TypeRef, RawRowEqualityComparer.Instance);
public readonly MDTable TypeDefTable = new MDTable(Table.TypeDef, RawRowEqualityComparer.Instance);
public readonly MDTable FieldPtrTable = new MDTable(Table.FieldPtr, RawRowEqualityComparer.Instance);
public readonly MDTable FieldTable = new MDTable(Table.Field, RawRowEqualityComparer.Instance);
public readonly MDTable MethodPtrTable = new MDTable(Table.MethodPtr, RawRowEqualityComparer.Instance);
public readonly MDTable MethodTable = new MDTable(Table.Method, RawRowEqualityComparer.Instance);
public readonly MDTable ParamPtrTable = new MDTable(Table.ParamPtr, RawRowEqualityComparer.Instance);
public readonly MDTable ParamTable = new MDTable(Table.Param, RawRowEqualityComparer.Instance);
public readonly MDTable InterfaceImplTable = new MDTable(Table.InterfaceImpl, RawRowEqualityComparer.Instance);
public readonly MDTable MemberRefTable = new MDTable(Table.MemberRef, RawRowEqualityComparer.Instance);
public readonly MDTable ConstantTable = new MDTable(Table.Constant, RawRowEqualityComparer.Instance);
public readonly MDTable CustomAttributeTable = new MDTable(Table.CustomAttribute, RawRowEqualityComparer.Instance);
public readonly MDTable FieldMarshalTable = new MDTable(Table.FieldMarshal, RawRowEqualityComparer.Instance);
public readonly MDTable DeclSecurityTable = new MDTable(Table.DeclSecurity, RawRowEqualityComparer.Instance);
public readonly MDTable ClassLayoutTable = new MDTable(Table.ClassLayout, RawRowEqualityComparer.Instance);
public readonly MDTable FieldLayoutTable = new MDTable(Table.FieldLayout, RawRowEqualityComparer.Instance);
public readonly MDTable StandAloneSigTable = new MDTable(Table.StandAloneSig, RawRowEqualityComparer.Instance);
public readonly MDTable EventMapTable = new MDTable(Table.EventMap, RawRowEqualityComparer.Instance);
public readonly MDTable EventPtrTable = new MDTable(Table.EventPtr, RawRowEqualityComparer.Instance);
public readonly MDTable EventTable = new MDTable(Table.Event, RawRowEqualityComparer.Instance);
public readonly MDTable PropertyMapTable = new MDTable(Table.PropertyMap, RawRowEqualityComparer.Instance);
public readonly MDTable PropertyPtrTable = new MDTable(Table.PropertyPtr, RawRowEqualityComparer.Instance);
public readonly MDTable PropertyTable = new MDTable(Table.Property, RawRowEqualityComparer.Instance);
public readonly MDTable MethodSemanticsTable = new MDTable(Table.MethodSemantics, RawRowEqualityComparer.Instance);
public readonly MDTable MethodImplTable = new MDTable(Table.MethodImpl, RawRowEqualityComparer.Instance);
public readonly MDTable ModuleRefTable = new MDTable(Table.ModuleRef, RawRowEqualityComparer.Instance);
public readonly MDTable TypeSpecTable = new MDTable(Table.TypeSpec, RawRowEqualityComparer.Instance);
public readonly MDTable ImplMapTable = new MDTable(Table.ImplMap, RawRowEqualityComparer.Instance);
public readonly MDTable FieldRVATable = new MDTable(Table.FieldRVA, RawRowEqualityComparer.Instance);
public readonly MDTable ENCLogTable = new MDTable(Table.ENCLog, RawRowEqualityComparer.Instance);
public readonly MDTable ENCMapTable = new MDTable(Table.ENCMap, RawRowEqualityComparer.Instance);
public readonly MDTable AssemblyTable = new MDTable(Table.Assembly, RawRowEqualityComparer.Instance);
public readonly MDTable AssemblyProcessorTable = new MDTable(Table.AssemblyProcessor, RawRowEqualityComparer.Instance);
public readonly MDTable AssemblyOSTable = new MDTable(Table.AssemblyOS, RawRowEqualityComparer.Instance);
public readonly MDTable AssemblyRefTable = new MDTable(Table.AssemblyRef, RawRowEqualityComparer.Instance);
public readonly MDTable AssemblyRefProcessorTable = new MDTable(Table.AssemblyRefProcessor, RawRowEqualityComparer.Instance);
public readonly MDTable AssemblyRefOSTable = new MDTable(Table.AssemblyRefOS, RawRowEqualityComparer.Instance);
public readonly MDTable FileTable = new MDTable(Table.File, RawRowEqualityComparer.Instance);
public readonly MDTable ExportedTypeTable = new MDTable(Table.ExportedType, RawRowEqualityComparer.Instance);
public readonly MDTable ManifestResourceTable = new MDTable(Table.ManifestResource, RawRowEqualityComparer.Instance);
public readonly MDTable NestedClassTable = new MDTable(Table.NestedClass, RawRowEqualityComparer.Instance);
public readonly MDTable GenericParamTable = new MDTable(Table.GenericParam, RawRowEqualityComparer.Instance);
public readonly MDTable MethodSpecTable = new MDTable(Table.MethodSpec, RawRowEqualityComparer.Instance);
public readonly MDTable GenericParamConstraintTable = new MDTable(Table.GenericParamConstraint, RawRowEqualityComparer.Instance);
public readonly MDTable DocumentTable = new MDTable(Table.Document, RawRowEqualityComparer.Instance);
public readonly MDTable MethodDebugInformationTable = new MDTable(Table.MethodDebugInformation, RawRowEqualityComparer.Instance);
public readonly MDTable LocalScopeTable = new MDTable(Table.LocalScope, RawRowEqualityComparer.Instance);
public readonly MDTable LocalVariableTable = new MDTable(Table.LocalVariable, RawRowEqualityComparer.Instance);
public readonly MDTable LocalConstantTable = new MDTable(Table.LocalConstant, RawRowEqualityComparer.Instance);
public readonly MDTable ImportScopeTable = new MDTable(Table.ImportScope, RawRowEqualityComparer.Instance);
public readonly MDTable StateMachineMethodTable = new MDTable(Table.StateMachineMethod, RawRowEqualityComparer.Instance);
public readonly MDTable CustomDebugInformationTable = new MDTable(Table.CustomDebugInformation, RawRowEqualityComparer.Instance);
#pragma warning restore
///
/// All tables
///
public readonly IMDTable[] Tables;
///
public string Name => IsENC ? "#-" : "#~";
///
public bool IsEmpty => false;
///
/// true if the Edit 'N Continue name will be used (#-)
///
public bool IsENC {
get {
if (options.UseENC.HasValue)
return options.UseENC.Value;
return hasDeletedRows ||
!FieldPtrTable.IsEmpty ||
!MethodPtrTable.IsEmpty ||
!ParamPtrTable.IsEmpty ||
!EventPtrTable.IsEmpty ||
!PropertyPtrTable.IsEmpty ||
!(InterfaceImplTable.IsEmpty || InterfaceImplTable.IsSorted) ||
!(ConstantTable.IsEmpty || ConstantTable.IsSorted) ||
!(CustomAttributeTable.IsEmpty || CustomAttributeTable.IsSorted) ||
!(FieldMarshalTable.IsEmpty || FieldMarshalTable.IsSorted) ||
!(DeclSecurityTable.IsEmpty || DeclSecurityTable.IsSorted) ||
!(ClassLayoutTable.IsEmpty || ClassLayoutTable.IsSorted) ||
!(FieldLayoutTable.IsEmpty || FieldLayoutTable.IsSorted) ||
!(EventMapTable.IsEmpty || EventMapTable.IsSorted) ||
!(PropertyMapTable.IsEmpty || PropertyMapTable.IsSorted) ||
!(MethodSemanticsTable.IsEmpty || MethodSemanticsTable.IsSorted) ||
!(MethodImplTable.IsEmpty || MethodImplTable.IsSorted) ||
!(ImplMapTable.IsEmpty || ImplMapTable.IsSorted) ||
!(FieldRVATable.IsEmpty || FieldRVATable.IsSorted) ||
!(NestedClassTable.IsEmpty || NestedClassTable.IsSorted) ||
!(GenericParamTable.IsEmpty || GenericParamTable.IsSorted) ||
!(GenericParamConstraintTable.IsEmpty || GenericParamConstraintTable.IsSorted);
}
}
///
/// true if any rows have been deleted (eg. a deleted TypeDef, Method, Field, etc.
/// Its name has been renamed to _Deleted).
///
public bool HasDeletedRows {
get => hasDeletedRows;
set => hasDeletedRows = value;
}
///
/// true if #Strings heap size > 0xFFFF
///
public bool BigStrings {
get => bigStrings;
set => bigStrings = value;
}
///
/// true if #GUID heap size > 0xFFFF
///
public bool BigGuid {
get => bigGuid;
set => bigGuid = value;
}
///
/// true if #Blob heap size > 0xFFFF
///
public bool BigBlob {
get => bigBlob;
set => bigBlob = value;
}
///
/// Constructor
///
/// Metadata owner
/// Options
public TablesHeap(Metadata metadata, TablesHeapOptions options) {
this.metadata = metadata;
this.options = options ?? new TablesHeapOptions();
hasDeletedRows = this.options.HasDeletedRows ?? false;
Tables = new IMDTable[] {
ModuleTable,
TypeRefTable,
TypeDefTable,
FieldPtrTable,
FieldTable,
MethodPtrTable,
MethodTable,
ParamPtrTable,
ParamTable,
InterfaceImplTable,
MemberRefTable,
ConstantTable,
CustomAttributeTable,
FieldMarshalTable,
DeclSecurityTable,
ClassLayoutTable,
FieldLayoutTable,
StandAloneSigTable,
EventMapTable,
EventPtrTable,
EventTable,
PropertyMapTable,
PropertyPtrTable,
PropertyTable,
MethodSemanticsTable,
MethodImplTable,
ModuleRefTable,
TypeSpecTable,
ImplMapTable,
FieldRVATable,
ENCLogTable,
ENCMapTable,
AssemblyTable,
AssemblyProcessorTable,
AssemblyOSTable,
AssemblyRefTable,
AssemblyRefProcessorTable,
AssemblyRefOSTable,
FileTable,
ExportedTypeTable,
ManifestResourceTable,
NestedClassTable,
GenericParamTable,
MethodSpecTable,
GenericParamConstraintTable,
new MDTable((Table)0x2D, RawDummyRow.Comparer),
new MDTable((Table)0x2E, RawDummyRow.Comparer),
new MDTable((Table)0x2F, RawDummyRow.Comparer),
DocumentTable,
MethodDebugInformationTable,
LocalScopeTable,
LocalVariableTable,
LocalConstantTable,
ImportScopeTable,
StateMachineMethodTable,
CustomDebugInformationTable,
};
}
struct RawDummyRow {
public static readonly IEqualityComparer Comparer = new RawDummyRowEqualityComparer();
sealed class RawDummyRowEqualityComparer : IEqualityComparer {
public bool Equals(RawDummyRow x, RawDummyRow y) => throw new NotSupportedException();
public int GetHashCode(RawDummyRow obj) => throw new NotSupportedException();
}
}
///
public void SetReadOnly() {
foreach (var mdt in Tables)
mdt.SetReadOnly();
}
///
public void SetOffset(FileOffset offset, RVA rva) {
this.offset = offset;
this.rva = rva;
// NOTE: This method can be called twice by NativeModuleWriter, see Metadata.SetOffset() for more info
}
///
public uint GetFileLength() {
if (length == 0)
CalculateLength();
return Utils.AlignUp(length, HeapBase.ALIGNMENT);
}
///
public uint GetVirtualSize() => GetFileLength();
///
public uint CalculateAlignment() => 0;
///
/// Calculates the length. This will set all MD tables to read-only.
///
public void CalculateLength() {
if (length != 0)
return;
SetReadOnly();
majorVersion = options.MajorVersion ?? 2;
minorVersion = options.MinorVersion ?? 0;
if (((majorVersion << 8) | minorVersion) <= 0x100) {
if (!GenericParamTable.IsEmpty || !MethodSpecTable.IsEmpty || !GenericParamConstraintTable.IsEmpty)
throw new ModuleWriterException("Tables heap version <= v1.0 but generic tables are not empty");
}
var dnTableSizes = new DotNetTableSizes();
var tableInfos = dnTableSizes.CreateTables(majorVersion, minorVersion);
var rowCounts = GetRowCounts();
var debugSizes = rowCounts;
if (systemTables is not null) {
debugSizes = new uint[rowCounts.Length];
for (int i = 0; i < rowCounts.Length; i++) {
if (DotNetTableSizes.IsSystemTable((Table)i))
debugSizes[i] = systemTables[i];
else
debugSizes[i] = rowCounts[i];
}
}
dnTableSizes.InitializeSizes(bigStrings, bigGuid, bigBlob, rowCounts, debugSizes, options.ForceBigColumns ?? false);
for (int i = 0; i < Tables.Length; i++)
Tables[i].TableInfo = tableInfos[i];
length = 24;
foreach (var mdt in Tables) {
if (mdt.IsEmpty)
continue;
length += (uint)(4 + mdt.TableInfo.RowSize * mdt.Rows);
}
if (options.ExtraData.HasValue)
length += 4;
}
uint[] GetRowCounts() {
var sizes = new uint[Tables.Length];
for (int i = 0; i < sizes.Length; i++)
sizes[i] = (uint)Tables[i].Rows;
return sizes;
}
internal void GetSystemTableRows(out ulong mask, uint[] tables) {
if (tables.Length != 0x40)
throw new InvalidOperationException();
var tablesMask = GetValidMask();
ulong bit = 1;
mask = 0;
for (int i = 0; i < 0x40; i++, bit <<= 1) {
var table = (Table)i;
if (DotNetTableSizes.IsSystemTable(table)) {
if ((tablesMask & bit) != 0) {
tables[i] = (uint)Tables[i].Rows;
mask |= bit;
}
else
tables[i] = 0;
}
else
tables[i] = 0;
}
}
internal void SetSystemTableRows(uint[] systemTables) => this.systemTables = (uint[])systemTables.Clone();
uint[] systemTables;
private void WriteTo0(DataWriter writer) {
writer.WriteUInt32(options.Reserved1 ?? 0);
writer.WriteByte(majorVersion);
writer.WriteByte(minorVersion);
writer.WriteByte((byte)GetMDStreamFlags());
writer.WriteByte(GetLog2Rid());
writer.WriteUInt64(GetValidMask());
writer.WriteUInt64(GetSortedMask());
foreach (var mdt in Tables) {
if (!mdt.IsEmpty)
writer.WriteInt32(mdt.Rows);
}
if (options.ExtraData.HasValue)
writer.WriteUInt32(options.ExtraData.Value);
writer.Write(metadata, ModuleTable);
writer.Write(metadata, TypeRefTable);
writer.Write(metadata, TypeDefTable);
writer.Write(metadata, FieldPtrTable);
writer.Write(metadata, FieldTable);
writer.Write(metadata, MethodPtrTable);
writer.Write(metadata, MethodTable);
writer.Write(metadata, ParamPtrTable);
writer.Write(metadata, ParamTable);
writer.Write(metadata, InterfaceImplTable);
writer.Write(metadata, MemberRefTable);
writer.Write(metadata, ConstantTable);
writer.Write(metadata, CustomAttributeTable);
writer.Write(metadata, FieldMarshalTable);
writer.Write(metadata, DeclSecurityTable);
writer.Write(metadata, ClassLayoutTable);
writer.Write(metadata, FieldLayoutTable);
writer.Write(metadata, StandAloneSigTable);
writer.Write(metadata, EventMapTable);
writer.Write(metadata, EventPtrTable);
writer.Write(metadata, EventTable);
writer.Write(metadata, PropertyMapTable);
writer.Write(metadata, PropertyPtrTable);
writer.Write(metadata, PropertyTable);
writer.Write(metadata, MethodSemanticsTable);
writer.Write(metadata, MethodImplTable);
writer.Write(metadata, ModuleRefTable);
writer.Write(metadata, TypeSpecTable);
writer.Write(metadata, ImplMapTable);
writer.Write(metadata, FieldRVATable);
writer.Write(metadata, ENCLogTable);
writer.Write(metadata, ENCMapTable);
writer.Write(metadata, AssemblyTable);
writer.Write(metadata, AssemblyProcessorTable);
writer.Write(metadata, AssemblyOSTable);
writer.Write(metadata, AssemblyRefTable);
writer.Write(metadata, AssemblyRefProcessorTable);
writer.Write(metadata, AssemblyRefOSTable);
writer.Write(metadata, FileTable);
writer.Write(metadata, ExportedTypeTable);
writer.Write(metadata, ManifestResourceTable);
writer.Write(metadata, NestedClassTable);
writer.Write(metadata, GenericParamTable);
writer.Write(metadata, MethodSpecTable);
writer.Write(metadata, GenericParamConstraintTable);
writer.Write(metadata, DocumentTable);
writer.Write(metadata, MethodDebugInformationTable);
writer.Write(metadata, LocalScopeTable);
writer.Write(metadata, LocalVariableTable);
writer.Write(metadata, LocalConstantTable);
writer.Write(metadata, ImportScopeTable);
writer.Write(metadata, StateMachineMethodTable);
writer.Write(metadata, CustomDebugInformationTable);
writer.WriteZeroes((int)(Utils.AlignUp(length, HeapBase.ALIGNMENT) - length));
}
///
public void WriteTo(DataWriter writer) {
EncryptionUtil.WriteWithEncIfNeed(writer, WriteTo0, e => e.TableEnc, EncryptionContext.BigSegmentSize);
}
MDStreamFlags GetMDStreamFlags() {
MDStreamFlags flags = 0;
if (bigStrings)
flags |= MDStreamFlags.BigStrings;
if (bigGuid)
flags |= MDStreamFlags.BigGUID;
if (bigBlob)
flags |= MDStreamFlags.BigBlob;
if (options.ExtraData.HasValue)
flags |= MDStreamFlags.ExtraData;
if (hasDeletedRows)
flags |= MDStreamFlags.HasDelete;
return flags;
}
byte GetLog2Rid() {
//TODO: Sometimes this is 16. Probably when at least one of the table indexes requires 4 bytes.
return options.Log2Rid ?? 1;
}
ulong GetValidMask() {
ulong mask = 0;
foreach (var mdt in Tables) {
if (!mdt.IsEmpty)
mask |= 1UL << (int)mdt.Table;
}
return mask;
}
ulong GetSortedMask() {
ulong mask = 0;
foreach (var mdt in Tables) {
if (mdt.IsSorted)
mask |= 1UL << (int)mdt.Table;
}
return mask;
}
///
public override string ToString() => Name;
}
}