Decompiled source of RunSaves v1.2.2
plugins/RunSaves.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using Microsoft.CodeAnalysis; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("RunSaves")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.2.0")] [assembly: AssemblyInformationalVersion("1.2.2+2af9320131467f2bae516e4261b13e5fe2047bc9")] [assembly: AssemblyProduct("RunSaves")] [assembly: AssemblyTitle("RunSaves")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 RunSaves { internal static class Game { private static RunManager run; private static PartyLevelManager party; private static ReviveManager revives; private static ImprovementsManager improvements; private static StoreManager store; internal static RunManager Run => Find<RunManager>(ref run); internal static PartyLevelManager Party => Find<PartyLevelManager>(ref party); internal static ReviveManager Revives => Find<ReviveManager>(ref revives); internal static ImprovementsManager Improvements => Find<ImprovementsManager>(ref improvements); internal static StoreManager Store => Find<StoreManager>(ref store); internal static bool StoreBusy { get { try { StoreManager val = Store; if ((Object)(object)val == (Object)null) { return false; } return val.showStoresCoroutine != null || val.IsShopPanelActive || val.IsExitingStore; } catch { return false; } } } internal static bool IsServer { get { try { NetworkManager singleton = NetworkManager.Singleton; return (Object)(object)singleton != (Object)null && singleton.IsListening && singleton.IsServer; } catch { return false; } } } internal static bool IsClientOnly { get { try { NetworkManager singleton = NetworkManager.Singleton; return (Object)(object)singleton != (Object)null && singleton.IsListening && !singleton.IsServer; } catch { return false; } } } private static T Find<T>(ref T cache) where T : Object { if (cache != null && !((Il2CppObjectBase)cache).WasCollected && (Object)(object)cache != (Object)null) { return cache; } cache = Object.FindObjectOfType<T>(); return cache; } internal static void ForgetObjects() { run = null; party = null; revives = null; improvements = null; store = null; } internal static List<GameplayPlayerIdentity> Players() { List<GameplayPlayerIdentity> list = new List<GameplayPlayerIdentity>(); foreach (GameplayPlayerIdentity item in Object.FindObjectsOfType<GameplayPlayerIdentity>()) { if ((Object)(object)item != (Object)null && ((NetworkBehaviour)item).IsSpawned) { list.Add(item); } } list.Sort((GameplayPlayerIdentity a, GameplayPlayerIdentity b) => ((NetworkBehaviour)a).OwnerClientId.CompareTo(((NetworkBehaviour)b).OwnerClientId)); return list; } internal static GameplayPlayerIdentity PlayerFor(ulong clientId) { foreach (GameplayPlayerIdentity item in Object.FindObjectsOfType<GameplayPlayerIdentity>()) { if ((Object)(object)item != (Object)null && ((NetworkBehaviour)item).IsSpawned && ((NetworkBehaviour)item).OwnerClientId == clientId) { return item; } } return null; } internal static GameplayPlayerIdentity LocalPlayer() { foreach (GameplayPlayerIdentity item in Object.FindObjectsOfType<GameplayPlayerIdentity>()) { if ((Object)(object)item != (Object)null && ((NetworkBehaviour)item).IsSpawned && ((NetworkBehaviour)item).IsOwner) { return item; } } return null; } internal static string KeyOf(GameplayPlayerIdentity p) { try { string platformPlayerId = p.PlatformPlayerId; if (!string.IsNullOrWhiteSpace(platformPlayerId)) { return platformPlayerId; } } catch { } return "name:" + NameOf(p); } internal static string NameOf(GameplayPlayerIdentity p) { try { string displayName = p.GetDisplayName(); if (!string.IsNullOrWhiteSpace(displayName)) { return displayName; } } catch { } return $"Player {((NetworkBehaviour)p).OwnerClientId}"; } internal static string CharacterOf(GameplayPlayerIdentity p) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) try { CharacterResource character = p.Character; if ((Object)(object)character != (Object)null) { return ((object)character.Id/*cast due to .constrained prefix*/).ToString(); } } catch { } return ""; } internal static List<SpellType> SpellsOf(GameplayPlayerIdentity p) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) List<SpellType> list = new List<SpellType>(); try { SpellEquipper spellEquipper = p.spellEquipper; NetworkList<SpellTypeNetworkWrapper> val = ((spellEquipper != null) ? spellEquipper.equippedSpells : null); if (val != null) { for (int i = 0; i < val.Count; i++) { list.Add(val[i].SpellType); } } } catch { } return list; } internal static string WorldName(RunManager rm) { try { RunConfiguration configuration = rm.Configuration; if ((Object)(object)configuration == (Object)null) { return "Unknown world"; } if ((Object)(object)configuration == (Object)(object)rm.verdantMeadowsConfiguration) { return "Verdant Meadows"; } if ((Object)(object)configuration == (Object)(object)rm.frozenAncientsConfiguration) { return "Frozen Ancients"; } if ((Object)(object)configuration == (Object)(object)rm.pyrestormPitConfiguration) { return "Pyrestorm Pit"; } if ((Object)(object)configuration == (Object)(object)rm.astralPlanesConfiguration) { return "Astral Planes"; } return ((Object)configuration).name; } catch { return "Unknown world"; } } internal static string ConfigName(RunManager rm) { try { RunConfiguration configuration = rm.Configuration; return ((configuration != null) ? ((Object)configuration).name : null) ?? ""; } catch { return ""; } } internal static bool InMainMenu() { try { return (Object)(object)Object.FindObjectOfType<MainMenuManager>() != (Object)null && (Object)(object)Run == (Object)null; } catch { return false; } } internal static bool TryParse<T>(string s, out T value) where T : struct, Enum { return Enum.TryParse<T>(s, out value); } internal static PlayerArtifacts ArtifactsOf(GameplayPlayerIdentity p) { try { IPlayerArtifacts artifacts = p.Artifacts; return (artifacts != null) ? ((Il2CppObjectBase)artifacts).TryCast<PlayerArtifacts>() : null; } catch { return null; } } internal static Dictionary<string, float> ArtifactStacksOf(GameplayPlayerIdentity p) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, float> dictionary = new Dictionary<string, float>(); try { PlayerArtifacts obj = ArtifactsOf(p); List<IArtifactValue> val = ((obj != null) ? obj.artifactValues : null); if (val == null) { return dictionary; } for (int i = 0; i < val.Count; i++) { IArtifactValue val2 = val[i]; if (val2 != null) { float stacks = val2.Stacks; if (stacks != 0f) { dictionary[((object)val2.ArtifactType/*cast due to .constrained prefix*/).ToString()] = stacks; } } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(25, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Reading artifact stacks: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(ex.Message); } log.LogWarning(val3); } return dictionary; } internal unsafe static int SetArtifactStacks(GameplayPlayerIdentity p, Dictionary<string, float> saved) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) int num = 0; if (saved == null || saved.Count == 0) { return 0; } PlayerArtifacts val = ArtifactsOf(p); List<IArtifactValue> val2 = ((val != null) ? val.artifactValues : null); if (val2 == null) { return 0; } for (int i = 0; i < val2.Count; i++) { IArtifactValue val3 = val2[i]; if (val3 == null || !saved.TryGetValue(((object)val3.ArtifactType/*cast due to .constrained prefix*/).ToString(), out var value)) { continue; } IntPtr pointer = ((Il2CppObjectBase)val3).Pointer; IntPtr intPtr = IntPtr.Zero; IntPtr intPtr2 = IL2CPP.il2cpp_object_get_class(pointer); while (intPtr2 != IntPtr.Zero && intPtr == IntPtr.Zero) { intPtr = IL2CPP.il2cpp_class_get_field_from_name(intPtr2, "<Stacks>k__BackingField"); intPtr2 = IL2CPP.il2cpp_class_get_parent(intPtr2); } if (!(intPtr == IntPtr.Zero)) { int num2 = (int)IL2CPP.il2cpp_field_get_offset(intPtr); *(float*)((byte*)(void*)pointer + num2) = value; try { val.NotifyStackChanged(val3.ArtifactType, value); } catch { } num++; } } return num; } internal static int RevivesPerformedOf(GameplayPlayerIdentity p) { try { return p.RevivesPerformed; } catch { return 0; } } } [HarmonyPatch] internal static class Net { private const string SpellsMsg = "girthy_mods.RunSaves.Spells"; private const string TimeMsg = "girthy_mods.RunSaves.Time"; private const string CountersMsg = "girthy_mods.RunSaves.Counters"; private static HandleNamedMessageDelegate spellsHandler; private static HandleNamedMessageDelegate timeHandler; private static HandleNamedMessageDelegate countersHandler; internal static List<SpellType> PendingSpells; internal static float PendingUntil; [HarmonyPatch(typeof(RunManager), "OnNetworkSpawn")] [HarmonyPostfix] private static void Listen() { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown try { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && !singleton.IsServer && singleton.CustomMessagingManager != null) { if (spellsHandler == null) { spellsHandler = DelegateSupport.ConvertDelegate<HandleNamedMessageDelegate>((Delegate)new Action<ulong, FastBufferReader>(OnSpells)); } if (timeHandler == null) { timeHandler = DelegateSupport.ConvertDelegate<HandleNamedMessageDelegate>((Delegate)new Action<ulong, FastBufferReader>(OnTime)); } singleton.CustomMessagingManager.RegisterNamedMessageHandler("girthy_mods.RunSaves.Spells", spellsHandler); singleton.CustomMessagingManager.RegisterNamedMessageHandler("girthy_mods.RunSaves.Time", timeHandler); if (countersHandler == null) { countersHandler = DelegateSupport.ConvertDelegate<HandleNamedMessageDelegate>((Delegate)new Action<ulong, FastBufferReader>(OnCounters)); } singleton.CustomMessagingManager.RegisterNamedMessageHandler("girthy_mods.RunSaves.Counters", countersHandler); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(8, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Listen: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } private static void Send(string name, string payload, ulong? client) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.CustomMessagingManager == null) { return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(Encoding.UTF8.GetByteCount(payload) * 2 + 64, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(payload, false); if (client.HasValue) { singleton.CustomMessagingManager.SendNamedMessage(name, client.Value, val, (NetworkDelivery)4); } else { singleton.CustomMessagingManager.SendNamedMessageToAll(name, val, (NetworkDelivery)4); } } finally { ((FastBufferWriter)(ref val)).Dispose(); } } internal unsafe static void SendSpells(ulong client, List<SpellType> spells) { Send("girthy_mods.RunSaves.Spells", string.Join(",", spells.ConvertAll((SpellType s) => ((object)(*(SpellType*)(&s))/*cast due to .constrained prefix*/).ToString())), client); } internal static void SendTime(float cycleTime, float runTime) { Send("girthy_mods.RunSaves.Time", cycleTime.ToString(CultureInfo.InvariantCulture) + ";" + runTime.ToString(CultureInfo.InvariantCulture), null); } internal static void SendCounters(ulong client, Dictionary<string, float> stacks, int revivesPerformed) { List<string> list = new List<string>(); if (stacks != null) { foreach (KeyValuePair<string, float> stack in stacks) { list.Add(stack.Key + "=" + stack.Value.ToString(CultureInfo.InvariantCulture)); } } if (list.Count != 0 || revivesPerformed != 0) { Send("girthy_mods.RunSaves.Counters", client.ToString(CultureInfo.InvariantCulture) + "|" + revivesPerformed.ToString(CultureInfo.InvariantCulture) + "|" + string.Join(",", list), null); } } private static void OnCounters(ulong sender, FastBufferReader reader) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown bool flag = default(bool); try { string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); string[] array = text.Split('|'); if (array.Length < 3) { return; } ulong clientId = ulong.Parse(array[0], CultureInfo.InvariantCulture); int revivesPerformed = int.Parse(array[1], CultureInfo.InvariantCulture); Dictionary<string, float> dictionary = new Dictionary<string, float>(); string[] array2 = array[2].Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array2) { int num = text2.IndexOf('='); if (num > 0) { dictionary[text2.Substring(0, num)] = float.Parse(text2.Substring(num + 1), CultureInfo.InvariantCulture); } } GameplayPlayerIdentity val = Game.PlayerFor(clientId); if (!((Object)(object)val == (Object)null)) { int num2 = Restorer.ApplyCounters(val, dictionary, revivesPerformed); ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(41, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Host restored run: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num2); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" artifact stacks for "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(Game.NameOf(val)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("."); } log.LogInfo(val2); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(18, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Receive counters: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Exception>(ex); } log2.LogError(val3); } } private static void OnSpells(ulong sender, FastBufferReader reader) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) bool flag = default(bool); try { string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); List<SpellType> list = new List<SpellType>(); string[] array = text.Split(',', StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { if (Enum.TryParse<SpellType>(array[i], out SpellType result)) { list.Add(result); } } PendingSpells = list; PendingUntil = Time.unscaledTime + 15f; ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(33, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Host restored run: re-equipping "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogInfo(val); TryEquipPending(); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(16, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Receive spells: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex); } log2.LogError(val2); } } internal static void TryEquipPending() { if (PendingSpells == null) { return; } if (Time.unscaledTime > PendingUntil) { Plugin.Log.LogWarning((object)"Couldn't re-equip restored spells: your wizard wasn't ready in time."); PendingSpells = null; return; } GameplayPlayerIdentity val = Game.LocalPlayer(); if (!((Object)(object)((val != null) ? val.spellEquipper : null) == (Object)null) && Game.SpellsOf(val).Count != 0) { EquipMissing(val, PendingSpells); PendingSpells = null; } } internal static int EquipMissing(GameplayPlayerIdentity p, List<SpellType> spells) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) List<SpellType> list = Game.SpellsOf(p); int num = 0; foreach (SpellType spell in spells) { if (!list.Contains(spell)) { p.spellEquipper.EquipSpell(spell); list.Add(spell); num++; } } return num; } private static void OnTime(ulong sender, FastBufferReader reader) { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown try { string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); string[] array = text.Split(';'); RunManager run = Game.Run; if ((Object)(object)run == (Object)null || array.Length < 2) { return; } float num = (run.currentCycleTimeInSeconds = float.Parse(array[0], CultureInfo.InvariantCulture)); run.currentRunTimeInSeconds = float.Parse(array[1], CultureInfo.InvariantCulture); List<EliteEnemyConfiguration> eliteEnemiesThatHaveBeenSpawned = run.eliteEnemiesThatHaveBeenSpawned; RunConfiguration configuration = run.Configuration; List<EliteEnemyConfiguration> val = ((configuration != null) ? configuration.EliteEnemies : null); if (eliteEnemiesThatHaveBeenSpawned == null || val == null) { return; } for (int i = 0; i < val.Count; i++) { EliteEnemyConfiguration val2 = val[i]; if ((Object)(object)val2 != (Object)null && (float)val2.SpawnTimeInSeconds() < num && !eliteEnemiesThatHaveBeenSpawned.Contains(val2)) { eliteEnemiesThatHaveBeenSpawned.Add(val2); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(14, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Receive time: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Exception>(ex); } log.LogError(val3); } } } [BepInPlugin("girthy_mods.RunSaves", "RunSaves", "1.2.2")] public class Plugin : BasePlugin { public const string Guid = "girthy_mods.RunSaves"; public const string Name = "RunSaves"; public const string Version = "1.2.2"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> AutosaveEachWave; internal static ConfigEntry<int> AutosaveIntervalSeconds; internal static ConfigEntry<bool> SaveOnQuit; internal static ConfigEntry<bool> ToastOnAutosave; internal static ConfigEntry<bool> ShowSaveButton; internal static ConfigEntry<string> SaveKey; internal static ConfigEntry<bool> ShowLobbyPrompt; internal static ConfigEntry<string> LobbyPanelSide; internal static ConfigEntry<string> PanelKey; public override void Load() { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected O, but got Unknown Log = ((BasePlugin)this).Log; Enabled = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn run saving and restoring on or off."); AutosaveEachWave = ((BasePlugin)this).Config.Bind<bool>("Saving", "AutosaveEachWave", true, "Host: save automatically when each new wave starts, in every run, including restored ones (the previous save is kept as save.prev.json)."); AutosaveIntervalSeconds = ((BasePlugin)this).Config.Bind<int>("Saving", "AutosaveIntervalSeconds", 60, new ConfigDescription("Host: also save quietly every this many seconds (0 = off). Keeps upgrades and levels gained mid-wave; a restore still starts at the beginning of the saved wave.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 600), Array.Empty<object>())); SaveOnQuit = ((BasePlugin)this).Config.Bind<bool>("Saving", "SaveOnQuit", true, "Host: save when quitting the run from the pause menu."); ToastOnAutosave = ((BasePlugin)this).Config.Bind<bool>("Saving", "ShowAutosaveMessage", true, "Show a short message when an autosave happens."); ShowSaveButton = ((BasePlugin)this).Config.Bind<bool>("Saving", "PauseMenuButton", true, "Host: add a Save Run button to the pause menu."); SaveKey = ((BasePlugin)this).Config.Bind<string>("Saving", "SaveKey", "", "Optional key to save during a run (Unity Input System key name, e.g. F5). Empty = no key."); ShowLobbyPrompt = ((BasePlugin)this).Config.Bind<bool>("Restoring", "LobbyPrompt", true, "Host: show the saved run in the lobby with Resume / Delete / Hide."); LobbyPanelSide = ((BasePlugin)this).Config.Bind<string>("Restoring", "LobbyPanelSide", "Right", new ConfigDescription("Which side of the lobby the saved-run panel sits on.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Right", "Left" }), Array.Empty<object>())); PanelKey = ((BasePlugin)this).Config.Bind<string>("Restoring", "PanelKey", "F10", "Key to show or hide the saved-run panel in the lobby (Unity Input System key name). Hide also leaves a small \"Saved Run\" tab to click."); Harmony val = new Harmony("girthy_mods.RunSaves"); val.PatchAll(typeof(Recorder)); val.PatchAll(typeof(Saver)); val.PatchAll(typeof(Restorer)); val.PatchAll(typeof(Net)); val.PatchAll(typeof(PauseButton)); ((BasePlugin)this).AddComponent<RunSavesUI>(); ManualLogSource log = Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(8, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("RunSaves"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("1.2.2"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" ready."); } log.LogInfo(val2); } } [HarmonyPatch] internal static class Recorder { internal static readonly Dictionary<string, List<Improvement>> Log = new Dictionary<string, List<Improvement>>(); internal static readonly Dictionary<string, SavedPlayer> Known = new Dictionary<string, SavedPlayer>(); private static readonly Dictionary<ulong, float> initTime = new Dictionary<ulong, float>(); private static readonly Dictionary<ulong, string> keyCache = new Dictionary<ulong, string>(); internal static bool Suppress; internal static bool RunActive; private const float StartingStatsWindow = 10f; internal static void ResetForNewRun() { Log.Clear(); Known.Clear(); initTime.Clear(); keyCache.Clear(); RunActive = false; } private static bool Executing(NetworkBehaviour b) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 try { return (int)b.__rpc_exec_stage == 1; } catch { return false; } } private static bool ShouldRecord(NetworkBehaviour b) { if (Plugin.Enabled.Value && !Suppress && RunActive && Game.IsServer) { return Executing(b); } return false; } internal static string KeyFor(ulong clientId) { if (keyCache.TryGetValue(clientId, out var value)) { return value; } GameplayPlayerIdentity val = Game.PlayerFor(clientId); if ((Object)(object)val == (Object)null) { return $"client:{clientId}"; } value = Game.KeyOf(val); keyCache[clientId] = value; Remember(val); return value; } internal unsafe static void Remember(GameplayPlayerIdentity p) { try { string key = Game.KeyOf(p); if (!Known.TryGetValue(key, out var value)) { Dictionary<string, SavedPlayer> known = Known; SavedPlayer obj = new SavedPlayer { Key = key }; value = obj; known[key] = obj; } value.Name = Game.NameOf(p); value.Character = Game.CharacterOf(p); value.WasHost = ((NetworkBehaviour)p).IsOwner; List<SpellType> list = Game.SpellsOf(p); if (list.Count > 0) { value.Spells = list.ConvertAll((SpellType s) => ((object)(*(SpellType*)(&s))/*cast due to .constrained prefix*/).ToString()); } value.ArtifactStacks = Game.ArtifactStacksOf(p); value.RevivesPerformed = Game.RevivesPerformedOf(p); } catch { } } internal static void Add(string key, Improvement imp) { if (!Log.TryGetValue(key, out var value)) { value = (Log[key] = new List<Improvement>()); } value.Add(imp); } [HarmonyPatch(typeof(RunManager), "OnNetworkSpawn")] [HarmonyPostfix] private static void RunSpawned() { Game.ForgetObjects(); ResetForNewRun(); } [HarmonyPatch(typeof(RunManager), "OnDestroy")] [HarmonyPostfix] private static void RunDespawned() { RunActive = false; Restorer.Abort("the run ended"); Game.ForgetObjects(); } [HarmonyPatch(typeof(GameplayPlayerIdentity), "InitializeOnClientsRpc")] [HarmonyPrefix] private static void PlayerInitialized(GameplayPlayerIdentity __instance) { try { if (Game.IsServer && Executing((NetworkBehaviour)(object)__instance)) { initTime[((NetworkBehaviour)__instance).OwnerClientId] = Time.unscaledTime; keyCache.Remove(((NetworkBehaviour)__instance).OwnerClientId); } } catch { } } [HarmonyPatch(typeof(ImprovementsManager), "SendCharacterStatModifiersRpc")] [HarmonyPrefix] private static void Stats(ImprovementsManager __instance, ulong clientId, Il2CppReferenceArray<StatModifierMessage> messages) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRecord((NetworkBehaviour)(object)__instance) || messages == null) { return; } try { if (initTime.TryGetValue(clientId, out var value) && Time.unscaledTime - value < 10f) { return; } List<StatEntry> list = new List<StatEntry>(); foreach (StatModifierMessage item in (Il2CppArrayBase<StatModifierMessage>)(object)messages) { if (item != null) { list.Add(new StatEntry { Stat = ((object)item.Stat/*cast due to .constrained prefix*/).ToString(), Value = item.Value, Type = ((object)item.Type/*cast due to .constrained prefix*/).ToString() }); } } if (list.Count > 0) { Add(KeyFor(clientId), new Improvement { Kind = "Stats", Stats = list }); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(14, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Record stats: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(ImprovementsManager), "SendSpellStatModifierRpc")] [HarmonyPrefix] private static void SpellStat(ImprovementsManager __instance, ulong clientId, SpellStatModifierMessage message) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRecord((NetworkBehaviour)(object)__instance) || message == null) { return; } try { Add(KeyFor(clientId), new Improvement { Kind = "SpellStat", Spell = ((object)message.Spell/*cast due to .constrained prefix*/).ToString(), Stat = ((object)message.Stat/*cast due to .constrained prefix*/).ToString(), Value = message.Value }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(22, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Record spell upgrade: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(ImprovementsManager), "SendSpellInfusionRpc")] [HarmonyPrefix] private static void Infusion(ImprovementsManager __instance, ulong clientId, SpellInfusionMessage message) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRecord((NetworkBehaviour)(object)__instance) || message == null) { return; } try { Add(KeyFor(clientId), new Improvement { Kind = "Infusion", Spell = ((object)message.Spell/*cast due to .constrained prefix*/).ToString(), Element = ((object)message.Element/*cast due to .constrained prefix*/).ToString() }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(17, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Record infusion: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(ImprovementsManager), "SendSpellVariantRpc")] [HarmonyPrefix] private static void Variant(ImprovementsManager __instance, ulong clientId, SpellVariantMessage message) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRecord((NetworkBehaviour)(object)__instance) || message == null) { return; } try { Add(KeyFor(clientId), new Improvement { Kind = "Variant", Spell = ((object)message.Spell/*cast due to .constrained prefix*/).ToString(), Variant = ((object)message.SpellVariant/*cast due to .constrained prefix*/).ToString() }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(22, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Record spell variant: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(ImprovementsManager), "SendArtifactRpc")] [HarmonyPrefix] private static void Artifact(ImprovementsManager __instance, ulong clientId, ArtifactMessage message) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (!ShouldRecord((NetworkBehaviour)(object)__instance) || message == null) { return; } try { Add(KeyFor(clientId), new Improvement { Kind = "Artifact", Artifact = ((object)message.Artifact/*cast due to .constrained prefix*/).ToString() }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(17, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Record artifact: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(ImprovementsManager), "SendFinalUpgradeRpc")] [HarmonyPrefix] private unsafe static void Final(ImprovementsManager __instance, ulong clientId, FinalUpgradeType finalUpgradeType) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown if (!ShouldRecord((NetworkBehaviour)(object)__instance)) { return; } try { Add(KeyFor(clientId), new Improvement { Kind = "Final", Final = ((object)(*(FinalUpgradeType*)(&finalUpgradeType))/*cast due to .constrained prefix*/).ToString() }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(22, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Record final upgrade: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } } [HarmonyPatch] internal static class Restorer { private enum Phase { None, WaitPlayers, WaitSpells, Settle, Replay, Finish } private class Match { public GameplayPlayerIdentity Player; public SavedPlayer Saved; public List<SpellType> Spells = new List<SpellType>(); } internal static RunSave Armed; private static RunSave active; private static Phase phase; private static float t0; private static float phaseStart; private static bool wavesJumped; private static bool startedThisRun; private static readonly List<Match> matches = new List<Match>(); private static readonly Queue<(Match m, Improvement imp)> queue = new Queue<(Match, Improvement)>(); private static int applied; private static int skipped; private static readonly List<string> notes = new List<string>(); private static bool revivesUsedSinceStart; internal static bool Busy => active != null; internal static void Abort(string why) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown if (active != null) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(18, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore stopped: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(why); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogWarning(val); Recorder.Suppress = false; while (queue.Count > 0) { var (match, imp) = queue.Dequeue(); Recorder.Add(match.Saved.Key, imp); } active = null; phase = Phase.None; } } [HarmonyPatch(typeof(RunManager), "OnNetworkSpawn")] [HarmonyPostfix] private static void RunSpawned() { startedThisRun = false; wavesJumped = false; } [HarmonyPatch(typeof(RunManager), "StartRunOnServerRpc")] [HarmonyPrefix] private static void StartRun_Prefix(RunManager __instance, out bool __state) { //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Expected O, but got Unknown //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown __state = false; bool flag = default(bool); try { __state = (int)((NetworkBehaviour)__instance).__rpc_exec_stage == 1 && Game.IsServer; if (!__state || startedThisRun || Armed == null || !Plugin.Enabled.Value) { return; } RunSave armed = Armed; Armed = null; string text = Game.ConfigName(__instance); if (!string.IsNullOrEmpty(armed.ConfigName) && text != armed.ConfigName) { string text2 = $"Saved run is in {armed.World}, but this run is in {Game.WorldName(__instance)}. Not restored (the save is kept)."; Plugin.Log.LogWarning((object)text2); Toast.Show(text2, 8f); return; } try { GameplayPlayerIdentity val = Game.LocalPlayer(); if ((Object)(object)val != (Object)null && val.ActiveCurseLevel != armed.CurseLevel) { Toast.Show($"Heads up: the saved run used curse level {armed.CurseLevel}, this one uses {val.ActiveCurseLevel}.", 6f); } } catch { } active = armed; wavesJumped = false; if (armed.Cycle > 1 && __instance.CycleNumber != null && __instance.CycleNumber.Value < armed.Cycle) { __instance.CycleNumber.Value = armed.Cycle; } ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(37, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Restoring saved run: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(armed.World); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", cycle "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(armed.Cycle); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", wave "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(armed.Wave); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("."); } log.LogInfo(val2); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(17, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Restore (start): "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Exception>(ex); } log2.LogError(val3); active = null; } } [HarmonyPatch(typeof(RunManager), "StartRunOnServerRpc")] [HarmonyPostfix] private static void StartRun_Postfix(RunManager __instance, bool __state) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown if (!__state || startedThisRun) { return; } startedThisRun = true; Recorder.RunActive = true; Saver.OnRunStarted(active != null); if (active == null) { return; } try { if (wavesJumped) { Net.SendTime(__instance.currentCycleTimeInSeconds, __instance.currentRunTimeInSeconds); } SetLevelAndRevives(active); t0 = Time.unscaledTime; SetPhase(Phase.WaitPlayers); Toast.Show($"Restoring saved run - wave {active.Wave}...", 4f); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(23, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore (after start): "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(_StartWaves_d__53), "MoveNext")] [HarmonyPrefix] private static void Waves_Prefix(_StartWaves_d__53 __instance) { //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Expected O, but got Unknown //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown if (active == null || wavesJumped) { return; } bool flag = default(bool); try { if (__instance.__1__state != 0) { return; } RunManager _4__this = __instance.__4__this; RunConfiguration val = ((_4__this != null) ? _4__this.Configuration : null); List<WaveConfiguration> val2 = ((val != null) ? val.Waves : null); if (val2 == null || val2.Count == 0) { return; } wavesJumped = true; int count = val2.Count; int num = Math.Clamp(active.Wave, 1, count); float num2 = val.RunDurationInSeconds / (float)count; __instance._waves_5__2 = val2; __instance._timeBetweenWavesInSeconds_5__3 = num2; __instance._index_5__4 = num - 2; _4__this.CurrentWave.Value = num - 1; __instance.__1__state = 1; float num3 = (_4__this.currentCycleTimeInSeconds = (float)(num - 1) * num2); _4__this.currentRunTimeInSeconds = Math.Max(num3, active.RunTime - active.CycleTime + num3); int num5 = 0; List<EliteEnemyConfiguration> eliteEnemiesThatHaveBeenSpawned = _4__this.eliteEnemiesThatHaveBeenSpawned; List<EliteEnemyConfiguration> eliteEnemies = val.EliteEnemies; if (eliteEnemiesThatHaveBeenSpawned != null && eliteEnemies != null) { for (int i = 0; i < eliteEnemies.Count; i++) { EliteEnemyConfiguration val3 = eliteEnemies[i]; if ((Object)(object)val3 != (Object)null && (float)val3.SpawnTimeInSeconds() < num3 && !eliteEnemiesThatHaveBeenSpawned.Contains(val3)) { eliteEnemiesThatHaveBeenSpawned.Add(val3); num5++; } } } ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val4 = new BepInExInfoLogInterpolatedStringHandler(70, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("Restore: starting at wave "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<int>(count); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" ("); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<float>(num3, "0"); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("s into the run), "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<int>(num5); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" earlier elites skipped."); } log.LogInfo(val4); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val5 = new BepInExErrorLogInterpolatedStringHandler(17, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val5).AppendLiteral("Restore (waves): "); ((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<Exception>(ex); } log2.LogError(val5); } } private static void SetPhase(Phase p) { phase = p; phaseStart = Time.unscaledTime; } internal static void Tick() { //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Expected O, but got Unknown if (active == null || phase == Phase.None) { return; } try { if (!Game.IsServer || (Object)(object)Game.Run == (Object)null) { Abort("not hosting a run any more"); return; } float unscaledTime = Time.unscaledTime; switch (phase) { case Phase.WaitPlayers: { List<GameplayPlayerIdentity> list = Game.Players(); if ((list.Count > 0 && list.TrueForAll((GameplayPlayerIdentity p) => Game.SpellsOf(p).Count > 0) && unscaledTime - t0 >= 1.5f) || unscaledTime - t0 > 15f) { MatchPlayers(list); EquipSpells(); SetPhase(Phase.WaitSpells); } break; } case Phase.WaitSpells: if ((matches.TrueForAll((Match m) => m.Spells.TrueForAll((SpellType s) => Game.SpellsOf(m.Player).Contains(s))) && unscaledTime - t0 >= 4f) || unscaledTime - phaseStart > 10f) { SetPhase(Phase.Settle); } break; case Phase.Settle: if (unscaledTime - phaseStart >= 1.5f) { BuildQueue(); SetPhase(Phase.Replay); } break; case Phase.Replay: { for (int i = 0; i < 10; i++) { if (queue.Count <= 0) { break; } Apply(queue.Dequeue()); } if (queue.Count == 0) { SetPhase(Phase.Finish); } break; } case Phase.Finish: FinishUp(); break; } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(9, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); Abort("an error (see above)"); } } private static void MatchPlayers(List<GameplayPlayerIdentity> players) { //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Unknown result type (might be due to invalid IL or missing references) matches.Clear(); notes.Clear(); List<SavedPlayer> list = new List<SavedPlayer>(active.Players); List<GameplayPlayerIdentity> list2 = new List<GameplayPlayerIdentity>(); foreach (GameplayPlayerIdentity player in players) { string key = Game.KeyOf(player); SavedPlayer savedPlayer = list.Find((SavedPlayer s) => s.Key == key); if (savedPlayer != null && !string.IsNullOrEmpty(savedPlayer.Character) && savedPlayer.Character != Game.CharacterOf(player)) { notes.Add($"{Game.NameOf(player)} picked {RunSave.Pretty(Game.CharacterOf(player))} instead of {RunSave.Pretty(savedPlayer.Character)}"); savedPlayer = null; } if (savedPlayer != null) { list.Remove(savedPlayer); matches.Add(new Match { Player = player, Saved = savedPlayer }); } else { list2.Add(player); } } foreach (GameplayPlayerIdentity item in list2) { string ch = Game.CharacterOf(item); SavedPlayer savedPlayer2 = list.Find((SavedPlayer s) => s.Character == ch); if (savedPlayer2 != null) { list.Remove(savedPlayer2); matches.Add(new Match { Player = item, Saved = savedPlayer2 }); notes.Add(Game.NameOf(item) + " gets " + savedPlayer2.Name + "'s build (same wizard)"); } else { notes.Add(Game.NameOf(item) + " isn't in the save (starts fresh)"); } } foreach (SavedPlayer item2 in list) { notes.Add(item2.Name + " isn't here (build kept in the save)"); if (!Recorder.Log.ContainsKey(item2.Key)) { Recorder.Log[item2.Key] = new List<Improvement>(item2.Improvements); } if (!Recorder.Known.ContainsKey(item2.Key)) { Recorder.Known[item2.Key] = item2; } } foreach (Match match in matches) { foreach (string spell in match.Saved.Spells) { if (Enum.TryParse<SpellType>(spell, out SpellType result) && !match.Spells.Contains(result)) { match.Spells.Add(result); } } } foreach (string note in notes) { Plugin.Log.LogInfo((object)("Restore: " + note)); } } private static void EquipSpells() { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown bool flag = default(bool); foreach (Match match in matches) { if (match.Spells.Count == 0) { continue; } if (((NetworkBehaviour)match.Player).IsOwner) { int num = Net.EquipMissing(match.Player, match.Spells); ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore: equipped "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" spells for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Game.NameOf(match.Player)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogInfo(val); } else { Net.SendSpells(((NetworkBehaviour)match.Player).OwnerClientId, match.Spells); ManualLogSource log2 = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(36, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore: asked "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Game.NameOf(match.Player)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" to re-equip "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(match.Spells.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" spells."); } log2.LogInfo(val); } } } private static void BuildQueue() { //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) queue.Clear(); applied = (skipped = 0); foreach (Match match in matches) { List<SpellType> list = Game.SpellsOf(match.Player); int num = 0; foreach (Improvement improvement in match.Saved.Improvements) { if (!string.IsNullOrEmpty(improvement.Spell) && (!Enum.TryParse<SpellType>(improvement.Spell, out SpellType result) || !list.Contains(result))) { num++; skipped++; } else { queue.Enqueue((match, improvement)); } } if (num > 0) { notes.Add($"{Game.NameOf(match.Player)}: {num} spell upgrades skipped (spells not re-equipped - do they have RunSaves?)"); } } ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore: replaying "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(queue.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" upgrades."); } log.LogInfo(val); } private static void Apply((Match m, Improvement imp) item) { //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Expected O, but got Unknown //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Expected O, but got Unknown //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_02fe: Unknown result type (might be due to invalid IL or missing references) ImprovementsManager improvements = Game.Improvements; if ((Object)(object)improvements == (Object)null) { skipped++; return; } GameplayPlayerIdentity player = item.m.Player; if (player == null || ((Il2CppObjectBase)player).WasCollected) { skipped++; return; } ulong ownerClientId = ((NetworkBehaviour)player).OwnerClientId; Improvement item2 = item.imp; Recorder.Suppress = true; try { switch (item2.Kind) { case "Stats": { List<StatModifierMessage> list = new List<StatModifierMessage>(); foreach (StatEntry item3 in item2.Stats ?? new List<StatEntry>()) { if (Enum.TryParse<StatType>(item3.Stat, out StatType result7) && Enum.TryParse<StatModifiersType>(item3.Type, out StatModifiersType result8)) { list.Add(new StatModifierMessage { Stat = result7, Value = item3.Value, Type = result8 }); } } if (list.Count == 0) { skipped++; return; } Il2CppReferenceArray<StatModifierMessage> val = new Il2CppReferenceArray<StatModifierMessage>((long)list.Count); for (int i = 0; i < list.Count; i++) { ((Il2CppArrayBase<StatModifierMessage>)(object)val)[i] = list[i]; } improvements.SendCharacterStatModifiersRpc(ownerClientId, val); break; } case "SpellStat": { if (!Enum.TryParse<SpellType>(item2.Spell, out SpellType result4) || !Enum.TryParse<StatType>(item2.Stat, out StatType result5)) { skipped++; return; } improvements.SendSpellStatModifierRpc(ownerClientId, new SpellStatModifierMessage { Spell = result4, Stat = result5, Value = item2.Value }); break; } case "Infusion": { if (!Enum.TryParse<SpellType>(item2.Spell, out SpellType result9) || !Enum.TryParse<ElementType>(item2.Element, out ElementType result10)) { skipped++; return; } improvements.SendSpellInfusionRpc(ownerClientId, new SpellInfusionMessage { Spell = result9, Element = result10 }); break; } case "Variant": { if (!Enum.TryParse<SpellType>(item2.Spell, out SpellType result2) || !Enum.TryParse<SpellVariant>(item2.Variant, out SpellVariant result3)) { skipped++; return; } improvements.SendSpellVariantRpc(ownerClientId, new SpellVariantMessage { Spell = result2, SpellVariant = result3 }); break; } case "Artifact": { if (!Enum.TryParse<ArtifactType>(item2.Artifact, out ArtifactType result6)) { skipped++; return; } improvements.SendArtifactRpc(ownerClientId, new ArtifactMessage { Artifact = result6 }); break; } case "Final": { if (!Enum.TryParse<FinalUpgradeType>(item2.Final, out FinalUpgradeType result)) { skipped++; return; } improvements.SendFinalUpgradeRpc(ownerClientId, result); break; } default: skipped++; return; } applied++; Recorder.Add(Game.KeyOf(player), item2); } catch (Exception ex) { skipped++; ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(31, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Restore: couldn't apply "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(item2.Kind); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" for "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(Game.NameOf(player)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message); } log.LogError(val2); } finally { Recorder.Suppress = false; } } internal static int ApplyCounters(GameplayPlayerIdentity p, Dictionary<string, float> stacks, int revivesPerformed) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown int result = 0; try { result = Game.SetArtifactStacks(p, stacks); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(27, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore (artifact stacks): "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogError(val); } try { if (revivesPerformed > p.RevivesPerformed) { p.RevivesPerformed = revivesPerformed; } } catch { } return result; } private static void SetLevelAndRevives(RunSave save) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown revivesUsedSinceStart = false; bool flag = default(bool); try { PartyLevelManager party = Game.Party; if ((Object)(object)party != (Object)null) { party.level = Math.Max(1, save.PartyLevel); party.SetLevelRpc(party.level); if (party.currentExperience != null) { party.currentExperience.Value = save.PartyExperience; } } ReviveManager revives = Game.Revives; if (((revives != null) ? revives.CurrentRevives : null) != null) { revives.CurrentRevives.Value = save.Revives; } ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(32, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore: party level "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(save.PartyLevel); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", revives "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(save.Revives); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogInfo(val); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(25, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Restore (level/revives): "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex); } log2.LogError(val2); } } [HarmonyPatch(typeof(ReviveManager), "UseReviveRpc")] [HarmonyPostfix] private static void ReviveUsed() { if (active != null) { revivesUsedSinceStart = true; } } private static void FinishUp() { //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown RunSave runSave = active; bool flag = default(bool); try { PartyLevelManager party = Game.Party; if ((Object)(object)party != (Object)null && runSave.PartyLevel > party.level) { party.level = runSave.PartyLevel; party.SetLevelRpc(runSave.PartyLevel); } ReviveManager revives = Game.Revives; if (((revives != null) ? revives.CurrentRevives : null) != null && revives.CurrentRevives.Value != runSave.Revives && !revivesUsedSinceStart) { revives.CurrentRevives.Value = runSave.Revives; } foreach (Match match in matches) { int num = ApplyCounters(match.Player, match.Saved.ArtifactStacks, match.Saved.RevivesPerformed); Net.SendCounters(((NetworkBehaviour)match.Player).OwnerClientId, match.Saved.ArtifactStacks, match.Saved.RevivesPerformed); if (num > 0) { ManualLogSource log = Plugin.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(31, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Restore: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" artifact stacks for "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Game.NameOf(match.Player)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } log.LogInfo(val); } } foreach (Match match2 in matches) { Recorder.Remember(match2.Player); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(25, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Restore (level/revives): "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex); } log2.LogError(val2); } string text = $"Run restored: wave {runSave.Wave}, level {runSave.PartyLevel}, {applied} upgrades" + ((skipped > 0) ? $" ({skipped} skipped)" : ""); Plugin.Log.LogInfo((object)text); Toast.Show(text + ((notes.Count > 0) ? ("\n" + string.Join("\n", notes)) : ""), (notes.Count > 0) ? 10f : 5f); active = null; phase = Phase.None; } } public class Improvement { public string Kind { get; set; } public string Spell { get; set; } public string Stat { get; set; } public float Value { get; set; } public string StatType { get; set; } public string Element { get; set; } public string Variant { get; set; } public string Artifact { get; set; } public string Final { get; set; } public List<StatEntry> Stats { get; set; } } public class StatEntry { public string Stat { get; set; } public float Value { get; set; } public string Type { get; set; } } public class SavedPlayer { public string Key { get; set; } public string Name { get; set; } public string Character { get; set; } public bool WasHost { get; set; } public List<string> Spells { get; set; } = new List<string>(); public List<Improvement> Improvements { get; set; } = new List<Improvement>(); public Dictionary<string, float> ArtifactStacks { get; set; } = new Dictionary<string, float>(); public int RevivesPerformed { get; set; } } public class RunSave { public int Version { get; set; } = 1; public string SavedAt { get; set; } public string Reason { get; set; } public string World { get; set; } public string ConfigName { get; set; } public int Cycle { get; set; } public int Wave { get; set; } public int WaveCount { get; set; } public float RunTime { get; set; } public float CycleTime { get; set; } public int PartyLevel { get; set; } public float PartyExperience { get; set; } public int Revives { get; set; } public int CurseLevel { get; set; } public List<SavedPlayer> Players { get; set; } = new List<SavedPlayer>(); public string Summary() { string value = ((Players.Count == 0) ? "no players" : string.Join(", ", Players.ConvertAll((SavedPlayer p) => p.Name + " (" + Pretty(p.Character) + ")"))); return $"{World} - Wave {Wave}/{WaveCount}{((Cycle > 1) ? $" (loop {Cycle})" : "")} - Level {PartyLevel}\n{value}"; } internal static string Pretty(string enumName) { if (string.IsNullOrEmpty(enumName)) { return "?"; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < enumName.Length; i++) { char c = enumName[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(enumName[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } } internal static class SaveStore { private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { WriteIndented = true }; private static RunSave cached; private static DateTime cachedStamp; private static readonly object writeLock = new object(); private static string pendingJson; private static bool writerRunning; private static string Dir => Path.Combine(Paths.ConfigPath, "RunSaves"); private static string FilePath => Path.Combine(Dir, "save.json"); private static string PrevPath => Path.Combine(Dir, "save.prev.json"); internal static bool Exists => File.Exists(FilePath); internal static void Write(RunSave save) { string text = JsonSerializer.Serialize(save, Options); cached = save; cachedStamp = DateTime.MaxValue; lock (writeLock) { pendingJson = text; if (writerRunning) { return; } writerRunning = true; } Task.Run((Action)WriterLoop); } private static void WriterLoop() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown while (true) { string text; lock (writeLock) { text = pendingJson; pendingJson = null; if (text == null) { writerRunning = false; break; } } try { Directory.CreateDirectory(Dir); string text2 = FilePath + ".tmp"; File.WriteAllText(text2, text); if (File.Exists(FilePath)) { File.Copy(FilePath, PrevPath, overwrite: true); } File.Copy(text2, FilePath, overwrite: true); File.Delete(text2); } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(25, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Writing the save failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogError(val); } } } internal static RunSave Read() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown try { if (!File.Exists(FilePath)) { cached = null; return null; } DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(FilePath); if (cached != null && (lastWriteTimeUtc == cachedStamp || cachedStamp == DateTime.MaxValue)) { return cached; } cached = JsonSerializer.Deserialize<RunSave>(File.ReadAllText(FilePath)); cachedStamp = lastWriteTimeUtc; return cached; } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Couldn't read the saved run: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogError(val); return null; } } internal static void Delete() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown lock (writeLock) { pendingJson = null; } try { if (File.Exists(FilePath)) { File.Copy(FilePath, PrevPath, overwrite: true); File.Delete(FilePath); } cached = null; } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Couldn't delete the saved run: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogError(val); } } } [HarmonyPatch] internal static class Saver { internal static bool OwnsSave; private static bool autosavePending; private static float autosaveGiveUpAt; private static float lastSaveAt; internal static void OnRunStarted(bool restored) { lastSaveAt = Time.unscaledTime; autosavePending = false; OwnsSave = restored || !SaveStore.Exists; } internal static bool CanSave(out string why) { why = null; if (!Plugin.Enabled.Value) { why = "the mod is turned off"; } else if (!Game.IsServer) { why = "only the host can save"; } else if (!Recorder.RunActive || (Object)(object)Game.Run == (Object)null) { why = "no run in progress"; } else if (Restorer.Busy) { why = "still restoring"; } else if (Game.StoreBusy) { why = "finish the shop first"; } return why == null; } internal static bool SaveManual(out string message) { bool num = Save("manual", out message); if (num) { OwnsSave = true; } return num; } internal static void Tick() { int value = Plugin.AutosaveIntervalSeconds.Value; if (value > 0 && Time.unscaledTime - lastSaveAt >= (float)value && Recorder.RunActive && !Restorer.Busy && !Game.StoreBusy && CanSave(out var why)) { lastSaveAt = Time.unscaledTime; if (Save("auto", out why)) { OwnsSave = true; } } if (autosavePending) { if (!Recorder.RunActive || Time.unscaledTime > autosaveGiveUpAt) { autosavePending = false; } else if (!Restorer.Busy && !Game.StoreBusy) { autosavePending = false; Autosave(); } } } private static void Autosave() { if (Save("auto", out var message)) { OwnsSave = true; lastSaveAt = Time.unscaledTime; if (Plugin.ToastOnAutosave.Value) { Toast.Show(message.Replace("Run saved", "Autosaved"), 2.5f); } } } internal static bool Save(string reason, out string message) { //IL_0515: Unknown result type (might be due to invalid IL or missing references) //IL_051c: Expected O, but got Unknown //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Expected O, but got Unknown if (!CanSave(out var why)) { message = "Can't save: " + why + "."; return false; } try { RunManager run = Game.Run; RunSave obj = new RunSave { SavedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm"), Reason = reason, World = Game.WorldName(run), ConfigName = Game.ConfigName(run), Cycle = (run.CycleNumber?.Value ?? 1), Wave = Math.Max(1, run.CurrentWave?.Value ?? 1) }; RunConfiguration configuration = run.Configuration; obj.WaveCount = ((configuration == null) ? ((int?)null) : configuration.Waves?.Count).GetValueOrDefault(); obj.RunTime = run.currentRunTimeInSeconds; obj.CycleTime = run.currentCycleTimeInSeconds; RunSave runSave = obj; PartyLevelManager party = Game.Party; if ((Object)(object)party != (Object)null) { runSave.PartyLevel = party.level; runSave.PartyExperience = party.currentExperience?.Value ?? 0f; } ReviveManager revives = Game.Revives; if ((Object)(object)revives != (Object)null) { runSave.Revives = revives.CurrentRevives?.Value ?? 0; } List<GameplayPlayerIdentity> list = Game.Players(); foreach (GameplayPlayerIdentity item3 in list) { Recorder.Remember(item3); } try { GameplayPlayerIdentity val = Game.LocalPlayer(); if ((Object)(object)val != (Object)null) { runSave.CurseLevel = val.ActiveCurseLevel; } } catch { } List<string> list2 = new List<string>(); foreach (GameplayPlayerIdentity item4 in list) { string item = Game.KeyOf(item4); if (!list2.Contains(item)) { list2.Add(item); } } foreach (string key in Recorder.Log.Keys) { if (!list2.Contains(key)) { list2.Add(key); } } foreach (string item5 in list2) { Recorder.Known.TryGetValue(item5, out var value); List<Improvement> value2; SavedPlayer item2 = new SavedPlayer { Key = item5, Name = (value?.Name ?? item5), Character = (value?.Character ?? ""), WasHost = (value?.WasHost ?? false), Spells = ((value?.Spells != null) ? new List<string>(value.Spells) : new List<string>()), Improvements = (Recorder.Log.TryGetValue(item5, out value2) ? new List<Improvement>(value2) : new List<Improvement>()), ArtifactStacks = ((value?.ArtifactStacks != null) ? new Dictionary<string, float>(value.ArtifactStacks) : new Dictionary<string, float>()), RevivesPerformed = (value?.RevivesPerformed ?? 0) }; runSave.Players.Add(item2); } SaveStore.Write(runSave); int num = 0; foreach (SavedPlayer player in runSave.Players) { num += player.Improvements.Count; } message = $"Run saved - wave {runSave.Wave}, level {runSave.PartyLevel}"; ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(60, 8, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Saved run ("); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(reason); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("): "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(runSave.World); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", cycle "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(runSave.Cycle); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", wave "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(runSave.Wave); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(runSave.WaveCount); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", "); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("level "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(runSave.PartyLevel); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(runSave.Players.Count); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" players, "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" upgrades."); } log.LogInfo(val2); return true; } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; bool flag2 = default(bool); BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(13, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Save failed: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Exception>(ex); } log2.LogError(val3); message = "Save failed (see the BepInEx log)."; return false; } } [HarmonyPatch(typeof(RunManager), "StartWave")] [HarmonyPostfix] private static void WaveStarted(RunManager __instance) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown try { if (Plugin.AutosaveEachWave.Value && !Restorer.Busy && Recorder.RunActive && (__instance.CurrentWave?.Value ?? 0) > 1) { if (Game.StoreBusy) { autosavePending = true; autosaveGiveUpAt = Time.unscaledTime + 60f; } else { Autosave(); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(10, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Autosave: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPatch(typeof(ConfirmQuitPanel), "Quit")] [HarmonyPrefix] private static void Quitting() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown try { if (Plugin.SaveOnQuit.Value && CanSave(out var why) && Save("quit", out why)) { OwnsSave = true; } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(14, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Save on quit: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } } internal static class UiKit { internal static TMP_FontAsset Font() { TMP_FontAsset val = null; try { TextMeshProUGUI val2 = Object.FindObjectOfType<TextMeshProUGUI>(); if ((Object)(object)val2 != (Object)null) { val = ((TMP_Text)val2).font; } if ((Object)(object)val == (Object)null) { val = TMP_Settings.defaultFontAsset; } } catch { } return val; } internal static GameObject Canvas(string name, int order) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown GameObject val = new GameObject(name); Object.DontDestroyOnLoad((Object)val); Canvas obj = val.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = order; CanvasScaler obj2 = val.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; val.AddComponent<GraphicRaycaster>(); return val; } internal static GameObject Rect(string name, Transform parent, Vector2 pos, Vector2 size) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown GameObject val = new GameObject(name); RectTransform val2 = val.AddComponent<RectTransform>(); val.transform.SetParent(parent, false); val2.anchoredPosition = pos; val2.sizeDelta = size; return val; } internal static TextMeshProUGUI Text(string name, Transform parent, TMP_FontAsset font, float size, Vector2 pos, Vector2 box, TextAlignmentOptions align = (TextAlignmentOptions)514) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI obj = Rect(name, parent, pos, box).AddComponent<TextMeshProUGUI>(); ((TMP_Text)obj).font = font; ((TMP_Text)obj).fontSize = size; ((TMP_Text)obj).alignment = align; ((TMP_Text)obj).textWrappingMode = (TextWrappingModes)1; ((TMP_Text)obj).overflowMode = (TextOverflowModes)0; ((TMP_Text)obj).richText = true; ((Graphic)obj).raycastTarget = false; ((Graphic)obj).color = Color.white; return obj; } internal static (Button button, TextMeshProUGUI label) Button(string name, Transform parent, TMP_FontAsset font, string label, Vector2 pos, Vector2 size, UnityAction onClick, Color color) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) GameObject val = Rect(name, parent, pos, size); Image val2 = val.AddComponent<Image>(); ((Graphic)val2).color = color; Button obj = val.AddComponent<Button>(); ((Selectable)obj).targetGraphic = (Graphic)(object)val2; ((UnityEvent)obj.onClick).AddListener(onClick); TextMeshProUGUI val3 = Text("Label", val.transform, font, 22f, Vector2.zero, size, (TextAlignmentOptions)514); ((TMP_Text)val3).text = label; return (button: obj, label: val3); } internal static bool Pressed(string keyName) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null || string.IsNullOrEmpty(keyName)) { return false; } if (!Enum.TryParse<Key>(keyName, ignoreCase: true, out Key result) || (int)result == 0) { return false; } KeyControl val = current[result]; if (val != null) { return ((ButtonControl)val).wasPressedThisFrame; } return false; } } internal static class Toast { private static GameObject canvas; private static GameObject panel; private static TextMeshProUGUI text; private static float hideAt; private const float ToastWidth = 360f; internal static void Show(string message, float seconds) { //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)panel == (Object)null) { TMP_FontAsset val = UiKit.Font(); if ((Object)(object)val == (Object)null) { Plugin.Log.LogInfo((object)message); return; } canvas = UiKit.Canvas("RunSaves_Toast", 520); panel = UiKit.Rect("Toast", canvas.transform, Vector2.zero, new Vector2(360f, 40f)); RectTransform component = panel.GetComponent<RectTransform>(); Vector2 val2 = (component.pivot = new Vector2(1f, 1f)); Vector2 anchorMin = (component.anchorMax = val2); component.anchorMin = anchorMin; component.anchoredPosition = new Vector2(-20f, -20f); Image obj = panel.AddComponent<Image>(); ((Graphic)obj).color = new Color(0.08f, 0.05f, 0.14f, 0.85f); ((Graphic)obj).raycastTarget = false; text = UiKit.Text("Text", panel.transform, val, 15f, Vector2.zero, new Vector2(340f, 30f), (TextAlignmentOptions)513); } ((TMP_Text)text).text = message; float num = 0f; try { num = ((TMP_Text)text).GetPreferredValues(message, 340f, 0f).y; } catch { } if (num <= 0f) { num = (float)message.Split('\n').Length * 19f; } panel.GetComponent<RectTransform>().sizeDelta = new Vector2(360f, num + 14f); ((Component)text).GetComponent<RectTransform>().sizeDelta = new Vector2(340f, num + 4f); canvas.SetActive(true); hideAt = Time.unscaledTime + seconds; } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val5 = new BepInExErrorLogInterpolatedStringHandler(7, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val5).AppendLiteral("Toast: "); ((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<string>(ex.Message); } log.LogError(val5); } } internal static void Tick() { if ((Object)(object)canvas != (Object)null && canvas.activeSelf && Time.unscaledTime >= hideAt) { canvas.SetActive(false); } } } [HarmonyPatch] internal static class PauseButton { private static GameObject button; private static TextMeshProUGUI label; private static UnityAction click; private static float resetLabelAt; [HarmonyPatch(typeof(PauseMenuManager), "Open")] [HarmonyPostfix] private static void Opened(PauseMenuManager __instance) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown try { bool flag = Plugin.Enabled.Value && Plugin.ShowSaveButton.Value && Game.IsServer && Recorder.RunActive; if (!Alive((Object)(object)button) || !SameMenu(__instance)) { button = null; label = null; if (flag) { Build(__instance); } } if ((Object)(object)button != (Object)null) { button.SetActive(flag); } if ((Object)(object)label != (Object)null) { ((TMP_Text)label).text = "Save Run"; } } catch (Exception ex) { ManualLogSource log = Plugin.Log; bool flag2 = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(19, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Pause menu button: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } private static bool SameMenu(PauseMenuManager menu) { Button val = (((Object)(object)menu.settingsButton != (Object)null) ? menu.settingsButton : menu.resumeButton); if ((Object)(object)val != (Object)null) { return (Object)(object)button.transform.parent == (Object)(object)((Component)val).transform.parent; } return false; } private static bool Alive(Object o) { if (o != null && !((Il2CppObjectBase)o).WasCollected) { return o != (Object)null; } return false; } private static void Build(PauseMenuManager menu) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown Button val = (((Object)(object)menu.settingsButton != (Object)null) ? menu.settingsButton : menu.resumeButton); if ((Object)(object)val == (Object)null) { return; } button = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent); ((Object)button).name = "RunSaves_SaveButton"; button.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex() + 1); foreach (Component componentsInChild in button.GetComponentsInChildren<Component>(true)) { if ((Object)(object)componentsInChild != (Object)null && ((MemberInfo)((Object)componentsInChild).GetIl2CppType()).Name == "LocalizeStringEvent") { Object.Destroy((Object)(object)componentsInChild); } } Button component = button.GetComponent<Button>(); component.onClick = new ButtonClickedEvent(); if (click == null) { click = DelegateSupport.ConvertDelegate<UnityAction>((Delegate)new Action(OnClick)); } ((UnityEvent)component.onClick).AddListener(click); label = button.GetComponentInChildren<TextMeshProUGUI>(true); if ((Object)(object)label != (Object)null) { ((TMP_Text)label).text = "Save Run"; } Plugin.Log.LogInfo((object)"Added Save Run to the pause menu."); } private static void OnClick() { string message; bool flag = Saver.SaveManual(out message); Toast.Show(message, 3f); if ((Object)(object)label != (Object)null) { ((TMP_Text)label).text = (flag ? "Saved!" : "Can't save"); resetLabelAt = Time.unscaledTime + 2f; } } internal static void Tick() { if (resetLabelAt > 0f && Time.unscaledTime >= resetLabelAt) { resetLabelAt = 0f; if (Alive((Object)(object)label)) { ((TMP_Text)label).text = "Save Run"; } } } } public class RunSavesUI : MonoBehaviour { private GameObject canvas; private GameObject panel; private GameObject tab; private TextMeshProUGUI title; private TextMeshProUGUI body; private TextMeshProUGUI status; private TextMeshProUGUI resumeLabel; private TextMeshProUGUI deleteLabel; private TextMeshProUGUI hideLabel; private bool hidden; private bool inMenu; private float nextScan; private float deleteConfirmUntil; private float nextErrorLog; private static UnityAction resumeAct; private static UnityAction deleteAct; private static UnityAction hideAct; private static UnityAction showAct; private static RunSavesUI instance; public RunSavesUI(IntPtr ptr) : base(ptr) { } private void Update() { instance = this; Step("restore", Restorer.Tick); Step("autosave", Saver.Tick); Step("client spells", Net.TryEquipPending); Step("toast", Toast.Tick); Step("pause", PauseButton.Tick); Step("save key", delegate { if (UiKit.Pressed(Plugin.SaveKey.Value) && Saver.CanSave(out var _)) { Saver.SaveManual(out var message); Toast.Show(message, 3f); } }); if (Time.unscaledTime >= nextScan) { nextScan = Time.unscaledTime + 0.5f; Step("menu check", delegate { inMenu = Game.InMainMenu(); }); } bool available = inMenu && Plugin.Enabled.Value && Plugin.ShowLobbyPrompt.Value && !Game.IsClientOnly && (SaveStore.Exists || Restorer.Armed != null); if (available) { Step("panel key", delegate { if (UiKit.Pressed(Plugin.PanelKey.Value)) { hidden = !hidden; } }); } if (available && (Object)(object)panel == (Object)null) { Step("build", Build); } if ((Object)(object)canvas != (Object)null && canvas.activeSelf != available) { Step("show/hide", delegate { canvas.SetActive(available); }); } if (available && (Object)(object)panel != (Object)null) { if (panel.activeSelf == hidden) { panel.SetActive(!hidden); } if ((Object)(object)tab != (Object)null && tab.activeSelf != hidden) { tab.SetActive(hidden); } if (!hidden && Time.frameCount % 15 == 0) { Step("refresh", Refresh); } } } private void Step(string what, Action action) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown try { action(); } catch (Exception ex) { if (!(Time.unscaledTime < nextErrorLog)) { nextErrorLog = Time.unscaledTime + 10f; ManualLogSource log = Plugin.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(7, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("UI ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(what); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("): "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } } private void Build() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown res