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.
776 lines
24 KiB
776 lines
24 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 TargetRuntime
|
|
{
|
|
Unknown,
|
|
Net_1_0,
|
|
Net_1_1,
|
|
Net_2_0,
|
|
Net_4_0
|
|
}
|
|
|
|
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)
|
|
{
|
|
this.baseDirectory = Path.GetDirectoryName(mainAssemblyFileName);
|
|
if (string.IsNullOrWhiteSpace(this.baseDirectory))
|
|
this.baseDirectory = Environment.CurrentDirectory;
|
|
AddSearchDirectory(baseDirectory);
|
|
}
|
|
}
|
|
|
|
internal static (TargetFrameworkIdentifier, Version) ParseTargetFramework(string targetFramework)
|
|
{
|
|
if (string.IsNullOrEmpty(targetFramework))
|
|
return (TargetFrameworkIdentifier.NETFramework, ZeroVersion);
|
|
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);
|
|
if (baseDirectory == null)
|
|
return null;
|
|
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;
|
|
|
|
string[] framework_dirs;
|
|
string framework_dir;
|
|
|
|
switch (decompilerRuntime)
|
|
{
|
|
case DecompilerRuntime.Mono:
|
|
framework_dir = Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName)!;
|
|
framework_dirs = new[] { framework_dir, Path.Combine(framework_dir, "Facades") };
|
|
break;
|
|
case DecompilerRuntime.NETCoreApp:
|
|
string windir;
|
|
try
|
|
{
|
|
windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
|
if (string.IsNullOrEmpty(windir))
|
|
{
|
|
goto default;
|
|
}
|
|
}
|
|
catch (PlatformNotSupportedException)
|
|
{
|
|
goto default;
|
|
}
|
|
framework_dir = Path.Combine(windir, "Microsoft.NET", "Framework64", "v4.0.30319");
|
|
framework_dirs = new[] { framework_dir };
|
|
break;
|
|
default:
|
|
framework_dir = Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName)!;
|
|
framework_dirs = new[] { framework_dir };
|
|
break;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (directory == null)
|
|
continue;
|
|
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 (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);
|
|
gac_folder.Append("__");
|
|
for (int i = 0; i < reference.PublicKeyToken!.Length; i++)
|
|
gac_folder.Append(reference.PublicKeyToken[i].ToString("x2"));
|
|
return 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))
|
|
{
|
|
// The root of the GAC should only contain folders, but make sure we handle the case where it does NOT in case
|
|
// someone has a non-standard layout (e.g. due to a broken installer).
|
|
string? assemblyParentPath = Path.GetDirectoryName(item.FullName);
|
|
if (assemblyParentPath?.Length > rootPath.Length)
|
|
{
|
|
string[]? name = assemblyParentPath.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
|
|
}
|
|
}
|