// 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; } }