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.

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