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.

231 lines
9.0 KiB

5 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.IO;
  6. using System.Reflection;
  7. using System.Runtime.InteropServices;
  8. using System.Linq;
  9. using IPA.Logging;
  10. using IPA.Utilities;
  11. using Mono.Cecil;
  12. namespace IPA.Loader
  13. {
  14. internal class CecilLibLoader : BaseAssemblyResolver
  15. {
  16. private static string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
  17. private static string CurrentAssemblyPath = Assembly.GetExecutingAssembly().Location;
  18. public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
  19. {
  20. LibLoader.SetupAssemblyFilenames();
  21. if (name.Name == CurrentAssemblyName)
  22. return AssemblyDefinition.ReadAssembly(CurrentAssemblyPath, parameters);
  23. if (LibLoader.FilenameLocations.TryGetValue($"{name.Name}.{name.Version}.dll", out var path))
  24. {
  25. if (File.Exists(path))
  26. return AssemblyDefinition.ReadAssembly(path, parameters);
  27. }
  28. else if (LibLoader.FilenameLocations.TryGetValue($"{name.Name}.dll", out path))
  29. {
  30. if (File.Exists(path))
  31. return AssemblyDefinition.ReadAssembly(path, parameters);
  32. }
  33. return base.Resolve(name, parameters);
  34. }
  35. }
  36. internal static class LibLoader
  37. {
  38. internal static string LibraryPath => Path.Combine(Environment.CurrentDirectory, "Libs");
  39. internal static string NativeLibraryPath => Path.Combine(LibraryPath, "Native");
  40. internal static Dictionary<string, string> FilenameLocations;
  41. internal static void Configure()
  42. {
  43. SetupAssemblyFilenames(true);
  44. AppDomain.CurrentDomain.AssemblyResolve -= AssemblyLibLoader;
  45. AppDomain.CurrentDomain.AssemblyResolve += AssemblyLibLoader;
  46. }
  47. internal static void SetupAssemblyFilenames(bool force = false)
  48. {
  49. if (FilenameLocations == null || force)
  50. {
  51. FilenameLocations = new Dictionary<string, string>();
  52. foreach (var fn in TraverseTree(LibraryPath, s => s != NativeLibraryPath))
  53. if (FilenameLocations.ContainsKey(fn.Name))
  54. Log(Logger.Level.Critical, $"Multiple instances of {fn.Name} exist in Libs! Ignoring {fn.FullName}");
  55. else FilenameLocations.Add(fn.Name, fn.FullName);
  56. if (!SetDefaultDllDirectories(LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_SYSTEM32
  57. | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_APPLICATION_DIR))
  58. {
  59. var err = new Win32Exception();
  60. Log(Logger.Level.Critical, $"Error configuring DLL search path");
  61. Log(Logger.Level.Critical, err);
  62. return;
  63. }
  64. void AddDir(string path)
  65. {
  66. var retPtr = AddDllDirectory(path);
  67. if (retPtr == IntPtr.Zero)
  68. {
  69. var err = new Win32Exception();
  70. Log(Logger.Level.Warning, $"Could not add DLL directory");
  71. Log(Logger.Level.Warning, err);
  72. }
  73. }
  74. if (Directory.Exists(NativeLibraryPath))
  75. {
  76. AddDir(NativeLibraryPath);
  77. TraverseTree(NativeLibraryPath, dir =>
  78. { // this is a terrible hack for iterating directories
  79. AddDir(dir); return true;
  80. }).All(f => true); // force it to iterate all
  81. }
  82. //var unityData = Directory.EnumerateDirectories(Environment.CurrentDirectory, "*_Data").First();
  83. //AddDir(Path.Combine(unityData, "Plugins"));
  84. foreach (var dir in Environment.GetEnvironmentVariable("path").Split(Path.PathSeparator))
  85. AddDir(dir);
  86. }
  87. }
  88. public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e)
  89. {
  90. var asmName = new AssemblyName(e.Name);
  91. return LoadLibrary(asmName);
  92. }
  93. internal static Assembly LoadLibrary(AssemblyName asmName)
  94. {
  95. Log(Logger.Level.Debug, $"Resolving library {asmName}");
  96. SetupAssemblyFilenames();
  97. var testFile = $"{asmName.Name}.{asmName.Version}.dll";
  98. Log(Logger.Level.Debug, $"Looking for file {testFile}");
  99. if (FilenameLocations.TryGetValue(testFile, out var path))
  100. {
  101. Log(Logger.Level.Debug, $"Found file {testFile} as {path}");
  102. if (File.Exists(path))
  103. return Assembly.LoadFrom(path);
  104. Log(Logger.Level.Critical, $"but {path} no longer exists!");
  105. }
  106. else if (FilenameLocations.TryGetValue(testFile = $"{asmName.Name}.dll", out path))
  107. {
  108. Log(Logger.Level.Debug, $"Found file {testFile} as {path}");
  109. if (File.Exists(path))
  110. return Assembly.LoadFrom(path);
  111. Log(Logger.Level.Critical, $"but {path} no longer exists!");
  112. }
  113. Log(Logger.Level.Critical, $"No library {asmName} found");
  114. return null;
  115. }
  116. internal static void Log(Logger.Level lvl, string message)
  117. { // multiple proxy methods to delay loading of assemblies until it's done
  118. if (Logger.LogCreated)
  119. AssemblyLibLoaderCallLogger(lvl, message);
  120. else
  121. if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0)
  122. Console.WriteLine($"[{lvl}] {message}");
  123. }
  124. internal static void Log(Logger.Level lvl, Exception message)
  125. { // multiple proxy methods to delay loading of assemblies until it's done
  126. if (Logger.LogCreated)
  127. AssemblyLibLoaderCallLogger(lvl, message);
  128. else
  129. if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0)
  130. Console.WriteLine($"[{lvl}] {message}");
  131. }
  132. private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, string message) => Logger.libLoader.Log(lvl, message);
  133. private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, Exception message) => Logger.libLoader.Log(lvl, message);
  134. // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree
  135. private static IEnumerable<FileInfo> TraverseTree(string root, Func<string, bool> dirValidator = null)
  136. {
  137. if (dirValidator == null) dirValidator = s => true;
  138. Stack<string> dirs = new Stack<string>(32);
  139. if (!Directory.Exists(root))
  140. throw new ArgumentException();
  141. dirs.Push(root);
  142. while (dirs.Count > 0)
  143. {
  144. string currentDir = dirs.Pop();
  145. string[] subDirs;
  146. try
  147. {
  148. subDirs = Directory.GetDirectories(currentDir);
  149. }
  150. catch (UnauthorizedAccessException)
  151. { continue; }
  152. catch (DirectoryNotFoundException)
  153. { continue; }
  154. string[] files;
  155. try
  156. {
  157. files = Directory.GetFiles(currentDir);
  158. }
  159. catch (UnauthorizedAccessException)
  160. { continue; }
  161. catch (DirectoryNotFoundException)
  162. { continue; }
  163. foreach (string str in subDirs)
  164. if (dirValidator(str)) dirs.Push(str);
  165. foreach (string file in files)
  166. {
  167. FileInfo nextValue;
  168. try
  169. {
  170. nextValue = new FileInfo(file);
  171. }
  172. catch (FileNotFoundException)
  173. { continue; }
  174. yield return nextValue;
  175. }
  176. }
  177. }
  178. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  179. private static extern IntPtr AddDllDirectory(string lpPathName);
  180. [Flags]
  181. [SuppressMessage("ReSharper", "InconsistentNaming")]
  182. [SuppressMessage("ReSharper", "UnusedMember.Local")]
  183. private enum LoadLibraryFlags : uint
  184. {
  185. None = 0,
  186. LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200,
  187. LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000,
  188. LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800,
  189. LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400,
  190. }
  191. [DllImport("kernel32.dll", SetLastError = true)]
  192. private static extern bool SetDefaultDllDirectories(LoadLibraryFlags dwFlags);
  193. }
  194. }