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.

188 lines
6.6 KiB

  1. using IllusionInjector.Logging;
  2. using IllusionPlugin;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Runtime.CompilerServices;
  10. using System.Runtime.InteropServices;
  11. using System.Text;
  12. namespace IllusionInjector
  13. {
  14. public static class PluginManager
  15. {
  16. #pragma warning disable CS0618 // Type or member is obsolete (IPlugin)
  17. /// <summary>
  18. /// Gets the list of loaded plugins and loads them if necessary.
  19. /// </summary>
  20. public static IEnumerable<IBeatSaberPlugin> BSPlugins
  21. {
  22. get
  23. {
  24. if(_bsPlugins == null)
  25. {
  26. LoadPlugins();
  27. }
  28. return _bsPlugins;
  29. }
  30. }
  31. private static List<IBeatSaberPlugin> _bsPlugins = null;
  32. public static IEnumerable<IPlugin> IPAPlugins
  33. {
  34. get
  35. {
  36. if (_ipaPlugins == null)
  37. {
  38. LoadPlugins();
  39. }
  40. return _ipaPlugins;
  41. }
  42. }
  43. private static List<IPlugin> _ipaPlugins = null;
  44. private static void LoadPlugins()
  45. {
  46. string pluginDirectory = Path.Combine(Environment.CurrentDirectory, "Plugins");
  47. // Process.GetCurrentProcess().MainModule crashes the game and Assembly.GetEntryAssembly() is NULL,
  48. // so we need to resort to P/Invoke
  49. string exeName = Path.GetFileNameWithoutExtension(AppInfo.StartupPath);
  50. Logger.log.Info(exeName);
  51. _bsPlugins = new List<IBeatSaberPlugin>();
  52. _ipaPlugins = new List<IPlugin>();
  53. if (!Directory.Exists(pluginDirectory)) return;
  54. string cacheDir = Path.Combine(pluginDirectory, ".cache");
  55. if (!Directory.Exists(cacheDir))
  56. {
  57. Directory.CreateDirectory(cacheDir);
  58. }
  59. else
  60. {
  61. foreach (string plugin in Directory.GetFiles(cacheDir, "*"))
  62. {
  63. File.Delete(plugin);
  64. }
  65. }
  66. //Copy plugins to .cache
  67. string[] originalPlugins = Directory.GetFiles(pluginDirectory, "*.dll");
  68. foreach (string s in originalPlugins)
  69. {
  70. string pluginCopy = Path.Combine(cacheDir, Path.GetFileName(s));
  71. File.Copy(Path.Combine(pluginDirectory, s), pluginCopy);
  72. }
  73. //Load copied plugins
  74. string[] copiedPlugins = Directory.GetFiles(cacheDir, "*.dll");
  75. foreach (string s in copiedPlugins)
  76. {
  77. var result = LoadPluginsFromFile(s, exeName);
  78. _bsPlugins.AddRange(result.Item1);
  79. _ipaPlugins.AddRange(result.Item2);
  80. }
  81. // DEBUG
  82. Logger.log.Info($"Running on Unity {UnityEngine.Application.unityVersion}");
  83. Logger.log.Info("-----------------------------");
  84. Logger.log.Info($"Loading plugins from {pluginDirectory} and found {_bsPlugins.Count}");
  85. Logger.log.Info("-----------------------------");
  86. foreach (var plugin in _bsPlugins)
  87. {
  88. Logger.log.Info($"{plugin.Name}: {plugin.Version}");
  89. }
  90. Logger.log.Info("-----------------------------");
  91. }
  92. private static Tuple<IEnumerable<IBeatSaberPlugin>, IEnumerable<IPlugin>> LoadPluginsFromFile(string file, string exeName)
  93. {
  94. List<IBeatSaberPlugin> bsPlugins = new List<IBeatSaberPlugin>();
  95. List<IPlugin> ipaPlugins = new List<IPlugin>();
  96. if (!File.Exists(file) || !file.EndsWith(".dll", true, null))
  97. return new Tuple<IEnumerable<IBeatSaberPlugin>, IEnumerable<IPlugin>>(bsPlugins, ipaPlugins);
  98. T OptionalGetPlugin<T>(Type t) where T : class
  99. {
  100. // use typeof() to allow for easier renaming (in an ideal world this compiles to a string, but ¯\_(ツ)_/¯)
  101. if (t.GetInterface(typeof(T).Name) != null)
  102. {
  103. try
  104. {
  105. T pluginInstance = Activator.CreateInstance(t) as T;
  106. string[] filter = null;
  107. if (pluginInstance is IGenericEnhancedPlugin)
  108. {
  109. filter = ((IGenericEnhancedPlugin)pluginInstance).Filter;
  110. }
  111. if (filter == null || filter.Contains(exeName, StringComparer.OrdinalIgnoreCase))
  112. return pluginInstance;
  113. }
  114. catch (Exception e)
  115. {
  116. Logger.log.Error($"Could not load plugin {t.FullName} in {Path.GetFileName(file)}! {e}");
  117. }
  118. }
  119. return null;
  120. }
  121. try
  122. {
  123. Assembly assembly = Assembly.LoadFrom(file);
  124. foreach (Type t in assembly.GetTypes())
  125. {
  126. IBeatSaberPlugin bsPlugin = OptionalGetPlugin<IBeatSaberPlugin>(t);
  127. if (bsPlugin != null)
  128. {
  129. bsPlugins.Add(bsPlugin);
  130. }
  131. else
  132. {
  133. IPlugin ipaPlugin = OptionalGetPlugin<IPlugin>(t);
  134. if (ipaPlugin != null)
  135. {
  136. ipaPlugins.Add(ipaPlugin);
  137. }
  138. }
  139. }
  140. }
  141. catch (Exception e)
  142. {
  143. Logger.log.Error($"Could not load {Path.GetFileName(file)}! {e}");
  144. }
  145. return new Tuple<IEnumerable<IBeatSaberPlugin>, IEnumerable<IPlugin>>(bsPlugins, ipaPlugins);
  146. }
  147. public class AppInfo
  148. {
  149. [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false)]
  150. private static extern int GetModuleFileName(HandleRef hModule, StringBuilder buffer, int length);
  151. private static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
  152. public static string StartupPath
  153. {
  154. get
  155. {
  156. StringBuilder stringBuilder = new StringBuilder(260);
  157. GetModuleFileName(NullHandleRef, stringBuilder, stringBuilder.Capacity);
  158. return stringBuilder.ToString();
  159. }
  160. }
  161. }
  162. #pragma warning restore CS0618 // Type or member is obsolete (IPlugin)
  163. }
  164. }