diff --git a/StarMap.API/README.md b/StarMap.API/README.md
index 0a90ade..41880b9 100644
--- a/StarMap.API/README.md
+++ b/StarMap.API/README.md
@@ -220,3 +220,31 @@ Methods marked with this attribute will be called after `KSA.Program.OnFrame` is
[StarMapAfterOnFrame]
public void ModMethod(double currentPlayerTime, double dtPlayer);
```
+
+#### StarMapAfterSave
+
+Namespace: `StarMap.API`
+Assembly: `StarMap.API`
+Target: Method
+
+Methods marked with this attribute will be called after KSA has written a save, with the directory
+it was written to. A mod that keeps per-save state can write its own file beside `universe.xml`.
+
+```csharp
+[StarMapAfterSave]
+public void ModMethod(System.IO.DirectoryInfo saveDirectory);
+```
+
+#### StarMapAfterLoad
+
+Namespace: `StarMap.API`
+Assembly: `StarMap.API`
+Target: Method
+
+Methods marked with this attribute will be called after KSA has loaded a save, with the directory
+it was read from.
+
+```csharp
+[StarMapAfterLoad]
+public void ModMethod(System.IO.DirectoryInfo saveDirectory);
+```
diff --git a/StarMap.API/SaveAttributes.cs b/StarMap.API/SaveAttributes.cs
new file mode 100644
index 0000000..6f8bb70
--- /dev/null
+++ b/StarMap.API/SaveAttributes.cs
@@ -0,0 +1,75 @@
+using System.IO;
+using System.Reflection;
+
+namespace StarMap.API
+{
+ ///
+ /// Methods marked with this attribute will be called after KSA has written a save.
+ ///
+ ///
+ /// Methods using this attribute must match the following signature:
+ ///
+ ///
+ /// public void MethodName(System.IO.DirectoryInfo saveDirectory);
+ ///
+ ///
+ /// Parameter requirements:
+ ///
+ /// -
+ ///
+ /// is the folder the save was written to.
+ ///
+ ///
+ ///
+ ///
+ /// Requirements:
+ ///
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public sealed class StarMapAfterSaveAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 1 &&
+ method.GetParameters()[0].ParameterType == typeof(DirectoryInfo);
+ }
+ }
+
+ ///
+ /// Methods marked with this attribute will be called after KSA has loaded a save.
+ ///
+ ///
+ /// Methods using this attribute must match the following signature:
+ ///
+ ///
+ /// public void MethodName(System.IO.DirectoryInfo saveDirectory);
+ ///
+ ///
+ /// Parameter requirements:
+ ///
+ /// -
+ ///
+ /// is the folder the save was read from.
+ ///
+ ///
+ ///
+ ///
+ /// Requirements:
+ ///
+ /// - Return type must be .
+ /// - Method must be an instance method (non-static).
+ ///
+ ///
+ public sealed class StarMapAfterLoadAttribute : StarMapMethodAttribute
+ {
+ public override bool IsValidSignature(MethodInfo method)
+ {
+ return method.ReturnType == typeof(void) &&
+ method.GetParameters().Length == 1 &&
+ method.GetParameters()[0].ParameterType == typeof(DirectoryInfo);
+ }
+ }
+}
diff --git a/StarMap.Core/Patches/UniverseDataPatcher.cs b/StarMap.Core/Patches/UniverseDataPatcher.cs
new file mode 100644
index 0000000..4bae1ee
--- /dev/null
+++ b/StarMap.Core/Patches/UniverseDataPatcher.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using HarmonyLib;
+using KSA;
+using StarMap.API;
+
+namespace StarMap.Core.Patches
+{
+ ///
+ /// Tells mods when a save is written or read, and which directory it is in.
+ ///
+ [HarmonyPatch(typeof(UniverseData))]
+ internal static class UniverseDataPatcher
+ {
+ private const string WriteToMethodName = "WriteTo";
+ private const string LoadFromMethodName = "LoadFrom";
+
+ // WriteTo is overloaded; the DirectoryInfo one is the save on disk.
+ [HarmonyPatch(WriteToMethodName, [typeof(DirectoryInfo)])]
+ [HarmonyPostfix]
+ public static void AfterWriteTo(DirectoryInfo directory)
+ {
+ var methods = StarMapCore.Instance?.Loader.ModRegistry.Get() ?? [];
+
+ foreach (var (_, @object, method) in methods)
+ {
+ method.Invoke(@object, [directory]);
+ }
+ }
+
+ [HarmonyPatch(LoadFromMethodName)]
+ [HarmonyPostfix]
+ public static void AfterLoadFrom(UncompressedSave uncompressedSave)
+ {
+ var methods = StarMapCore.Instance?.Loader.ModRegistry.Get() ?? [];
+
+ foreach (var (_, @object, method) in methods)
+ {
+ method.Invoke(@object, [uncompressedSave.Directory]);
+ }
+ }
+ }
+}