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.

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