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.

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