using System.Diagnostics; using System.Net; using System.Reflection; using HarmonyLib; using PluginLoader.Compiler; using PluginLoader.Data; using PluginLoader.GUI; using PluginLoader.Stats; using Sandbox.Game.World; using VRage.Plugins; using SEPMPlugin = PluginLoader.SEPM.SEPMPlugin; namespace PluginLoader; public class Main : IHandleInputPlugin { private const string HarmonyVersion = "2.2.1.0"; public static Main Instance; private readonly List plugins = new(); private bool init; public Main() { var sw = Stopwatch.StartNew(); Splash = new(); Instance = this; var temp = Cursor.Current; Cursor.Current = Cursors.AppStarting; var pluginsDir = LoaderTools.PluginsDir; Directory.CreateDirectory(pluginsDir); LogFile.Init(pluginsDir); LogFile.WriteLine("Starting - v" + Assembly.GetExecutingAssembly().GetName().Version.ToString(3)); // Fix tls 1.2 not supported on Windows 7 - github.com is tls 1.2 only try { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; } catch (NotSupportedException e) { LogFile.WriteLine("An error occurred while setting up networking, web requests will probably fail: " + e); } Splash.SetText("Finding references..."); RoslynReferences.GenerateAssemblyList(); AppDomain.CurrentDomain.AssemblyResolve += ResolveDependencies; Config = PluginConfig.Load(pluginsDir); List = new(pluginsDir, Config); Config.Init(List); StatsClient.OverrideBaseUrl(Config.StatsServerBaseUrl); Splash.SetText("Patching..."); LogFile.WriteLine("Patching"); // Check harmony version var expectedHarmony = new Version(HarmonyVersion); var actualHarmony = typeof(Harmony).Assembly.GetName().Version; if (expectedHarmony != actualHarmony) LogFile.WriteLine( $"WARNING: Unexpected Harmony version, plugins may be unstable. Expected {expectedHarmony} but found {actualHarmony}"); new Harmony("avaness.PluginLoader").PatchAll(Assembly.GetExecutingAssembly()); Splash.SetText("Instantiating plugins..."); LogFile.WriteLine("Instantiating plugins"); foreach (var id in Config) { var data = List[id]; if (data is GitHubPlugin github) github.Init(pluginsDir); if (PluginInstance.TryGet(data, out var p)) { plugins.Add(p); if (data.IsLocal) HasLocal = true; } } sw.Stop(); // FIXME: It can potentially run in the background speeding up the game's startup ReportEnabledPlugins(); LogFile.WriteLine($"Finished startup. Took {sw.ElapsedMilliseconds}ms"); Cursor.Current = temp; Splash.Delete(); Splash = null; } public PluginList List { get; } public PluginConfig Config { get; } public SplashScreen Splash { get; } /// /// True if a local plugin was loaded /// public bool HasLocal { get; } // Skip local plugins, keep only enabled ones public string[] TrackablePluginIds => Config.EnabledPlugins.Where(id => !List[id].IsLocal).ToArray(); public void Init(object gameInstance) { LogFile.WriteLine($"Initializing {plugins.Count} plugins"); for (var i = plugins.Count - 1; i >= 0; i--) { var p = plugins[i]; if (!p.Init(gameInstance)) plugins.RemoveAtFast(i); } init = true; } public void Update() { if (init) for (var i = plugins.Count - 1; i >= 0; i--) { var p = plugins[i]; if (!p.Update()) plugins.RemoveAtFast(i); } } public void HandleInput() { if (init) for (var i = plugins.Count - 1; i >= 0; i--) { var p = plugins[i]; if (!p.HandleInput()) plugins.RemoveAtFast(i); } } public void Dispose() { foreach (var p in plugins) p.Dispose(); plugins.Clear(); AppDomain.CurrentDomain.AssemblyResolve -= ResolveDependencies; LogFile.Dispose(); Instance = null; } public bool TryGetPluginInstance(string id, out PluginInstance instance) { instance = null; if (!init) return false; foreach (var p in plugins) if (p.Id == id) { instance = p; return true; } return false; } private void ReportEnabledPlugins() { if (!PlayerConsent.ConsentGiven) return; Splash.SetText("Reporting plugin usage..."); LogFile.WriteLine("Reporting plugin usage"); // Config has already been validated at this point so all enabled plugins will have list items // FIXME: Move into a background thread if (StatsClient.Track(TrackablePluginIds)) LogFile.WriteLine("List of enabled plugins has been sent to the statistics server"); else LogFile.WriteLine("Failed to send the list of enabled plugins to the statistics server"); } public void RegisterComponents() { LogFile.WriteLine($"Registering {plugins.Count} components"); foreach (var plugin in plugins) plugin.RegisterSession(MySession.Static); } public void DisablePlugins() { Config.Disable(); plugins.Clear(); LogFile.WriteLine("Disabled all plugins"); } public void InstantiatePlugins() { LogFile.WriteLine($"Loading {plugins.Count} plugins"); for (var i = plugins.Count - 1; i >= 0; i--) { var p = plugins[i]; if (!p.Instantiate()) plugins.RemoveAtFast(i); } } private Assembly ResolveDependencies(object sender, ResolveEventArgs args) { var assembly = args.RequestingAssembly?.GetName().ToString(); if (args.Name.Contains("0Harmony")) { if (assembly != null) LogFile.WriteLine("Resolving 0Harmony for " + assembly); else LogFile.WriteLine("Resolving 0Harmony"); return typeof(Harmony).Assembly; } if (args.Name.Contains("SEPluginManager")) { if (assembly != null) LogFile.WriteLine("Resolving SEPluginManager for " + assembly); else LogFile.WriteLine("Resolving SEPluginManager"); return typeof(SEPMPlugin).Assembly; } return null; } }