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.

183 lines
6.8 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()
  31. {
  32. if (FilenameLocations == null)
  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. Log(Logger.Level.Debug, $"Resolving library {asmName}");
  45. SetupAssemblyFilenames();
  46. var testFile = $"{asmName.Name}.{asmName.Version}.dll";
  47. Log(Logger.Level.Debug, $"Looking for file {testFile}");
  48. if (FilenameLocations.TryGetValue(testFile, out string path))
  49. {
  50. Log(Logger.Level.Debug, $"Found file {testFile} as {path}");
  51. if (File.Exists(path))
  52. {
  53. return Assembly.LoadFrom(path);
  54. }
  55. Log(Logger.Level.Critical, $"but {path} no longer exists!");
  56. }
  57. Log(Logger.Level.Critical, $"No library {asmName} found");
  58. return null;
  59. }
  60. internal static void Log(Logger.Level lvl, string message)
  61. { // multiple proxy methods to delay loading of assemblies until it's done
  62. if (Logger.LogCreated)
  63. AssemblyLibLoaderCallLogger(lvl, message);
  64. else
  65. if (((byte)lvl & (byte)StandardLogger.PrintFilter) != 0)
  66. Console.WriteLine($"[{lvl}] {message}");
  67. }
  68. private static void AssemblyLibLoaderCallLogger(Logger.Level lvl, string message)
  69. {
  70. Logger.libLoader.Log(lvl, message);
  71. }
  72. // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree
  73. private static IEnumerable<FileInfo> TraverseTree(string root, Func<string, bool> dirValidator = null)
  74. {
  75. if (dirValidator == null) dirValidator = s => true;
  76. // Data structure to hold names of subfolders to be
  77. // examined for files.
  78. Stack<string> dirs = new Stack<string>(32);
  79. if (!Directory.Exists(root))
  80. {
  81. throw new ArgumentException();
  82. }
  83. dirs.Push(root);
  84. while (dirs.Count > 0)
  85. {
  86. string currentDir = dirs.Pop();
  87. string[] subDirs;
  88. try
  89. {
  90. subDirs = Directory.GetDirectories(currentDir);
  91. }
  92. // An UnauthorizedAccessException exception will be thrown if we do not have
  93. // discovery permission on a folder or file. It may or may not be acceptable
  94. // to ignore the exception and continue enumerating the remaining files and
  95. // folders. It is also possible (but unlikely) that a DirectoryNotFound exception
  96. // will be raised. This will happen if currentDir has been deleted by
  97. // another application or thread after our call to Directory.Exists. The
  98. // choice of which exceptions to catch depends entirely on the specific task
  99. // you are intending to perform and also on how much you know with certainty
  100. // about the systems on which this code will run.
  101. catch (UnauthorizedAccessException)
  102. {
  103. //Console.WriteLine(e.Message);
  104. continue;
  105. }
  106. catch (DirectoryNotFoundException)
  107. {
  108. //Console.WriteLine(e.Message);
  109. continue;
  110. }
  111. string[] files;
  112. try
  113. {
  114. files = Directory.GetFiles(currentDir);
  115. }
  116. catch (UnauthorizedAccessException)
  117. {
  118. //Console.WriteLine(e.Message);
  119. continue;
  120. }
  121. catch (DirectoryNotFoundException)
  122. {
  123. //Console.WriteLine(e.Message);
  124. continue;
  125. }
  126. // Push the subdirectories onto the stack for traversal.
  127. // This could also be done before handing the files.
  128. foreach (string str in subDirs)
  129. if (dirValidator(str)) dirs.Push(str);
  130. // Perform the required action on each file here.
  131. // Modify this block to perform your required task.
  132. foreach (string file in files)
  133. {
  134. FileInfo nextValue;
  135. try
  136. {
  137. // Perform whatever action is required in your scenario.
  138. nextValue = new FileInfo(file);
  139. //Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
  140. }
  141. catch (FileNotFoundException)
  142. {
  143. // If file was deleted by a separate application
  144. // or thread since the call to TraverseTree()
  145. // then just continue.
  146. //Console.WriteLine(e.Message);
  147. continue;
  148. }
  149. yield return nextValue;
  150. }
  151. }
  152. }
  153. }
  154. }