From de1b5aea4fdf367a11fefe7728b097f9d45525ea Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Wed, 19 Sep 2018 22:08:50 -0500 Subject: [PATCH] Library loader now supports nested folders in Libs/ --- IPA.Injector/IPA.Injector.csproj | 1 + IPA.Injector/Injector.cs | 38 +------- IPA.Injector/LibLoader.cs | 159 +++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 IPA.Injector/LibLoader.cs diff --git a/IPA.Injector/IPA.Injector.csproj b/IPA.Injector/IPA.Injector.csproj index 3b616354..11ad1325 100644 --- a/IPA.Injector/IPA.Injector.csproj +++ b/IPA.Injector/IPA.Injector.csproj @@ -49,6 +49,7 @@ + diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs index 2cd57b08..02bb9211 100644 --- a/IPA.Injector/Injector.cs +++ b/IPA.Injector/Injector.cs @@ -19,10 +19,10 @@ namespace IPA.Injector injected = true; #region Add Library load locations - AppDomain.CurrentDomain.AssemblyResolve += AssemblyLibLoader; + AppDomain.CurrentDomain.AssemblyResolve += LibLoader.AssemblyLibLoader; try { - if (!SetDllDirectory(Path.Combine(Environment.CurrentDirectory, "Libs", "Native"))) + if (!SetDllDirectory(LibLoader.NativeDir)) { libLoader.Warn("Unable to add native library path to load path"); } @@ -39,40 +39,6 @@ namespace IPA.Injector [return: MarshalAs(UnmanagedType.Bool)] static extern bool SetDllDirectory(string lpPathName); - #region Managed library loader - private static string libsDir; - private static Assembly AssemblyLibLoader(object source, ResolveEventArgs e) - { - if (libsDir == null) - libsDir = Path.Combine(Environment.CurrentDirectory, "Libs"); - - var asmName = new AssemblyName(e.Name); - Log(Level.Debug, $"Resolving library {asmName}"); - - var testFilen = Path.Combine(libsDir, $"{asmName.Name}.{asmName.Version}.dll"); - Log(Level.Debug, $"Looking for file {testFilen}"); - - if (File.Exists(testFilen)) - return Assembly.LoadFile(testFilen); - - Log(Level.Critical, $"Could not load library {asmName}"); - - return null; - } - private static void Log(Level lvl, string message) - { // multiple proxy methods to delay loading of assemblies until it's done - if (LogCreated) - AssemblyLibLoaderCallLogger(lvl, message); - else - if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0) - Console.WriteLine($"[{lvl}] {message}"); - } - private static void AssemblyLibLoaderCallLogger(Level lvl, string message) - { - libLoader.Log(lvl, message); - } - #endregion - private static void Bootstrapper_Destroyed() { PluginComponent.Create(); diff --git a/IPA.Injector/LibLoader.cs b/IPA.Injector/LibLoader.cs new file mode 100644 index 00000000..61343826 --- /dev/null +++ b/IPA.Injector/LibLoader.cs @@ -0,0 +1,159 @@ +using IPA.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using static IPA.Logging.Logger; + +namespace IPA.Injector +{ + internal class LibLoader + { + public static string LibsDir { get; set; } = Path.Combine(Environment.CurrentDirectory, "Libs"); + public static string NativeDir { get; set; } = Path.Combine(LibsDir, "Native"); + private static Dictionary filenameLocations = null; + + public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e) + { + var asmName = new AssemblyName(e.Name); + Log(Level.Debug, $"Resolving library {asmName}"); + + if (filenameLocations == null) + { + filenameLocations = new Dictionary(); + + foreach (var fn in TraverseTree(LibsDir, s => s != NativeDir)) + filenameLocations.Add(fn.Name, fn.FullName); + } + + var testFilen = $"{asmName.Name}.{asmName.Version}.dll"; + Log(Level.Debug, $"Looking for file {testFilen}"); + + if (filenameLocations.TryGetValue(testFilen, out string path)) + { + Log(Level.Debug, $"Found file {testFilen} as {path}"); + if (File.Exists(path)) + { + return Assembly.LoadFrom(path); + } + else + { + Log(Level.Critical, $"but {path} no longer exists!"); + } + } + + Log(Level.Critical, $"No library {asmName} found"); + + return null; + } + + private static void Log(Level lvl, string message) + { // multiple proxy methods to delay loading of assemblies until it's done + if (LogCreated) + AssemblyLibLoaderCallLogger(lvl, message); + else + if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0) + Console.WriteLine($"[{lvl}] {message}"); + } + + private static void AssemblyLibLoaderCallLogger(Level lvl, string message) + { + libLoader.Log(lvl, message); + } + + // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree + private static IEnumerable TraverseTree(string root, Func dirValidator = null) + { + if (dirValidator == null) dirValidator = (s) => true; + + // Data structure to hold names of subfolders to be + // examined for files. + Stack dirs = new Stack(32); + + if (!System.IO.Directory.Exists(root)) + { + throw new ArgumentException(); + } + dirs.Push(root); + + while (dirs.Count > 0) + { + string currentDir = dirs.Pop(); + string[] subDirs; + try + { + subDirs = System.IO.Directory.GetDirectories(currentDir); + } + // An UnauthorizedAccessException exception will be thrown if we do not have + // discovery permission on a folder or file. It may or may not be acceptable + // to ignore the exception and continue enumerating the remaining files and + // folders. It is also possible (but unlikely) that a DirectoryNotFound exception + // will be raised. This will happen if currentDir has been deleted by + // another application or thread after our call to Directory.Exists. The + // choice of which exceptions to catch depends entirely on the specific task + // you are intending to perform and also on how much you know with certainty + // about the systems on which this code will run. + catch (UnauthorizedAccessException e) + { + //Console.WriteLine(e.Message); + continue; + } + catch (System.IO.DirectoryNotFoundException e) + { + //Console.WriteLine(e.Message); + continue; + } + + string[] files = null; + try + { + files = System.IO.Directory.GetFiles(currentDir); + } + + catch (UnauthorizedAccessException e) + { + + //Console.WriteLine(e.Message); + continue; + } + + catch (System.IO.DirectoryNotFoundException e) + { + //Console.WriteLine(e.Message); + continue; + } + + // Push the subdirectories onto the stack for traversal. + // This could also be done before handing the files. + foreach (string str in subDirs) + if (dirValidator(str)) dirs.Push(str); + + // Perform the required action on each file here. + // Modify this block to perform your required task. + foreach (string file in files) + { + FileInfo nextValue = null; + try + { + // Perform whatever action is required in your scenario. + nextValue = new System.IO.FileInfo(file); + //Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime); + } + catch (System.IO.FileNotFoundException e) + { + // If file was deleted by a separate application + // or thread since the call to TraverseTree() + // then just continue. + //Console.WriteLine(e.Message); + continue; + } + + yield return nextValue; + } + } + } + } +}