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.

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