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.

227 lines
8.1 KiB

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