Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions StarMap.Core/ModRepository/RuntimeMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,18 @@ public bool InitializeMod(ModRegistry modRegistry, IPatchManager patchManager, I
modRegistry.AddModMethod(ModId, attr, modObject, classMethod);
break;
case StarMapAllModsLoadedAttribute:
var allModsLoadedPatch = patchManager.RegisterStaticVoidPatch(IPatchService.PatchType.Postfix, this, typeof(KSA.ModLibrary).GetMethod(nameof(ModLibrary.LoadAll))!,
var allModsLoadedPatch = patchManager.RegisterStaticVoidPatch(IPatchService.PatchType.Postfix, this, typeof(KSA.ModLibrary).GetMethod(nameof(ModLibrary.LoadAll)),
args =>
{
classMethod.Invoke(modObject, args);
// The callback takes no parameters, whatever LoadAll takes.
classMethod.Invoke(modObject, []);
return true;
});
Patches.Add(allModsLoadedPatch);
break;

case StarMapImmediateLoadAttribute:
var immediateLoadPatch = patchManager.RegisterInstanceVoidPatch<Mod>(IPatchService.PatchType.Prefix, this, typeof(KSA.Mod).GetMethod(nameof(Mod.PrepareSystems))!,
var immediateLoadPatch = patchManager.RegisterInstanceVoidPatch<Mod>(IPatchService.PatchType.Prefix, this, typeof(KSA.Mod).GetMethod(nameof(Mod.LoadSettings)),
(args, mod) =>
{
if (mod.Id.Equals(ModId))
Expand Down
33 changes: 25 additions & 8 deletions StarMap.Core/Patches/PatchManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ namespace StarMap.Core.Patches
{
internal interface IPatchManager : IDisposable
{
IDisposable RegisterInstancePatch<T, U>(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceCallback<T, U> callback);
IDisposable RegisterInstanceVoidPatch<T>(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceVoidCallback<T> callback);
IDisposable RegisterStaticPatch<U>(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticCallback<U> callback);
IDisposable RegisterStaticVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticVoidCallback callback);
IDisposable RegisterInstancePatch<T, U>(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceCallback<T, U> callback);
IDisposable RegisterInstanceVoidPatch<T>(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceVoidCallback<T> callback);
IDisposable RegisterStaticPatch<U>(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticCallback<U> callback);
IDisposable RegisterStaticVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticVoidCallback callback);
}

internal class PatchManager : IPatchManager
Expand All @@ -34,25 +34,42 @@ public PatchManager(ILogger logger)
_instance = this;
}

public IDisposable RegisterInstancePatch<T, U>(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceCallback<T, U> callback)
public IDisposable RegisterInstancePatch<T, U>(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceCallback<T, U> callback)
{
if (method is null) return MissingTarget(mod);
return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new InstanceCallback<T, U>(callback), _logger);
}
public IDisposable RegisterInstanceVoidPatch<T>(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceVoidCallback<T> callback)
public IDisposable RegisterInstanceVoidPatch<T>(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceVoidCallback<T> callback)
{
if (method is null) return MissingTarget(mod);
return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new InstanceVoidCallback<T>(callback), _logger);
}

public IDisposable RegisterStaticPatch<U>(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticCallback<U> callback)
public IDisposable RegisterStaticPatch<U>(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticCallback<U> callback)
{
if (method is null) return MissingTarget(mod);
return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new StaticCallback<U>(callback), _logger);
}
public IDisposable RegisterStaticVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticVoidCallback callback)
public IDisposable RegisterStaticVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticVoidCallback callback)
{
if (method is null) return MissingTarget(mod);
return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new StaticVoidCallback(callback), _logger);
}


// A KSA update can remove a method StarMap hooks. Skipping the one callback keeps the loader
// and every other mod running.
private IDisposable MissingTarget(RuntimeMod? mod)
{
_logger.Warning($"StarMap - A hook target is missing from this KSA build; a callback of {mod?.ModId ?? "StarMap"} will not run");
return new NoPatch();
}

private sealed class NoPatch : IDisposable
{
public void Dispose() { }
}

private MethodPatch GetOrCreateMethodPatch(MethodBase method)
{
if (!_patches.TryGetValue(method, out var methodPatch))
Expand Down
3 changes: 2 additions & 1 deletion StarMap.Core/UI/ConfirmRestart/ConfirmRestart.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Brutal.GlfwApi;
using Brutal.Collections;
using Brutal.GlfwApi;
using Brutal.ImGuiApi;
using Brutal.VulkanApi;
using Brutal.VulkanApi.Abstractions;
Expand Down
2 changes: 1 addition & 1 deletion docs/modding/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Mark the entry class with `[StarMapMod]`. StarMap creates one instance for the m
| Attribute | Required method signature | Called |
| --- | --- | --- |
| `[StarMapBeforeMain]` | `void Method()` | After the mod is initialized and before KSA starts. |
| `[StarMapImmediateLoad]` | `void Method(KSA.Mod definingMod)` | Immediately before KSA prepares that mod's systems. |
| `[StarMapImmediateLoad]` | `void Method(KSA.Mod definingMod)` | When KSA registers that mod, before its settings and any content load. |
| `[StarMapAllModsLoaded]` | `void Method()` | After KSA's `ModLibrary.LoadAll` completes. |
| `[StarMapUnload]` | `void Method()` | When KSA unloads. Dispose mod-owned subscriptions and menu items here. |
| `[StarMapBeforeGui]` | `void Method(double dt)` | Before KSA's GUI frame callback. |
Expand Down
Loading