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.

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