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
108 changes: 108 additions & 0 deletions StarMap.API/BaseAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using KSA;
using System.Reflection;

namespace StarMap.API
{
/// <summary>
/// Marks the main class for a StarMap mod.
/// Only attributes on methods within classes marked with this attribute will be considered.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class StarMapModAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method)]
public abstract class StarMapMethodAttribute : Attribute
{
public abstract bool IsValidSignature(MethodInfo info);
}

/// <summary>
/// Methods marked with this attribute will be called immediately when the mod is loaded.
/// </summary>
/// <remarks>
/// Methods using this attribute must match the following signature:
///
/// <code>
/// public void MethodName(KSA.Mod definingMod);
/// </code>
///
/// Parameter requirements:
/// <list type="bullet">
/// <item>
/// <description>
/// <paramref name="definingMod"/> the KSA.Mod instance that is being loaded.
/// </description>
/// </item>
/// </list>
///
/// Requirements:
/// <list type="bullet">
/// <item><description>Return type must be <see cref="void"/>.</description></item>
/// <item><description>Method must be an instance method (non-static).</description></item>
/// </list>
/// </remarks>
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);
}
}

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Methods using this attribute must follow this signature:
///
/// <code>
/// public void MethodName();
/// </code>
///
/// Specifically:
/// <list type="bullet">
/// <item><description>No parameters are allowed.</description></item>
/// <item><description>Return type must be <see cref="void"/>.</description></item>
/// <item><description>Method must be an instance method (non-static).</description></item>
/// </list>
/// </remarks>
public class StarMapAllModsLoadedAttribute : StarMapMethodAttribute
{
public override bool IsValidSignature(MethodInfo method)
{
return method.ReturnType == typeof(void) &&
method.GetParameters().Length == 0;
}
}

/// <summary>
/// Methods marked with this attribute will be called when KSA is unloaded
/// </summary>
/// <remarks>
/// Methods using this attribute must follow this signature:
///
/// <code>
/// public void MethodName();
/// </code>
///
/// Specifically:
/// <list type="bullet">
/// <item><description>No parameters are allowed.</description></item>
/// <item><description>Return type must be <see cref="void"/>.</description></item>
/// <item><description>Method must be an instance method (non-static).</description></item>
/// </list>
/// </remarks>
public class StarMapUnloadAttribute : StarMapMethodAttribute
{
public override bool IsValidSignature(MethodInfo method)
{
return method.ReturnType == typeof(void) &&
method.GetParameters().Length == 0;
}
}
}
6 changes: 0 additions & 6 deletions StarMap.API/IStarMapInterface.cs

This file was deleted.

10 changes: 0 additions & 10 deletions StarMap.API/IStarMapMod.cs

This file was deleted.

8 changes: 0 additions & 8 deletions StarMap.API/IStarMapOnUi.cs

This file was deleted.

76 changes: 76 additions & 0 deletions StarMap.API/OnGuiAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Reflection;

namespace StarMap.API
{
/// <summary>
/// Methods marked with this attribute will be called before KSA starts creating its ImGui interface.
/// </summary>
/// <remarks>
/// Methods using this attribute must match the following signature:
///
/// <code>
/// public void MethodName(double dt);
/// </code>
///
/// Parameter requirements:
/// <list type="bullet">
/// <item>
/// <description>
/// <paramref name="dt"/>
/// </description>
/// </item>
/// </list>
///
/// Requirements:
/// <list type="bullet">
/// <item><description>Return type must be <see cref="void"/>.</description></item>
/// <item><description>Method must be an instance method (non-static).</description></item>
/// </list>
/// </remarks>
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);

}
}

/// <summary>
/// Methods marked with this attribute will be called when KSA has finished creating its ImGui interface.
/// </summary>
/// <remarks>
/// Methods using this attribute must match the following signature:
///
/// <code>
/// public void MethodName(double dt);
/// </code>
///
/// Parameter requirements:
/// <list type="bullet">
/// <item>
/// <description>
/// <paramref name="dt"/>
/// </description>
/// </item>
/// </list>
///
/// Requirements:
/// <list type="bullet">
/// <item><description>Return type must be <see cref="void"/>.</description></item>
/// <item><description>Method must be an instance method (non-static).</description></item>
/// </list>
/// </remarks>
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);

}
}
}
16 changes: 16 additions & 0 deletions StarMap.API/README.md
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions StarMap.API/StarMap.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,15 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Reference Include="..\..\Import\KSA.dll">
<ExcludeAssets>runtime</ExcludeAssets>
</Reference>
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' != 'Debug'">
<PackageReference Include="StarMap.KSA.Dummy" Version="1.0.6">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>
</Project>
77 changes: 45 additions & 32 deletions StarMap.Core/ModRepository/LoadedModRepository.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using HarmonyLib;
using KSA;
using KSA;
using StarMap.API;
using System.Reflection;
using System.Runtime.Loader;
Expand All @@ -9,29 +8,34 @@ namespace StarMap.Core.ModRepository
internal class LoadedModRepository : IDisposable
{
private readonly AssemblyLoadContext _coreAssemblyLoadContext;
private readonly IEnumerable<Type> _registeredInterfaces = [];
private readonly Dictionary<string, StarMapMethodAttribute> _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<AttributeUsageAttribute>()?.ValidOn.HasFlag(AttributeTargets.Method) == true
)
.Select(ConvertAttributeType)
.OfType<(string attributeName, StarMapMethodAttribute attribute)>()
.ToDictionary();
}

public void LoadMod(Mod mod)
Expand All @@ -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<MethodInfo>();

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<IStarMapMod>())
foreach (var (_, @object, method) in _mods.Get<StarMapAllModsLoadedAttribute>())
{
mod.OnFullyLoaded();
method.Invoke(@object, []);
}
}

public void Dispose()
{
foreach (var mod in _mods.Get<IStarMapMod>())
foreach (var (_, @object, method) in _mods.Get<StarMapUnloadAttribute>())
{
mod.Unload();
method.Invoke(@object, []);
}

_mods.Dispose();
Expand Down
Loading
Loading