diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs
new file mode 100644
index 000000000..1bd746efd
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs
@@ -0,0 +1,83 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the ClassLayout table — explicit field-layout overrides on TypeDefs marked
+ /// Sequential or Explicit (e.g. [StructLayout(LayoutKind.Explicit)]). System.Reflection.Metadata
+ /// doesn't surface these rows through a typed enumeration so we walk the table bytes
+ /// directly via a BlobReader.
+ ///
+ public sealed class ClassLayoutTableTreeNode : MetadataTableTreeNode
+ {
+ public ClassLayoutTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.ClassLayout, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.ClassLayout);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.ClassLayout);
+ int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ ushort packingSize = reader.ReadUInt16();
+ uint classSize = reader.ReadUInt32();
+ int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new ClassLayoutEntry(rid, packingSize, classSize, MetadataTokens.TypeDefinitionHandle(parentRow)));
+ }
+ return list;
+ }
+
+ public sealed class ClassLayoutEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x0F000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Parent => MetadataTokens.GetToken(parent);
+
+ public ushort PackingSize { get; }
+
+ public uint ClassSize { get; }
+
+ readonly TypeDefinitionHandle parent;
+
+ public ClassLayoutEntry(int rid, ushort packingSize, uint classSize, TypeDefinitionHandle parent)
+ {
+ RID = rid;
+ PackingSize = packingSize;
+ ClassSize = classSize;
+ this.parent = parent;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs
new file mode 100644
index 000000000..9acedccd6
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs
@@ -0,0 +1,80 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the EventMap table — pairs each TypeDef that declares events with the first
+ /// row of its event list (later rows belong to the same type until the next EventMap row).
+ ///
+ public sealed class EventMapTableTreeNode : MetadataTableTreeNode
+ {
+ public EventMapTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.EventMap, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.EventMap);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.EventMap);
+ int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4;
+ int eventDefSize = metadata.GetTableRowCount(TableIndex.Event) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ int eventListRow = eventDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new EventMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.EventDefinitionHandle(eventListRow)));
+ }
+ return list;
+ }
+
+ public sealed class EventMapEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x12000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Parent => MetadataTokens.GetToken(parent);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int EventList => MetadataTokens.GetToken(eventList);
+
+ readonly TypeDefinitionHandle parent;
+ readonly EventDefinitionHandle eventList;
+
+ public EventMapEntry(int rid, TypeDefinitionHandle parent, EventDefinitionHandle eventList)
+ {
+ RID = rid;
+ this.parent = parent;
+ this.eventList = eventList;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs
new file mode 100644
index 000000000..28976fb1f
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs
@@ -0,0 +1,81 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the FieldLayout table — explicit per-field byte offsets for fields whose
+ /// owning TypeDef has set.
+ ///
+ public sealed class FieldLayoutTableTreeNode : MetadataTableTreeNode
+ {
+ public FieldLayoutTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.FieldLayout, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.FieldLayout);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.FieldLayout);
+ int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int offset = reader.ReadInt32();
+ int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new FieldLayoutEntry(rid, offset, MetadataTokens.FieldDefinitionHandle(fieldRow)));
+ }
+ return list;
+ }
+
+ public sealed class FieldLayoutEntry
+ {
+ // Use a verbatim fieldHandle local because C# 14 made `field` contextual inside property
+ // accessors — referencing it without `@` would name the auto-property's hidden
+ // backing field instead of this member.
+ readonly FieldDefinitionHandle fieldHandle;
+
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x10000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Field => MetadataTokens.GetToken(fieldHandle);
+
+ [ColumnInfo("X8")]
+ public int FieldOffset { get; }
+
+ public FieldLayoutEntry(int rid, int fieldOffset, FieldDefinitionHandle field)
+ {
+ RID = rid;
+ FieldOffset = fieldOffset;
+ fieldHandle = field;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs
new file mode 100644
index 000000000..9bd226b48
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs
@@ -0,0 +1,83 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the FieldMarshal table — native-marshalling descriptors attached to fields
+ /// or parameters. Parent is a HasFieldMarshal coded token (Field / Param); NativeType
+ /// is the heap-offset of a marshal-spec blob.
+ ///
+ public sealed class FieldMarshalTableTreeNode : MetadataTableTreeNode
+ {
+ public FieldMarshalTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.FieldMarshal, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.FieldMarshal);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.FieldMarshal);
+ int hasFieldMarshalRefSize = metadata.ComputeCodedTokenSize(32768, TableMask.Field | TableMask.Param);
+ int blobHeapSize = metadata.GetHeapSize(HeapIndex.Blob) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ uint parentTag = (uint)(hasFieldMarshalRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
+ int blobOffset = blobHeapSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new FieldMarshalEntry(rid,
+ MetadataReaderHelpers.FromHasFieldMarshalTag(parentTag),
+ MetadataTokens.BlobHandle(blobOffset)));
+ }
+ return list;
+ }
+
+ public sealed class FieldMarshalEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x0D000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Parent => MetadataTokens.GetToken(parent);
+
+ [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
+ public int NativeType => MetadataTokens.GetHeapOffset(nativeType);
+
+ readonly EntityHandle parent;
+ readonly BlobHandle nativeType;
+
+ public FieldMarshalEntry(int rid, EntityHandle parent, BlobHandle nativeType)
+ {
+ RID = rid;
+ this.parent = parent;
+ this.nativeType = nativeType;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs
new file mode 100644
index 000000000..e550a13fc
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs
@@ -0,0 +1,80 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the FieldRVA table — for static fields backed by a fixed PE-image RVA, the
+ /// table maps each FieldDef to its data location. Common with fixed array initializers
+ /// (the compiler emits a synthesised type whose static fields point at the .rdata bytes).
+ ///
+ public sealed class FieldRVATableTreeNode : MetadataTableTreeNode
+ {
+ public FieldRVATableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.FieldRva, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.FieldRva);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.FieldRva);
+ int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int rva = reader.ReadInt32();
+ int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new FieldRVAEntry(rid, rva, MetadataTokens.FieldDefinitionHandle(fieldRow)));
+ }
+ return list;
+ }
+
+ public sealed class FieldRVAEntry
+ {
+ // Verbatim fieldHandle — see FieldLayoutTableTreeNode for the C# 14 keyword note.
+ readonly FieldDefinitionHandle fieldHandle;
+
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x1D000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Field => MetadataTokens.GetToken(fieldHandle);
+
+ [ColumnInfo("X8")]
+ public int RVA { get; }
+
+ public FieldRVAEntry(int rid, int rva, FieldDefinitionHandle field)
+ {
+ RID = rid;
+ RVA = rva;
+ fieldHandle = field;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs
new file mode 100644
index 000000000..6e1b6e94a
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs
@@ -0,0 +1,95 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the ImplMap table — P/Invoke linkage. Each row maps a managed MethodDef (or
+ /// Field, in legacy CIL) to its native entry point: the extern name plus the
+ /// ModuleRef holding the DLL identifier.
+ ///
+ public sealed class ImplMapTableTreeNode : MetadataTableTreeNode
+ {
+ public ImplMapTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.ImplMap, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.ImplMap);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.ImplMap);
+ int moduleRefSize = metadata.GetTableRowCount(TableIndex.ModuleRef) < ushort.MaxValue ? 2 : 4;
+ int memberForwardedTagSize = metadata.ComputeCodedTokenSize(32768, TableMask.MethodDef | TableMask.Field);
+ int stringHandleSize = metadata.GetHeapSize(HeapIndex.String) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ MethodImportAttributes mappingFlags = (MethodImportAttributes)reader.ReadUInt16();
+ uint memberForwardedTag = (uint)(memberForwardedTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
+ int importNameOffset = stringHandleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ int importScopeRow = moduleRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new ImplMapEntry(rid, mappingFlags,
+ MetadataReaderHelpers.FromMemberForwardedTag(memberForwardedTag),
+ metadata.GetString(MetadataTokens.StringHandle(importNameOffset)),
+ MetadataTokens.ModuleReferenceHandle(importScopeRow)));
+ }
+ return list;
+ }
+
+ public sealed class ImplMapEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x1C000000 | RID;
+
+ [ColumnInfo("X8")]
+ public MethodImportAttributes MappingFlags { get; }
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int MemberForwarded => MetadataTokens.GetToken(memberForwarded);
+
+ public string ImportName { get; }
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int ImportScope => MetadataTokens.GetToken(importScope);
+
+ readonly EntityHandle memberForwarded;
+ readonly ModuleReferenceHandle importScope;
+
+ public ImplMapEntry(int rid, MethodImportAttributes mappingFlags, EntityHandle memberForwarded, string importName, ModuleReferenceHandle importScope)
+ {
+ RID = rid;
+ MappingFlags = mappingFlags;
+ this.memberForwarded = memberForwarded;
+ ImportName = importName;
+ this.importScope = importScope;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs
new file mode 100644
index 000000000..3116335e7
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs
@@ -0,0 +1,82 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the InterfaceImpl table — every type/interface implementation pair. Class is
+ /// a TypeDef row; Interface is a TypeDefOrRef coded token (TypeDef / TypeRef / TypeSpec).
+ ///
+ public sealed class InterfaceImplTableTreeNode : MetadataTableTreeNode
+ {
+ public InterfaceImplTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.InterfaceImpl, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.InterfaceImpl);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.InterfaceImpl);
+ int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4;
+ int interfaceTagSize = metadata.ComputeCodedTokenSize(32768, TableMask.TypeDef | TableMask.TypeRef | TableMask.TypeSpec);
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int classRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ uint interfaceTag = (uint)(interfaceTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
+ list.Add(new InterfaceImplEntry(rid,
+ MetadataTokens.TypeDefinitionHandle(classRow),
+ MetadataReaderHelpers.FromTypeDefOrRefTag(interfaceTag)));
+ }
+ return list;
+ }
+
+ public sealed class InterfaceImplEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x09000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Class => MetadataTokens.GetToken(@class);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Interface => MetadataTokens.GetToken(@interface);
+
+ readonly TypeDefinitionHandle @class;
+ readonly EntityHandle @interface;
+
+ public InterfaceImplEntry(int rid, TypeDefinitionHandle @class, EntityHandle @interface)
+ {
+ RID = rid;
+ this.@class = @class;
+ this.@interface = @interface;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs
new file mode 100644
index 000000000..f3f07bf17
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs
@@ -0,0 +1,75 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the MethodImpl table — explicit interface implementations and method-body
+ /// overrides where the declared signature differs from the impl signature. Each row
+ /// pairs a Type, a MethodDeclaration (the override target), and a MethodBody (the impl).
+ ///
+ public sealed class MethodImplTableTreeNode : MetadataTableTreeNode
+ {
+ public MethodImplTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.MethodImpl, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ for (int row = 1; row <= metadataFile.Metadata.GetTableRowCount(TableIndex.MethodImpl); row++)
+ list.Add(new MethodImplEntry(metadataFile, MetadataTokens.MethodImplementationHandle(row)));
+ return list;
+ }
+
+ public sealed class MethodImplEntry
+ {
+ readonly MetadataFile metadataFile;
+ readonly MethodImplementationHandle handle;
+ readonly MethodImplementation methodImpl;
+
+ public int RID => MetadataTokens.GetToken(handle) & 0xFFFFFF;
+
+ [ColumnInfo("X8")]
+ public int Token => MetadataTokens.GetToken(handle);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Type => MetadataTokens.GetToken(methodImpl.Type);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int MethodDeclaration => MetadataTokens.GetToken(methodImpl.MethodDeclaration);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int MethodBody => MetadataTokens.GetToken(methodImpl.MethodBody);
+
+ public MethodImplEntry(MetadataFile metadataFile, MethodImplementationHandle handle)
+ {
+ this.metadataFile = metadataFile;
+ this.handle = handle;
+ methodImpl = metadataFile.Metadata.GetMethodImplementation(handle);
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs
new file mode 100644
index 000000000..5efc61ec3
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs
@@ -0,0 +1,77 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the MethodSemantics table — links accessor methods (get/set/add/remove/raise)
+ /// to the property or event they implement.
+ /// returns a typed enumeration so byte-level reading isn't required.
+ ///
+ public sealed class MethodSemanticsTableTreeNode : MetadataTableTreeNode
+ {
+ public MethodSemanticsTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.MethodSemantics, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ foreach (var row in metadataFile.Metadata.GetMethodSemantics())
+ list.Add(new MethodSemanticsEntry(row.Handle, row.Semantics, row.Method, row.Association));
+ return list;
+ }
+
+ public sealed class MethodSemanticsEntry
+ {
+ public int RID => MetadataTokens.GetToken(handle) & 0xFFFFFF;
+
+ [ColumnInfo("X8")]
+ public int Token => MetadataTokens.GetToken(handle);
+
+ [ColumnInfo("X8")]
+ public MethodSemanticsAttributes Semantics { get; }
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Method => MetadataTokens.GetToken(method);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Association => MetadataTokens.GetToken(association);
+
+ readonly Handle handle;
+ readonly MethodDefinitionHandle method;
+ readonly EntityHandle association;
+
+ public MethodSemanticsEntry(Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association)
+ {
+ this.handle = handle;
+ Semantics = semantics;
+ this.method = method;
+ this.association = association;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs
new file mode 100644
index 000000000..a8b4de244
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs
@@ -0,0 +1,80 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the NestedClass table — pairs each nested type with its enclosing type. Walked
+ /// directly from the table bytes because System.Reflection.Metadata only exposes
+ /// nested-type discovery as a per-typedef enumeration.
+ ///
+ public sealed class NestedClassTableTreeNode : MetadataTableTreeNode
+ {
+ public NestedClassTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.NestedClass, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.NestedClass);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.NestedClass);
+ int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int nestedRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ int enclosingRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new NestedClassEntry(rid, MetadataTokens.TypeDefinitionHandle(nestedRow), MetadataTokens.TypeDefinitionHandle(enclosingRow)));
+ }
+ return list;
+ }
+
+ public sealed class NestedClassEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x29000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int NestedClass => MetadataTokens.GetToken(nested);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int EnclosingClass => MetadataTokens.GetToken(enclosing);
+
+ readonly TypeDefinitionHandle nested;
+ readonly TypeDefinitionHandle enclosing;
+
+ public NestedClassEntry(int rid, TypeDefinitionHandle nested, TypeDefinitionHandle enclosing)
+ {
+ RID = rid;
+ this.nested = nested;
+ this.enclosing = enclosing;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs
new file mode 100644
index 000000000..c1d86ce66
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs
@@ -0,0 +1,80 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the PropertyMap table — pairs each TypeDef that declares properties with the
+ /// first row of its property list. Same shape as EventMap but for properties.
+ ///
+ public sealed class PropertyMapTableTreeNode : MetadataTableTreeNode
+ {
+ public PropertyMapTableTreeNode(MetadataFile metadataFile)
+ : base(TableIndex.PropertyMap, metadataFile)
+ {
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(TableIndex.PropertyMap);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(TableIndex.PropertyMap);
+ int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4;
+ int propertyDefSize = metadata.GetTableRowCount(TableIndex.Property) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ int propertyListRow = propertyDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new PropertyMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.PropertyDefinitionHandle(propertyListRow)));
+ }
+ return list;
+ }
+
+ public sealed class PropertyMapEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => 0x15000000 | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Parent => MetadataTokens.GetToken(parent);
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int PropertyList => MetadataTokens.GetToken(propertyList);
+
+ readonly TypeDefinitionHandle parent;
+ readonly PropertyDefinitionHandle propertyList;
+
+ public PropertyMapEntry(int rid, TypeDefinitionHandle parent, PropertyDefinitionHandle propertyList)
+ {
+ RID = rid;
+ this.parent = parent;
+ this.propertyList = propertyList;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs
new file mode 100644
index 000000000..71fc2c481
--- /dev/null
+++ b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs
@@ -0,0 +1,88 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ILSpy.Metadata.CorTables
+{
+ ///
+ /// View of the five Ptr tables (FieldPtr / MethodPtr / ParamPtr / EventPtr / PropertyPtr).
+ /// Present only in metadata produced with #~ + indirection (rare; mostly EnC or some
+ /// custom emitters), and each row carries a single handle into the table the Ptr points
+ /// at. One node type drives all five since the row shape is identical.
+ ///
+ public sealed class PtrTableTreeNode : MetadataTableTreeNode
+ {
+ readonly TableIndex referencedTableKind;
+
+ public PtrTableTreeNode(TableIndex kind, MetadataFile metadataFile)
+ : base(kind, metadataFile)
+ {
+ referencedTableKind = kind switch {
+ TableIndex.EventPtr => TableIndex.Event,
+ TableIndex.FieldPtr => TableIndex.Field,
+ TableIndex.MethodPtr => TableIndex.MethodDef,
+ TableIndex.ParamPtr => TableIndex.Param,
+ TableIndex.PropertyPtr => TableIndex.Property,
+ _ => throw new ArgumentOutOfRangeException(nameof(kind), $"PtrTable does not support {kind}"),
+ };
+ }
+
+ protected override IReadOnlyList LoadTable()
+ {
+ var list = new List();
+ var metadata = metadataFile.Metadata;
+ var length = metadata.GetTableRowCount(Kind);
+ var reader = metadata.AsBlobReader();
+ reader.Offset = metadata.GetTableMetadataOffset(Kind);
+ int handleSize = metadata.GetTableRowCount(referencedTableKind) < ushort.MaxValue ? 2 : 4;
+ for (int rid = 1; rid <= length; rid++)
+ {
+ int handleRow = handleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
+ list.Add(new PtrEntry(rid, Kind, MetadataTokens.EntityHandle(((int)referencedTableKind << 24) | handleRow)));
+ }
+ return list;
+ }
+
+ public sealed class PtrEntry
+ {
+ public int RID { get; }
+
+ [ColumnInfo("X8")]
+ public int Token => ((int)kind << 24) | RID;
+
+ [ColumnInfo("X8", Kind = ColumnKind.Token)]
+ public int Handle => MetadataTokens.GetToken(handle);
+
+ readonly TableIndex kind;
+ readonly EntityHandle handle;
+
+ public PtrEntry(int rid, TableIndex kind, EntityHandle handle)
+ {
+ RID = rid;
+ this.kind = kind;
+ this.handle = handle;
+ }
+ }
+ }
+}
diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs
index ad161638e..1fb09a307 100644
--- a/ILSpy/Metadata/Helpers.cs
+++ b/ILSpy/Metadata/Helpers.cs
@@ -18,6 +18,8 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Metadata;
namespace ILSpy.Metadata
{
@@ -90,4 +92,108 @@ namespace ILSpy.Metadata
Meaning = meaning;
}
}
+
+ ///
+ /// Bitmask covering each CLI metadata table. Used as the second argument to
+ /// when computing how many bytes
+ /// a coded-token column occupies in a given metadata blob — same encoding as the WPF
+ /// host's TableMask.
+ ///
+ [Flags]
+ public enum TableMask : ulong
+ {
+ Module = 0x1,
+ TypeRef = 0x2,
+ TypeDef = 0x4,
+ FieldPtr = 0x8,
+ Field = 0x10,
+ MethodPtr = 0x20,
+ MethodDef = 0x40,
+ ParamPtr = 0x80,
+ Param = 0x100,
+ InterfaceImpl = 0x200,
+ MemberRef = 0x400,
+ Constant = 0x800,
+ CustomAttribute = 0x1000,
+ FieldMarshal = 0x2000,
+ DeclSecurity = 0x4000,
+ ClassLayout = 0x8000,
+ FieldLayout = 0x10000,
+ StandAloneSig = 0x20000,
+ EventMap = 0x40000,
+ EventPtr = 0x80000,
+ Event = 0x100000,
+ PropertyMap = 0x200000,
+ PropertyPtr = 0x400000,
+ Property = 0x800000,
+ MethodSemantics = 0x1000000,
+ MethodImpl = 0x2000000,
+ ModuleRef = 0x4000000,
+ TypeSpec = 0x8000000,
+ ImplMap = 0x10000000,
+ FieldRva = 0x20000000,
+ EnCLog = 0x40000000,
+ EnCMap = 0x80000000,
+ Assembly = 0x100000000,
+ AssemblyRef = 0x800000000,
+ File = 0x4000000000,
+ ExportedType = 0x8000000000,
+ ManifestResource = 0x10000000000,
+ NestedClass = 0x20000000000,
+ GenericParam = 0x40000000000,
+ MethodSpec = 0x80000000000,
+ GenericParamConstraint = 0x100000000000,
+ Document = 0x1000000000000,
+ MethodDebugInformation = 0x2000000000000,
+ LocalScope = 0x4000000000000,
+ LocalVariable = 0x8000000000000,
+ LocalConstant = 0x10000000000000,
+ ImportScope = 0x20000000000000,
+ StateMachineMethod = 0x40000000000000,
+ CustomDebugInformation = 0x80000000000000,
+ }
+
+ ///
+ /// Reflection-backed shims onto System.Reflection.Metadata internals — needed to
+ /// decode coded-token columns and compute their on-disk widths. The corresponding public
+ /// APIs do not exist; the WPF host carries the same reflection infrastructure. Pre-existing
+ /// risk: if a future runtime renames TypeDefOrRefTag.ConvertToHandle or the
+ /// TableRowCounts field on MetadataReader, these methods will throw and
+ /// the affected metadata-table viewers will break.
+ ///
+ public static class MetadataReaderHelpers
+ {
+ static readonly FieldInfo? rowCountsField;
+ static readonly MethodInfo? computeCodedTokenSizeMethod;
+ static readonly MethodInfo? typeDefOrRefConvert;
+ static readonly MethodInfo? hasFieldMarshalConvert;
+ static readonly MethodInfo? memberForwardedConvert;
+
+ static MetadataReaderHelpers()
+ {
+ rowCountsField = typeof(MetadataReader)
+ .GetField("TableRowCounts", BindingFlags.NonPublic | BindingFlags.Instance);
+ computeCodedTokenSizeMethod = typeof(MetadataReader)
+ .GetMethod("ComputeCodedTokenSize", BindingFlags.Instance | BindingFlags.NonPublic);
+ var asm = typeof(TypeDefinitionHandle).Assembly;
+ typeDefOrRefConvert = asm.GetType("System.Reflection.Metadata.Ecma335.TypeDefOrRefTag")
+ ?.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic);
+ hasFieldMarshalConvert = asm.GetType("System.Reflection.Metadata.Ecma335.HasFieldMarshalTag")
+ ?.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic);
+ memberForwardedConvert = asm.GetType("System.Reflection.Metadata.Ecma335.MemberForwardedTag")
+ ?.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic);
+ }
+
+ public static EntityHandle FromTypeDefOrRefTag(uint tag)
+ => (EntityHandle)typeDefOrRefConvert!.Invoke(null, [tag])!;
+
+ public static EntityHandle FromHasFieldMarshalTag(uint tag)
+ => (EntityHandle)hasFieldMarshalConvert!.Invoke(null, [tag])!;
+
+ public static EntityHandle FromMemberForwardedTag(uint tag)
+ => (EntityHandle)memberForwardedConvert!.Invoke(null, [tag])!;
+
+ public static int ComputeCodedTokenSize(this MetadataReader metadata, int largeRowSize, TableMask mask)
+ => (int)computeCodedTokenSizeMethod!.Invoke(metadata, [largeRowSize, rowCountsField!.GetValue(metadata)!, (ulong)mask])!;
+ }
}
diff --git a/ILSpy/Metadata/MetadataTablesTreeNode.cs b/ILSpy/Metadata/MetadataTablesTreeNode.cs
index bfbd1f474..d371fb6a5 100644
--- a/ILSpy/Metadata/MetadataTablesTreeNode.cs
+++ b/ILSpy/Metadata/MetadataTablesTreeNode.cs
@@ -106,6 +106,19 @@ namespace ILSpy.Metadata
TableIndex.ImportScope => new ImportScopeTableTreeNode(metadataFile),
TableIndex.StateMachineMethod => new StateMachineMethodTableTreeNode(metadataFile),
TableIndex.CustomDebugInformation => new CustomDebugInformationTableTreeNode(metadataFile),
+ TableIndex.MethodImpl => new MethodImplTableTreeNode(metadataFile),
+ TableIndex.MethodSemantics => new MethodSemanticsTableTreeNode(metadataFile),
+ TableIndex.ClassLayout => new ClassLayoutTableTreeNode(metadataFile),
+ TableIndex.FieldLayout => new FieldLayoutTableTreeNode(metadataFile),
+ TableIndex.FieldRva => new FieldRVATableTreeNode(metadataFile),
+ TableIndex.NestedClass => new NestedClassTableTreeNode(metadataFile),
+ TableIndex.EventMap => new EventMapTableTreeNode(metadataFile),
+ TableIndex.PropertyMap => new PropertyMapTableTreeNode(metadataFile),
+ TableIndex.InterfaceImpl => new InterfaceImplTableTreeNode(metadataFile),
+ TableIndex.FieldMarshal => new FieldMarshalTableTreeNode(metadataFile),
+ TableIndex.ImplMap => new ImplMapTableTreeNode(metadataFile),
+ TableIndex.FieldPtr or TableIndex.MethodPtr or TableIndex.ParamPtr
+ or TableIndex.EventPtr or TableIndex.PropertyPtr => new PtrTableTreeNode(table, metadataFile),
_ => new UnsupportedMetadataTableTreeNode(table, metadataFile),
};
}