using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("UndoEverywhere")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.3.0")] [assembly: AssemblyInformationalVersion("0.2.3+551a0b5ba64814d29c82a426a5a310a0632ffcd3")] [assembly: AssemblyProduct("UndoEverywhere")] [assembly: AssemblyTitle("UndoEverywhere")] [assembly: AssemblyVersion("0.2.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace UndoEverywhere { [BepInPlugin("UndoEverywhere", "UndoEverywhere", "0.2.3")] public class Plugin : BaseUnityPlugin { private class CoroutineRunner : MonoBehaviour { } internal static ManualLogSource Logger = new ManualLogSource("UndoEverywhere"); private static CoroutineRunner? coroutineRunner; public void Awake() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"Plugin UndoEverywhere is loaded!"); Harmony val = new Harmony("UndoEverywhere"); try { val.PatchAll(); } catch (Exception arg) { Logger.LogError((object)$"PatchAll failed -- some patches are NOT applied: {arg}"); } } internal static Coroutine RunCoroutine(IEnumerator routine) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if ((Object)(object)coroutineRunner == (Object)null) { GameObject val = new GameObject("CoroutineRunner"); Object.DontDestroyOnLoad((Object)val); coroutineRunner = val.AddComponent(); } return ((MonoBehaviour)coroutineRunner).StartCoroutine(routine); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "UndoEverywhere"; public const string PLUGIN_NAME = "UndoEverywhere"; public const string PLUGIN_VERSION = "0.2.3"; } } namespace UndoEverywhere.Patches { [HarmonyPatch(typeof(PauseDialog), "ShouldShowRestartBattleButton")] internal static class PauseDialog_RestartOutsideBattle_Patch { private static void Postfix(SaveManager ___saveManager, ref bool __result) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!__result && !((Object)(object)___saveManager == (Object)null) && StageStartMarker.HasCheckpoint(___saveManager) && StageStartMarker.IsRestartableState(___saveManager.GetGameSequence())) { __result = true; } } } [HarmonyPatch(typeof(PauseDialog), "RefreshRestartBattleButton")] internal static class PauseDialog_RestartLabel_Patch { private static string? vanillaText; internal static bool RingRestartArmed { get; private set; } internal static string? VanillaConfirmText { get; private set; } private static void Postfix(SaveManager ___saveManager, TMP_Text ___restartBattleNormalLabel, string ___confirmRestartBattleKey) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (vanillaText == null) { vanillaText = ___restartBattleNormalLabel.text; } bool flag = (Object)(object)___saveManager != (Object)null && !___saveManager.IsInBattle() && StageStartMarker.HasCheckpoint(___saveManager) && StageStartMarker.IsRestartableState(___saveManager.GetGameSequence()); ___restartBattleNormalLabel.text = (flag ? "Restart Ring" : vanillaText); RingRestartArmed = flag; if (flag) { VanillaConfirmText = (string.IsNullOrEmpty(___confirmRestartBattleKey) ? null : LocalizationExtensions.Localize(___confirmRestartBattleKey, (ILocalizationParameterContext)null)); } } } [HarmonyPatch(typeof(ScreenManager), "ShowConfirmationDialog")] internal static class ScreenManager_RingRestartConfirmText_Patch { private static void Prefix(ref string content) { if (PauseDialog_RestartLabel_Patch.RingRestartArmed) { string vanillaConfirmText = PauseDialog_RestartLabel_Patch.VanillaConfirmText; if (!string.IsNullOrEmpty(vanillaConfirmText) && content == vanillaConfirmText) { content = "Are you sure you want to restart the current ring?"; } } } } [HarmonyPatch(typeof(SaveManager), "RestartBattle")] internal static class SaveManager_RestartStage_Patch { private static bool Prefix(SaveManager __instance) { if (__instance.IsInBattle()) { return true; } if (!StageStartMarker.TryGetCheckpointJson(__instance, out string json)) { Plugin.Logger.LogWarning((object)"[SaveManager_RestartStage_Patch] no stage-start checkpoint set, aborting restart"); return false; } try { Plugin.RunCoroutine(RestartStageCoroutine(__instance, json)); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SaveManager_RestartStage_Patch] exception starting coroutine: {arg}"); } return false; } private static IEnumerator RestartStageCoroutine(SaveManager saveManager, string checkpointJson) { try { JsonUtility.FromJsonOverwrite(checkpointJson, StageStartMarker.GetActiveSaveData(saveManager)); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SaveManager_RestartStage_Patch] exception restoring checkpoint: {arg}"); yield break; } GameStateManager gameStateManager = AllGameManagers.Instance.GetGameStateManager(); ScreenManager screenManager = AllGameManagers.Instance.GetScreenManager(); RunType runType = saveManager.GetRunType(); string sharecode = saveManager.GetShareCode(); gameStateManager.LeaveGame(); screenManager.ReturnToMainMenu(); yield return (object)new WaitUntil((Func)(() => screenManager.GetScreenActive((ScreenName)15))); yield return null; yield return null; yield return null; bool runLoaded = false; gameStateManager.StartGame(runType, sharecode, (Action)delegate { runLoaded = true; }); yield return (object)new WaitUntil((Func)(() => runLoaded)); } } internal static class StageStartMarker { [Serializable] private class CheckpointHeader { public string id = string.Empty; } private static readonly PropertyInfo ActiveSaveDataProperty = AccessTools.Property(typeof(SaveManager), "ActiveSaveData"); private static string? cachedPath; private static DateTime cachedStamp; private static long cachedLength; private static string? cachedRunId; private static string? CheckpointPath(SaveManager saveManager) { string activeSavePath = saveManager.ActiveSavePath; if (!string.IsNullOrEmpty(activeSavePath)) { return activeSavePath + ".stagestart"; } return null; } internal static object GetActiveSaveData(SaveManager saveManager) { return ActiveSaveDataProperty.GetValue(saveManager); } private static bool HasRealRun(SaveManager saveManager) { if (saveManager.HasRun((RunType)(-1), "", true)) { return saveManager.GetCurrentDistance() >= 0; } return false; } internal static void CaptureCurrent(SaveManager saveManager, string reason) { string text = CheckpointPath(saveManager); if (text == null || !HasRealRun(saveManager)) { Plugin.Logger.LogDebug((object)("[StageStartMarker] skipped capture (" + reason + "): no set-up run yet")); return; } try { string contents = JsonUtility.ToJson(GetActiveSaveData(saveManager)); File.WriteAllText(text, contents); InvalidateRunIdCache(); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StageStartMarker] failed to capture checkpoint ({reason}) to '{text}': {arg}"); } } private static string? LiveRunId(SaveManager saveManager) { object activeSaveData = GetActiveSaveData(saveManager); object obj = ((activeSaveData is SaveData) ? activeSaveData : null); if (obj == null) { return null; } return ((BaseSaveData)obj).GetID(); } private static string? CheckpointRunId(string path) { FileInfo fileInfo = new FileInfo(path); if (!fileInfo.Exists) { return null; } if (path == cachedPath && fileInfo.LastWriteTimeUtc == cachedStamp && fileInfo.Length == cachedLength) { return cachedRunId; } try { cachedRunId = JsonUtility.FromJson(File.ReadAllText(path))?.id; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StageStartMarker] failed to read checkpoint run id from '{path}': {arg}"); cachedRunId = null; } cachedPath = path; cachedStamp = fileInfo.LastWriteTimeUtc; cachedLength = fileInfo.Length; return cachedRunId; } private static bool IsCheckpointForCurrentRun(SaveManager saveManager, string path) { string text = LiveRunId(saveManager); if (!string.IsNullOrEmpty(text)) { return CheckpointRunId(path) == text; } return false; } internal static bool HasCheckpoint(SaveManager saveManager) { string text = CheckpointPath(saveManager); if (text != null) { return IsCheckpointForCurrentRun(saveManager, text); } return false; } private static void InvalidateRunIdCache() { cachedPath = null; cachedRunId = null; } internal static void DeleteCheckpoint(SaveManager saveManager, string reason) { string text = CheckpointPath(saveManager); if (text == null || !File.Exists(text)) { return; } try { File.Delete(text); InvalidateRunIdCache(); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StageStartMarker] failed to delete checkpoint ({reason}) at '{text}': {arg}"); } } internal static bool TryGetCheckpointJson(SaveManager saveManager, out string json) { string text = CheckpointPath(saveManager); if (text == null || !IsCheckpointForCurrentRun(saveManager, text)) { json = string.Empty; return false; } json = File.ReadAllText(text); return true; } internal static bool IsRestartableState(GameSequence sequence) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if ((int)sequence != 1 && (int)sequence != 6 && (int)sequence != 4) { return (int)sequence > 0; } return false; } } [HarmonyPatch(typeof(MapScreen), "HandleCurrentGameSequence")] internal static class MapScreen_StageStartBackfill_Patch { private static void Postfix(SaveManager ___saveManager) { if ((Object)(object)___saveManager != (Object)null && ___saveManager.IsAlive() && !StageStartMarker.HasCheckpoint(___saveManager)) { StageStartMarker.CaptureCurrent(___saveManager, "MapScreen.HandleCurrentGameSequence"); } } } [HarmonyPatch(typeof(GameStateManager), "EndGame")] internal static class GameStateManager_ClearCheckpointOnRunEnd_Patch { private static void Postfix(EndGameReason endGameReason) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) AllGameManagers instance = AllGameManagers.Instance; SaveManager val = ((instance != null) ? instance.GetSaveManager() : null); if ((Object)(object)val != (Object)null && (int)val.GetGameSequence() == 4) { StageStartMarker.DeleteCheckpoint(val, $"run ended ({endGameReason})"); } } } [HarmonyPatch(typeof(GameScreen), "ShowScreenAfterBattle")] internal static class GameScreen_StageStartMarker_Patch { private static void Postfix(SaveManager ___saveManager) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)___saveManager.GetGameSequence() != 4) { StageStartMarker.CaptureCurrent(___saveManager, "ShowScreenAfterBattle"); } } } }