using System.Diagnostics; using System.Reflection; using System.Text.RegularExpressions; using System.Xml.Serialization; using PluginLoader.GUI; using ProtoBuf; using Sandbox.Graphics.GUI; using VRage; using VRage.Utils; namespace PluginLoader.Data; [XmlInclude(typeof(WorkshopPlugin))] [XmlInclude(typeof(SEPMPlugin))] [XmlInclude(typeof(GitHubPlugin))] [XmlInclude(typeof(ModPlugin))] [ProtoContract] [ProtoInclude(100, typeof(SteamPlugin))] [ProtoInclude(103, typeof(GitHubPlugin))] [ProtoInclude(104, typeof(ModPlugin))] public abstract class PluginData : IEquatable { public abstract string Source { get; } [XmlIgnore] public Version Version { get; protected set; } [XmlIgnore] public virtual PluginStatus Status { get; set; } = PluginStatus.None; public virtual string StatusString { get { switch (Status) { case PluginStatus.PendingUpdate: return "Pending Update"; case PluginStatus.Updated: return "Updated"; case PluginStatus.Error: return "Error!"; case PluginStatus.Blocked: return "Not whitelisted!"; default: return ""; } } } [XmlIgnore] public bool IsLocal => Source == MyTexts.GetString(MyCommonTexts.Local); [ProtoMember(1)] public virtual string Id { get; set; } [ProtoMember(2)] public string FriendlyName { get; set; } = "Unknown"; [ProtoMember(3)] public bool Hidden { get; set; } = false; [ProtoMember(4)] public string GroupId { get; set; } [ProtoMember(5)] public string Tooltip { get; set; } [ProtoMember(6)] public string Author { get; set; } [ProtoMember(7)] public string Description { get; set; } [XmlIgnore] public List Group { get; } = new(); [XmlIgnore] public bool Enabled => Main.Instance.Config.IsEnabled(Id); public bool Equals(PluginData other) { return other != null && Id == other.Id; } public abstract Assembly? GetAssembly(); public virtual bool TryLoadAssembly(out Assembly? a) { if (Status == PluginStatus.Error) { a = null; return false; } try { // Get the file path a = GetAssembly(); if (Status == PluginStatus.Blocked) return false; if (a == null) { LogFile.WriteLine("Failed to load " + ToString()); Error(); return false; } return true; } catch (Exception e) { var name = ToString(); LogFile.WriteLine($"Failed to load {name} because of an error: " + e); if (e is MissingMemberException) LogFile.WriteLine($"Is {name} up to date?"); if (e is NotSupportedException && e.Message.Contains("loadFromRemoteSources")) Error($"The plugin {name} was blocked by windows. Please unblock the file in the dll file properties."); else Error(); a = null; return false; } } public override bool Equals(object obj) { return Equals(obj as PluginData); } public override int GetHashCode() { return 2108858624 + EqualityComparer.Default.GetHashCode(Id); } public static bool operator ==(PluginData left, PluginData right) { return EqualityComparer.Default.Equals(left, right); } public static bool operator !=(PluginData left, PluginData right) { return !(left == right); } public override string ToString() { return Id + '|' + FriendlyName; } public void Error(string msg = null) { Status = PluginStatus.Error; if (msg == null) msg = $"The plugin '{this}' caused an error. It is recommended that you disable this plugin and restart. The game may be unstable beyond this point. See loader.log or the game log for details."; var file = MyLog.Default.GetFilePath(); if (File.Exists(file) && file.EndsWith(".log")) { MyLog.Default.Flush(); msg += "\n\nWould you like to open the game log?"; var result = LoaderTools.ShowMessageBox(msg, "Plugin Loader", MessageBoxButtons.YesNo, MessageBoxIcon.Error); if (result == DialogResult.Yes) Process.Start(file); } else { LoaderTools.ShowMessageBox(msg, "Plugin Loader", MessageBoxButtons.OK, MessageBoxIcon.Error); } } protected void ErrorSecurity(string hash) { Status = PluginStatus.Blocked; LoaderTools.ShowMessageBox($"Unable to load the plugin {this} because it is not whitelisted!", "Plugin Loader", MessageBoxButtons.OK, MessageBoxIcon.Error); LogFile.WriteLine("Error: " + this + " with an sha256 of " + hash + " is not on the whitelist!"); } public abstract void Show(); public virtual void GetDescriptionText(MyGuiControlMultilineText textbox) { textbox.Visible = true; textbox.Clear(); if (string.IsNullOrEmpty(Description)) { if (string.IsNullOrEmpty(Tooltip)) textbox.AppendText("No description"); else textbox.AppendText(CapLength(Tooltip, 1000)); return; } var text = CapLength(Description, 1000); var textStart = 0; foreach (Match m in Regex.Matches(text, @"https?:\/\/(www\.)?[\w-.]{2,256}\.[a-z]{2,4}\b[\w-.@:%\+~#?&//=]*")) { var textLen = m.Index - textStart; if (textLen > 0) textbox.AppendText(text.Substring(textStart, textLen)); textbox.AppendLink(m.Value, m.Value); textStart = m.Index + m.Length; } if (textStart < text.Length) textbox.AppendText(text.Substring(textStart)); } private string CapLength(string s, int len) { if (s.Length > len) return s.Substring(0, len); return s; } public virtual bool OpenContextMenu(MyGuiControlContextMenu menu) { return false; } public virtual void ContextMenuClicked(MyGuiScreenPluginConfig screen, MyGuiControlContextMenu.EventArgs args) { } }