├── MetadataLocator.Test.CLR20.x64 ├── Program.cs └── MetadataLocator.Test.CLR20.x64.csproj ├── MetadataLocator.Test.CLR20.x86 ├── Program.cs └── MetadataLocator.Test.CLR20.x86.csproj ├── MetadataLocator.Test.CLR40.x64 ├── Program.cs └── MetadataLocator.Test.CLR40.x64.csproj ├── MetadataLocator.Test.CLR40.x86 ├── Program.cs └── MetadataLocator.Test.CLR40.x86.csproj ├── MetadataLocator.Test.CoreCLR31.x64 ├── Program.cs └── MetadataLocator.Test.CoreCLR31.x64.csproj ├── MetadataLocator.Test.CoreCLR31.x86 ├── Program.cs └── MetadataLocator.Test.CoreCLR31.x86.csproj ├── MetadataLocator.Test.CoreCLR60.x64 ├── Program.cs └── MetadataLocator.Test.CoreCLR60.x64.csproj ├── MetadataLocator.Test.CoreCLR60.x86 ├── Program.cs └── MetadataLocator.Test.CoreCLR60.x86.csproj ├── Images └── locate-table-stream.png ├── MetadataLocator.Test.CLR.props ├── MetadataLocator ├── System │ ├── HandleProcessCorruptedStateExceptionsAttribute.cs │ ├── Array2.cs │ ├── NRT_Helpers.cs │ └── NRT.cs ├── MetadataLocator.csproj ├── ReflectionHelpers.cs ├── PEInfo.cs ├── RuntimeEnvironment.cs ├── MetadataInfo.cs ├── MetadataImport.cs ├── Memory.cs ├── Utils.cs ├── PEInfoImpl.cs ├── TestAssemblyManager.cs ├── RuntimeDefinitions.cs └── MetadataInfoImpl.cs ├── MetadataLocator.Test ├── System │ ├── Array2.cs │ ├── NRT_Helpers.cs │ └── NRT.cs ├── MetadataImportTests.cs ├── MetadataLocator.Test.csproj ├── Assert.cs ├── Utils.cs ├── RuntimeDefinitionTests.cs └── TestDriver.cs ├── MetadataLocator.Test.CoreCLR.props ├── MetadataLocator.Test.props ├── appveyor.yml ├── README.md ├── LICENSE ├── .gitattributes ├── MetadataLocator.sln ├── .gitignore └── .editorconfig /MetadataLocator.Test.CLR20.x64/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR20.x86/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR40.x64/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR40.x86/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR31.x64/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR31.x86/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR60.x64/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR60.x86/Program.cs: -------------------------------------------------------------------------------- 1 | using MetadataLocator.Test; 2 | 3 | TestDriver.Test(); 4 | -------------------------------------------------------------------------------- /Images/locate-table-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwh1004/MetadataLocator/HEAD/Images/locate-table-stream.png -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR20.x64/MetadataLocator.Test.CLR20.x64.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net35 4 | x64 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR20.x86/MetadataLocator.Test.CLR20.x86.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net35 4 | x86 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR40.x64/MetadataLocator.Test.CLR40.x64.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net40 4 | x64 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR40.x86/MetadataLocator.Test.CLR40.x86.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net40 4 | x86 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CLR.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR60.x64/MetadataLocator.Test.CoreCLR60.x64.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | x64 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR60.x86/MetadataLocator.Test.CoreCLR60.x86.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | x86 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR31.x64/MetadataLocator.Test.CoreCLR31.x64.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | x64 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR31.x86/MetadataLocator.Test.CoreCLR31.x86.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | x86 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetadataLocator/System/HandleProcessCorruptedStateExceptionsAttribute.cs: -------------------------------------------------------------------------------- 1 | #if !NET40_OR_GREATER 2 | namespace System.Runtime.ExceptionServices; 3 | 4 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 5 | sealed class HandleProcessCorruptedStateExceptionsAttribute : Attribute { 6 | } 7 | #endif 8 | -------------------------------------------------------------------------------- /MetadataLocator/System/Array2.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | static class Array2 { 4 | public static T[] Empty() { 5 | return EmptyArray.Value; 6 | } 7 | 8 | static class EmptyArray { 9 | #pragma warning disable CA1825 10 | public static readonly T[] Value = new T[0]; 11 | #pragma warning restore CA1825 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MetadataLocator.Test/System/Array2.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | static class Array2 { 4 | public static T[] Empty() { 5 | return EmptyArray.Value; 6 | } 7 | 8 | static class EmptyArray { 9 | #pragma warning disable CA1825 10 | public static readonly T[] Value = new T[0]; 11 | #pragma warning restore CA1825 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MetadataLocator.Test.CoreCLR.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | NU1702 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MetadataLocator/System/NRT_Helpers.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace System.Diagnostics; 4 | 5 | static class Debug2 { 6 | [Conditional("DEBUG")] 7 | public static void Assert([DoesNotReturnIf(false)] bool condition) { 8 | Debug.Assert(condition); 9 | } 10 | 11 | [Conditional("DEBUG")] 12 | public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) { 13 | Debug.Assert(condition, message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MetadataLocator.Test/MetadataImportTests.cs: -------------------------------------------------------------------------------- 1 | namespace MetadataLocator.Test; 2 | 3 | static unsafe class MetadataImportTests { 4 | public static void Test() { 5 | var module = typeof(MetadataImportTests).Module; 6 | var metadataImport = MetadataImport.Create(module); 7 | Assert.IsTrue(metadataImport is not null); 8 | nuint pVersion = metadataImport.GetVersionString(); 9 | var version = new string((sbyte*)pVersion); 10 | Assert.IsTrue(version == "v2.0.50727"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MetadataLocator.Test.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(ProjectName) 4 | 2.0.0.0 5 | Copyright © 2019-2022 Wwh 6 | 7 | 8 | true 9 | 10.0 10 | enable 11 | ..\bin\$(Configuration) 12 | false 13 | 14 | 15 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | image: Visual Studio 2022 3 | configuration: Release 4 | platform: Any CPU 5 | before_build: 6 | - cmd: appveyor-retry nuget restore 7 | build: 8 | project: MetadataLocator.sln 9 | verbosity: normal 10 | artifacts: 11 | - path: bin\Release 12 | name: MetadataLocator 13 | deploy: 14 | - provider: GitHub 15 | tag: $(APPVEYOR_REPO_TAG_NAME) 16 | release: MetadataLocator 17 | auth_token: 18 | secure: +8UJ1C312inNq+80I8WST34vPMrCylnmTx+9rmuIh1qnsArA5x2b8yc+kcwkXmQC 19 | on: 20 | APPVEYOR_REPO_TAG: true -------------------------------------------------------------------------------- /MetadataLocator.Test/MetadataLocator.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net35 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetadataLocator 2 | Locate the address of .NET metadata by CLR to anti anti dump 3 | 4 | ![locate-table-stream](./Images/locate-table-stream.png) 5 | Locate the table stream under the full protection of ConfuserEx (include AntiDump protection) 6 | 7 | ## Requirements 8 | - Any .NET Framework 9 | 10 | ## Downloads 11 | GitHub: [Latest release](https://github.com/wwh1004/MetadataLocator/releases/latest/download/MetadataLocator.zip) 12 | 13 | AppVeyor: [![Build status](https://ci.appveyor.com/api/projects/status/7mb50g6o0dfn89do?svg=true)](https://ci.appveyor.com/project/wwh1004/metadatalocator) 14 | -------------------------------------------------------------------------------- /MetadataLocator/MetadataLocator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(ProjectName) 4 | 2.0.0.1 5 | Copyright © 2019-2022 Wwh 6 | 7 | 8 | net35 9 | true 10 | 10.0 11 | enable 12 | ..\bin\$(Configuration) 13 | true 14 | false 15 | 16 | 17 | -------------------------------------------------------------------------------- /MetadataLocator.Test/Assert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace MetadataLocator.Test; 5 | 6 | sealed class AssertFailedException : Exception { 7 | public AssertFailedException() { 8 | } 9 | 10 | public AssertFailedException(string message) : base(message) { 11 | } 12 | } 13 | 14 | static class Assert { 15 | public static void IsTrue([DoesNotReturnIf(false)] bool condition) { 16 | if (!condition) 17 | throw new AssertFailedException(); 18 | } 19 | 20 | public static void IsTrue([DoesNotReturnIf(false)] bool condition, string message) { 21 | if (!condition) 22 | throw new AssertFailedException(message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MetadataLocator.Test/System/NRT_Helpers.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace System { 4 | static class string2 { 5 | public static bool IsNullOrEmpty([NotNullWhen(false)] string? value) { 6 | return string.IsNullOrEmpty(value); 7 | } 8 | } 9 | } 10 | 11 | namespace System.Diagnostics { 12 | static class Debug2 { 13 | [Conditional("DEBUG")] 14 | public static void Assert([DoesNotReturnIf(false)] bool condition) { 15 | Debug.Assert(condition); 16 | } 17 | 18 | [Conditional("DEBUG")] 19 | public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) { 20 | Debug.Assert(condition, message); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MetadataLocator.Test/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection.Emit; 4 | 5 | namespace MetadataLocator.Test; 6 | 7 | static class Utils { 8 | static readonly Dictionary sizes = new(); 9 | 10 | public static uint SizeOf(Type type) { 11 | if (!sizes.TryGetValue(type, out uint size)) { 12 | size = GetSize(type); 13 | sizes.Add(type, size); 14 | } 15 | return size; 16 | } 17 | 18 | static uint GetSize(Type type) { 19 | var dm = new DynamicMethod($"SizeOf_{type.Name}", typeof(int), Type.EmptyTypes, typeof(Utils).Module, true); 20 | var il = dm.GetILGenerator(); 21 | il.Emit(OpCodes.Sizeof, type); 22 | il.Emit(OpCodes.Ret); 23 | return (uint)(int)dm.Invoke(null, null); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MetadataLocator/System/NRT.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK || NETSTANDARD2_0 2 | namespace System.Diagnostics.CodeAnalysis; 3 | 4 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 5 | sealed class NotNullWhenAttribute : Attribute { 6 | public bool ReturnValue { get; } 7 | 8 | public NotNullWhenAttribute(bool returnValue) { 9 | ReturnValue = returnValue; 10 | } 11 | } 12 | 13 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 14 | sealed class DoesNotReturnAttribute : Attribute { 15 | public DoesNotReturnAttribute() { 16 | } 17 | } 18 | 19 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 20 | sealed class DoesNotReturnIfAttribute : Attribute { 21 | public bool ParameterValue { get; } 22 | 23 | public DoesNotReturnIfAttribute(bool parameterValue) { 24 | ParameterValue = parameterValue; 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /MetadataLocator.Test/System/NRT.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK || NETSTANDARD2_0 2 | namespace System.Diagnostics.CodeAnalysis; 3 | 4 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 5 | sealed class NotNullWhenAttribute : Attribute { 6 | public bool ReturnValue { get; } 7 | 8 | public NotNullWhenAttribute(bool returnValue) { 9 | ReturnValue = returnValue; 10 | } 11 | } 12 | 13 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 14 | sealed class DoesNotReturnAttribute : Attribute { 15 | public DoesNotReturnAttribute() { 16 | } 17 | } 18 | 19 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 20 | sealed class DoesNotReturnIfAttribute : Attribute { 21 | public bool ParameterValue { get; } 22 | 23 | public DoesNotReturnIfAttribute(bool parameterValue) { 24 | ParameterValue = parameterValue; 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /MetadataLocator/ReflectionHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace MetadataLocator; 6 | 7 | static class ReflectionHelpers { 8 | static readonly FieldInfo moduleHandleField = GetModuleHandleField(); 9 | 10 | static FieldInfo GetModuleHandleField() { 11 | if (RuntimeEnvironment.Version < RuntimeVersion.Fx40) 12 | return typeof(ModuleHandle).GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance); 13 | return typeof(object).Module.GetType("System.Reflection.RuntimeModule").GetField("m_pData", BindingFlags.NonPublic | BindingFlags.Instance); 14 | } 15 | 16 | public static nuint GetModuleHandle(Module module) { 17 | if (RuntimeEnvironment.Version < RuntimeVersion.Fx40) 18 | return (nuint)(nint)moduleHandleField.GetValue(module.ModuleHandle); 19 | return (nuint)(nint)moduleHandleField.GetValue(module); 20 | } 21 | 22 | public static nuint GetNativeModuleHandle(Module module) { 23 | if (module is null) 24 | throw new ArgumentNullException(nameof(module)); 25 | 26 | return (nuint)(nint)Marshal.GetHINSTANCE(module); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 wwh1004 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MetadataLocator/PEInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace MetadataLocator; 4 | 5 | /// 6 | /// CLR internal image layout 7 | /// 8 | public sealed class PEImageLayout { 9 | /// 10 | /// Determine if current instance is invalid 11 | /// 12 | public bool IsInvalid => ImageBase == 0; 13 | 14 | /// 15 | /// Image base address 16 | /// 17 | public nuint ImageBase; 18 | 19 | /// 20 | /// Image size (size of file on disk, as opposed to OptionalHeaders.SizeOfImage) 21 | /// 22 | public uint ImageSize; 23 | 24 | /// 25 | /// Address of 26 | /// 27 | public nuint CorHeaderAddress; 28 | } 29 | 30 | /// 31 | /// .NET PE Info 32 | /// 33 | public sealed class PEInfo { 34 | /// 35 | /// Determine if current instance is invalid 36 | /// 37 | public bool IsInvalid => LoadedLayout.IsInvalid; 38 | 39 | /// 40 | /// Image file path 41 | /// 42 | public string FilePath = string.Empty; 43 | 44 | /// 45 | /// If image is loaded in memory 46 | /// 47 | public bool InMemory => string.IsNullOrEmpty(FilePath); 48 | 49 | /// 50 | /// Flat image layout, maybe empty (Assembly.Load(byte[])) 51 | /// 52 | public PEImageLayout FlatLayout = new(); 53 | 54 | /// 55 | /// Mapped image layout, maybe empty (Assembly.LoadFile) 56 | /// 57 | public PEImageLayout MappedLayout = new(); 58 | 59 | /// 60 | /// Loaded image layout, not empty (Assembly.LoadFile) 61 | /// 62 | public PEImageLayout LoadedLayout = new(); 63 | 64 | /// 65 | /// Get the .NET PE info of a module 66 | /// 67 | /// 68 | /// 69 | public static PEInfo Create(Module module) { 70 | return PEInfoImpl.GetPEInfo(module); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MetadataLocator.Test/RuntimeDefinitionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using static MetadataLocator.RuntimeDefinitions; 4 | 5 | namespace MetadataLocator.Test; 6 | 7 | static unsafe class RuntimeDefinitionTests { 8 | readonly struct ArchSize { 9 | public readonly ushort X86; 10 | public readonly ushort X64; 11 | 12 | public ArchSize(ushort x86, ushort x64) { 13 | X86 = x86; 14 | X64 = x64; 15 | } 16 | } 17 | 18 | static readonly Dictionary sizeMap = new() { 19 | [typeof(SString)] = new(0x10, 0x18), 20 | [typeof(Crst)] = new(0x1c, 0x30), 21 | [typeof(PEDecoder)] = new(0x18, 0x28), 22 | [typeof(StgPoolReadOnly)] = new(0x18, 0x28), 23 | [typeof(StgBlobPoolReadOnly)] = new(0x18, 0x28), 24 | [typeof(TableRO)] = new(0x04, 0x08), 25 | [typeof(TableROs)] = new(0x00b4, 0x0168), 26 | [typeof(StringHeapRO)] = new(0x18, 0x28), 27 | [typeof(BlobHeapRO)] = new(0x18, 0x28), 28 | [typeof(GuidHeapRO)] = new(0x18, 0x28), 29 | [typeof(CMiniMdSchemaBase)] = new(0x18, 0x18), 30 | [typeof(CMiniMdSchema)] = new(0xd0, 0xd0), 31 | [typeof(CMiniColDef)] = new(0x03, 0x03), 32 | [typeof(CMiniTableDef)] = new(0x08, 0x10), 33 | [typeof(CMiniTableDefs)] = new(0x0168, 0x02d0), 34 | [typeof(CMiniMdBase_20)] = new(0x0250, 0x03c0), 35 | [typeof(CMiniMdBase_40)] = new(0x0258, 0x03c0), 36 | [typeof(CMiniMd_20)] = new(0x0368, 0x05c8), 37 | [typeof(CMiniMd_40)] = new(0x0370, 0x05c8), 38 | [typeof(CLiteWeightStgdb_CMiniMd_20)] = new(0x0370, 0x05d8), 39 | [typeof(CLiteWeightStgdb_CMiniMd_40)] = new(0x0378, 0x05d8), 40 | [typeof(MDInternalRO_20)] = new(0x0378, 0x05e0), 41 | [typeof(MDInternalRO_40)] = new(0x0380, 0x05e0), 42 | [typeof(MDInternalRO_45)] = new(0x0380, 0x05e8), 43 | }; 44 | 45 | public static void VerifySize() { 46 | foreach (var sizeEntry in sizeMap) { 47 | uint actualSize = Utils.SizeOf(sizeEntry.Key); 48 | uint expectedSize = sizeof(nuint) == 4 ? sizeEntry.Value.X86 : sizeEntry.Value.X64; 49 | Assert.IsTrue(actualSize == expectedSize, $"Expected size 0x{expectedSize:X} bytes but got 0x{actualSize:X} bytes for {sizeEntry.Key.Name}"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /MetadataLocator/RuntimeEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace MetadataLocator; 7 | 8 | enum RuntimeFlavor { 9 | /// 10 | /// .NET Framework 1.0 ~ 4.8 11 | /// 12 | Framework, 13 | 14 | /// 15 | /// .NET Core 1.0 ~ 3.1 and .NET 5.0 + 16 | /// 17 | Core 18 | } 19 | 20 | enum RuntimeVersion { 21 | Fx20, 22 | Fx40, 23 | Fx45, 24 | Fx453, 25 | Core10, 26 | Core30, 27 | Core31, 28 | Core50, 29 | Core60, 30 | } 31 | 32 | static class RuntimeEnvironment { 33 | static readonly Version fx45 = new(4, 0, 30319, 17000); 34 | static readonly Version fx453 = new(4, 5, 0, 0); // .NET 4.6 Preview 35 | 36 | public static RuntimeFlavor Flavor { get; } = GetRuntimeFlavor(); 37 | 38 | public static RuntimeVersion Version { get; } = GetRuntimeVersion(); 39 | 40 | static RuntimeFlavor GetRuntimeFlavor() { 41 | switch (typeof(object).Module.ScopeName) { 42 | case "CommonLanguageRuntimeLibrary": return RuntimeFlavor.Framework; 43 | case "System.Private.CoreLib.dll": return RuntimeFlavor.Core; 44 | default: throw new NotSupportedException(); 45 | } 46 | } 47 | 48 | static RuntimeVersion GetRuntimeVersion() { 49 | var version = Environment.Version; 50 | int major = version.Major; 51 | switch (Flavor) { 52 | case RuntimeFlavor.Framework: { 53 | if (major == 4) { 54 | var path = new StringBuilder(MAX_PATH); 55 | if (!GetModuleFileName(GetModuleHandle("clr.dll"), path, MAX_PATH)) 56 | break; 57 | var fileVersion = GetFileVersion(path.ToString()); 58 | if (fileVersion >= fx453) 59 | return RuntimeVersion.Fx453; 60 | if (fileVersion >= fx45) 61 | return RuntimeVersion.Fx45; 62 | return RuntimeVersion.Fx40; 63 | } 64 | else if (major == 2) { 65 | return RuntimeVersion.Fx20; 66 | } 67 | break; 68 | } 69 | case RuntimeFlavor.Core: { 70 | if (major == 4) 71 | return RuntimeVersion.Core10; 72 | // Improve .NET Core version APIs: https://github.com/dotnet/runtime/issues/28701 73 | // Environment.Version works fine since .NET Core v3.0 74 | int minor = version.Minor; 75 | Debug2.Assert(major <= 6, "Update RuntimeDefinitions if need"); 76 | if (major >= 6) 77 | return RuntimeVersion.Core60; 78 | if (major >= 5) 79 | return RuntimeVersion.Core50; 80 | if (major >= 3) { 81 | if (minor >= 1) 82 | return RuntimeVersion.Core31; 83 | return RuntimeVersion.Core30; 84 | } 85 | break; 86 | } 87 | } 88 | Debug2.Assert(false); 89 | throw new NotSupportedException(); 90 | } 91 | 92 | static Version GetFileVersion(string filePath) { 93 | var versionInfo = FileVersionInfo.GetVersionInfo(filePath); 94 | return new Version(versionInfo.FileMajorPart, versionInfo.FileMinorPart, versionInfo.FileBuildPart, versionInfo.FilePrivatePart); 95 | } 96 | 97 | #region NativeMethods 98 | const ushort MAX_PATH = 260; 99 | 100 | [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true)] 101 | static extern nuint GetModuleHandle(string? lpModuleName); 102 | 103 | [DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true)] 104 | [return: MarshalAs(UnmanagedType.Bool)] 105 | static extern bool GetModuleFileName(nuint hModule, StringBuilder lpFilename, uint nSize); 106 | #endregion 107 | } 108 | -------------------------------------------------------------------------------- /MetadataLocator/MetadataInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace MetadataLocator; 5 | 6 | /// 7 | /// Metadata schema 8 | /// 9 | public sealed class MetadataSchema { 10 | /// 11 | /// Determine if current instance is invalid 12 | /// 13 | public bool IsInvalid => ValidMask == 0; 14 | 15 | /// 16 | public uint Reserved1; 17 | 18 | /// 19 | public byte MajorVersion; 20 | 21 | /// 22 | public byte MinorVersion; 23 | 24 | /// 25 | public byte Flags; 26 | 27 | /// 28 | public byte Log2Rid; 29 | 30 | /// 31 | public ulong ValidMask; 32 | 33 | /// 34 | public ulong SortedMask; 35 | 36 | /// 37 | /// Array length always equals to if not empty 38 | public uint[] RowCounts = Array2.Empty(); 39 | 40 | /// 41 | public uint ExtraData; 42 | } 43 | 44 | /// 45 | /// Metadata stream info 46 | /// 47 | public abstract class MetadataStreamInfo { 48 | /// 49 | /// Determine if current instance is invalid 50 | /// 51 | public bool IsInvalid => Address == 0; 52 | 53 | /// 54 | /// Address of stream 55 | /// 56 | public nuint Address; 57 | 58 | /// 59 | /// Length of stream 60 | /// 61 | public uint Length; 62 | } 63 | 64 | /// 65 | /// Metadata table info (#~, #-) 66 | /// 67 | public sealed class MetadataTableInfo : MetadataStreamInfo { 68 | /// 69 | /// Is compressed table stream (#~) 70 | /// 71 | public bool IsCompressed; 72 | 73 | /// 74 | /// Table count, see and 75 | /// 76 | public uint TableCount; 77 | 78 | /// 79 | /// Size of each row 80 | /// 81 | /// Array length always equals to if not empty 82 | public uint[] RowSizes = Array2.Empty(); 83 | } 84 | 85 | /// 86 | /// Metadata heap info (#Strings, #US, #GUID, #Blob) 87 | /// 88 | public sealed class MetadataHeapInfo : MetadataStreamInfo { 89 | } 90 | 91 | /// 92 | /// Metadata info 93 | /// 94 | public sealed class MetadataInfo { 95 | /// 96 | /// Determine if current instance is invalid 97 | /// 98 | public bool IsInvalid => MetadataAddress == 0; 99 | 100 | /// 101 | /// The instance of 102 | /// 103 | public MetadataImport MetadataImport = MetadataImport.Empty; 104 | 105 | /// 106 | /// Address of metadata 107 | /// 108 | public nuint MetadataAddress; 109 | 110 | /// 111 | /// Size of metadata 112 | /// 113 | /// Currently return 0x1c if table stream is uncompressed (aka #-) 114 | public uint MetadataSize; 115 | 116 | /// 117 | /// Metadata schema 118 | /// 119 | public MetadataSchema Schema = new(); 120 | 121 | /// 122 | /// #~ or #- info 123 | /// 124 | public MetadataTableInfo TableStream = new(); 125 | 126 | /// 127 | /// #Strings heap info 128 | /// 129 | public MetadataHeapInfo StringHeap = new(); 130 | 131 | /// 132 | /// #US heap info 133 | /// 134 | public MetadataHeapInfo UserStringHeap = new(); 135 | 136 | /// 137 | /// #GUID heap info 138 | /// 139 | public MetadataHeapInfo GuidHeap = new(); 140 | 141 | /// 142 | /// #Blob heap info 143 | /// 144 | public MetadataHeapInfo BlobHeap = new(); 145 | 146 | /// 147 | /// Get the metadata info of a module 148 | /// 149 | /// 150 | /// 151 | public static MetadataInfo Create(Module module) { 152 | return MetadataInfoImpl.GetMetadataInfo(module); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /MetadataLocator/MetadataImport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using SR = System.Reflection; 4 | 5 | namespace MetadataLocator; 6 | 7 | /// 8 | /// IMDInternalImport function 9 | /// 10 | public enum MetadataImportFunction { 11 | /// 12 | /// IMDInternalImport.GetVersionString 13 | /// 14 | GetVersionString 15 | } 16 | 17 | /// 18 | /// IMDInternalImport info 19 | /// 20 | public sealed unsafe class MetadataImport { 21 | sealed class Profile { 22 | public readonly Guid IID; 23 | public readonly uint GetVersionString; 24 | 25 | public Profile(Guid iid, uint getVersionString) { 26 | IID = iid; 27 | GetVersionString = getVersionString; 28 | } 29 | } 30 | 31 | static readonly SR.MethodInfo getMetadataImport = typeof(ModuleHandle).GetMethod("_GetMetadataImport", SR.BindingFlags.NonPublic | SR.BindingFlags.Instance | SR.BindingFlags.Static); 32 | static readonly Profile[] profiles = { 33 | new(new(0xce0f34ed, 0xbbc6, 0x11d2, 0x94, 0x1e, 0x00, 0x00, 0xf8, 0x08, 0x34, 0x60), 102), // fx20 ~ core31 34 | new(new(0x1b119f60, 0xc507, 0x4024, 0xbb, 0x39, 0xf8, 0x22, 0x3f, 0xb3, 0xe1, 0xfd), 92) // core50+ 35 | }; 36 | static Profile? cachedProfile; 37 | 38 | // IMDInternalImport interface contains breaking change in this commit: https://github.com/dotnet/coreclr/commit/ef7767a3ba1c0a34b55cbd5496b799b17218ca14#diff-1a774ef53fa72817aedde61a45204358e0cd0a76272780558180dc27a2f85cb5L309 39 | // so we must call QueryInterface to judge which version IMDInternalImport we are using 40 | 41 | /// 42 | /// Empty instance 43 | /// 44 | public static readonly MetadataImport Empty = new(0); 45 | 46 | /// 47 | /// Determine if current instance is invalid 48 | /// 49 | public bool IsInvalid => This == 0; 50 | 51 | /// 52 | /// Instance of IMDInternalImport 53 | /// 54 | public nuint This { get; } 55 | 56 | /// 57 | /// Virtual function vtable 58 | /// 59 | public nuint Vfptr => *(nuint*)This; 60 | 61 | MetadataImport(nuint @this) { 62 | This = @this; 63 | } 64 | 65 | /// 66 | /// A wrapper for _GetMetadataImport to get instance of IMDInternalImport interface 67 | /// 68 | /// 69 | /// 70 | public static MetadataImport Create(SR.Module module) { 71 | if (module is null) 72 | throw new ArgumentNullException(nameof(module)); 73 | 74 | nuint pMDImport = getMetadataImport.IsStatic 75 | ? (nuint)(nint)getMetadataImport.Invoke(null, new object[] { module }) 76 | : (nuint)SR.Pointer.Unbox(getMetadataImport.Invoke(module.ModuleHandle, null)); 77 | return pMDImport != 0 ? new MetadataImport(pMDImport) : Empty; 78 | } 79 | 80 | /// 81 | /// Get virtual function address 82 | /// 83 | /// 84 | /// 85 | /// 86 | public nuint GetFunction(MetadataImportFunction function) { 87 | var profile = GetProfile(This); 88 | switch (function) { 89 | case MetadataImportFunction.GetVersionString: 90 | return ((nuint*)Vfptr)[profile.GetVersionString]; 91 | default: 92 | throw new NotSupportedException(); 93 | } 94 | } 95 | 96 | /// 97 | /// Call IMDInternalImport.GetVersionString and return pVersion 98 | /// 99 | /// 100 | public nuint GetVersionString() { 101 | var getVersionString = (delegate* unmanaged[Stdcall])GetFunction(MetadataImportFunction.GetVersionString); 102 | int hr = getVersionString(This, out nuint pVersion); 103 | Debug2.Assert(hr >= 0); 104 | return pVersion; 105 | } 106 | 107 | static Profile GetProfile(nuint pMDImport) { 108 | Debug2.Assert(pMDImport != 0); 109 | if (cachedProfile is not null) 110 | return cachedProfile; 111 | 112 | var vfptr = *(nuint**)pMDImport; 113 | var queryInterface = (delegate* unmanaged[Stdcall])vfptr[0]; 114 | var release = (delegate* unmanaged[Stdcall])vfptr[2]; 115 | foreach (var profile in profiles) { 116 | int hr = queryInterface(pMDImport, profile.IID, out nuint pUnk); 117 | if (hr < 0) 118 | continue; 119 | 120 | Debug2.Assert(pUnk == pMDImport); 121 | release(pMDImport); 122 | cachedProfile = profile; 123 | return profile; 124 | } 125 | 126 | throw new NotSupportedException("Please update IMDInternalImport profiles"); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /MetadataLocator/Memory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Runtime.ExceptionServices; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace MetadataLocator; 9 | 10 | sealed class Pointer { 11 | public static readonly Pointer Empty = new(0, Array2.Empty()); 12 | 13 | public bool IsEmpty => BaseAddress == 0 && Offsets.Count == 0; 14 | 15 | public nuint BaseAddress { get; set; } 16 | 17 | public IList Offsets { get; } 18 | 19 | public Pointer(IEnumerable offsets) { 20 | Offsets = new List(offsets); 21 | } 22 | 23 | public Pointer(nuint baseAddress, IEnumerable offsets) { 24 | BaseAddress = baseAddress; 25 | Offsets = new List(offsets); 26 | } 27 | 28 | public Pointer(Pointer pointer) { 29 | BaseAddress = pointer.BaseAddress; 30 | Offsets = new List(pointer.Offsets); 31 | } 32 | 33 | public void Add(uint offset) { 34 | Offsets.Add(offset); 35 | } 36 | 37 | public void Add(IEnumerable offsets) { 38 | if (offsets is null) 39 | throw new ArgumentNullException(nameof(offsets)); 40 | 41 | foreach (uint offset in offsets) 42 | Offsets.Add(offset); 43 | } 44 | } 45 | 46 | static unsafe class Memory { 47 | [ThreadStatic] 48 | static readonly byte[] stringBuffer = new byte[4096]; 49 | 50 | [HandleProcessCorruptedStateExceptions] 51 | public static bool TryToAddress(Pointer pointer, out nuint address) { 52 | address = 0; 53 | if (pointer is null) 54 | return false; 55 | 56 | try { 57 | address = 0; 58 | nuint newAddress = pointer.BaseAddress; 59 | var offsets = pointer.Offsets; 60 | if (offsets.Count > 0) { 61 | for (int i = 0; i < offsets.Count - 1; i++) { 62 | newAddress += offsets[i]; 63 | if (!TryReadUIntPtr(newAddress, out newAddress)) 64 | return false; 65 | } 66 | newAddress += offsets[offsets.Count - 1]; 67 | } 68 | address = newAddress; 69 | return true; 70 | } 71 | catch { 72 | return false; 73 | } 74 | } 75 | 76 | [HandleProcessCorruptedStateExceptions] 77 | public static bool TryReadUInt32(nuint address, out uint value) { 78 | value = 0; 79 | if (address == 0) 80 | return false; 81 | 82 | if (IsBadReadPtr(address, 4)) 83 | return false; 84 | 85 | try { 86 | value = *(uint*)address; 87 | return true; 88 | } 89 | catch { 90 | return false; 91 | } 92 | } 93 | 94 | [HandleProcessCorruptedStateExceptions] 95 | public static bool TryReadUInt64(nuint address, out ulong value) { 96 | value = 0; 97 | if (address == 0) 98 | return false; 99 | 100 | if (IsBadReadPtr(address, 8)) 101 | return false; 102 | 103 | try { 104 | value = *(ulong*)address; 105 | return true; 106 | } 107 | catch { 108 | return false; 109 | } 110 | } 111 | 112 | [HandleProcessCorruptedStateExceptions] 113 | public static bool TryReadUIntPtr(nuint address, out nuint value) { 114 | value = 0; 115 | if (address == 0) 116 | return false; 117 | 118 | if (IsBadReadPtr(address, (uint)sizeof(nuint))) 119 | return false; 120 | 121 | try { 122 | value = *(nuint*)address; 123 | return true; 124 | } 125 | catch { 126 | value = 0; 127 | return false; 128 | } 129 | } 130 | 131 | [HandleProcessCorruptedStateExceptions] 132 | public static bool TryReadAnsiString(nuint address, [NotNullWhen(true)] out string? value) { 133 | value = null; 134 | if (address == 0) 135 | return false; 136 | 137 | if (IsBadReadPtr(address, 8)) 138 | return false; 139 | 140 | try { 141 | value = new string((sbyte*)address); 142 | return true; 143 | } 144 | catch { 145 | return false; 146 | } 147 | } 148 | 149 | [HandleProcessCorruptedStateExceptions] 150 | public static bool TryReadUnicodeString(nuint address, [NotNullWhen(true)] out string? value) { 151 | value = null; 152 | if (address == 0) 153 | return false; 154 | 155 | if (IsBadReadPtr(address, 8)) 156 | return false; 157 | 158 | try { 159 | value = new string((char*)address); 160 | return true; 161 | } 162 | catch { 163 | return false; 164 | } 165 | } 166 | 167 | [HandleProcessCorruptedStateExceptions] 168 | public static bool TryReadUtf8String(nuint address, [NotNullWhen(true)] out string? value) { 169 | value = null; 170 | if (address == 0) 171 | return false; 172 | 173 | if (IsBadReadPtr(address, 8)) 174 | return false; 175 | 176 | try { 177 | uint i = 0; 178 | for (; *(byte*)(address + i) != 0; i++) 179 | stringBuffer[i] = *(byte*)(address + i); 180 | // TODO: assume no string larger than 4096 bytes 181 | stringBuffer[i] = 0; 182 | value = Encoding.UTF8.GetString(stringBuffer, 0, (int)i); 183 | return true; 184 | } 185 | catch { 186 | return false; 187 | } 188 | } 189 | 190 | [DllImport("kernel32.dll", SetLastError = true)] 191 | static extern bool IsBadReadPtr(nuint lp, nuint ucb); 192 | // TODO: In .NET Core, HandleProcessCorruptedStateExceptionsAttribute is invalid and we can't capture AccessViolationException no longer. 193 | // We should find a better way to check address readable. 194 | } 195 | -------------------------------------------------------------------------------- /MetadataLocator/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | 6 | namespace MetadataLocator; 7 | 8 | static unsafe class Utils { 9 | public static void Check(RuntimeDefinitions.Module* module, string moduleName) { 10 | Check(Memory.TryReadUIntPtr((nuint)module, out _)); 11 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453) { 12 | nuint m_pSimpleName = ((RuntimeDefinitions.Module_453*)module)->m_pSimpleName; 13 | bool b = Memory.TryReadUtf8String(m_pSimpleName, out var simpleName) && simpleName == moduleName; 14 | Check(b); 15 | } 16 | } 17 | 18 | public static void Check(RuntimeDefinitions.PEFile* file) { 19 | Check(Memory.TryReadUIntPtr((nuint)file, out _)); 20 | } 21 | 22 | public static void Check(RuntimeDefinitions.PEImage* image, bool inMemory) { 23 | Check(Memory.TryReadUIntPtr((nuint)image, out _)); 24 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40) { 25 | var m_Path = ((RuntimeDefinitions.PEImage_40*)image)->m_path; 26 | bool b = Memory.TryReadUnicodeString(m_Path.m_buffer, out var path) && (inMemory ? path.Length == 0 : File.Exists(path)); 27 | Check(b); 28 | } 29 | else { 30 | var m_Path = ((RuntimeDefinitions.PEImage_20*)image)->m_path; 31 | bool b = Memory.TryReadUnicodeString(m_Path.m_buffer, out var path) && (inMemory ? path.Length == 0 : File.Exists(path)); 32 | Check(b); 33 | } 34 | } 35 | 36 | public static void Check(RuntimeDefinitions.PEImageLayout* layout, bool inMemory) { 37 | Check(Memory.TryReadUIntPtr((nuint)layout, out _)); 38 | Check((ushort)layout->__base.m_base == 0); 39 | Check(layout->__base.m_pCorHeader - layout->__base.m_base == (inMemory ? 0x208u : 0x2008)); 40 | } 41 | 42 | public static void Check(RuntimeDefinitions.IMAGE_COR20_HEADER* corHeader) { 43 | Check(Memory.TryReadUIntPtr((nuint)corHeader, out _)); 44 | Check(corHeader->cb == 0x48); 45 | Check(corHeader->MajorRuntimeVersion == 2); 46 | Check(corHeader->MinorRuntimeVersion == 5); 47 | } 48 | 49 | public static void Check(RuntimeDefinitions.STORAGESIGNATURE* storageSignature) { 50 | Check(Memory.TryReadUIntPtr((nuint)storageSignature, out _)); 51 | Check(storageSignature->lSignature == RuntimeDefinitions.STORAGE_MAGIC_SIG); 52 | Check(storageSignature->iMajorVer == 1); 53 | Check(storageSignature->iMinorVer == 1); 54 | } 55 | 56 | public static void Check([DoesNotReturnIf(false)] bool condition) { 57 | if (!condition) { 58 | Debug2.Assert(false, "Contains error in RuntimeDefinitions"); 59 | throw new InvalidOperationException("Contains error in RuntimeDefinitions"); 60 | } 61 | } 62 | 63 | public static bool Verify(Pointer template, bool? testUncompressed, Predicate checker) { 64 | for (int i = 0; i < 5; i++) { 65 | for (TestAssemblyFlags inMemory = 0; inMemory <= TestAssemblyFlags.InMemory; inMemory += (int)TestAssemblyFlags.InMemory) { 66 | var uncompressed = testUncompressed == true ? TestAssemblyFlags.Uncompressed : 0; 67 | var uncompressedEnd = testUncompressed == false ? 0 : TestAssemblyFlags.Uncompressed; 68 | for (; uncompressed <= uncompressedEnd; uncompressed += (int)TestAssemblyFlags.Uncompressed) { 69 | var assembly = TestAssemblyManager.GetAssembly((TestAssemblyFlags)i | inMemory | uncompressed); 70 | nuint value = ReadUIntPtr(template, assembly.ModuleHandle); 71 | if (value == 0 || !checker(value)) 72 | return false; 73 | } 74 | } 75 | } 76 | return true; 77 | } 78 | 79 | public static uint ReadUInt32(Pointer template, nuint baseAddress) { 80 | nuint address = ReadPointer(template, baseAddress); 81 | if (!Memory.TryReadUInt32(address, out uint value)) 82 | return default; 83 | return value; 84 | } 85 | 86 | public static nuint ReadUIntPtr(Pointer template, nuint baseAddress) { 87 | nuint address = ReadPointer(template, baseAddress); 88 | if (!Memory.TryReadUIntPtr(address, out nuint value)) 89 | return default; 90 | return value; 91 | } 92 | 93 | public static string ReadUnicodeString(Pointer template, nuint baseAddress) { 94 | nuint address = ReadPointer(template, baseAddress); 95 | if (!Memory.TryReadUnicodeString(address, out var value)) 96 | return string.Empty; 97 | return value; 98 | } 99 | 100 | public static nuint ReadPointer(Pointer template, nuint baseAddress) { 101 | if (template.IsEmpty) 102 | return default; 103 | var pointer = MakePointer(template, baseAddress); 104 | if (!Memory.TryToAddress(pointer, out nuint address)) 105 | return default; 106 | return address; 107 | } 108 | 109 | public static Pointer MakePointer(Pointer template, nuint baseAddress) { 110 | Debug2.Assert(!template.IsEmpty); 111 | return new Pointer(template) { BaseAddress = baseAddress }; 112 | } 113 | 114 | public static Pointer[] WithOffset(Pointer basePointer, uint[] offsets) { 115 | var pointers = new Pointer[offsets.Length]; 116 | for (int i = 0; i < pointers.Length; i++) { 117 | var pointer = new Pointer(basePointer); 118 | pointer.Add(offsets[i]); 119 | pointers[i] = pointer; 120 | } 121 | return pointers; 122 | } 123 | 124 | public static Pointer WithOffset(Pointer basePointer, uint offset) { 125 | var pointer = new Pointer(basePointer); 126 | pointer.Add(offset); 127 | return pointer; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /MetadataLocator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator", "MetadataLocator\MetadataLocator.csproj", "{AED69D77-8C7B-41BA-9967-AC9B522430F9}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test", "MetadataLocator.Test\MetadataLocator.Test.csproj", "{BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR20.x86", "MetadataLocator.Test.CLR20.x86\MetadataLocator.Test.CLR20.x86.csproj", "{7368F4FE-ED71-433B-A525-F20EA7FA488D}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR20.x64", "MetadataLocator.Test.CLR20.x64\MetadataLocator.Test.CLR20.x64.csproj", "{B925862F-E6BB-4F18-B560-B19153AB97D7}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR40.x86", "MetadataLocator.Test.CLR40.x86\MetadataLocator.Test.CLR40.x86.csproj", "{7BA7644F-B706-415E-AE0E-CC5FC6247021}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CLR40.x64", "MetadataLocator.Test.CLR40.x64\MetadataLocator.Test.CLR40.x64.csproj", "{195944D9-ABA2-44CE-9F7D-AE432D114BD1}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR31.x86", "MetadataLocator.Test.CoreCLR31.x86\MetadataLocator.Test.CoreCLR31.x86.csproj", "{104612D5-3CBB-4235-914C-88C5B4736D16}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR31.x64", "MetadataLocator.Test.CoreCLR31.x64\MetadataLocator.Test.CoreCLR31.x64.csproj", "{EE92CDA2-49A8-473B-AC22-81F5605B9B80}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR60.x86", "MetadataLocator.Test.CoreCLR60.x86\MetadataLocator.Test.CoreCLR60.x86.csproj", "{BCC1A39F-DB7B-4949-97C6-195E2E4B2526}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetadataLocator.Test.CoreCLR60.x64", "MetadataLocator.Test.CoreCLR60.x64\MetadataLocator.Test.CoreCLR60.x64.csproj", "{93F97993-AF94-4E41-959E-E3154DC8B179}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {AED69D77-8C7B-41BA-9967-AC9B522430F9}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {BDFD2EFD-B6B7-4783-97A1-B8D1493E171C}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {7368F4FE-ED71-433B-A525-F20EA7FA488D}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {B925862F-E6BB-4F18-B560-B19153AB97D7}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {7BA7644F-B706-415E-AE0E-CC5FC6247021}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {195944D9-ABA2-44CE-9F7D-AE432D114BD1}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {104612D5-3CBB-4235-914C-88C5B4736D16}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {EE92CDA2-49A8-473B-AC22-81F5605B9B80}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {BCC1A39F-DB7B-4949-97C6-195E2E4B2526}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {93F97993-AF94-4E41-959E-E3154DC8B179}.Release|Any CPU.Build.0 = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | GlobalSection(ExtensibilityGlobals) = postSolution 77 | SolutionGuid = {638DD4A8-E582-4EF9-B751-DF759C0C80FC} 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /MetadataLocator.Test/TestDriver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace MetadataLocator.Test; 6 | 7 | public static unsafe class TestDriver { 8 | static int indent; 9 | 10 | public static void Test() { 11 | RuntimeDefinitionTests.VerifySize(); 12 | MetadataImportTests.Test(); 13 | Print(Assembly.GetEntryAssembly().ManifestModule, true); 14 | Print(typeof(MetadataInfo).Module, true); 15 | //RunTestAssemblys(5); 16 | Console.ReadKey(true); 17 | } 18 | 19 | static void RunTestAssemblys(int count) { 20 | for (int i = 0; i < count; i++) { 21 | for (TestAssemblyFlags inMemory = 0; inMemory <= TestAssemblyFlags.InMemory; inMemory += (int)TestAssemblyFlags.InMemory) { 22 | for (TestAssemblyFlags uncompressed = 0; uncompressed <= TestAssemblyFlags.Uncompressed; uncompressed += (int)TestAssemblyFlags.Uncompressed) { 23 | var assembly = TestAssemblyManager.GetAssembly((TestAssemblyFlags)i | inMemory | uncompressed); 24 | Print(assembly.Module, true); 25 | } 26 | } 27 | } 28 | } 29 | 30 | static void Print(Module module, bool end = false) { 31 | Print($"{module.Assembly.GetName().Name}: {{"); 32 | indent++; 33 | Print(nameof(PEInfo), PEInfo.Create(module)); 34 | Print(nameof(MetadataInfo), MetadataInfo.Create(module), true); 35 | indent--; 36 | Print(end ? "}" : "},"); 37 | } 38 | 39 | static void Print(string name, PEInfo peInfo, bool end = false) { 40 | if (peInfo.IsInvalid) { 41 | Print(end ? $"{name}: null" : $"{name}: null,"); 42 | return; 43 | } 44 | 45 | Print($"{name}: {{"); 46 | indent++; 47 | Print($"{nameof(PEInfo.FilePath)}: {peInfo.FilePath},"); 48 | Print($"{nameof(PEInfo.InMemory)}: {peInfo.InMemory},"); 49 | Print(nameof(PEInfo.FlatLayout), peInfo.FlatLayout); 50 | Print(nameof(PEInfo.MappedLayout), peInfo.MappedLayout); 51 | Print(nameof(PEInfo.LoadedLayout), peInfo.LoadedLayout, true); 52 | indent--; 53 | Print(end ? "}" : "},"); 54 | } 55 | 56 | static void Print(string name, PEImageLayout imageLayout, bool end = false) { 57 | if (imageLayout.IsInvalid) { 58 | Print(end ? $"{name}: null" : $"{name}: null,"); 59 | return; 60 | } 61 | 62 | Print($"{name}: {{"); 63 | indent++; 64 | Print($"{nameof(PEImageLayout.ImageBase)}: {FormatHex(imageLayout.ImageBase)},"); 65 | Print($"{nameof(PEImageLayout.ImageSize)}: {FormatHex(imageLayout.ImageSize)},"); 66 | Print($"{nameof(PEImageLayout.CorHeaderAddress)}: {FormatHex(imageLayout.CorHeaderAddress)}"); 67 | indent--; 68 | Print(end ? "}" : "},"); 69 | } 70 | 71 | static void Print(string name, MetadataInfo metadataInfo, bool end = false) { 72 | if (metadataInfo.IsInvalid) { 73 | Print(end ? $"{name}: null" : $"{name}: null,"); 74 | return; 75 | } 76 | 77 | Print($"{name}: {{"); 78 | indent++; 79 | Print($"{nameof(MetadataInfo.MetadataAddress)}: {FormatHex(metadataInfo.MetadataAddress)},"); 80 | Print($"{nameof(MetadataInfo.MetadataSize)}: {FormatHex(metadataInfo.MetadataSize)},"); 81 | Print(nameof(MetadataInfo.Schema), metadataInfo.Schema); 82 | Print(nameof(MetadataInfo.TableStream), metadataInfo.TableStream); 83 | Print(nameof(MetadataInfo.StringHeap), metadataInfo.StringHeap); 84 | Print(nameof(MetadataInfo.UserStringHeap), metadataInfo.UserStringHeap); 85 | Print(nameof(MetadataInfo.GuidHeap), metadataInfo.GuidHeap); 86 | Print(nameof(MetadataInfo.BlobHeap), metadataInfo.BlobHeap, true); 87 | indent--; 88 | Print(end ? "}" : "},"); 89 | } 90 | 91 | static void Print(string name, MetadataSchema schema, bool end = false) { 92 | if (schema.IsInvalid) { 93 | Print(end ? $"{name}: null" : $"{name}: null,"); 94 | return; 95 | } 96 | 97 | Print($"{name}: {{"); 98 | indent++; 99 | Print($"{nameof(MetadataSchema.Reserved1)}: {schema.Reserved1},"); 100 | Print($"{nameof(MetadataSchema.MajorVersion)}: {schema.MajorVersion},"); 101 | Print($"{nameof(MetadataSchema.MinorVersion)}: {schema.MinorVersion},"); 102 | Print($"{nameof(MetadataSchema.Flags)}: {schema.Flags},"); 103 | Print($"{nameof(MetadataSchema.Log2Rid)}: {schema.Log2Rid},"); 104 | Print($"{nameof(MetadataSchema.ValidMask)}: {FormatHex(schema.ValidMask)},"); 105 | Print($"{nameof(MetadataSchema.SortedMask)}: {FormatHex(schema.SortedMask)},"); 106 | Print($"{nameof(MetadataSchema.RowCounts)}: \"{string.Join(",", schema.RowCounts.Select(t => $"0x{t:X}").ToArray())}\","); 107 | Print($"{nameof(MetadataSchema.ExtraData)}: {schema.ExtraData}"); 108 | indent--; 109 | Print(end ? "}" : "},"); 110 | } 111 | 112 | static void Print(string name, MetadataTableInfo tableInfo, bool end = false) { 113 | if (tableInfo.IsInvalid) { 114 | Print(end ? $"{name}: null" : $"{name}: null,"); 115 | return; 116 | } 117 | 118 | Print($"{name}: {{"); 119 | indent++; 120 | Print($"{nameof(MetadataTableInfo.Address)}: {FormatHex(tableInfo.Address)},"); 121 | Print($"{nameof(MetadataTableInfo.Length)}: {FormatHex(tableInfo.Length)},"); 122 | Print($"{nameof(MetadataTableInfo.IsCompressed)}: {tableInfo.IsCompressed}"); 123 | Print($"{nameof(MetadataTableInfo.TableCount)}: {tableInfo.TableCount},"); 124 | Print($"{nameof(MetadataTableInfo.RowSizes)}: \"{string.Join(",", tableInfo.RowSizes.Select(t => $"0x{t:X}").ToArray())}\""); 125 | indent--; 126 | Print(end ? "}" : "},"); 127 | } 128 | 129 | static void Print(string name, MetadataHeapInfo heapInfo, bool end = false) { 130 | if (heapInfo.IsInvalid) { 131 | Print($"{name}: null,"); 132 | return; 133 | } 134 | 135 | Print($"{name}: {{"); 136 | indent++; 137 | Print($"{nameof(MetadataStreamInfo.Address)}: {FormatHex(heapInfo.Address)},"); 138 | Print($"{nameof(MetadataStreamInfo.Length)}: {FormatHex(heapInfo.Length)}"); 139 | indent--; 140 | Print(end ? "}" : "},"); 141 | } 142 | 143 | static void Print(string value) { 144 | Console.WriteLine(new string(' ', indent * 2) + value); 145 | } 146 | 147 | static string FormatHex(uint value) { 148 | return $"0x{value:X8}"; 149 | } 150 | 151 | static string FormatHex(ulong value) { 152 | return $"0x{value:X16}"; 153 | } 154 | 155 | static string FormatHex(nuint value) { 156 | return sizeof(nuint) == 4 ? FormatHex((uint)value) : FormatHex((ulong)value); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | # wwh1004 366 | launchSettings.json -------------------------------------------------------------------------------- /MetadataLocator/PEInfoImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using static MetadataLocator.RuntimeDefinitions; 5 | using SR = System.Reflection; 6 | 7 | namespace MetadataLocator; 8 | 9 | static unsafe class PEInfoImpl { 10 | static Pointer filePathPointer = Pointer.Empty; 11 | static Pointer flatImageLayoutPointer = Pointer.Empty; 12 | static Pointer mappedImageLayoutPointer = Pointer.Empty; 13 | static Pointer loadedImageLayoutPointer = Pointer.Empty; 14 | static bool isInitialized; 15 | 16 | static void Initialize() { 17 | if (isInitialized) 18 | return; 19 | 20 | filePathPointer = ScanFilePathPointer(); 21 | Debug2.Assert(!filePathPointer.IsEmpty); 22 | loadedImageLayoutPointer = ScanLoadedImageLayoutPointer(out bool isMappedLayoutExisting); 23 | Debug2.Assert(!loadedImageLayoutPointer.IsEmpty); 24 | if (isMappedLayoutExisting) { 25 | var t = new Pointer(loadedImageLayoutPointer); 26 | t.Offsets[t.Offsets.Count - 1] -= (uint)sizeof(nuint); 27 | mappedImageLayoutPointer = t; 28 | t = new Pointer(t); 29 | t.Offsets[t.Offsets.Count - 1] -= (uint)sizeof(nuint); 30 | flatImageLayoutPointer = t; 31 | } 32 | else { 33 | var t = new Pointer(loadedImageLayoutPointer); 34 | t.Offsets[t.Offsets.Count - 1] -= (uint)sizeof(nuint); 35 | flatImageLayoutPointer = t; 36 | } 37 | isInitialized = true; 38 | } 39 | 40 | static Pointer ScanFilePathPointer() { 41 | const bool InMemory = false; 42 | 43 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 44 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 45 | nuint module = assembly.ModuleHandle; 46 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 47 | // Get native Module object 48 | 49 | uint m_file_Offset; 50 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453) 51 | m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy); 52 | else 53 | m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy); 54 | nuint m_file = *(nuint*)(module + m_file_Offset); 55 | Utils.Check((PEFile*)m_file); 56 | // Module.m_file 57 | 58 | uint m_openedILimage_Offset = (uint)((nuint)(&PEFile.Dummy->m_openedILimage) - (nuint)PEFile.Dummy); 59 | nuint m_openedILimage = *(nuint*)(m_file + m_openedILimage_Offset); 60 | Utils.Check((PEImage*)m_openedILimage, InMemory); 61 | // PEFile.m_openedILimage 62 | 63 | uint m_path_m_buffer_Offset = 0; 64 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40) 65 | m_path_m_buffer_Offset = (uint)((nuint)(&PEImage_40.Dummy->m_path.m_buffer) - (nuint)PEImage_40.Dummy); 66 | else 67 | m_path_m_buffer_Offset = (uint)((nuint)(&PEImage_20.Dummy->m_path.m_buffer) - (nuint)PEImage_20.Dummy); 68 | nuint m_path_m_buffer = *(nuint*)(m_openedILimage + m_path_m_buffer_Offset); 69 | Utils.Check(Memory.TryReadUnicodeString(m_path_m_buffer, out var path) && File.Exists(path)); 70 | // PEImage.m_path.m_buffer 71 | 72 | var pointer = new Pointer(new[] { 73 | m_file_Offset, 74 | m_openedILimage_Offset, 75 | m_path_m_buffer_Offset 76 | }); 77 | Utils.Check(Utils.Verify(pointer, null, p => Memory.TryReadUnicodeString(p, out var path) && (path.Length == 0 || File.Exists(path)))); 78 | pointer.Add(0); 79 | return pointer; 80 | } 81 | 82 | static Pointer ScanLoadedImageLayoutPointer(out bool isMappedLayoutExisting) { 83 | const bool InMemory = true; 84 | 85 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 86 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 87 | nuint module = assembly.ModuleHandle; 88 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 89 | // Get native Module object 90 | 91 | uint m_file_Offset; 92 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453) 93 | m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy); 94 | else 95 | m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy); 96 | nuint m_file = *(nuint*)(module + m_file_Offset); 97 | Utils.Check((PEFile*)m_file); 98 | // Module.m_file 99 | 100 | uint m_openedILimage_Offset = (uint)((nuint)(&PEFile.Dummy->m_openedILimage) - (nuint)PEFile.Dummy); 101 | nuint m_openedILimage = *(nuint*)(m_file + m_openedILimage_Offset); 102 | Utils.Check((PEImage*)m_openedILimage, InMemory); 103 | // PEFile.m_openedILimage 104 | 105 | nuint m_pMDImport = MetadataImport.Create(assembly.Module).This; 106 | uint m_pMDImport_Offset; 107 | bool found = false; 108 | for (m_pMDImport_Offset = 0x40; m_pMDImport_Offset < 0xD0; m_pMDImport_Offset += 4) { 109 | if (*(nuint*)(m_openedILimage + m_pMDImport_Offset) != m_pMDImport) 110 | continue; 111 | found = true; 112 | break; 113 | } 114 | Utils.Check(found); 115 | // PEFile.m_pMDImport (not use, just for locating previous member 'm_pLayouts') 116 | isMappedLayoutExisting = false; 117 | uint m_pLayouts_Loaded_Offset = m_pMDImport_Offset - 4 - (uint)sizeof(nuint); 118 | uint m_pLayouts_Offset_Min = m_pLayouts_Loaded_Offset - (4 * (uint)sizeof(nuint)); 119 | nuint actualModuleBase = ReflectionHelpers.GetNativeModuleHandle(assembly.Module); 120 | found = false; 121 | for (; m_pLayouts_Loaded_Offset >= m_pLayouts_Offset_Min; m_pLayouts_Loaded_Offset -= 4) { 122 | var m_pLayout = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset); 123 | if (!Memory.TryReadUIntPtr((nuint)m_pLayout, out _)) 124 | continue; 125 | if (!Memory.TryReadUIntPtr(m_pLayout->__vfptr, out _)) 126 | continue; 127 | if (actualModuleBase != m_pLayout->__base.m_base) 128 | continue; 129 | Debug2.Assert(InMemory); 130 | var m_pLayout_prev1 = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset - (uint)sizeof(nuint)); 131 | var m_pLayout_prev2 = *(RuntimeDefinitions.PEImageLayout**)(m_openedILimage + m_pLayouts_Loaded_Offset - (2 * (uint)sizeof(nuint))); 132 | if (m_pLayout_prev2 == m_pLayout) 133 | isMappedLayoutExisting = true; 134 | else if (m_pLayout_prev1 == m_pLayout) 135 | isMappedLayoutExisting = false; // latest .NET, TODO: update comment when .NET 7.0 released 136 | found = true; 137 | break; 138 | } 139 | Utils.Check(found); 140 | nuint m_pLayouts_Loaded = *(nuint*)(m_openedILimage + m_pLayouts_Loaded_Offset); 141 | Utils.Check((RuntimeDefinitions.PEImageLayout*)m_pLayouts_Loaded, InMemory); 142 | // PEImage.m_pLayouts[IMAGE_LOADED] 143 | 144 | uint m_pCorHeader_Offset = (uint)((nuint)(&RuntimeDefinitions.PEImageLayout.Dummy->__base.m_pCorHeader) - (nuint)RuntimeDefinitions.PEImageLayout.Dummy); 145 | nuint m_pCorHeader = *(nuint*)(m_pLayouts_Loaded + m_pCorHeader_Offset); 146 | Utils.Check((IMAGE_COR20_HEADER*)m_pCorHeader); 147 | // PEImageLayout.m_pCorHeader 148 | 149 | var pointer = new Pointer(new[] { 150 | m_file_Offset, 151 | m_openedILimage_Offset, 152 | m_pLayouts_Loaded_Offset 153 | }); 154 | Utils.Check(Utils.Verify(pointer, null, p => Memory.TryReadUIntPtr(p + (uint)sizeof(nuint), out nuint @base) && (ushort)@base == 0)); 155 | Utils.Check(Utils.Verify(Utils.WithOffset(pointer, m_pCorHeader_Offset), null, p => Memory.TryReadUInt32(p, out uint cb) && cb == 0x48)); 156 | return pointer; 157 | } 158 | 159 | public static PEInfo GetPEInfo(SR.Module module) { 160 | if (module is null) 161 | throw new ArgumentNullException(nameof(module)); 162 | 163 | Initialize(); 164 | nuint moduleHandle = ReflectionHelpers.GetModuleHandle(module); 165 | var peInfo = new PEInfo { 166 | FilePath = Utils.ReadUnicodeString(filePathPointer, moduleHandle), 167 | FlatLayout = GetImageLayout(Utils.ReadUIntPtr(flatImageLayoutPointer, moduleHandle)), 168 | MappedLayout = GetImageLayout(Utils.ReadUIntPtr(mappedImageLayoutPointer, moduleHandle)), 169 | LoadedLayout = GetImageLayout(Utils.ReadUIntPtr(loadedImageLayoutPointer, moduleHandle)) 170 | }; 171 | return peInfo; 172 | } 173 | 174 | static PEImageLayout GetImageLayout(nuint pImageLayout) { 175 | if (pImageLayout == 0) 176 | return new(); 177 | 178 | var pLayout = (RuntimeDefinitions.PEImageLayout*)pImageLayout; 179 | var layout = new PEImageLayout { 180 | ImageBase = pLayout->__base.m_base, 181 | ImageSize = pLayout->__base.m_size, 182 | CorHeaderAddress = pLayout->__base.m_pCorHeader 183 | }; 184 | return layout; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = crlf 7 | indent_size = 4 8 | indent_style = tab 9 | insert_final_newline = true 10 | tab_width = 4 11 | trim_trailing_whitespace = true 12 | 13 | # C# files 14 | [*.cs] 15 | 16 | #### .NET Coding Conventions #### 17 | 18 | # Organize usings 19 | dotnet_separate_import_directive_groups = false 20 | dotnet_sort_system_directives_first = true 21 | 22 | # this. and Me. preferences 23 | dotnet_style_qualification_for_event = false:suggestion 24 | dotnet_style_qualification_for_field = false:suggestion 25 | dotnet_style_qualification_for_method = false:suggestion 26 | dotnet_style_qualification_for_property = false:suggestion 27 | 28 | # Language keywords vs BCL types preferences 29 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 30 | dotnet_style_predefined_type_for_member_access = true:suggestion 31 | 32 | # Parentheses preferences 33 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion 34 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion 35 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion 36 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion 37 | 38 | # Modifier preferences 39 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 40 | 41 | # Expression-level preferences 42 | dotnet_style_coalesce_expression = true:suggestion 43 | dotnet_style_collection_initializer = true:suggestion 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_namespace_match_folder = true:suggestion 46 | dotnet_style_null_propagation = true:suggestion 47 | dotnet_style_object_initializer = true:suggestion 48 | dotnet_style_operator_placement_when_wrapping = end_of_line 49 | dotnet_style_prefer_auto_properties = false:suggestion 50 | dotnet_style_prefer_compound_assignment = true:suggestion 51 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 52 | dotnet_style_prefer_conditional_expression_over_return = true:silent 53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 54 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 55 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 56 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 57 | dotnet_style_prefer_simplified_interpolation = true:suggestion 58 | 59 | # Field preferences 60 | dotnet_style_readonly_field = true:suggestion 61 | 62 | # Parameter preferences 63 | dotnet_code_quality_unused_parameters = all:suggestion 64 | 65 | # Suppression preferences 66 | dotnet_remove_unnecessary_suppression_exclusions = none 67 | 68 | # New line preferences 69 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 70 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 71 | 72 | #### C# Coding Conventions #### 73 | 74 | # var preferences 75 | csharp_style_var_elsewhere = true:suggestion 76 | csharp_style_var_for_built_in_types = false:silent 77 | csharp_style_var_when_type_is_apparent = true:suggestion 78 | 79 | # Expression-bodied members 80 | csharp_style_expression_bodied_accessors = true:suggestion 81 | csharp_style_expression_bodied_constructors = false:suggestion 82 | csharp_style_expression_bodied_indexers = true:suggestion 83 | csharp_style_expression_bodied_lambdas = true:suggestion 84 | csharp_style_expression_bodied_local_functions = false:suggestion 85 | csharp_style_expression_bodied_methods = false:suggestion 86 | csharp_style_expression_bodied_operators = false:suggestion 87 | csharp_style_expression_bodied_properties = true:suggestion 88 | 89 | # Pattern matching preferences 90 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 91 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 92 | csharp_style_prefer_not_pattern = true:suggestion 93 | csharp_style_prefer_pattern_matching = true:silent 94 | csharp_style_prefer_switch_expression = false:suggestion 95 | 96 | # Null-checking preferences 97 | csharp_style_conditional_delegate_call = true:suggestion 98 | 99 | # Modifier preferences 100 | csharp_prefer_static_local_function = true:suggestion 101 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent 102 | 103 | # Code-block preferences 104 | csharp_prefer_braces = false:silent 105 | csharp_prefer_simple_using_statement = true:suggestion 106 | csharp_style_namespace_declarations = file_scoped:suggestion 107 | 108 | # Expression-level preferences 109 | csharp_prefer_simple_default_expression = true:suggestion 110 | csharp_style_deconstructed_variable_declaration = true:suggestion 111 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 112 | csharp_style_inlined_variable_declaration = true:suggestion 113 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 114 | csharp_style_prefer_index_operator = true:suggestion 115 | csharp_style_prefer_null_check_over_type_check = true:suggestion 116 | csharp_style_prefer_range_operator = true:suggestion 117 | csharp_style_throw_expression = true:suggestion 118 | csharp_style_unused_value_assignment_preference = unused_local_variable:silent 119 | csharp_style_unused_value_expression_statement_preference = unused_local_variable:silent 120 | 121 | # 'using' directive preferences 122 | csharp_using_directive_placement = outside_namespace:suggestion 123 | 124 | # New line preferences 125 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 126 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 127 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 128 | 129 | #### C# Formatting Rules #### 130 | 131 | # New line preferences 132 | csharp_new_line_before_catch = true 133 | csharp_new_line_before_else = true 134 | csharp_new_line_before_finally = true 135 | csharp_new_line_before_members_in_anonymous_types = true 136 | csharp_new_line_before_members_in_object_initializers = true 137 | csharp_new_line_before_open_brace = none 138 | csharp_new_line_between_query_expression_clauses = false 139 | 140 | # Indentation preferences 141 | csharp_indent_block_contents = true 142 | csharp_indent_braces = false 143 | csharp_indent_case_contents = true 144 | csharp_indent_case_contents_when_block = false 145 | csharp_indent_labels = one_less_than_current 146 | csharp_indent_switch_labels = false 147 | 148 | # Space preferences 149 | csharp_space_after_cast = false 150 | csharp_space_after_colon_in_inheritance_clause = true 151 | csharp_space_after_comma = true 152 | csharp_space_after_dot = false 153 | csharp_space_after_keywords_in_control_flow_statements = true 154 | csharp_space_after_semicolon_in_for_statement = true 155 | csharp_space_around_binary_operators = before_and_after 156 | csharp_space_around_declaration_statements = false 157 | csharp_space_before_colon_in_inheritance_clause = true 158 | csharp_space_before_comma = false 159 | csharp_space_before_dot = false 160 | csharp_space_before_open_square_brackets = false 161 | csharp_space_before_semicolon_in_for_statement = false 162 | csharp_space_between_empty_square_brackets = false 163 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 164 | csharp_space_between_method_call_name_and_opening_parenthesis = false 165 | csharp_space_between_method_call_parameter_list_parentheses = false 166 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 167 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 168 | csharp_space_between_method_declaration_parameter_list_parentheses = false 169 | csharp_space_between_parentheses = false 170 | csharp_space_between_square_brackets = false 171 | 172 | # Wrapping preferences 173 | csharp_preserve_single_line_blocks = true 174 | csharp_preserve_single_line_statements = true 175 | 176 | #### Naming styles #### 177 | 178 | # Naming rules 179 | 180 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 181 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 182 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 183 | 184 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 185 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 186 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 187 | 188 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 189 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 190 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 191 | 192 | # Symbol specifications 193 | 194 | dotnet_naming_symbols.interface.applicable_kinds = interface 195 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 196 | dotnet_naming_symbols.interface.required_modifiers = 197 | 198 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 199 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 200 | dotnet_naming_symbols.types.required_modifiers = 201 | 202 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 203 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 204 | dotnet_naming_symbols.non_field_members.required_modifiers = 205 | 206 | # Naming styles 207 | 208 | dotnet_naming_style.pascal_case.required_prefix = 209 | dotnet_naming_style.pascal_case.required_suffix = 210 | dotnet_naming_style.pascal_case.word_separator = 211 | dotnet_naming_style.pascal_case.capitalization = pascal_case 212 | 213 | dotnet_naming_style.begins_with_i.required_prefix = I 214 | dotnet_naming_style.begins_with_i.required_suffix = 215 | dotnet_naming_style.begins_with_i.word_separator = 216 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 217 | -------------------------------------------------------------------------------- /MetadataLocator/TestAssemblyManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | namespace MetadataLocator; 8 | 9 | /// 10 | /// Test assembly generation options 11 | /// 12 | [Flags] 13 | enum TestAssemblyFlags { 14 | /// 15 | /// The number of assembly 16 | /// 17 | IndexMask = 0xFF, 18 | 19 | /// 20 | /// Load it from memory 21 | /// 22 | InMemory = 0x100, 23 | 24 | /// 25 | /// Use #- table stream 26 | /// 27 | Uncompressed = 0x200 28 | } 29 | 30 | /// 31 | /// Assembly for test 32 | /// 33 | sealed class TestAssembly { 34 | public TestAssemblyFlags Flags { get; } 35 | 36 | public int Index => (int)(Flags & TestAssemblyFlags.IndexMask); 37 | 38 | public bool InMemory => (Flags & TestAssemblyFlags.InMemory) != 0; 39 | 40 | public bool Uncompressed => (Flags & TestAssemblyFlags.Uncompressed) != 0; 41 | 42 | public Assembly Assembly { get; } 43 | 44 | public Module Module { get; } 45 | 46 | public nuint ModuleHandle { get; } 47 | 48 | public TestAssembly(TestAssemblyFlags flags, Assembly assembly) { 49 | Flags = flags; 50 | Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); 51 | Module = assembly.ManifestModule; 52 | ModuleHandle = ReflectionHelpers.GetModuleHandle(assembly.ManifestModule); 53 | } 54 | } 55 | 56 | /// 57 | /// Manager of 58 | /// 59 | sealed class TestAssemblyManager { 60 | const int CACHE_MAGIC = 2; 61 | // Update it if old cache format is invalid 62 | 63 | static readonly Dictionary assemblies = new(); 64 | 65 | /// 66 | /// Get a test assembly 67 | /// 68 | /// 69 | /// 70 | public static TestAssembly GetAssembly(TestAssemblyFlags flags) { 71 | if (!assemblies.TryGetValue(flags, out var testAssembly)) { 72 | string path = GetOrCreateAssembly(flags); 73 | bool inMemory = (flags & TestAssemblyFlags.InMemory) != 0; 74 | var assembly = inMemory ? Assembly.Load(File.ReadAllBytes(path)) : Assembly.LoadFile(path); 75 | testAssembly = new TestAssembly(flags, assembly); 76 | assemblies.Add(flags, testAssembly); 77 | } 78 | return testAssembly; 79 | } 80 | 81 | static string GetOrCreateAssembly(TestAssemblyFlags flags) { 82 | var tempDirectory = Path.Combine(Path.GetTempPath(), $"MetadataLocator_{CACHE_MAGIC:X2}_{RuntimeEnvironment.Version}"); 83 | if (!Directory.Exists(tempDirectory)) 84 | Directory.CreateDirectory(tempDirectory); 85 | string cachePath = Path.Combine(tempDirectory, $"{(uint)flags:X8}.dll"); 86 | 87 | if (File.Exists(cachePath)) 88 | return cachePath; 89 | 90 | var rawAssembly = Decompress(data); 91 | 92 | ulong a = 0x5F5F5F5F5F5F5F5F; // ________ 93 | ulong b = FlagsToAscii(flags); // 00000000 94 | int c = ReplaceAll(rawAssembly, a, b); 95 | Debug2.Assert(c == 2); 96 | if ((flags & TestAssemblyFlags.Uncompressed) != 0) 97 | ConvertToUncompressed(rawAssembly); 98 | File.WriteAllBytes(cachePath, rawAssembly); 99 | return cachePath; 100 | } 101 | 102 | static unsafe ulong FlagsToAscii(TestAssemblyFlags flags) { 103 | var s = $"{(uint)flags:X8}"; 104 | ulong n = 0; 105 | for (int i = 0; i < 8; i++) 106 | n |= (ulong)s[i] << (i * 8); 107 | return n; 108 | } 109 | 110 | static unsafe int ReplaceAll(byte[] data, ulong a, ulong b) { 111 | fixed (byte* p = data) { 112 | int count = 0; 113 | int length = data.Length - 7; 114 | for (int i = 0; i < length; i++) { 115 | if (*(ulong*)(p + i) == a) { 116 | *(ulong*)(p + i) = b; 117 | count++; 118 | i += 7; 119 | } 120 | } 121 | return count; 122 | } 123 | } 124 | 125 | static unsafe void ConvertToUncompressed(byte[] data) { 126 | fixed (byte* p = data) { 127 | int i = 0; 128 | int length = data.Length - 3; 129 | for (; i < length; i++) { 130 | if (*(uint*)(p + i) == 0x424A5342) 131 | break; 132 | } 133 | Debug2.Assert(i != data.Length); 134 | length = data.Length - 1; 135 | for (; i < length; i++) { 136 | if (*(ushort*)(p + i) == 0x7E23) 137 | break; 138 | } 139 | Debug2.Assert(i != data.Length); 140 | *(ushort*)(p + i) = 0x2D23; 141 | } 142 | } 143 | 144 | static byte[] Decompress(byte[] data) { 145 | var buffer = new List(data.Length * 2); 146 | for (int i = 0; i < data.Length; i++) { 147 | if (data[i] == 0xFF) { 148 | ushort count = (ushort)(data[i + 1] | (data[i + 2] << 8)); 149 | if (count != 0xFFFF) { 150 | Debug2.Assert(count > 3); 151 | while (count-- > 0) 152 | buffer.Add(0x00); 153 | } 154 | else { 155 | buffer.Add(0xFF); 156 | } 157 | i += 2; 158 | } 159 | else { 160 | buffer.Add(data[i]); 161 | } 162 | } 163 | return buffer.ToArray(); 164 | } 165 | 166 | static readonly byte[] data = new byte[] { 167 | 0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 168 | 0xFF, 0xFF, 0x00, 0x00, 0xB8, 0xFF, 0x07, 0x00, 0x40, 0xFF, 0x23, 0x00, 0x80, 0x00, 0x00, 0x00, 169 | 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68, 170 | 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F, 171 | 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20, 172 | 0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A, 0x24, 0xFF, 0x07, 0x00, 0x50, 0x45, 0x00, 0x00, 173 | 0x4C, 0x01, 0x02, 0x00, 0xA7, 0xC5, 0x04, 0x62, 0xFF, 0x08, 0x00, 0xE0, 0x00, 0x02, 0x21, 0x0B, 174 | 0x01, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x06, 0x00, 0x1E, 0x22, 0x00, 0x00, 175 | 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x04, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 176 | 0x02, 0x00, 0x00, 0x04, 0xFF, 0x07, 0x00, 0x04, 0xFF, 0x08, 0x00, 0x60, 0x00, 0x00, 0x00, 0x02, 177 | 0xFF, 0x06, 0x00, 0x03, 0x00, 0x40, 0x85, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0xFF, 0x04, 0x00, 178 | 0x10, 0x00, 0x00, 0x10, 0xFF, 0x06, 0x00, 0x10, 0xFF, 0x0B, 0x00, 0xCC, 0x21, 0x00, 0x00, 0x4F, 179 | 0xFF, 0x1C, 0x00, 0x40, 0x00, 0x00, 0x0C, 0xFF, 0x34, 0x00, 0x20, 0x00, 0x00, 0x08, 0xFF, 0x0B, 180 | 0x00, 0x08, 0x20, 0x00, 0x00, 0x48, 0xFF, 0x0B, 0x00, 0x2E, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 181 | 0x00, 0x24, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0xFF, 182 | 0x0E, 0x00, 0x20, 0x00, 0x00, 0x60, 0x2E, 0x72, 0x65, 0x6C, 0x6F, 0x63, 0x00, 0x00, 0x0C, 0xFF, 183 | 0x04, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0xFF, 0x0E, 0x00, 0x40, 0x00, 184 | 0x00, 0x42, 0xFF, 0x39, 0x00, 0x22, 0xFF, 0x06, 0x00, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 185 | 0x00, 0x58, 0x20, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x01, 0xFF, 0x37, 0x00, 0x1E, 0x02, 0x28, 186 | 0x01, 0x00, 0x00, 0x0A, 0x2A, 0x42, 0x53, 0x4A, 0x42, 0x01, 0x00, 0x01, 0xFF, 0x05, 0x00, 0x0C, 187 | 0x00, 0x00, 0x00, 0x76, 0x32, 0x2E, 0x30, 0x2E, 0x35, 0x30, 0x37, 0x32, 0x37, 0xFF, 0x04, 0x00, 188 | 0x05, 0x00, 0x6C, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x23, 0x7E, 0x00, 0x00, 0x0C, 0x01, 189 | 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x23, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x73, 0xFF, 0x04, 190 | 0x00, 0x4C, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x23, 0x55, 0x53, 0x00, 0x54, 0x01, 0x00, 191 | 0x00, 0x10, 0x00, 0x00, 0x00, 0x23, 0x47, 0x55, 0x49, 0x44, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 192 | 0x00, 0x10, 0x00, 0x00, 0x00, 0x23, 0x42, 0x6C, 0x6F, 0x62, 0xFF, 0x07, 0x00, 0x02, 0x00, 0x00, 193 | 0x01, 0x47, 0x04, 0x00, 0x00, 0x09, 0xFF, 0x04, 0x00, 0xFA, 0x01, 0x33, 0x00, 0x16, 0x00, 0x00, 194 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 195 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x05, 0x00, 0x1E, 0x00, 0x01, 0xFF, 196 | 0x05, 0x00, 0x06, 0x00, 0x11, 0x00, 0x0A, 0xFF, 0x05, 0x00, 0x01, 0xFF, 0x05, 0x00, 0x01, 0x00, 197 | 0x01, 0x00, 0x01, 0x00, 0x10, 0x00, 0x34, 0x00, 0x34, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00, 198 | 0x50, 0x20, 0xFF, 0x04, 0x00, 0x86, 0x18, 0x18, 0x00, 0x01, 0x00, 0x01, 0x00, 0x09, 0x00, 0x18, 199 | 0x00, 0x01, 0xFF, 0x13, 0x00, 0x36, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x0B, 0x00, 0x05, 0x00, 0x2B, 200 | 0xFF, 0x08, 0x00, 0x3C, 0x4D, 0x6F, 0x64, 0x75, 0x6C, 0x65, 0x3E, 0x00, 0x53, 0x79, 0x73, 0x74, 201 | 0x65, 0x6D, 0x00, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x2E, 0x63, 0x74, 0x6F, 0x72, 0x00, 202 | 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x6D, 0x73, 0x63, 203 | 0x6F, 0x72, 0x6C, 0x69, 0x62, 0x00, 0x5F, 0x00, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 204 | 0x00, 0x00, 0x00, 0x03, 0x20, 0xFF, 0x05, 0x00, 0x83, 0xBA, 0x7D, 0x5E, 0x24, 0xF6, 0x28, 0x4D, 205 | 0x9C, 0xE0, 0x95, 0x2F, 0xF7, 0x42, 0xFE, 0xF4, 0x00, 0x03, 0x20, 0x00, 0x01, 0x08, 0xB7, 0x7A, 206 | 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89, 0x00, 0x00, 0xF4, 0x21, 0xFF, 0x0A, 0x00, 0x0E, 0x22, 0x00, 207 | 0x00, 0x00, 0x20, 0xFF, 0x17, 0x00, 0x22, 0xFF, 0x0C, 0x00, 0x5F, 0x43, 0x6F, 0x72, 0x44, 0x6C, 208 | 0x6C, 0x4D, 0x61, 0x69, 0x6E, 0x00, 0x6D, 0x73, 0x63, 0x6F, 0x72, 0x65, 0x65, 0x2E, 0x64, 0x6C, 209 | 0x6C, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0xFF, 0x25, 0x00, 0x20, 0x40, 0xFF, 0xDE, 0x01, 0x20, 0x00, 210 | 0x00, 0x0C, 0x00, 0x00, 0x00, 0x20, 0x32, 0xFF, 0xF6, 0x01 211 | }; 212 | } 213 | 214 | //.assembly extern mscorlib 215 | //{ 216 | // .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) 217 | // .ver 2:0:0:0 218 | //} 219 | //.assembly '________' 220 | //{ 221 | //} 222 | //.module '________.dll' 223 | //.class public auto ansi beforefieldinit _._ extends [mscorlib]System.Object 224 | //{ 225 | // .method public hidebysig specialname rtspecialname instance void .ctor() cil managed 226 | // { 227 | // .maxstack 8 228 | // IL_0000: ldarg.0 229 | // IL_0001: call instance void [mscorlib]System.Object::.ctor() 230 | // IL_0006: ret 231 | // } 232 | //} 233 | 234 | //static byte[] Compress(byte[] data) { 235 | // var buffer = new List(data.Length); 236 | // for (int i = 0; i < data.Length; i++) { 237 | // if (data[i] == 0) { 238 | // int count = CountZero(data, i); 239 | // i += count - 1; 240 | // if (count > 3) { 241 | // Debug2.Assert(count < 0xFFFF); 242 | // buffer.Add(0xFF); 243 | // buffer.Add((byte)count); 244 | // buffer.Add((byte)(count >> 8)); 245 | // } 246 | // else { 247 | // while (count-- > 0) 248 | // buffer.Add(0x00); 249 | // } 250 | // } 251 | // else if (data[i] == 0xFF) { 252 | // buffer.Add(0xFF); 253 | // buffer.Add(0xFF); 254 | // buffer.Add(0xFF); 255 | // } 256 | // else { 257 | // buffer.Add(data[i]); 258 | // } 259 | // } 260 | // return buffer.ToArray(); 261 | 262 | // static int CountZero(byte[] data, int index) { 263 | // int i = index; 264 | // for (; i < data.Length && data[i] == 0; i++) ; 265 | // return i - index; 266 | // } 267 | //} 268 | -------------------------------------------------------------------------------- /MetadataLocator/RuntimeDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace MetadataLocator; 4 | 5 | #pragma warning disable CS0649 6 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 7 | public static unsafe class RuntimeDefinitions { 8 | static class DummyBuffer { 9 | public static readonly nuint Value = VirtualAlloc(0, 0x2000, 0x1000, 4); 10 | 11 | [DllImport("kernel32.dll", SetLastError = true)] 12 | static extern nuint VirtualAlloc(nuint lpAddress, uint dwSize, uint flAllocationType, uint flProtect); 13 | } 14 | 15 | #region Basic 16 | public const uint STORAGE_MAGIC_SIG = 0x424A5342; // BSJB 17 | 18 | public struct IMAGE_DATA_DIRECTORY { 19 | public uint VirtualAddress; 20 | public uint Size; 21 | } 22 | 23 | public struct IMAGE_COR20_HEADER { 24 | public uint cb; 25 | public ushort MajorRuntimeVersion; 26 | public ushort MinorRuntimeVersion; 27 | public IMAGE_DATA_DIRECTORY MetaData; 28 | public uint Flags; 29 | public uint EntryPointTokenOrRVA; 30 | public IMAGE_DATA_DIRECTORY Resources; 31 | public IMAGE_DATA_DIRECTORY StrongNameSignature; 32 | public IMAGE_DATA_DIRECTORY CodeManagerTable; 33 | public IMAGE_DATA_DIRECTORY VTableFixups; 34 | public IMAGE_DATA_DIRECTORY ExportAddressTableJumps; 35 | public IMAGE_DATA_DIRECTORY ManagedNativeHeader; 36 | }; 37 | 38 | public unsafe struct STORAGESIGNATURE { 39 | public uint lSignature; // "Magic" signature. 40 | public ushort iMajorVer; // Major file version. 41 | public ushort iMinorVer; // Minor file version. 42 | public uint iExtraData; // Offset to next structure of information 43 | public uint iVersionString; // Length of version string 44 | public fixed byte pVersion[1]; // Version string 45 | } 46 | 47 | public struct STORAGEHEADER { 48 | public byte fFlags; 49 | public byte pad; 50 | public ushort iStreams; 51 | } 52 | 53 | // ========================================================================================== 54 | // SString is the base class for safe strings. 55 | // ========================================================================================== 56 | /* complete, 0x10 / 0x18 bytes */ 57 | public struct SString { 58 | /* important */ 59 | public uint m_size; 60 | public uint m_allocation; 61 | public uint m_flags; 62 | /* important */ 63 | public nuint m_buffer; 64 | } 65 | 66 | /* complete, 0x1c / 0x30 bytes */ 67 | public struct Crst { 68 | public nuint __padding_0; 69 | public uint __padding_1; 70 | public uint __padding_2; 71 | public nuint __padding_3; 72 | public nuint __padding_4; 73 | public nuint __padding_5; 74 | public uint __padding_6; 75 | } 76 | #endregion 77 | 78 | #region PE 79 | //public const int IMAGE_FLAT = 0; 80 | //public const int IMAGE_MAPPED = 1; // comment: not existing in .NET ? TODO: update comment when .NET 7.0 released https://github.com/dotnet/runtime/commit/35e4e97867db6bb2cc1c9f1e91c80dd80759e259#diff-42902be0f805f8e1cce666fd0dfb892ffbac53d3c5897beaa86d965df22ef1dbL287 81 | //public const int IMAGE_LOADED = 2; 82 | //public const int IMAGE_LOADED_FOR_INTROSPECTION = 3; // comment: not existing in .NET Core v3.0+ https://github.com/dotnet/coreclr/commit/af4ec7c89d0192ad14392da04e8c097da8ec9e48#diff-dd1e605d2e73125b21c2617d94bb41def043971508334410a1d62675cf768b6dL332 83 | //public const int IMAGE_COUNT = 4; 84 | 85 | // -------------------------------------------------------------------------------- 86 | // PEDecoder - Utility class for reading and verifying PE files. 87 | // 88 | // Note that the Check step is optional if you are willing to trust the 89 | // integrity of the image. 90 | // (Or at any rate can be factored into an initial verification step.) 91 | // 92 | // Functions which access the memory of the PE file take a "flat" flag - this 93 | // indicates whether the PE images data has been loaded flat the way it resides in the file, 94 | // or if the sections have been mapped into memory at the proper base addresses. 95 | // 96 | // Finally, some functions take an optional "size" argument, which can be used for 97 | // range verification. This is an optional parameter, but if you omit it be sure 98 | // you verify the size in some other way. 99 | // -------------------------------------------------------------------------------- 100 | /* complete, 0x18 / 0x28 bytes */ 101 | public struct PEDecoder { 102 | /* important */ 103 | public nuint m_base; 104 | /* important */ 105 | public uint m_size; // size of file on disk, as opposed to OptionalHeaders.SizeOfImage 106 | public uint m_flags; 107 | public nuint m_pNTHeaders; 108 | /* important */ 109 | public nuint m_pCorHeader; 110 | public nuint m_pNativeHeader; 111 | // public nuint m_pReadyToRunHeader; // comment: only in coreclr 112 | } 113 | 114 | /* incomplete */ 115 | public struct PEImageLayout { 116 | public static readonly PEImageLayout* Dummy = (PEImageLayout*)DummyBuffer.Value; 117 | 118 | public nuint __vfptr; 119 | public PEDecoder __base; 120 | 121 | // ... some paddings ... 122 | } 123 | 124 | // -------------------------------------------------------------------------------- 125 | // PEImage is a PE file loaded by our "simulated LoadLibrary" mechanism. A PEImage 126 | // can be loaded either FLAT (same layout as on disk) or MAPPED (PE sections 127 | // mapped into virtual addresses.) 128 | // 129 | // The MAPPED format is currently limited to "IL only" images - this can be checked 130 | // for via PEDecoder::IsILOnlyImage. 131 | // 132 | // NOTE: PEImage will NEVER call LoadLibrary. 133 | // -------------------------------------------------------------------------------- 134 | /* incomplete */ 135 | public struct PEImage { 136 | } 137 | 138 | /* incomplete */ 139 | public struct PEImage_20 { 140 | public static readonly PEImage_20* Dummy = (PEImage_20*)DummyBuffer.Value; 141 | 142 | public Crst m_PdbStreamLock; 143 | public nuint m_pPdbStream; 144 | /* important */ 145 | public SString m_path; 146 | 147 | // ... some paddings ... 148 | /* important */ 149 | //public fixed nuint m_pLayouts[IMAGE_COUNT]; 150 | //public bool m_bInHashMap; 151 | //public nuint m_pMDTracker; // comments: might exists 152 | /* important */ 153 | //public nuin m_pMDImport; 154 | } 155 | 156 | /* incomplete */ 157 | public struct PEImage_40 { 158 | public static readonly PEImage_40* Dummy = (PEImage_40*)DummyBuffer.Value; 159 | 160 | /* important */ 161 | public SString m_path; 162 | 163 | // ... some paddings ... 164 | /* important */ 165 | //public fixed nuint m_pLayouts[IMAGE_COUNT]; 166 | //public bool m_bInHashMap; 167 | //public nuint m_pMDTracker; // comments: might exists 168 | /* important */ 169 | //public nuin m_pMDImport; 170 | } 171 | 172 | /* incomplete */ 173 | public struct PEFile { 174 | public static readonly PEFile* Dummy = (PEFile*)DummyBuffer.Value; 175 | 176 | public nuint __vfptr; 177 | public PEImage* m_identity; // Identity image 178 | /* important */ 179 | public PEImage* m_openedILimage; // IL image, NULL if we didn't need to open the file 180 | 181 | // ... some paddings ... 182 | /* important */ 183 | //public nuint m_pMDImport; 184 | } 185 | #endregion 186 | 187 | #region StgPool 188 | /* complete, 0x10 / 0x18 bytes */ 189 | public struct StgPoolSeg { 190 | /* important */ 191 | public byte* m_pSegData; // Pointer to the data. 192 | public StgPoolSeg* m_pNextSeg; // Pointer to next segment, or NULL. 193 | /* important */ 194 | public uint m_cbSegSize; // Size of the segment buffer. If this is last segment (code:m_pNextSeg is NULL), then it's the 195 | // allocation size. If this is not the last segment, then this is shrinked to segment data size 196 | // (code:m_cbSegNext). 197 | public uint m_cbSegNext; // Offset of next available byte in segment. Segment relative. 198 | } 199 | 200 | /* complete, 0x18 / 0x28 bytes */ 201 | public struct StgPoolReadOnly { 202 | public nuint __vfptr; 203 | public StgPoolSeg __base; 204 | public nuint m_HotHeap; 205 | } 206 | 207 | /* complete, 0x18 / 0x28 bytes */ 208 | public struct StgBlobPoolReadOnly { 209 | public StgPoolReadOnly __base; 210 | } 211 | 212 | //public struct StgPool { 213 | // public StgPoolReadOnly __base; 214 | // public uint m_ulGrowInc; // How many bytes at a time. 215 | // public StgPoolSeg* m_pCurSeg; // Current seg for append -- end of chain. 216 | // public uint m_cbCurSegOffset; // Base offset of current seg. 217 | // public uint m_bFree_or_bReadOnly; // True if we should free base data. Extension data is always freed. 218 | // // True if we shouldn't append. 219 | // public uint m_nVariableAlignmentMask; // Alignment mask (variable 0, 1 or 3). 220 | // public uint m_cbStartOffsetOfEdit; // Place in the pool where edits started 221 | // public int m_fValidOffsetOfEdit; // Is the pool edit offset valid 222 | //} 223 | #endregion 224 | 225 | #region Metadata Table 226 | public const int TBL_Module = 0; 227 | public const int TBL_TypeRef = 1; 228 | public const int TBL_TypeDef = 2; 229 | public const int TBL_FieldPtr = 3; 230 | public const int TBL_Field = 4; 231 | public const int TBL_MethodPtr = 5; 232 | public const int TBL_Method = 6; 233 | public const int TBL_ParamPtr = 7; 234 | public const int TBL_Param = 8; 235 | public const int TBL_InterfaceImpl = 9; 236 | public const int TBL_MemberRef = 10; 237 | public const int TBL_Constant = 11; 238 | public const int TBL_CustomAttribute = 12; 239 | public const int TBL_FieldMarshal = 13; 240 | public const int TBL_DeclSecurity = 14; 241 | public const int TBL_ClassLayout = 15; 242 | public const int TBL_FieldLayout = 16; 243 | public const int TBL_StandAloneSig = 17; 244 | public const int TBL_EventMap = 18; 245 | public const int TBL_EventPtr = 19; 246 | public const int TBL_Event = 20; 247 | public const int TBL_PropertyMap = 21; 248 | public const int TBL_PropertyPtr = 22; 249 | public const int TBL_Property = 23; 250 | public const int TBL_MethodSemantics = 24; 251 | public const int TBL_MethodImpl = 25; 252 | public const int TBL_ModuleRef = 26; 253 | public const int TBL_TypeSpec = 27; 254 | public const int TBL_ImplMap = 28; 255 | public const int TBL_FieldRVA = 29; 256 | public const int TBL_ENCLog = 30; 257 | public const int TBL_ENCMap = 31; 258 | public const int TBL_Assembly = 32; 259 | public const int TBL_AssemblyProcessor = 33; 260 | public const int TBL_AssemblyOS = 34; 261 | public const int TBL_AssemblyRef = 35; 262 | public const int TBL_AssemblyRefProcessor = 36; 263 | public const int TBL_AssemblyRefOS = 37; 264 | public const int TBL_File = 38; 265 | public const int TBL_ExportedType = 39; 266 | public const int TBL_ManifestResource = 40; 267 | public const int TBL_NestedClass = 41; 268 | public const int TBL_GenericParam = 42; 269 | public const int TBL_MethodSpec = 43; 270 | public const int TBL_GenericParamConstraint = 44; 271 | public const int TBL_COUNT = 45; // Highest table. 272 | public const int TBL_COUNT_V1 = 42; // Highest table in v1.0 database 273 | public const int TBL_COUNT_V2 = 45; // Highest in v2.0 database 274 | 275 | /* complete, 0x04 / 0x08 bytes */ 276 | public struct TableRO { 277 | public nuint m_pData; 278 | } 279 | 280 | /* complete, 0x00b4 / 0x0168 bytes */ 281 | public struct TableROs { 282 | public TableRO Module; 283 | public TableRO TypeRef; 284 | public TableRO TypeDef; 285 | public TableRO FieldPtr; 286 | public TableRO Field; 287 | public TableRO MethodPtr; 288 | public TableRO Method; 289 | public TableRO ParamPtr; 290 | public TableRO Param; 291 | public TableRO InterfaceImpl; 292 | public TableRO MemberRef; 293 | public TableRO Constant; 294 | public TableRO CustomAttribute; 295 | public TableRO FieldMarshal; 296 | public TableRO DeclSecurity; 297 | public TableRO ClassLayout; 298 | public TableRO FieldLayout; 299 | public TableRO StandAloneSig; 300 | public TableRO EventMap; 301 | public TableRO EventPtr; 302 | public TableRO Event; 303 | public TableRO PropertyMap; 304 | public TableRO PropertyPtr; 305 | public TableRO Property; 306 | public TableRO MethodSemantics; 307 | public TableRO MethodImpl; 308 | public TableRO ModuleRef; 309 | public TableRO TypeSpec; 310 | public TableRO ImplMap; 311 | public TableRO FieldRVA; 312 | public TableRO ENCLog; 313 | public TableRO ENCMap; 314 | public TableRO Assembly; 315 | public TableRO AssemblyProcessor; 316 | public TableRO AssemblyOS; 317 | public TableRO AssemblyRef; 318 | public TableRO AssemblyRefProcessor; 319 | public TableRO AssemblyRefOS; 320 | public TableRO File; 321 | public TableRO ExportedType; 322 | public TableRO ManifestResource; 323 | public TableRO NestedClass; 324 | public TableRO GenericParam; 325 | public TableRO MethodSpec; 326 | public TableRO GenericParamConstraint; 327 | } 328 | #endregion 329 | 330 | #region Metadata Heap 331 | /* complete, 0x18 / 0x28 bytes */ 332 | public struct StringHeapRO { 333 | public StgPoolReadOnly m_StringPool; 334 | } 335 | 336 | /* complete, 0x18 / 0x28 bytes */ 337 | public struct BlobHeapRO { 338 | public StgBlobPoolReadOnly m_BlobPool; 339 | } 340 | 341 | /* complete, 0x18 / 0x28 bytes */ 342 | public struct GuidHeapRO { 343 | public StgPoolReadOnly m_GuidPool; 344 | } 345 | #endregion 346 | 347 | #region Metadata Model 348 | //***************************************************************************** 349 | // The mini, hard-coded schema. For each table, we persist the count of 350 | // records. We also persist the size of string, blob, guid, and rid 351 | // columns. From this information, we can calculate the record sizes, and 352 | // then the sizes of the tables. 353 | //***************************************************************************** 354 | /* complete, 0x18 / 0x18 bytes */ 355 | public struct CMiniMdSchemaBase { 356 | public uint m_ulReserved; // Reserved, must be zero. 357 | public byte m_major; // Version numbers. 358 | public byte m_minor; 359 | public byte m_heaps; // Bits for heap sizes. 360 | public byte m_rid; // log-base-2 of largest rid. 361 | public ulong m_maskvalid; // Bit mask of present table counts. 362 | public ulong m_sorted; // Bit mask of sorted tables. 363 | } 364 | 365 | /* complete, 0xd0 / 0xd0 bytes */ 366 | public struct CMiniMdSchema { 367 | public CMiniMdSchemaBase __base; 368 | public fixed uint m_cRecs[TBL_COUNT]; // Counts of various tables. 369 | public uint m_ulExtra; // Extra data, only persisted if non-zero. (m_heaps&EXTRA_DATA flags) 370 | } 371 | 372 | /* complete, 0x03 / 0x03 bytes */ 373 | public struct CMiniColDef { 374 | public byte m_Type; // Type of the column. 375 | public byte m_oColumn; // Offset of the column. 376 | public byte m_cbColumn; // Size of the column. 377 | }; 378 | 379 | /* complete, 0x08 / 0x10 bytes */ 380 | public struct CMiniTableDef { 381 | public CMiniColDef* m_pColDefs; // Array of field defs. 382 | public byte m_cCols; // Count of columns in the table. 383 | public byte m_iKey; // Column which is the key, if any. 384 | public ushort m_cbRec; // Size of the records. 385 | }; 386 | 387 | /* complete, 0x0168 / 0x02d0 bytes */ 388 | public struct CMiniTableDefs { 389 | public CMiniTableDef Module; 390 | public CMiniTableDef TypeRef; 391 | public CMiniTableDef TypeDef; 392 | public CMiniTableDef FieldPtr; 393 | public CMiniTableDef Field; 394 | public CMiniTableDef MethodPtr; 395 | public CMiniTableDef Method; 396 | public CMiniTableDef ParamPtr; 397 | public CMiniTableDef Param; 398 | public CMiniTableDef InterfaceImpl; 399 | public CMiniTableDef MemberRef; 400 | public CMiniTableDef Constant; 401 | public CMiniTableDef CustomAttribute; 402 | public CMiniTableDef FieldMarshal; 403 | public CMiniTableDef DeclSecurity; 404 | public CMiniTableDef ClassLayout; 405 | public CMiniTableDef FieldLayout; 406 | public CMiniTableDef StandAloneSig; 407 | public CMiniTableDef EventMap; 408 | public CMiniTableDef EventPtr; 409 | public CMiniTableDef Event; 410 | public CMiniTableDef PropertyMap; 411 | public CMiniTableDef PropertyPtr; 412 | public CMiniTableDef Property; 413 | public CMiniTableDef MethodSemantics; 414 | public CMiniTableDef MethodImpl; 415 | public CMiniTableDef ModuleRef; 416 | public CMiniTableDef TypeSpec; 417 | public CMiniTableDef ImplMap; 418 | public CMiniTableDef FieldRVA; 419 | public CMiniTableDef ENCLog; 420 | public CMiniTableDef ENCMap; 421 | public CMiniTableDef Assembly; 422 | public CMiniTableDef AssemblyProcessor; 423 | public CMiniTableDef AssemblyOS; 424 | public CMiniTableDef AssemblyRef; 425 | public CMiniTableDef AssemblyRefProcessor; 426 | public CMiniTableDef AssemblyRefOS; 427 | public CMiniTableDef File; 428 | public CMiniTableDef ExportedType; 429 | public CMiniTableDef ManifestResource; 430 | public CMiniTableDef NestedClass; 431 | public CMiniTableDef GenericParam; 432 | public CMiniTableDef MethodSpec; 433 | public CMiniTableDef GenericParamConstraint; 434 | } 435 | 436 | /* complete, 0x0250 / 0x03c0 bytes */ 437 | public struct CMiniMdBase_20 { 438 | public static readonly CMiniMdBase_20* Dummy = (CMiniMdBase_20*)DummyBuffer.Value; 439 | 440 | public nuint __vfptr; 441 | public CMiniMdSchema m_Schema; // data header. 442 | public uint m_TblCount; // Tables in this database. 443 | public CMiniTableDefs m_TableDefs; 444 | public uint m_iStringsMask; 445 | public uint m_iGuidsMask; 446 | public uint m_iBlobsMask; 447 | } 448 | 449 | /* complete, 0x0258 / 0x03c0 bytes */ 450 | public struct CMiniMdBase_40 { 451 | public static readonly CMiniMdBase_40* Dummy = (CMiniMdBase_40*)DummyBuffer.Value; 452 | 453 | public nuint __vfptr; 454 | public CMiniMdSchema m_Schema; // data header. 455 | public uint m_TblCount; // Tables in this database. 456 | public int m_fVerifiedByTrustedSource; // whether the data was verified by a trusted source 457 | public CMiniTableDefs m_TableDefs; 458 | public uint m_iStringsMask; 459 | public uint m_iGuidsMask; 460 | public uint m_iBlobsMask; 461 | } 462 | 463 | /* complete, 0x0368 / 0x05c8 bytes */ 464 | public struct CMiniMd_20 { 465 | public CMiniMdBase_20 __base; 466 | public TableROs m_Tables; 467 | public GuidHeapRO m_GuidHeap; 468 | public StringHeapRO m_StringHeap; 469 | public BlobHeapRO m_BlobHeap; 470 | public BlobHeapRO m_UserStringHeap; 471 | } 472 | 473 | /* complete, 0x0370 / 0x05c8 bytes */ 474 | public struct CMiniMd_40 { 475 | public CMiniMdBase_40 __base; 476 | public TableROs m_Tables; 477 | public StringHeapRO m_StringHeap; 478 | public BlobHeapRO m_BlobHeap; 479 | public BlobHeapRO m_UserStringHeap; 480 | public GuidHeapRO m_GuidHeap; 481 | } 482 | 483 | public struct CMiniMdRW { 484 | //public CMiniMdBase __base; 485 | 486 | // ... some paddings ... 487 | } 488 | #endregion 489 | 490 | #region LiteWeightStgdb 491 | /* complete, 0x0370 / 0x05d8 bytes */ 492 | public struct CLiteWeightStgdb_CMiniMd_20 { 493 | public CMiniMd_20 m_MiniMd; 494 | public nuint m_pvMd; 495 | public uint m_cbMd; 496 | } 497 | 498 | /* complete, 0x0378 / 0x05d8 bytes */ 499 | public struct CLiteWeightStgdb_CMiniMd_40 { 500 | public CMiniMd_40 m_MiniMd; 501 | public nuint m_pvMd; 502 | public uint m_cbMd; 503 | } 504 | 505 | public struct CLiteWeightStgdb_CMiniMdRW { 506 | public CMiniMdRW m_MiniMd; 507 | 508 | // ... some paddings ... 509 | //public nuint m_pvMd; 510 | //public uint m_cbMd; 511 | } 512 | 513 | public struct CLiteWeightStgdbRW { 514 | public CLiteWeightStgdb_CMiniMdRW __base; 515 | 516 | // ... some paddings ... 517 | } 518 | #endregion 519 | 520 | #region Metadata Internal 521 | public struct MDInternalRO { 522 | } 523 | 524 | /* complete, 0x0378 / 0x05e0 bytes */ 525 | public struct MDInternalRO_20 { 526 | public nuint __vfptr_IMDInternalImport; 527 | public CLiteWeightStgdb_CMiniMd_20 m_LiteWeightStgdb; 528 | } 529 | 530 | /* complete, 0x0380 / 0x05e0 bytes */ 531 | public struct MDInternalRO_40 { 532 | public nuint __vfptr_IMDInternalImport; 533 | public CLiteWeightStgdb_CMiniMd_40 m_LiteWeightStgdb; 534 | } 535 | 536 | /* complete, 0x0380 / 0x05e0 bytes */ 537 | public struct MDInternalRO_45 { 538 | public nuint __vfptr_IMDInternalImport; 539 | public nuint __vfptr_IMDCommon; 540 | public CLiteWeightStgdb_CMiniMd_40 m_LiteWeightStgdb; 541 | } 542 | 543 | public struct MDInternalRW { 544 | } 545 | 546 | public struct MDInternalRW_20 { 547 | public static readonly MDInternalRW_20* Dummy = (MDInternalRW_20*)DummyBuffer.Value; 548 | 549 | public nuint __vfptr_IMDInternalImport; 550 | public CLiteWeightStgdbRW* m_pStgdb; 551 | } 552 | 553 | public struct MDInternalRW_45 { 554 | public static readonly MDInternalRW_45* Dummy = (MDInternalRW_45*)DummyBuffer.Value; 555 | 556 | public nuint __vfptr_IMDInternalImport; 557 | public nuint __vfptr_IMDCommon; 558 | public CLiteWeightStgdbRW* m_pStgdb; 559 | } 560 | #endregion 561 | 562 | #region Reflection 563 | /* incomplete */ 564 | public struct Module { 565 | } 566 | 567 | /* incomplete */ 568 | public struct Module_20 { 569 | public static readonly Module_20* Dummy = (Module_20*)DummyBuffer.Value; 570 | 571 | public nuint __vfptr; 572 | /* important */ 573 | public PEFile* m_file; 574 | } 575 | 576 | /* incomplete */ 577 | public struct Module_453 { 578 | public static readonly Module_453* Dummy = (Module_453*)DummyBuffer.Value; 579 | 580 | public nuint __vfptr; 581 | public nuint m_pSimpleName; 582 | /* important */ 583 | public PEFile* m_file; 584 | } 585 | #endregion 586 | } 587 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 588 | #pragma warning restore CS0649 589 | -------------------------------------------------------------------------------- /MetadataLocator/MetadataInfoImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using static MetadataLocator.RuntimeDefinitions; 4 | using SR = System.Reflection; 5 | 6 | namespace MetadataLocator; 7 | 8 | static unsafe class MetadataInfoImpl { 9 | sealed class Profile { 10 | public nuint Vfptr; // MDInternalRO or MDInternalRW 11 | public bool Uncompressed; 12 | public bool RidAsIndex; 13 | public Pointer Schema = Pointer.Empty; 14 | public Pointer TableCount = Pointer.Empty; 15 | public Pointer TableDefs = Pointer.Empty; 16 | public Pointer MetadataAddress = Pointer.Empty; 17 | public Pointer MetadataSize = Pointer.Empty; 18 | public Pointer[] HeapAddress = Array2.Empty(); 19 | public Pointer[] HeapSize = Array2.Empty(); 20 | public Pointer TableAddress = Pointer.Empty; 21 | public uint NextTableOffset; 22 | } 23 | 24 | const int StringHeapIndex = 0; 25 | const int UserStringsHeapIndex = 1; 26 | const int GuidHeapIndex = 2; 27 | const int BlobHeapIndex = 3; 28 | 29 | static Profile[] profiles = Array2.Empty(); 30 | static bool isInitialized; 31 | 32 | static void Initialize() { 33 | if (isInitialized) 34 | return; 35 | 36 | profiles = new Profile[2]; 37 | profiles[0] = CreateProfile(false); 38 | profiles[1] = CreateProfile(true); 39 | 40 | isInitialized = true; 41 | } 42 | 43 | static Profile CreateProfile(bool uncompressed) { 44 | const bool InMemory = false; 45 | 46 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 47 | if (uncompressed) 48 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 49 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 50 | nuint module = assembly.ModuleHandle; 51 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 52 | // Get native Module object 53 | 54 | var stgdbPointer = ScanLiteWeightStgdbPointer(uncompressed, out nuint vfptr); 55 | ScanMetadataOffsets(stgdbPointer, uncompressed, out uint metadataAddressOffset, out uint metadataSizeOffset); 56 | var info = new MiniMetadataInfo(Utils.ReadUIntPtr(Utils.WithOffset(stgdbPointer, metadataAddressOffset), module)); 57 | ScanSchemaOffset(stgdbPointer, info, uncompressed, out uint schemaOffset); 58 | ScanTableDefsOffsets(stgdbPointer, uncompressed, schemaOffset, out uint tableCountOffset, out uint tableDefsOffset); 59 | ScanHeapOffsets(stgdbPointer, info, uncompressed, out var heapAddressOffsets, out var heapSizeOffsets); 60 | bool ridAsIndex = RuntimeEnvironment.Version < RuntimeVersion.Fx40 && !uncompressed; 61 | // see sscli2 CMiniMd::SetTablePointers, Set the pointers to consecutive areas of a large buffer. 62 | info.CalculateTableAddress(Utils.ReadPointer(Utils.WithOffset(stgdbPointer, tableDefsOffset), module), ridAsIndex); 63 | ScanTableOffset(stgdbPointer, info, uncompressed, out uint tableAddressOffset, out uint nextTableOffset); 64 | var profile = new Profile { 65 | Vfptr = vfptr, 66 | Uncompressed = uncompressed, 67 | RidAsIndex = ridAsIndex, 68 | Schema = Utils.WithOffset(stgdbPointer, schemaOffset), 69 | TableCount = Utils.WithOffset(stgdbPointer, tableCountOffset), 70 | TableDefs = Utils.WithOffset(stgdbPointer, tableDefsOffset), 71 | MetadataAddress = Utils.WithOffset(stgdbPointer, metadataAddressOffset), 72 | MetadataSize = Utils.WithOffset(stgdbPointer, metadataSizeOffset), 73 | HeapAddress = Utils.WithOffset(stgdbPointer, heapAddressOffsets), 74 | HeapSize = Utils.WithOffset(stgdbPointer, heapSizeOffsets), 75 | TableAddress = Utils.WithOffset(stgdbPointer, tableAddressOffset), 76 | NextTableOffset = nextTableOffset 77 | }; 78 | return profile; 79 | } 80 | 81 | // Not really a CLiteWeightStgdb, for compressed metadata it returens 82 | static Pointer ScanLiteWeightStgdbPointer(bool uncompressed, out nuint vfptr) { 83 | const bool InMemory = false; 84 | 85 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 86 | if (uncompressed) 87 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 88 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 89 | nuint module = assembly.ModuleHandle; 90 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 91 | // Get native Module object 92 | 93 | uint m_file_Offset; 94 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx453) 95 | m_file_Offset = (uint)((nuint)(&Module_453.Dummy->m_file) - (nuint)Module_453.Dummy); 96 | else 97 | m_file_Offset = (uint)((nuint)(&Module_20.Dummy->m_file) - (nuint)Module_20.Dummy); 98 | nuint m_file = *(nuint*)(module + m_file_Offset); 99 | Utils.Check((PEFile*)m_file); 100 | // Module.m_file 101 | 102 | var metadataImport = MetadataImport.Create(assembly.Module); 103 | vfptr = metadataImport.Vfptr; 104 | nuint m_pMDImport = metadataImport.This; 105 | uint m_pMDImport_Offset; 106 | bool found = false; 107 | for (m_pMDImport_Offset = 0; m_pMDImport_Offset < 8 * (uint)sizeof(nuint); m_pMDImport_Offset += 4) { 108 | if (*(nuint*)(m_file + m_pMDImport_Offset) != m_pMDImport) 109 | continue; 110 | found = true; 111 | break; 112 | } 113 | Utils.Check(found); 114 | // PEFile.m_pMDImport 115 | 116 | uint m_pStgdb_Offset = 0; 117 | if (uncompressed) { 118 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx45) 119 | m_pStgdb_Offset = (uint)((nuint)(&MDInternalRW_45.Dummy->m_pStgdb) - (nuint)MDInternalRW_45.Dummy); 120 | else 121 | m_pStgdb_Offset = (uint)((nuint)(&MDInternalRW_20.Dummy->m_pStgdb) - (nuint)MDInternalRW_20.Dummy); 122 | } 123 | // MDInternalRW.m_pStgdb 124 | 125 | var pointer = new Pointer(new[] { 126 | m_file_Offset, 127 | m_pMDImport_Offset 128 | }); 129 | if (m_pStgdb_Offset != 0) 130 | pointer.Add(m_pStgdb_Offset); 131 | Utils.Check(Utils.Verify(pointer, uncompressed, p => Memory.TryReadUInt32(p, out _))); 132 | return pointer; 133 | } 134 | 135 | static void ScanMetadataOffsets(Pointer stgdbPointer, bool uncompressed, out uint metadataAddressOffset, out uint metadataSizeOffset) { 136 | const bool InMemory = false; 137 | 138 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 139 | if (uncompressed) 140 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 141 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 142 | nuint module = assembly.ModuleHandle; 143 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 144 | // Get native Module object 145 | 146 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module); 147 | var peInfo = PEInfo.Create(assembly.Module); 148 | var imageLayout = peInfo.MappedLayout.IsInvalid ? peInfo.LoadedLayout : peInfo.MappedLayout; 149 | var m_pCorHeader = (IMAGE_COR20_HEADER*)imageLayout.CorHeaderAddress; 150 | nuint m_pvMd = imageLayout.ImageBase + m_pCorHeader->MetaData.VirtualAddress; 151 | uint m_cbMd = uncompressed ? 0x1c : m_pCorHeader->MetaData.Size; 152 | // *pcb = sizeof(STORAGESIGNATURE) + pStorage->GetVersionStringLength(); 153 | // TODO: we should calculate actual metadata size for uncompressed metadata 154 | uint start = uncompressed ? (sizeof(nuint) == 4 ? 0x1000u : 0x19A0) : (sizeof(nuint) == 4 ? 0x350u : 0x5B0); 155 | uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x1200u : 0x1BA0) : (sizeof(nuint) == 4 ? 0x39Cu : 0x5FC); 156 | uint m_pvMd_Offset = 0; 157 | for (uint offset = start; offset <= end; offset += 4) { 158 | if (*(nuint*)(pStgdb + offset) != m_pvMd) 159 | continue; 160 | if (*(uint*)(pStgdb + offset + (uint)sizeof(nuint)) != m_cbMd) 161 | continue; 162 | m_pvMd_Offset = offset; 163 | break; 164 | } 165 | Utils.Check(m_pvMd_Offset != 0); 166 | 167 | Utils.Check(Utils.Verify(Utils.WithOffset(stgdbPointer, m_pvMd_Offset), uncompressed, p => Memory.TryReadUInt32(p, out uint signature) && signature == 0x424A5342)); 168 | metadataAddressOffset = m_pvMd_Offset; 169 | metadataSizeOffset = m_pvMd_Offset + (uint)sizeof(nuint); 170 | } 171 | 172 | static void ScanSchemaOffset(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint schemaOffset) { 173 | const bool InMemory = false; 174 | 175 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 176 | if (uncompressed) 177 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 178 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 179 | nuint module = assembly.ModuleHandle; 180 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 181 | // Get native Module object 182 | 183 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module); 184 | for (schemaOffset = 0; schemaOffset < 0x30; schemaOffset += 4) { 185 | if (*(ulong*)(pStgdb + schemaOffset) != info.Header1) 186 | continue; 187 | if (*(ulong*)(pStgdb + schemaOffset + 0x08) != info.ValidMask) 188 | continue; 189 | if (*(ulong*)(pStgdb + schemaOffset + 0x10) != info.SortedMask) 190 | continue; 191 | break; 192 | } 193 | Utils.Check(schemaOffset != 0x30); 194 | // CMiniMdBase.m_Schema 195 | } 196 | 197 | static void ScanTableDefsOffsets(Pointer stgdbPointer, bool uncompressed, uint schemaOffset, out uint tableCountOffset, out uint tableDefsOffset) { 198 | const bool InMemory = false; 199 | 200 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 201 | if (uncompressed) 202 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 203 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 204 | nuint module = assembly.ModuleHandle; 205 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 206 | // Get native Module object 207 | 208 | nuint pSchema = Utils.ReadPointer(Utils.WithOffset(stgdbPointer, schemaOffset), module); 209 | nuint p = pSchema + (uint)sizeof(CMiniMdSchema); 210 | uint m_TblCount = *(uint*)p; 211 | tableCountOffset = schemaOffset + (uint)(p - pSchema); 212 | Utils.Check(m_TblCount == TBL_COUNT_V1 || m_TblCount == TBL_COUNT_V2); 213 | // CMiniMdBase.m_TblCount 214 | 215 | if (RuntimeEnvironment.Version >= RuntimeVersion.Fx40) 216 | p += (uint)((nuint)(&CMiniMdBase_40.Dummy->m_TableDefs) - (nuint)(&CMiniMdBase_40.Dummy->m_TblCount)); 217 | else 218 | p += (uint)((nuint)(&CMiniMdBase_20.Dummy->m_TableDefs) - (nuint)(&CMiniMdBase_20.Dummy->m_TblCount)); 219 | tableDefsOffset = schemaOffset + (uint)(p - pSchema); 220 | var m_TableDefs = (CMiniTableDef*)p; 221 | for (int i = 0; i < TBL_COUNT; i++) 222 | Utils.Check(Memory.TryReadUInt32((nuint)m_TableDefs[i].m_pColDefs, out _)); 223 | // CMiniMdBase.m_TableDefs 224 | } 225 | 226 | static void ScanHeapOffsets(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint[] heapAddressOffsets, out uint[] heapSizeOffsets) { 227 | const bool InMemory = false; 228 | 229 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 230 | if (uncompressed) 231 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 232 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 233 | nuint module = assembly.ModuleHandle; 234 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 235 | // Get native Module object 236 | 237 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module); 238 | uint start = uncompressed ? (sizeof(nuint) == 4 ? 0xD00u : 0x1500) : (sizeof(nuint) == 4 ? 0x2A0u : 0x500); 239 | uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x1000u : 0x1900) : (sizeof(nuint) == 4 ? 0x3A0u : 0x600); 240 | heapAddressOffsets = new uint[4]; 241 | heapSizeOffsets = new uint[heapAddressOffsets.Length]; 242 | int found = 0; 243 | for (uint offset = start; offset < end; offset += 4) { 244 | nuint address = *(nuint*)(pStgdb + offset); 245 | uint size = *(uint*)(pStgdb + offset + (2 * (uint)sizeof(nuint))); 246 | if (address == info.StringHeapAddress) { 247 | Utils.Check(info.StringHeapSize - 8 < size && size <= info.StringHeapSize); 248 | Utils.Check(heapAddressOffsets[0] == 0); 249 | heapAddressOffsets[StringHeapIndex] = offset; 250 | heapSizeOffsets[StringHeapIndex] = offset + (2 * (uint)sizeof(nuint)); 251 | found++; 252 | } 253 | else if (address == info.UserStringHeapAddress) { 254 | Utils.Check(info.UserStringHeapSize - 8 < size && size <= info.UserStringHeapSize); 255 | Utils.Check(heapAddressOffsets[1] == 0); 256 | heapAddressOffsets[UserStringsHeapIndex] = offset; 257 | heapSizeOffsets[UserStringsHeapIndex] = offset + (2 * (uint)sizeof(nuint)); 258 | found++; 259 | } 260 | else if (address == info.GuidHeapAddress) { 261 | Utils.Check(info.GuidHeapSize - 8 < size && size <= info.GuidHeapSize); 262 | Utils.Check(heapAddressOffsets[2] == 0); 263 | heapAddressOffsets[GuidHeapIndex] = offset; 264 | heapSizeOffsets[GuidHeapIndex] = offset + (2 * (uint)sizeof(nuint)); 265 | found++; 266 | } 267 | else if (address == info.BlobHeapAddress) { 268 | Utils.Check(info.BlobHeapSize - 8 < size && size <= info.BlobHeapSize); 269 | Utils.Check(heapAddressOffsets[3] == 0); 270 | heapAddressOffsets[BlobHeapIndex] = offset; 271 | heapSizeOffsets[BlobHeapIndex] = offset + (2 * (uint)sizeof(nuint)); 272 | found++; 273 | } 274 | } 275 | Utils.Check(found == 4); 276 | // Find heeap info offsets 277 | 278 | for (int i = 0; i < heapAddressOffsets.Length; i++) 279 | Utils.Check(Utils.Verify(Utils.WithOffset(stgdbPointer, heapAddressOffsets[i]), uncompressed, p => Memory.TryReadUInt32(p, out _))); 280 | } 281 | 282 | static void ScanTableOffset(Pointer stgdbPointer, MiniMetadataInfo info, bool uncompressed, out uint tableAddressOffset, out uint nextTableOffset) { 283 | const bool InMemory = false; 284 | 285 | var assemblyFlags = InMemory ? TestAssemblyFlags.InMemory : 0; 286 | if (uncompressed) 287 | assemblyFlags |= TestAssemblyFlags.Uncompressed; 288 | var assembly = TestAssemblyManager.GetAssembly(assemblyFlags); 289 | nuint module = assembly.ModuleHandle; 290 | Utils.Check((Module*)module, assembly.Module.Assembly.GetName().Name); 291 | // Get native Module object 292 | 293 | tableAddressOffset = 0; 294 | nextTableOffset = 0; 295 | nuint pStgdb = Utils.ReadUIntPtr(stgdbPointer, module); 296 | uint start = uncompressed ? (sizeof(nuint) == 4 ? 0x2A0u : 0x500) : (sizeof(nuint) == 4 ? 0x200u : 0x350); 297 | uint end = uncompressed ? (sizeof(nuint) == 4 ? 0x4A0u : 0x800) : (sizeof(nuint) == 4 ? 0x300u : 0x450); 298 | for (uint offset = start; offset < end; offset += 4) { 299 | nuint pFirst = pStgdb + offset; 300 | if (*(nuint*)pFirst != info.TableAddress[0]) 301 | continue; 302 | 303 | uint start2 = 4; 304 | uint end2 = uncompressed ? 0x100u : 0x20; 305 | uint offset2 = start2; 306 | for (; offset2 < end2; offset2 += 4) { 307 | if (*(nuint*)(pFirst + offset2) != info.TableAddress[1]) 308 | continue; 309 | if (*(nuint*)(pFirst + (2 * offset2)) != info.TableAddress[2]) 310 | continue; 311 | break; 312 | } 313 | if (offset2 == end2) 314 | continue; 315 | 316 | tableAddressOffset = offset; 317 | nextTableOffset = offset2; 318 | break; 319 | } 320 | Utils.Check(tableAddressOffset != 0); 321 | Utils.Check(nextTableOffset != 0); 322 | // CMiniMd.m_Tables 323 | } 324 | 325 | public static MetadataInfo GetMetadataInfo(SR.Module module) { 326 | if (module is null) 327 | throw new ArgumentNullException(nameof(module)); 328 | 329 | Initialize(); 330 | var profile = FindProfile(module); 331 | if (profile is null) 332 | return new MetadataInfo(); 333 | 334 | nuint moduleHandle = ReflectionHelpers.GetModuleHandle(module); 335 | var schema = GetSchema(profile, moduleHandle); 336 | var metadataInfo = new MetadataInfo { 337 | MetadataAddress = Utils.ReadUIntPtr(profile.MetadataAddress, moduleHandle), 338 | MetadataSize = Utils.ReadUInt32(profile.MetadataSize, moduleHandle), 339 | Schema = schema, 340 | TableStream = GetTableStream(profile, moduleHandle, schema), 341 | StringHeap = GetHeapInfo(profile, StringHeapIndex, moduleHandle), 342 | UserStringHeap = GetHeapInfo(profile, UserStringsHeapIndex, moduleHandle), 343 | GuidHeap = GetHeapInfo(profile, GuidHeapIndex, moduleHandle), 344 | BlobHeap = GetHeapInfo(profile, BlobHeapIndex, moduleHandle) 345 | }; 346 | return metadataInfo; 347 | } 348 | 349 | static Profile? FindProfile(SR.Module module) { 350 | nuint vfptr = MetadataImport.Create(module).Vfptr; 351 | foreach (var profile in profiles) { 352 | if (vfptr == profile.Vfptr) 353 | return profile; 354 | } 355 | Debug2.Assert(false); 356 | return null; 357 | } 358 | 359 | static MetadataSchema GetSchema(Profile profile, nuint moduleHandle) { 360 | var pSchema = (CMiniMdSchema*)Utils.ReadPointer(profile.Schema, moduleHandle); 361 | if (pSchema is null) { 362 | Debug2.Assert(false); 363 | return new(); 364 | } 365 | var rows = new uint[TBL_COUNT]; 366 | for (int i = 0; i < rows.Length; i++) 367 | rows[i] = pSchema->m_cRecs[i]; 368 | var schema = new MetadataSchema { 369 | Reserved1 = pSchema->__base.m_ulReserved, 370 | MajorVersion = pSchema->__base.m_major, 371 | MinorVersion = pSchema->__base.m_minor, 372 | Log2Rid = pSchema->__base.m_rid, 373 | Flags = pSchema->__base.m_heaps, 374 | ValidMask = pSchema->__base.m_maskvalid, 375 | SortedMask = pSchema->__base.m_sorted, 376 | RowCounts = rows, 377 | ExtraData = pSchema->m_ulExtra 378 | }; 379 | return schema; 380 | } 381 | 382 | static MetadataTableInfo GetTableStream(Profile profile, nuint moduleHandle, MetadataSchema schema) { 383 | uint tableCount = Utils.ReadUInt32(profile.TableCount, moduleHandle); 384 | var rowSizes = GetRowSizes(profile, moduleHandle); 385 | var rowCounts = schema.RowCounts; 386 | uint tablesSize = 0; 387 | uint validTableCount = 0; 388 | for (int i = 0; i < (int)tableCount; i++) { 389 | if ((schema.ValidMask & (1ul << i)) == 0) 390 | continue; 391 | tablesSize += rowSizes[i] * rowCounts[i]; 392 | validTableCount++; 393 | } 394 | uint headerSize = 0x18 + (validTableCount * 4); 395 | nuint pTable = Utils.ReadUIntPtr(profile.TableAddress, moduleHandle); 396 | if (profile.RidAsIndex) 397 | pTable += rowSizes[0]; 398 | nuint address = pTable - headerSize; 399 | uint size = headerSize + tablesSize; 400 | var tableInfo = new MetadataTableInfo { 401 | Address = address, 402 | Length = (size + 3) & ~3u, 403 | IsCompressed = !profile.Uncompressed, 404 | TableCount = tableCount, 405 | RowSizes = rowSizes 406 | }; 407 | return tableInfo; 408 | } 409 | 410 | static uint[] GetRowSizes(Profile profile, nuint moduleHandle) { 411 | var tableDefs = (CMiniTableDef*)Utils.ReadPointer(profile.TableDefs, moduleHandle); 412 | var rowSizes = new uint[TBL_COUNT]; 413 | for (int i = 0; i < TBL_COUNT; i++) 414 | rowSizes[i] = tableDefs[i].m_cbRec; 415 | return rowSizes; 416 | } 417 | 418 | static MetadataHeapInfo GetHeapInfo(Profile profile, int index, nuint moduleHandle) { 419 | uint size = Utils.ReadUInt32(profile.HeapSize[index], moduleHandle); 420 | if (size == 0) 421 | return new MetadataHeapInfo(); 422 | // TODO: also check m_pSegData is pointer to m_zeros 423 | var heapInfo = new MetadataHeapInfo { 424 | Address = Utils.ReadUIntPtr(profile.HeapAddress[index], moduleHandle), 425 | Length = (size + 3) & ~3u 426 | }; 427 | return heapInfo; 428 | } 429 | 430 | sealed class MiniMetadataInfo { 431 | public ulong Header1; 432 | public ulong ValidMask; 433 | public ulong SortedMask; 434 | public nuint TableStreamAddress; 435 | public uint TableStreamSize; 436 | public nuint StringHeapAddress; 437 | public uint StringHeapSize; 438 | public nuint UserStringHeapAddress; 439 | public uint UserStringHeapSize; 440 | public nuint GuidHeapAddress; 441 | public uint GuidHeapSize; 442 | public nuint BlobHeapAddress; 443 | public uint BlobHeapSize; 444 | public uint[] RowCounts; 445 | public nuint[] TableAddress; 446 | 447 | public MiniMetadataInfo(nuint pMetadata) { 448 | var pStorageSignature = (STORAGESIGNATURE*)pMetadata; 449 | Utils.Check(pStorageSignature); 450 | var pStorageHeader = (STORAGEHEADER*)((nuint)pStorageSignature + 0x10 + pStorageSignature->iVersionString); 451 | nuint p = (nuint)pStorageHeader + (uint)sizeof(STORAGEHEADER); 452 | Utils.Check(pStorageHeader->iStreams == 5); 453 | // must have 5 streams so we can get all stream info offsets 454 | for (int i = 0; i < pStorageHeader->iStreams; i++) { 455 | uint offset = *(uint*)p; 456 | p += 4; 457 | uint size = *(uint*)p; 458 | p += 4; 459 | Utils.Check(Memory.TryReadAnsiString(p, out var name)); 460 | p += ((uint)name.Length + 1 + 3) & ~3u; 461 | switch (name) { 462 | case "#~": 463 | case "#-": 464 | Utils.Check(TableStreamAddress == 0); 465 | TableStreamAddress = pMetadata + offset; 466 | TableStreamSize = size; 467 | break; 468 | case "#Strings": 469 | Utils.Check(StringHeapAddress == 0); 470 | StringHeapAddress = pMetadata + offset; 471 | StringHeapSize = size; 472 | break; 473 | case "#US": 474 | Utils.Check(UserStringHeapAddress == 0); 475 | UserStringHeapAddress = pMetadata + offset; 476 | UserStringHeapSize = size; 477 | break; 478 | case "#GUID": 479 | Utils.Check(GuidHeapAddress == 0); 480 | GuidHeapAddress = pMetadata + offset; 481 | GuidHeapSize = size; 482 | break; 483 | case "#Blob": 484 | Utils.Check(BlobHeapAddress == 0); 485 | BlobHeapAddress = pMetadata + offset; 486 | BlobHeapSize = size; 487 | break; 488 | default: 489 | Debug2.Assert(false); 490 | throw new NotSupportedException(); 491 | } 492 | } 493 | Header1 = *(ulong*)TableStreamAddress; 494 | ValidMask = *(ulong*)(TableStreamAddress + 0x08); 495 | SortedMask = *(ulong*)(TableStreamAddress + 0x10); 496 | TableAddress = new nuint[TBL_COUNT]; 497 | RowCounts = new uint[TBL_COUNT]; 498 | p = TableStreamAddress + 0x18; 499 | for (int i = 0; i < TBL_COUNT; i++) { 500 | if ((ValidMask & (1ul << i)) == 0) 501 | continue; 502 | RowCounts[i] = *(uint*)p; 503 | p += 4; 504 | } 505 | TableAddress[0] = p; 506 | } 507 | 508 | public void CalculateTableAddress(nuint tableDefs, bool ridAsIndex) { 509 | nuint p = TableAddress[0]; 510 | var tableDefs2 = (CMiniTableDef*)tableDefs; 511 | for (int i = 0; i < TBL_COUNT; i++) { 512 | TableAddress[i] = ridAsIndex ? p - tableDefs2[i].m_cbRec : p; 513 | p += RowCounts[i] * tableDefs2[i].m_cbRec; 514 | } 515 | } 516 | } 517 | } 518 | --------------------------------------------------------------------------------