context: /*
      Copyright (C) 2014-2019 de4dot@gmail.com
  
      This file is part of dnSpy
  
      dnSpy is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      dnSpy is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Diagnostics;
  using System.Runtime.InteropServices;
  using dnlib.DotNet;
  using dnlib.DotNet.MD;
  
  namespace dnSpy.Debugger.DotNet.Metadata.Impl.COMD {
  	static class MDAPI {
  		const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130);
  		const int CLDB_E_INDEX_NOTFOUND = unchecked((int)0x80131124);
  
  		static bool IsGlobal(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return false;
  			int hr = mdi.IsGlobal(token, out int bGlobal);
  			return hr == 0 && bGlobal != 0;
  		}
  
  		public static bool IsValidToken(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return false;
  			return mdi.IsValidToken(token);
  		}
  
  		public unsafe static bool GetClassLayout(IMetaDataImport2 mdi, uint token, out ushort packingSize, out uint classSize) {
  			packingSize = 0;
  			classSize = 0;
  			if (mdi is null)
  				return false;
  
  			uint dwPackSize = 0, ulClassSize = 0;
  			int hr = mdi.GetClassLayout(token, new IntPtr(&dwPackSize), null, 0, IntPtr.Zero, new IntPtr(&ulClassSize));
  			if (hr != 0)
  				return false;
  
  			packingSize = (ushort)dwPackSize;
  			classSize = ulClassSize;
  			return true;
  		}
  
  		public unsafe static uint? GetRVA(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  
  			int hr = mdi.GetRVA(token, out uint ulCodeRVA, IntPtr.Zero);
  			if (hr != 0)
  				return null;
  			return ulCodeRVA;
  		}
  
  		public unsafe static uint[] GetPermissionSetTokens(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return Array.Empty<uint>();
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi.EnumPermissionSets(ref iter, token, 0, IntPtr.Zero, 0, out uint cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var tokens = new uint[ulCount];
  				fixed (uint* p = &tokens[0])
  					hr = mdi.EnumPermissionSets(ref iter, token, 0, new IntPtr(p), (uint)tokens.Length, out cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return tokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi.CloseEnum(iter);
  			}
  		}
  
  		public unsafe static (IntPtr addr, uint size, uint action) GetPermissionSetBlob(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return (IntPtr.Zero, 0, 0);
  			IntPtr pvPermission;
  			uint cbPermission;
  			uint dwAction;
  			int hr = mdi.GetPermissionSetProps(token, new IntPtr(&dwAction), new IntPtr(&pvPermission), new IntPtr(&cbPermission));
  			if (hr != 0 || pvPermission == IntPtr.Zero)
  				return (IntPtr.Zero, 0, 0);
  
  			return (pvPermission, cbPermission, dwAction);
  		}
  
  		public unsafe static bool GetPinvokeMapProps(IMetaDataImport2 mdi, uint token, out DmdPInvokeAttributes attrs, out uint moduleToken) {
  			attrs = 0;
  			moduleToken = 0;
  			if (mdi is null)
  				return false;
  
  			uint dwMappingFlags, mrImportDLL;
  			int hr = mdi.GetPinvokeMap(token, new IntPtr(&dwMappingFlags), IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&mrImportDLL));
  			if (hr != 0)
  				return false;
  			attrs = (DmdPInvokeAttributes)dwMappingFlags;
  			moduleToken = mrImportDLL;
  			return true;
  		}
  
  		public unsafe static string? GetPinvokeMapName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			char[]? nameBuf = null;
  			uint chImportName;
  			int hr = mdi.GetPinvokeMap(token, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chImportName), IntPtr.Zero);
  			if (hr >= 0 && chImportName != 0) {
  				nameBuf = new char[chImportName];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetPinvokeMap(token, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chImportName), IntPtr.Zero);
  			}
  			if (hr < 0)
  				return null;
  
  			return chImportName <= 1 ? string.Empty : new string(nameBuf!, 0, (int)chImportName - 1);
  		}
  
  		public unsafe static string? GetMemberRefName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			char[]? nameBuf = null;
  			uint chMember;
  			int hr = mdi.GetMemberRefProps(token, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chMember), IntPtr.Zero, IntPtr.Zero);
  			if (hr >= 0 && chMember != 0) {
  				nameBuf = new char[chMember];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetMemberRefProps(token, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chMember), IntPtr.Zero, IntPtr.Zero);
  			}
  			if (hr < 0)
  				return null;
  
  			return chMember <= 1 ? string.Empty : new string(nameBuf!, 0, (int)chMember - 1);
  		}
  
  		public unsafe static uint GetMemberRefClassToken(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			uint tk;
  			int hr = mdi.GetMemberRefProps(token, new IntPtr(&tk), IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			return hr != 0 ? 0 : tk;
  		}
  
  		public unsafe static (IntPtr addr, uint size) GetMemberRefSignatureBlob(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return (IntPtr.Zero, 0);
  			IntPtr pvSigBlob;
  			uint cbSig;
  			int hr = mdi.GetMemberRefProps(token, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&pvSigBlob), new IntPtr(&cbSig));
  			if (hr != 0 || pvSigBlob == IntPtr.Zero)
  				return (IntPtr.Zero, 0);
  
  			return (pvSigBlob, cbSig);
  		}
  
  		public unsafe static uint GetMethodOwnerRid(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			if (IsGlobal(mdi, token))
  				return 1;
  			uint ownerToken;
  			int hr = mdi.GetMethodProps(token, new IntPtr(&ownerToken), IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr != 0)
  				return 0;
  			var ownerMdToken = new MDToken(ownerToken);
  			return ownerMdToken.Table == Table.TypeDef ? ownerMdToken.Rid : 0;
  		}
  
  		public unsafe static uint[] GetMethodTokens(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return Array.Empty<uint>();
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi.EnumMethods(ref iter, token, IntPtr.Zero, 0, out uint cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var tokens = new uint[ulCount];
  				fixed (uint* p = &tokens[0])
  					hr = mdi.EnumMethods(ref iter, token, new IntPtr(p), (uint)tokens.Length, out cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return tokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi.CloseEnum(iter);
  			}
  		}
  
  		public unsafe static (IntPtr addr, uint size) GetMethodSignatureBlob(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return (IntPtr.Zero, 0);
  			uint cbSigBlob;
  			IntPtr pvSigBlob;
  			int hr = mdi.GetMethodProps(token, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, new IntPtr(&pvSigBlob), new IntPtr(&cbSigBlob), IntPtr.Zero, IntPtr.Zero);
  			if (hr < 0 || pvSigBlob == IntPtr.Zero)
  				return (IntPtr.Zero, 0);
  
  			return (pvSigBlob, cbSigBlob);
  		}
  
  		public static unsafe string? GetMethodName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			char[]? nameBuf = null;
  			uint chMethod;
  			int hr = mdi.GetMethodProps(token, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chMethod), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr >= 0 && chMethod != 0) {
  				nameBuf = new char[chMethod];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetMethodProps(token, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chMethod), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			}
  			if (hr < 0)
  				return null;
  
  			if (chMethod <= 1)
  				return string.Empty;
  			return new string(nameBuf!, 0, (int)chMethod - 1);
  		}
  
  		public static unsafe bool GetMethodAttributes(IMetaDataImport2 mdi, uint token, out DmdMethodAttributes dwAttr, out DmdMethodImplAttributes dwImplFlags) {
  			dwAttr = 0;
  			dwImplFlags = 0;
  			if (mdi is null)
  				return false;
  			uint dwAttr2, dwImplFlags2;
  			int hr = mdi.GetMethodProps(token, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&dwAttr2), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, new IntPtr(&dwImplFlags2));
  			if (hr < 0)
  				return false;
  			dwAttr = (DmdMethodAttributes)dwAttr2;
  			dwImplFlags = (DmdMethodImplAttributes)dwImplFlags2;
  			return true;
  		}
  
  		public unsafe static uint[] GetInterfaceImplTokens(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return Array.Empty<uint>();
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi.EnumInterfaceImpls(ref iter, token, IntPtr.Zero, 0, out uint cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var tokens = new uint[ulCount];
  				fixed (uint* p = &tokens[0])
  					hr = mdi.EnumInterfaceImpls(ref iter, token, new IntPtr(p), (uint)tokens.Length, out cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return tokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi.CloseEnum(iter);
  			}
  		}
  
  		public unsafe static uint GetInterfaceImplInterfaceToken(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  
  			uint tkIface;
  			int hr = mdi.GetInterfaceImplProps(token, IntPtr.Zero, new IntPtr(&tkIface));
  			return hr == 0 ? tkIface : 0;
  		}
  
  		public unsafe static uint[] GetParamTokens(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return Array.Empty<uint>();
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi.EnumParams(ref iter, token, IntPtr.Zero, 0, out uint cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var tokens = new uint[ulCount];
  				fixed (uint* p = &tokens[0])
  					hr = mdi.EnumParams(ref iter, token, new IntPtr(p), (uint)tokens.Length, out cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return tokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi.CloseEnum(iter);
  			}
  		}
  
  		public unsafe static string? GetParamName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			char[]? nameBuf = null;
  			uint chName;
  			int hr = mdi.GetParamProps(token, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chName), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr >= 0 && chName != 0) {
  				nameBuf = new char[chName];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetParamProps(token, IntPtr.Zero, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chName), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			}
  			if (hr < 0)
  				return null;
  
  			return chName <= 1 ? string.Empty : new string(nameBuf!, 0, (int)chName - 1);
  		}
  
  		public unsafe static bool GetParamSeqAndAttrs(IMetaDataImport2 mdi, uint token, out uint seq, out DmdParameterAttributes attrs) {
  			seq = uint.MaxValue;
  			attrs = 0;
  			if (mdi is null)
  				return false;
  
  			uint ulSequence, dwAttr;
  			int hr = mdi.GetParamProps(token, IntPtr.Zero, new IntPtr(&ulSequence), IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&dwAttr), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr != 0)
  				return false;
  
  			seq = ulSequence;
  			attrs = (DmdParameterAttributes)dwAttr;
  			return true;
  		}
  
  		public unsafe static object? GetParamConstant(IMetaDataImport2 mdi, uint token, out ElementType constantType) {
  			constantType = ElementType.End;
  			if (mdi is null)
  				return null;
  			uint cchValue;
  			IntPtr pValue;
  			ElementType constantTypeTmp;
  			int hr = mdi.GetParamProps(token, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, new IntPtr(&constantTypeTmp), new IntPtr(&pValue), new IntPtr(&cchValue));
  			if (hr < 0 || pValue == IntPtr.Zero)
  				return null;
  			constantType = constantTypeTmp;
  			return ReadConstant(pValue, cchValue, constantType);
  		}
  
  		public static unsafe string? GetTypeRefName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			uint chName;
  			char[]? nameBuf = null;
  			int hr = mdi.GetTypeRefProps(token, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chName));
  			if (hr >= 0 && chName != 0) {
  				nameBuf = new char[chName];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetTypeRefProps(token, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chName));
  			}
  			if (hr < 0)
  				return null;
  
  			if (chName <= 1)
  				return string.Empty;
  			return new string(nameBuf!, 0, (int)chName - 1);
  		}
  
  		public static unsafe uint GetTypeRefResolutionScope(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			uint tkResolutionScope;
  			int hr = mdi.GetTypeRefProps(token, new IntPtr(&tkResolutionScope), IntPtr.Zero, 0, IntPtr.Zero);
  			return hr == 0 ? tkResolutionScope : 0;
  		}
  
  		public unsafe static uint[] GetTypeDefTokens(IMetaDataImport2 mdi) {
  			if (mdi is null)
  				return Array.Empty<uint>();
  
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi.EnumTypeDefs(ref iter, IntPtr.Zero, 0, out uint count);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi.CountEnum(iter, ref ulCount);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				hr = mdi.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				// The global type isn't included
  				var tokens = new uint[ulCount + 1];
  				if (tokens.Length > 1) {
  					fixed (uint* p = &tokens[1])
  						hr = mdi.EnumTypeDefs(ref iter, new IntPtr(p), ulCount, out count);
  				}
  				if (hr < 0)
  					return Array.Empty<uint>();
  				tokens[0] = 0x02000001;
  				return tokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi.CloseEnum(iter);
  			}
  		}
  
  		public unsafe static uint GetTypeDefExtends(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			uint tkExtends;
  			int hr = mdi.GetTypeDefProps(token, IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, new IntPtr(&tkExtends));
  			return hr != 0 ? 0 : tkExtends;
  		}
  
  		public static unsafe string? GetTypeDefName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			uint chTypeDef;
  			char[]? nameBuf = null;
  			int hr = mdi.GetTypeDefProps(token, IntPtr.Zero, 0, new IntPtr(&chTypeDef), IntPtr.Zero, IntPtr.Zero);
  			if (hr >= 0 && chTypeDef != 0) {
  				nameBuf = new char[chTypeDef];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetTypeDefProps(token, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chTypeDef), IntPtr.Zero, IntPtr.Zero);
  			}
  			if (hr < 0)
  				return null;
  
  			if (chTypeDef <= 1)
  				return string.Empty;
  			return new string(nameBuf!, 0, (int)chTypeDef - 1);
  		}
  
  		public static unsafe DmdTypeAttributes? GetTypeDefAttributes(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			uint dwTypeDefFlags;
  			int hr = mdi.GetTypeDefProps(token, IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&dwTypeDefFlags), IntPtr.Zero);
  			return hr == 0 ? (DmdTypeAttributes)dwTypeDefFlags : (DmdTypeAttributes?)null;
  		}
  
  		public static unsafe uint GetTypeDefEnclosingType(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			uint dwTypeDefFlags;
  			int hr = mdi.GetTypeDefProps(token, IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&dwTypeDefFlags), IntPtr.Zero);
  			if (hr != 0)
  				return 0;
  
  			if ((dwTypeDefFlags & 7) >= 2) {
  				hr = mdi.GetNestedClassProps(token, out uint enclType);
  				if (hr == 0)
  					return enclType;
  			}
  
  			return 0;
  		}
  
  		public static unsafe string? GetGenericParamName(IMetaDataImport2 mdi2, uint token) {
  			if (mdi2 is null)
  				return null;
  
  			char[]? nameBuf = null;
  			uint chName;
  			int hr = mdi2.GetGenericParamProps(token, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chName));
  			if (hr >= 0 && chName != 0) {
  				nameBuf = new char[chName];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi2.GetGenericParamProps(token, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chName));
  			}
  			if (hr < 0)
  				return null;
  
  			if (chName <= 1)
  				return string.Empty;
  			return new string(nameBuf!, 0, (int)chName - 1);
  		}
  
  		public static unsafe bool GetGenericParamNumAndAttrs(IMetaDataImport2 mdi2, uint token, out ushort number, out DmdGenericParameterAttributes attrs) {
  			number = ushort.MaxValue;
  			attrs = 0;
  			if (mdi2 is null)
  				return false;
  
  			uint ulParamSeq, dwParamFlags;
  			int hr = mdi2.GetGenericParamProps(token, new IntPtr(&ulParamSeq), new IntPtr(&dwParamFlags), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero);
  			if (hr != 0)
  				return false;
  
  			number = (ushort)ulParamSeq;
  			attrs = (DmdGenericParameterAttributes)dwParamFlags;
  			return true;
  		}
  
  		public unsafe static uint[] GetGenericParamTokens(IMetaDataImport2 mdi2, uint token) {
  			if (mdi2 is null)
  				return Array.Empty<uint>();
  
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi2.EnumGenericParams(ref iter, token, IntPtr.Zero, 0, out uint cGenericParams);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi2.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi2.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var gpTokens = new uint[ulCount];
  				fixed (uint* p = &gpTokens[0])
  					hr = mdi2.EnumGenericParams(ref iter, token, new IntPtr(p), (uint)gpTokens.Length, out cGenericParams);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return gpTokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi2.CloseEnum(iter);
  			}
  		}
  
  		public unsafe static uint GetGenericParamConstraintTypeToken(IMetaDataImport2 mdi2, uint token) {
  			if (mdi2 is null)
  				return 0;
  			uint tkConstraintType;
  			int hr = mdi2.GetGenericParamConstraintProps(token, IntPtr.Zero, new IntPtr(&tkConstraintType));
  			if (hr != 0)
  				return 0;
  			return tkConstraintType;
  		}
  
  		public unsafe static uint[] GetGenericParamConstraintTokens(IMetaDataImport2 mdi2, uint token) {
  			if (mdi2 is null)
  				return Array.Empty<uint>();
  
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi2.EnumGenericParamConstraints(ref iter, token, IntPtr.Zero, 0, out uint cGenericParamConstraints);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi2.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi2.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var gpcTokens = new uint[ulCount];
  				fixed (uint* p = &gpcTokens[0])
  					hr = mdi2.EnumGenericParamConstraints(ref iter, token, new IntPtr(p), (uint)gpcTokens.Length, out cGenericParamConstraints);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return gpcTokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi2.CloseEnum(iter);
  			}
  		}
  
  		public static unsafe (IntPtr addr, uint size) GetStandAloneSigBlob(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return (IntPtr.Zero, 0);
  
  			int hr = mdi.GetSigFromToken(token, out var pvSig, out uint cbSig);
  			if (hr < 0 || pvSig == IntPtr.Zero)
  				return (IntPtr.Zero, 0);
  			return (pvSig, cbSig);
  		}
  
  		public unsafe static COR_FIELD_OFFSET[]? GetFieldOffsets(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			if ((token & 0x00FFFFFF) == 0)
  				return null;
  
  			int cFieldOffset = 0;
  			int hr = mdi.GetClassLayout(token, IntPtr.Zero, null, 0, new IntPtr(&cFieldOffset), IntPtr.Zero);
  			Debug.Assert(hr == 0 || hr == CLDB_E_RECORD_NOTFOUND || hr == CLDB_E_INDEX_NOTFOUND);
  			var fieldOffsets = cFieldOffset == 0 ? Array.Empty<COR_FIELD_OFFSET>() : new COR_FIELD_OFFSET[cFieldOffset];
  			if (hr == 0 && fieldOffsets.Length != 0)
  				hr = mdi.GetClassLayout(token, IntPtr.Zero, fieldOffsets, fieldOffsets.Length, new IntPtr(&cFieldOffset), IntPtr.Zero);
  			return hr != 0 ? null : fieldOffsets;
  		}
  
  		public static uint? GetFieldOffset(IMetaDataImport2 mdi, uint token, uint fieldToken) {
  			var offsets = GetFieldOffsets(mdi, token);
  			if (offsets is null)
  				return null;
  			foreach (var info in offsets) {
  				if (info.FieldToken == fieldToken) {
  					if (info.Offset != uint.MaxValue)
  						return info.Offset;
  					return null;
  				}
  			}
  			return null;
  		}
  
  		public unsafe static (IntPtr addr, uint size) GetFieldMarshalBlob(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return (IntPtr.Zero, 0);
  			if ((token & 0x00FFFFFF) == 0)
  				return (IntPtr.Zero, 0);
  
  			int hr = mdi.GetFieldMarshal(token, out var pvNativeType, out uint cbNativeType);
  			Debug.Assert(hr == 0 || hr == CLDB_E_RECORD_NOTFOUND);
  			if (hr != 0)
  				return (IntPtr.Zero, 0);
  
  			return (pvNativeType, cbNativeType);
  		}
  
  		public unsafe static uint GetFieldOwnerRid(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			if (IsGlobal(mdi, token))
  				return 1;
  			uint ownerToken;
  			int hr = mdi.GetFieldProps(token, new IntPtr(&ownerToken), IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr != 0)
  				return 0;
  			var ownerMdToken = new MDToken(ownerToken);
  			return ownerMdToken.Table == Table.TypeDef ? ownerMdToken.Rid : 0;
  		}
  
  		public static unsafe string? GetFieldName(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return null;
  			uint chField = 0, dwAttr = 0;
  			char[]? nameBuf = null;
  			int hr = mdi.GetFieldProps(token, IntPtr.Zero, IntPtr.Zero, 0, new IntPtr(&chField), new IntPtr(&dwAttr), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr >= 0 && chField != 0) {
  				nameBuf = new char[chField];
  				fixed (char* p = &nameBuf[0])
  					hr = mdi.GetFieldProps(token, IntPtr.Zero, new IntPtr(p), (uint)nameBuf.Length, new IntPtr(&chField), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			}
  			if (hr < 0)
  				return null;
  
  			if (chField <= 1)
  				return string.Empty;
  			return new string(nameBuf!, 0, (int)chField - 1);
  		}
  
  		public unsafe static uint[] GetFieldTokens(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return Array.Empty<uint>();
  			var iter = IntPtr.Zero;
  			try {
  				int hr = mdi.EnumFields(ref iter, token, IntPtr.Zero, 0, out uint cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				uint ulCount = 0;
  				hr = mdi.CountEnum(iter, ref ulCount);
  				if (hr < 0 || ulCount == 0)
  					return Array.Empty<uint>();
  
  				hr = mdi.ResetEnum(iter, 0);
  				if (hr < 0)
  					return Array.Empty<uint>();
  
  				var tokens = new uint[ulCount];
  				fixed (uint* p = &tokens[0])
  					hr = mdi.EnumFields(ref iter, token, new IntPtr(p), (uint)tokens.Length, out cTokens);
  				if (hr < 0)
  					return Array.Empty<uint>();
  				return tokens;
  			}
  			finally {
  				if (iter != IntPtr.Zero)
  					mdi.CloseEnum(iter);
  			}
  		}
  
  		public static unsafe (IntPtr addr, uint size) GetFieldSignatureBlob(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return (IntPtr.Zero, 0);
  
  			uint sigLen = 0;
  			IntPtr sigAddr;
  			int hr = mdi.GetFieldProps(token, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, new IntPtr(&sigAddr), new IntPtr(&sigLen), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			if (hr < 0 || sigAddr == IntPtr.Zero)
  				return (IntPtr.Zero, 0);
  
  			return (sigAddr, sigLen);
  		}
  
  		public unsafe static DmdFieldAttributes GetFieldAttributes(IMetaDataImport2 mdi, uint token) {
  			if (mdi is null)
  				return 0;
  			uint dwAttr;
  			int hr = mdi.GetFieldProps(token, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, new IntPtr(&dwAttr), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
  			Debug.Assert(hr == 0);
  			return hr < 0 ? 0 : (DmdFieldAttributes)dwAttr;
  		}
  
  		public unsafe static object? GetFieldConstant(IMetaDataImport2 mdi, uint token, out ElementType constantType) {
  			constantType = ElementType.End;
  			if (mdi is null)
  				return null;
  			uint cchValue;
  			IntPtr pValue;
  			ElementType constantTypeTmp;
  			int hr = mdi.GetFieldProps(token, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, new IntPtr(&constantTypeTmp), new IntPtr(&pValue), new IntPtr(&cchValue));
  			if (hr < 0 || pValue == IntPtr.Zero)
  				return null;
  			constantType = constantTypeTmp;
  			return ReadConstant(pValue, cchValue, constantType);
  		}
  
  		unsafe static object? ReadConstant(IntPtr addr, uint size, ElementType elementType) {
  			var p = (byte*)addr;
  			if (p is null)
  				return null;
  
  			// size is always 0 unless it's a string...
  			switch (elementType) {
  			case ElementType.Boolean:	return *p != 0;
  			case ElementType.Char:		return *(char*)p;
  			case ElementType.I1:		return *(sbyte*)p;
  			case ElementType.U1:		return *p;
  			case ElementType.I2:		return *(short*)p;
  			case ElementType.U2:		return *(ushort*)p;
  			case ElementType.I4:		return *(int*)p;
  			case ElementType.U4:		return *(uint*)p;
  			case ElementType.I8:		return *(long*)p;
  			case ElementType.U8:		return *(ulong*)p;
  
gt: 			case ElementType.R4:		return *(float*)p;