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.

237 lines
9.1 KiB

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