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.

271 lines
11 KiB

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