/* 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 . */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; using dnlib.DotNet; namespace dnSpy.Contracts.Utilities { /// /// GAC file info /// public readonly struct GacFileInfo { /// /// Assembly /// public IAssembly Assembly { get; } /// /// Path to file /// public string Path { get; } internal GacFileInfo(IAssembly asm, string path) { Assembly = asm; Path = path; } } /// /// GAC version /// public enum GacVersion { /// /// .NET Framework 1.0-3.5 /// V2, /// /// .NET Framework 4.0+ /// V4, } /// /// GAC path info /// public readonly struct GacPathInfo { /// /// Path of dir containing assemblies /// public readonly string Path; /// /// GAC version /// public readonly GacVersion Version; /// /// Constructor /// /// Path /// Version public GacPathInfo(string path, GacVersion version) { Path = path ?? throw new ArgumentNullException(nameof(path)); Version = version; } } /// /// GAC /// public static class GacInfo { /// /// All GAC paths /// public static GacPathInfo[] GacPaths { get; } /// /// Other GAC paths /// public static GacPathInfo[] OtherGacPaths { get; } /// /// WinMD paths /// public static string[] WinmdPaths { get; } /// /// Checks if .NET 2.0-3.5 GAC exists /// public static bool HasGAC2 { get; } sealed class GacDirInfo { public readonly int Version; public readonly string Path; public readonly string Prefix; public readonly string[] SubDirs; public GacDirInfo(int version, string prefix, string path, string[] subDirs) { Version = version; Prefix = prefix; Path = path; SubDirs = subDirs; } } static readonly GacDirInfo[] gacDirInfos; static readonly string[] monoVerDirs = new string[] { // The "-api" dirs are reference assembly dirs. "4.5", @"4.5\Facades", "4.5-api", @"4.5-api\Facades", "4.0", "4.0-api", "3.5", "3.5-api", "3.0", "3.0-api", "2.0", "2.0-api", "1.1", "1.0", }; static GacInfo() { var gacDirInfosList = new List(); var newOtherGacPaths = new List(); var newWinmdPaths = new List(); bool hasGAC2; if (Type.GetType("Mono.Runtime") is not null) { hasGAC2 = false; var dirs = new Dictionary(StringComparer.OrdinalIgnoreCase); var extraMonoPathsList = new List(); foreach (var prefix in FindMonoPrefixes()) { var dir = Path.Combine(Path.Combine(Path.Combine(prefix, "lib"), "mono"), "gac"); if (dirs.ContainsKey(dir)) continue; dirs[dir] = true; if (Directory.Exists(dir)) { gacDirInfosList.Add(new GacDirInfo(4, "", Path.GetDirectoryName(dir)!, new string[] { Path.GetFileName(dir) })); } dir = Path.GetDirectoryName(dir)!; foreach (var verDir in monoVerDirs) { var dir2 = dir; foreach (var d in verDir.Split(new char[] { '\\' })) dir2 = Path.Combine(dir2, d); if (Directory.Exists(dir2)) extraMonoPathsList.Add(new GacPathInfo(dir2, GacVersion.V4)); } } var paths = Environment.GetEnvironmentVariable("MONO_PATH"); if (paths is not null) { foreach (var tmp in paths.Split(Path.PathSeparator)) { var path = tmp.Trim(); if (path != string.Empty && Directory.Exists(path)) extraMonoPathsList.Add(new GacPathInfo(path, GacVersion.V4)); } } newOtherGacPaths.AddRange(extraMonoPathsList); } else { hasGAC2 = false; var windir = Environment.GetEnvironmentVariable("WINDIR"); if (!string.IsNullOrEmpty(windir)) { string path; // .NET Framework 1.x and 2.x path = Path.Combine(windir, "assembly"); if (Directory.Exists(path)) { hasGAC2 = File.Exists(Path.Combine(path, @"GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll")) || File.Exists(Path.Combine(path, @"GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll")); if (hasGAC2) { gacDirInfosList.Add(new GacDirInfo(2, "", path, gacPaths4)); } } // .NET Framework 4.x path = Path.Combine(Path.Combine(windir, "Microsoft.NET"), "assembly"); if (Directory.Exists(path)) { gacDirInfosList.Add(new GacDirInfo(4, "v4.0_", path, gacPaths2)); } } AddIfExists(newWinmdPaths, Environment.SystemDirectory, "WinMetadata"); } OtherGacPaths = newOtherGacPaths.ToArray(); WinmdPaths = newWinmdPaths.ToArray(); gacDirInfos = gacDirInfosList.ToArray(); GacPaths = gacDirInfos.Select(a => new GacPathInfo(a.Path, a.Version == 2 ? GacVersion.V2 : GacVersion.V4)).ToArray(); HasGAC2 = hasGAC2; } // Prefer GAC_32 if this is a 32-bit process, and GAC_64 if this is a 64-bit process static readonly string[] gacPaths2 = IntPtr.Size == 4 ? new string[] { "GAC_32", "GAC_64", "GAC_MSIL" } : new string[] { "GAC_64", "GAC_32", "GAC_MSIL" }; static readonly string[] gacPaths4 = IntPtr.Size == 4 ? new string[] { "GAC_32", "GAC_64", "GAC_MSIL", "GAC" } : new string[] { "GAC_64", "GAC_32", "GAC_MSIL", "GAC" }; static string? GetCurrentMonoPrefix() { string? path = typeof(object).Module.FullyQualifiedName; for (int i = 0; i < 4; i++) path = Path.GetDirectoryName(path); return path; } static IEnumerable FindMonoPrefixes() { if (GetCurrentMonoPrefix() is string monoPrefix) yield return monoPrefix; var prefixes = Environment.GetEnvironmentVariable("MONO_GAC_PREFIX"); if (!string.IsNullOrEmpty(prefixes)) { foreach (var tmp in prefixes.Split(Path.PathSeparator)) { var prefix = tmp.Trim(); if (prefix != string.Empty) yield return prefix; } } } static void AddIfExists(List paths, string basePath, string extraPath) { var path = Path.Combine(basePath, extraPath); if (Directory.Exists(path)) paths.Add(path); } /// /// Checks whether is in the GAC /// /// Filename /// public static bool IsGacPath(string filename) { if (!File.Exists(filename)) return false; foreach (var info in GacPaths) { if (IsSubPath(info.Path, filename)) return true; } foreach (var info in OtherGacPaths) { if (IsSubPath(info.Path, filename)) return true; } return false; } static bool IsSubPath(string path, string filename) { filename = Path.GetFullPath(Path.GetDirectoryName(filename)!); var root = Path.GetPathRoot(filename); while (!StringComparer.OrdinalIgnoreCase.Equals(filename, root)) { if (StringComparer.OrdinalIgnoreCase.Equals(path, filename)) return true; filename = Path.GetDirectoryName(filename)!; } return false; } /// /// Finds an assembly in the GAC /// /// Assembly /// public static string? FindInGac(IAssembly asm) => FindInGac(asm, -1); /// /// Finds an assembly in the GAC /// /// Assembly /// 2, 4, or -1 /// public static string? FindInGac(IAssembly? asm, int version) { if (asm is null) return null; var pkt = PublicKeyBase.ToPublicKeyToken(asm.PublicKeyOrToken); if (PublicKeyBase.IsNullOrEmpty2(pkt)) return null; foreach (var info in gacDirInfos) { if (version != -1 && version != info.Version) continue; foreach (var name in GetAssemblies(info, pkt, asm)) return name; } return null; } static IEnumerable GetAssemblies(GacDirInfo gacInfo, PublicKeyToken pkt, IAssembly assembly) { string pktString = pkt.ToString(); string verString = assembly.Version.ToString(); var cultureString = UTF8String.ToSystemStringOrEmpty(assembly.Culture); if (cultureString.Equals("neutral", StringComparison.OrdinalIgnoreCase)) cultureString = string.Empty; var asmSimpleName = UTF8String.ToSystemStringOrEmpty(assembly.Name); foreach (var subDir in gacInfo.SubDirs) { var baseDir = Path.Combine(gacInfo.Path, subDir); string pathName; try { baseDir = Path.Combine(baseDir, asmSimpleName); baseDir = Path.Combine(baseDir, $"{gacInfo.Prefix}{verString}_{cultureString}_{pktString}"); pathName = Path.Combine(baseDir, asmSimpleName + ".dll"); } catch (ArgumentException) { // Invalid char(s) in asmSimpleName, cultureString yield break; } if (File.Exists(pathName)) yield return pathName; } } /// /// Gets all assemblies in the GAC /// /// CLR major version, eg. 2 or 4 /// public static IEnumerable GetAssemblies(int majorVersion) { foreach (var info in gacDirInfos) { if (info.Version == majorVersion) return GetAssemblies(info); } Debug.Fail("Invalid version"); return Array.Empty(); } static IEnumerable GetAssemblies(GacDirInfo gacInfo) { foreach (var subDir in gacInfo.SubDirs) { var baseDir = Path.Combine(gacInfo.Path, subDir); foreach (var dir in GetDirectories(baseDir)) { foreach (var dir2 in GetDirectories(dir)) { Version? version; string culture; PublicKeyToken pkt; if (gacInfo.Version == 2) { var m = gac2Regex.Match(Path.GetFileName(dir2)); if (!m.Success || m.Groups.Count != 4) continue; if (!Version.TryParse(m.Groups[1].Value, out version)) continue; culture = m.Groups[2].Value; pkt = new PublicKeyToken(m.Groups[3].Value); if (PublicKeyBase.IsNullOrEmpty2(pkt)) continue; } else if (gacInfo.Version == 4) { var m = gac4Regex.Match(Path.GetFileName(dir2)); if (!m.Success || m.Groups.Count != 4) continue; if (!Version.TryParse(m.Groups[1].Value, out version)) continue; culture = m.Groups[2].Value; pkt = new PublicKeyToken(m.Groups[3].Value); if (PublicKeyBase.IsNullOrEmpty2(pkt)) continue; } else throw new InvalidOperationException(); var asmName = Path.GetFileName(dir); var file = Path.Combine(dir2, asmName) + ".dll"; if (!File.Exists(file)) { file = Path.Combine(dir2, asmName) + ".exe"; if (!File.Exists(file)) continue; } var asmInfo = new AssemblyNameInfo { Name = asmName, Version = version, Culture = culture, PublicKeyOrToken = pkt, }; yield return new GacFileInfo(asmInfo, file); } } } } static readonly Regex gac2Regex = new Regex("^([^_]+)_([^_]*)_([a-fA-F0-9]{16})$", RegexOptions.Compiled); static readonly Regex gac4Regex = new Regex("^v[^_]+_([^_]+)_([^_]*)_([a-fA-F0-9]{16})$", RegexOptions.Compiled); static string[] GetDirectories(string dir) { try { return Directory.GetDirectories(dir); } catch { } return Array.Empty(); } } }