Skip to content

Commit

Permalink
Merge pull request #280 from SpaceWarpDev/preload-and-restart
Browse files Browse the repository at this point in the history
Added preloader and restarter
  • Loading branch information
jan-bures authored Jan 4, 2024
2 parents ef9b9c3 + e2c5cea commit 0844f5e
Show file tree
Hide file tree
Showing 15 changed files with 392 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<ModulesObjPath>$(SolutionDir)build/obj/modules/$(Configuration)</ModulesObjPath>
<PatcherBinPath>$(SolutionDir)build/bin/patcher/$(Configuration)</PatcherBinPath>
<PatcherObjPath>$(SolutionDir)build/obj/patcher/$(Configuration)</PatcherObjPath>
<RestarterBinPath>$(SolutionDir)build/bin/restarter/$(Configuration)</RestarterBinPath>
<RestarterObjPath>$(SolutionDir)build/obj/restarter/$(Configuration)</RestarterObjPath>
<PreloaderBinPath>$(SolutionDir)build/bin/preloader/$(Configuration)</PreloaderBinPath>
<PreloaderObjPath>$(SolutionDir)build/obj/preloader/$(Configuration)</PreloaderObjPath>
<BaseOutputPath>$(ModulesBinPath)/$(MSBuildProjectName)</BaseOutputPath>
<BaseIntermediateOutputPath>$(ModulesObjPath)/$(MSBuildProjectName)</BaseIntermediateOutputPath>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
Expand Down
20 changes: 20 additions & 0 deletions SpaceWarp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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

2 changes: 1 addition & 1 deletion plugin_template/doorstop_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 <current folder>\output_log.txt
redirectOutputLog=false
# If enabled, DOORSTOP_DISABLE env var value is ignored
Expand Down
6 changes: 6 additions & 0 deletions src/SpaceWarp.Core/API/Mods/JSON/ModInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,10 @@ public string Name
/// </summary>
[JsonProperty("conflicts", Required = Required.DisallowNull)]
public List<DependencyInfo> Conflicts { get; internal set; } = new();

/// <summary>
/// The filenames of patcher assemblies of the mod.
/// </summary>
[JsonProperty("patchers", Required = Required.DisallowNull)]
public List<string> Patchers { get; internal set; } = new();
}
11 changes: 9 additions & 2 deletions src/SpaceWarp.Core/API/Mods/JSON/SpecVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@ public sealed record SpecVersion
public static SpecVersion V1_3 { get; } = new(1, 3);

/// <summary>
/// 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.
/// </summary>
public static SpecVersion V2_0 { get; } = new(2, 0);

/// <summary>
/// Specification version 2.1 (SpaceWarp 1.8.x) - requires that mods specify their preload patchers in the
/// swinfo.json file.
/// </summary>
public static SpecVersion V2_1 { get; } = new(2, 1);


// ReSharper restore InconsistentNaming

/// <summary>
Expand Down
11 changes: 11 additions & 0 deletions src/SpaceWarp.Preloader/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Project="$(SolutionDir)/Directory.Build.props"/>

<!-- Build configuration -->
<PropertyGroup>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<BaseOutputPath>$(PreloaderBinPath)/$(MSBuildProjectName)</BaseOutputPath>
<BaseIntermediateOutputPath>$(PreloaderObjPath)/$(MSBuildProjectName)</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
158 changes: 158 additions & 0 deletions src/SpaceWarp.Preloader/Entrypoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System.Reflection;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;

namespace SpaceWarp.Preloader;

internal static class Entrypoint
{
private static readonly List<string> PreloadAssemblyPaths =
[
Path.Combine("KSP2_x64_Data", "Managed", "Newtonsoft.Json.dll"),
Path.Combine("BepInEx", "core", "BepInEx.Preloader.dll"),
];

private static string _gameFolder;
private static Logger _logger;

[UsedImplicitly]
public static void Main(string[] args)
{
_gameFolder = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0])!;
_logger = new Logger(_gameFolder);

PreloadAssemblies();
ProcessAllPatchers();
StartBepinex();
}

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<string>();
var disablePatchers = new List<string>();

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<string> patchers) ReadSwinfo(string swinfoPath)
{
_logger.LogDebug($"Reading {swinfoPath}...");

var swinfo = JObject.Parse(File.ReadAllText(swinfoPath));

var guid = swinfo["mod_id"]?.Value<string>();
if (guid == null)
{
throw new Exception($"{swinfoPath} does not contain a mod_id.");
}

var patchers = swinfo["patchers"]?.Values<string>().ToList();
if (patchers == null)
{
_logger.LogInfo($"{guid} does not contain patchers, skipping.");
}

return (guid, patchers);
}

private static void RenameAllPatchers(ICollection<string> enablePatchers, ICollection<string> 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
}
79 changes: 79 additions & 0 deletions src/SpaceWarp.Preloader/Logger.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
13 changes: 13 additions & 0 deletions src/SpaceWarp.Preloader/SpaceWarp.Preloader.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- References -->
<ItemGroup Label="NuGet package references">
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" Private="false"/>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" Private="false"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" Private="false"/>
</ItemGroup>
<ItemGroup>
<Reference Include="BepInEx.Preloader" Publicize="true" PrivateAssets="all" Private="false">
<HintPath>$(SolutionDir)/plugin_template/BepInEx/core/BepInEx.Preloader.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions src/SpaceWarp.Restarter/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Project="$(SolutionDir)/Directory.Build.props"/>

<!-- Build configuration -->
<PropertyGroup>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<BaseOutputPath>$(RestarterBinPath)/$(MSBuildProjectName)</BaseOutputPath>
<BaseIntermediateOutputPath>$(RestarterObjPath)/$(MSBuildProjectName)</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
Loading

0 comments on commit 0844f5e

Please sign in to comment.