diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs index 0dfd5f0..dcd8033 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanHostHelper.cs @@ -15,6 +15,7 @@ using MegaCrit.Sts2.Core.Platform; using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Saves; +// ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedType.Global // ReSharper disable ClassNeverInstantiated.Global @@ -137,7 +138,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers ushort port, int maxPlayers) { var readSaveResult = - SaveManager.Instance.LoadAndCanonicalizeMultiplayerRunSave( + LanRunSaveManagerHelper.LoadAndCanonicalizeMultiplayerRunSave( PlatformUtil.GetLocalPlayerId(PlatformType.None)); if (!readSaveResult.Success || readSaveResult.SaveData == null) { @@ -176,7 +177,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers return; var readSaveResult = - SaveManager.Instance.LoadAndCanonicalizeMultiplayerRunSave( + LanRunSaveManagerHelper.LoadAndCanonicalizeMultiplayerRunSave( PlatformUtil.GetLocalPlayerId(PlatformType.None)); if (readSaveResult is { Success: true, SaveData: not null }) { @@ -205,7 +206,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers $"ERROR: Failed to load multiplayer run save: status={readSaveResult.Status}. Deleting current run..."); } - SaveManager.Instance.DeleteCurrentMultiplayerRun(); + LanRunSaveManagerHelper.DeleteCurrentMultiplayerRun(); updateButtons(); } } diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs new file mode 100644 index 0000000..458b646 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/LanRunSaveManagerHelper.cs @@ -0,0 +1,99 @@ +using HarmonyLib; +using MegaCrit.Sts2.Core.Logging; +using MegaCrit.Sts2.Core.Runs; +using MegaCrit.Sts2.Core.Saves; +using MegaCrit.Sts2.Core.Saves.Managers; +using MegaCrit.Sts2.Core.Saves.Migrations; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable ClassNeverInstantiated.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Helpers +{ + internal class LanRunSaveManagerHelper + { + public static ISaveStore SaveStore => + Traverse.Create(SaveManager.Instance).Field("_saveStore").GetValue(); + + public static MigrationManager MigrationManager => Traverse.Create(SaveManager.Instance) + .Field("_migrationManager").GetValue(); + + public static IProfileIdProvider ProfileIdProvider => SaveManager.Instance; + + public static string CurrentMultiplayerRunSavePath => + RunSaveManager.GetRunSavePath(ProfileIdProvider.CurrentProfileId, "current_lan_run_mp.save"); + + public static bool HasMultiplayerRunSave => SaveStore.FileExists(CurrentMultiplayerRunSavePath); + + public static ReadSaveResult LoadAndCanonicalizeMultiplayerRunSave(ulong localPlayerId) + { + var readSaveResult = LoadMultiplayerRunSave(); + if (readSaveResult is { Success: true, SaveData: not null }) + { + try + { + var data = RunManager.CanonicalizeSave(readSaveResult.SaveData, localPlayerId); + return new ReadSaveResult(data, ReadSaveStatus.Success); + } + catch (Exception value) + { + Log.Error($"Multiplayer run save validation failed: {value}"); + RenameBrokenMultiplayerRunSave(ReadSaveStatus.ValidationFailed); + return new ReadSaveResult(ReadSaveStatus.ValidationFailed, + $"Save file validation failed: {value}"); + } + } + + return readSaveResult; + } + + public static ReadSaveResult LoadMultiplayerRunSave() + { + var readSaveResult = MigrationManager.LoadSave(CurrentMultiplayerRunSavePath); + if (readSaveResult.Success) + { + return readSaveResult; + } + + if (readSaveResult.Status == ReadSaveStatus.FileNotFound) + { + Log.Info("Multiplayer run save file not found at " + CurrentMultiplayerRunSavePath); + } + else if (!readSaveResult.Status.IsRecoverable()) + { + Log.Error( + $"Failed to load multiplayer run save: status={readSaveResult.Status} msg={readSaveResult.ErrorMessage}"); + } + else + { + Log.Warn( + $"Multiplayer run save had recoverable issues: status={readSaveResult.Status} msg={readSaveResult.ErrorMessage}"); + } + + return readSaveResult; + } + + public static void DeleteCurrentMultiplayerRun() + { + SaveStore.DeleteFile(CurrentMultiplayerRunSavePath); + } + + public static void RenameBrokenMultiplayerRunSave(ReadSaveStatus status) + { + try + { + if (HasMultiplayerRunSave) + { + var text = CorruptFileHandler.GenerateCorruptFilePath(CurrentMultiplayerRunSavePath, status); + SaveStore.RenameFile(CurrentMultiplayerRunSavePath, text); + Log.Error( + $"Corrupt multiplayer run save detected: Renamed '{CurrentMultiplayerRunSavePath}' to '{text}'"); + } + } + catch (Exception ex) + { + Log.Warn("Failed to rename broken multiplayer run save: " + ex.Message); + } + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Helpers/SettingsHelper.cs b/SlayTheSpire2.LAN.Multiplayer/Helpers/SettingsHelper.cs index 101f47e..f7739be 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Helpers/SettingsHelper.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Helpers/SettingsHelper.cs @@ -12,7 +12,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Helpers public readonly SettingsModel SettingsModel; - private readonly GodotFileIo _modsDir = new($"{UserDataPathProvider.GetAccountScopedBasePath(null)}/mods"); + private readonly GodotFileIo _modsDir = + new(Path.Combine(UserDataPathProvider.GetAccountScopedBasePath(null), "mods")); private SettingsHelper() { diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs index 7ab5be0..1eb8fbf 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/NMultiplayerSubmenuPatch.cs @@ -5,7 +5,6 @@ using MegaCrit.Sts2.Core.Helpers; using MegaCrit.Sts2.Core.Nodes.GodotExtensions; using MegaCrit.Sts2.Core.Nodes.Screens.MainMenu; using MegaCrit.Sts2.Core.Runs; -using MegaCrit.Sts2.Core.Saves; using SlayTheSpire2.LAN.Multiplayer.Helpers; // ReSharper disable UnusedMember.Global @@ -111,17 +110,18 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs { if (NMultiplayerSubmenuButtonHelpers.LanHostButton != null) { - NMultiplayerSubmenuButtonHelpers.LanHostButton.Visible = !SaveManager.Instance.HasMultiplayerRunSave; + NMultiplayerSubmenuButtonHelpers.LanHostButton.Visible = !LanRunSaveManagerHelper.HasMultiplayerRunSave; } if (NMultiplayerSubmenuButtonHelpers.LanLoadButton != null) { - NMultiplayerSubmenuButtonHelpers.LanLoadButton.Visible = SaveManager.Instance.HasMultiplayerRunSave; + NMultiplayerSubmenuButtonHelpers.LanLoadButton.Visible = LanRunSaveManagerHelper.HasMultiplayerRunSave; } if (NMultiplayerSubmenuButtonHelpers.LanAbandonButton != null) { - NMultiplayerSubmenuButtonHelpers.LanAbandonButton.Visible = SaveManager.Instance.HasMultiplayerRunSave; + NMultiplayerSubmenuButtonHelpers.LanAbandonButton.Visible = + LanRunSaveManagerHelper.HasMultiplayerRunSave; } } } diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs index dec2f5f..b1bffa3 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/NSettingsScreenPatch.cs @@ -11,7 +11,7 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs [HarmonyPatch(typeof(NSettingsScreen), "_Ready")] internal class NSettingsScreenPatch { - public static void Prefix(NSettingsScreen __instance) + private static void Prefix(NSettingsScreen __instance) { var moddingNode = __instance.GetNode("%Modding"); diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs new file mode 100644 index 0000000..b29e230 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/RunSaveManagerPatch.cs @@ -0,0 +1,61 @@ +using System.Text.Json; +using HarmonyLib; +using MegaCrit.Sts2.Core.Helpers; +using MegaCrit.Sts2.Core.Multiplayer.Game; +using MegaCrit.Sts2.Core.Platform; +using MegaCrit.Sts2.Core.Rooms; +using MegaCrit.Sts2.Core.Runs; +using MegaCrit.Sts2.Core.Saves; +using MegaCrit.Sts2.Core.Saves.Managers; +using SlayTheSpire2.LAN.Multiplayer.Helpers; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch(typeof(RunSaveManager), "SaveRun")] + internal class RunSaveManagerSaveRunPatch + { + private static bool Prefix(RunSaveManager __instance, AbstractRoom? preFinishedRoom, bool ____forceSynchronous, + ISaveStore ____saveStore, Action? ___Saved, ref Task __result) + { + if (!RunManager.Instance.ShouldSave || (RunManager.Instance.NetService.Type != NetGameType.Singleplayer && + RunManager.Instance.NetService.Type != NetGameType.Host)) + return false; + + __result = TaskHelper.RunSafely(SaveRun(__instance, preFinishedRoom, ____forceSynchronous, ____saveStore, + ___Saved)); + + return false; + } + + private static async Task SaveRun(RunSaveManager runSaveManager, AbstractRoom? preFinishedRoom, + bool forceSynchronous, + ISaveStore saveStore, Action? saved) + { + var value = RunManager.Instance.ToSave(preFinishedRoom); + var savePath = RunManager.Instance.NetService.Type.IsMultiplayer() + ? RunManager.Instance.NetService.Platform == PlatformType.None + ? LanRunSaveManagerHelper.CurrentMultiplayerRunSavePath + : Traverse.Create(runSaveManager).Property("CurrentMultiplayerRunSavePath").GetValue() + : Traverse.Create(runSaveManager).Property("CurrentRunSavePath").GetValue(); + using var stream = new MemoryStream(); + if (!forceSynchronous) + { + await JsonSerializer.SerializeAsync(stream, value, + JsonSerializationUtility.GetTypeInfo(), CancellationToken.None); + } + else + { + await JsonSerializer.SerializeAsync(stream, value, + JsonSerializationUtility.GetTypeInfo()); + } + + stream.Seek(0L, SeekOrigin.Begin); + await saveStore.WriteFileAsync(savePath, stream.ToArray()); + + saved?.Invoke(); + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj b/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj index 4948492..52e0a05 100644 --- a/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj +++ b/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj @@ -4,6 +4,8 @@ net9.0 enable enable + 1.1.0.0 + 1.1.0.0