From 08c6a0c26d5acdf7ba4df87b0f4ceea8d752c878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bure=C5=A1?= Date: Thu, 4 Jan 2024 03:29:20 +0100 Subject: [PATCH 1/2] Added preloader and restarter --- Directory.Build.props | 4 ++ SpaceWarp.sln | 20 ++++++++++ plugin_template/doorstop_config.ini | 2 +- src/SpaceWarp.Preloader/Directory.Build.props | 11 +++++ src/SpaceWarp.Preloader/Entrypoint.cs | 28 +++++++++++++ .../SpaceWarp.Preloader.csproj | 12 ++++++ src/SpaceWarp.Restarter/Directory.Build.props | 11 +++++ src/SpaceWarp.Restarter/Restarter.cs | 40 +++++++++++++++++++ .../SpaceWarp.Restarter.csproj | 10 +++++ src/SpaceWarp/Directory.Build.targets | 24 ++++++++++- src/SpaceWarp/SpaceWarp.csproj | 1 + src/SpaceWarpPatcher/SpaceWarpPatcher.csproj | 2 +- 12 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/SpaceWarp.Preloader/Directory.Build.props create mode 100644 src/SpaceWarp.Preloader/Entrypoint.cs create mode 100644 src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj create mode 100644 src/SpaceWarp.Restarter/Directory.Build.props create mode 100644 src/SpaceWarp.Restarter/Restarter.cs create mode 100644 src/SpaceWarp.Restarter/SpaceWarp.Restarter.csproj diff --git a/Directory.Build.props b/Directory.Build.props index 9433b81a..97da1d9b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,6 +21,10 @@ $(SolutionDir)build/obj/modules/$(Configuration) $(SolutionDir)build/bin/patcher/$(Configuration) $(SolutionDir)build/obj/patcher/$(Configuration) + $(SolutionDir)build/bin/restarter/$(Configuration) + $(SolutionDir)build/obj/restarter/$(Configuration) + $(SolutionDir)build/bin/preloader/$(Configuration) + $(SolutionDir)build/obj/preloader/$(Configuration) $(ModulesBinPath)/$(MSBuildProjectName) $(ModulesObjPath)/$(MSBuildProjectName) $(MSBuildProjectName) diff --git a/SpaceWarp.sln b/SpaceWarp.sln index c19f2bbb..34301bf3 100644 --- a/SpaceWarp.sln +++ b/SpaceWarp.sln @@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceWarp.Game", "src/Space EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceWarp.Messaging", "src/SpaceWarp.Messaging/SpaceWarp.Messaging.csproj", "{66BA4E01-8521-42EB-9D7D-8EB653757177}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceWarp.Preloader", "src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj", "{0957B5C2-B882-45A4-981E-F9EEE0B551C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceWarp.Restarter", "src/SpaceWarp.Restarter/SpaceWarp.Restarter.csproj", "{4B509D31-4C02-4D6E-8508-8417F0745776}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceWarp.Sound", "src/SpaceWarp.Sound/SpaceWarp.Sound.csproj", "{BA439A24-7EA3-4E79-A44C-1FA303B9331C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceWarp.UI", "src/SpaceWarp.UI/SpaceWarp.UI.csproj", "{CB131B63-51E6-4ED7-A47C-28B1EB65B8D7}" @@ -98,6 +102,22 @@ Global {8DB42693-9177-40B9-AC6A-B6D7A4823FAD}.Deploy|Any CPU.Build.0 = Deploy|Any CPU {8DB42693-9177-40B9-AC6A-B6D7A4823FAD}.DeployAndRun|Any CPU.ActiveCfg = DeployAndRun|Any CPU {8DB42693-9177-40B9-AC6A-B6D7A4823FAD}.DeployAndRun|Any CPU.Build.0 = DeployAndRun|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.Release|Any CPU.Build.0 = Release|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.Deploy|Any CPU.ActiveCfg = Deploy|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.Deploy|Any CPU.Build.0 = Deploy|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.DeployAndRun|Any CPU.ActiveCfg = DeployAndRun|Any CPU + {4B509D31-4C02-4D6E-8508-8417F0745776}.DeployAndRun|Any CPU.Build.0 = DeployAndRun|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.Release|Any CPU.Build.0 = Release|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.Deploy|Any CPU.ActiveCfg = Deploy|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.Deploy|Any CPU.Build.0 = Deploy|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.DeployAndRun|Any CPU.ActiveCfg = DeployAndRun|Any CPU + {0957B5C2-B882-45A4-981E-F9EEE0B551C6}.DeployAndRun|Any CPU.Build.0 = DeployAndRun|Any CPU EndGlobalSection EndGlobal diff --git a/plugin_template/doorstop_config.ini b/plugin_template/doorstop_config.ini index a68f30f1..20556758 100644 --- a/plugin_template/doorstop_config.ini +++ b/plugin_template/doorstop_config.ini @@ -2,7 +2,7 @@ # Specifies whether assembly executing is enabled enabled=true # Specifies the path (absolute, or relative to the game's exe) to the DLL/EXE that should be executed by Doorstop -targetAssembly=BepInEx\core\BepInEx.Preloader.dll +targetAssembly=BepInEx\core\SpaceWarp.Preloader.dll # Specifies whether Unity's output log should be redirected to \output_log.txt redirectOutputLog=false # If enabled, DOORSTOP_DISABLE env var value is ignored diff --git a/src/SpaceWarp.Preloader/Directory.Build.props b/src/SpaceWarp.Preloader/Directory.Build.props new file mode 100644 index 00000000..7bd628c0 --- /dev/null +++ b/src/SpaceWarp.Preloader/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + + false + $(PreloaderBinPath)/$(MSBuildProjectName) + $(PreloaderObjPath)/$(MSBuildProjectName) + + diff --git a/src/SpaceWarp.Preloader/Entrypoint.cs b/src/SpaceWarp.Preloader/Entrypoint.cs new file mode 100644 index 00000000..81bee39f --- /dev/null +++ b/src/SpaceWarp.Preloader/Entrypoint.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Reflection; +using JetBrains.Annotations; + +namespace SpaceWarp.Preloader; + +internal static class Entrypoint +{ + private static string _gameFolder; + + [UsedImplicitly] + public static void Main(string[] args) + { + _gameFolder = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0])!; + + StartBepinex(); + } + + private static void StartBepinex() + { + var bepinexFolder = Path.Combine(_gameFolder, "BepInEx", "core"); + var bepinexPreloaderPath = Path.Combine(bepinexFolder, "BepInEx.Preloader.dll"); + + Assembly.LoadFile(bepinexPreloaderPath); + BepInEx.Preloader.Entrypoint.Main(); + } +} \ No newline at end of file diff --git a/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj b/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj new file mode 100644 index 00000000..2242dd56 --- /dev/null +++ b/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj @@ -0,0 +1,12 @@ + + + + + + + + + ..\..\plugin_template\BepInEx\core\BepInEx.Preloader.dll + + + diff --git a/src/SpaceWarp.Restarter/Directory.Build.props b/src/SpaceWarp.Restarter/Directory.Build.props new file mode 100644 index 00000000..017615e9 --- /dev/null +++ b/src/SpaceWarp.Restarter/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + + false + $(RestarterBinPath)/$(MSBuildProjectName) + $(RestarterObjPath)/$(MSBuildProjectName) + + diff --git a/src/SpaceWarp.Restarter/Restarter.cs b/src/SpaceWarp.Restarter/Restarter.cs new file mode 100644 index 00000000..1c1ac823 --- /dev/null +++ b/src/SpaceWarp.Restarter/Restarter.cs @@ -0,0 +1,40 @@ +using System.Diagnostics; +using System.Reflection; + +if (args.Length < 1) +{ + Console.WriteLine( + $"Usage: {Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)} " + + $"[optional arguments]" + ); + Environment.Exit(1); +} + +var processPath = args[0]; +var processName = Path.GetFileNameWithoutExtension(processPath); +var processArgs = args.Length > 1 ? args[1..] : Array.Empty(); + +Console.WriteLine($"Waiting for {processName} to exit..."); + +while (true) +{ + if (Process.GetProcessesByName(processName).Length == 0) + { + try + { + Console.WriteLine($"{processName}.exe is not running. Attempting to start the process..."); + Process.Start(processPath, processArgs); + Console.WriteLine($"{processName}.exe started successfully."); + break; + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred while starting {processName}.exe: {ex.Message}"); + Environment.Exit(1); + } + } + else + { + Thread.Sleep(500); + } +} diff --git a/src/SpaceWarp.Restarter/SpaceWarp.Restarter.csproj b/src/SpaceWarp.Restarter/SpaceWarp.Restarter.csproj new file mode 100644 index 00000000..009e5c98 --- /dev/null +++ b/src/SpaceWarp.Restarter/SpaceWarp.Restarter.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + SpaceWarpRestarter + + diff --git a/src/SpaceWarp/Directory.Build.targets b/src/SpaceWarp/Directory.Build.targets index a971e012..d3973e50 100644 --- a/src/SpaceWarp/Directory.Build.targets +++ b/src/SpaceWarp/Directory.Build.targets @@ -80,6 +80,22 @@ SourceFiles="@(PatcherPDBs)" DestinationFolder="$(SolutionDir)/dist/$(ConfigurationName)/BepInEx/patchers/$(ProjectName)"/> + + + + + + + + + + + + + + + + - + + DestinationFolder="$(KSP2DIR)/%(RecursiveDir)"/> diff --git a/src/SpaceWarp/SpaceWarp.csproj b/src/SpaceWarp/SpaceWarp.csproj index 9cd2cccc..2eb3e56a 100644 --- a/src/SpaceWarp/SpaceWarp.csproj +++ b/src/SpaceWarp/SpaceWarp.csproj @@ -11,6 +11,7 @@ + diff --git a/src/SpaceWarpPatcher/SpaceWarpPatcher.csproj b/src/SpaceWarpPatcher/SpaceWarpPatcher.csproj index c304ba38..f7720300 100644 --- a/src/SpaceWarpPatcher/SpaceWarpPatcher.csproj +++ b/src/SpaceWarpPatcher/SpaceWarpPatcher.csproj @@ -4,7 +4,7 @@ - + From e2c5cea3440bc02286805576a7e4b3dc6aa9c797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bure=C5=A1?= Date: Thu, 4 Jan 2024 23:35:02 +0100 Subject: [PATCH 2/2] Fully working preloader (hopefully) --- src/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs | 6 + .../API/Mods/JSON/SpecVersion.cs | 11 +- src/SpaceWarp.Preloader/Entrypoint.cs | 164 ++++++++++++++++-- src/SpaceWarp.Preloader/Logger.cs | 79 +++++++++ .../SpaceWarp.Preloader.csproj | 7 +- src/SpaceWarp/Directory.Build.targets | 12 +- 6 files changed, 254 insertions(+), 25 deletions(-) create mode 100644 src/SpaceWarp.Preloader/Logger.cs diff --git a/src/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs b/src/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs index 49b94b35..6be09bda 100644 --- a/src/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs +++ b/src/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs @@ -108,4 +108,10 @@ public string Name /// [JsonProperty("conflicts", Required = Required.DisallowNull)] public List Conflicts { get; internal set; } = new(); + + /// + /// The filenames of patcher assemblies of the mod. + /// + [JsonProperty("patchers", Required = Required.DisallowNull)] + public List Patchers { get; internal set; } = new(); } \ No newline at end of file diff --git a/src/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs b/src/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs index 0deb2600..1c322806 100644 --- a/src/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs +++ b/src/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs @@ -45,11 +45,18 @@ public sealed record SpecVersion public static SpecVersion V1_3 { get; } = new(1, 3); /// - /// Specification version 2.0 (SpaceWarp 1.5.x and 2.0.x) - removes support for version checking from .csproj files, - /// + /// Specification version 2.0 (SpaceWarp 1.5 - 1.7.x) - removes support for version checking from .csproj files, + /// adds support for specifying mod conflicts. Switched to semantic versioning. /// public static SpecVersion V2_0 { get; } = new(2, 0); + /// + /// Specification version 2.1 (SpaceWarp 1.8.x) - requires that mods specify their preload patchers in the + /// swinfo.json file. + /// + public static SpecVersion V2_1 { get; } = new(2, 1); + + // ReSharper restore InconsistentNaming /// diff --git a/src/SpaceWarp.Preloader/Entrypoint.cs b/src/SpaceWarp.Preloader/Entrypoint.cs index 81bee39f..92882c10 100644 --- a/src/SpaceWarp.Preloader/Entrypoint.cs +++ b/src/SpaceWarp.Preloader/Entrypoint.cs @@ -1,28 +1,158 @@ -using System; -using System.IO; -using System.Reflection; +using System.Reflection; using JetBrains.Annotations; +using Newtonsoft.Json.Linq; namespace SpaceWarp.Preloader; internal static class Entrypoint { - private static string _gameFolder; + private static readonly List PreloadAssemblyPaths = + [ + Path.Combine("KSP2_x64_Data", "Managed", "Newtonsoft.Json.dll"), + Path.Combine("BepInEx", "core", "BepInEx.Preloader.dll"), + ]; - [UsedImplicitly] - public static void Main(string[] args) - { - _gameFolder = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0])!; + private static string _gameFolder; + private static Logger _logger; - StartBepinex(); - } + [UsedImplicitly] + public static void Main(string[] args) + { + _gameFolder = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0])!; + _logger = new Logger(_gameFolder); - private static void StartBepinex() - { - var bepinexFolder = Path.Combine(_gameFolder, "BepInEx", "core"); - var bepinexPreloaderPath = Path.Combine(bepinexFolder, "BepInEx.Preloader.dll"); + PreloadAssemblies(); + ProcessAllPatchers(); + StartBepinex(); + } - Assembly.LoadFile(bepinexPreloaderPath); - BepInEx.Preloader.Entrypoint.Main(); - } + private static void PreloadAssemblies() + { + foreach (var fullPath in PreloadAssemblyPaths.Select(assemblyPath => Path.Combine(_gameFolder, assemblyPath))) + { + try + { + _logger.LogDebug($"Preloading {fullPath}..."); + Assembly.LoadFile(fullPath); + } + catch (Exception ex) + { + _logger.LogException(ex, $"An error occurred while preloading the assembly {fullPath}:"); + } + } + } + + private static void StartBepinex() + { + BepInEx.Preloader.Entrypoint.Main(); + } + + #region Disabling patchers of disabled plugins + + private static void ProcessAllPatchers() + { + var disabledPluginGuids = GetDisabledPluginGuids(); + + var swinfoPaths = Directory + .EnumerateFiles( + Path.Combine(_gameFolder, "BepInEx", "plugins"), + "swinfo.json", + SearchOption.AllDirectories + ); + + var enablePatchers = new List(); + var disablePatchers = new List(); + + foreach (var swinfoPath in swinfoPaths) + { + try + { + var (guid, patchers) = ReadSwinfo(swinfoPath); + + if (patchers == null) + { + continue; + } + + if (!disabledPluginGuids.Contains(guid)) + { + enablePatchers.AddRange(patchers.Select(StripExtension)); + } + else + { + disablePatchers.AddRange(patchers.Select(StripExtension)); + } + } + catch (Exception ex) + { + _logger.LogException(ex, $"An error occurred while processing {swinfoPath}:"); + } + } + + RenameAllPatchers(enablePatchers, disablePatchers); + } + + private static string[] GetDisabledPluginGuids() + { + var disabledPluginsPath = Path.Combine(_gameFolder, "BepInEx", "disabled_plugins.cfg"); + + return File.Exists(disabledPluginsPath) + ? File.ReadAllLines(disabledPluginsPath) + : []; + } + + private static (string guid, List patchers) ReadSwinfo(string swinfoPath) + { + _logger.LogDebug($"Reading {swinfoPath}..."); + + var swinfo = JObject.Parse(File.ReadAllText(swinfoPath)); + + var guid = swinfo["mod_id"]?.Value(); + if (guid == null) + { + throw new Exception($"{swinfoPath} does not contain a mod_id."); + } + + var patchers = swinfo["patchers"]?.Values().ToList(); + if (patchers == null) + { + _logger.LogInfo($"{guid} does not contain patchers, skipping."); + } + + return (guid, patchers); + } + + private static void RenameAllPatchers(ICollection enablePatchers, ICollection disablePatchers) + { + var patchers = Directory + .EnumerateFiles(Path.Combine(_gameFolder, "BepInEx", "patchers"), "*", SearchOption.AllDirectories) + .Where(file => file.EndsWith(".dll") || file.EndsWith(".dll.disabled")); + + foreach (var patcher in patchers) + { + var patcherName = StripExtension(Path.GetFileName(patcher)); + + if (enablePatchers.Contains(patcherName) && patcher.EndsWith(".dll.disabled")) + { + _logger.LogDebug($"Enabling {patcherName}..."); + File.Move(patcher, patcher.Replace(".dll.disabled", ".dll")); + } + else if (disablePatchers.Contains(patcherName) && patcher.EndsWith(".dll")) + { + _logger.LogDebug($"Disabling {patcherName}..."); + File.Move(patcher, patcher.Replace(".dll", ".dll.disabled")); + } + else + { + _logger.LogDebug($"Skipping {patcherName}..."); + } + } + } + + private static string StripExtension(string filename) + { + return filename.Replace(".disabled", "").Replace(".dll", ""); + } + + #endregion } \ No newline at end of file diff --git a/src/SpaceWarp.Preloader/Logger.cs b/src/SpaceWarp.Preloader/Logger.cs new file mode 100644 index 00000000..fcfea00e --- /dev/null +++ b/src/SpaceWarp.Preloader/Logger.cs @@ -0,0 +1,79 @@ +using System.ComponentModel; + +namespace SpaceWarp.Preloader; + +internal enum LogLevel +{ + Debug, + Info, + Warning, + Error +} + +internal static class LogLevelExtensions +{ + public static string ToLogString(this LogLevel logLevel) + { + return logLevel switch + { + LogLevel.Debug => "DEBUG", + LogLevel.Info => "INFO ", + LogLevel.Warning => "WARN ", + LogLevel.Error => "ERR ", + _ => throw new InvalidEnumArgumentException(nameof(logLevel), (int)logLevel, typeof(LogLevel)) + }; + } +} + +internal class Logger +{ + private readonly string _logPath; + + public Logger(string gamePath) + { + _logPath = Path.Combine(gamePath, "BepInEx", "SpaceWarp.Preload.log"); + + if (File.Exists(_logPath)) + { + File.Delete(_logPath); + } + } + + private void Log(object message, LogLevel logLevel = LogLevel.Info) + { + var logMessage = + $"[{logLevel.ToLogString()}: {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}{Environment.NewLine}"; + File.AppendAllText(_logPath, logMessage); + } + + public void LogDebug(object message) + { + Log(message, LogLevel.Debug); + } + + public void LogInfo(object message) + { + Log(message, LogLevel.Info); + } + + public void LogWarning(object message) + { + Log(message, LogLevel.Warning); + } + + public void LogError(object message) + { + Log(message, LogLevel.Error); + } + + public void LogException(Exception ex, string message = null) + { + var logMessage = $"{ex.Message}{Environment.NewLine}{ex.StackTrace}"; + if (message != null) + { + logMessage = $"{message}{Environment.NewLine}{logMessage}"; + } + + LogError(logMessage); + } +} \ No newline at end of file diff --git a/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj b/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj index 2242dd56..68b83e8a 100644 --- a/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj +++ b/src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj @@ -1,12 +1,13 @@  - - + + + - ..\..\plugin_template\BepInEx\core\BepInEx.Preloader.dll + $(SolutionDir)/plugin_template/BepInEx/core/BepInEx.Preloader.dll diff --git a/src/SpaceWarp/Directory.Build.targets b/src/SpaceWarp/Directory.Build.targets index d3973e50..35a1ded4 100644 --- a/src/SpaceWarp/Directory.Build.targets +++ b/src/SpaceWarp/Directory.Build.targets @@ -83,11 +83,17 @@ - + + + + + + @@ -139,9 +145,9 @@ Directories="$(KSP2DIR)/BepInEx/plugins/$(ProjectName)"/> - -