Skip to content

Commit

Permalink
Added ext data support for KKS game save files
Browse files Browse the repository at this point in the history
  • Loading branch information
ManlyMarco committed Aug 29, 2021
1 parent 3a4d03a commit 8fb3533
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/KKS_ExtensibleSaveFormat/KKS.ExtendedSave.SaveData.Hooks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection.Emit;
using BepisPlugins;
using HarmonyLib;
using MessagePack;
using SaveData;
using UnityEngine;

namespace ExtensibleSaveFormat
{
public partial class ExtendedSave
{
internal static partial class Hooks
{
#region Loading

[HarmonyPostfix, HarmonyPatch(typeof(WorldData), nameof(WorldData.SetBytes))]
private static void SaveDataLoadHook(BinaryReader br, ref WorldData saveData)
{
try
{
string marker = br.ReadString();
int version = br.ReadInt32();

int length = br.ReadInt32();

if (marker == Marker && version == DataVersion && length > 0)
{
byte[] bytes = br.ReadBytes(length);
var dictionary = MessagePackSerializer.Deserialize<Dictionary<string, PluginData>>(bytes);

internalSaveDataDictionary.Set(saveData, dictionary);
}
else
internalSaveDataDictionary.Set(saveData, new Dictionary<string, PluginData>()); //Overriding with empty data just in case there is some remnant from former loads.

}
catch (EndOfStreamException)
{
// Incomplete/non-existant data
internalSaveDataDictionary.Set(saveData, new Dictionary<string, PluginData>());
}
catch (InvalidOperationException)
{
// Invalid/unexpected deserialized data
internalSaveDataDictionary.Set(saveData, new Dictionary<string, PluginData>());
}

//Firing the event in any case
SaveDataReadEvent(saveData);
}

#endregion

#region Saving

// Nope, not patching the save lambda, not doing it, noooope
[HarmonyPostfix, HarmonyPatch(typeof(WorldData), nameof(WorldData.GetBytes), typeof(WorldData))]
private static void SaveDataSaveHook(WorldData saveData, ref byte[] __result)
{
try
{
SaveDataWriteEvent(saveData);

Logger.Log(BepInEx.Logging.LogLevel.Debug, "SaveData hook!");

Dictionary<string, PluginData> extendedData = GetAllExtendedData(saveData);
if (extendedData == null)
return;

// Append the ext data
using (var ms = new MemoryStream())
{
using (BinaryWriter bw = new BinaryWriter(ms))
{
// Write the original data first
bw.Write(__result);

// Then write our data
bw.Write(Marker);
bw.Write(DataVersion);
byte[] data = MessagePackSerializer.Serialize(extendedData);
bw.Write(data.Length);
bw.Write(data);
}

// Replace the result, not the most efficient way but save files shouldn't be big enough to matter since it's all done in memory
__result = ms.ToArray();
}
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
}
}

#endregion
}
}
}
102 changes: 102 additions & 0 deletions src/KKS_ExtensibleSaveFormat/KKS.ExtendedSave.SaveData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using BepInEx;
using BepisPlugins;
using SaveData;

namespace ExtensibleSaveFormat
{
public partial class ExtendedSave : BaseUnityPlugin
{
internal static WeakKeyDictionary<WorldData, Dictionary<string, PluginData>> internalSaveDataDictionary = new WeakKeyDictionary<WorldData, Dictionary<string, PluginData>>();

/// <summary>
/// Get a dictionary of ID, PluginData containing all extended data for a SaveData
/// </summary>
/// <param name="saveData">SaveData for which to get extended data</param>
/// <returns>Dictionary of ID, PluginData</returns>
public static Dictionary<string, PluginData> GetAllExtendedData(SaveData.WorldData saveData) => internalSaveDataDictionary.Get(saveData);

/// <summary>
/// Get PluginData for a SaveData for the specified extended save data ID
/// </summary>
/// <param name="saveData">SaveData for which to get extended save file</param>
/// <param name="id">ID of the data saved to the save file</param>
/// <returns>PluginData</returns>
public static PluginData GetExtendedDataById(WorldData saveData, string id)
{
if (saveData == null || id == null)
return null;

var dict = internalSaveDataDictionary.Get(saveData);

return dict != null && dict.TryGetValue(id, out var extendedSection) ? extendedSection : null;
}

/// <summary>
/// Set PluginData for a SaveData for the specified extended save data ID
/// </summary>
/// <param name="saveData">SaveData for which to set extended data</param>
/// <param name="id">ID of the data to be saved to the save file</param>
/// <param name="extendedFormatData">PluginData to save to the save file</param>
public static void SetExtendedDataById(WorldData saveData, string id, PluginData extendedFormatData)
{
Dictionary<string, PluginData> chaDictionary = internalSaveDataDictionary.Get(saveData);

if (chaDictionary == null)
{
chaDictionary = new Dictionary<string, PluginData>();
internalSaveDataDictionary.Set(saveData, chaDictionary);
}

chaDictionary[id] = extendedFormatData;
}


/// <summary> SaveData event handler </summary>
public delegate void SaveDataEventHandler(WorldData saveData);
/// <summary> Register methods to trigger on save file being saved </summary>
public static event SaveDataEventHandler SaveDataBeingSaved;
/// <summary> Register methods to trigger on save file being loaded </summary>
public static event SaveDataEventHandler SaveDataBeingLoaded;


internal static void SaveDataWriteEvent(WorldData saveData)
{
if (SaveDataBeingSaved == null)
return;

foreach (var entry in SaveDataBeingSaved.GetInvocationList())
{
var handler = (SaveDataEventHandler)entry;
try
{
handler.Invoke(saveData);
}
catch (Exception ex)
{
Logger.LogError($"Subscriber crash in {nameof(ExtendedSave)}.{nameof(SaveDataBeingSaved)} - {ex}");
}
}
}

internal static void SaveDataReadEvent(WorldData saveData)
{
if (SaveDataBeingLoaded == null)
return;

foreach (var entry in SaveDataBeingLoaded.GetInvocationList())
{
var handler = (SaveDataEventHandler)entry;
try
{
handler.Invoke(saveData);
}
catch (Exception ex)
{
Logger.LogError($"Subscriber crash in {nameof(ExtendedSave)}.{nameof(SaveDataBeingLoaded)} - {ex}");
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/KKS_ExtensibleSaveFormat/KKS_ExtensibleSaveFormat.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="KKS.ExtendedSave.SaveData.cs" />
<Compile Include="KKS.ExtendedSave.SaveData.Hooks.cs" />
<Compile Include="KKS.ExtendedSave.Events.cs" />
<Compile Include="KKS.ExtendedSave.Hooks.cs" />
<Compile Include="KKS.ExtendedSave.cs" />
Expand Down

0 comments on commit 8fb3533

Please sign in to comment.