diff --git a/StarMap.API/BaseAttributes.cs b/StarMap.API/BaseAttributes.cs
new file mode 100644
index 0000000..f5df406
--- /dev/null
+++ b/StarMap.API/BaseAttributes.cs
@@ -0,0 +1,108 @@
+using KSA;
+using System.Reflection;
+
+namespace StarMap.API
+{
+ ///
+ /// Marks the main class for a StarMap mod.
+ /// Only attributes on methods within classes marked with this attribute will be considered.
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public class StarMapModAttribute : Attribute
+ {
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public abstract class StarMapMethodAttribute : Attribute
+ {
+ public abstract bool IsValidSignature(MethodInfo info);
+ }
+
+ ///
+ /// Methods marked with this attribute will be called immediately when the mod is loaded.
+ ///
+ ///
+ /// Methods using this attribute must match the following signature:
+ ///
+ ///
+ /// public void MethodName(KSA.Mod definingMod);
+ ///
+ ///
+ /// Parameter requirements:
+ ///
+ /// -
+ ///
+ /// the KSA.Mod instance that is being loaded.
+ ///
+ ///
+ ///
+ ///
+ /// Requirements:
+ ///
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public class StarMapImmediateLoadAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 1 &&
+ method.GetParameters()[0].ParameterType == typeof(Mod);
+ }
+ }
+
+ ///
+ /// Methods marked with this attribute will be called when all mods are loaded.
+ /// This is to be used for when the mod has dependencies on other mods.
+ ///
+ ///
+ /// Methods using this attribute must follow this signature:
+ ///
+ ///
+ /// public void MethodName();
+ ///
+ ///
+ /// Specifically:
+ ///
+ /// - No parameters are allowed.
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public class StarMapAllModsLoadedAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 0;
+ }
+ }
+
+ ///
+ /// Methods marked with this attribute will be called when KSA is unloaded
+ ///
+ ///
+ /// Methods using this attribute must follow this signature:
+ ///
+ ///
+ /// public void MethodName();
+ ///
+ ///
+ /// Specifically:
+ ///
+ /// - No parameters are allowed.
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public class StarMapUnloadAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 0;
+ }
+ }
+}
diff --git a/StarMap.API/IStarMapInterface.cs b/StarMap.API/IStarMapInterface.cs
deleted file mode 100644
index 0d1d329..0000000
--- a/StarMap.API/IStarMapInterface.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace StarMap.API
-{
- public interface IStarMapInterface
- {
- }
-}
diff --git a/StarMap.API/IStarMapMod.cs b/StarMap.API/IStarMapMod.cs
deleted file mode 100644
index 7bfe317..0000000
--- a/StarMap.API/IStarMapMod.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace StarMap.API
-{
- public interface IStarMapMod : IStarMapInterface
- {
- bool ImmediateUnload { get; }
- void OnImmediatLoad();
- void OnFullyLoaded();
- void Unload();
- }
-}
diff --git a/StarMap.API/IStarMapOnUi.cs b/StarMap.API/IStarMapOnUi.cs
deleted file mode 100644
index 1b590ca..0000000
--- a/StarMap.API/IStarMapOnUi.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace StarMap.API
-{
- public interface IStarMapOnUi : IStarMapInterface
- {
- void OnBeforeUi(double dt);
- void OnAfterUi(double dt);
- }
-}
diff --git a/StarMap.API/OnGuiAttributes.cs b/StarMap.API/OnGuiAttributes.cs
new file mode 100644
index 0000000..f2e17bb
--- /dev/null
+++ b/StarMap.API/OnGuiAttributes.cs
@@ -0,0 +1,76 @@
+using System.Reflection;
+
+namespace StarMap.API
+{
+ ///
+ /// Methods marked with this attribute will be called before KSA starts creating its ImGui interface.
+ ///
+ ///
+ /// Methods using this attribute must match the following signature:
+ ///
+ ///
+ /// public void MethodName(double dt);
+ ///
+ ///
+ /// Parameter requirements:
+ ///
+ /// -
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Requirements:
+ ///
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public sealed class StarMapBeforeGuiAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 1 &&
+ method.GetParameters()[0].ParameterType == typeof(double);
+
+ }
+ }
+
+ ///
+ /// Methods marked with this attribute will be called when KSA has finished creating its ImGui interface.
+ ///
+ ///
+ /// Methods using this attribute must match the following signature:
+ ///
+ ///
+ /// public void MethodName(double dt);
+ ///
+ ///
+ /// Parameter requirements:
+ ///
+ /// -
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Requirements:
+ ///
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public sealed class StarMapAfterGuiAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 1 &&
+ method.GetParameters()[0].ParameterType == typeof(double);
+
+ }
+ }
+}
diff --git a/StarMap.API/README.md b/StarMap.API/README.md
new file mode 100644
index 0000000..edfc959
--- /dev/null
+++ b/StarMap.API/README.md
@@ -0,0 +1,16 @@
+# StarMap API
+
+This package provides the API for mods to interface with the [StarMap](https://github.com/StarMapLoader/StarMap) modloader.
+The main class of the mod should be marked by the StarMapMod attribute.
+Then methods within this class can be marked with any of the StarMapMethod attributes.
+At the initialization of the mod within KSA, an instance of the StarMapMod is created, only the first class that has this attribute will be considered.
+Any method within this class that has any of the attributes will used, so if two methods use StarMapImmediateLoad, both will be called.
+
+## Attributes
+
+- StarMapMod: Main attribute to mark the mod class
+- StarMapImmediateLoad: Called immediatly when the mod is loaded in KSA
+- StarMapAllModsLoaded: Called once all mods are loaded, can be used when this mod has a dependency on another mod
+- StarMapUnload: Called when KSA is unloaded
+- StarMapBeforeGui: Called just before KSA starts drawing its Ui
+- StarMapAfterGui: Called after KSA has drawn its Ui
diff --git a/StarMap.API/StarMap.API.csproj b/StarMap.API/StarMap.API.csproj
index 125f4c9..0f34d76 100644
--- a/StarMap.API/StarMap.API.csproj
+++ b/StarMap.API/StarMap.API.csproj
@@ -6,4 +6,15 @@
enable
+
+
+ runtime
+
+
+
+
+
+ runtime
+
+
diff --git a/StarMap.Core/ModRepository/LoadedModRepository.cs b/StarMap.Core/ModRepository/LoadedModRepository.cs
index 4d13ce9..c1e0b3a 100644
--- a/StarMap.Core/ModRepository/LoadedModRepository.cs
+++ b/StarMap.Core/ModRepository/LoadedModRepository.cs
@@ -1,5 +1,4 @@
-using HarmonyLib;
-using KSA;
+using KSA;
using StarMap.API;
using System.Reflection;
using System.Runtime.Loader;
@@ -9,29 +8,34 @@ namespace StarMap.Core.ModRepository
internal class LoadedModRepository : IDisposable
{
private readonly AssemblyLoadContext _coreAssemblyLoadContext;
- private readonly IEnumerable _registeredInterfaces = [];
+ private readonly Dictionary _registeredMethodAttributes = [];
private readonly ModRegistry _mods = new();
public ModRegistry Mods => _mods;
+ private (string attributeName, StarMapMethodAttribute attribute)? ConvertAttributeType(Type attrType)
+ {
+ if ((Activator.CreateInstance(attrType) as StarMapMethodAttribute) is not StarMapMethodAttribute attrObject) return null;
+ return (attrType.Name, attrObject);
+ }
+
public LoadedModRepository(AssemblyLoadContext coreAssemblyLoadContext)
{
_coreAssemblyLoadContext = coreAssemblyLoadContext;
- var baseInterface = typeof(IStarMapInterface);
- Assembly starMapTypes = baseInterface.Assembly;
+ Assembly coreAssembly = typeof(StarMapModAttribute).Assembly;
- _registeredInterfaces = starMapTypes
+ _registeredMethodAttributes = coreAssembly
.GetTypes()
- .Where(
- t => t.IsInterface &&
- baseInterface.IsAssignableFrom(t) &&
- t != baseInterface);
- }
-
- private bool IsStarMapMod(Type type)
- {
- return typeof(IStarMapMod).IsAssignableFrom(type) && !type.IsInterface;
+ .Where(t =>
+ typeof(StarMapMethodAttribute).IsAssignableFrom(t) &&
+ t.IsClass &&
+ !t.IsAbstract &&
+ t.GetCustomAttribute()?.ValidOn.HasFlag(AttributeTargets.Method) == true
+ )
+ .Select(ConvertAttributeType)
+ .OfType<(string attributeName, StarMapMethodAttribute attribute)>()
+ .ToDictionary();
}
public void LoadMod(Mod mod)
@@ -46,45 +50,54 @@ public void LoadMod(Mod mod)
var modLoadContext = new ModAssemblyLoadContext(mod, _coreAssemblyLoadContext);
var modAssembly = modLoadContext.LoadFromAssemblyName(new AssemblyName() { Name = mod.Name });
- var modClass = modAssembly.GetTypes().FirstOrDefault(IsStarMapMod);
+ var modClass = modAssembly.GetTypes().FirstOrDefault(type => type.IsDefined(typeof(StarMapModAttribute), inherit: false));
if (modClass is null) return;
- if (modClass.CreateInstance() is not IStarMapInterface modObject) return;
+ var modObject = Activator.CreateInstance(modClass);
+ if (modObject is null) return;
- foreach (var interfaceType in _registeredInterfaces)
+ var classMethods = modClass.GetMethods();
+ var immediateLoadMethods = new List();
+
+ foreach (var classMethod in classMethods)
{
- if (interfaceType.IsAssignableFrom(modClass))
+ var stringAttrs = classMethod.GetCustomAttributes().Select((attr) => attr.GetType().Name).Where(_registeredMethodAttributes.Keys.Contains);
+ foreach (var stringAttr in stringAttrs)
{
- _mods.Add(interfaceType, modObject);
- }
- }
+ var attr = _registeredMethodAttributes[stringAttr];
- if (modObject is not IStarMapMod starMapMod) return;
+ if (!attr.IsValidSignature(classMethod)) return;
- starMapMod.OnImmediatLoad();
+ if (attr.GetType() == typeof(StarMapModAttribute))
+ {
+ immediateLoadMethods.Add(classMethod);
+ }
+
+ _mods.Add(attr, modObject, classMethod);
+ }
+ }
- if (starMapMod.ImmediateUnload)
+ foreach (var method in immediateLoadMethods)
{
- modLoadContext.Unload();
- return;
+ method.Invoke(modObject, [mod]);
}
- Console.WriteLine($"Loaded mod: {mod.Name}");
+ Console.WriteLine($"StarMap - Loaded mod: {mod.Name}");
}
public void OnAllModsLoaded()
{
- foreach (var mod in _mods.Get())
+ foreach (var (_, @object, method) in _mods.Get())
{
- mod.OnFullyLoaded();
+ method.Invoke(@object, []);
}
}
public void Dispose()
{
- foreach (var mod in _mods.Get())
+ foreach (var (_, @object, method) in _mods.Get())
{
- mod.Unload();
+ method.Invoke(@object, []);
}
_mods.Dispose();
diff --git a/StarMap.Core/ModRepository/ModRegistry.cs b/StarMap.Core/ModRepository/ModRegistry.cs
index 1a7cabd..ffd9c0b 100644
--- a/StarMap.Core/ModRepository/ModRegistry.cs
+++ b/StarMap.Core/ModRepository/ModRegistry.cs
@@ -1,53 +1,42 @@
using StarMap.API;
+using System.Reflection;
namespace StarMap.Core.ModRepository
{
internal sealed class ModRegistry : IDisposable
{
- private readonly Dictionary> _map = new();
+ private readonly Dictionary> _map = new();
- public void Add(Type iface, IStarMapInterface instance)
+ public void Add(StarMapMethodAttribute attribute, object @object, MethodInfo method)
{
- // --- type-safety checks ---
- if (!typeof(IStarMapInterface).IsAssignableFrom(iface) || !iface.IsInterface)
- throw new ArgumentException($"{iface} is not an interface inheriting {typeof(IStarMapInterface).Name}");
-
- if (instance is null)
- throw new ArgumentNullException(nameof(instance));
-
- Type implType = instance.GetType();
-
- if (!iface.IsAssignableFrom(implType))
- throw new ArgumentException(
- $"{implType.Name} does not implement {iface.Name}"
- );
+ var attributeType = attribute.GetType();
// --- add instance ---
- if (!_map.TryGetValue(iface, out var list))
+ if (!_map.TryGetValue(attributeType, out var list))
{
list = [];
- _map[iface] = list;
+ _map[attributeType] = list;
}
- list.Add(instance);
+ list.Add((attribute, @object, method));
}
- public IReadOnlyList Get()
- where TInterface : IStarMapInterface
+ public IReadOnlyList<(StarMapMethodAttribute attribute, object @object, MethodInfo method)> Get()
+ where TAttribute : Attribute
{
- if (_map.TryGetValue(typeof(TInterface), out var list))
+ if (_map.TryGetValue(typeof(TAttribute), out var list))
{
- return list.Cast().ToList();
+ return list.Cast<(StarMapMethodAttribute attribute, object @object, MethodInfo method)>().ToList();
}
- return Array.Empty();
+ return Array.Empty<(StarMapMethodAttribute attribute, object @object, MethodInfo method)>();
}
- public IReadOnlyList Get(Type iface)
+ public IReadOnlyList<(StarMapMethodAttribute attribute, object @object, MethodInfo method)> Get(Type iface)
{
return _map.TryGetValue(iface, out var list)
? list
- : Array.Empty();
+ : Array.Empty<(StarMapMethodAttribute attribute, object @object, MethodInfo method)>();
}
public void Dispose()
diff --git a/StarMap.Core/Patches/ProgramPatcher.cs b/StarMap.Core/Patches/ProgramPatcher.cs
index 260fc94..69b79ca 100644
--- a/StarMap.Core/Patches/ProgramPatcher.cs
+++ b/StarMap.Core/Patches/ProgramPatcher.cs
@@ -13,11 +13,11 @@ internal static class ProgramPatcher
[HarmonyPrefix]
public static void BeforeOnDrawUi(double dt)
{
- var mods = StarMapCore.Instance?.LoadedMods.Mods.Get() ?? [];
+ var methods = StarMapCore.Instance?.LoadedMods.Mods.Get() ?? [];
- foreach (var mod in mods)
+ foreach (var (_, @object, method) in methods)
{
- mod.OnBeforeUi(dt);
+ method.Invoke(@object, [dt]);
}
}
@@ -25,11 +25,11 @@ public static void BeforeOnDrawUi(double dt)
[HarmonyPostfix]
public static void AfterOnDrawUi(double dt)
{
- var mods = StarMapCore.Instance?.LoadedMods.Mods.Get() ?? [];
+ var methods = StarMapCore.Instance?.LoadedMods.Mods.Get() ?? [];
- foreach (var mod in mods)
+ foreach (var (_, @object, method) in methods)
{
- mod.OnAfterUi(dt);
+ method.Invoke(@object, [dt]);
}
}
}
diff --git a/StarMap.Core/StarMapCore.cs b/StarMap.Core/StarMapCore.cs
index 702b31a..0690665 100644
--- a/StarMap.Core/StarMapCore.cs
+++ b/StarMap.Core/StarMapCore.cs
@@ -24,9 +24,7 @@ public StarMapCore(AssemblyLoadContext coreAssemblyLoadContext)
public void Init()
{
- _harmony.PatchAll();
- // Currently needed to force patch in release mode
- Harmony.GetAllPatchedMethods();
+ _harmony.PatchAll(typeof(StarMapCore).Assembly);
}
public void DeInit()
diff --git a/StarMap.Types/LoaderConfig.cs b/StarMap.Types/LoaderConfig.cs
index f7646b6..aede82b 100644
--- a/StarMap.Types/LoaderConfig.cs
+++ b/StarMap.Types/LoaderConfig.cs
@@ -1,10 +1,4 @@
-using StarMap.Types.Proto.IPC;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
+using System.Text.Json;
namespace StarMap.Types
{
@@ -15,36 +9,36 @@ public bool TryLoadConfig()
{
if (!File.Exists("./StarMapConfig.json"))
{
- Console.WriteLine("Please fill the StarMapConfig.json and restart the program");
+ Console.WriteLine("StarMap - Please fill the StarMapConfig.json and restart the program");
File.WriteAllText("./StarMapConfig.json", JsonSerializer.Serialize(new LoaderConfig(), new JsonSerializerOptions { WriteIndented = true }));
return false;
}
-
+
var jsonString = File.ReadAllText("./StarMapConfig.json");
var config = JsonSerializer.Deserialize(jsonString);
-
+
if (config is null) return false;
-
+
if (string.IsNullOrEmpty(config.GameLocation))
{
- Console.WriteLine("The 'GameLocation' property in StarMapConfig.json is either empty or points to a non-existing file.");
+ Console.WriteLine("StarMap - The 'GameLocation' property in StarMapConfig.json is either empty or points to a non-existing file.");
return false;
}
-
+
string path = config.GameLocation;
-
+
if (Directory.Exists(path))
{
path = Path.Combine(path, "KSA.dll");
}
-
+
if (!File.Exists(path))
{
- Console.WriteLine("Could not find KSA.dll. Make sure the folder or file path is correct:");
+ Console.WriteLine("StarMap - Could not find KSA.dll. Make sure the folder or file path is correct:");
Console.WriteLine(path);
return false;
}
-
+
GameLocation = path;
return true;
}
diff --git a/StarMap.Types/Pipes/PipeClient.cs b/StarMap.Types/Pipes/PipeClient.cs
index c9d182a..6941bde 100644
--- a/StarMap.Types/Pipes/PipeClient.cs
+++ b/StarMap.Types/Pipes/PipeClient.cs
@@ -28,7 +28,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken)
options: PipeOptions.Asynchronous);
- Console.WriteLine($"Connecting to pipe {_pipeName}...");
+ Console.WriteLine($"StarMap - Connecting to pipe {_pipeName}...");
await _client.ConnectAsync(cancellationToken);
_readingCts = new CancellationTokenSource();
diff --git a/StarMap/Program.cs b/StarMap/Program.cs
index de2ac49..3e68e45 100644
--- a/StarMap/Program.cs
+++ b/StarMap/Program.cs
@@ -10,15 +10,15 @@ static void Main(string[] args)
{
if (args.Length < 1)
{
- Console.WriteLine("StarMapLoader - Running Starmap in solo mode!");
+ Console.WriteLine("StarMap - Running Starmap in solo mode!");
SoleModeInner();
return;
}
- Console.WriteLine("Running Starmap in loader mode.");
+ Console.WriteLine("StarMap - Running Starmap in loader mode.");
var pipeName = args[0];
- Console.WriteLine($"Connection to pipe: {pipeName}");
+ Console.WriteLine($"StarMap - Connection to pipe: {pipeName}");
MainInner(pipeName).GetAwaiter().GetResult();
}
@@ -39,7 +39,7 @@ static void SoleModeInner()
var gameSurveyer = new GameSurveyer(dumbFacade, gameAssemblyContext, gameConfig.GameLocation);
if (!gameSurveyer.TryLoadCoreAndGame())
{
- Console.WriteLine("Unable to load mod manager and game in solo mode.");
+ Console.WriteLine("StarMap - Unable to load mod manager and game in solo mode.");
return;
}
diff --git a/StarMapLoader/ModRepository.cs b/StarMapLoader/ModRepository.cs
index b118b00..a1441be 100644
--- a/StarMapLoader/ModRepository.cs
+++ b/StarMapLoader/ModRepository.cs
@@ -1,7 +1,6 @@
-using System.Collections;
-using System.Text.Json;
-using StarMap.Index.API;
+using StarMap.Index.API;
using StarMap.Types.Proto.IPC;
+using System.Text.Json;
namespace StarMapLoader
{
@@ -107,7 +106,7 @@ public void ApplyModUpdates()
}
catch (Exception ex)
{
- Console.WriteLine($"Unable to apply update for mod: {modChange.Mod.Name} to version {modChange.AfterVersion?.Version ?? ""}: {ex}");
+ Console.WriteLine($"StarMap - Unable to apply update for mod: {modChange.Mod.Name} to version {modChange.AfterVersion?.Version ?? ""}: {ex}");
}
}