mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
734 lines
23 KiB
734 lines
23 KiB
// Copyright (c) 2018 Siegfried Pammer |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
#nullable enable |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics.CodeAnalysis; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Reflection.Metadata; |
|
using System.Reflection.PortableExecutable; |
|
using System.Text; |
|
using System.Text.RegularExpressions; |
|
using System.Threading.Tasks; |
|
|
|
namespace ICSharpCode.Decompiler.Metadata |
|
{ |
|
public enum TargetFrameworkIdentifier |
|
{ |
|
NETFramework, |
|
NETCoreApp, |
|
NETStandard, |
|
Silverlight, |
|
NET |
|
} |
|
|
|
enum DecompilerRuntime |
|
{ |
|
NETFramework, |
|
NETCoreApp, |
|
Mono |
|
} |
|
|
|
/// <summary> |
|
/// Used to resolve assemblies referenced by an assembly. |
|
/// </summary> |
|
public class UniversalAssemblyResolver : AssemblyReferenceClassifier, IAssemblyResolver |
|
{ |
|
static UniversalAssemblyResolver() |
|
{ |
|
// TODO : test whether this works with Mono on *Windows*, not sure if we'll |
|
// ever need this... |
|
if (Type.GetType("Mono.Runtime") != null) |
|
decompilerRuntime = DecompilerRuntime.Mono; |
|
else if (typeof(object).Assembly.GetName().Name == "System.Private.CoreLib") |
|
decompilerRuntime = DecompilerRuntime.NETCoreApp; |
|
else if (Environment.OSVersion.Platform == PlatformID.Unix) |
|
decompilerRuntime = DecompilerRuntime.Mono; |
|
} |
|
|
|
readonly Lazy<DotNetCorePathFinder> dotNetCorePathFinder; |
|
readonly bool throwOnError; |
|
readonly PEStreamOptions streamOptions; |
|
readonly MetadataReaderOptions metadataOptions; |
|
readonly string? mainAssemblyFileName; |
|
readonly string? baseDirectory; |
|
readonly List<string?> directories = new List<string?>(); |
|
static readonly List<string> gac_paths = GetGacPaths(); |
|
static readonly DecompilerRuntime decompilerRuntime; |
|
|
|
public void AddSearchDirectory(string? directory) |
|
{ |
|
directories.Add(directory); |
|
if (dotNetCorePathFinder.IsValueCreated) |
|
{ |
|
dotNetCorePathFinder.Value.AddSearchDirectory(directory); |
|
} |
|
} |
|
|
|
public void RemoveSearchDirectory(string? directory) |
|
{ |
|
directories.Remove(directory); |
|
if (dotNetCorePathFinder.IsValueCreated) |
|
{ |
|
dotNetCorePathFinder.Value.RemoveSearchDirectory(directory); |
|
} |
|
} |
|
|
|
public string?[] GetSearchDirectories() |
|
{ |
|
return directories.ToArray(); |
|
} |
|
|
|
readonly string targetFramework; |
|
readonly string runtimePack; |
|
readonly TargetFrameworkIdentifier targetFrameworkIdentifier; |
|
readonly Version targetFrameworkVersion; |
|
|
|
/// <summary> |
|
/// Creates a new instance of the <see cref="UniversalAssemblyResolver"/>. |
|
/// </summary> |
|
/// <param name="mainAssemblyFileName"> |
|
/// The full path to the "main assembly" (i.e., the assembly being decompiled). This is used to |
|
/// resolve assemblies that are located next the main assembly. If no full path is used, the resolver |
|
/// falls back to using <see cref="Environment.CurrentDirectory"/>. |
|
/// </param> |
|
/// <param name="throwOnError"> |
|
/// If <see langword="true"/> an <see cref="ResolutionException"/> is thrown, in case the |
|
/// assembly reference cannot be resolved. |
|
/// </param> |
|
/// <param name="targetFramework"> |
|
/// The target framework name as used by <see cref="System.Runtime.Versioning.TargetFrameworkAttribute"/>. |
|
/// That is, "{framework},Version={version}": currently it supports ".NETCoreApp", ".NETStandard" and |
|
/// "Silverlight", if the string doesn't match any of these, the resolver falls back to ".NET Framework", |
|
/// which is "classic" .NET <= 4.8. |
|
/// </param> |
|
/// <param name="runtimePack"> |
|
/// Identifier of the runtime pack this assembly was compiled for. |
|
/// If omitted, falling back to "Microsoft.NETCore.App" and this is ignored in case of classic .NET</param> |
|
/// <param name="streamOptions">Options used for the <see cref="PEReader"/>.</param> |
|
/// <param name="metadataOptions">Options used for the <see cref="MetadataReader"/>.</param> |
|
public UniversalAssemblyResolver(string? mainAssemblyFileName, bool throwOnError, string? targetFramework, |
|
string? runtimePack = null, PEStreamOptions streamOptions = PEStreamOptions.Default, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default) |
|
{ |
|
this.mainAssemblyFileName = mainAssemblyFileName; |
|
this.throwOnError = throwOnError; |
|
this.streamOptions = streamOptions; |
|
this.metadataOptions = metadataOptions; |
|
this.targetFramework = targetFramework ?? string.Empty; |
|
this.runtimePack = runtimePack ?? "Microsoft.NETCore.App"; |
|
(targetFrameworkIdentifier, targetFrameworkVersion) = ParseTargetFramework(this.targetFramework); |
|
this.dotNetCorePathFinder = new Lazy<DotNetCorePathFinder>(InitDotNetCorePathFinder); |
|
if (mainAssemblyFileName != null) |
|
{ |
|
string baseDirectory = Path.GetDirectoryName(mainAssemblyFileName); |
|
if (string.IsNullOrWhiteSpace(this.baseDirectory)) |
|
this.baseDirectory = Environment.CurrentDirectory; |
|
AddSearchDirectory(baseDirectory); |
|
} |
|
} |
|
|
|
internal static (TargetFrameworkIdentifier, Version) ParseTargetFramework(string targetFramework) |
|
{ |
|
string[] tokens = targetFramework.Split(','); |
|
TargetFrameworkIdentifier identifier; |
|
|
|
switch (tokens[0].Trim().ToUpperInvariant()) |
|
{ |
|
case ".NETCOREAPP": |
|
identifier = TargetFrameworkIdentifier.NETCoreApp; |
|
break; |
|
case ".NETSTANDARD": |
|
identifier = TargetFrameworkIdentifier.NETStandard; |
|
break; |
|
case "SILVERLIGHT": |
|
identifier = TargetFrameworkIdentifier.Silverlight; |
|
break; |
|
default: |
|
identifier = TargetFrameworkIdentifier.NETFramework; |
|
break; |
|
} |
|
|
|
Version? version = null; |
|
|
|
for (int i = 1; i < tokens.Length; i++) |
|
{ |
|
var pair = tokens[i].Trim().Split('='); |
|
|
|
if (pair.Length != 2) |
|
continue; |
|
|
|
switch (pair[0].Trim().ToUpperInvariant()) |
|
{ |
|
case "VERSION": |
|
var versionString = pair[1].TrimStart('v', ' ', '\t'); |
|
if (identifier == TargetFrameworkIdentifier.NETCoreApp || |
|
identifier == TargetFrameworkIdentifier.NETStandard) |
|
{ |
|
if (versionString.Length == 3) |
|
versionString += ".0"; |
|
} |
|
if (!Version.TryParse(versionString, out version)) |
|
version = null; |
|
// .NET 5 or greater still use ".NETCOREAPP" as TargetFrameworkAttribute value... |
|
if (version?.Major >= 5 && identifier == TargetFrameworkIdentifier.NETCoreApp) |
|
identifier = TargetFrameworkIdentifier.NET; |
|
break; |
|
} |
|
} |
|
|
|
return (identifier, version ?? ZeroVersion); |
|
} |
|
|
|
#if !VSADDIN |
|
public PEFile? Resolve(IAssemblyReference name) |
|
{ |
|
var file = FindAssemblyFile(name); |
|
return CreatePEFileFromFileName(file, ex => new ResolutionException(name, file, ex)); |
|
} |
|
|
|
public PEFile? ResolveModule(PEFile mainModule, string moduleName) |
|
{ |
|
string baseDirectory = Path.GetDirectoryName(mainModule.FileName); |
|
string moduleFileName = Path.Combine(baseDirectory, moduleName); |
|
return CreatePEFileFromFileName(moduleFileName, ex => new ResolutionException(mainModule.FileName, moduleName, moduleFileName, ex)); |
|
} |
|
|
|
private PEFile? CreatePEFileFromFileName(string? fileName, Func<Exception?, Exception> makeException) |
|
{ |
|
if (fileName == null) |
|
{ |
|
if (throwOnError) |
|
throw makeException(null); |
|
return null; |
|
} |
|
|
|
try |
|
{ |
|
FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); |
|
return new PEFile(fileName, stream, streamOptions, metadataOptions); |
|
} |
|
catch (BadImageFormatException ex) |
|
{ |
|
if (throwOnError) |
|
throw makeException(ex); |
|
} |
|
catch (IOException ex) |
|
{ |
|
if (throwOnError) |
|
throw makeException(ex); |
|
} |
|
return null; |
|
} |
|
|
|
public Task<PEFile?> ResolveAsync(IAssemblyReference name) |
|
{ |
|
return Task.Run(() => Resolve(name)); |
|
} |
|
|
|
public Task<PEFile?> ResolveModuleAsync(PEFile mainModule, string moduleName) |
|
{ |
|
return Task.Run(() => ResolveModule(mainModule, moduleName)); |
|
} |
|
#endif |
|
|
|
public override bool IsSharedAssembly(IAssemblyReference reference, [NotNullWhen(true)] out string? runtimePack) |
|
{ |
|
return dotNetCorePathFinder.Value.TryResolveDotNetCoreShared(reference, out runtimePack) != null; |
|
} |
|
|
|
public string? FindAssemblyFile(IAssemblyReference name) |
|
{ |
|
if (name.IsWindowsRuntime) |
|
{ |
|
return FindWindowsMetadataFile(name); |
|
} |
|
|
|
string? file; |
|
switch (targetFrameworkIdentifier) |
|
{ |
|
case TargetFrameworkIdentifier.NET: |
|
case TargetFrameworkIdentifier.NETCoreApp: |
|
case TargetFrameworkIdentifier.NETStandard: |
|
if (IsZeroOrAllOnes(targetFrameworkVersion)) |
|
goto default; |
|
file = dotNetCorePathFinder.Value.TryResolveDotNetCore(name); |
|
if (file != null) |
|
return file; |
|
goto default; |
|
case TargetFrameworkIdentifier.Silverlight: |
|
if (IsZeroOrAllOnes(targetFrameworkVersion)) |
|
goto default; |
|
file = ResolveSilverlight(name, targetFrameworkVersion); |
|
if (file != null) |
|
return file; |
|
goto default; |
|
default: |
|
return ResolveInternal(name); |
|
} |
|
} |
|
|
|
DotNetCorePathFinder InitDotNetCorePathFinder() |
|
{ |
|
DotNetCorePathFinder dotNetCorePathFinder; |
|
if (mainAssemblyFileName == null) |
|
dotNetCorePathFinder = new DotNetCorePathFinder(targetFrameworkIdentifier, targetFrameworkVersion, runtimePack); |
|
else |
|
dotNetCorePathFinder = new DotNetCorePathFinder(mainAssemblyFileName, targetFramework, runtimePack, targetFrameworkIdentifier, targetFrameworkVersion); |
|
foreach (var directory in directories) |
|
{ |
|
dotNetCorePathFinder.AddSearchDirectory(directory); |
|
} |
|
return dotNetCorePathFinder; |
|
} |
|
|
|
string? FindWindowsMetadataFile(IAssemblyReference name) |
|
{ |
|
// Finding Windows Metadata (winmd) is currently only supported on Windows. |
|
if (Environment.OSVersion.Platform != PlatformID.Win32NT) |
|
return null; |
|
|
|
// TODO : Find a way to detect the base directory for the required Windows SDK. |
|
string? basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Windows Kits", "10", "References"); |
|
|
|
if (!Directory.Exists(basePath)) |
|
return FindWindowsMetadataInSystemDirectory(name); |
|
|
|
// TODO : Find a way to detect the required Windows SDK version. |
|
var di = new DirectoryInfo(basePath); |
|
basePath = null; |
|
foreach (var versionFolder in di.EnumerateDirectories()) |
|
{ |
|
basePath = versionFolder.FullName; |
|
} |
|
|
|
if (basePath == null) |
|
return FindWindowsMetadataInSystemDirectory(name); |
|
|
|
basePath = Path.Combine(basePath, name.Name); |
|
|
|
if (!Directory.Exists(basePath)) |
|
return FindWindowsMetadataInSystemDirectory(name); |
|
|
|
basePath = Path.Combine(basePath, FindClosestVersionDirectory(basePath, name.Version)); |
|
|
|
if (!Directory.Exists(basePath)) |
|
return FindWindowsMetadataInSystemDirectory(name); |
|
|
|
string file = Path.Combine(basePath, name.Name + ".winmd"); |
|
|
|
if (!File.Exists(file)) |
|
return FindWindowsMetadataInSystemDirectory(name); |
|
|
|
return file; |
|
} |
|
|
|
string? FindWindowsMetadataInSystemDirectory(IAssemblyReference name) |
|
{ |
|
string file = Path.Combine(Environment.SystemDirectory, "WinMetadata", name.Name + ".winmd"); |
|
if (File.Exists(file)) |
|
return file; |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// This only works on Windows |
|
/// </summary> |
|
string? ResolveSilverlight(IAssemblyReference name, Version? version) |
|
{ |
|
string[] targetFrameworkSearchPaths = { |
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft Silverlight"), |
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Silverlight") |
|
}; |
|
|
|
foreach (var baseDirectory in targetFrameworkSearchPaths) |
|
{ |
|
if (!Directory.Exists(baseDirectory)) |
|
continue; |
|
var versionDirectory = Path.Combine(baseDirectory, FindClosestVersionDirectory(baseDirectory, version)); |
|
var file = SearchDirectory(name, versionDirectory); |
|
if (file != null) |
|
return file; |
|
} |
|
return null; |
|
} |
|
|
|
string FindClosestVersionDirectory(string? basePath, Version? version) |
|
{ |
|
string? path = null; |
|
foreach (var folder in new DirectoryInfo(basePath).GetDirectories().Select(d => DotNetCorePathFinder.ConvertToVersion(d.Name)) |
|
.Where(v => v.Item1 != null).OrderByDescending(v => v.Item1)) |
|
{ |
|
if (path == null || version == null || folder.Item1 >= version) |
|
path = folder.Item2; |
|
} |
|
return path ?? version?.ToString() ?? "."; |
|
} |
|
|
|
string? ResolveInternal(IAssemblyReference name) |
|
{ |
|
if (name == null) |
|
throw new ArgumentNullException(nameof(name)); |
|
|
|
var assembly = SearchDirectory(name, directories); |
|
if (assembly != null) |
|
return assembly; |
|
|
|
var framework_dir = Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName); |
|
var framework_dirs = decompilerRuntime == DecompilerRuntime.Mono |
|
? new[] { framework_dir, Path.Combine(framework_dir, "Facades") } |
|
: new[] { framework_dir }; |
|
|
|
if (IsSpecialVersionOrRetargetable(name)) |
|
{ |
|
assembly = SearchDirectory(name, framework_dirs); |
|
if (assembly != null) |
|
return assembly; |
|
} |
|
|
|
if (name.Name == "mscorlib") |
|
{ |
|
assembly = GetCorlib(name); |
|
if (assembly != null) |
|
return assembly; |
|
} |
|
|
|
assembly = GetAssemblyInGac(name); |
|
if (assembly != null) |
|
return assembly; |
|
|
|
// when decompiling assemblies that target frameworks prior to 4.0, we can fall back to the 4.0 assemblies in case the target framework is not installed. |
|
// but when looking for Microsoft.Build.Framework, Version=15.0.0.0 we should not use the version 4.0 assembly here so that the LoadedAssembly logic can instead fall back to version 15.1.0.0 |
|
if (name.Version <= new Version(4, 0, 0, 0)) |
|
{ |
|
assembly = SearchDirectory(name, framework_dirs); |
|
if (assembly != null) |
|
return assembly; |
|
} |
|
|
|
if (throwOnError) |
|
throw new ResolutionException(name, null, null); |
|
return null; |
|
} |
|
|
|
#region .NET / mono GAC handling |
|
string? SearchDirectory(IAssemblyReference name, IEnumerable<string?> directories) |
|
{ |
|
foreach (var directory in directories) |
|
{ |
|
string? file = SearchDirectory(name, directory); |
|
if (file != null) |
|
return file; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
static bool IsSpecialVersionOrRetargetable(IAssemblyReference reference) |
|
{ |
|
return IsZeroOrAllOnes(reference.Version) || reference.IsRetargetable; |
|
} |
|
|
|
string? SearchDirectory(IAssemblyReference name, string? directory) |
|
{ |
|
var extensions = name.IsWindowsRuntime ? new[] { ".winmd", ".dll" } : new[] { ".dll", ".exe" }; |
|
foreach (var extension in extensions) |
|
{ |
|
string file = Path.Combine(directory, name.Name + extension); |
|
if (!File.Exists(file)) |
|
continue; |
|
try |
|
{ |
|
return file; |
|
} |
|
catch (BadImageFormatException) |
|
{ |
|
continue; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
static bool IsZeroOrAllOnes(Version? version) |
|
{ |
|
return version == null |
|
|| (version.Major == 0 && version.Minor == 0 && version.Build == 0 && version.Revision == 0) |
|
|| (version.Major == 65535 && version.Minor == 65535 && version.Build == 65535 && version.Revision == 65535); |
|
} |
|
|
|
internal static Version ZeroVersion = new Version(0, 0, 0, 0); |
|
|
|
string? GetCorlib(IAssemblyReference reference) |
|
{ |
|
var version = reference.Version; |
|
var corlib = typeof(object).Assembly.GetName(); |
|
|
|
if (decompilerRuntime != DecompilerRuntime.NETCoreApp) |
|
{ |
|
if (corlib.Version == version || IsSpecialVersionOrRetargetable(reference)) |
|
return typeof(object).Module.FullyQualifiedName; |
|
} |
|
|
|
if (reference.PublicKeyToken == null) |
|
return null; |
|
if (version == null) |
|
return null; |
|
|
|
string? path; |
|
if (decompilerRuntime == DecompilerRuntime.Mono) |
|
{ |
|
path = GetMonoMscorlibBasePath(version); |
|
} |
|
else |
|
{ |
|
path = GetMscorlibBasePath(version, reference.PublicKeyToken.ToHexString(8)); |
|
} |
|
|
|
if (path == null) |
|
return null; |
|
|
|
var file = Path.Combine(path, "mscorlib.dll"); |
|
if (File.Exists(file)) |
|
return file; |
|
|
|
return null; |
|
} |
|
|
|
string? GetMscorlibBasePath(Version version, string? publicKeyToken) |
|
{ |
|
string? GetSubFolderForVersion() |
|
{ |
|
switch (version.Major) |
|
{ |
|
case 1: |
|
if (version.MajorRevision == 3300) |
|
return "v1.0.3705"; |
|
return "v1.1.4322"; |
|
case 2: |
|
return "v2.0.50727"; |
|
case 4: |
|
return "v4.0.30319"; |
|
default: |
|
if (throwOnError) |
|
throw new NotSupportedException("Version not supported: " + version); |
|
return null; |
|
} |
|
} |
|
|
|
if (publicKeyToken == "969db8053d3322ac") |
|
{ |
|
string programFiles = Environment.Is64BitOperatingSystem ? |
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) : |
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); |
|
string cfPath = $@"Microsoft.NET\SDK\CompactFramework\v{version.Major}.{version.Minor}\WindowsCE\"; |
|
string cfBasePath = Path.Combine(programFiles, cfPath); |
|
if (Directory.Exists(cfBasePath)) |
|
return cfBasePath; |
|
} |
|
else |
|
{ |
|
string rootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Microsoft.NET"); |
|
string[] frameworkPaths = new[] { |
|
Path.Combine(rootPath, "Framework"), |
|
Path.Combine(rootPath, "Framework64") |
|
}; |
|
|
|
string? folder = GetSubFolderForVersion(); |
|
|
|
if (folder != null) |
|
{ |
|
foreach (var path in frameworkPaths) |
|
{ |
|
var basePath = Path.Combine(path, folder); |
|
if (Directory.Exists(basePath)) |
|
return basePath; |
|
} |
|
} |
|
} |
|
|
|
if (throwOnError) |
|
throw new NotSupportedException("Version not supported: " + version); |
|
return null; |
|
} |
|
|
|
string? GetMonoMscorlibBasePath(Version version) |
|
{ |
|
var path = Directory.GetParent(typeof(object).Module.FullyQualifiedName).Parent.FullName; |
|
if (version.Major == 1) |
|
path = Path.Combine(path, "1.0"); |
|
else if (version.Major == 2) |
|
{ |
|
if (version.MajorRevision == 5) |
|
path = Path.Combine(path, "2.1"); |
|
else |
|
path = Path.Combine(path, "2.0"); |
|
} |
|
else if (version.Major == 4) |
|
path = Path.Combine(path, "4.0"); |
|
else |
|
{ |
|
if (throwOnError) |
|
throw new NotSupportedException("Version not supported: " + version); |
|
return null; |
|
} |
|
return path; |
|
} |
|
|
|
public static List<string> GetGacPaths() |
|
{ |
|
if (decompilerRuntime == DecompilerRuntime.Mono) |
|
return GetDefaultMonoGacPaths(); |
|
|
|
var paths = new List<string>(2); |
|
var windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); |
|
if (windir == null) |
|
return paths; |
|
|
|
paths.Add(Path.Combine(windir, "assembly")); |
|
paths.Add(Path.Combine(windir, "Microsoft.NET", "assembly")); |
|
return paths; |
|
} |
|
|
|
static List<string> GetDefaultMonoGacPaths() |
|
{ |
|
var paths = new List<string>(1); |
|
var gac = GetCurrentMonoGac(); |
|
if (gac != null) |
|
paths.Add(gac); |
|
|
|
var gac_paths_env = Environment.GetEnvironmentVariable("MONO_GAC_PREFIX"); |
|
if (string.IsNullOrEmpty(gac_paths_env)) |
|
return paths; |
|
|
|
var prefixes = gac_paths_env.Split(Path.PathSeparator); |
|
foreach (var prefix in prefixes) |
|
{ |
|
if (string.IsNullOrEmpty(prefix)) |
|
continue; |
|
|
|
var gac_path = Path.Combine(Path.Combine(Path.Combine(prefix, "lib"), "mono"), "gac"); |
|
if (Directory.Exists(gac_path) && !paths.Contains(gac_path)) |
|
paths.Add(gac_path); |
|
} |
|
|
|
return paths; |
|
} |
|
|
|
static string GetCurrentMonoGac() |
|
{ |
|
return Path.Combine( |
|
Directory.GetParent( |
|
Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName)).FullName, |
|
"gac"); |
|
} |
|
|
|
public static string? GetAssemblyInGac(IAssemblyReference reference) |
|
{ |
|
if (reference.PublicKeyToken == null || reference.PublicKeyToken.Length == 0) |
|
return null; |
|
|
|
if (decompilerRuntime == DecompilerRuntime.Mono) |
|
return GetAssemblyInMonoGac(reference); |
|
|
|
return GetAssemblyInNetGac(reference); |
|
} |
|
|
|
static string? GetAssemblyInMonoGac(IAssemblyReference reference) |
|
{ |
|
for (int i = 0; i < gac_paths.Count; i++) |
|
{ |
|
var gac_path = gac_paths[i]; |
|
var file = GetAssemblyFile(reference, string.Empty, gac_path); |
|
if (File.Exists(file)) |
|
return file; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
static string? GetAssemblyInNetGac(IAssemblyReference reference) |
|
{ |
|
var gacs = new[] { "GAC_MSIL", "GAC_32", "GAC_64", "GAC" }; |
|
var prefixes = new[] { string.Empty, "v4.0_" }; |
|
|
|
for (int i = 0; i < gac_paths.Count; i++) |
|
{ |
|
for (int j = 0; j < gacs.Length; j++) |
|
{ |
|
var gac = Path.Combine(gac_paths[i], gacs[j]); |
|
var file = GetAssemblyFile(reference, prefixes[i], gac); |
|
if (Directory.Exists(gac) && File.Exists(file)) |
|
return file; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
static string GetAssemblyFile(IAssemblyReference reference, string? prefix, string? gac) |
|
{ |
|
var gac_folder = new StringBuilder() |
|
.Append(prefix) |
|
.Append(reference.Version); |
|
if (reference.PublicKeyToken != null) |
|
{ |
|
gac_folder.Append("__"); |
|
for (int i = 0; i < reference.PublicKeyToken.Length; i++) |
|
gac_folder.Append(reference.PublicKeyToken[i].ToString("x2")); |
|
} |
|
return Path.Combine( |
|
Path.Combine( |
|
Path.Combine(gac, reference.Name), gac_folder.ToString()), |
|
reference.Name + ".dll"); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the names of all assemblies in the GAC. |
|
/// </summary> |
|
public static IEnumerable<AssemblyNameReference> EnumerateGac() |
|
{ |
|
var gacs = new[] { "GAC_MSIL", "GAC_32", "GAC_64", "GAC" }; |
|
foreach (var path in GetGacPaths()) |
|
{ |
|
foreach (var gac in gacs) |
|
{ |
|
string rootPath = Path.Combine(path, gac); |
|
if (!Directory.Exists(rootPath)) |
|
continue; |
|
foreach (var item in new DirectoryInfo(rootPath).EnumerateFiles("*.dll", SearchOption.AllDirectories)) |
|
{ |
|
string[] name = Path.GetDirectoryName(item.FullName).Substring(rootPath.Length + 1).Split(new[] { "\\" }, StringSplitOptions.RemoveEmptyEntries); |
|
if (name.Length != 2) |
|
continue; |
|
var match = Regex.Match(name[1], $"(v4.0_)?(?<version>[^_]+)_(?<culture>[^_]+)?_(?<publicKey>[^_]+)"); |
|
if (!match.Success) |
|
continue; |
|
string culture = match.Groups["culture"].Value; |
|
if (string.IsNullOrEmpty(culture)) |
|
culture = "neutral"; |
|
yield return AssemblyNameReference.Parse(name[0] + ", Version=" + match.Groups["version"].Value + ", Culture=" + culture + ", PublicKeyToken=" + match.Groups["publicKey"].Value); |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endregion |
|
} |
|
}
|
|
|