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.

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