// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using dnlib.DotNet.MD; namespace dnlib.DotNet.Writer { /// /// Preserves metadata tokens /// sealed class PreserveTokensMetadata : Metadata { readonly ModuleDefMD mod; readonly Rows typeRefInfos = new Rows(); readonly Dictionary typeToRid = new Dictionary(); MemberDefDict fieldDefInfos; MemberDefDict methodDefInfos; MemberDefDict paramDefInfos; readonly Rows memberRefInfos = new Rows(); readonly Rows standAloneSigInfos = new Rows(); MemberDefDict eventDefInfos; MemberDefDict propertyDefInfos; readonly Rows typeSpecInfos = new Rows(); readonly Rows methodSpecInfos = new Rows(); readonly Dictionary callConvTokenToSignature = new Dictionary(); [DebuggerDisplay("{Rid} -> {NewRid} {Def}")] sealed class MemberDefInfo where T : IMDTokenProvider { public readonly T Def; /// /// Its real rid /// public uint Rid; /// /// Its logical rid or real rid. If the ptr table exists (eg. MethodPtr), then it's /// an index into it, else it's the real rid. /// public uint NewRid; public MemberDefInfo(T def, uint rid) { Def = def; Rid = rid; NewRid = rid; } } [DebuggerDisplay("Count = {Count}")] sealed class MemberDefDict where T : IMDTokenProvider { readonly Type defMDType; uint userRid = 0x01000000; uint newRid = 1; int numDefMDs; int numDefUsers; int tableSize; bool wasSorted; readonly bool preserveRids; readonly bool enableRidToInfo; readonly Dictionary> defToInfo = new Dictionary>(); Dictionary> ridToInfo; readonly List> defs = new List>(); List> sortedDefs; readonly Dictionary collectionPositions = new Dictionary(); /// /// Gets total number of defs in the list. It does not necessarily return /// the table size. Use for that. /// public int Count => defs.Count; /// /// Gets the number of rows that need to be created in the table /// public int TableSize => tableSize; /// /// Returns true if the ptr table (eg. MethodPtr) is needed /// public bool NeedPtrTable => preserveRids && !wasSorted; public MemberDefDict(Type defMDType, bool preserveRids) : this(defMDType, preserveRids, false) { } public MemberDefDict(Type defMDType, bool preserveRids, bool enableRidToInfo) { this.defMDType = defMDType; this.preserveRids = preserveRids; this.enableRidToInfo = enableRidToInfo; } public uint Rid(T def) => defToInfo[def].Rid; public bool TryGetRid(T def, out uint rid) { if (def == null || !defToInfo.TryGetValue(def, out var info)) { rid = 0; return false; } rid = info.Rid; return true; } /// /// Sorts the table /// /// Comparer public void Sort(Comparison> comparer) { if (!preserveRids) { // It's already sorted sortedDefs = defs; return; } sortedDefs = new List>(defs); sortedDefs.Sort(comparer); wasSorted = true; for (int i = 0; i < sortedDefs.Count; i++) { var def = sortedDefs[i]; uint newRid = (uint)i + 1; def.NewRid = newRid; if (def.Rid != newRid) wasSorted = false; } } public MemberDefInfo Get(int i) => defs[i]; public MemberDefInfo GetSorted(int i) => sortedDefs[i]; public MemberDefInfo GetByRid(uint rid) { ridToInfo.TryGetValue(rid, out var info); return info; } /// /// Adds a def. must be called after adding the last def. /// /// The def /// Collection position public void Add(T def, int collPos) { uint rid; if (def.GetType() == defMDType) { numDefMDs++; rid = preserveRids ? def.Rid : newRid++; } else { numDefUsers++; rid = preserveRids ? userRid++ : newRid++; } var info = new MemberDefInfo(def, rid); defToInfo[def] = info; defs.Add(info); collectionPositions.Add(def, collPos); } /// /// Must be called after 'ing the last def /// public void SortDefs() { // It's already sorted if we don't preserve rids if (preserveRids) { // Sort all def MDs before user defs defs.Sort((a, b) => a.Rid.CompareTo(b.Rid)); // Fix user created defs' rids uint newRid = numDefMDs == 0 ? 1 : defs[numDefMDs - 1].Rid + 1; for (int i = numDefMDs; i < defs.Count; i++) defs[i].Rid = newRid++; // Now we know total table size tableSize = (int)newRid - 1; } else tableSize = defs.Count; if (enableRidToInfo) { ridToInfo = new Dictionary>(defs.Count); foreach (var info in defs) ridToInfo.Add(info.Rid, info); } if ((uint)tableSize > 0x00FFFFFF) throw new ModuleWriterException("Table is too big"); } public int GetCollectionPosition(T def) => collectionPositions[def]; } protected override int NumberOfMethods => methodDefInfos.Count; public PreserveTokensMetadata(ModuleDef module, UniqueChunkList constants, MethodBodyChunks methodBodies, NetResources netResources, MetadataOptions options, DebugMetadataKind debugKind, bool isStandaloneDebugMetadata) : base(module, constants, methodBodies, netResources, options, debugKind, isStandaloneDebugMetadata) { mod = module as ModuleDefMD; if (mod is null) throw new ModuleWriterException("Not a ModuleDefMD"); } /// public override uint GetRid(TypeRef tr) { typeRefInfos.TryGetRid(tr, out uint rid); return rid; } /// public override uint GetRid(TypeDef td) { if (td is null) { Error("TypeDef is null"); return 0; } if (typeToRid.TryGetValue(td, out uint rid)) return rid; Error("TypeDef '{0}' (0x{1:X8}) is not defined in this module '{2}'. A type was removed that is still referenced by this module.", td, td.MDToken.Raw, module); return 0; } /// public override uint GetRid(FieldDef fd) { if (fieldDefInfos.TryGetRid(fd, out uint rid)) return rid; if (fd is null) Error("Field is null"); else Error("Field '{0}' (0x{1:X8}) is not defined in this module '{2}'. A field was removed that is still referenced by this module.", fd, fd.MDToken.Raw, module); return 0; } /// public override uint GetRid(MethodDef md) { if (methodDefInfos.TryGetRid(md, out uint rid)) return rid; if (md is null) Error("Method is null"); else Error("Method '{0}' (0x{1:X8}) is not defined in this module '{2}'. A method was removed that is still referenced by this module.", md, md.MDToken.Raw, module); return 0; } /// public override uint GetRid(ParamDef pd) { if (paramDefInfos.TryGetRid(pd, out uint rid)) return rid; if (pd is null) Error("Param is null"); else Error("Param '{0}' (0x{1:X8}) is not defined in this module '{2}'. A parameter was removed that is still referenced by this module.", pd, pd.MDToken.Raw, module); return 0; } /// public override uint GetRid(MemberRef mr) { memberRefInfos.TryGetRid(mr, out uint rid); return rid; } /// public override uint GetRid(StandAloneSig sas) { standAloneSigInfos.TryGetRid(sas, out uint rid); return rid; } /// public override uint GetRid(EventDef ed) { if (eventDefInfos.TryGetRid(ed, out uint rid)) return rid; if (ed is null) Error("Event is null"); else Error("Event '{0}' (0x{1:X8}) is not defined in this module '{2}'. An event was removed that is still referenced by this module.", ed, ed.MDToken.Raw, module); return 0; } /// public override uint GetRid(PropertyDef pd) { if (propertyDefInfos.TryGetRid(pd, out uint rid)) return rid; if (pd is null) Error("Property is null"); else Error("Property '{0}' (0x{1:X8}) is not defined in this module '{2}'. A property was removed that is still referenced by this module.", pd, pd.MDToken.Raw, module); return 0; } /// public override uint GetRid(TypeSpec ts) { typeSpecInfos.TryGetRid(ts, out uint rid); return rid; } /// public override uint GetRid(MethodSpec ms) { methodSpecInfos.TryGetRid(ms, out uint rid); return rid; } /// protected override void Initialize() { fieldDefInfos = new MemberDefDict(typeof(FieldDefMD), PreserveFieldRids); methodDefInfos = new MemberDefDict(typeof(MethodDefMD), PreserveMethodRids, true); paramDefInfos = new MemberDefDict(typeof(ParamDefMD), PreserveParamRids); eventDefInfos = new MemberDefDict(typeof(EventDefMD), PreserveEventRids); propertyDefInfos = new MemberDefDict(typeof(PropertyDefMD), PreservePropertyRids); CreateEmptyTableRows(); } /// protected override TypeDef[] GetAllTypeDefs() { if (!PreserveTypeDefRids) { var types2 = module.GetTypes().ToArray(); InitializeTypeToRid(types2); return types2; } var typeToIndex = new Dictionary(); var types = new List(); uint index = 0; const uint IS_TYPEDEFMD = 0x80000000; const uint INDEX_BITS = 0x00FFFFFF; foreach (var type in module.GetTypes()) { if (type is null) continue; types.Add(type); uint val = (uint)index++; if (type.GetType() == typeof(TypeDefMD)) val |= IS_TYPEDEFMD; typeToIndex[type] = val; } var globalType = types[0]; types.Sort((a, b) => { if (a == b) return 0; // Make sure the global type is always sorted first, even if it's // a TypeDefUser if (a == globalType) return -1; if (b == globalType) return 1; // Sort all TypeDefMDs before all TypeDefUsers uint ai = typeToIndex[a]; uint bi = typeToIndex[b]; bool amd = (ai & IS_TYPEDEFMD) != 0; bool bmd = (bi & IS_TYPEDEFMD) != 0; if (amd == bmd) { // Both are TypeDefMDs or both are TypeDefUsers // If TypeDefMDs, only compare rids since rids are preserved if (amd) return a.Rid.CompareTo(b.Rid); // If TypeDefUsers, rids aren't preserved so compare by index return (ai & INDEX_BITS).CompareTo(bi & INDEX_BITS); } if (amd) return -1; return 1; }); // Some of the original types may have been removed. Create dummy types // so TypeDef rids can be preserved. var newTypes = new List(types.Count); uint prevRid = 1; newTypes.Add(globalType); for (int i = 1; i < types.Count; i++) { var type = types[i]; // TypeDefUsers were sorted last so when we reach one, we can stop if (type.GetType() != typeof(TypeDefMD)) { while (i < types.Count) newTypes.Add(types[i++]); break; } uint currRid = type.Rid; int extraTypes = (int)(currRid - prevRid - 1); if (extraTypes != 0) { // always >= 0 since currRid > prevRid // At least one type has been removed. Create dummy types. for (int j = 0; j < extraTypes; j++) newTypes.Add(new TypeDefUser("dummy", Guid.NewGuid().ToString("B"), module.CorLibTypes.Object.TypeDefOrRef)); } newTypes.Add(type); prevRid = currRid; } var newTypesArray = newTypes.ToArray(); InitializeTypeToRid(newTypesArray); return newTypesArray; } void InitializeTypeToRid(TypeDef[] types) { uint rid = 1; foreach (var type in types) { if (type is null) continue; if (typeToRid.ContainsKey(type)) continue; typeToRid[type] = rid++; } } /// protected override void AllocateTypeDefRids() { foreach (var type in allTypeDefs) { uint rid = tablesHeap.TypeDefTable.Create(new RawTypeDefRow()); if (typeToRid[type] != rid) throw new ModuleWriterException("Got a different rid than expected"); } } /// /// Reserves rows in TypeRef, MemberRef, StandAloneSig, /// TypeSpec and MethodSpec where we will store the original rows /// to make sure they get the same rid. Any user created rows will be stored at /// the end of each table. /// void CreateEmptyTableRows() { uint rows; if (PreserveTypeRefRids) { rows = mod.TablesStream.TypeRefTable.Rows; for (uint i = 0; i < rows; i++) tablesHeap.TypeRefTable.Create(new RawTypeRefRow()); } if (PreserveMemberRefRids) { rows = mod.TablesStream.MemberRefTable.Rows; for (uint i = 0; i < rows; i++) tablesHeap.MemberRefTable.Create(new RawMemberRefRow()); } if (PreserveStandAloneSigRids) { rows = mod.TablesStream.StandAloneSigTable.Rows; for (uint i = 0; i < rows; i++) tablesHeap.StandAloneSigTable.Create(new RawStandAloneSigRow()); } if (PreserveTypeSpecRids) { rows = mod.TablesStream.TypeSpecTable.Rows; for (uint i = 0; i < rows; i++) tablesHeap.TypeSpecTable.Create(new RawTypeSpecRow()); } if (PreserveMethodSpecRids) { rows = mod.TablesStream.MethodSpecTable.Rows; for (uint i = 0; i < rows; i++) tablesHeap.MethodSpecTable.Create(new RawMethodSpecRow()); } } /// /// Adds any non-referenced rows that haven't been added yet but are present in /// the original file. If there are any non-referenced rows, it's usually a sign /// that an obfuscator has encrypted one or more methods or that it has added /// some rows it uses to decrypt something. /// void InitializeUninitializedTableRows() { InitializeTypeRefTableRows(); InitializeMemberRefTableRows(); InitializeStandAloneSigTableRows(); InitializeTypeSpecTableRows(); InitializeMethodSpecTableRows(); } bool initdTypeRef = false; void InitializeTypeRefTableRows() { if (!PreserveTypeRefRids || initdTypeRef) return; initdTypeRef = true; uint rows = mod.TablesStream.TypeRefTable.Rows; for (uint rid = 1; rid <= rows; rid++) AddTypeRef(mod.ResolveTypeRef(rid)); tablesHeap.TypeRefTable.ReAddRows(); } bool initdMemberRef = false; void InitializeMemberRefTableRows() { if (!PreserveMemberRefRids || initdMemberRef) return; initdMemberRef = true; uint rows = mod.TablesStream.MemberRefTable.Rows; for (uint rid = 1; rid <= rows; rid++) { if (tablesHeap.MemberRefTable[rid].Class != 0) continue; AddMemberRef(mod.ResolveMemberRef(rid), true); } tablesHeap.MemberRefTable.ReAddRows(); } bool initdStandAloneSig = false; void InitializeStandAloneSigTableRows() { if (!PreserveStandAloneSigRids || initdStandAloneSig) return; initdStandAloneSig = true; uint rows = mod.TablesStream.StandAloneSigTable.Rows; for (uint rid = 1; rid <= rows; rid++) { if (tablesHeap.StandAloneSigTable[rid].Signature != 0) continue; AddStandAloneSig(mod.ResolveStandAloneSig(rid), true); } tablesHeap.StandAloneSigTable.ReAddRows(); } bool initdTypeSpec = false; void InitializeTypeSpecTableRows() { if (!PreserveTypeSpecRids || initdTypeSpec) return; initdTypeSpec = true; uint rows = mod.TablesStream.TypeSpecTable.Rows; for (uint rid = 1; rid <= rows; rid++) { if (tablesHeap.TypeSpecTable[rid].Signature != 0) continue; AddTypeSpec(mod.ResolveTypeSpec(rid), true); } tablesHeap.TypeSpecTable.ReAddRows(); } bool initdMethodSpec = false; void InitializeMethodSpecTableRows() { if (!PreserveMethodSpecRids || initdMethodSpec) return; initdMethodSpec = true; uint rows = mod.TablesStream.MethodSpecTable.Rows; for (uint rid = 1; rid <= rows; rid++) { if (tablesHeap.MethodSpecTable[rid].Method != 0) continue; AddMethodSpec(mod.ResolveMethodSpec(rid), true); } tablesHeap.MethodSpecTable.ReAddRows(); } /// protected override void AllocateMemberDefRids() { FindMemberDefs(); const int numEvents = 5; RaiseProgress(Writer.MetadataEvent.AllocateMemberDefRids, 0.0 / numEvents); for (int i = 1; i <= fieldDefInfos.TableSize; i++) { if ((uint)i != tablesHeap.FieldTable.Create(new RawFieldRow())) throw new ModuleWriterException("Invalid field rid"); } for (int i = 1; i <= methodDefInfos.TableSize; i++) { if ((uint)i != tablesHeap.MethodTable.Create(new RawMethodRow())) throw new ModuleWriterException("Invalid method rid"); } for (int i = 1; i <= paramDefInfos.TableSize; i++) { if ((uint)i != tablesHeap.ParamTable.Create(new RawParamRow())) throw new ModuleWriterException("Invalid param rid"); } for (int i = 1; i <= eventDefInfos.TableSize; i++) { if ((uint)i != tablesHeap.EventTable.Create(new RawEventRow())) throw new ModuleWriterException("Invalid event rid"); } for (int i = 1; i <= propertyDefInfos.TableSize; i++) { if ((uint)i != tablesHeap.PropertyTable.Create(new RawPropertyRow())) throw new ModuleWriterException("Invalid property rid"); } SortFields(); SortMethods(); SortParameters(); SortEvents(); SortProperties(); RaiseProgress(Writer.MetadataEvent.AllocateMemberDefRids, 1.0 / numEvents); if (fieldDefInfos.NeedPtrTable) { for (int i = 0; i < fieldDefInfos.Count; i++) { var info = fieldDefInfos.GetSorted(i); if ((uint)i + 1 != tablesHeap.FieldPtrTable.Add(new RawFieldPtrRow(info.Rid))) throw new ModuleWriterException("Invalid field ptr rid"); } ReUseDeletedFieldRows(); } if (methodDefInfos.NeedPtrTable) { for (int i = 0; i < methodDefInfos.Count; i++) { var info = methodDefInfos.GetSorted(i); if ((uint)i + 1 != tablesHeap.MethodPtrTable.Add(new RawMethodPtrRow(info.Rid))) throw new ModuleWriterException("Invalid method ptr rid"); } ReUseDeletedMethodRows(); } if (paramDefInfos.NeedPtrTable) { // NOTE: peverify does not support the ParamPtr table. It's a bug. for (int i = 0; i < paramDefInfos.Count; i++) { var info = paramDefInfos.GetSorted(i); if ((uint)i + 1 != tablesHeap.ParamPtrTable.Add(new RawParamPtrRow(info.Rid))) throw new ModuleWriterException("Invalid param ptr rid"); } ReUseDeletedParamRows(); } if (eventDefInfos.NeedPtrTable) { for (int i = 0; i < eventDefInfos.Count; i++) { var info = eventDefInfos.GetSorted(i); if ((uint)i + 1 != tablesHeap.EventPtrTable.Add(new RawEventPtrRow(info.Rid))) throw new ModuleWriterException("Invalid event ptr rid"); } } if (propertyDefInfos.NeedPtrTable) { for (int i = 0; i < propertyDefInfos.Count; i++) { var info = propertyDefInfos.GetSorted(i); if ((uint)i + 1 != tablesHeap.PropertyPtrTable.Add(new RawPropertyPtrRow(info.Rid))) throw new ModuleWriterException("Invalid property ptr rid"); } } RaiseProgress(Writer.MetadataEvent.AllocateMemberDefRids, 2.0 / numEvents); InitializeMethodAndFieldList(); InitializeParamList(); InitializeEventMap(); InitializePropertyMap(); RaiseProgress(Writer.MetadataEvent.AllocateMemberDefRids, 3.0 / numEvents); // We must re-use deleted event/property rows after we've initialized // the event/prop map tables. if (eventDefInfos.NeedPtrTable) ReUseDeletedEventRows(); if (propertyDefInfos.NeedPtrTable) ReUseDeletedPropertyRows(); RaiseProgress(Writer.MetadataEvent.AllocateMemberDefRids, 4.0 / numEvents); InitializeTypeRefTableRows(); InitializeTypeSpecTableRows(); InitializeMemberRefTableRows(); InitializeMethodSpecTableRows(); } /// /// Re-uses all Field rows which aren't owned by any type due to the fields /// having been deleted by the user. The reason we must do this is that the /// FieldPtr and Field tables must be the same size. /// void ReUseDeletedFieldRows() { if (tablesHeap.FieldPtrTable.IsEmpty) return; if (fieldDefInfos.TableSize == tablesHeap.FieldPtrTable.Rows) return; var hasOwner = new bool[fieldDefInfos.TableSize]; for (int i = 0; i < fieldDefInfos.Count; i++) hasOwner[(int)fieldDefInfos.Get(i).Rid - 1] = true; CreateDummyPtrTableType(); uint fieldSig = GetSignature(new FieldSig(module.CorLibTypes.Byte)); for (int i = 0; i < hasOwner.Length; i++) { if (hasOwner[i]) continue; uint frid = (uint)i + 1; var frow = new RawFieldRow((ushort)(FieldAttributes.Public | FieldAttributes.Static), stringsHeap.Add($"f{frid:X6}"), fieldSig); tablesHeap.FieldTable[frid] = frow; tablesHeap.FieldPtrTable.Create(new RawFieldPtrRow(frid)); } if (fieldDefInfos.TableSize != tablesHeap.FieldPtrTable.Rows) throw new ModuleWriterException("Didn't create all dummy fields"); } /// /// Re-uses all Method rows which aren't owned by any type due to the methods /// having been deleted by the user. The reason we must do this is that the /// MethodPtr and Method tables must be the same size. /// void ReUseDeletedMethodRows() { if (tablesHeap.MethodPtrTable.IsEmpty) return; if (methodDefInfos.TableSize == tablesHeap.MethodPtrTable.Rows) return; var hasOwner = new bool[methodDefInfos.TableSize]; for (int i = 0; i < methodDefInfos.Count; i++) hasOwner[(int)methodDefInfos.Get(i).Rid - 1] = true; CreateDummyPtrTableType(); uint methodSig = GetSignature(MethodSig.CreateInstance(module.CorLibTypes.Void)); for (int i = 0; i < hasOwner.Length; i++) { if (hasOwner[i]) continue; uint mrid = (uint)i + 1; var mrow = new RawMethodRow(0, (ushort)(MethodImplAttributes.IL | MethodImplAttributes.Managed), (ushort)(MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Abstract), stringsHeap.Add($"m{mrid:X6}"), methodSig, (uint)paramDefInfos.Count); tablesHeap.MethodTable[mrid] = mrow; tablesHeap.MethodPtrTable.Create(new RawMethodPtrRow(mrid)); } if (methodDefInfos.TableSize != tablesHeap.MethodPtrTable.Rows) throw new ModuleWriterException("Didn't create all dummy methods"); } /// /// Re-uses all Param rows which aren't owned by any type due to the params /// having been deleted by the user. The reason we must do this is that the /// ParamPtr and Param tables must be the same size. /// This method must be called after since /// this method will create more methods at the end of the Method table. /// void ReUseDeletedParamRows() { if (tablesHeap.ParamPtrTable.IsEmpty) return; if (paramDefInfos.TableSize == tablesHeap.ParamPtrTable.Rows) return; var hasOwner = new bool[paramDefInfos.TableSize]; for (int i = 0; i < paramDefInfos.Count; i++) hasOwner[(int)paramDefInfos.Get(i).Rid - 1] = true; CreateDummyPtrTableType(); // For each param, attach it to a new method. Another alternative would be to create // one (or a few) methods with tons of parameters. uint methodSig = GetSignature(MethodSig.CreateInstance(module.CorLibTypes.Void)); for (int i = 0; i < hasOwner.Length; i++) { if (hasOwner[i]) continue; uint prid = (uint)i + 1; var prow = new RawParamRow(0, 0, stringsHeap.Add($"p{prid:X6}")); tablesHeap.ParamTable[prid] = prow; uint ptrRid = tablesHeap.ParamPtrTable.Create(new RawParamPtrRow(prid)); var mrow = new RawMethodRow(0, (ushort)(MethodImplAttributes.IL | MethodImplAttributes.Managed), (ushort)(MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Abstract), stringsHeap.Add($"mp{prid:X6}"), methodSig, ptrRid); uint mrid = tablesHeap.MethodTable.Create(mrow); if (tablesHeap.MethodPtrTable.Rows > 0) tablesHeap.MethodPtrTable.Create(new RawMethodPtrRow(mrid)); } if (paramDefInfos.TableSize != tablesHeap.ParamPtrTable.Rows) throw new ModuleWriterException("Didn't create all dummy params"); } /// /// Re-uses all Event rows which aren't owned by any type due to the events /// having been deleted by the user. The reason we must do this is that the /// EventPtr and Event tables must be the same size. /// void ReUseDeletedEventRows() { if (tablesHeap.EventPtrTable.IsEmpty) return; if (eventDefInfos.TableSize == tablesHeap.EventPtrTable.Rows) return; var hasOwner = new bool[eventDefInfos.TableSize]; for (int i = 0; i < eventDefInfos.Count; i++) hasOwner[(int)eventDefInfos.Get(i).Rid - 1] = true; uint typeRid = CreateDummyPtrTableType(); tablesHeap.EventMapTable.Create(new RawEventMapRow(typeRid, (uint)tablesHeap.EventPtrTable.Rows + 1)); uint eventType = AddTypeDefOrRef(module.CorLibTypes.Object.TypeDefOrRef); for (int i = 0; i < hasOwner.Length; i++) { if (hasOwner[i]) continue; uint erid = (uint)i + 1; var frow = new RawEventRow(0, stringsHeap.Add($"E{erid:X6}"), eventType); tablesHeap.EventTable[erid] = frow; tablesHeap.EventPtrTable.Create(new RawEventPtrRow(erid)); } if (eventDefInfos.TableSize != tablesHeap.EventPtrTable.Rows) throw new ModuleWriterException("Didn't create all dummy events"); } /// /// Re-uses all Property rows which aren't owned by any type due to the properties /// having been deleted by the user. The reason we must do this is that the /// PropertyPtr and Property tables must be the same size. /// void ReUseDeletedPropertyRows() { if (tablesHeap.PropertyPtrTable.IsEmpty) return; if (propertyDefInfos.TableSize == tablesHeap.PropertyPtrTable.Rows) return; var hasOwner = new bool[propertyDefInfos.TableSize]; for (int i = 0; i < propertyDefInfos.Count; i++) hasOwner[(int)propertyDefInfos.Get(i).Rid - 1] = true; uint typeRid = CreateDummyPtrTableType(); tablesHeap.PropertyMapTable.Create(new RawPropertyMapRow(typeRid, (uint)tablesHeap.PropertyPtrTable.Rows + 1)); uint propertySig = GetSignature(PropertySig.CreateStatic(module.CorLibTypes.Object)); for (int i = 0; i < hasOwner.Length; i++) { if (hasOwner[i]) continue; uint prid = (uint)i + 1; var frow = new RawPropertyRow(0, stringsHeap.Add($"P{prid:X6}"), propertySig); tablesHeap.PropertyTable[prid] = frow; tablesHeap.PropertyPtrTable.Create(new RawPropertyPtrRow(prid)); } if (propertyDefInfos.TableSize != tablesHeap.PropertyPtrTable.Rows) throw new ModuleWriterException("Didn't create all dummy properties"); } /// /// Creates a dummy TypeDef at the end of the TypeDef table that will own /// dummy methods and fields. These dummy methods and fields are only created if the size /// of the ptr table is less than the size of the non-ptr table (eg. size MethodPtr table /// is less than size Method table). The only reason the ptr table would be smaller than /// the non-ptr table is when some field/method has been deleted and we must preserve /// all method/field rids. /// uint CreateDummyPtrTableType() { if (dummyPtrTableTypeRid != 0) return dummyPtrTableTypeRid; var flags = TypeAttributes.NotPublic | TypeAttributes.AutoLayout | TypeAttributes.Class | TypeAttributes.Abstract | TypeAttributes.AnsiClass; int numFields = fieldDefInfos.NeedPtrTable ? fieldDefInfos.Count : fieldDefInfos.TableSize; int numMethods = methodDefInfos.NeedPtrTable ? methodDefInfos.Count : methodDefInfos.TableSize; var row = new RawTypeDefRow((uint)flags, stringsHeap.Add(Guid.NewGuid().ToString("B")), stringsHeap.Add("dummy_ptr"), AddTypeDefOrRef(module.CorLibTypes.Object.TypeDefOrRef), (uint)numFields + 1, (uint)numMethods + 1); dummyPtrTableTypeRid = tablesHeap.TypeDefTable.Create(row); if (dummyPtrTableTypeRid == 1) throw new ModuleWriterException("Dummy ptr type is the first type"); return dummyPtrTableTypeRid; } uint dummyPtrTableTypeRid; void FindMemberDefs() { int count; var added = new Dictionary(); int pos; foreach (var type in allTypeDefs) { if (type is null) continue; pos = 0; var fields = type.Fields; count = fields.Count; for (int i = 0; i < count; i++) { var field = fields[i]; if (field is null) continue; fieldDefInfos.Add(field, pos++); } pos = 0; var methods = type.Methods; count = methods.Count; for (int i = 0; i < count; i++) { var method = methods[i]; if (method is null) continue; methodDefInfos.Add(method, pos++); } pos = 0; var events = type.Events; count = events.Count; for (int i = 0; i < count; i++) { var evt = events[i]; if (evt is null || added.ContainsKey(evt)) continue; added[evt] = true; eventDefInfos.Add(evt, pos++); } pos = 0; var properties = type.Properties; count = properties.Count; for (int i = 0; i < count; i++) { var prop = properties[i]; if (prop is null || added.ContainsKey(prop)) continue; added[prop] = true; propertyDefInfos.Add(prop, pos++); } } fieldDefInfos.SortDefs(); methodDefInfos.SortDefs(); eventDefInfos.SortDefs(); propertyDefInfos.SortDefs(); for (int i = 0; i < methodDefInfos.Count; i++) { var method = methodDefInfos.Get(i).Def; pos = 0; foreach (var param in Sort(method.ParamDefs)) { if (param is null) continue; paramDefInfos.Add(param, pos++); } } paramDefInfos.SortDefs(); } void SortFields() => fieldDefInfos.Sort((a, b) => { var dta = a.Def.DeclaringType is null ? 0 : typeToRid[a.Def.DeclaringType]; var dtb = b.Def.DeclaringType is null ? 0 : typeToRid[b.Def.DeclaringType]; if (dta == 0 || dtb == 0) return a.Rid.CompareTo(b.Rid); if (dta != dtb) return dta.CompareTo(dtb); return fieldDefInfos.GetCollectionPosition(a.Def).CompareTo(fieldDefInfos.GetCollectionPosition(b.Def)); }); void SortMethods() => methodDefInfos.Sort((a, b) => { var dta = a.Def.DeclaringType is null ? 0 : typeToRid[a.Def.DeclaringType]; var dtb = b.Def.DeclaringType is null ? 0 : typeToRid[b.Def.DeclaringType]; if (dta == 0 || dtb == 0) return a.Rid.CompareTo(b.Rid); if (dta != dtb) return dta.CompareTo(dtb); return methodDefInfos.GetCollectionPosition(a.Def).CompareTo(methodDefInfos.GetCollectionPosition(b.Def)); }); void SortParameters() => paramDefInfos.Sort((a, b) => { var dma = a.Def.DeclaringMethod is null ? 0 : methodDefInfos.Rid(a.Def.DeclaringMethod); var dmb = b.Def.DeclaringMethod is null ? 0 : methodDefInfos.Rid(b.Def.DeclaringMethod); if (dma == 0 || dmb == 0) return a.Rid.CompareTo(b.Rid); if (dma != dmb) return dma.CompareTo(dmb); return paramDefInfos.GetCollectionPosition(a.Def).CompareTo(paramDefInfos.GetCollectionPosition(b.Def)); }); void SortEvents() => eventDefInfos.Sort((a, b) => { var dta = a.Def.DeclaringType is null ? 0 : typeToRid[a.Def.DeclaringType]; var dtb = b.Def.DeclaringType is null ? 0 : typeToRid[b.Def.DeclaringType]; if (dta == 0 || dtb == 0) return a.Rid.CompareTo(b.Rid); if (dta != dtb) return dta.CompareTo(dtb); return eventDefInfos.GetCollectionPosition(a.Def).CompareTo(eventDefInfos.GetCollectionPosition(b.Def)); }); void SortProperties() => propertyDefInfos.Sort((a, b) => { var dta = a.Def.DeclaringType is null ? 0 : typeToRid[a.Def.DeclaringType]; var dtb = b.Def.DeclaringType is null ? 0 : typeToRid[b.Def.DeclaringType]; if (dta == 0 || dtb == 0) return a.Rid.CompareTo(b.Rid); if (dta != dtb) return dta.CompareTo(dtb); return propertyDefInfos.GetCollectionPosition(a.Def).CompareTo(propertyDefInfos.GetCollectionPosition(b.Def)); }); void InitializeMethodAndFieldList() { uint fieldList = 1, methodList = 1; foreach (var type in allTypeDefs) { uint index = typeToRid[type]; var typeRow = tablesHeap.TypeDefTable[index]; typeRow = new RawTypeDefRow(typeRow.Flags, typeRow.Name, typeRow.Namespace, typeRow.Extends, fieldList, methodList); tablesHeap.TypeDefTable[index] = typeRow; fieldList += (uint)type.Fields.Count; methodList += (uint)type.Methods.Count; } } void InitializeParamList() { uint ridList = 1; for (uint methodRid = 1; methodRid <= methodDefInfos.TableSize; methodRid++) { var methodInfo = methodDefInfos.GetByRid(methodRid); var row = tablesHeap.MethodTable[methodRid]; row = new RawMethodRow(row.RVA, row.ImplFlags, row.Flags, row.Name, row.Signature, ridList); tablesHeap.MethodTable[methodRid] = row; if (methodInfo is not null) ridList += (uint)methodInfo.Def.ParamDefs.Count; } } void InitializeEventMap() { if (!tablesHeap.EventMapTable.IsEmpty) throw new ModuleWriterException("EventMap table isn't empty"); TypeDef type = null; for (int i = 0; i < eventDefInfos.Count; i++) { var info = eventDefInfos.GetSorted(i); if (type == info.Def.DeclaringType) continue; type = info.Def.DeclaringType; var row = new RawEventMapRow(typeToRid[type], info.NewRid); uint eventMapRid = tablesHeap.EventMapTable.Create(row); eventMapInfos.Add(type, eventMapRid); } } void InitializePropertyMap() { if (!tablesHeap.PropertyMapTable.IsEmpty) throw new ModuleWriterException("PropertyMap table isn't empty"); TypeDef type = null; for (int i = 0; i < propertyDefInfos.Count; i++) { var info = propertyDefInfos.GetSorted(i); if (type == info.Def.DeclaringType) continue; type = info.Def.DeclaringType; var row = new RawPropertyMapRow(typeToRid[type], info.NewRid); uint propertyMapRid = tablesHeap.PropertyMapTable.Create(row); propertyMapInfos.Add(type, propertyMapRid); } } /// protected override uint AddTypeRef(TypeRef tr) { if (tr is null) { Error("TypeRef is null"); return 0; } if (typeRefInfos.TryGetRid(tr, out uint rid)) { if (rid == 0) Error("TypeRef 0x{0:X8} has an infinite ResolutionScope loop.", tr.MDToken.Raw); return rid; } typeRefInfos.Add(tr, 0); // Prevent inf recursion bool isOld = PreserveTypeRefRids && mod.ResolveTypeRef(tr.Rid) == tr; var row = new RawTypeRefRow(AddResolutionScope(tr.ResolutionScope), stringsHeap.Add(tr.Name), stringsHeap.Add(tr.Namespace)); if (isOld) { rid = tr.Rid; tablesHeap.TypeRefTable[tr.Rid] = row; } else rid = tablesHeap.TypeRefTable.Add(row); typeRefInfos.SetRid(tr, rid); AddCustomAttributes(Table.TypeRef, rid, tr); AddCustomDebugInformationList(Table.TypeRef, rid, tr); return rid; } /// protected override uint AddTypeSpec(TypeSpec ts) => AddTypeSpec(ts, false); uint AddTypeSpec(TypeSpec ts, bool forceIsOld) { if (ts is null) { Error("TypeSpec is null"); return 0; } if (typeSpecInfos.TryGetRid(ts, out uint rid)) { if (rid == 0) Error("TypeSpec 0x{0:X8} has an infinite TypeSig loop.", ts.MDToken.Raw); return rid; } typeSpecInfos.Add(ts, 0); // Prevent inf recursion bool isOld = forceIsOld || (PreserveTypeSpecRids && mod.ResolveTypeSpec(ts.Rid) == ts); var row = new RawTypeSpecRow(GetSignature(ts.TypeSig, ts.ExtraData)); if (isOld) { rid = ts.Rid; tablesHeap.TypeSpecTable[ts.Rid] = row; } else rid = tablesHeap.TypeSpecTable.Add(row); typeSpecInfos.SetRid(ts, rid); AddCustomAttributes(Table.TypeSpec, rid, ts); AddCustomDebugInformationList(Table.TypeSpec, rid, ts); return rid; } /// protected override uint AddMemberRef(MemberRef mr) => AddMemberRef(mr, false); uint AddMemberRef(MemberRef mr, bool forceIsOld) { if (mr is null) { Error("MemberRef is null"); return 0; } if (memberRefInfos.TryGetRid(mr, out uint rid)) return rid; bool isOld = forceIsOld || (PreserveMemberRefRids && mod.ResolveMemberRef(mr.Rid) == mr); var row = new RawMemberRefRow(AddMemberRefParent(mr.Class), stringsHeap.Add(mr.Name), GetSignature(mr.Signature)); if (isOld) { rid = mr.Rid; tablesHeap.MemberRefTable[mr.Rid] = row; } else rid = tablesHeap.MemberRefTable.Add(row); memberRefInfos.Add(mr, rid); AddCustomAttributes(Table.MemberRef, rid, mr); AddCustomDebugInformationList(Table.MemberRef, rid, mr); return rid; } /// protected override uint AddStandAloneSig(StandAloneSig sas) => AddStandAloneSig(sas, false); uint AddStandAloneSig(StandAloneSig sas, bool forceIsOld) { if (sas is null) { Error("StandAloneSig is null"); return 0; } if (standAloneSigInfos.TryGetRid(sas, out uint rid)) return rid; bool isOld = forceIsOld || (PreserveStandAloneSigRids && mod.ResolveStandAloneSig(sas.Rid) == sas); var row = new RawStandAloneSigRow(GetSignature(sas.Signature)); if (isOld) { rid = sas.Rid; tablesHeap.StandAloneSigTable[sas.Rid] = row; } else rid = tablesHeap.StandAloneSigTable.Add(row); standAloneSigInfos.Add(sas, rid); AddCustomAttributes(Table.StandAloneSig, rid, sas); AddCustomDebugInformationList(Table.StandAloneSig, rid, sas); return rid; } /// public override MDToken GetToken(IList locals, uint origToken) { if (!PreserveStandAloneSigRids || !IsValidStandAloneSigToken(origToken)) return base.GetToken(locals, origToken); uint rid = AddStandAloneSig(new LocalSig(locals, false), origToken); if (rid == 0) return base.GetToken(locals, origToken); return new MDToken(Table.StandAloneSig, rid); } /// protected override uint AddStandAloneSig(MethodSig methodSig, uint origToken) { if (!PreserveStandAloneSigRids || !IsValidStandAloneSigToken(origToken)) return base.AddStandAloneSig(methodSig, origToken); uint rid = AddStandAloneSig(methodSig, origToken); if (rid == 0) return base.AddStandAloneSig(methodSig, origToken); return rid; } /// protected override uint AddStandAloneSig(FieldSig fieldSig, uint origToken) { if (!PreserveStandAloneSigRids || !IsValidStandAloneSigToken(origToken)) return base.AddStandAloneSig(fieldSig, origToken); uint rid = AddStandAloneSig(fieldSig, origToken); if (rid == 0) return base.AddStandAloneSig(fieldSig, origToken); return rid; } uint AddStandAloneSig(CallingConventionSig callConvSig, uint origToken) { uint sig = GetSignature(callConvSig); if (callConvTokenToSignature.TryGetValue(origToken, out uint otherSig)) { if (sig == otherSig) return MDToken.ToRID(origToken); Warning("Could not preserve StandAloneSig token 0x{0:X8}", origToken); return 0; } uint rid = MDToken.ToRID(origToken); var sas = mod.ResolveStandAloneSig(rid); if (standAloneSigInfos.Exists(sas)) { Warning("StandAloneSig 0x{0:X8} already exists", origToken); return 0; } // Make sure it uses the updated sig var oldSig = sas.Signature; try { sas.Signature = callConvSig; AddStandAloneSig(sas, true); } finally { sas.Signature = oldSig; } callConvTokenToSignature.Add(origToken, sig); return MDToken.ToRID(origToken); } bool IsValidStandAloneSigToken(uint token) { if (MDToken.ToTable(token) != Table.StandAloneSig) return false; uint rid = MDToken.ToRID(token); return mod.TablesStream.StandAloneSigTable.IsValidRID(rid); } /// protected override uint AddMethodSpec(MethodSpec ms) => AddMethodSpec(ms, false); uint AddMethodSpec(MethodSpec ms, bool forceIsOld) { if (ms is null) { Error("MethodSpec is null"); return 0; } if (methodSpecInfos.TryGetRid(ms, out uint rid)) return rid; bool isOld = forceIsOld || (PreserveMethodSpecRids && mod.ResolveMethodSpec(ms.Rid) == ms); var row = new RawMethodSpecRow(AddMethodDefOrRef(ms.Method), GetSignature(ms.Instantiation)); if (isOld) { rid = ms.Rid; tablesHeap.MethodSpecTable[ms.Rid] = row; } else rid = tablesHeap.MethodSpecTable.Add(row); methodSpecInfos.Add(ms, rid); AddCustomAttributes(Table.MethodSpec, rid, ms); AddCustomDebugInformationList(Table.MethodSpec, rid, ms); return rid; } /// protected override void BeforeSortingCustomAttributes() => InitializeUninitializedTableRows(); } }