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.

194 lines
7.2 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using IPA.Logging;
  6. using Mono.Cecil;
  7. namespace IPA.Loader
  8. {
  9. internal class CecilLibLoader : BaseAssemblyResolver
  10. {
  11. public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
  12. {
  13. LibLoader.SetupAssemblyFilenames();
  14. var testFile = $"{name.Name}.{name.Version}.dll";
  15. if (LibLoader.FilenameLocations.TryGetValue(testFile, out string path))
  16. {
  17. if (File.Exists(path))
  18. {
  19. return AssemblyDefinition.ReadAssembly(path, parameters);
  20. }
  21. }
  22. return base.Resolve(name, parameters);
  23. }
  24. }
  25. internal static class LibLoader
  26. {
  27. internal static string LibraryPath => Path.Combine(Environment.CurrentDirectory, "Libs");
  28. internal static string NativeLibraryPath => Path.Combine(LibraryPath, "Native");
  29. internal static Dictionary<string, string> FilenameLocations;
  30. internal static void SetupAssemblyFilenames(bool force = false)
  31. {
  32. if (FilenameLocations == null || force)
  33. {
  34. FilenameLocations = new Dictionary<string, string>();
  35. foreach (var fn in TraverseTree(LibraryPath, s => s != NativeLibraryPath))
  36. if (FilenameLocations.ContainsKey(fn.Name))
  37. Log(Logger.Level.Critical, $"Multiple instances of {fn.Name} exist in Libs! Ignoring {fn.FullName}");
  38. else FilenameLocations.Add(fn.Name, fn.FullName);
  39. }
  40. }
  41. public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e)
  42. {
  43. var asmName = new AssemblyName(e.Name);
  44. return LoadLibrary(asmName);
  45. }
  46. internal static Assembly LoadLibrary(AssemblyName asmName)
  47. {
  48. Log(Logger.Level.Debug, $"Resolving library {asmName}");
  49. SetupAssemblyFilenames();
  50. var testFile = $"{asmName.Name}.{asmName.Version}.dll";
  51. Log(Logger.Level.Debug, $"Looking for file {testFile}");
  52. if (FilenameLocations.TryGetValue(testFile, out var path))
  53. {
  54. Log(Logger.Level.Debug, $"Found file {testFile} as {path}");
  55. if (File.Exists(path))
  56. return Assembly.LoadFrom(path);
  57. Log(Logger.Level.Critical, $"but {path} no longer exists!");
  58. }
  59. else if (FilenameLocations.TryGetValue(testFile = $"{asmName.Name}.dll", out path))
  60. {
  61. Log(Logger.Level.Debug, $"Found file {testFile} as {path}");
  62. if (File.Exists(path))
  63. return Assembly.LoadFrom(path);
  64. Log(Logger.Level.Critical, $"but {path} no longer exists!");
  65. }
  66. Log(Logger.Level.Critical, $"No library {asmName} found");
  67. return null;
  68. }
  69. internal static void Log(Logger.Level lvl, string message)
  70. { // multiple proxy methods to delay loading of assemblies until it's done
  71. if (Logger.LogCreated)
  72. AssemblyLibLoaderCallLogger(lvl, message);
  73. else
  74. if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0)
  75. Console.WriteLine($"[{lvl}] {message}");
  76. }
  77. private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, string message)
  78. {
  79. Logger.libLoader.Log(lvl, message);
  80. }
  81. // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree
  82. private static IEnumerable<FileInfo> TraverseTree(string root, Func<string, bool> dirValidator = null)
  83. {
  84. if (dirValidator == null) dirValidator = s => true;
  85. // Data structure to hold names of subfolders to be
  86. // examined for files.
  87. Stack<string> dirs = new Stack<string>(32);
  88. if (!Directory.Exists(root))
  89. {
  90. throw new ArgumentException();
  91. }
  92. dirs.Push(root);
  93. while (dirs.Count > 0)
  94. {
  95. string currentDir = dirs.Pop();
  96. string[] subDirs;
  97. try
  98. {
  99. subDirs = Directory.GetDirectories(currentDir);
  100. }
  101. // An UnauthorizedAccessException exception will be thrown if we do not have
  102. // discovery permission on a folder or file. It may or may not be acceptable
  103. // to ignore the exception and continue enumerating the remaining files and
  104. // folders. It is also possible (but unlikely) that a DirectoryNotFound exception
  105. // will be raised. This will happen if currentDir has been deleted by
  106. // another application or thread after our call to Directory.Exists. The
  107. // choice of which exceptions to catch depends entirely on the specific task
  108. // you are intending to perform and also on how much you know with certainty
  109. // about the systems on which this code will run.
  110. catch (UnauthorizedAccessException)
  111. {
  112. //Console.WriteLine(e.Message);
  113. continue;
  114. }
  115. catch (DirectoryNotFoundException)
  116. {
  117. //Console.WriteLine(e.Message);
  118. continue;
  119. }
  120. string[] files;
  121. try
  122. {
  123. files = Directory.GetFiles(currentDir);
  124. }
  125. catch (UnauthorizedAccessException)
  126. {
  127. //Console.WriteLine(e.Message);
  128. continue;
  129. }
  130. catch (DirectoryNotFoundException)
  131. {
  132. //Console.WriteLine(e.Message);
  133. continue;
  134. }
  135. // Push the subdirectories onto the stack for traversal.
  136. // This could also be done before handing the files.
  137. foreach (string str in subDirs)
  138. if (dirValidator(str)) dirs.Push(str);
  139. // Perform the required action on each file here.
  140. // Modify this block to perform your required task.
  141. foreach (string file in files)
  142. {
  143. FileInfo nextValue;
  144. try
  145. {
  146. // Perform whatever action is required in your scenario.
  147. nextValue = new FileInfo(file);
  148. //Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
  149. }
  150. catch (FileNotFoundException)
  151. {
  152. // If file was deleted by a separate application
  153. // or thread since the call to TraverseTree()
  154. // then just continue.
  155. //Console.WriteLine(e.Message);
  156. continue;
  157. }
  158. yield return nextValue;
  159. }
  160. }
  161. }
  162. }
  163. }