// 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 {
///
/// Resolves assemblies
///
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 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> moduleSearchPaths = new Dictionary>();
readonly Dictionary cachedAssemblies = new Dictionary(StringComparer.OrdinalIgnoreCase);
readonly List preSearchPaths = new List();
readonly List postSearchPaths = new List();
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();
if (Type.GetType("Mono.Runtime") is not null) {
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)) {
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 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;
}
}
}
///
/// Gets/sets the default
///
public ModuleContext DefaultModuleContext {
get => defaultModuleContext;
set => defaultModuleContext = value;
}
///
/// true if should find an assembly that matches exactly.
/// false if it first tries to match exactly, and if that fails, it picks an
/// assembly that is closest to the requested assembly.
///
public bool FindExactMatch {
get => findExactMatch;
set => findExactMatch = value;
}
///
/// true 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 is true.
///
public bool EnableFrameworkRedirect {
get => enableFrameworkRedirect;
set => enableFrameworkRedirect = value;
}
///
/// If true, all modules in newly resolved assemblies will have their
/// property set to true. This is
/// enabled by default since these modules shouldn't be modified by the user.
///
public bool EnableTypeDefCache {
get => enableTypeDefCache;
set => enableTypeDefCache = value;
}
///
/// true to search the Global Assembly Cache. Default value is true.
///
public bool UseGAC {
get => useGac;
set => useGac = value;
}
///
/// Gets paths searched before trying the standard locations
///
public IList PreSearchPaths => preSearchPaths;
///
/// Gets paths searched after trying the standard locations
///
public IList PostSearchPaths => postSearchPaths;
///
/// Default constructor
///
public AssemblyResolver()
: this(null) {
}
///
/// Constructor
///
/// Module context for all resolved assemblies
public AssemblyResolver(ModuleContext defaultModuleContext) {
this.defaultModuleContext = defaultModuleContext;
enableFrameworkRedirect = true;
}
///
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
}
///
/// Add a module's assembly to the assembly cache
///
/// The module whose assembly should be cached
/// true if 's assembly is cached, false
/// if it's not cached because some other assembly with the exact same full name has
/// already been cached or if or its assembly is null.
public bool AddToCache(ModuleDef module) => module is not null && AddToCache(module.Assembly);
///
/// Add an assembly to the assembly cache
///
/// The assembly
/// true if is cached, false if it's not
/// cached because some other assembly with the exact same full name has already been
/// cached or if is null.
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
}
///
/// Removes a module's assembly from the cache
///
/// The module
/// true if its assembly was removed, false if it wasn't removed
/// since it wasn't in the cache, it has no assembly, or was
/// null
public bool Remove(ModuleDef module) => module is not null && Remove(module.Assembly);
///
/// Removes the assembly from the cache
///
/// The assembly
/// true if it was removed, false if it wasn't removed since it
/// wasn't in the cache or if was null
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
}
///
/// Clears the cache and calls on each cached module.
/// Use to remove any assemblies you added yourself
/// using before calling this method if you don't want
/// them disposed.
///
public void Clear() {
List asms;
#if THREAD_SAFE
theLock.EnterWriteLock(); try {
#endif
asms = new List(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();
}
}
///
/// Gets the cached assemblies in this resolver.
///
/// The cached assemblies.
public IEnumerable 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;
}
///
/// Finds an assembly that exactly matches the requested assembly
///
/// Assembly to find
/// Search paths or null if none
/// Module context
/// An instance or null if an exact match
/// couldn't be found.
AssemblyDef FindExactAssembly(IAssembly assembly, IEnumerable 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;
}
///
/// Finds the closest assembly from the already cached assemblies
///
/// Assembly to find
/// The closest or null if none found
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 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;
}
///
/// Returns true if is inserted in
///
/// Assembly to check
bool IsCached(AssemblyDef asm) {
if (asm is null)
return false;
return cachedAssemblies.TryGetValue(GetAssemblyNameKey(asm), out var cachedAsm) &&
cachedAsm == asm;
}
IEnumerable FindAssemblies2(IAssembly assembly, IEnumerable 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;
}
}
}
}
///
/// Called before
///
/// Assembly to find
/// The module that needs to resolve an assembly or null
/// We're trying to find an exact match
/// null or an enumerable of full paths to try
protected virtual IEnumerable PreFindAssemblies(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
foreach (var path in FindAssemblies2(assembly, preSearchPaths))
yield return path;
}
///
/// Called after (if it fails)
///
/// Assembly to find
/// The module that needs to resolve an assembly or null
/// We're trying to find an exact match
/// null or an enumerable of full paths to try
protected virtual IEnumerable PostFindAssemblies(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
foreach (var path in FindAssemblies2(assembly, postSearchPaths))
yield return path;
}
///
/// Called after (if it fails)
///
/// Assembly to find
/// The module that needs to resolve an assembly or null
/// We're trying to find an exact match
/// null or an enumerable of full paths to try
protected virtual IEnumerable 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 FindAssembliesGac(IAssembly assembly, ModuleDef sourceModule, bool matchExactly) {
if (matchExactly)
return FindAssembliesGacExactly(assembly, sourceModule);
return FindAssembliesGacAny(assembly, sourceModule);
}
IEnumerable 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 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 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 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 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 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 GetDirs(string baseDir) {
if (!Directory.Exists(baseDir))
return Array2.Empty();
var dirs = new List();
try {
foreach (var di in new DirectoryInfo(baseDir).GetDirectories())
dirs.Add(di.FullName);
}
catch {
}
return dirs;
}
IEnumerable 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;
}
}
}
}
///
/// Gets all search paths to use for this module
///
/// The module or null if unknown
/// A list of all search paths to use for this module
IEnumerable 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(GetModuleSearchPaths(module));
return searchPaths;
}
///
/// Gets all module search paths. This is usually empty unless its assembly has
/// a .config file specifying any additional private search paths in a
/// <probing/> element.
///
/// The module or null if unknown
/// A list of search paths
protected virtual IEnumerable GetModuleSearchPaths(ModuleDef module) => GetModulePrivateSearchPaths(module);
///
/// Gets all private assembly search paths as found in the module's .config file.
///
/// The module or null if unknown
/// A list of search paths
protected IEnumerable GetModulePrivateSearchPaths(ModuleDef module) {
if (module is null)
return Array2.Empty();
var asm = module.Assembly;
if (asm is null)
return Array2.Empty();
module = asm.ManifestModule;
if (module is null)
return Array2.Empty(); // 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 { baseDir };
return Array2.Empty();
}
IEnumerable GetPrivatePaths(string baseDir, string configFileName) {
var searchPaths = new List();
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;
}
}
}