obfuz/Plugins/dnlib/DotNet/AssemblyResolver.cs

833 lines
28 KiB
C#

// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using dnlib.Threading;
namespace dnlib.DotNet {
/// <summary>
/// Resolves assemblies
/// </summary>
public class AssemblyResolver : IAssemblyResolver {
static readonly ModuleDef nullModule = new ModuleDefUser();
// DLL files are searched before EXE files
static readonly string[] assemblyExtensions = new string[] { ".dll", ".exe" };
static readonly string[] winMDAssemblyExtensions = new string[] { ".winmd" };
static readonly List<GacInfo> gacInfos;
static readonly string[] extraMonoPaths;
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",
};
ModuleContext defaultModuleContext;
readonly Dictionary<ModuleDef, List<string>> moduleSearchPaths = new Dictionary<ModuleDef, List<string>>();
readonly Dictionary<string, AssemblyDef> cachedAssemblies = new Dictionary<string, AssemblyDef>(StringComparer.OrdinalIgnoreCase);
readonly List<string> preSearchPaths = new List<string>();
readonly List<string> postSearchPaths = new List<string>();
bool findExactMatch;
bool enableFrameworkRedirect;
bool enableTypeDefCache = true;
bool useGac = true;
#if THREAD_SAFE
readonly Lock theLock = Lock.Create();
#endif
sealed class GacInfo {
public readonly int Version;
public readonly string Path;
public readonly string Prefix;
public readonly string[] SubDirs;
public GacInfo(int version, string prefix, string path, string[] subDirs) {
Version = version;
Prefix = prefix;
Path = path;
SubDirs = subDirs;
}
}
static AssemblyResolver() {
gacInfos = new List<GacInfo>();
if (Type.GetType("Mono.Runtime") is not null) {
var dirs = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
var extraMonoPathsList = new List<string>();
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)) {
gacInfos.Add(new GacInfo(-1, "", 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(dir2);
}
}
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(path);
}
}
extraMonoPaths = extraMonoPathsList.ToArray();
}
else {
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)) {
gacInfos.Add(new GacInfo(2, "", path, new string[] {
"GAC_32", "GAC_64", "GAC_MSIL", "GAC"
}));
}
// .NET Framework 4.x
path = Path.Combine(Path.Combine(windir, "Microsoft.NET"), "assembly");
if (Directory.Exists(path)) {
gacInfos.Add(new GacInfo(4, "v4.0_", path, new string[] {
"GAC_32", "GAC_64", "GAC_MSIL"
}));
}
}
}
}
static string GetCurrentMonoPrefix() {
var path = typeof(object).Module.FullyQualifiedName;
for (int i = 0; i < 4; i++)
path = Path.GetDirectoryName(path);
return path;
}
static IEnumerable<string> FindMonoPrefixes() {
yield return GetCurrentMonoPrefix();
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;
}
}
}
/// <summary>
/// Gets/sets the default <see cref="ModuleContext"/>
/// </summary>
public ModuleContext DefaultModuleContext {
get => defaultModuleContext;
set => defaultModuleContext = value;
}
/// <summary>
/// <c>true</c> if <see cref="Resolve"/> should find an assembly that matches exactly.
/// <c>false</c> if it first tries to match exactly, and if that fails, it picks an
/// assembly that is closest to the requested assembly.
/// </summary>
public bool FindExactMatch {
get => findExactMatch;
set => findExactMatch = value;
}
/// <summary>
/// <c>true</c> if resolved .NET framework assemblies can be redirected to the source
/// module's framework assembly version. Eg. if a resolved .NET Framework 3.5 assembly can be
/// redirected to a .NET Framework 4.0 assembly if the source module is a .NET Framework 4.0 assembly. This is
/// ignored if <see cref="FindExactMatch"/> is <c>true</c>.
/// </summary>
public bool EnableFrameworkRedirect {
get => enableFrameworkRedirect;
set => enableFrameworkRedirect = value;
}
/// <summary>
/// If <c>true</c>, all modules in newly resolved assemblies will have their
/// <see cref="ModuleDef.EnableTypeDefFindCache"/> property set to <c>true</c>. This is
/// enabled by default since these modules shouldn't be modified by the user.
/// </summary>
public bool EnableTypeDefCache {
get => enableTypeDefCache;
set => enableTypeDefCache = value;
}
/// <summary>
/// true to search the Global Assembly Cache. Default value is true.
/// </summary>
public bool UseGAC {
get => useGac;
set => useGac = value;
}
/// <summary>
/// Gets paths searched before trying the standard locations
/// </summary>
public IList<string> PreSearchPaths => preSearchPaths;
/// <summary>
/// Gets paths searched after trying the standard locations
/// </summary>
public IList<string> PostSearchPaths => postSearchPaths;
/// <summary>
/// Default constructor
/// </summary>
public AssemblyResolver()
: this(null) {
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="defaultModuleContext">Module context for all resolved assemblies</param>
public AssemblyResolver(ModuleContext defaultModuleContext) {
this.defaultModuleContext = defaultModuleContext;
enableFrameworkRedirect = true;
}
/// <inheritdoc/>
public AssemblyDef Resolve(IAssembly assembly, ModuleDef sourceModule) {
if (assembly is null)
return null;
if (EnableFrameworkRedirect && !FindExactMatch)
FrameworkRedirect.ApplyFrameworkRedirect(ref assembly, sourceModule);
#if THREAD_SAFE
theLock.EnterWriteLock(); try {
#endif
var resolvedAssembly = Resolve2(assembly, sourceModule);
if (resolvedAssembly is null) {
string asmName = UTF8String.ToSystemStringOrEmpty(assembly.Name);
string asmNameTrimmed = asmName.Trim();
if (asmName != asmNameTrimmed) {
assembly = new AssemblyNameInfo {
Name = asmNameTrimmed,
Version = assembly.Version,
PublicKeyOrToken = assembly.PublicKeyOrToken,
Culture = assembly.Culture,
};
resolvedAssembly = Resolve2(assembly, sourceModule);
}
}
if (resolvedAssembly is null) {
// Make sure we don't search for this assembly again. This speeds up callers who
// keep asking for this assembly when trying to resolve many different TypeRefs
cachedAssemblies[GetAssemblyNameKey(assembly)] = null;
return null;
}
var key1 = GetAssemblyNameKey(resolvedAssembly);
var key2 = GetAssemblyNameKey(assembly);
cachedAssemblies.TryGetValue(key1, out var asm1);
cachedAssemblies.TryGetValue(key2, out var asm2);
if (asm1 != resolvedAssembly && asm2 != resolvedAssembly) {
// This assembly was just resolved
if (enableTypeDefCache) {
var modules = resolvedAssembly.Modules;
int count = modules.Count;
for (int i = 0; i < count; i++) {
var module = modules[i];
if (module is not null)
module.EnableTypeDefFindCache = true;
}
}
}
bool inserted = false;
if (!cachedAssemblies.ContainsKey(key1)) {
cachedAssemblies.Add(key1, resolvedAssembly);
inserted = true;
}
if (!cachedAssemblies.ContainsKey(key2)) {
cachedAssemblies.Add(key2, resolvedAssembly);
inserted = true;
}
if (inserted || asm1 == resolvedAssembly || asm2 == resolvedAssembly)
return resolvedAssembly;
// Dupe assembly. Don't insert it.
var dupeModule = resolvedAssembly.ManifestModule;
if (dupeModule is not null)
dupeModule.Dispose();
return asm1 ?? asm2;
#if THREAD_SAFE
} finally { theLock.ExitWriteLock(); }
#endif
}
/// <summary>
/// Add a module's assembly to the assembly cache
/// </summary>
/// <param name="module">The module whose assembly should be cached</param>
/// <returns><c>true</c> if <paramref name="module"/>'s assembly is cached, <c>false</c>
/// if it's not cached because some other assembly with the exact same full name has
/// already been cached or if <paramref name="module"/> or its assembly is <c>null</c>.</returns>
public bool AddToCache(ModuleDef module) => module is not null && AddToCache(module.Assembly);
/// <summary>
/// Add an assembly to the assembly cache
/// </summary>
/// <param name="asm">The assembly</param>
/// <returns><c>true</c> if <paramref name="asm"/> is cached, <c>false</c> if it's not
/// cached because some other assembly with the exact same full name has already been
/// cached or if <paramref name="asm"/> is <c>null</c>.</returns>
public bool AddToCache(AssemblyDef asm) {
if (asm is null)
return false;
var asmKey = GetAssemblyNameKey(asm);
#if THREAD_SAFE
theLock.EnterWriteLock(); try {
#endif
if (cachedAssemblies.TryGetValue(asmKey, out var cachedAsm) && cachedAsm is not null)
return asm == cachedAsm;
if (enableTypeDefCache)
{
var modules = asm.Modules;
int count = modules.Count;
for (int i = 0; i < count; i++)
{
var module = modules[i];
if (module is not null)
module.EnableTypeDefFindCache = true;
}
}
cachedAssemblies[asmKey] = asm;
return true;
#if THREAD_SAFE
} finally { theLock.ExitWriteLock(); }
#endif
}
/// <summary>
/// Removes a module's assembly from the cache
/// </summary>
/// <param name="module">The module</param>
/// <returns><c>true</c> if its assembly was removed, <c>false</c> if it wasn't removed
/// since it wasn't in the cache, it has no assembly, or <paramref name="module"/> was
/// <c>null</c></returns>
public bool Remove(ModuleDef module) => module is not null && Remove(module.Assembly);
/// <summary>
/// Removes the assembly from the cache
/// </summary>
/// <param name="asm">The assembly</param>
/// <returns><c>true</c> if it was removed, <c>false</c> if it wasn't removed since it
/// wasn't in the cache or if <paramref name="asm"/> was <c>null</c></returns>
public bool Remove(AssemblyDef asm) {
if (asm is null)
return false;
var asmKey = GetAssemblyNameKey(asm);
#if THREAD_SAFE
theLock.EnterWriteLock(); try {
#endif
return cachedAssemblies.Remove(asmKey);
#if THREAD_SAFE
} finally { theLock.ExitWriteLock(); }
#endif
}
/// <summary>
/// Clears the cache and calls <see cref="IDisposable.Dispose()"/> on each cached module.
/// Use <see cref="Remove(AssemblyDef)"/> to remove any assemblies you added yourself
/// using <see cref="AddToCache(AssemblyDef)"/> before calling this method if you don't want
/// them disposed.
/// </summary>
public void Clear() {
List<AssemblyDef> asms;
#if THREAD_SAFE
theLock.EnterWriteLock(); try {
#endif
asms = new List<AssemblyDef>(cachedAssemblies.Values);
cachedAssemblies.Clear();
#if THREAD_SAFE
} finally { theLock.ExitWriteLock(); }
#endif
foreach (var asm in asms) {
if (asm is null)
continue;
foreach (var mod in asm.Modules)
mod.Dispose();
}
}
/// <summary>
/// Gets the cached assemblies in this resolver.
/// </summary>
/// <returns>The cached assemblies.</returns>
public IEnumerable<AssemblyDef> GetCachedAssemblies() {
AssemblyDef[] assemblies;
#if THREAD_SAFE
theLock.EnterReadLock(); try {
#endif
assemblies = cachedAssemblies.Values.ToArray();
#if THREAD_SAFE
} finally { theLock.ExitReadLock(); }
#endif
return assemblies;
}
static string GetAssemblyNameKey(IAssembly asmName) {
// Make sure the name contains PublicKeyToken= and not PublicKey=
return asmName.FullNameToken;
}
AssemblyDef Resolve2(IAssembly assembly, ModuleDef sourceModule) {
if (cachedAssemblies.TryGetValue(GetAssemblyNameKey(assembly), out var resolvedAssembly))
return resolvedAssembly;
var moduleContext = defaultModuleContext;
if (moduleContext is null && sourceModule is not null)
moduleContext = sourceModule.Context;
resolvedAssembly = FindExactAssembly(assembly, PreFindAssemblies(assembly, sourceModule, true), moduleContext) ??
FindExactAssembly(assembly, FindAssemblies(assembly, sourceModule, true), moduleContext) ??
FindExactAssembly(assembly, PostFindAssemblies(assembly, sourceModule, true), moduleContext);
if (resolvedAssembly is not null)
return resolvedAssembly;
if (!findExactMatch) {
resolvedAssembly = FindClosestAssembly(assembly);
resolvedAssembly = FindClosestAssembly(assembly, resolvedAssembly, PreFindAssemblies(assembly, sourceModule, false), moduleContext);
resolvedAssembly = FindClosestAssembly(assembly, resolvedAssembly, FindAssemblies(assembly, sourceModule, false), moduleContext);
resolvedAssembly = FindClosestAssembly(assembly, resolvedAssembly, PostFindAssemblies(assembly, sourceModule, false), moduleContext);
}
return resolvedAssembly;
}
/// <summary>
/// Finds an assembly that exactly matches the requested assembly
/// </summary>
/// <param name="assembly">Assembly to find</param>
/// <param name="paths">Search paths or <c>null</c> if none</param>
/// <param name="moduleContext">Module context</param>
/// <returns>An <see cref="AssemblyDef"/> instance or <c>null</c> if an exact match
/// couldn't be found.</returns>
AssemblyDef FindExactAssembly(IAssembly assembly, IEnumerable<string> paths, ModuleContext moduleContext) {
if (paths is null)
return null;
var asmComparer = AssemblyNameComparer.CompareAll;
foreach (var path in paths) {
ModuleDefMD mod = null;
try {
mod = ModuleDefMD.Load(path, moduleContext);
var asm = mod.Assembly;
if (asm is not null && asmComparer.Equals(assembly, asm)) {
mod = null;
return asm;
}
}
catch {
}
finally {
if (mod is not null)
mod.Dispose();
}
}
return null;
}
/// <summary>
/// Finds the closest assembly from the already cached assemblies
/// </summary>
/// <param name="assembly">Assembly to find</param>
/// <returns>The closest <see cref="AssemblyDef"/> or <c>null</c> if none found</returns>
AssemblyDef FindClosestAssembly(IAssembly assembly) {
AssemblyDef closest = null;
var asmComparer = AssemblyNameComparer.CompareAll;
foreach (var kv in cachedAssemblies) {
var asm = kv.Value;
if (asm is null)
continue;
if (asmComparer.CompareClosest(assembly, closest, asm) == 1)
closest = asm;
}
return closest;
}
AssemblyDef FindClosestAssembly(IAssembly assembly, AssemblyDef closest, IEnumerable<string> paths, ModuleContext moduleContext) {
if (paths is null)
return closest;
var asmComparer = AssemblyNameComparer.CompareAll;
foreach (var path in paths) {
ModuleDefMD mod = null;
try {
mod = ModuleDefMD.Load(path, moduleContext);
var asm = mod.Assembly;
if (asm is not null && asmComparer.CompareClosest(assembly, closest, asm) == 1) {
if (!IsCached(closest) && closest is not null) {
var closeMod = closest.ManifestModule;
if (closeMod is not null)
closeMod.Dispose();
}
closest = asm;
mod = null;
}
}
catch {
}
finally {
if (mod is not null)
mod.Dispose();
}
}
return closest;
}
/// <summary>
/// Returns <c>true</c> if <paramref name="asm"/> is inserted in <see cref="cachedAssemblies"/>
/// </summary>
/// <param name="asm">Assembly to check</param>
bool IsCached(AssemblyDef asm) {
if (asm is null)
return false;
return cachedAssemblies.TryGetValue(GetAssemblyNameKey(asm), out var cachedAsm) &&
cachedAsm == asm;
}
IEnumerable<string> FindAssemblies2(IAssembly assembly, IEnumerable<string> paths) {
if (paths is not null) {
var asmSimpleName = UTF8String.ToSystemStringOrEmpty(assembly.Name);
var exts = assembly.IsContentTypeWindowsRuntime ? winMDAssemblyExtensions : assemblyExtensions;
foreach (var ext in exts) {
foreach (var path in paths) {
string fullPath;
try {
fullPath = Path.Combine(path, asmSimpleName + ext);
}
catch (ArgumentException) {
// Invalid path chars
yield break;
}
if (File.Exists(fullPath))
yield return fullPath;
}
}
}
}
/// <summary>
/// Called before <see cref="FindAssemblies"/>
/// </summary>
/// <param name="assembly">Assembly to find</param>
/// <param name="sourceModule">The module that needs to resolve an assembly or <c>null</c></param>
/// <param name="matchExactly">We're trying to find an exact match</param>
/// <returns><c>null</c> or an enumerable of full paths to try</returns>
protected virtual IEnumerable<string> PreFindAssemblies(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
foreach (var path in FindAssemblies2(assembly, preSearchPaths))
yield return path;
}
/// <summary>
/// Called after <see cref="FindAssemblies"/> (if it fails)
/// </summary>
/// <param name="assembly">Assembly to find</param>
/// <param name="sourceModule">The module that needs to resolve an assembly or <c>null</c></param>
/// <param name="matchExactly">We're trying to find an exact match</param>
/// <returns><c>null</c> or an enumerable of full paths to try</returns>
protected virtual IEnumerable<string> PostFindAssemblies(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
foreach (var path in FindAssemblies2(assembly, postSearchPaths))
yield return path;
}
/// <summary>
/// Called after <see cref="PreFindAssemblies"/> (if it fails)
/// </summary>
/// <param name="assembly">Assembly to find</param>
/// <param name="sourceModule">The module that needs to resolve an assembly or <c>null</c></param>
/// <param name="matchExactly">We're trying to find an exact match</param>
/// <returns><c>null</c> or an enumerable of full paths to try</returns>
protected virtual IEnumerable<string> FindAssemblies(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
if (assembly.IsContentTypeWindowsRuntime) {
string path;
try {
path = Path.Combine(Path.Combine(Environment.SystemDirectory, "WinMetadata"), assembly.Name + ".winmd");
}
catch (ArgumentException) {
// Invalid path chars
path = null;
}
if (File.Exists(path))
yield return path;
}
else {
if (UseGAC) {
foreach (var path in FindAssembliesGac(assembly, sourceModule, matchExactly))
yield return path;
}
}
foreach (var path in FindAssembliesModuleSearchPaths(assembly, sourceModule, matchExactly))
yield return path;
}
IEnumerable<string> FindAssembliesGac(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
if (matchExactly)
return FindAssembliesGacExactly(assembly, sourceModule);
return FindAssembliesGacAny(assembly, sourceModule);
}
IEnumerable<GacInfo> GetGacInfos(ModuleDef sourceModule) {
int version = sourceModule is null ? int.MinValue : sourceModule.IsClr40 ? 4 : 2;
// Try the correct GAC first (eg. GAC4 if it's a .NET Framework 4 assembly)
foreach (var gacInfo in gacInfos) {
if (gacInfo.Version == version)
yield return gacInfo;
}
foreach (var gacInfo in gacInfos) {
if (gacInfo.Version != version)
yield return gacInfo;
}
}
IEnumerable<string> FindAssembliesGacExactly(IAssembly assembly, ModuleDef sourceModule) {
foreach (var gacInfo in GetGacInfos(sourceModule)) {
foreach (var path in FindAssembliesGacExactly(gacInfo, assembly, sourceModule))
yield return path;
}
if (extraMonoPaths is not null) {
foreach (var path in GetExtraMonoPaths(assembly, sourceModule))
yield return path;
}
}
static IEnumerable<string> GetExtraMonoPaths(IAssembly assembly, ModuleDef sourceModule) {
if (extraMonoPaths is not null) {
foreach (var dir in extraMonoPaths) {
string file;
try {
file = Path.Combine(dir, assembly.Name + ".dll");
}
catch (ArgumentException) {
// Invalid path chars
break;
}
if (File.Exists(file))
yield return file;
}
}
}
IEnumerable<string> FindAssembliesGacExactly(GacInfo gacInfo, IAssembly assembly, ModuleDef sourceModule) {
var pkt = PublicKeyBase.ToPublicKeyToken(assembly.PublicKeyOrToken);
if (gacInfo is not null && pkt is not null) {
string pktString = pkt.ToString();
string verString = Utils.CreateVersionWithNoUndefinedValues(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);
try {
baseDir = Path.Combine(baseDir, asmSimpleName);
}
catch (ArgumentException) {
// Invalid path chars
break;
}
baseDir = Path.Combine(baseDir, $"{gacInfo.Prefix}{verString}_{cultureString}_{pktString}");
var pathName = Path.Combine(baseDir, asmSimpleName + ".dll");
if (File.Exists(pathName))
yield return pathName;
}
}
}
IEnumerable<string> FindAssembliesGacAny(IAssembly assembly, ModuleDef sourceModule) {
foreach (var gacInfo in GetGacInfos(sourceModule)) {
foreach (var path in FindAssembliesGacAny(gacInfo, assembly, sourceModule))
yield return path;
}
if (extraMonoPaths is not null) {
foreach (var path in GetExtraMonoPaths(assembly, sourceModule))
yield return path;
}
}
IEnumerable<string> FindAssembliesGacAny(GacInfo gacInfo, IAssembly assembly, ModuleDef sourceModule) {
if (gacInfo is not null) {
var asmSimpleName = UTF8String.ToSystemStringOrEmpty(assembly.Name);
foreach (var subDir in gacInfo.SubDirs) {
var baseDir = Path.Combine(gacInfo.Path, subDir);
try {
baseDir = Path.Combine(baseDir, asmSimpleName);
}
catch (ArgumentException) {
// Invalid path chars
break;
}
foreach (var dir in GetDirs(baseDir)) {
var pathName = Path.Combine(dir, asmSimpleName + ".dll");
if (File.Exists(pathName))
yield return pathName;
}
}
}
}
IEnumerable<string> GetDirs(string baseDir) {
if (!Directory.Exists(baseDir))
return Array2.Empty<string>();
var dirs = new List<string>();
try {
foreach (var di in new DirectoryInfo(baseDir).GetDirectories())
dirs.Add(di.FullName);
}
catch {
}
return dirs;
}
IEnumerable<string> FindAssembliesModuleSearchPaths(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
string asmSimpleName = UTF8String.ToSystemStringOrEmpty(assembly.Name);
var searchPaths = GetSearchPaths(sourceModule);
var exts = assembly.IsContentTypeWindowsRuntime ? winMDAssemblyExtensions : assemblyExtensions;
foreach (var ext in exts) {
foreach (var path in searchPaths) {
for (int i = 0; i < 2; i++) {
string path2;
try {
if (i == 0)
path2 = Path.Combine(path, asmSimpleName + ext);
else
path2 = Path.Combine(Path.Combine(path, asmSimpleName), asmSimpleName + ext);
}
catch (ArgumentException) {
// Invalid path chars
yield break;
}
if (File.Exists(path2))
yield return path2;
}
}
}
}
/// <summary>
/// Gets all search paths to use for this module
/// </summary>
/// <param name="module">The module or <c>null</c> if unknown</param>
/// <returns>A list of all search paths to use for this module</returns>
IEnumerable<string> GetSearchPaths(ModuleDef module) {
var keyModule = module;
if (keyModule is null)
keyModule = nullModule;
if (moduleSearchPaths.TryGetValue(keyModule, out var searchPaths))
return searchPaths;
moduleSearchPaths[keyModule] = searchPaths = new List<string>(GetModuleSearchPaths(module));
return searchPaths;
}
/// <summary>
/// Gets all module search paths. This is usually empty unless its assembly has
/// a <c>.config</c> file specifying any additional private search paths in a
/// &lt;probing/&gt; element.
/// </summary>
/// <param name="module">The module or <c>null</c> if unknown</param>
/// <returns>A list of search paths</returns>
protected virtual IEnumerable<string> GetModuleSearchPaths(ModuleDef module) => GetModulePrivateSearchPaths(module);
/// <summary>
/// Gets all private assembly search paths as found in the module's <c>.config</c> file.
/// </summary>
/// <param name="module">The module or <c>null</c> if unknown</param>
/// <returns>A list of search paths</returns>
protected IEnumerable<string> GetModulePrivateSearchPaths(ModuleDef module) {
if (module is null)
return Array2.Empty<string>();
var asm = module.Assembly;
if (asm is null)
return Array2.Empty<string>();
module = asm.ManifestModule;
if (module is null)
return Array2.Empty<string>(); // Should never happen
string baseDir = null;
try {
var imageName = module.Location;
if (imageName != string.Empty) {
var directoryInfo = Directory.GetParent(imageName);
if (directoryInfo is not null) {
baseDir = directoryInfo.FullName;
var configName = imageName + ".config";
if (File.Exists(configName))
return GetPrivatePaths(baseDir, configName);
}
}
}
catch {
}
if (baseDir is not null)
return new List<string> { baseDir };
return Array2.Empty<string>();
}
IEnumerable<string> GetPrivatePaths(string baseDir, string configFileName) {
var searchPaths = new List<string>();
try {
var dirName = Path.GetDirectoryName(Path.GetFullPath(configFileName));
searchPaths.Add(dirName);
using (var xmlStream = new FileStream(configFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
var doc = new XmlDocument();
doc.Load(XmlReader.Create(xmlStream));
foreach (var tmp in doc.GetElementsByTagName("probing")) {
var probingElem = tmp as XmlElement;
if (probingElem is null)
continue;
var privatePath = probingElem.GetAttribute("privatePath");
if (string.IsNullOrEmpty(privatePath))
continue;
foreach (var tmp2 in privatePath.Split(';')) {
var path = tmp2.Trim();
if (path == "")
continue;
var newPath = Path.GetFullPath(Path.Combine(dirName, path.Replace('\\', Path.DirectorySeparatorChar)));
if (Directory.Exists(newPath) && newPath.StartsWith(baseDir + Path.DirectorySeparatorChar))
searchPaths.Add(newPath);
}
}
}
}
catch (ArgumentException) {
}
catch (IOException) {
}
catch (XmlException) {
}
return searchPaths;
}
}
}