Skip to content

Commit

Permalink
Merge pull request #288 from rockfactory/fix-revert-to-vab-loading
Browse files Browse the repository at this point in the history
Fix mods save data loading when reverting to VAB
  • Loading branch information
cheese3660 authored Jan 28, 2024
2 parents 0c99215 + afa4c21 commit 029775c
Showing 1 changed file with 127 additions and 39 deletions.
166 changes: 127 additions & 39 deletions src/SpaceWarp.Core/Patching/SaveGameManager/SaveGamePatches.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using HarmonyLib;
using KSP.Game;
using KSP.Game.Load;
using KSP.IO;
using Newtonsoft.Json;
using SpaceWarp.API.SaveGameManager;
using SpaceWarp.Backend.SaveGameManager;
using SpaceWarp.InternalUtilities;
Expand All @@ -11,6 +13,24 @@ namespace SpaceWarp.Patching.SaveGameManager;
internal class SaveLoadPatches
{
#region Saving

/// <summary>
/// Common method used before serialization to save plugin data, if any.
/// </summary>
private static void SavePluginData(LoadGameData data)
{
// Take the game's LoadGameData, extend it with our own class and copy plugin save data to it
SpaceWarpSerializedSavedGame modSaveData = new();
InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(data.SavedGame, modSaveData);
modSaveData.serializedPluginSaveData = ModSaves.InternalPluginSaveData;
data.SavedGame = modSaveData;

// Initiate save callback for plugins that specified a callback function
foreach (var plugin in ModSaves.InternalPluginSaveData)
{
plugin.SaveEventCallback(plugin.SaveData);
}
}

[HarmonyPatch(typeof(SerializeGameDataFlowAction), MethodType.Constructor, [typeof(string), typeof(LoadGameData)])]
[HarmonyPostfix]
Expand All @@ -27,26 +47,122 @@ SerializeGameDataFlowAction __instance
return;
}

// Take the game's LoadGameData, extend it with our own class and copy plugin save data to it
SpaceWarpSerializedSavedGame modSaveData = new();
InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(data.SavedGame, modSaveData);
modSaveData.serializedPluginSaveData = ModSaves.InternalPluginSaveData;
data.SavedGame = modSaveData;

// Initiate save callback for plugins that specified a callback function
foreach (var plugin in ModSaves.InternalPluginSaveData)
SavePluginData(data);
}

/// <summary>
/// Handles save game serialization in memory, like when launching from VAB. Current
/// game is serialized to a buffer and kept in memory.
/// </summary>
[HarmonyPatch(typeof(SerializeGameToMemoryFlowAction), MethodType.Constructor, [typeof(LoadOrSaveCampaignTicket)])]
[HarmonyPostfix]
private static void InjectToMemoryPluginSaveGameData(
LoadOrSaveCampaignTicket loadOrSaveCampaignTicket,
// ReSharper disable once InconsistentNaming
SerializeGameToMemoryFlowAction __instance
)
{
// Skip plugin data injection if there are no mods that have registered for save/load actions
if (ModSaves.InternalPluginSaveData.Count == 0)
{
plugin.SaveEventCallback(plugin.SaveData);
return;
}

SavePluginData(loadOrSaveCampaignTicket.LoadGameData);
}

#endregion

#region Loading

/// <summary>
/// Common method used after deserialization to load plugin data, if any.
/// </summary>
private static void LoadPluginSaveData(SpaceWarpSerializedSavedGame serializedSavedGame)
{
// Perform plugin load data if plugin data is found in the save file
if (serializedSavedGame.serializedPluginSaveData.Count <= 0) return;

// Iterate through each plugin
foreach (var loadedData in serializedSavedGame.serializedPluginSaveData)
{
// Match registered plugin GUID with the GUID found in the save file
var existingData = ModSaves.InternalPluginSaveData.Find(
p => p.ModGuid == loadedData.ModGuid
);
if (existingData == null)
{
SpaceWarpPlugin.Instance.SWLogger.LogWarning(
$"Saved data for plugin '{loadedData.ModGuid}' found during a load event, however " +
$"that plugin isn't registered for save/load events. Skipping load for this plugin."
);
continue;
}

// Perform a callback if plugin specified a callback function. This is done before plugin data is
// actually updated.
existingData.LoadEventCallback(loadedData.SaveData);

// Copy loaded data to the SaveData object plugin registered
InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(
loadedData.SaveData,
existingData.SaveData
);
}
}

/// <summary>
/// DeserializeBufferFlowAction is used when reverting to VAB / Launch from flight
/// </summary>
[HarmonyPatch(typeof(DeserializeBufferFlowAction), "DoAction")]
[HarmonyPrefix]
private static bool DeserializeBufferLoadedPluginData(
Action resolve,
Action<string> reject,
// ReSharper disable once InconsistentNaming
DeserializeBufferFlowAction __instance
)
{
// Skip plugin deserialization if there are no mods that have registered for save/load actions
if (ModSaves.InternalPluginSaveData.Count == 0)
{
return true;
}

__instance._game.UI.SetLoadingBarText(__instance.Description);
try
{
if (DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings == null)
{
DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings = IOProvider.CloneSerializerSettings(IOProvider.GetDefaultSerializerSettings());
DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings.NullValueHandling = NullValueHandling.Ignore;
}

// Deserialize save buffer to our own class that extends game's SerializedSavedGame
var serializedSavedGame = IOProvider.FromBuffer<SpaceWarpSerializedSavedGame>(__instance._savedGameBuffer, DeserializeBufferFlowAction._ignoreNullValueSerialzationSettings);
__instance._data.SavedGame = serializedSavedGame;
__instance._data.DataLength = (long) __instance._savedGameBuffer.Length;

// Perform plugin load data if plugin data is found in the save file
LoadPluginSaveData(serializedSavedGame);
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
reject(ex.Message);
}

resolve();

return false;
}

/// <summary>
/// DeserializeContentsFlowAction is used when loading a save file
/// </summary>
[HarmonyPatch(typeof(DeserializeContentsFlowAction), "DoAction")]
[HarmonyPrefix]
private static bool DeserializeLoadedPluginData(
private static bool DeserializeContentsLoadedPluginData(
Action resolve,
Action<string> reject,
// ReSharper disable once InconsistentNaming
Expand All @@ -68,35 +184,7 @@ DeserializeContentsFlowAction __instance
__instance._data.DataLength = IOProvider.GetFileSize(__instance._filename);

// Perform plugin load data if plugin data is found in the save file
if (serializedSavedGame.serializedPluginSaveData.Count > 0)
{
// Iterate through each plugin
foreach (var loadedData in serializedSavedGame.serializedPluginSaveData)
{
// Match registered plugin GUID with the GUID found in the save file
var existingData = ModSaves.InternalPluginSaveData.Find(
p => p.ModGuid == loadedData.ModGuid
);
if (existingData == null)
{
SpaceWarpPlugin.Instance.SWLogger.LogWarning(
$"Saved data for plugin '{loadedData.ModGuid}' found during a load event, however " +
$"that plugin isn't registered for save/load events. Skipping load for this plugin."
);
continue;
}

// Perform a callback if plugin specified a callback function. This is done before plugin data is
// actually updated.
existingData.LoadEventCallback(loadedData.SaveData);

// Copy loaded data to the SaveData object plugin registered
InternalExtensions.CopyFieldAndPropertyDataFromSourceToTargetObject(
loadedData.SaveData,
existingData.SaveData
);
}
}
LoadPluginSaveData(serializedSavedGame);
}
catch (Exception ex)
{
Expand Down

0 comments on commit 029775c

Please sign in to comment.