diff --git a/Directory.Packages.props b/Directory.Packages.props index 358addf..9d2fb00 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,7 +19,7 @@ - - + + \ No newline at end of file diff --git a/StarMap.Core/ModRepository/RuntimeMod.cs b/StarMap.Core/ModRepository/RuntimeMod.cs index 62bf7a8..1fb6acf 100644 --- a/StarMap.Core/ModRepository/RuntimeMod.cs +++ b/StarMap.Core/ModRepository/RuntimeMod.cs @@ -159,17 +159,17 @@ 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))!, - args => + var allModsLoadedPatch = patchManager.RegisterStaticVoidPatch(IPatchService.PatchType.Postfix, this, typeof(KSA.ModLibrary).GetMethod(nameof(ModLibrary.LoadAll)), + _ => { - classMethod.Invoke(modObject, args); + classMethod.Invoke(modObject, []); return true; }); Patches.Add(allModsLoadedPatch); break; case StarMapImmediateLoadAttribute: - var immediateLoadPatch = patchManager.RegisterInstanceVoidPatch(IPatchService.PatchType.Prefix, this, typeof(KSA.Mod).GetMethod(nameof(Mod.PrepareSystems))!, + var immediateLoadPatch = patchManager.RegisterInstanceVoidPatch(IPatchService.PatchType.Prefix, this, typeof(KSA.Mod).GetMethod(nameof(Mod.LoadSettings)), (args, mod) => { if (mod.Id.Equals(ModId)) diff --git a/StarMap.Core/Patches/PatchManager.cs b/StarMap.Core/Patches/PatchManager.cs index 9d91f2f..c6ee34e 100644 --- a/StarMap.Core/Patches/PatchManager.cs +++ b/StarMap.Core/Patches/PatchManager.cs @@ -9,10 +9,10 @@ namespace StarMap.Core.Patches { internal interface IPatchManager : IDisposable { - IDisposable RegisterInstancePatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceCallback callback); - IDisposable RegisterInstanceVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceVoidCallback callback); - IDisposable RegisterStaticPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticCallback callback); - IDisposable RegisterStaticVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticVoidCallback callback); + IDisposable RegisterInstancePatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceCallback callback); + IDisposable RegisterInstanceVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceVoidCallback callback); + IDisposable RegisterStaticPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticCallback callback); + IDisposable RegisterStaticVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticVoidCallback callback); } internal class PatchManager : IPatchManager @@ -34,25 +34,42 @@ public PatchManager(ILogger logger) _instance = this; } - public IDisposable RegisterInstancePatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceCallback callback) + public IDisposable RegisterInstancePatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceCallback callback) { + if (method is null) return MissingTarget(mod); return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new InstanceCallback(callback), _logger); } - public IDisposable RegisterInstanceVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.InstanceVoidCallback callback) + public IDisposable RegisterInstanceVoidPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.InstanceVoidCallback callback) { + if (method is null) return MissingTarget(mod); return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new InstanceVoidCallback(callback), _logger); } - public IDisposable RegisterStaticPatch(PatchType patchType, RuntimeMod? mod, MethodBase method, IPatchService.StaticCallback callback) + public IDisposable RegisterStaticPatch(PatchType patchType, RuntimeMod? mod, MethodBase? method, IPatchService.StaticCallback callback) { + if (method is null) return MissingTarget(mod); return GetOrCreateMethodPatch(method).RegisterPatch(patchType, mod, new StaticCallback(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)) diff --git a/docs/changelog.md b/docs/changelog.md index 5f0aeac..f399649 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,16 @@ # Changelog -## 0.5 +## 0.5.3 + +- Ensure StarMap is compatible with KSA 2025.11.10.5801 + +## 0.5.1 and 0.5.2 + +### Fixed + +- Ensure API nuget is released correctly + +## 0.5.0 ### Added diff --git a/docs/modding/lifecycle.md b/docs/modding/lifecycle.md index 70c9dcd..f1b930b 100644 --- a/docs/modding/lifecycle.md +++ b/docs/modding/lifecycle.md @@ -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. |