diff --git a/CringeLauncher.sln b/CringeLauncher.sln index 60cb0a0..c63d6ec 100644 --- a/CringeLauncher.sln +++ b/CringeLauncher.sln @@ -2,8 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CringeBootstrap", "CringeBootstrap\CringeBootstrap.csproj", "{219C897E-452D-49B5-80C4-F3008718C16A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginLoader", "PluginLoader\PluginLoader.csproj", "{A7C22A74-56EA-4DC2-89AA-A1134BFB8497}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CringeLauncher", "CringeLauncher\CringeLauncher.csproj", "{2A1B48E9-ED82-4EEB-A18A-E4148DFE3A19}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CringeBootstrap.Abstractions", "CringeBootstrap.Abstractions\CringeBootstrap.Abstractions.csproj", "{12AA2BBC-E795-4065-AF4A-9A44AFF69D92}" @@ -26,10 +24,6 @@ Global {219C897E-452D-49B5-80C4-F3008718C16A}.Debug|Any CPU.Build.0 = Debug|Any CPU {219C897E-452D-49B5-80C4-F3008718C16A}.Release|Any CPU.ActiveCfg = Release|Any CPU {219C897E-452D-49B5-80C4-F3008718C16A}.Release|Any CPU.Build.0 = Release|Any CPU - {A7C22A74-56EA-4DC2-89AA-A1134BFB8497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A7C22A74-56EA-4DC2-89AA-A1134BFB8497}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A7C22A74-56EA-4DC2-89AA-A1134BFB8497}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A7C22A74-56EA-4DC2-89AA-A1134BFB8497}.Release|Any CPU.Build.0 = Release|Any CPU {2A1B48E9-ED82-4EEB-A18A-E4148DFE3A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2A1B48E9-ED82-4EEB-A18A-E4148DFE3A19}.Debug|Any CPU.Build.0 = Debug|Any CPU {2A1B48E9-ED82-4EEB-A18A-E4148DFE3A19}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/PluginLoader/Compiler/RoslynCompiler.cs b/PluginLoader/Compiler/RoslynCompiler.cs deleted file mode 100644 index 6983acd..0000000 --- a/PluginLoader/Compiler/RoslynCompiler.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Text; - -namespace PluginLoader.Compiler; - -public class RoslynCompiler -{ - private readonly List source = new(); - private readonly bool debugBuild; - - public RoslynCompiler(bool debugBuild = false) - { - this.debugBuild = debugBuild; - } - - public void Load(Stream s, string name) - { - var mem = new MemoryStream(); - using (mem) - { - s.CopyTo(mem); - source.Add(new(mem, name, debugBuild)); - } - } - - public byte[] Compile(string assemblyName, out byte[] symbols) - { - symbols = null; - - var compilation = CSharpCompilation.Create( - assemblyName, - source.Select(x => x.Tree), - RoslynReferences.EnumerateAllReferences(), - new( - OutputKind.DynamicallyLinkedLibrary, - optimizationLevel: debugBuild ? OptimizationLevel.Debug : OptimizationLevel.Release, allowUnsafe: true)); - - using (var pdb = new MemoryStream()) - using (var ms = new MemoryStream()) - { - // write IL code into memory - EmitResult result; - if (debugBuild) - result = compilation.Emit(ms, pdb, - embeddedTexts: source.Select(x => x.Text), - options: new(debugInformationFormat: DebugInformationFormat.PortablePdb, - pdbFilePath: Path.ChangeExtension(assemblyName, "pdb"))); - else - result = compilation.Emit(ms); - - if (!result.Success) - { - // handle exceptions - var failures = result.Diagnostics.Where(diagnostic => - diagnostic.IsWarningAsError || - diagnostic.Severity == DiagnosticSeverity.Error); - - foreach (var diagnostic in failures) - { - var location = diagnostic.Location; - var source = this.source.FirstOrDefault(x => x.Tree == location.SourceTree); - LogFile.Log.Debug( - $"{diagnostic.Id}: {diagnostic.GetMessage()} in file:\n{source?.Name ?? "null"} ({location.GetLineSpan().StartLinePosition})"); - } - - throw new("Compilation failed!"); - } - - if (debugBuild) - { - pdb.Seek(0, SeekOrigin.Begin); - symbols = pdb.ToArray(); - } - - ms.Seek(0, SeekOrigin.Begin); - return ms.ToArray(); - } - } - - private class Source - { - public Source(Stream s, string name, bool includeText) - { - Name = name; - var source = SourceText.From(s, canBeEmbedded: includeText); - if (includeText) - { - Text = EmbeddedText.FromSource(name, source); - Tree = CSharpSyntaxTree.ParseText(source, new(LanguageVersion.Latest), name); - } - else - { - Tree = CSharpSyntaxTree.ParseText(source, new(LanguageVersion.Latest)); - } - } - - public string Name { get; } - public SyntaxTree Tree { get; } - public EmbeddedText Text { get; } - } -} \ No newline at end of file diff --git a/PluginLoader/Compiler/RoslynReferences.cs b/PluginLoader/Compiler/RoslynReferences.cs deleted file mode 100644 index b64515d..0000000 --- a/PluginLoader/Compiler/RoslynReferences.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using HarmonyLib; -using Microsoft.CodeAnalysis; - -namespace PluginLoader.Compiler; - -public static class RoslynReferences -{ - private static readonly Dictionary allReferences = new(); - private static readonly HashSet referenceBlacklist = new(new[] { "System.ValueTuple" }); - - public static void GenerateAssemblyList() - { - if (allReferences.Count > 0) - return; - - var harmonyInfo = typeof(Harmony).Assembly.GetName(); - - var loadedAssemblies = new Stack(AppDomain.CurrentDomain.GetAssemblies().Where(IsValidReference)); - - var sb = new StringBuilder(); - - sb.AppendLine(); - var line = "==================================="; - sb.AppendLine(line); - sb.AppendLine("Assembly References"); - sb.AppendLine(line); - - try - { - foreach (var a in loadedAssemblies) - { - // Prevent other Harmony versions from being loaded - var name = a.GetName(); - if (name.Name == harmonyInfo.Name && name.Version != harmonyInfo.Version) - { - LogFile.Log.Debug( - $"WARNING: Multiple Harmony assemblies are loaded. Plugin Loader is using {harmonyInfo} but found {name}"); - continue; - } - - AddAssemblyReference(a); - sb.AppendLine(a.FullName); - } - - foreach(var a in GetOtherReferences()) - { - AddAssemblyReference(a); - sb.AppendLine(a.FullName); - } - - sb.AppendLine(line); - while (loadedAssemblies.Count > 0) - { - var a = loadedAssemblies.Pop(); - - foreach (var name in a.GetReferencedAssemblies()) - { - // Prevent other Harmony versions from being loaded - if (name.Name == harmonyInfo.Name && name.Version != harmonyInfo.Version) - { - LogFile.Log.Debug( - $"WARNING: Multiple Harmony assemblies are loaded. Plugin Loader is using {harmonyInfo} but found {name}"); - continue; - } - - if (!ContainsReference(name) && TryLoadAssembly(name, out var aRef) && IsValidReference(aRef)) - { - AddAssemblyReference(aRef); - sb.AppendLine(name.FullName); - loadedAssemblies.Push(aRef); - } - } - } - - sb.AppendLine(line); - } - catch (Exception e) - { - sb.Append("Error: ").Append(e).AppendLine(); - } - - LogFile.Log.Debug(sb.ToString(), false); - } - - /// - /// This method is used to load references that otherwise would not exist or be optimized out - /// - private static IEnumerable GetOtherReferences() - { - yield return typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly; - } - - private static bool ContainsReference(AssemblyName name) - { - return allReferences.ContainsKey(name.Name); - } - - private static bool TryLoadAssembly(AssemblyName name, out Assembly aRef) - { - try - { - aRef = AssemblyLoadContext.GetLoadContext(typeof(RoslynReferences).Assembly)!.LoadFromAssemblyName(name); - return true; - } - catch (IOException) - { - aRef = null; - return false; - } - } - - private static void AddAssemblyReference(Assembly a) - { - var name = a.GetName().Name; - if (!allReferences.ContainsKey(name)) - allReferences.Add(name, MetadataReference.CreateFromFile(a.Location)); - } - - public static IEnumerable EnumerateAllReferences() - { - return allReferences.Values; - } - - private static bool IsValidReference(Assembly a) - { - return !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location) && !referenceBlacklist.Contains(a.GetName().Name); - } - - public static void LoadReference(string name) - { - try - { - var aName = new AssemblyName(name); - if (!allReferences.ContainsKey(aName.Name)) - { - var a = Assembly.Load(aName); - LogFile.Log.Debug("Reference added at runtime: " + a.FullName); - MetadataReference aRef = MetadataReference.CreateFromFile(a.Location); - allReferences[a.GetName().Name] = aRef; - } - } - catch (IOException) - { - LogFile.Log.Debug("WARNING: Unable to find the assembly '" + name + "'!"); - } - } -} \ No newline at end of file diff --git a/PluginLoader/Data/GitHubPlugin.cs b/PluginLoader/Data/GitHubPlugin.cs deleted file mode 100644 index e36f040..0000000 --- a/PluginLoader/Data/GitHubPlugin.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.IO.Compression; -using System.Reflection; -using System.Text; -using System.Xml.Serialization; -using PluginLoader.Compiler; -using PluginLoader.Network; -using ProtoBuf; -using Sandbox.Graphics.GUI; - -namespace PluginLoader.Data; - -[ProtoContract] -public class GitHubPlugin : PluginData -{ - private const string pluginFile = "plugin.dll"; - private const string commitHashFile = "commit.sha1"; - private string cacheDir, assemblyName; - - public GitHubPlugin() - { - Status = PluginStatus.None; - } - - public override string Source => "GitHub"; - - [ProtoMember(1)] public string Commit { get; set; } - - [ProtoMember(2)] - [XmlArray] - [XmlArrayItem("Directory")] - public string[] SourceDirectories { get; set; } - - public void Init(string mainDirectory) - { - var nameArgs = Id.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); - if (nameArgs.Length != 2) - throw new("Invalid GitHub name: " + Id); - - if (SourceDirectories != null) - for (var i = SourceDirectories.Length - 1; i >= 0; i--) - { - var path = SourceDirectories[i].Replace('\\', '/').TrimStart('/'); - - if (path.Length == 0) - { - SourceDirectories.RemoveAtFast(i); - continue; - } - - if (path[path.Length - 1] != '/') - path += '/'; - - SourceDirectories[i] = path; - } - - assemblyName = MakeSafeString(nameArgs[1]); - cacheDir = Path.Combine(mainDirectory, "GitHub", nameArgs[0], nameArgs[1]); - } - - private string MakeSafeString(string s) - { - var sb = new StringBuilder(); - foreach (var ch in s) - if (char.IsLetterOrDigit(ch)) - sb.Append(ch); - else - sb.Append('_'); - return sb.ToString(); - } - - public override Assembly? GetAssembly() - { - if (!Directory.Exists(cacheDir)) - Directory.CreateDirectory(cacheDir); - - Assembly? a; - - var dllFile = Path.Combine(cacheDir, pluginFile); - var commitFile = Path.Combine(cacheDir, commitHashFile); - if (!File.Exists(dllFile) || !File.Exists(commitFile) || File.ReadAllText(commitFile) != Commit) - { - var lbl = Main.Instance.Splash; - lbl.SetText($"Downloading '{FriendlyName}'"); - var data = CompileFromSource(x => lbl.SetBarValue(x)); - File.WriteAllBytes(dllFile, data); - File.WriteAllText(commitFile, Commit); - Status = PluginStatus.Updated; - lbl.SetText($"Compiled '{FriendlyName}'"); - a = Assembly.Load(data); - } - else - { - try - { - a = Assembly.LoadFile(dllFile); - } - catch - { - LogFile.Log.Debug($"Error loading {dllFile}, deleting file"); - File.Delete(dllFile); - throw; - } - } - - Version = a.GetName().Version; - return a; - } - - - public byte[] CompileFromSource(Action callback = null) - { - var compiler = new RoslynCompiler(); - using (var s = GitHub.DownloadRepo(Id, Commit, out var fileName)) - using (var zip = new ZipArchive(s)) - { - callback?.Invoke(0); - for (var i = 0; i < zip.Entries.Count; i++) - { - var entry = zip.Entries[i]; - CompileFromSource(compiler, entry); - callback?.Invoke(i / (float)zip.Entries.Count); - } - - callback?.Invoke(1); - } - - return compiler.Compile(assemblyName + '_' + Path.GetRandomFileName(), out _); - } - - private void CompileFromSource(RoslynCompiler compiler, ZipArchiveEntry entry) - { - if (AllowedZipPath(entry.FullName)) - using (var entryStream = entry.Open()) - { - compiler.Load(entryStream, entry.FullName); - } - } - - private bool AllowedZipPath(string path) - { - if (!path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) - return false; - - if (SourceDirectories == null || SourceDirectories.Length == 0) - return true; - - path = RemoveRoot(path); // Make the base of the path the root of the repository - - foreach (var dir in SourceDirectories) - if (path.StartsWith(dir, StringComparison.Ordinal)) - return true; - return false; - } - - private string RemoveRoot(string path) - { - path = path.Replace('\\', '/').TrimStart('/'); - var index = path.IndexOf('/'); - if (index >= 0 && index + 1 < path.Length) - return path.Substring(index + 1); - return path; - } - - public override void Show() - { - MyGuiSandbox.OpenUrl("https://github.com/" + Id, UrlOpenMode.SteamOrExternalWithConfirm); - } -} \ No newline at end of file diff --git a/PluginLoader/Data/ISteamItem.cs b/PluginLoader/Data/ISteamItem.cs deleted file mode 100644 index 6025d6b..0000000 --- a/PluginLoader/Data/ISteamItem.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace PluginLoader.Data; - -public interface ISteamItem -{ - string Id { get; } - ulong WorkshopId { get; } -} \ No newline at end of file diff --git a/PluginLoader/Data/LocalFolderPlugin.cs b/PluginLoader/Data/LocalFolderPlugin.cs deleted file mode 100644 index 3067312..0000000 --- a/PluginLoader/Data/LocalFolderPlugin.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using System.Text; -using System.Xml.Serialization; -using PluginLoader.Compiler; -using PluginLoader.GUI; -using Sandbox.Graphics.GUI; -using VRage; - -namespace PluginLoader.Data; - -public class LocalFolderPlugin : PluginData -{ - private const string XmlDataType = "Xml files (*.xml)|*.xml|All files (*.*)|*.*"; - private const int GitTimeout = 10000; - private string[] sourceDirectories; - - public LocalFolderPlugin(Config settings) - { - Id = settings.Folder; - FriendlyName = Path.GetFileName(Id); - Status = PluginStatus.None; - FolderSettings = settings; - DeserializeFile(settings.DataFile); - } - - private LocalFolderPlugin(string folder) - { - Id = folder; - Status = PluginStatus.None; - FolderSettings = new() - { - Folder = folder - }; - } - - public override string Source => MyTexts.GetString(MyCommonTexts.Local); - - public Config FolderSettings { get; } - - public override Assembly? GetAssembly() - { - if (Directory.Exists(Id)) - { - var compiler = new RoslynCompiler(FolderSettings.DebugBuild); - var hasFile = false; - var sb = new StringBuilder(); - sb.Append("Compiling files from ").Append(Id).Append(":").AppendLine(); - foreach (var file in GetProjectFiles(Id)) - using (var fileStream = File.OpenRead(file)) - { - hasFile = true; - var name = file.Substring(Id.Length + 1, file.Length - (Id.Length + 1)); - sb.Append(name).Append(", "); - compiler.Load(fileStream, file); - } - - if (hasFile) - { - sb.Length -= 2; - LogFile.Log.Debug(sb.ToString()); - } - else - { - throw new IOException("No files were found in the directory specified."); - } - - var data = compiler.Compile(FriendlyName + '_' + Path.GetRandomFileName(), out var symbols); - var a = Assembly.Load(data, symbols); - Version = a.GetName().Version; - return a; - } - - throw new DirectoryNotFoundException("Unable to find directory '" + Id + "'"); - } - - private IEnumerable GetProjectFiles(string folder) - { - string gitError = null; - try - { - var p = new Process(); - - // Redirect the output stream of the child process. - p.StartInfo.UseShellExecute = false; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.RedirectStandardError = true; - p.StartInfo.FileName = "git"; - p.StartInfo.Arguments = "ls-files --cached --others --exclude-standard"; - p.StartInfo.WorkingDirectory = folder; - p.Start(); - - // Do not wait for the child process to exit before - // reading to the end of its redirected stream. - // Read the output stream first and then wait. - var gitOutput = p.StandardOutput.ReadToEnd(); - gitError = p.StandardError.ReadToEnd(); - if (!p.WaitForExit(GitTimeout)) - { - p.Kill(); - throw new TimeoutException("Git operation timed out."); - } - - if (p.ExitCode == 0) - { - var files = gitOutput.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); - return files.Where(x => x.EndsWith(".cs") && IsValidProjectFile(x)) - .Select(x => Path.Combine(folder, x.Trim().Replace('/', Path.DirectorySeparatorChar))) - .Where(x => File.Exists(x)); - } - - LogFile.Log.Error("An error occurred while checking git for project files. Git output: {GitOutput}", gitError); - } - catch (Exception e) - { - LogFile.Log.Error(e, "An error occurred while checking git for project files. Git output: {GitOutput}", gitError); - } - - - var sep = Path.DirectorySeparatorChar; - return Directory.EnumerateFiles(folder, "*.cs", SearchOption.AllDirectories) - .Where(x => !x.Contains(sep + "bin" + sep) && !x.Contains(sep + "obj" + sep) && - IsValidProjectFile(x)); - } - - private bool IsValidProjectFile(string file) - { - if (sourceDirectories == null || sourceDirectories.Length == 0) - return true; - file = file.Replace('\\', '/'); - foreach (var dir in sourceDirectories) - if (file.StartsWith(dir)) - return true; - return false; - } - - public override string ToString() - { - return Id; - } - - public override void Show() - { - var folder = Path.GetFullPath(Id); - if (Directory.Exists(folder)) - Process.Start("explorer.exe", $"\"{folder}\""); - } - - public override bool OpenContextMenu(MyGuiControlContextMenu menu) - { - menu.Clear(); - menu.AddItem(new("Remove")); - menu.AddItem(new("Load data file")); - if (FolderSettings.DebugBuild) - menu.AddItem(new("Switch to release build")); - else - menu.AddItem(new("Switch to debug build")); - return true; - } - - public override void ContextMenuClicked(MyGuiScreenPluginConfig screen, MyGuiControlContextMenu.EventArgs args) - { - switch (args.ItemIndex) - { - case 0: - Main.Instance.Config.PluginFolders.Remove(Id); - screen.RemovePlugin(this); - screen.RequireRestart(); - break; - case 1: - LoaderTools.OpenFileDialog("Open an xml data file", Path.GetDirectoryName(FolderSettings.DataFile), - XmlDataType, file => DeserializeFile(file, screen)); - break; - case 2: - FolderSettings.DebugBuild = !FolderSettings.DebugBuild; - screen.RequireRestart(); - break; - } - } - - // Deserializes a file and refreshes the plugin screen - private void DeserializeFile(string file, MyGuiScreenPluginConfig screen = null) - { - if (!File.Exists(file)) - return; - - try - { - var xml = new XmlSerializer(typeof(PluginData)); - - using (var reader = File.OpenText(file)) - { - var resultObj = xml.Deserialize(reader); - if (resultObj.GetType() != typeof(GitHubPlugin)) throw new("Xml file is not of type GitHubPlugin!"); - - var github = (GitHubPlugin)resultObj; - github.Init(LoaderTools.PluginsDir); - FriendlyName = github.FriendlyName; - Tooltip = github.Tooltip; - Author = github.Author; - Description = github.Description; - sourceDirectories = github.SourceDirectories; - FolderSettings.DataFile = file; - if (screen != null && screen.Visible && screen.IsOpened) - screen.RefreshSidePanel(); - } - } - catch (Exception e) - { - LogFile.Log.Error(e, "Error while reading the xml file"); - } - } - - public static void CreateNew(Action onComplete) - { - LoaderTools.OpenFolderDialog("Open the root of your project", LoaderTools.PluginsDir, folder => - { - if (Main.Instance.List.Contains(folder)) - { - MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, - messageText: new("That folder already exists in the list!")); - return; - } - - var plugin = new LocalFolderPlugin(folder); - LoaderTools.OpenFileDialog("Open the xml data file", folder, XmlDataType, file => - { - plugin.DeserializeFile(file); - onComplete(plugin); - }); - }); - } - - - public class Config - { - public Config() - { - } - - public Config(string folder, string dataFile) - { - Folder = folder; - DataFile = dataFile; - } - - public string Folder { get; set; } - public string DataFile { get; set; } - public bool DebugBuild { get; set; } = true; - public bool Valid => Directory.Exists(Folder) && File.Exists(DataFile); - } -} \ No newline at end of file diff --git a/PluginLoader/Data/LocalPlugin.cs b/PluginLoader/Data/LocalPlugin.cs deleted file mode 100644 index 47a1b51..0000000 --- a/PluginLoader/Data/LocalPlugin.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using Sandbox.Graphics.GUI; -using VRage; - -namespace PluginLoader.Data; - -public class LocalPlugin : PluginData -{ - private LocalPlugin() - { - } - - public LocalPlugin(string dll) - { - Id = dll; - Status = PluginStatus.None; - } - - public override string Source => MyTexts.GetString(MyCommonTexts.Local); - - public override string Id - { - get => base.Id; - set - { - base.Id = value; - if (File.Exists(value)) - FriendlyName = Path.GetFileName(value); - } - } - - public override Assembly? GetAssembly() - { - if (!File.Exists(Id)) return null; - //prevent random unloading if being used by another process - int maxRetries = 10; - while (maxRetries > 0) - { - try - { - AppDomain.CurrentDomain.AssemblyResolve += LoadFromSameFolder; - var a = Assembly.LoadFile(Id); - Version = a?.GetName()?.Version ?? Version; - return a; - } - catch (IOException) - { - LogFile.Log.Debug($"Waiting to load {Id} because it's being used by another process"); - Thread.Sleep(250); - maxRetries--; - } - } - - return null; - } - - public override string ToString() - { - return Id; - } - - public override void Show() - { - var file = Path.GetFullPath(Id); - if (File.Exists(file)) - Process.Start("explorer.exe", $"/select, \"{file}\""); - } - - private Assembly? LoadFromSameFolder(object sender, ResolveEventArgs args) - { - if (args.RequestingAssembly?.IsDynamic ?? false) - return null; - - if (args.Name.Contains("0Harmony") || args.Name.Contains("SEPluginManager")) - return null; - - var location = args.RequestingAssembly?.Location; - if (string.IsNullOrWhiteSpace(location) || !Path.GetFullPath(location) - .StartsWith(Path.GetDirectoryName(Id)!, - StringComparison.OrdinalIgnoreCase)) - return null; - - var folderPath = Path.GetDirectoryName(Id); - if (string.IsNullOrEmpty(folderPath)) return null; - var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); - if (!File.Exists(assemblyPath)) - return null; - - var assembly = Assembly.LoadFile(assemblyPath); - LogFile.Log.Debug("Resolving " + assembly.GetName().Name + " for " + args.RequestingAssembly?.FullName, false); - - var main = Main.Instance; - if (!main.Config.IsEnabled(assemblyPath)) - main.List.Remove(assemblyPath); - - return assembly; - } - - public override void GetDescriptionText(MyGuiControlMultilineText textbox) - { - textbox.Visible = false; - textbox.Clear(); - } -} \ No newline at end of file diff --git a/PluginLoader/Data/ModPlugin.cs b/PluginLoader/Data/ModPlugin.cs deleted file mode 100644 index b3bc6e0..0000000 --- a/PluginLoader/Data/ModPlugin.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Reflection; -using System.Xml.Serialization; -using ProtoBuf; -using Sandbox.Graphics.GUI; -using VRage.Game; -using VRage.GameServices; - -namespace PluginLoader.Data; - -[ProtoContract] -public class ModPlugin : PluginData, ISteamItem -{ - private bool isLegacy; - - private string modLocation; - - public override string Source => "Mod"; - - [ProtoMember(1)] - [XmlArray] - [XmlArrayItem("Id")] - public ulong[] DependencyIds { get; set; } = new ulong[0]; - - [XmlIgnore] public ModPlugin[] Dependencies { get; set; } = new ModPlugin[0]; - - public string ModLocation - { - get - { - if (modLocation != null) - return modLocation; - modLocation = Path.Combine(Path.GetFullPath(@"..\..\..\workshop\content\244850\"), WorkshopId.ToString()); - if (Directory.Exists(modLocation) && !Directory.Exists(Path.Combine(modLocation, "Data"))) - { - var legacyFile = Directory.EnumerateFiles(modLocation, "*_legacy.bin").FirstOrDefault(); - if (legacyFile != null) - { - isLegacy = true; - modLocation = legacyFile; - } - } - - return modLocation; - } - } - - public bool Exists => Directory.Exists(ModLocation) || (isLegacy && File.Exists(modLocation)); - - [XmlIgnore] public ulong WorkshopId { get; private set; } - - public override string Id - { - get => base.Id; - set - { - base.Id = value; - WorkshopId = ulong.Parse(Id); - } - } - - public override Assembly? GetAssembly() - { - return null; - } - - public override bool TryLoadAssembly(out Assembly? a) - { - a = null; - return false; - } - - public override void Show() - { - MyGuiSandbox.OpenUrl("https://steamcommunity.com/workshop/filedetails/?id=" + Id, - UrlOpenMode.SteamOrExternalWithConfirm); - } - - public MyObjectBuilder_Checkpoint.ModItem GetModItem() - { - var modItem = new MyObjectBuilder_Checkpoint.ModItem(WorkshopId, "Steam"); - modItem.SetModData(new WorkshopItem(ModLocation)); - return modItem; - } - - public MyModContext GetModContext() - { - var modContext = new MyModContext(); - modContext.Init(GetModItem()); - modContext.Init(WorkshopId.ToString(), null, ModLocation); - return modContext; - } - - private class WorkshopItem : MyWorkshopItem - { - public WorkshopItem(string folder) - { - Folder = folder; - } - } -} \ No newline at end of file diff --git a/PluginLoader/Data/PluginData.cs b/PluginLoader/Data/PluginData.cs deleted file mode 100644 index bf87c40..0000000 --- a/PluginLoader/Data/PluginData.cs +++ /dev/null @@ -1,220 +0,0 @@ -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.Log.Debug("Failed to load " + ToString()); - Error(); - return false; - } - - return true; - } - catch (Exception e) - { - var name = ToString(); - LogFile.Log.Debug($"Failed to load {name} because of an error: " + e); - if (e is MissingMemberException) - LogFile.Log.Debug($"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.Log.Debug("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) - { - } -} \ No newline at end of file diff --git a/PluginLoader/Data/PluginStatus.cs b/PluginLoader/Data/PluginStatus.cs deleted file mode 100644 index bed0418..0000000 --- a/PluginLoader/Data/PluginStatus.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PluginLoader.Data; - -public enum PluginStatus -{ - None, - PendingUpdate, - Updated, - Error, - Blocked -} \ No newline at end of file diff --git a/PluginLoader/Data/SEPMPlugin.cs b/PluginLoader/Data/SEPMPlugin.cs deleted file mode 100644 index 1d9b39e..0000000 --- a/PluginLoader/Data/SEPMPlugin.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.IO.Compression; -using ProtoBuf; - -namespace PluginLoader.Data; - -[ProtoContract] -public class SEPMPlugin : SteamPlugin -{ - private const string NameFile = "name.txt"; - - private string dataFolder; - - protected SEPMPlugin() - { - } - - public override string Source => "SEPM"; - protected override string HashFile => "sepm-plugin.txt"; - - protected override void CheckForUpdates() - { - dataFolder = Path.Combine(root, "sepm-plugin"); - - if (Directory.Exists(dataFolder)) - base.CheckForUpdates(); - else - Status = PluginStatus.PendingUpdate; - } - - protected override void ApplyUpdate() - { - if (Directory.Exists(dataFolder)) - Directory.Delete(dataFolder, true); - - ZipFile.ExtractToDirectory(sourceFile, dataFolder); - } - - protected override string GetAssemblyFile() - { - if (!Directory.Exists(dataFolder)) - return null; - return Directory.EnumerateFiles(dataFolder, "*.dll") - .Where(s => !s.Equals("0Harmony.dll", StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); - } -} \ No newline at end of file diff --git a/PluginLoader/Data/SteamPlugin.cs b/PluginLoader/Data/SteamPlugin.cs deleted file mode 100644 index e3572fd..0000000 --- a/PluginLoader/Data/SteamPlugin.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Reflection; -using System.Xml.Serialization; -using ProtoBuf; -using Sandbox.Graphics.GUI; - -namespace PluginLoader.Data; - -[ProtoContract] -[ProtoInclude(101, typeof(SEPMPlugin))] -[ProtoInclude(102, typeof(WorkshopPlugin))] -public abstract class SteamPlugin : PluginData, ISteamItem -{ - protected string root, sourceFile, hashFile; - - [XmlArray] [ProtoMember(1)] public string[] AllowedHashes { get; set; } - - protected abstract string HashFile { get; } - - [XmlIgnore] public ulong WorkshopId { get; private set; } - - public override string Id - { - get => base.Id; - set - { - base.Id = value; - WorkshopId = ulong.Parse(Id); - } - } - - public void Init(string sourceFile) - { - Status = PluginStatus.None; - this.sourceFile = sourceFile; - root = Path.GetDirectoryName(sourceFile); - hashFile = Path.Combine(root, HashFile); - - CheckForUpdates(); - } - - protected virtual void CheckForUpdates() - { - if (File.Exists(hashFile)) - { - var oldHash = File.ReadAllText(hashFile); - var newHash = LoaderTools.GetHash1(sourceFile); - if (oldHash != newHash) - Status = PluginStatus.PendingUpdate; - } - else - { - Status = PluginStatus.PendingUpdate; - } - } - - public override Assembly? GetAssembly() - { - if (Status == PluginStatus.PendingUpdate) - { - LogFile.Log.Debug("Updating " + this); - ApplyUpdate(); - if (Status == PluginStatus.PendingUpdate) - { - File.WriteAllText(hashFile, LoaderTools.GetHash1(sourceFile)); - Status = PluginStatus.Updated; - } - else - { - return null; - } - } - - var dll = GetAssemblyFile(); - if (dll == null || !File.Exists(dll)) - return null; - if (!VerifyAllowed(dll)) - return null; - var a = Assembly.LoadFile(dll); - Version = a.GetName().Version; - return a; - } - - protected abstract void ApplyUpdate(); - protected abstract string GetAssemblyFile(); - - public override void Show() - { - MyGuiSandbox.OpenUrl("https://steamcommunity.com/workshop/filedetails/?id=" + Id, - UrlOpenMode.SteamOrExternalWithConfirm); - } - - private bool VerifyAllowed(string dll) - { - if (AllowedHashes == null || AllowedHashes.Length == 0) - return true; - - var hash = LoaderTools.GetHash256(dll); - foreach (var s in AllowedHashes) - if (s == hash) - return true; - - ErrorSecurity(hash); - return false; - } -} \ No newline at end of file diff --git a/PluginLoader/Data/WorkshopPlugin.cs b/PluginLoader/Data/WorkshopPlugin.cs deleted file mode 100644 index 5bd05b0..0000000 --- a/PluginLoader/Data/WorkshopPlugin.cs +++ /dev/null @@ -1,43 +0,0 @@ -using ProtoBuf; -using VRage; - -namespace PluginLoader.Data; - -[ProtoContract] -public class WorkshopPlugin : SteamPlugin -{ - private string assembly; - - protected WorkshopPlugin() - { - } - - public override string Source => MyTexts.GetString(MyCommonTexts.Workshop); - protected override string HashFile => "hash.txt"; - - protected override void CheckForUpdates() - { - assembly = Path.Combine(root, Path.GetFileNameWithoutExtension(sourceFile) + ".dll"); - - var found = false; - foreach (var dll in Directory.EnumerateFiles(root, "*.dll")) - if (dll == assembly) - found = true; - else - File.Delete(dll); - if (!found) - Status = PluginStatus.PendingUpdate; - else - base.CheckForUpdates(); - } - - protected override void ApplyUpdate() - { - File.Copy(sourceFile, assembly, true); - } - - protected override string GetAssemblyFile() - { - return assembly; - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/ConfirmationDialog.cs b/PluginLoader/GUI/ConfirmationDialog.cs deleted file mode 100644 index a4e700c..0000000 --- a/PluginLoader/GUI/ConfirmationDialog.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Text; -using Sandbox; -using Sandbox.Graphics.GUI; -using VRage.Utils; -using VRageMath; - -namespace PluginLoader.GUI; - -public static class ConfirmationDialog -{ - public static MyGuiScreenMessageBox CreateMessageBox( - MyMessageBoxStyleEnum styleEnum = MyMessageBoxStyleEnum.Error, - MyMessageBoxButtonsType buttonType = MyMessageBoxButtonsType.OK, - StringBuilder messageText = null, - StringBuilder messageCaption = null, - MyStringId? okButtonText = null, - MyStringId? cancelButtonText = null, - MyStringId? yesButtonText = null, - MyStringId? noButtonText = null, - Action callback = null, - int timeoutInMiliseconds = 0, - MyGuiScreenMessageBox.ResultEnum focusedResult = MyGuiScreenMessageBox.ResultEnum.YES, - bool canHideOthers = true, - Vector2? size = null, - bool useOpacity = true, - Vector2? position = null, - bool focusable = true, - bool canBeHidden = false, - Action onClosing = null) - { - var num1 = (int)styleEnum; - var num2 = (int)buttonType; - var messageText1 = messageText; - var messageCaption1 = messageCaption; - var nullable = okButtonText; - var okButtonText1 = nullable ?? MyCommonTexts.Ok; - nullable = cancelButtonText; - var cancelButtonText1 = nullable ?? MyCommonTexts.Cancel; - nullable = yesButtonText; - var yesButtonText1 = nullable ?? MyCommonTexts.Yes; - nullable = noButtonText; - var noButtonText1 = nullable ?? MyCommonTexts.No; - var callback1 = callback; - var timeoutInMiliseconds1 = timeoutInMiliseconds; - var num3 = (int)focusedResult; - var num4 = canHideOthers ? 1 : 0; - var size1 = size; - var num5 = useOpacity ? MySandboxGame.Config.UIBkOpacity : 1.0; - var num6 = useOpacity ? MySandboxGame.Config.UIOpacity : 1.0; - var position1 = position; - var num7 = focusable ? 1 : 0; - var num8 = canBeHidden ? 1 : 0; - var onClosing1 = onClosing; - var dlg = new MyGuiScreenMessageBox((MyMessageBoxStyleEnum)num1, (MyMessageBoxButtonsType)num2, messageText1, - messageCaption1, okButtonText1, cancelButtonText1, yesButtonText1, - noButtonText1, callback1, timeoutInMiliseconds1, - (MyGuiScreenMessageBox.ResultEnum)num3, num4 != 0, size1, (float)num5, - (float)num6, position1, num7 != 0, num8 != 0, onClosing1); - - if (dlg.Controls.GetControlByName("MyGuiControlMultilineText") is MyGuiControlMultilineText text) - text.TextAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_CENTER; - - return dlg; - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/GuiControls/RatingControl.cs b/PluginLoader/GUI/GuiControls/RatingControl.cs deleted file mode 100644 index 03daea3..0000000 --- a/PluginLoader/GUI/GuiControls/RatingControl.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Sandbox.Graphics; -using Sandbox.Graphics.GUI; -using VRage.Utils; -using VRageMath; - -namespace PluginLoader.GUI.GuiControls; - -// From Sandbox.Game.Screens.Helpers.MyGuiControlRating -internal class RatingControl : MyGuiControlBase -{ - private readonly float m_space = 8f; - - public string EmptyTexture = "Textures\\GUI\\Icons\\Rating\\NoStar.png"; - - public string FilledTexture = "Textures\\GUI\\Icons\\Rating\\FullStar.png"; - - public string HalfFilledTexture = "Textures\\GUI\\Icons\\Rating\\HalfStar.png"; - - private int m_maxValue; - private readonly Vector2 m_textureSize = new(32f); - - public RatingControl(int value = 0, int maxValue = 10) - { - Value = value; - m_maxValue = maxValue; - BackgroundTexture = null; - ColorMask = Vector4.One; - } - - public int MaxValue - { - get => m_maxValue; - set - { - m_maxValue = value; - RecalculateSize(); - } - } - - public int Value { get; set; } - - private void RecalculateSize() - { - var vector = MyGuiManager.GetHudNormalizedSizeFromPixelSize(m_textureSize) * new Vector2(0.75f, 1f); - var hudNormalizedSizeFromPixelSize = MyGuiManager.GetHudNormalizedSizeFromPixelSize(new(m_space * 0.75f, 0f)); - Size = new((vector.X + hudNormalizedSizeFromPixelSize.X) * m_maxValue, vector.Y); - } - - public float GetWidth() - { - var num = MyGuiManager.GetHudNormalizedSizeFromPixelSize(m_textureSize).X * 0.75f; - var num2 = MyGuiManager.GetHudNormalizedSizeFromPixelSize(new(m_space * 0.75f, 0f)).X; - return (num + num2) * MaxValue / 2f; - } - - public override void Draw(float transitionAlpha, float backgroundTransitionAlpha) - { - base.Draw(transitionAlpha, backgroundTransitionAlpha); - if (MaxValue <= 0) return; - var normalizedSize = MyGuiManager.GetHudNormalizedSizeFromPixelSize(m_textureSize) * new Vector2(0.75f, 1f); - var hudNormalizedSizeFromPixelSize = MyGuiManager.GetHudNormalizedSizeFromPixelSize(new(m_space * 0.75f, 0f)); - var vector = GetPositionAbsoluteTopLeft() + new Vector2(0f, (Size.Y - normalizedSize.Y) / 2f); - var vector2 = new Vector2((normalizedSize.X + hudNormalizedSizeFromPixelSize.X) * 0.5f, normalizedSize.Y); - for (var i = 0; i < MaxValue; i += 2) - { - var normalizedCoord = vector + new Vector2(vector2.X * i, 0f); - if (i == Value - 1) - MyGuiManager.DrawSpriteBatch(HalfFilledTexture, normalizedCoord, normalizedSize, - ApplyColorMaskModifiers(ColorMask, Enabled, transitionAlpha), - MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, false, false); - else if (i < Value) - MyGuiManager.DrawSpriteBatch(FilledTexture, normalizedCoord, normalizedSize, - ApplyColorMaskModifiers(ColorMask, Enabled, transitionAlpha), - MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, false, false); - else - MyGuiManager.DrawSpriteBatch(EmptyTexture, normalizedCoord, normalizedSize, - ApplyColorMaskModifiers(ColorMask, Enabled, transitionAlpha), - MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, false, false); - } - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/ItemView.cs b/PluginLoader/GUI/ItemView.cs deleted file mode 100644 index dac4159..0000000 --- a/PluginLoader/GUI/ItemView.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PluginLoader.GUI; - -public class ItemView -{ - public readonly string[] Labels; - public readonly object[] Values; - - public ItemView(string[] labels, object[] values) - { - Labels = labels; - Values = values; - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/MyGuiScreenPluginConfig.cs b/PluginLoader/GUI/MyGuiScreenPluginConfig.cs deleted file mode 100644 index 00b3771..0000000 --- a/PluginLoader/GUI/MyGuiScreenPluginConfig.cs +++ /dev/null @@ -1,838 +0,0 @@ -using System.Text; -using PluginLoader.Data; -using PluginLoader.Patch; -using PluginLoader.Stats; -using PluginLoader.Stats.Model; -using Sandbox; -using Sandbox.Game.Gui; -using Sandbox.Game.Multiplayer; -using Sandbox.Game.Screens.Helpers; -using Sandbox.Game.World; -using Sandbox.Graphics.GUI; -using VRage; -using VRage.Audio; -using VRage.Game; -using VRage.Input; -using VRage.Utils; -using VRageMath; -using static Sandbox.Graphics.GUI.MyGuiScreenMessageBox; - -namespace PluginLoader.GUI; - -public class MyGuiScreenPluginConfig : MyGuiScreenBase -{ - private const float BarWidth = 0.85f; - private const float Spacing = 0.0175f; - - private static bool allItemsVisible = true; - private static bool _hideLocalPlugins = true; - - public readonly Dictionary AfterRebootEnableFlags = new(); - - private readonly Dictionary pluginCheckboxes = new(); - private readonly PluginDetailsPanel pluginDetails; - private MyGuiControlButton buttonMore; - private MyGuiControlContextMenu contextMenu; - private bool forceRestart; - private MyGuiControlContextMenu pluginContextMenu; - private MyGuiControlLabel pluginCountLabel; - public PluginStats PluginStats; - - private MyGuiControlTable pluginTable; - private string[] tableFilter; - - /// - /// The plugins screen, the constructor itself sets up the menu properties. - /// - private MyGuiScreenPluginConfig() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR, - new Vector2(1f, 0.97f), false, null, MySandboxGame.Config.UIBkOpacity, - MySandboxGame.Config.UIOpacity) - { - EnabledBackgroundFade = true; - m_closeOnEsc = true; - m_drawEvenWithoutFocus = true; - CanHideOthers = true; - CanBeHidden = true; - CloseButtonEnabled = true; - - foreach (var plugin in Main.Instance.List) - AfterRebootEnableFlags[plugin.Id] = plugin.Enabled; - - pluginDetails = new(this); - } - - private static PluginConfig Config => Main.Instance.Config; - - private PluginData SelectedPlugin - { - get => pluginDetails.Plugin; - set => pluginDetails.Plugin = value; - } - - private bool RequiresRestart => forceRestart || - Main.Instance.List.Any( - plugin => plugin.Enabled != AfterRebootEnableFlags[plugin.Id]); - - public static void OpenMenu() - { - if (Main.Instance.List.HasError) - MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(buttonType: MyMessageBoxButtonsType.OK, - messageText: new( - "An error occurred while downloading the plugin list.\nPlease send your game log to the developers of Plugin Loader."), - messageCaption: MyTexts.Get( - MyCommonTexts.MessageBoxCaptionError), - callback: x => - MyGuiSandbox.AddScreen( - new MyGuiScreenPluginConfig()))); - else - MyGuiSandbox.AddScreen(new MyGuiScreenPluginConfig()); - } - - public override string GetFriendlyName() - { - return "MyGuiScreenPluginConfig"; - } - - public override void LoadContent() - { - base.LoadContent(); - RecreateControls(true); - PlayerConsent.OnConsentChanged += OnConsentChanged; - } - - public override void HandleUnhandledInput(bool receivedFocusInThisUpdate) - { - var input = MyInput.Static; - if (input.IsNewKeyPressed(MyKeys.F5) && input.IsAnyAltKeyPressed() && input.IsAnyCtrlKeyPressed()) - Patch_IngameRestart.ShowRestartMenu(); - } - - public override void UnloadContent() - { - PlayerConsent.OnConsentChanged -= OnConsentChanged; - pluginDetails.OnPluginToggled -= EnablePlugin; - base.UnloadContent(); - } - - private void OnConsentChanged() - { - DownloadStats(); - } - - private void DownloadStats() - { - LogFile.Log.Debug("Downloading user statistics"); - Task.Run(() => - { - StatsClient.DownloadStats(); - pluginDetails?.LoadPluginData(); - }); - } - - /// - /// Initializes the controls of the menu on the left side of the menu. - /// - public override void RecreateControls(bool constructor) - { - base.RecreateControls(constructor); - - var title = AddCaption("Plugins List"); - - // Sets the origin relative to the center of the caption on the X axis and to the bottom the caption on the y axis. - var origin = title.Position += new Vector2(0f, title.Size.Y / 2); - - origin.Y += Spacing; - - // Adds a bar right below the caption. - var titleBar = new MyGuiControlSeparatorList(); - titleBar.AddHorizontal(new(origin.X - BarWidth / 2, origin.Y), BarWidth); - Controls.Add(titleBar); - - origin.Y += Spacing; - - // Change the position of this to move the entire middle section of the menu, the menu bars, menu title, and bottom buttons won't move - // Adds a search bar right below the bar on the left side of the menu. - var searchBox = new MyGuiControlSearchBox(new Vector2(origin.X - BarWidth / 2, origin.Y), - originAlign: MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP); - - // Changing the search box X size will change the plugin list length. - searchBox.Size = new(0.4f, searchBox.Size.Y); - searchBox.OnTextChanged += SearchBox_TextChanged; - Controls.Add(searchBox); - -#region Visibility Button - - // Adds a button to show only enabled plugins. Located right of the search bar. - var buttonVisibility = new MyGuiControlButton( - new Vector2(origin.X - BarWidth / 2 + searchBox.Size.X, origin.Y) + new Vector2(0.003f, 0.002f), - MyGuiControlButtonStyleEnum.Rectangular, new Vector2(searchBox.Size.Y * 2.52929769833f), - onButtonClick: OnVisibilityClick, originAlign: MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, - toolTip: "Show only enabled plugins.", buttonScale: 0.5f); - - if (allItemsVisible || Config.Count == 0) - { - allItemsVisible = true; - buttonVisibility.Icon = IconHide; - } - else - { - buttonVisibility.Icon = IconShow; - } - - Controls.Add(buttonVisibility); - -#endregion - - origin.Y += searchBox.Size.Y + Spacing; - -#region Plugin List - - // Adds the plugin list on the right of the menu below the search bar. - pluginTable = new() - { - Position = new(origin.X - BarWidth / 2, origin.Y), - Size = new(searchBox.Size.X + buttonVisibility.Size.X + 0.001f, - 0.6f), // The y value can be bigger than the visible rows count as the visibleRowsCount controls the height. - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, - ColumnsCount = 3, - VisibleRowsCount = 20 - }; - - pluginTable.SetCustomColumnWidths(new[] - { - 0.22f, - 0.6f, - 0.22f - }); - - pluginTable.SetColumnName(0, new("Source")); - pluginTable.SetColumnComparison(0, CellTextOrDataComparison); - pluginTable.SetColumnName(1, new("Name")); - pluginTable.SetColumnComparison(1, CellTextComparison); - pluginTable.SetColumnName(2, new("Enable")); - pluginTable.SetColumnComparison(2, CellTextComparison); - - // Default sorting - pluginTable.SortByColumn(2, MyGuiControlTable.SortStateEnum.Ascending); - - // Selecting list items load their details in OnItemSelected - pluginTable.ItemSelected += OnItemSelected; - Controls.Add(pluginTable); - - // Double clicking list items toggles the enable flag - pluginTable.ItemDoubleClicked += OnItemDoubleClicked; - -#endregion - - origin.Y += Spacing + pluginTable.Size.Y; - - // Adds the bar at the bottom between just above the buttons. - var bottomBar = new MyGuiControlSeparatorList(); - bottomBar.AddHorizontal(new(origin.X - BarWidth / 2, origin.Y), BarWidth); - Controls.Add(bottomBar); - - origin.Y += Spacing; - - // Adds buttons at bottom of menu - var buttonRestart = new MyGuiControlButton(origin, MyGuiControlButtonStyleEnum.Default, null, null, - MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - "Restart the game and apply changes.", new("Apply"), 0.8f, - MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - MyGuiControlHighlightType.WHEN_ACTIVE, OnRestartButtonClick); - var buttonClose = new MyGuiControlButton(origin, MyGuiControlButtonStyleEnum.Default, null, null, - MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - "Closes the dialog without saving changes to plugin selection", - new("Cancel"), 0.8f, - MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - MyGuiControlHighlightType.WHEN_ACTIVE, OnCancelButtonClick); - buttonMore = new(origin, MyGuiControlButtonStyleEnum.Tiny, null, null, - MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, "Advanced", new("..."), 0.8f, - MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - MyGuiControlHighlightType.WHEN_ACTIVE, OnMoreButtonClick); - - // FIXME: Use MyLayoutHorizontal instead - AlignRow(origin, 0.05f, buttonRestart, buttonClose); - Controls.Add(buttonRestart); - Controls.Add(buttonClose); - buttonMore.Position = buttonClose.Position + new Vector2(buttonClose.Size.X / 2 + 0.05f, 0); - Controls.Add(buttonMore); - - // Adds a place to show the total amount of plugins and to show the total amount of visible plugins. - pluginCountLabel = new(new Vector2(origin.X - BarWidth / 2, buttonRestart.Position.Y), - originAlign: MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP); - Controls.Add(pluginCountLabel); - - // Right side panel showing the details of the selected plugin - var rightSideOrigin = buttonVisibility.Position + - new Vector2(Spacing * 1.778f + buttonVisibility.Size.X / 2, - -(buttonVisibility.Size.Y / 2)); - pluginDetails.CreateControls(rightSideOrigin); - Controls.Add(pluginDetails); - pluginDetails.OnPluginToggled += EnablePlugin; - - // Context menu for the more (...) button - contextMenu = new(); - contextMenu.Deactivate(); - contextMenu.CreateNewContextMenu(); - contextMenu.AddItem(new("Add development folder"), "Open and compile a folder for development", - userData: nameof(OnLoadFolder)); - contextMenu.AddItem(new("Save profile"), "Saved the current plugin selection", userData: nameof(OnSaveProfile)); - contextMenu.AddItem(new("Load profile"), "Loads a saved plugin selection", userData: nameof(OnLoadProfile)); - contextMenu.AddItem(new("------------")); - contextMenu.AddItem( - new(PlayerConsent.ConsentGiven ? "Revoke consent" : "Give consent"), - PlayerConsent.ConsentGiven - ? "Revoke consent to data handling, clear my votes" - : "Give consent to data handling, allow me to vote", - userData: nameof(OnConsent)); - contextMenu.Enabled = true; - contextMenu.ItemClicked += OnContextMenuItemClicked; - contextMenu.OnDeactivated += OnContextMenuDeactivated; - // contextMenu.SetMaxSize(new Vector2(0.2f, 0.7f)); - Controls.Add(contextMenu); - - // Context menu for the plugin list - pluginContextMenu = new(); - pluginContextMenu.Deactivate(); - pluginContextMenu.CreateNewContextMenu(); - pluginContextMenu.ItemClicked += OnPluginContextMenuItemClicked; - pluginContextMenu.OnDeactivated += OnContextMenuDeactivated; - Controls.Add(pluginContextMenu); - - // Refreshes the table to show plugins on plugin list - RefreshTable(); - - DownloadStats(); - } - - public void RequireRestart() - { - forceRestart = true; - } - - private void OnLoadFolder() - { - LocalFolderPlugin.CreateNew(plugin => - { - Config.PluginFolders[plugin.Id] = plugin.FolderSettings; - CreatePlugin(plugin); - }); - } - - public void CreatePlugin(PluginData data) - { - Main.Instance.List.Add(data); - AfterRebootEnableFlags[data.Id] = true; - Config.SetEnabled(data.Id, true); - forceRestart = true; - RefreshTable(tableFilter); - } - - public void RemovePlugin(PluginData data) - { - Main.Instance.List.Remove(data.Id); - AfterRebootEnableFlags.Remove(data.Id); - Config.SetEnabled(data.Id, false); - forceRestart = true; - RefreshTable(tableFilter); - } - - public void RefreshSidePanel() - { - pluginDetails?.LoadPluginData(); - } - - /// - /// Event that triggers when the visibility button is clicked. This method shows all plugins or only enabled plugins. - /// - /// The button to assign this event to. - private void OnVisibilityClick(MyGuiControlButton btn) - { - if (allItemsVisible && _hideLocalPlugins) - { - allItemsVisible = false; - btn.Icon = IconShow; - } - else if (_hideLocalPlugins) - { - allItemsVisible = true; - _hideLocalPlugins = false; - } - else - { - _hideLocalPlugins = true; - btn.Icon = IconHide; - } - - RefreshTable(tableFilter); - } - - private static int CellTextOrDataComparison(MyGuiControlTable.Cell x, MyGuiControlTable.Cell y) - { - var result = TextComparison(x.Text, y.Text); - if (result != 0) return result; - - return TextComparison((StringBuilder)x.UserData, (StringBuilder)y.UserData); - } - - private static int CellTextComparison(MyGuiControlTable.Cell x, MyGuiControlTable.Cell y) - { - return TextComparison(x.Text, y.Text); - } - - private static int TextComparison(StringBuilder x, StringBuilder y) - { - if (x == null) - { - if (y == null) - return 0; - return 1; - } - - if (y == null) - return -1; - - return x.CompareTo(y); - } - - /// - /// Clears the table and adds the list of plugins and their information. - /// - /// Text filter - private void RefreshTable(string[] filter = null) - { - pluginTable.Clear(); - pluginTable.Controls.Clear(); - pluginCheckboxes.Clear(); - var list = Main.Instance.List; - var noFilter = filter == null || filter.Length == 0; - foreach (var plugin in list) - { - var enabled = AfterRebootEnableFlags[plugin.Id]; - - if (plugin.IsLocal && _hideLocalPlugins) continue; - - if (noFilter && (plugin.Hidden || !allItemsVisible) && !enabled) - continue; - - if (!noFilter && !FilterName(plugin.FriendlyName, filter)) - continue; - - var row = new MyGuiControlTable.Row(plugin); - pluginTable.Add(row); - - var name = new StringBuilder(plugin.FriendlyName); - row.AddCell(new(plugin.Source, name)); - - var tip = plugin.FriendlyName; - if (!string.IsNullOrWhiteSpace(plugin.Tooltip)) - tip += "\n" + plugin.Tooltip; - row.AddCell(new(plugin.FriendlyName, toolTip: tip)); - - var text = new StringBuilder(FormatCheckboxSortKey(plugin, enabled)); - var enabledCell = new MyGuiControlTable.Cell(text, name); - var enabledCheckbox = new MyGuiControlCheckbox(isChecked: enabled) - { - UserData = plugin, - Visible = true - }; - enabledCheckbox.IsCheckedChanged += OnPluginCheckboxChanged; - enabledCell.Control = enabledCheckbox; - pluginTable.Controls.Add(enabledCheckbox); - pluginCheckboxes[plugin.Id] = enabledCheckbox; - row.AddCell(enabledCell); - } - - pluginCountLabel.Text = pluginTable.RowsCount + "/" + list.Count + " visible"; - pluginTable.Sort(false); - pluginTable.SelectedRowIndex = null; - tableFilter = filter; - pluginTable.SelectedRowIndex = 0; - - var args = new MyGuiControlTable.EventArgs { RowIndex = 0 }; - OnItemSelected(pluginTable, args); - } - - private static string FormatCheckboxSortKey(PluginData plugin, bool enabled) - { - // Uses a prefix of + and - to list plugins to enable to the top - return enabled ? $"+{plugin.FriendlyName}|{plugin.Source}" : $"-{plugin.FriendlyName}|{plugin.Source}"; - } - - /// - /// Event that triggers when the text in the searchbox is changed. - /// - /// The text that was entered into the searchbox. - private void SearchBox_TextChanged(string txt) - { - var args = txt.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - RefreshTable(args); - } - - private static bool FilterName(string name, IEnumerable filter) - { - return filter.All(s => name.Contains(s, StringComparison.OrdinalIgnoreCase)); - } - - /// - /// Sets text on right side of screen. - /// - /// Table to get the plugin data. - /// Event arguments. - private void OnItemSelected(MyGuiControlTable table, MyGuiControlTable.EventArgs args) - { - if (!TryGetPluginByRowIndex(args.RowIndex, out var plugin)) - return; - - if (args.MouseButton == MyMouseButtonsEnum.Right && plugin.OpenContextMenu(pluginContextMenu)) - { - pluginContextMenu.ItemList_UseSimpleItemListMouseOverCheck = true; - pluginContextMenu.Activate(); - } - - contextMenu.Deactivate(); - SelectedPlugin = plugin; - } - - private void OnItemDoubleClicked(MyGuiControlTable table, MyGuiControlTable.EventArgs args) - { - if (!TryGetPluginByRowIndex(args.RowIndex, out var data)) - return; - - EnablePlugin(data, !AfterRebootEnableFlags[data.Id]); - } - - private bool TryGetPluginByRowIndex(int rowIndex, out PluginData plugin) - { - if (rowIndex < 0 || rowIndex >= pluginTable.RowsCount) - { - plugin = null; - return false; - } - - var row = pluginTable.GetRow(rowIndex); - plugin = row.UserData as PluginData; - return plugin != null; - } - - private void AlignRow(Vector2 origin, float spacing, params MyGuiControlBase[] elements) - { - if (elements.Length == 0) - return; - - float totalWidth = 0; - for (var i = 0; i < elements.Length; i++) - { - var btn = elements[i]; - totalWidth += btn.Size.X; - if (i < elements.Length - 1) - totalWidth += spacing; - } - - var originX = origin.X - totalWidth / 2; - foreach (var btn in elements) - { - var halfWidth = btn.Size.X / 2; - originX += halfWidth; - btn.Position = new(originX, origin.Y); - originX += spacing + halfWidth; - } - } - - private void OnPluginCheckboxChanged(MyGuiControlCheckbox checkbox) - { - var plugin = (PluginData)checkbox.UserData; - EnablePlugin(plugin, checkbox.IsChecked); - - if (ReferenceEquals(plugin, SelectedPlugin)) - pluginDetails.LoadPluginData(); - } - - public void EnablePlugin(PluginData plugin, bool enable) - { - if (enable == AfterRebootEnableFlags[plugin.Id]) - return; - - AfterRebootEnableFlags[plugin.Id] = enable; - - SetPluginCheckbox(plugin, enable); - - if (enable) - { - DisableOtherPluginsInSameGroup(plugin); - EnableDependencies(plugin); - } - } - - private void SetPluginCheckbox(PluginData plugin, bool enable) - { - if (!pluginCheckboxes.TryGetValue(plugin.Id, out var checkbox)) - return; // The checkbox might not exist if the target plugin is a dependency not currently in the table - checkbox.IsChecked = enable; - - var row = pluginTable.Find(x => ReferenceEquals(x.UserData as PluginData, plugin)); - row?.GetCell(2).Text.Clear().Append(FormatCheckboxSortKey(plugin, enable)); - } - - private void DisableOtherPluginsInSameGroup(PluginData plugin) - { - foreach (var other in plugin.Group) - if (!ReferenceEquals(other, plugin)) - EnablePlugin(other, false); - } - - private void EnableDependencies(PluginData plugin) - { - if (plugin is not ModPlugin mod || mod.Dependencies == null) - return; - - foreach (PluginData other in mod.Dependencies) - if (!ReferenceEquals(other, plugin)) - EnablePlugin(other, true); - } - - private void OnCancelButtonClick(MyGuiControlButton btn) - { - CloseScreen(); - } - - private void OnMoreButtonClick(MyGuiControlButton _) - { - contextMenu.ItemList_UseSimpleItemListMouseOverCheck = true; - contextMenu.Enabled = false; - contextMenu.Activate(false); - contextMenu.OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP; - contextMenu.Position = buttonMore.Position + buttonMore.Size * new Vector2(-1.3f, -1.9f); - FocusContextMenuList(); - } - - private void FocusContextMenuList() - { - var guiControlsOwner = (IMyGuiControlsOwner)contextMenu; - while (guiControlsOwner.Owner != null) - { - guiControlsOwner = guiControlsOwner.Owner; - if (guiControlsOwner is not MyGuiScreenBase myGuiScreenBase) - continue; - - myGuiScreenBase.FocusedControl = contextMenu.GetInnerList(); - break; - } - } - - private void OnContextMenuDeactivated() - { - contextMenu.Enabled = true; - } - - private void OnContextMenuItemClicked(MyGuiControlContextMenu _, MyGuiControlContextMenu.EventArgs args) - { - contextMenu.Deactivate(); - - switch ((string)args.UserData) - { - case nameof(OnLoadFolder): - OnLoadFolder(); - break; - - case nameof(OnSaveProfile): - OnSaveProfile(); - break; - - case nameof(OnLoadProfile): - OnLoadProfile(); - break; - - case nameof(OnConsent): - OnConsent(); - break; - } - } - - private void OnPluginContextMenuItemClicked(MyGuiControlContextMenu menu, MyGuiControlContextMenu.EventArgs args) - { - SelectedPlugin?.ContextMenuClicked(this, args); - } - - private void OnSaveProfile() - { - var timestamp = DateTime.Now.ToString("O").Substring(0, 19).Replace('T', ' '); - MyGuiSandbox.AddScreen(new NameDialog(OnProfileNameProvided, "Save profile", timestamp)); - } - - private void OnProfileNameProvided(string name) - { - var afterRebootEnablePluginIds = AfterRebootEnableFlags - .Where(p => p.Value) - .Select(p => p.Key); - - var profile = new Profile(name, afterRebootEnablePluginIds.ToArray()); - Config.ProfileMap[profile.Key] = profile; - Config.Save(); - } - - private void OnLoadProfile() - { - MyGuiSandbox.AddScreen(new ProfilesDialog("Load profile", OnProfileLoaded)); - } - - private void OnProfileLoaded(Profile profile) - { - var pluginsEnabledInProfile = profile.Plugins.ToHashSet(); - - foreach (var plugin in Main.Instance.List) - EnablePlugin(plugin, pluginsEnabledInProfile.Contains(plugin.Id)); - - pluginTable.SortByColumn(2, MyGuiControlTable.SortStateEnum.Ascending); - } - - private void OnConsent() - { - PlayerConsent.ShowDialog(); - } - - private void OnRestartButtonClick(MyGuiControlButton btn) - { - if (!RequiresRestart) - { - CloseScreen(); - return; - } - - MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Info, - MyMessageBoxButtonsType.YES_NO_CANCEL, - new( - "A restart is required to apply changes. Would you like to restart the game now?"), - new("Apply Changes?"), callback: AskRestartResult)); - } - - private void Save() - { - if (!RequiresRestart) - return; - - foreach (var plugin in Main.Instance.List) - Config.SetEnabled(plugin.Id, AfterRebootEnableFlags[plugin.Id]); - - Config.Save(); - } - -#region Icons - - // Source: MyTerminalControlPanel - private static readonly MyGuiHighlightTexture IconHide = new() - { - Normal = "Textures\\GUI\\Controls\\button_hide.dds", - Highlight = "Textures\\GUI\\Controls\\button_hide.dds", - Focus = "Textures\\GUI\\Controls\\button_hide_focus.dds", - SizePx = new(40f, 40f) - }; - - // Source: MyTerminalControlPanel - private static readonly MyGuiHighlightTexture IconShow = new() - { - Normal = "Textures\\GUI\\Controls\\button_unhide.dds", - Highlight = "Textures\\GUI\\Controls\\button_unhide.dds", - Focus = "Textures\\GUI\\Controls\\button_unhide_focus.dds", - SizePx = new(40f, 40f) - }; - -#endregion - -#region Restart - - private void AskRestartResult(ResultEnum result) - { - if (result == ResultEnum.YES) - { - Save(); - if (MyGuiScreenGamePlay.Static != null) - { - ShowSaveMenu(delegate { LoaderTools.UnloadAndRestart(); }); - return; - } - - LoaderTools.UnloadAndRestart(); - } - else if (result == ResultEnum.NO) - { - Save(); - CloseScreen(); - } - } - - /// - /// From WesternGamer/InGameWorldLoading - /// - /// Action after code is executed. - private static void ShowSaveMenu(Action afterMenu) - { - // Sync.IsServer is backwards - if (!Sync.IsServer) - { - afterMenu(); - return; - } - - var message = ""; - var isCampaign = false; - var buttonsType = MyMessageBoxButtonsType.YES_NO_CANCEL; - - // Sync.IsServer is backwards - if (Sync.IsServer && !MySession.Static.Settings.EnableSaving) - { - message += - "Are you sure that you want to restart the game? All progress from the last checkpoint will be lost."; - isCampaign = true; - buttonsType = MyMessageBoxButtonsType.YES_NO; - } - else - { - message += "Save changes before restarting game?"; - } - - var saveMenu = MyGuiSandbox.CreateMessageBox(buttonType: buttonsType, messageText: new(message), - messageCaption: MyTexts.Get( - MyCommonTexts.MessageBoxCaptionPleaseConfirm), - callback: ShowSaveMenuCallback, - cancelButtonText: MyStringId.GetOrCompute("Don't Restart")); - saveMenu.InstantClose = false; - MyGuiSandbox.AddScreen(saveMenu); - - void ShowSaveMenuCallback(ResultEnum callbackReturn) - { - if (isCampaign) - { - if (callbackReturn == ResultEnum.YES) - afterMenu(); - - return; - } - - switch (callbackReturn) - { - case ResultEnum.YES: - MyAsyncSaving.Start(delegate - { - MySandboxGame.Static.OnScreenshotTaken += - UnloadAndExitAfterScreenshotWasTaken; - }); - break; - - case ResultEnum.NO: - MyAudio.Static.Mute = true; - MyAudio.Static.StopMusic(); - afterMenu(); - break; - } - } - - void UnloadAndExitAfterScreenshotWasTaken(object sender, EventArgs e) - { - MySandboxGame.Static.OnScreenshotTaken -= UnloadAndExitAfterScreenshotWasTaken; - afterMenu(); - } - } - -#endregion -} \ No newline at end of file diff --git a/PluginLoader/GUI/NameDialog.cs b/PluginLoader/GUI/NameDialog.cs deleted file mode 100644 index 108243d..0000000 --- a/PluginLoader/GUI/NameDialog.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Sandbox; -using Sandbox.Game.Gui; -using Sandbox.Game.Localization; -using Sandbox.Graphics.GUI; -using VRage; -using VRage.Utils; -using VRageMath; -using Color = VRageMath.Color; - -// ReSharper disable VirtualMemberCallInConstructor -#pragma warning disable 618 - -namespace PluginLoader.GUI; - -internal class NameDialog : MyGuiScreenDebugBase -{ - private readonly string caption; - private readonly string defaultName; - private readonly int maxLength; - - private readonly Action onOk; - private MyGuiControlButton cancelButton; - private MyGuiControlTextbox nameBox; - private MyGuiControlButton okButton; - - public NameDialog( - Action onOk, - string caption = "Name", - string defaultName = "", - int maxLength = 40) - : base(new(0.5f, 0.5f), new Vector2(0.5f, 0.28f), - MyGuiConstants.SCREEN_BACKGROUND_COLOR * MySandboxGame.Config.UIBkOpacity, true) - { - this.onOk = onOk; - this.caption = caption; - this.defaultName = defaultName; - this.maxLength = maxLength; - - RecreateControls(true); - - CanBeHidden = true; - CanHideOthers = true; - CloseButtonEnabled = true; - - m_onEnterCallback = ReturnOk; - } - - private Vector2 DialogSize => m_size ?? Vector2.One; - - public override void RecreateControls(bool constructor) - { - base.RecreateControls(constructor); - - AddCaption(caption, Color.White.ToVector4(), new Vector2(0.0f, 0.003f)); - - var controlSeparatorList1 = new MyGuiControlSeparatorList(); - controlSeparatorList1.AddHorizontal(new(-0.39f * DialogSize.X, -0.5f * DialogSize.Y + 0.075f), - DialogSize.X * 0.78f); - Controls.Add(controlSeparatorList1); - - var controlSeparatorList2 = new MyGuiControlSeparatorList(); - controlSeparatorList2.AddHorizontal(new(-0.39f * DialogSize.X, +0.5f * DialogSize.Y - 0.123f), - DialogSize.X * 0.78f); - Controls.Add(controlSeparatorList2); - - nameBox = new(new Vector2(0.0f, -0.027f), maxLength: maxLength) - { - Text = defaultName, - Size = new(0.385f, 1f) - }; - nameBox.SelectAll(); - Controls.Add(nameBox); - - okButton = new(originAlign: MyGuiDrawAlignEnum.HORISONTAL_RIGHT_AND_VERTICAL_CENTER, - text: MyTexts.Get(MyCommonTexts.Ok), onButtonClick: OnOk); - cancelButton = new(originAlign: MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_CENTER, - text: MyTexts.Get(MyCommonTexts.Cancel), onButtonClick: OnCancel); - - var okPosition = new Vector2(0.001f, 0.5f * DialogSize.Y - 0.071f); - var halfDistance = new Vector2(0.018f, 0.0f); - - okButton.Position = okPosition - halfDistance; - cancelButton.Position = okPosition + halfDistance; - - okButton.SetToolTip(MyTexts.GetString(MySpaceTexts.ToolTipNewsletter_Ok)); - cancelButton.SetToolTip(MyTexts.GetString(MySpaceTexts.ToolTipOptionsSpace_Cancel)); - - Controls.Add(okButton); - Controls.Add(cancelButton); - } - - private void CallResultCallback(string text) - { - if (text == null) - return; - - onOk(text); - } - - private void ReturnOk() - { - if (nameBox.GetTextLength() <= 0) - return; - - CallResultCallback(nameBox.Text); - CloseScreen(); - } - - private void OnOk(MyGuiControlButton button) - { - ReturnOk(); - } - - private void OnCancel(MyGuiControlButton button) - { - CloseScreen(); - } - - public override string GetFriendlyName() - { - return "NameDialog"; - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/PlayerConsent.cs b/PluginLoader/GUI/PlayerConsent.cs deleted file mode 100644 index 6086712..0000000 --- a/PluginLoader/GUI/PlayerConsent.cs +++ /dev/null @@ -1,90 +0,0 @@ -using PluginLoader.Stats; -using Sandbox.Graphics.GUI; -using VRageMath; - -namespace PluginLoader.GUI; - -public static class PlayerConsent -{ - public static bool ConsentRequested => !string.IsNullOrEmpty(Main.Instance.Config.DataHandlingConsentDate); - - public static bool ConsentGiven => Main.Instance.Config.DataHandlingConsent; - public static event Action OnConsentChanged; - - public static void ShowDialog(Action continuation = null) - { - MyGuiSandbox.AddScreen( - ConfirmationDialog.CreateMessageBox(buttonType: MyMessageBoxButtonsType.YES_NO_CANCEL, - messageText: new( - " Would you like to rate plugins and inform developers?\r\n" + - "\r\n" + - "\r\n" + - "YES: Plugin Loader will send the list of enabled plugins to our server\r\n" + - " each time the game starts. Your Steam ID is sent only in hashed form,\r\n" + - " which makes it hard to identify you. Plugin usage statistics is kept\r\n" + - " for up to 90 days. Votes on plugins are preserved indefinitely.\r\n" + - " Server log files and database backups may be kept up to 90 days.\r\n" + - " Location of data storage: European Union\r\n" + - "\r\n" + - "\r\n" + - "NO: None of your data will be sent to nor stored on our statistics server.\r\n" + - " Plugin Loader will still connect to download the statistics shown.\r\n"), - size: new Vector2(0.6f, 0.6f), - messageCaption: new("Consent"), - callback: result => GetConfirmation(result, continuation))); - } - - private static void GetConfirmation(MyGuiScreenMessageBox.ResultEnum result, Action continuation) - { - if (result == MyGuiScreenMessageBox.ResultEnum.CANCEL) - return; - - var consent = result == MyGuiScreenMessageBox.ResultEnum.YES; - - var consentWithdrawn = ConsentRequested && ConsentGiven && !consent; - if (consentWithdrawn) - { - MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Info, - MyMessageBoxButtonsType.YES_NO_CANCEL, - new( - "Are you sure to withdraw your consent to data handling?\r\n\r\nDoing so would irrecoverably remove all your votes\r\nand usage data from our statistics server."), - new("Confirm consent withdrawal"), - callback: res => - StoreConsent(res, false, continuation))); - return; - } - - StoreConsent(MyGuiScreenMessageBox.ResultEnum.YES, consent, continuation); - } - - private static void StoreConsent(MyGuiScreenMessageBox.ResultEnum confirmationResult, bool consent, - Action continuation) - { - if (confirmationResult != MyGuiScreenMessageBox.ResultEnum.YES) - return; - - if (ConsentRequested && consent == ConsentGiven) - { - continuation?.Invoke(); - return; - } - - if (!StatsClient.Consent(consent)) - { - LogFile.Log.Debug("Failed to register player consent on statistics server"); - return; - } - - var config = Main.Instance.Config; - config.DataHandlingConsentDate = Tools.Tools.FormatDateIso8601(DateTime.Today); - config.DataHandlingConsent = consent; - config.Save(); - - if (consent) - StatsClient.Track(Main.Instance.TrackablePluginIds); - - OnConsentChanged?.Invoke(); - - continuation?.Invoke(); - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/PluginDetails.cs b/PluginLoader/GUI/PluginDetails.cs deleted file mode 100644 index 6d76113..0000000 --- a/PluginLoader/GUI/PluginDetails.cs +++ /dev/null @@ -1,439 +0,0 @@ -using PluginLoader.Data; -using PluginLoader.GUI.GuiControls; -using PluginLoader.Stats; -using PluginLoader.Stats.Model; -using Sandbox.Graphics.GUI; -using VRage.Game; -using VRage.Utils; -using VRageMath; - -namespace PluginLoader.GUI; - -public class PluginDetailsPanel : MyGuiControlParent -{ - private readonly PluginStat dummyStat = new(); - - private readonly MyGuiScreenPluginConfig pluginsDialog; - private MyGuiControlLabel authorLabel; - private MyGuiControlLabel authorText; - private MyGuiControlButton configButton; - private MyGuiControlCompositePanel descriptionPanel; - private MyGuiControlMultilineText descriptionText; - private MyGuiControlButton downvoteButton; - private MyGuiControlLabel downvoteCountText; - private MyGuiControlImage downvoteIcon; - private MyGuiControlCheckbox enableCheckbox; - private MyGuiControlLabel enableLabel; - private MyGuiControlButton infoButton; - private PluginInstance instance; - - // Layout management - private MyLayoutTable layoutTable; - - // Plugin currently loaded into the panel or null if none are loaded - private PluginData plugin; - - // Panel controls - private MyGuiControlLabel pluginNameLabel; - private MyGuiControlLabel pluginNameText; - private RatingControl ratingControl; - private MyGuiControlLabel ratingLabel; - private MyGuiControlLabel statusLabel; - private MyGuiControlLabel statusText; - private MyGuiControlButton upvoteButton; - private MyGuiControlLabel upvoteCountText; - private MyGuiControlImage upvoteIcon; - private MyGuiControlLabel usageLabel; - private MyGuiControlLabel usageText; - private MyGuiControlLabel versionLabel; - private MyGuiControlLabel versionText; - - public PluginDetailsPanel(MyGuiScreenPluginConfig dialog) - { - pluginsDialog = dialog; - } - - public PluginData Plugin - { - get => plugin; - set - { - if (ReferenceEquals(value, Plugin)) - return; - - plugin = value; - - if (plugin == null) - { - DisableControls(); - ClearPluginData(); - return; - } - - if (Main.Instance.TryGetPluginInstance(plugin.Id, out var instance)) - this.instance = instance; - else - this.instance = null; - - EnableControls(); - LoadPluginData(); - } - } - - private PluginStat PluginStat => pluginsDialog.PluginStats?.Stats.GetValueOrDefault(plugin.Id) ?? dummyStat; - public event Action OnPluginToggled; - - private void DisableControls() - { - foreach (var control in Controls) - control.Enabled = false; - } - - private void EnableControls() - { - foreach (var control in Controls) - control.Enabled = true; - } - - private void ClearPluginData() - { - pluginNameText.Text = ""; - authorText.Text = ""; - versionText.Text = ""; - statusText.Text = ""; - usageText.Text = ""; - ratingControl.Value = 0; - upvoteButton.Checked = false; - downvoteButton.Checked = false; - descriptionText.Text.Clear(); - enableCheckbox.IsChecked = false; - } - - public void LoadPluginData() - { - if (plugin == null) - return; - - var stat = PluginStat; - var vote = stat.Vote; - var nonLocal = !plugin.IsLocal; - var canVote = (plugin.Enabled || stat.Tried) && nonLocal; - var showVotes = canVote || nonLocal; - - pluginNameText.Text = plugin.FriendlyName ?? "N/A"; - - authorText.Text = plugin.Author ?? (plugin.IsLocal ? "Local" : "N/A"); - - versionText.Text = plugin.Version?.ToString() ?? "N/A"; - - statusLabel.Visible = nonLocal; - statusText.Visible = nonLocal; - statusText.Text = plugin.Status == PluginStatus.None - ? plugin.Enabled ? "Up to date" : "N/A" - : plugin.StatusString; - - usageLabel.Visible = nonLocal; - usageText.Visible = nonLocal; - usageText.Text = stat.Players.ToString(); - - ratingLabel.Visible = showVotes; - - upvoteIcon.Visible = showVotes; - upvoteButton.Visible = canVote; - upvoteButton.Checked = vote > 0; - upvoteCountText.Visible = showVotes; - upvoteCountText.Text = $"{stat.Upvotes}"; - - downvoteIcon.Visible = showVotes; - downvoteButton.Visible = canVote; - downvoteButton.Checked = vote < 0; - downvoteCountText.Visible = showVotes; - downvoteCountText.Text = $"{stat.Downvotes}"; - - ratingControl.Value = stat.Rating; - - plugin.GetDescriptionText(descriptionText); - descriptionPanel.Visible = descriptionText.Visible; - - enableCheckbox.IsChecked = pluginsDialog.AfterRebootEnableFlags[plugin.Id]; - - configButton.Enabled = instance != null && instance.HasConfigDialog; - configButton.Visible = instance != null; - } - - public virtual void CreateControls(Vector2 rightSideOrigin) - { - // Plugin name - pluginNameLabel = new() - { - Text = "Name", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - pluginNameText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - // Author - authorLabel = new() - { - Text = "Author", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - authorText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - // Version - versionLabel = new() - { - Text = "Version", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - versionText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - // Status - statusLabel = new() - { - Text = "Status", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - statusText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - // Usage - usageLabel = new() - { - Text = "Usage", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - usageText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - // Rating - ratingLabel = new() - { - Text = "Rating", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - upvoteButton = new(null, MyGuiControlButtonStyleEnum.Rectangular, onButtonClick: OnRateUpClicked, - size: new Vector2(0.03f)) - { - CanHaveFocus = false - }; - upvoteIcon = CreateRateIcon(upvoteButton, "Textures\\GUI\\Icons\\Blueprints\\like_test.png"); - upvoteIcon.CanHaveFocus = false; - upvoteCountText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - downvoteButton = new(null, MyGuiControlButtonStyleEnum.Rectangular, onButtonClick: OnRateDownClicked, - size: new Vector2(0.03f)) - { - CanHaveFocus = false - }; - downvoteIcon = CreateRateIcon(downvoteButton, "Textures\\GUI\\Icons\\Blueprints\\dislike_test.png"); - downvoteIcon.CanHaveFocus = false; - downvoteCountText = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - - ratingControl = new() - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - Visible = false // FIXME: Make the rating (stars) visible later! Its positioning should already be good. - }; - - // Plugin description - descriptionText = new() - { - Name = "DescriptionText", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - TextAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, - TextBoxAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP - }; - descriptionText.OnLinkClicked += (x, url) => MyGuiSandbox.OpenUrl(url, UrlOpenMode.SteamOrExternalWithConfirm); - descriptionPanel = new() - { - BackgroundTexture = MyGuiConstants.TEXTURE_RECTANGLE_DARK_BORDER - }; - - // Enable checkbox - enableLabel = new() - { - Text = "Enabled", - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP - }; - enableCheckbox = new(toolTip: "Enables loading the plugin when SE is started.") - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - Enabled = false - }; - enableCheckbox.IsCheckedChanged += TogglePlugin; - - // Info button - infoButton = new(onButtonClick: _ => Plugin?.Show()) - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - Text = "Plugin Info" - }; - - // Plugin config button - configButton = new(onButtonClick: _ => instance?.OpenConfig()) - { - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - Text = "Plugin Config" - }; - - LayoutControls(rightSideOrigin); - } - - private void LayoutControls(Vector2 rightSideOrigin) - { - layoutTable = new(this, rightSideOrigin, new(1f, 1f)); - layoutTable.SetColumnWidths(168f, 468f); - layoutTable.SetRowHeights(60f, 60f, 60f, 60f, 60f, 60f, 420f, 60f, 60f); - - var row = 0; - - layoutTable.Add(pluginNameLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(pluginNameText, MyAlignH.Left, MyAlignV.Center, row, 1); - row++; - - layoutTable.Add(authorLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(authorText, MyAlignH.Left, MyAlignV.Center, row, 1); - row++; - - layoutTable.Add(versionLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(versionText, MyAlignH.Left, MyAlignV.Center, row, 1); - row++; - - layoutTable.Add(statusLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(statusText, MyAlignH.Left, MyAlignV.Center, row, 1); - row++; - - layoutTable.Add(usageLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(usageText, MyAlignH.Left, MyAlignV.Center, row, 1); - row++; - - layoutTable.Add(ratingLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(upvoteCountText, MyAlignH.Left, MyAlignV.Center, row, 1); - layoutTable.Add(upvoteButton, MyAlignH.Left, MyAlignV.Center, row, 1); - layoutTable.Add(upvoteIcon, MyAlignH.Left, MyAlignV.Center, row, 1); - layoutTable.Add(downvoteCountText, MyAlignH.Left, MyAlignV.Center, row, 1); - layoutTable.Add(downvoteButton, MyAlignH.Left, MyAlignV.Center, row, 1); - layoutTable.Add(downvoteIcon, MyAlignH.Left, MyAlignV.Center, row, 1); - layoutTable.Add(ratingControl, MyAlignH.Left, MyAlignV.Center, row, 1); - - const float counterWidth = 0.05f; - const float spacing = 0.005f; - var buttonWidth = upvoteButton.Size.X; - var voteWidth = buttonWidth + spacing + counterWidth + 3 * spacing; - var buttonToIconOffset = new Vector2(0.004f, -0.001f); - upvoteIcon.Position = upvoteButton.Position + buttonToIconOffset; - upvoteCountText.Position = upvoteButton.Position + new Vector2(buttonWidth + spacing, 0f); - downvoteButton.Position = upvoteButton.Position + new Vector2(voteWidth, 0f); - downvoteIcon.Position = downvoteButton.Position + buttonToIconOffset; - downvoteCountText.Position = downvoteButton.Position + new Vector2(buttonWidth + spacing, 0f); - ratingControl.Position = downvoteButton.Position + new Vector2(voteWidth, 0f); - row++; - - layoutTable.AddWithSize(descriptionPanel, MyAlignH.Center, MyAlignV.Top, row, 0, 1, 2); - layoutTable.AddWithSize(descriptionText, MyAlignH.Center, MyAlignV.Center, row, 0, 1, 2); - row++; - - layoutTable.Add(enableLabel, MyAlignH.Left, MyAlignV.Center, row, 0); - layoutTable.Add(enableCheckbox, MyAlignH.Left, MyAlignV.Center, row, 1); - row++; - - const float infoConfigSpacing = 0.015f; - layoutTable.AddWithSize(infoButton, MyAlignH.Right, MyAlignV.Center, row, 0, 1, 2); - layoutTable.AddWithSize(configButton, MyAlignH.Right, MyAlignV.Center, row, 0, 1, 2); - configButton.Position += new Vector2(0f, infoConfigSpacing); - infoButton.Position = configButton.Position + new Vector2(-configButton.Size.X - infoConfigSpacing, 0); - // row++; - - var border = 0.002f * Vector2.One; - descriptionPanel.Position -= border; - descriptionPanel.Size += 2 * border; - - DisableControls(); - } - - private void TogglePlugin(MyGuiControlCheckbox obj) - { - if (plugin == null) - return; - - OnPluginToggled?.Invoke(plugin, enableCheckbox.IsChecked); - } - - private void OnRateUpClicked(MyGuiControlButton button) - { - Vote(1); - } - - private void OnRateDownClicked(MyGuiControlButton button) - { - Vote(-1); - } - - private void Vote(int vote) - { - if (PlayerConsent.ConsentGiven) - StoreVote(vote); - else - PlayerConsent.ShowDialog(() => StoreVote(vote)); - } - - private void StoreVote(int vote) - { - if (!PlayerConsent.ConsentGiven || pluginsDialog.PluginStats == null) - return; - - var originalStat = PluginStat; - if (originalStat.Vote == vote) - vote = 0; - - var updatedStat = StatsClient.Vote(plugin.Id, vote); - if (updatedStat == null) - return; - - pluginsDialog.PluginStats.Stats[plugin.Id] = updatedStat; - LoadPluginData(); - } - - // From Sandbox.Game.Screens.MyGuiScreenNewWorkshopGame - -#region Vote buttons - - private MyGuiControlImage CreateRateIcon(MyGuiControlButton button, string texture) - { - var myGuiControlImage = new MyGuiControlImage(null, null, null, null, new[] { texture }); - AdjustButtonForIcon(button, myGuiControlImage); - myGuiControlImage.Size = button.Size * 0.6f; - return myGuiControlImage; - } - - private void AdjustButtonForIcon(MyGuiControlButton button, MyGuiControlImage icon) - { - button.Size = new(button.Size.X, button.Size.X * 4f / 3f); - button.HighlightChanged += delegate(MyGuiControlBase control) - { - icon.ColorMask = control.HasHighlight ? MyGuiConstants.HIGHLIGHT_TEXT_COLOR : Vector4.One; - }; - } - -#endregion -} \ No newline at end of file diff --git a/PluginLoader/GUI/ProfilesDialog.cs b/PluginLoader/GUI/ProfilesDialog.cs deleted file mode 100644 index e2bdef0..0000000 --- a/PluginLoader/GUI/ProfilesDialog.cs +++ /dev/null @@ -1,96 +0,0 @@ -using PluginLoader.Data; - -namespace PluginLoader.GUI; - -public class ProfilesDialog : TableDialogBase -{ - private readonly Action onProfileLoaded; - - public ProfilesDialog(string caption, Action onProfileLoaded) : base(caption) - { - this.onProfileLoaded = onProfileLoaded; - } - - private static PluginConfig Config => Main.Instance.Config; - private static Dictionary ProfileMap => Config.ProfileMap; - private static PluginList PluginList => Main.Instance.List; - - protected override string ItemName => "profile"; - protected override string[] ColumnHeaders => new[] { "Name", "Enabled plugins and mods" }; - protected override float[] ColumnWidths => new[] { 0.55f, 0.43f }; - - protected override object[] ExampleValues => new object[] { null, 0 }; - - protected override IEnumerable IterItemKeys() - { - return ProfileMap.Keys.ToArray(); - } - - protected override ItemView GetItemView(string key) - { - if (!ProfileMap.TryGetValue(key, out var profile)) - return null; - - var locals = 0; - var plugins = 0; - var mods = 0; - foreach (var id in profile.Plugins) - { - if (!PluginList.TryGetPlugin(id, out var plugin)) - continue; - - switch (plugin) - { - case ModPlugin: - mods++; - break; - - case LocalPlugin: - locals++; - break; - - default: - plugins++; - break; - } - } - - var infoItems = new List(); - if (locals > 0) - infoItems.Add(locals > 1 ? $"{locals} local plugins" : "1 local plugin"); - if (plugins > 0) - infoItems.Add(plugins > 1 ? $"{plugins} plugins" : "1 plugin"); - if (mods > 0) - infoItems.Add(mods > 1 ? $"{mods} mods" : "1 mod"); - - var info = string.Join(", ", infoItems); - var labels = new[] { profile.Name, info }; - - var total = locals + plugins + mods; - var values = new object[] { null, total }; - - return new(labels, values); - } - - protected override void OnLoad(string key) - { - if (!ProfileMap.TryGetValue(key, out var profile)) - return; - - onProfileLoaded(profile); - } - - protected override void OnRenamed(string key, string name) - { - if (!ProfileMap.TryGetValue(key, out var profile)) - return; - - profile.Name = name; - } - - protected override void OnDelete(string key) - { - ProfileMap.Remove(key); - Config.Save(); - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/SplashScreen.cs b/PluginLoader/GUI/SplashScreen.cs deleted file mode 100644 index 472ed49..0000000 --- a/PluginLoader/GUI/SplashScreen.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Reflection; -using VRage; - -namespace PluginLoader.GUI; - -public sealed class SplashScreen : Form -{ - private const float barWidth = 0.98f; // 98% of width - private const float barHeight = 0.06f; // 6% of height - private readonly RectangleF bar; - private readonly PictureBox gifBox; - - private readonly bool invalid; - private readonly Label lbl; - - private float barValue = float.NaN; - - public SplashScreen() - { - CheckForIllegalCrossThreadCalls = false; - if (!TryLoadImage(out var gif)) - { - invalid = true; - return; - } - - Name = "SplashScreenPluginLoader"; - TopMost = true; - FormBorderStyle = FormBorderStyle.None; - Size = new((int)(gif.Width * 1.65), (int)(gif.Height * 1.25)); - BackColor = Color.Black; - UseWaitCursor = true; - ShowInTaskbar = false; - - var barSize = new SizeF(Size.Width * barWidth, Size.Height * barHeight); - var padding = (1 - barWidth) * Size.Width * 0.5f; - var barStart = new PointF(padding, Size.Height - barSize.Height - padding); - bar = new(barStart, barSize); - - var lblFont = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Bold); - lbl = new() - { - Name = "PluginLoaderInfo", - Font = lblFont, - BackColor = Color.Black, - ForeColor = Color.White, - MaximumSize = Size, - Size = new(Size.Width, lblFont.Height), - TextAlign = ContentAlignment.MiddleCenter, - Location = new(0, (int)(barStart.Y - lblFont.Height - 1)) - }; - Controls.Add(lbl); - - gifBox = new() - { - Name = "PluginLoaderAnimation", - Image = gif, - Size = Size, - AutoSize = false, - SizeMode = PictureBoxSizeMode.CenterImage - }; - Controls.Add(gifBox); - - gifBox.Paint += OnPictureBoxDraw; - - CenterToScreen(); - } - - public object GameInfo { get; private set; } - - private bool TryLoadImage(out Image img) - { - try - { - var myAssembly = Assembly.GetExecutingAssembly(); - var myStream = myAssembly.GetManifestResourceStream("PluginLoader.splash.gif")!; - img = new Bitmap(myStream); - return true; - } - catch - { - img = null; - return false; - } - } - - public void SetText(string msg) - { - if (invalid) - return; - - lbl.Text = msg; - barValue = float.NaN; - } - - public void SetBarValue(float percent = float.NaN) - { - if (invalid) - return; - - barValue = percent; - } - - private void OnPictureBoxDraw(object sender, PaintEventArgs e) - { - if (!float.IsNaN(barValue)) - { - var graphics = e.Graphics; - graphics.FillRectangle(Brushes.DarkSlateGray, bar); - graphics.FillRectangle(Brushes.White, new RectangleF(bar.Location, new(bar.Width * barValue, bar.Height))); - } - } - - public void Delete() - { - if (invalid) - return; - - gifBox.Paint -= OnPictureBoxDraw; - Close(); - Dispose(); - MyVRage.Platform.Windows.Window.ShowAndFocus(); - } -} \ No newline at end of file diff --git a/PluginLoader/GUI/TableDialogBase.cs b/PluginLoader/GUI/TableDialogBase.cs deleted file mode 100644 index 7d1fe07..0000000 --- a/PluginLoader/GUI/TableDialogBase.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Sandbox; -using Sandbox.Game.Gui; -using Sandbox.Game.Localization; -using Sandbox.Graphics.GUI; -using VRage; -using VRage.Game; -using VRage.Utils; -using VRageMath; -using Color = VRageMath.Color; - -namespace PluginLoader.GUI; - -[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] -public abstract class TableDialogBase : MyGuiScreenDebugBase -{ - protected readonly string Caption; - protected readonly string DefaultKey; - protected readonly Dictionary NamesByKey = new(); - protected MyGuiControlButton CancelButton; - - protected int ColumnCount; - protected MyGuiControlButton DeleteButton; - - protected MyGuiControlButton LoadButton; - protected MyGuiControlButton RenameButton; - - protected MyGuiControlTable Table; - - protected TableDialogBase( - string caption, - string defaultKey = null) - : base(new(0.5f, 0.5f), new Vector2(1f, 0.8f), - MyGuiConstants.SCREEN_BACKGROUND_COLOR * MySandboxGame.Config.UIBkOpacity, true) - { - Caption = caption; - DefaultKey = defaultKey; - - // ReSharper disable once VirtualMemberCallInConstructor - RecreateControls(true); - - CanBeHidden = true; - CanHideOthers = true; - CloseButtonEnabled = true; - - m_onEnterCallback = LoadAndClose; - } - - protected abstract string ItemName { get; } - protected abstract string[] ColumnHeaders { get; } - protected abstract float[] ColumnWidths { get; } - protected abstract object[] ExampleValues { get; } - - private Vector2 DialogSize => m_size ?? Vector2.One; - - private string SelectedKey => Table.SelectedRow?.UserData as string; - - public override string GetFriendlyName() - { - return "ListDialog"; - } - - protected virtual string NormalizeName(string name) - { - return name.Trim(); - } - - protected virtual int CompareNames(string a, string b) - { - return string.Compare(a, b, StringComparison.CurrentCultureIgnoreCase); - } - - protected abstract IEnumerable IterItemKeys(); - protected abstract ItemView GetItemView(string key); - - protected abstract void OnLoad(string key); - protected abstract void OnRenamed(string key, string name); - protected abstract void OnDelete(string key); - - public override void RecreateControls(bool constructor) - { - base.RecreateControls(constructor); - - AddCaption(Caption, Color.White.ToVector4(), new Vector2(0.0f, 0.003f)); - - CreateTable(); - CreateButtons(); - } - - private void CreateTable() - { - var columnHeaders = ColumnHeaders; - var columnWidths = ColumnWidths; - - if (columnHeaders == null || columnWidths == null) - return; - - ColumnCount = columnHeaders.Length; - - Table = new() - { - Position = new(0.001f, -0.5f * DialogSize.Y + 0.1f), - Size = new(0.85f * DialogSize.X, DialogSize.Y - 0.25f), - OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, - ColumnsCount = ColumnCount, - VisibleRowsCount = 15 - }; - - Table.SetCustomColumnWidths(columnWidths); - - var exampleValues = ExampleValues; - for (var colIdx = 0; colIdx < ColumnCount; colIdx++) - { - Table.SetColumnName(colIdx, new(columnHeaders[colIdx])); - - switch (exampleValues[colIdx]) - { - case int _: - Table.SetColumnComparison(colIdx, CellIntComparison); - break; - - default: - Table.SetColumnComparison(colIdx, CellTextComparison); - break; - } - } - - AddItems(); - - Table.SortByColumn(0); - - Table.ItemDoubleClicked += OnItemDoubleClicked; - - Controls.Add(Table); - } - - private int CellTextComparison(MyGuiControlTable.Cell x, MyGuiControlTable.Cell y) - { - var a = NormalizeName(x.Text.ToString()); - var b = NormalizeName(y.Text.ToString()); - return CompareNames(a, b); - } - - private int CellIntComparison(MyGuiControlTable.Cell x, MyGuiControlTable.Cell y) - { - return (x.UserData as int? ?? 0) - (y.UserData as int? ?? 0); - } - - private void CreateButtons() - { - LoadButton = new( - visualStyle: MyGuiControlButtonStyleEnum.Default, - originAlign: MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - text: new("Load"), onButtonClick: OnLoadButtonClick); - - RenameButton = new( - visualStyle: MyGuiControlButtonStyleEnum.Small, - originAlign: MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - text: new("Rename"), onButtonClick: OnRenameButtonClick); - - DeleteButton = new( - visualStyle: MyGuiControlButtonStyleEnum.Small, - originAlign: MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - text: new("Delete"), onButtonClick: OnDeleteButtonClick); - - CancelButton = new( - visualStyle: MyGuiControlButtonStyleEnum.Default, - originAlign: MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, - text: MyTexts.Get(MyCommonTexts.Cancel), onButtonClick: OnCancelButtonClick); - - var xs = 0.85f * DialogSize.X; - var y = 0.5f * (DialogSize.Y - 0.15f); - LoadButton.Position = new(-0.39f * xs, y); - RenameButton.Position = new(-0.08f * xs, y); - DeleteButton.Position = new(0.08f * xs, y); - CancelButton.Position = new(0.39f * xs, y); - - LoadButton.SetToolTip($"Loads the selected {ItemName}"); - RenameButton.SetToolTip($"Renames the selected {ItemName}"); - DeleteButton.SetToolTip($"Deletes the selected {ItemName}"); - CancelButton.SetToolTip(MyTexts.GetString(MySpaceTexts.ToolTipOptionsSpace_Cancel)); - - Controls.Add(LoadButton); - Controls.Add(RenameButton); - Controls.Add(DeleteButton); - Controls.Add(CancelButton); - } - - private void AddItems() - { - NamesByKey.Clear(); - - foreach (var key in IterItemKeys()) - AddRow(key); - - if (TryFindRow(DefaultKey, out var rowIdx)) - Table.SelectedRowIndex = rowIdx; - } - - private void AddRow(string key) - { - var view = GetItemView(key); - if (view == null) - return; - - var row = new MyGuiControlTable.Row(key); - for (var i = 0; i < ColumnCount; i++) - row.AddCell(new(view.Labels[i], view.Values[i])); - - Table.Add(row); - NamesByKey[key] = view.Labels[0]; - } - - private void OnItemDoubleClicked(MyGuiControlTable table, MyGuiControlTable.EventArgs args) - { - LoadAndClose(); - } - - private void OnLoadButtonClick(MyGuiControlButton _) - { - LoadAndClose(); - } - - private void LoadAndClose() - { - if (string.IsNullOrEmpty(SelectedKey)) - return; - - OnLoad(SelectedKey); - CloseScreen(); - } - - private void OnCancelButtonClick(MyGuiControlButton _) - { - CloseScreen(); - } - - private void OnRenameButtonClick(MyGuiControlButton _) - { - if (string.IsNullOrEmpty(SelectedKey)) - return; - - if (!NamesByKey.TryGetValue(SelectedKey, out var oldName)) - return; - - MyGuiSandbox.AddScreen(new NameDialog(newName => OnNewNameSpecified(SelectedKey, newName), - $"Rename saved {ItemName}", oldName)); - } - - private void OnNewNameSpecified(string key, string newName) - { - newName = NormalizeName(newName); - - if (!TryFindRow(key, out var rowIdx)) - return; - - OnRenamed(key, newName); - - var view = GetItemView(key); - - NamesByKey[key] = view.Labels[0]; - - var row = Table.GetRow(rowIdx); - for (var colIdx = 0; colIdx < ColumnCount; colIdx++) - { - var cell = row.GetCell(colIdx); - var sb = cell.Text; - sb.Clear(); - sb.Append(view.Labels[colIdx]); - } - - Table.Sort(); - } - - private void OnDeleteButtonClick(MyGuiControlButton _) - { - var key = SelectedKey; - if (string.IsNullOrEmpty(key)) - return; - - var name = NamesByKey.GetValueOrDefault(key) ?? "?"; - - MyGuiSandbox.AddScreen( - MyGuiSandbox.CreateMessageBox(buttonType: MyMessageBoxButtonsType.YES_NO, - messageText: new( - $"Are you sure to delete this saved {ItemName}?\r\n\r\n{name}"), - messageCaption: new("Confirmation"), - callback: result => OnDeleteForSure(result, key))); - } - - private void OnDeleteForSure(MyGuiScreenMessageBox.ResultEnum result, string key) - { - if (result != MyGuiScreenMessageBox.ResultEnum.YES) - return; - - NamesByKey.Remove(key); - - if (TryFindRow(key, out var rowIdx)) - Table.Remove(Table.GetRow(rowIdx)); - - OnDelete(key); - } - - private bool TryFindRow(string key, out int index) - { - if (key == null) - { - index = -1; - return false; - } - - var count = Table.RowsCount; - for (index = 0; index < count; index++) - if (Table.GetRow(index).UserData as string == key) - return true; - - index = -1; - return false; - } -} \ No newline at end of file diff --git a/PluginLoader/LoaderTools.cs b/PluginLoader/LoaderTools.cs deleted file mode 100644 index 6af56ef..0000000 --- a/PluginLoader/LoaderTools.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System.Diagnostics; -using System.Security.Cryptography; -using System.Text; -using Windows.UI.Popups; -using NLog; -using PluginLoader.SEPM; -using Sandbox; -using Sandbox.Game.World; -using Sandbox.Graphics.GUI; -using VRage.FileSystem; -using VRage.Input; -using VRage.Plugins; - -namespace PluginLoader; - -public static class LoaderTools -{ - public static string PluginsDir => Path.GetFullPath(Path.Combine(MyFileSystem.ExePath, "Plugins")); - - public static DialogResult ShowMessageBox(string message, string title, MessageBoxButtons buttons, MessageBoxIcon icon) - { - var dialog = new MessageDialog(message, title); - - switch (buttons) - { - case MessageBoxButtons.OK: - dialog.Commands.Add(new UICommand("Ok")); - break; - case MessageBoxButtons.OKCancel: - dialog.Commands.Add(new UICommand("Ok")); - dialog.Commands.Add(new UICommand("Cancel")); - break; - case MessageBoxButtons.AbortRetryIgnore: - break; - case MessageBoxButtons.YesNoCancel: - dialog.Commands.Add(new UICommand("Yes")); - dialog.Commands.Add(new UICommand("No")); - dialog.Commands.Add(new UICommand("Cancel")); - break; - case MessageBoxButtons.YesNo: - dialog.Commands.Add(new UICommand("Yes")); - dialog.Commands.Add(new UICommand("No")); - break; - case MessageBoxButtons.RetryCancel: - dialog.Commands.Add(new UICommand("Retry")); - dialog.Commands.Add(new UICommand("Cancel")); - break; - case MessageBoxButtons.CancelTryContinue: - break; - default: - throw new ArgumentOutOfRangeException(nameof(buttons), buttons, null); - } - - var hwnd = Process.GetCurrentProcess().MainWindowHandle; - - if (hwnd != IntPtr.Zero) - WinRT.Interop.InitializeWithWindow.Initialize(dialog, hwnd); - else - { - Console.WriteLine(message); - return DialogResult.Cancel; - } - - var result = dialog.ShowAsync().AsTask().Result; - - return buttons switch - { - MessageBoxButtons.OK => DialogResult.OK, - MessageBoxButtons.OKCancel => result.Label == "Ok" ? DialogResult.OK : DialogResult.Cancel, - MessageBoxButtons.AbortRetryIgnore => DialogResult.Ignore, - MessageBoxButtons.YesNoCancel => result.Label switch - { - "Yes" => DialogResult.Yes, - "No" => DialogResult.No, - _ => DialogResult.Cancel - }, - MessageBoxButtons.YesNo => result.Label switch - { - "Yes" => DialogResult.Yes, - _ => DialogResult.No - }, - MessageBoxButtons.RetryCancel => result.Label switch - { - "Retry" => DialogResult.Retry, - _ => DialogResult.Cancel - }, - MessageBoxButtons.CancelTryContinue => DialogResult.Cancel, - _ => throw new ArgumentOutOfRangeException(nameof(buttons), buttons, null) - }; - } - - - public static void UnloadAndRestart() - { - MySessionLoader.Unload(); - MySandboxGame.Config.ControllerDefaultOnStart = MyInput.Static.IsJoystickLastUsed; - MySandboxGame.Config.Save(); - MyScreenManager.CloseAllScreensNowExcept(null); - MyPlugins.Unload(); - LogManager.Flush(); - Restart(); - } - - public static void Restart() - { - Process.Start("explorer.exe", "steam://rungameid/244850"); - Process.GetCurrentProcess().Kill(); - } - - public static void ExecuteMain(SEPMPlugin plugin) - { - var name = plugin.GetType().ToString(); - plugin.Main(new(name), new()); - } - - public static string GetHash1(string file) - { - using (var sha = new SHA1Managed()) - { - return GetHash(file, sha); - } - } - - public static string GetHash256(string file) - { - using (var sha = new SHA256CryptoServiceProvider()) - { - return GetHash(file, sha); - } - } - - public static string GetHash(string file, HashAlgorithm hash) - { - using (var fileStream = new FileStream(file, FileMode.Open)) - { - using (var bufferedStream = new BufferedStream(fileStream)) - { - var data = hash.ComputeHash(bufferedStream); - var sb = new StringBuilder(2 * data.Length); - foreach (var b in data) - sb.AppendFormat("{0:x2}", b); - return sb.ToString(); - } - } - } - - - public static void OpenFileDialog(string title, string directory, string filter, Action onOk) - { - var t = new Thread(() => OpenFileDialogThread(title, directory, filter, onOk)); - t.SetApartmentState(ApartmentState.STA); - t.Start(); - } - - private static void OpenFileDialogThread(string title, string directory, string filter, Action onOk) - { - try - { - // Get the file path via prompt - using (var openFileDialog = new OpenFileDialog()) - { - if (Directory.Exists(directory)) - openFileDialog.InitialDirectory = directory; - openFileDialog.Title = title; - openFileDialog.Filter = filter; - openFileDialog.RestoreDirectory = true; - - if (openFileDialog.ShowDialog() == DialogResult.OK) - // Move back to the main thread so that we can interact with keen code again - MySandboxGame.Static.Invoke( - () => onOk(openFileDialog.FileName), - "PluginLoader"); - } - } - catch (Exception e) - { - LogFile.Log.Error(e, "Error while opening file dialog"); - } - } - - public static void OpenFolderDialog(string title, string directory, Action onOk) - { - var t = new Thread(() => OpenFolderDialogThread(title, directory, onOk)); - t.SetApartmentState(ApartmentState.STA); - t.Start(); - } - - private static void OpenFolderDialogThread(string title, string directory, Action onOk) - { - try - { - // Get the file path via prompt - using (var openFileDialog = new FolderBrowserDialog()) - { - if (Directory.Exists(directory)) - openFileDialog.SelectedPath = directory; - openFileDialog.Description = title; - - if (openFileDialog.ShowDialog() == DialogResult.OK) - // Move back to the main thread so that we can interact with keen code again - MySandboxGame.Static.Invoke( - () => onOk(openFileDialog.SelectedPath), - "PluginLoader"); - } - } - catch (Exception e) - { - LogFile.Log.Error(e, "Error while opening file dialog"); - } - } -} \ No newline at end of file diff --git a/PluginLoader/LogFile.cs b/PluginLoader/LogFile.cs deleted file mode 100644 index 662b0c9..0000000 --- a/PluginLoader/LogFile.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Runtime.CompilerServices; -using NLog; -using NLog.Layouts; -using NLog.Targets; -using NLog.Targets.Wrappers; - -namespace PluginLoader; - -public static class LogFile -{ - private const string FileName = "loader.log"; - private const string LoggerName = "PluginLoader"; - - public static readonly Logger Log = LogManager.GetLogger(LoggerName); - - public static void Init(string mainPath) - { - RuntimeHelpers.RunClassConstructor( - Type.GetType("GameAnalyticsSDK.Net.Logging.GALogger, GameAnalytics.Mono", true)!.TypeHandle); - - var target = new AsyncTargetWrapper(new FileTarget - { - Name = "pluginLog", - Layout = Layout.FromString("${longdate:universaltime=true} ${level} ${message:withexception=true}"), - FileName = Layout.FromString(Path.Combine(mainPath, FileName)), - FileNameKind = FilePathKind.Absolute, - EnableFileDelete = true, - DeleteOldFileOnStartup = true, - }); - LogManager.Configuration.AddTarget(target); - LogManager.Configuration.LoggingRules.Insert(0, new(LoggerName, LogLevel.Trace, target) - { - FinalMinLevel = LogLevel.Info - }); - LogManager.ReconfigExistingLoggers(); - } -} \ No newline at end of file diff --git a/PluginLoader/Main.cs b/PluginLoader/Main.cs deleted file mode 100644 index bf27af7..0000000 --- a/PluginLoader/Main.cs +++ /dev/null @@ -1,254 +0,0 @@ -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 -{ - public static Main Instance; - - private readonly List plugins = new(); - - private bool init; - - public Main() - { - var sw = Stopwatch.StartNew(); - - RunSplash(); - - Instance = this; - - var temp = Cursor.Current; - Cursor.Current = Cursors.AppStarting; - - var pluginsDir = LoaderTools.PluginsDir; - - LogFile.Init(Directory.CreateDirectory(pluginsDir).FullName); - LogFile.Log.Debug("Starting - v{Version}", 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.Log.Warn(e, "An error occurred while setting up networking, web requests will probably fail"); - } - - 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.Log.Debug("Patching"); - - new Harmony("avaness.PluginLoader").PatchAll(Assembly.GetExecutingAssembly()); - - Splash?.SetText("Instantiating plugins..."); - LogFile.Log.Debug("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.Log.Debug("Finished startup. Took {Time}ms", sw.ElapsedMilliseconds); - - Cursor.Current = temp; - } - - public PluginList List { get; } - public PluginConfig Config { get; } - public SplashScreen? Splash { get; set; } - - /// - /// 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.Log.Debug("Initializing {PluginsCount} plugins", plugins.Count); - 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; - 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 RunSplash() - { - var resetEvent = new ManualResetEventSlim(); - var thread = new Thread(() => - { - Application.EnableVisualStyles(); - Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); - Splash = new(); - resetEvent.Set(); - Task.Run(() => - { - Sandbox.MySandboxGame.m_windowCreatedEvent.WaitOne(); - Splash.Invoke(() => Splash.Delete()); - }); - Application.Run(Splash); - }); - - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - resetEvent.Wait(); - } - - private void ReportEnabledPlugins() - { - if (!PlayerConsent.ConsentGiven) - return; - - Splash?.SetText("Reporting plugin usage..."); - LogFile.Log.Debug("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.Log.Debug("List of enabled plugins has been sent to the statistics server"); - else - LogFile.Log.Debug("Failed to send the list of enabled plugins to the statistics server"); - } - - public void RegisterComponents() - { - LogFile.Log.Debug("Registering {PluginsCount} components", plugins.Count); - foreach (var plugin in plugins) - plugin.RegisterSession(MySession.Static); - } - - public void DisablePlugins() - { - Config.Disable(); - plugins.Clear(); - LogFile.Log.Debug("Disabled all plugins"); - } - - public void InstantiatePlugins() - { - LogFile.Log.Debug($"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(); - var requestedName = new AssemblyName(args.Name); - switch (requestedName.Name) - { - case "0Harmony": - { - if (assembly != null) - LogFile.Log.Debug("Resolving 0Harmony for {AssemblyName}", assembly); - else - LogFile.Log.Debug("Resolving 0Harmony"); - return typeof(Harmony).Assembly; - } - case "SEPluginManager": - { - if (assembly != null) - LogFile.Log.Debug("Resolving SEPluginManager for {AssemblyName}", assembly); - else - LogFile.Log.Debug("Resolving SEPluginManager"); - return typeof(SEPMPlugin).Assembly; - } - default: - return null; - } - } -} \ No newline at end of file diff --git a/PluginLoader/Network/GitHub.cs b/PluginLoader/Network/GitHub.cs deleted file mode 100644 index 226ed97..0000000 --- a/PluginLoader/Network/GitHub.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace PluginLoader.Network; - -public static class GitHub -{ - public const string listRepoName = "sepluginloader/PluginHub"; - public const string listRepoCommit = "main"; - public const string listRepoHash = "plugins.sha1"; - - private const string repoZipUrl = "https://github.com/{0}/archive/{1}.zip"; - private const string rawUrl = "https://raw.githubusercontent.com/{0}/{1}/"; - - private static readonly HttpClient Client = new(); - - public static Stream DownloadRepo(string name, string commit, out string? fileName) - { - var uri = new Uri(string.Format(repoZipUrl, name, commit), UriKind.Absolute); - LogFile.Log.Debug("Downloading {Uri}", uri); - using var response = Client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).Result; - - fileName = response.Content.Headers.ContentDisposition?.FileName; - - using var stream = response.Content.ReadAsStream(); - var mem = new MemoryStream(); - stream.CopyTo(mem); - mem.Position = 0; - return mem; - } - - public static Stream DownloadFile(string name, string commit, string path) - { - var uri = new Uri(string.Format(rawUrl, name, commit) + path.TrimStart('/'), UriKind.Absolute); - LogFile.Log.Debug("Downloading {Uri}", uri); - return Client.GetStreamAsync(uri).Result; - } -} \ No newline at end of file diff --git a/PluginLoader/Patch/Patch_ComponentRegistered.cs b/PluginLoader/Patch/Patch_ComponentRegistered.cs deleted file mode 100644 index 8a94e69..0000000 --- a/PluginLoader/Patch/Patch_ComponentRegistered.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using HarmonyLib; -using Sandbox.Game.World; -using VRage.Game; -using VRage.Plugins; - -namespace PluginLoader.Patch; - -[HarmonyPatch(typeof(MySession), "RegisterComponentsFromAssembly")] -[HarmonyPatch(new[] { typeof(Assembly), typeof(bool), typeof(MyModContext) })] -public static class Patch_ComponentRegistered -{ - public static void Prefix(Assembly assembly) - { - if (assembly == MyPlugins.GameAssembly) - Main.Instance?.RegisterComponents(); - } -} \ No newline at end of file diff --git a/PluginLoader/Patch/Patch_CreateMenu.cs b/PluginLoader/Patch/Patch_CreateMenu.cs deleted file mode 100644 index 2d218f0..0000000 --- a/PluginLoader/Patch/Patch_CreateMenu.cs +++ /dev/null @@ -1,60 +0,0 @@ -using HarmonyLib; -using PluginLoader.GUI; -using Sandbox.Graphics.GUI; -using SpaceEngineers.Game.GUI; -using VRage.Game; -using VRage.Utils; -using VRageMath; - -// ReSharper disable InconsistentNaming - -namespace PluginLoader.Patch; - -[HarmonyPatch(typeof(MyGuiScreenMainMenu), "CreateMainMenu")] -public static class Patch_CreateMainMenu -{ - public static void Postfix(MyGuiScreenMainMenu __instance, Vector2 leftButtonPositionOrigin, - ref Vector2 lastButtonPosition) - { - MyGuiControlButton lastBtn = null; - foreach (var control in __instance.Controls) - if (control is MyGuiControlButton btn && btn.Position == lastButtonPosition) - { - lastBtn = btn; - break; - } - - Vector2 position; - if (lastBtn == null) - { - position = lastButtonPosition + MyGuiConstants.MENU_BUTTONS_POSITION_DELTA; - } - else - { - position = lastBtn.Position; - lastBtn.Position = lastButtonPosition + MyGuiConstants.MENU_BUTTONS_POSITION_DELTA; - } - - var openBtn = new MyGuiControlButton(position, MyGuiControlButtonStyleEnum.StripeLeft, - originAlign: MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM, - text: new("Plugins"), - onButtonClick: _ => MyGuiScreenPluginConfig.OpenMenu()) - { - BorderEnabled = false, - BorderSize = 0, - BorderHighlightEnabled = false, - BorderColor = Vector4.Zero - }; - __instance.Controls.Add(openBtn); - } -} - -[HarmonyPatch(typeof(MyGuiScreenMainMenu), "CreateInGameMenu")] -public static class Patch_CreateInGameMenu -{ - public static void Postfix(MyGuiScreenMainMenu __instance, Vector2 leftButtonPositionOrigin, - ref Vector2 lastButtonPosition) - { - Patch_CreateMainMenu.Postfix(__instance, leftButtonPositionOrigin, ref lastButtonPosition); - } -} \ No newline at end of file diff --git a/PluginLoader/Patch/Patch_DisableConfig.cs b/PluginLoader/Patch/Patch_DisableConfig.cs deleted file mode 100644 index 70f0c63..0000000 --- a/PluginLoader/Patch/Patch_DisableConfig.cs +++ /dev/null @@ -1,26 +0,0 @@ -using HarmonyLib; -using Sandbox; -using VRage.Input; - -namespace PluginLoader.Patch; - -[HarmonyPatch(typeof(MySandboxGame), "LoadData")] -public static class Patch_DisableConfig -{ - public static void Postfix() - { - // This is the earliest point in which I can use MyInput.Static - if (Main.Instance == null) - return; - - var main = Main.Instance; - var config = main.Config; - if (config != null && config.Count > 0 && MyInput.Static is MyVRageInput && - MyInput.Static.IsKeyPress(MyKeys.Escape) - && LoaderTools.ShowMessageBox("Escape pressed. Start the game with all plugins disabled?", - "Plugin Loader", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) - main.DisablePlugins(); - else - main.InstantiatePlugins(); - } -} \ No newline at end of file diff --git a/PluginLoader/Patch/Patch_IngameRestart.cs b/PluginLoader/Patch/Patch_IngameRestart.cs deleted file mode 100644 index 53d21d9..0000000 --- a/PluginLoader/Patch/Patch_IngameRestart.cs +++ /dev/null @@ -1,38 +0,0 @@ -using HarmonyLib; -using Sandbox.Game.Gui; -using Sandbox.Graphics.GUI; -using VRage; -using VRage.Input; - -namespace PluginLoader.Patch; - -[HarmonyPatch(typeof(MyGuiScreenGamePlay), "ShowLoadMessageBox")] -public static class Patch_IngameRestart -{ - public static bool Prefix() - { - if (Main.Instance.HasLocal && MyInput.Static.IsAnyAltKeyPressed() && MyInput.Static.IsAnyCtrlKeyPressed()) - { - ShowRestartMenu(); - return false; - } - - return true; - } - - public static void ShowRestartMenu() - { - var box = MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.YES_NO, - new("Plugin Loader: Are you sure you want to restart the game?"), - MyTexts.Get(MyCommonTexts.MessageBoxCaptionPleaseConfirm), - callback: OnMessageClosed); - box.SkipTransition = true; - box.CloseBeforeCallback = true; - MyGuiSandbox.AddScreen(box); - } - - private static void OnMessageClosed(MyGuiScreenMessageBox.ResultEnum result) - { - if (result == MyGuiScreenMessageBox.ResultEnum.YES) LoaderTools.UnloadAndRestart(); - } -} \ No newline at end of file diff --git a/PluginLoader/Patch/Patch_MyDefinitionManager.cs b/PluginLoader/Patch/Patch_MyDefinitionManager.cs deleted file mode 100644 index 92bc50c..0000000 --- a/PluginLoader/Patch/Patch_MyDefinitionManager.cs +++ /dev/null @@ -1,37 +0,0 @@ -using HarmonyLib; -using PluginLoader.Data; -using Sandbox.Definitions; -using VRage.Game; - -namespace PluginLoader.Patch; - -[HarmonyPatch(typeof(MyDefinitionManager), "LoadData")] -public static class Patch_MyDefinitionManager -{ - public static void Prefix(ref List mods) - { - try - { - var currentMods = new HashSet(mods.Select(x => x.PublishedFileId)); - var newMods = new List(mods); - - var list = Main.Instance.List; - foreach (var id in Main.Instance.Config.EnabledPlugins) - { - var data = list[id]; - if (data is ModPlugin mod && !currentMods.Contains(mod.WorkshopId) && mod.Exists) - { - LogFile.Log.Debug("Loading client mod definitions for " + mod.WorkshopId); - newMods.Add(mod.GetModItem()); - } - } - - mods = newMods; - } - catch (Exception e) - { - LogFile.Log.Debug("An error occured while loading client mods: " + e); - throw; - } - } -} \ No newline at end of file diff --git a/PluginLoader/Patch/Patch_MyScriptManager.cs b/PluginLoader/Patch/Patch_MyScriptManager.cs deleted file mode 100644 index 0b2bfa4..0000000 --- a/PluginLoader/Patch/Patch_MyScriptManager.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Reflection; -using HarmonyLib; -using PluginLoader.Data; -using Sandbox.Game.World; -using VRage.Game; - -namespace PluginLoader.Patch; - -[HarmonyPatch(typeof(MyScriptManager), "LoadData")] -public static class Patch_MyScripManager -{ - private static readonly Action loadScripts; - - static Patch_MyScripManager() - { - loadScripts = (Action)Delegate.CreateDelegate( - typeof(Action), - typeof(MyScriptManager).GetMethod("LoadScripts", BindingFlags.Instance | BindingFlags.NonPublic)); - } - - public static void Postfix(MyScriptManager __instance) - { - try - { - HashSet currentMods; - if (MySession.Static.Mods != null) - currentMods = new(MySession.Static.Mods.Select(x => x.PublishedFileId)); - else - currentMods = new(); - - var list = Main.Instance.List; - foreach (var id in Main.Instance.Config.EnabledPlugins) - { - var data = list[id]; - if (data is ModPlugin mod && !currentMods.Contains(mod.WorkshopId) && mod.Exists) - { - LogFile.Log.Debug("Loading client mod scripts for " + mod.WorkshopId); - loadScripts(__instance, mod.ModLocation, mod.GetModContext()); - } - } - } - catch (Exception e) - { - LogFile.Log.Debug("An error occured while loading client mods: " + e); - throw; - } - } -} \ No newline at end of file diff --git a/PluginLoader/PluginConfig.cs b/PluginLoader/PluginConfig.cs deleted file mode 100644 index c6ed64d..0000000 --- a/PluginLoader/PluginConfig.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System.Text; -using System.Xml.Serialization; -using PluginLoader.Data; - -namespace PluginLoader; - -public class PluginConfig -{ - private const string fileName = "config.xml"; - - [XmlIgnore] public readonly Dictionary ProfileMap = new(); - - private string filePath; - - private int networkTimeout = 5000; - - [XmlArray] - [XmlArrayItem("Id")] - public string[] Plugins - { - get => EnabledPlugins.ToArray(); - set => EnabledPlugins = new(value); - } - - [XmlIgnore] public HashSet EnabledPlugins { get; private set; } = new(); - - [XmlArray] - [XmlArrayItem("Plugin")] - public LocalFolderPlugin.Config[] LocalFolderPlugins - { - get => PluginFolders.Values.ToArray(); - set { PluginFolders = value.ToDictionary(x => x.Folder); } - } - - [XmlIgnore] public Dictionary PluginFolders { get; private set; } = new(); - - [XmlArray] - [XmlArrayItem("Profile")] - public Profile[] Profiles - { - get => ProfileMap.Values.ToArray(); - set - { - ProfileMap.Clear(); - foreach (var profile in value) - ProfileMap[profile.Key] = profile; - } - } - - public string ListHash { get; set; } - - // Base URL for the statistics server, change to http://localhost:5000 in config.xml for local development - // ReSharper disable once UnassignedGetOnlyAutoProperty - public string StatsServerBaseUrl { get; } - - // User consent to use the StatsServer - public bool DataHandlingConsent { get; set; } - public string DataHandlingConsentDate { get; set; } - - public int Count => EnabledPlugins.Count; - - public void Init(PluginList plugins) - { - // Remove plugins from config that no longer exist - var toRemove = new List(); - - var sb = new StringBuilder("Enabled plugins: "); - var localPlugins = new StringBuilder("Local plugins: "); - foreach (var id in EnabledPlugins) - { - if (!plugins.TryGetPlugin(id, out var plugin)) - { - LogFile.Log.Debug($"{id} was in the config but is no longer available", false); - toRemove.Add(id); - } - else if (!plugin.IsLocal) - { - sb.Append(id).Append(", "); - } - else - { - localPlugins.Append(id).Append(", "); - } - } - - - if (EnabledPlugins.Count > 0) - sb.Length -= 2; - else - sb.Append("None"); - LogFile.Log.Debug(sb.ToString()); - - if (localPlugins.Length > 15) - localPlugins.Length -= 2; - else - localPlugins.Append("None"); - LogFile.Log.Debug(localPlugins.ToString(), false); - - foreach (var id in toRemove) - EnabledPlugins.Remove(id); - - if (toRemove.Count > 0) - Save(); - } - - public void Disable() - { - EnabledPlugins.Clear(); - } - - - public void Save() - { - try - { - LogFile.Log.Debug("Saving config"); - var serializer = new XmlSerializer(typeof(PluginConfig)); - if (File.Exists(filePath)) - File.Delete(filePath); - var fs = File.OpenWrite(filePath); - serializer.Serialize(fs, this); - fs.Flush(); - fs.Close(); - } - catch (Exception e) - { - LogFile.Log.Debug("An error occurred while saving plugin config: " + e); - } - } - - public static PluginConfig Load(string mainDirectory) - { - var path = Path.Combine(mainDirectory, fileName); - if (File.Exists(path)) - try - { - var serializer = new XmlSerializer(typeof(PluginConfig)); - var fs = File.OpenRead(path); - var config = (PluginConfig)serializer.Deserialize(fs); - fs.Close(); - config.filePath = path; - return config; - } - catch (Exception e) - { - LogFile.Log.Debug("An error occurred while loading plugin config: " + e); - } - - return new() - { - filePath = path - }; - } - - public IEnumerator GetEnumerator() - { - return EnabledPlugins.GetEnumerator(); - } - - public bool IsEnabled(string id) - { - return EnabledPlugins.Contains(id); - } - - public void SetEnabled(string id, bool enabled) - { - if (EnabledPlugins.Contains(id) == enabled) - return; - - if (enabled) - { - EnabledPlugins.Add(id); - Main.Instance.List.SubscribeToItem(id); - } - else - { - EnabledPlugins.Remove(id); - } - } -} \ No newline at end of file diff --git a/PluginLoader/PluginInstance.cs b/PluginLoader/PluginInstance.cs deleted file mode 100644 index c839feb..0000000 --- a/PluginLoader/PluginInstance.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Reflection; -using HarmonyLib; -using PluginLoader.Data; -using Sandbox.Game.World; -using VRage.Game.Components; -using VRage.Plugins; -using SEPMPlugin = PluginLoader.SEPM.SEPMPlugin; - -namespace PluginLoader; - -public class PluginInstance -{ - private readonly PluginData data; - private readonly Assembly? mainAssembly; - private readonly Type mainType; - private IHandleInputPlugin inputPlugin; - private MethodInfo openConfigDialog; - private IPlugin plugin; - - private PluginInstance(PluginData data, Assembly? mainAssembly, Type mainType) - { - this.data = data; - this.mainAssembly = mainAssembly; - this.mainType = mainType; - } - - public string Id => data.Id; - public bool HasConfigDialog => openConfigDialog != null; - - public bool Instantiate() - { - try - { - plugin = (IPlugin)Activator.CreateInstance(mainType); - inputPlugin = plugin as IHandleInputPlugin; - } - catch (Exception e) - { - ThrowError($"Failed to instantiate {data} because of an error: {e}"); - return false; - } - - try - { - openConfigDialog = AccessTools.DeclaredMethod(mainType, "OpenConfigDialog", Array.Empty()); - } - catch (Exception e) - { - LogFile.Log.Error(e, $"Unable to find OpenConfigDialog() in {data} due to an error"); - openConfigDialog = null; - } - - return true; - } - - public void OpenConfig() - { - if (plugin == null || openConfigDialog == null) - return; - - try - { - openConfigDialog.Invoke(plugin, Array.Empty()); - } - catch (Exception e) - { - ThrowError($"Failed to open plugin config for {data} because of an error: {e}"); - } - } - - public bool Init(object gameInstance) - { - if (plugin == null) - return false; - - try - { - if (plugin is SEPMPlugin sepm) - LoaderTools.ExecuteMain(sepm); - plugin.Init(gameInstance); - return true; - } - catch (Exception e) - { - ThrowError($"Failed to initialize {data} because of an error: {e}"); - return false; - } - } - - public void RegisterSession(MySession session) - { - if (plugin != null) - try - { - var descType = typeof(MySessionComponentDescriptor); - var count = 0; - foreach (var t in mainAssembly.GetTypes().Where(t => Attribute.IsDefined(t, descType))) - { - var comp = (MySessionComponentBase)Activator.CreateInstance(t); - session.RegisterComponent(comp, comp.UpdateOrder, comp.Priority); - count++; - } - - if (count > 0) - LogFile.Log.Debug($"Registered {count} session components from: {mainAssembly.FullName}", !data.IsLocal); - } - catch (Exception e) - { - ThrowError($"Failed to register {data} because of an error: {e}"); - } - } - - public bool Update() - { - if (plugin == null) - return false; - - plugin.Update(); - return true; - } - - public bool HandleInput() - { - if (plugin == null) - return false; - - inputPlugin?.HandleInput(); - return true; - } - - public void Dispose() - { - if (plugin != null) - try - { - plugin.Dispose(); - plugin = null; - inputPlugin = null; - } - catch (Exception e) - { - data.Status = PluginStatus.Error; - LogFile.Log.Error(e, $"Failed to dispose {data} because of an error"); - } - } - - private void ThrowError(string error) - { - LogFile.Log.Debug(error); - data.Error(); - Dispose(); - } - - public static bool TryGet(PluginData data, out PluginInstance instance) - { - instance = null; - if (data.Status == PluginStatus.Error || !data.TryLoadAssembly(out var a)) - return false; - - var pluginType = a.GetTypes().FirstOrDefault(t => t.IsAssignableTo(typeof(IPlugin))); - if (pluginType == null) - { - LogFile.Log.Warn($"Failed to load {data} because it does not contain an IPlugin"); - data.Error(); - return false; - } - - instance = new(data, a, pluginType); - return true; - } - - public override string ToString() - { - return data.ToString(); - } -} \ No newline at end of file diff --git a/PluginLoader/PluginList.cs b/PluginLoader/PluginList.cs deleted file mode 100644 index 539844c..0000000 --- a/PluginLoader/PluginList.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.IO.Compression; -using System.Xml.Serialization; -using PluginLoader.Data; -using PluginLoader.Network; -using ProtoBuf; - -namespace PluginLoader; - -public class PluginList : IEnumerable -{ - private Dictionary plugins = new(); - - public PluginList(string mainDirectory, PluginConfig config) - { - var lbl = Main.Instance.Splash; - - lbl.SetText("Downloading plugin list..."); - DownloadList(mainDirectory, config); - - if (plugins.Count == 0) - { - LogFile.Log.Warn("WARNING: No plugins in the plugin list. Plugin list will contain local plugins only."); - HasError = true; - } - - FindWorkshopPlugins(config); - FindLocalPlugins(config, mainDirectory); - LogFile.Log.Debug($"Found {plugins.Count} plugins"); - FindPluginGroups(); - FindModDependencies(); - } - - public int Count => plugins.Count; - - public bool HasError { get; } - - public PluginData this[string key] - { - get => plugins[key]; - set => plugins[key] = value; - } - - - public IEnumerator GetEnumerator() - { - return plugins.Values.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return plugins.Values.GetEnumerator(); - } - - public bool Contains(string id) - { - return plugins.ContainsKey(id); - } - - public bool TryGetPlugin(string id, out PluginData pluginData) - { - return plugins.TryGetValue(id, out pluginData); - } - - /// - /// Ensures the user is subscribed to the steam plugin. - /// - public void SubscribeToItem(string id) - { - if (plugins.TryGetValue(id, out var data) && data is ISteamItem steam) - SteamAPI.SubscribeToItem(steam.WorkshopId); - } - - public bool Remove(string id) - { - return plugins.Remove(id); - } - - public void Add(PluginData data) - { - plugins[data.Id] = data; - } - - private void FindPluginGroups() - { - var groups = 0; - foreach (var group in plugins.Values.Where(x => !string.IsNullOrWhiteSpace(x.GroupId)).GroupBy(x => x.GroupId)) - { - groups++; - foreach (var data in group) - data.Group.AddRange(group.Where(x => x != data)); - } - - if (groups > 0) - LogFile.Log.Debug($"Found {groups} plugin groups"); - } - - private void FindModDependencies() - { - foreach (var data in plugins.Values) - if (data is ModPlugin mod) - FindModDependencies(mod); - } - - private void FindModDependencies(ModPlugin mod) - { - if (mod.DependencyIds == null) - return; - - var dependencies = new Dictionary(); - dependencies.Add(mod.WorkshopId, mod); - var toProcess = new Stack(); - toProcess.Push(mod); - - while (toProcess.Count > 0) - { - var temp = toProcess.Pop(); - - if (temp.DependencyIds == null) - continue; - - foreach (var id in temp.DependencyIds) - if (!dependencies.ContainsKey(id) && plugins.TryGetValue(id.ToString(), out var data) && - data is ModPlugin dependency) - { - toProcess.Push(dependency); - dependencies[id] = dependency; - } - } - - dependencies.Remove(mod.WorkshopId); - mod.Dependencies = dependencies.Values.ToArray(); - } - - private void DownloadList(string mainDirectory, PluginConfig config) - { - var whitelist = Path.Combine(mainDirectory, "whitelist.bin"); - - PluginData[] list; - var currentHash = config.ListHash; - string newHash; - if (!TryDownloadWhitelistHash(out newHash)) - { - // No connection to plugin hub, read from cache - if (!TryReadWhitelistFile(whitelist, out list)) - return; - } - else if (currentHash == null || currentHash != newHash) - { - // Plugin list changed, try downloading new version first - if (!TryDownloadWhitelistFile(whitelist, newHash, config, out list) - && !TryReadWhitelistFile(whitelist, out list)) - return; - } - else - { - // Plugin list did not change, try reading the current version first - if (!TryReadWhitelistFile(whitelist, out list) - && !TryDownloadWhitelistFile(whitelist, newHash, config, out list)) - return; - } - - if (list != null) - plugins = list.ToDictionary(x => x.Id); - } - - private bool TryReadWhitelistFile(string file, [NotNullWhen(true)] out PluginData[]? list) - { - list = null; - - if (File.Exists(file) && new FileInfo(file).Length > 0) - { - LogFile.Log.Debug("Reading whitelist from cache"); - try - { - using (Stream binFile = File.OpenRead(file)) - { - list = Serializer.Deserialize(binFile); - } - - LogFile.Log.Debug("Whitelist retrieved from disk"); - return true; - } - catch (Exception e) - { - LogFile.Log.Warn(e, "Error while reading whitelist"); - } - } - else - { - LogFile.Log.Debug("No whitelist cache exists"); - } - - return false; - } - - private bool TryDownloadWhitelistFile(string file, string hash, PluginConfig config, out PluginData[] list) - { - var newPlugins = new Dictionary(); - - try - { - using (var zipFileStream = GitHub.DownloadRepo(GitHub.listRepoName, GitHub.listRepoCommit, out _)) - using (var zipFile = new ZipArchive(zipFileStream)) - { - var xml = new XmlSerializer(typeof(PluginData)); - foreach (var entry in zipFile.Entries) - { - if (!entry.FullName.EndsWith("xml", StringComparison.OrdinalIgnoreCase)) - continue; - - using var entryStream = entry.Open(); - - try - { - var data = (PluginData?)xml.Deserialize(entryStream) ?? throw new InvalidOperationException($"Deserialized data is null for {entry.FullName}"); - newPlugins[data.Id] = data; - } - catch (InvalidOperationException e) - { - LogFile.Log.Error(e, "An error occurred while reading the plugin xml"); - } - } - } - - list = newPlugins.Values.ToArray(); - return TrySaveWhitelist(file, list, hash, config); - } - catch (Exception e) - { - LogFile.Log.Error(e, "Error while downloading whitelist"); - throw; - } - } - - private bool TrySaveWhitelist(string file, PluginData[] list, string hash, PluginConfig config) - { - try - { - LogFile.Log.Debug("Saving whitelist to disk"); - using (var binFile = File.Create(file)) - { - Serializer.Serialize(binFile, list); - } - - config.ListHash = hash; - config.Save(); - - LogFile.Log.Debug("Whitelist updated"); - return true; - } - catch (Exception e) - { - LogFile.Log.Error(e, "Error while saving whitelist"); - try - { - File.Delete(file); - } - catch - { - } - - return false; - } - } - - private bool TryDownloadWhitelistHash(out string hash) - { - hash = null; - try - { - using (var hashStream = - GitHub.DownloadFile(GitHub.listRepoName, GitHub.listRepoCommit, GitHub.listRepoHash)) - using (var hashStreamReader = new StreamReader(hashStream)) - { - hash = hashStreamReader.ReadToEnd().Trim(); - } - - return true; - } - catch (Exception e) - { - LogFile.Log.Debug("Error while downloading whitelist hash: " + e); - return false; - } - } - - private void FindLocalPlugins(PluginConfig config, string mainDirectory) - { - foreach (var dll in Directory.EnumerateFiles(mainDirectory, "*.dll", SearchOption.AllDirectories)) - if (!dll.Contains(Path.DirectorySeparatorChar + "GitHub" + Path.DirectorySeparatorChar, - StringComparison.OrdinalIgnoreCase)) - { - var local = new LocalPlugin(dll); - var name = local.FriendlyName; - if (!name.StartsWith("0Harmony") && !name.StartsWith("Microsoft")) - plugins[local.Id] = local; - } - - foreach (var folderConfig in config.PluginFolders.Values) - if (folderConfig.Valid) - { - var local = new LocalFolderPlugin(folderConfig); - plugins[local.Id] = local; - } - } - - private void FindWorkshopPlugins(PluginConfig config) - { - var steamPlugins = new List(plugins.Values.Select(x => x as ISteamItem).Where(x => x != null)); - - Main.Instance.Splash?.SetText("Updating workshop items..."); - - SteamAPI.Update(steamPlugins.Where(x => config.IsEnabled(x.Id)).Select(x => x.WorkshopId)); - - var workshop = Path.GetFullPath(@"..\..\..\workshop\content\244850\"); - foreach (var steam in steamPlugins) - try - { - var path = Path.Combine(workshop, steam.Id); - if (Directory.Exists(path)) - { - if (steam is SteamPlugin plugin && TryGetPlugin(path, out string dllFile)) - plugin.Init(dllFile); - } - else if (config.IsEnabled(steam.Id)) - { - ((PluginData)steam).Status = PluginStatus.Error; - LogFile.Log.Debug($"The plugin '{steam}' is missing and cannot be loaded."); - } - } - catch (Exception e) - { - LogFile.Log.Debug($"An error occurred while searching for the workshop plugin {steam}: {e}"); - } - } - - private bool TryGetPlugin(string modRoot, out string pluginFile) - { - foreach (var file in Directory.EnumerateFiles(modRoot, "*.plugin")) - { - var name = Path.GetFileName(file); - if (!name.StartsWith("0Harmony", StringComparison.OrdinalIgnoreCase)) - { - pluginFile = file; - return true; - } - } - - var sepm = Path.Combine(modRoot, "Data", "sepm-plugin.zip"); - if (File.Exists(sepm)) - { - pluginFile = sepm; - return true; - } - - pluginFile = null; - return false; - } -} \ No newline at end of file diff --git a/PluginLoader/PluginLoader.csproj b/PluginLoader/PluginLoader.csproj deleted file mode 100644 index 77c4e3b..0000000 --- a/PluginLoader/PluginLoader.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - net8.0-windows10.0.19041.0 - enable - true - true - true - true - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - PreserveNewest - PreserveNewest - - - - \ No newline at end of file diff --git a/PluginLoader/Profile.cs b/PluginLoader/Profile.cs deleted file mode 100644 index fde8777..0000000 --- a/PluginLoader/Profile.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace PluginLoader; - -public class Profile -{ - public Profile() - { - } - - public Profile(string name, string[] plugins) - { - Key = Guid.NewGuid().ToString(); - Name = name; - Plugins = plugins; - } - - // Unique key of the profile - public string Key { get; set; } - - // Name of the profile - public string Name { get; set; } - - // Plugin IDs - public string[] Plugins { get; set; } -} \ No newline at end of file diff --git a/PluginLoader/SEPM/Logger.cs b/PluginLoader/SEPM/Logger.cs deleted file mode 100644 index 0f7bdf5..0000000 --- a/PluginLoader/SEPM/Logger.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PluginLoader.SEPM; - -public class Logger -{ - public void Log(string text) - { - } -} \ No newline at end of file diff --git a/PluginLoader/SEPM/SEPMPlugin.cs b/PluginLoader/SEPM/SEPMPlugin.cs deleted file mode 100644 index f5d1c72..0000000 --- a/PluginLoader/SEPM/SEPMPlugin.cs +++ /dev/null @@ -1,9 +0,0 @@ -using HarmonyLib; -using VRage.Plugins; - -namespace PluginLoader.SEPM; - -public interface SEPMPlugin : IPlugin -{ - void Main(Harmony harmony, Logger log); -} \ No newline at end of file diff --git a/PluginLoader/Stats/Model/ConsentRequest.cs b/PluginLoader/Stats/Model/ConsentRequest.cs deleted file mode 100644 index 63b088d..0000000 --- a/PluginLoader/Stats/Model/ConsentRequest.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ReSharper disable UnusedAutoPropertyAccessor.Global - -namespace PluginLoader.Stats.Model; - -// Request data received from the Plugin Loader to store user consent or withdrawal, -// this request is NOT sent if the user does not give consent in the first place -public class ConsentRequest -{ - // Hash of the player's Steam ID - public string PlayerHash { get; set; } - - // True if the consent has just given, false if has just withdrawn - public bool Consent { get; set; } -} \ No newline at end of file diff --git a/PluginLoader/Stats/Model/PluginStat.cs b/PluginLoader/Stats/Model/PluginStat.cs deleted file mode 100644 index 4b8ea4d..0000000 --- a/PluginLoader/Stats/Model/PluginStat.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace PluginLoader.Stats.Model; - -// Statistics for a single plugin -public class PluginStat -{ - // Number of players who successfully started SE with this plugin enabled anytime during the past 30 days - public int Players { get; set; } - - // Total number of upvotes and downvotes since the beginning (votes do not expire) - public int Upvotes { get; set; } - public int Downvotes { get; set; } - - // Whether the requesting player tried the plugin - public bool Tried { get; set; } - - // Current vote of the requesting player - // +1: Upvoted - // 0: No vote (or cleared it) - // -1: Downvoted - public int Vote { get; set; } - - // Number of half stars [1-10] based on the upvote ratio, zero if there are not enough votes on the plugin yet - public int Rating { get; set; } -} \ No newline at end of file diff --git a/PluginLoader/Stats/Model/PluginStats.cs b/PluginLoader/Stats/Model/PluginStats.cs deleted file mode 100644 index b5fba93..0000000 --- a/PluginLoader/Stats/Model/PluginStats.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace PluginLoader.Stats.Model; - -// Statistics for all plugins -public class PluginStats -{ - // Key: pluginId - public Dictionary Stats { get; set; } = new(); - - // Token the player is required to present for voting (making it harder to spoof votes) - public string VotingToken { get; set; } -} \ No newline at end of file diff --git a/PluginLoader/Stats/Model/TrackRequest.cs b/PluginLoader/Stats/Model/TrackRequest.cs deleted file mode 100644 index 5d56aad..0000000 --- a/PluginLoader/Stats/Model/TrackRequest.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace PluginLoader.Stats.Model; - -// Request data sent to the StatsServer each time the game is started -public class TrackRequest -{ - // Hash of the player's Steam ID - // Hexdump of the first 80 bits of SHA1($"{steamId}") - // The client determines the ID of the player, never the server. - // Using a hash is required for data protection and privacy. - // Using a hash makes it impractical to track back usage or votes to - // individual players, while still allowing for near-perfect deduplication. - // It also prevents stealing all the Steam IDs from the server's database. - public string PlayerHash { get; set; } - - // Ids of enabled plugins when the game started - public string[] EnabledPluginIds { get; set; } -} \ No newline at end of file diff --git a/PluginLoader/Stats/Model/VoteRequest.cs b/PluginLoader/Stats/Model/VoteRequest.cs deleted file mode 100644 index 788c646..0000000 --- a/PluginLoader/Stats/Model/VoteRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace PluginLoader.Stats.Model; - -// Request data sent to the StatsServer each time the user changes his/her vote on a plugin -public class VoteRequest -{ - // Id of the plugin - public string PluginId { get; set; } - - // Obfuscated player identifier, see Track.PlayerHash - public string PlayerHash { get; set; } - - // Voting token returned with the plugin stats - public string VotingToken { get; set; } - - // Vote to store - // +1: Upvote - // 0: Clear vote - // -1: Downvote - public int Vote { get; set; } -} \ No newline at end of file diff --git a/PluginLoader/Stats/StatsClient.cs b/PluginLoader/Stats/StatsClient.cs deleted file mode 100644 index 0c63ee1..0000000 --- a/PluginLoader/Stats/StatsClient.cs +++ /dev/null @@ -1,101 +0,0 @@ -using PluginLoader.GUI; -using PluginLoader.Stats.Model; -using PluginLoader.Tools; - -namespace PluginLoader.Stats; - -public static class StatsClient -{ - // API address - private static string baseUri = "https://pluginstats.ferenczi.eu"; - private static string playerHash; - - // Latest voting token received - private static string votingToken; - - // API endpoints - private static string ConsentUri => $"{baseUri}/Consent"; - private static string StatsUri => $"{baseUri}/Stats"; - private static string TrackUri => $"{baseUri}/Track"; - private static string VoteUri => $"{baseUri}/Vote"; - - // Hashed Steam ID of the player - private static string PlayerHash => - playerHash ??= Tools.Tools.Sha1HexDigest($"{Tools.Tools.GetSteamId()}").Substring(0, 20); - - public static void OverrideBaseUrl(string uri) - { - if (string.IsNullOrEmpty(uri)) - return; - - baseUri = uri; - } - - public static bool Consent(bool consent) - { - if (consent) - LogFile.Log.Debug("Registering player consent on the statistics server"); - else - LogFile.Log.Debug("Withdrawing player consent, removing user data from the statistics server"); - - var consentRequest = new ConsentRequest - { - PlayerHash = PlayerHash, - Consent = consent - }; - - return SimpleHttpClient.Post(ConsentUri, consentRequest); - } - - // This function may be called from another thread. - public static PluginStats DownloadStats() - { - if (!PlayerConsent.ConsentGiven) - { - LogFile.Log.Info("Downloading plugin statistics anonymously..."); - votingToken = null; - return SimpleHttpClient.Get(StatsUri); - } - - LogFile.Log.Info("Downloading plugin statistics, ratings and votes for " + PlayerHash); - - var parameters = new Dictionary { ["playerHash"] = PlayerHash }; - var pluginStats = SimpleHttpClient.Get(StatsUri, parameters); - - votingToken = pluginStats?.VotingToken; - - return pluginStats; - } - - public static bool Track(string[] pluginIds) - { - var trackRequest = new TrackRequest - { - PlayerHash = PlayerHash, - EnabledPluginIds = pluginIds - }; - - return SimpleHttpClient.Post(TrackUri, trackRequest); - } - - public static PluginStat Vote(string pluginId, int vote) - { - if (votingToken == null) - { - LogFile.Log.Debug("Voting token is not available, cannot vote"); - return null; - } - - LogFile.Log.Debug($"Voting {vote} on plugin {pluginId}"); - var voteRequest = new VoteRequest - { - PlayerHash = PlayerHash, - PluginId = pluginId, - VotingToken = votingToken, - Vote = vote - }; - - var stat = SimpleHttpClient.Post(VoteUri, voteRequest); - return stat; - } -} \ No newline at end of file diff --git a/PluginLoader/SteamAPI.cs b/PluginLoader/SteamAPI.cs deleted file mode 100644 index 92f29ee..0000000 --- a/PluginLoader/SteamAPI.cs +++ /dev/null @@ -1,64 +0,0 @@ -using NLog; -using Steamworks; - -namespace PluginLoader; - -public static class SteamAPI -{ - public static bool IsSubscribed(ulong id) - { - var state = (EItemState)SteamUGC.GetItemState(new(id)); - return (state & EItemState.k_EItemStateSubscribed) == EItemState.k_EItemStateSubscribed; - } - - public static void SubscribeToItem(ulong id) - { - SteamUGC.SubscribeItem(new(id)); - } - - public static void Update(IEnumerable ids) - { - var enumerable = ids as ulong[] ?? ids.ToArray(); - - LogFile.Log.Info("Updating {Count} workshop items", enumerable.Length); - - try - { - UpdateInternal(enumerable); - } - catch (Exception e) - { - LogFile.Log.Error(e, "An error occurred while updating workshop items"); - throw; - } - } - - private static void UpdateInternal(IEnumerable ids) - { - var count = 0; - using var callback = Callback.Create(t => - { - if (t.m_eResult == EResult.k_EResultOK) - Interlocked.Increment(ref count); - - LogFile.Log.Log(t.m_eResult == EResult.k_EResultOK ? LogLevel.Info : LogLevel.Error, - "Download finished for {Id} with {State}", t.m_nPublishedFileId.m_PublishedFileId, t.m_eResult); - }); - - //items could have other flags besides installed - var toDownload = ids.Where(b => - ((EItemState)SteamUGC.GetItemState(new(b)) & EItemState.k_EItemStateInstalled) == 0).ToArray(); - foreach (var id in toDownload) - { - LogFile.Log.Info("Updating workshop item {Id}", id); - - SteamUGC.DownloadItem(new(id), true); - } - - while (count < toDownload.Length) - { - Steamworks.SteamAPI.RunCallbacks(); - Thread.Sleep(10); - } - } -} \ No newline at end of file diff --git a/PluginLoader/Tools/PostHttpContent.cs b/PluginLoader/Tools/PostHttpContent.cs deleted file mode 100644 index 31a3726..0000000 --- a/PluginLoader/Tools/PostHttpContent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Net; - -namespace PluginLoader.Tools; - -public class PostHttpContent : HttpContent -{ - private readonly byte[] content; - - public PostHttpContent(string content) - { - this.content = content == null ? null : Tools.Utf8.GetBytes(content); - } - - protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) - { - if (content != null && content.Length > 0) - await stream.WriteAsync(content, 0, content.Length); - } - - protected override bool TryComputeLength(out long length) - { - length = content.Length; - return true; - } -} \ No newline at end of file diff --git a/PluginLoader/Tools/SimpleHttpClient.cs b/PluginLoader/Tools/SimpleHttpClient.cs deleted file mode 100644 index a563b08..0000000 --- a/PluginLoader/Tools/SimpleHttpClient.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Net; -using System.Text; -using LitJson; - -namespace PluginLoader.Tools; - -public static class SimpleHttpClient -{ - // REST API request timeout in milliseconds - private const int TimeoutMs = 3000; - - public static TV Get(string url) - where TV : class, new() - { - try - { - using var response = (HttpWebResponse)CreateRequest(HttpMethod.Get, url).GetResponse(); - - using var responseStream = response.GetResponseStream(); - if (responseStream == null) - return null; - - using var streamReader = new StreamReader(responseStream, Encoding.UTF8); - return JsonMapper.ToObject(streamReader.ReadToEnd()); - } - catch (WebException e) - { - LogFile.Log.Info($"REST API request failed: GET {url} [{e.Message}]"); - return null; - } - } - - public static TV Get(string url, Dictionary parameters) - where TV : class, new() - { - var uriBuilder = new StringBuilder(url); - AppendQueryParameters(uriBuilder, parameters); - var uri = uriBuilder.ToString(); - - try - { - using var response = (HttpWebResponse)CreateRequest(HttpMethod.Get, uri).GetResponse(); - - using var responseStream = response.GetResponseStream(); - if (responseStream == null) - return null; - - using var streamReader = new StreamReader(responseStream, Encoding.UTF8); - return JsonMapper.ToObject(streamReader.ReadToEnd()); - } - catch (WebException e) - { - LogFile.Log.Info($"REST API request failed: GET {uri} [{e.Message}]"); - return null; - } - } - - public static TV Post(string url) - where TV : class, new() - { - try - { - var request = CreateRequest(HttpMethod.Post, url); - request.ContentLength = 0L; - return PostRequest(request); - } - catch (WebException e) - { - LogFile.Log.Info($"REST API request failed: POST {url} [{e.Message}]"); - return null; - } - } - - public static TV Post(string url, Dictionary parameters) - where TV : class, new() - { - var uriBuilder = new StringBuilder(url); - AppendQueryParameters(uriBuilder, parameters); - var uri = uriBuilder.ToString(); - - try - { - var request = CreateRequest(HttpMethod.Post, uri); - request.ContentType = "application/x-www-form-urlencoded"; - request.ContentLength = 0; - return PostRequest(request); - } - catch (WebException e) - { - LogFile.Log.Info($"REST API request failed: POST {uri} [{e.Message}]"); - return null; - } - } - - public static TV Post(string url, TR body) - where TR : class, new() - where TV : class, new() - { - try - { - var request = CreateRequest(HttpMethod.Post, url); - var requestJson = JsonMapper.ToJson(body); - var requestBytes = Encoding.UTF8.GetBytes(requestJson); - request.ContentType = "application/json"; - request.ContentLength = requestBytes.Length; - return PostRequest(request, requestBytes); - } - catch (WebException e) - { - LogFile.Log.Info($"REST API request failed: POST {url} [{e.Message}]"); - return null; - } - } - - public static bool Post(string url, TR body) - where TR : class, new() - { - try - { - var request = CreateRequest(HttpMethod.Post, url); - var requestJson = JsonMapper.ToJson(body); - var requestBytes = Encoding.UTF8.GetBytes(requestJson); - request.ContentType = "application/json"; - request.ContentLength = requestBytes.Length; - return PostRequest(request, requestBytes); - } - catch (WebException e) - { - LogFile.Log.Info($"REST API request failed: POST {url} [{e.Message}]"); - return false; - } - } - - private static TV PostRequest(HttpWebRequest request, byte[] body = null) where TV : class, new() - { - if (body != null) - { - using var requestStream = request.GetRequestStream(); - requestStream.Write(body, 0, body.Length); - requestStream.Close(); - } - - using var response = (HttpWebResponse)request.GetResponse(); - using var responseStream = response.GetResponseStream(); - if (responseStream == null) - return null; - - using var streamReader = new StreamReader(responseStream, Encoding.UTF8); - var data = JsonMapper.ToObject(streamReader.ReadToEnd()); - return data; - } - - private static bool PostRequest(HttpWebRequest request, byte[] body = null) - { - if (body != null) - { - using var requestStream = request.GetRequestStream(); - requestStream.Write(body, 0, body.Length); - requestStream.Close(); - } - - using var response = (HttpWebResponse)request.GetResponse(); - - return response.StatusCode == HttpStatusCode.OK; - } - - private static HttpWebRequest CreateRequest(HttpMethod method, string url) - { - var http = WebRequest.CreateHttp(url); - http.Method = method.ToString().ToUpper(); - http.Timeout = TimeoutMs; - return http; - } - - private static void AppendQueryParameters(StringBuilder stringBuilder, Dictionary parameters) - { - if (parameters == null || parameters.Count == 0) - return; - - var first = true; - foreach (var p in parameters) - { - stringBuilder.Append(first ? '?' : '&'); - first = false; - stringBuilder.Append(Uri.EscapeDataString(p.Key)); - stringBuilder.Append('='); - stringBuilder.Append(Uri.EscapeDataString(p.Value)); - } - } -} \ No newline at end of file diff --git a/PluginLoader/Tools/Tools.cs b/PluginLoader/Tools/Tools.cs deleted file mode 100644 index 5f0c173..0000000 --- a/PluginLoader/Tools/Tools.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using Steamworks; - -namespace PluginLoader.Tools; - -public static class Tools -{ - public static readonly UTF8Encoding Utf8 = new(); - - public static string Sha1HexDigest(string text) - { - using var sha1 = new SHA1Managed(); - var buffer = Utf8.GetBytes(text); - var digest = sha1.ComputeHash(buffer); - return BytesToHex(digest); - } - - private static string BytesToHex(IReadOnlyCollection ba) - { - var hex = new StringBuilder(2 * ba.Count); - - foreach (var t in ba) - hex.Append(t.ToString("x2")); - - return hex.ToString(); - } - - public static string FormatDateIso8601(DateTime dt) - { - return dt.ToString("s").Substring(0, 10); - } - - public static ulong GetSteamId() - { - return SteamUser.GetSteamID().m_SteamID; - } - - // FIXME: Replace this with the proper library call, I could not find one - public static string FormatUriQueryString(Dictionary parameters) - { - var query = new StringBuilder(); - foreach (var p in parameters) - { - if (query.Length > 0) - query.Append('&'); - query.Append($"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"); - } - - return query.ToString(); - } -} \ No newline at end of file diff --git a/PluginLoader/packages.lock.json b/PluginLoader/packages.lock.json deleted file mode 100644 index 048b816..0000000 --- a/PluginLoader/packages.lock.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "version": 1, - "dependencies": { - "net8.0-windows10.0.19041": { - "Krafs.Publicizer": { - "type": "Direct", - "requested": "[2.2.1, )", - "resolved": "2.2.1", - "contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q==" - }, - "Lib.Harmony.Thin": { - "type": "Direct", - "requested": "[2.3.3, )", - "resolved": "2.3.3", - "contentHash": "jsaFv7XnWJnyfyvFbkgIkZtV6tWMteNUcDK3idq+3LwPqpTFNxsOv2eKmj4qqP8QR8UynG1Y9AUaC/+dVruMHg==", - "dependencies": { - "MonoMod.Core": "1.1.0", - "System.Text.Json": "8.0.1" - } - }, - "Microsoft.CodeAnalysis.CSharp": { - "type": "Direct", - "requested": "[4.11.0, )", - "resolved": "4.11.0", - "contentHash": "6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==", - "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0]", - "System.Collections.Immutable": "8.0.0", - "System.Reflection.Metadata": "8.0.0" - } - }, - "NLog": { - "type": "Direct", - "requested": "[5.3.4, )", - "resolved": "5.3.4", - "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" - }, - "SpaceEngineersDedicated.ReferenceAssemblies": { - "type": "Direct", - "requested": "[1.*, )", - "resolved": "1.204.18", - "contentHash": "GT7/9CBMx4jjor41zLOOl87YYM/JdJD8xp9ccXyuhP2oUaz25H3ZmCQuGeAuZNENKru1a/7hZrId4PwlMDGoew==", - "dependencies": { - "SharpDX": "4.2.0-keen-cringe", - "protobuf-net": "1.0.0" - } - }, - "Steamworks.NET": { - "type": "Direct", - "requested": "[20.1.0, )", - "resolved": "20.1.0", - "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" - }, - "Microsoft.CodeAnalysis.Analyzers": { - "type": "Transitive", - "resolved": "3.3.4", - "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==" - }, - "Microsoft.CodeAnalysis.Common": { - "type": "Transitive", - "resolved": "4.11.0", - "contentHash": "djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==", - "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "System.Collections.Immutable": "8.0.0", - "System.Reflection.Metadata": "8.0.0" - } - }, - "Mono.Cecil": { - "type": "Transitive", - "resolved": "0.11.5", - "contentHash": "fxfX+0JGTZ8YQeu1MYjbBiK2CYTSzDyEeIixt+yqKKTn7FW8rv7JMY70qevup4ZJfD7Kk/VG/jDzQQTpfch87g==" - }, - "MonoMod.Backports": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "GUAjCrCZEddqHKHFA7Lh61PgTzoKY7gfBShFe0hQe0p8iynHhBK3TWGyRi+QIw/PGfaRPwx6c33CPGFURBVM6g==", - "dependencies": { - "MonoMod.ILHelpers": "1.0.1" - } - }, - "MonoMod.Core": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "Ks8RntZGVcktr2QF/AovTEbuOkrgXz6omjrvT5LRveOIQJuy+IFuEQPBVWu+cSKVIoZD5XkpRFvlVrItgPIrXw==", - "dependencies": { - "Mono.Cecil": "0.11.5", - "MonoMod.Backports": "1.1.0", - "MonoMod.ILHelpers": "1.0.1", - "MonoMod.Utils": "25.0.4" - } - }, - "MonoMod.ILHelpers": { - "type": "Transitive", - "resolved": "1.0.1", - "contentHash": "6djj/Hz+/eTomo1H/sJEJNxBz2ZdhXjvH0MOmyU2xRtbjaIfBQuyVV0zNUbJhMY/8qoWrz7WXfskfFhdaY0afA==" - }, - "MonoMod.Utils": { - "type": "Transitive", - "resolved": "25.0.4", - "contentHash": "cB94MaZtFD9u4clYEFTwM4jGXnJnzXsxYF3yBpMZKHhXOas66tMF2frbdYte023i0MH4C5iRJbDjxHmA4x5VgA==", - "dependencies": { - "Mono.Cecil": "0.11.5", - "MonoMod.Backports": "1.1.0", - "MonoMod.ILHelpers": "1.0.1" - } - }, - "protobuf-net": { - "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "kTGOK0E87473sOImOjgZOnz3kTC2aMLffoRWQLYNuBLJnwNNmjanF9IkevZ9Q7yYLeABQfcF3BpeepuMntMVNw==" - }, - "SharpDX": { - "type": "Transitive", - "resolved": "4.2.0-keen-cringe", - "contentHash": "LaJN3h1Gi1FWVdef2I5WtOH9gwzKCBniH0CragarbkN2QheYY6Lqm+91PcOfp1w/4wdVb+k8Kjv3sO393Tphtw==" - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" - }, - "System.Reflection.Metadata": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", - "dependencies": { - "System.Collections.Immutable": "8.0.0" - } - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "7AWk2za1hSEJBppe/Lg+uDcam2TrDqwIKa9XcPssSwyjC2xa39EKEGul3CO5RWNF+hMuZG4zlBDrvhBdDTg4lg==", - "dependencies": { - "System.Text.Encodings.Web": "8.0.0" - } - } - } - } -} \ No newline at end of file diff --git a/PluginLoader/splash.gif b/PluginLoader/splash.gif deleted file mode 100644 index ad6fb35..0000000 Binary files a/PluginLoader/splash.gif and /dev/null differ diff --git a/PluginLoader/steam_api64.dll b/PluginLoader/steam_api64.dll deleted file mode 100644 index 5b798af..0000000 Binary files a/PluginLoader/steam_api64.dll and /dev/null differ