Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SaveMaster v1.1.3
BepInEx\plugins\MechGaming-SaveMaster\SaveMaster.dll
Decompiled 5 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MechGaming")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.3.0")] [assembly: AssemblyInformationalVersion("1.1.3")] [assembly: AssemblyProduct("SaveMaster")] [assembly: AssemblyTitle("SaveMaster")] [assembly: AssemblyVersion("1.1.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 MechGaming.REPO.SaveMaster { internal static class HotkeyInput { private const int KeyDownMask = 32768; private const float TextInputScanInterval = 0.15f; private static readonly Dictionary<KeyCode, bool> PreviousStates = new Dictionary<KeyCode, bool>(); private static readonly Type? UnityInputFieldType = Type.GetType("UnityEngine.UI.InputField, UnityEngine.UI"); private static readonly Type? TmpInputFieldType = Type.GetType("TMPro.TMP_InputField, Unity.TextMeshPro"); private static int _currentProcessId; private static float _nextTextInputScanTime; private static bool _cachedNoTextInputActive = true; private static int CurrentProcessId { get { if (_currentProcessId == 0) { _currentProcessId = Process.GetCurrentProcess().Id; } return _currentProcessId; } } internal static bool GetKeyDown(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) bool flag = GetUnityKeyDown(key) || GetWindowsKey(key); bool value; bool flag2 = PreviousStates.TryGetValue(key, out value) && value; PreviousStates[key] = flag; if (flag) { return !flag2; } return false; } internal static bool NoTextInputActive(Func<bool> gameCheck) { try { if (gameCheck()) { _cachedNoTextInputActive = true; return true; } } catch { } if (Time.unscaledTime < _nextTextInputScanTime) { return _cachedNoTextInputActive; } _nextTextInputScanTime = Time.unscaledTime + 0.15f; _cachedNoTextInputActive = !AnyFocusedTextInput(); return _cachedNoTextInputActive; } private static bool GetUnityKeyDown(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return Input.GetKeyDown(key); } catch { return false; } } private static bool GetWindowsKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) int num = ToVirtualKey(key); if (num != 0 && IsForegroundProcess()) { return IsVirtualKeyDown(num); } return false; } private static bool IsVirtualKeyDown(int virtualKey) { try { return (GetAsyncKeyState(virtualKey) & 0x8000) != 0; } catch { return false; } } private static bool IsForegroundProcess() { try { IntPtr foregroundWindow = GetForegroundWindow(); if (foregroundWindow == IntPtr.Zero) { return false; } GetWindowThreadProcessId(foregroundWindow, out var processId); return processId == CurrentProcessId; } catch { return false; } } private static int ToVirtualKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected I4, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_0024: Expected I4, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected I4, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected I4, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Invalid comparison between Unknown and I4 //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Expected I4, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Invalid comparison between Unknown and I4 //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 //IL_00e0: 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_00f8: Expected I4, but got Unknown if ((int)key >= 97 && (int)key <= 122) { return 65 + (key - 97); } if ((int)key >= 48 && (int)key <= 57) { return 48 + (key - 48); } if ((int)key >= 282 && (int)key <= 678) { return 112 + (key - 282); } if ((int)key <= 27) { if ((int)key <= 9) { if ((int)key == 8) { return 8; } if ((int)key == 9) { return 9; } } else { if ((int)key == 13) { return 13; } if ((int)key == 27) { return 27; } } } else if ((int)key <= 127) { if ((int)key == 32) { return 32; } if ((int)key == 127) { return 46; } } else { switch (key - 273) { case 7: return 33; case 8: return 34; case 6: return 35; case 5: return 36; case 3: return 37; case 0: return 38; case 2: return 39; case 1: return 40; case 4: return 45; } switch (key - 303) { case 1: return 160; case 0: return 161; case 3: return 162; case 2: return 163; case 5: return 164; case 4: return 165; } switch (key - 323) { case 0: return 1; case 1: return 2; case 2: return 4; } } return 0; } private static bool AnyFocusedTextInput() { if (!TypeHasFocusedInput(UnityInputFieldType)) { return TypeHasFocusedInput(TmpInputFieldType); } return true; } private static bool TypeHasFocusedInput(Type? inputType) { if (inputType == null) { return false; } try { Object[] array = Resources.FindObjectsOfTypeAll(inputType); for (int i = 0; i < array.Length; i++) { if (IsFocusedInput(array[i])) { return true; } } } catch { return false; } return false; } private static bool IsFocusedInput(Object input) { Component val = (Component)(object)((input is Component) ? input : null); if (val == null || !val.gameObject.activeInHierarchy) { return false; } Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if (val2 != null && !val2.enabled) { return false; } PropertyInfo property = ((object)input).GetType().GetProperty("isFocused", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool)) { try { return (bool)property.GetValue(input, null); } catch { return false; } } return false; } [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int virtualKey); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr windowHandle, out uint processId); } [BepInPlugin("MechGaming.REPO.SaveMaster", "SaveMaster", "1.1.3")] public sealed class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <LoadSaveRoutine>d__65 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; public SaveEntry entry; public RestorePoint restorePoint; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadSaveRoutine>d__65(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Expected O, but got Unknown int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; if (plugin._operationRunning) { plugin.PublishStatus("Save operation already running."); return false; } if (!plugin.CanManageSaves()) { plugin.PublishStatus("Save loading is host/singleplayer only."); return false; } plugin._operationRunning = true; plugin.PublishStatus("Loading " + entry.FileName + "..."); if (!plugin.PrepareRestorePoint(entry, restorePoint)) { plugin._operationRunning = false; return false; } List<string> backups = GetBackups(entry.FileName); ClearRunFailureFlags(); if (IsMultiplayer()) { SemiFunc.MenuActionHostGame(entry.FileName, backups); } else { SemiFunc.MenuActionSingleplayerGame(entry.FileName, backups); } <>2__current = (object)new WaitForSecondsRealtime(1.2f); <>1__state = 1; return true; } case 1: <>1__state = -1; plugin.TryReloadInMemory(entry.FileName); plugin._operationRunning = false; plugin.SetMenuOpen(open: false); plugin.PublishStatus("Loaded " + entry.FileName + "."); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PluginGuid = "MechGaming.REPO.SaveMaster"; public const string PluginName = "SaveMaster"; public const string PluginVersion = "1.1.3"; private const int WindowWidth = 840; private const int WindowHeight = 590; private const float RefreshCooldown = 0.5f; private static readonly FieldInfo SaveFileCurrentField = AccessTools.Field(typeof(StatsManager), "saveFileCurrent"); private static readonly FieldInfo TeamNameField = AccessTools.Field(typeof(StatsManager), "teamName"); private static readonly FieldInfo TeamNameChangedField = AccessTools.Field(typeof(StatsManager), "teamNameChanged"); private static readonly FieldInfo Es3PasswordField = AccessTools.Field(typeof(StatsManager), "totallyNormalString"); private static readonly FieldInfo MenuPageSavesMaxSaveFilesField = AccessTools.Field(typeof(MenuPageSaves), "maxSaveFiles"); private static readonly FieldInfo RunManagerAllPlayersDeadField = AccessTools.Field(typeof(RunManager), "allPlayersDead"); private static readonly FieldInfo RunManagerLevelFailedField = AccessTools.Field(typeof(RunManager), "levelFailed"); private static readonly FieldInfo RunManagerGameOverField = AccessTools.Field(typeof(RunManager), "gameOver"); private readonly List<SaveEntry> _saveEntries = new List<SaveEntry>(); private Harmony? _harmony; private ConfigEntry<KeyCode> _menuKey; private ConfigEntry<KeyCode> _quickLoadKey; private ConfigEntry<bool> _enableQuickLoad; private ConfigEntry<bool> _autoLoadCheckpointOnDeath; private ConfigEntry<bool> _preventGameDeletes; private ConfigEntry<bool> _allowManagerDeletes; private Rect _windowRect = new Rect(40f, 40f, 840f, 590f); private Vector2 _scrollPosition; private bool _menuOpen; private bool _loadingSaves; private bool _operationRunning; private bool _storedCursorVisible; private CursorLockMode _storedCursorLockMode; private float _lastRefreshTime = -0.5f; private float _statusMessageUntil; private string _statusMessage = string.Empty; private DialogMode _dialogMode; private SaveEntry? _dialogSave; private RestorePoint _dialogRestorePoint; private string _newSaveName = string.Empty; private string _renameSaveName = string.Empty; internal static Plugin? Instance { get; private set; } internal static bool ManagedDeleteInProgress { get; private set; } private static string SavesRoot => Path.Combine(Application.persistentDataPath, "saves"); private void Awake() { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown Instance = this; _menuKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "ManagerKey", (KeyCode)288, "Key used to open the SaveMaster manager."); _quickLoadKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "QuickLoadKey", (KeyCode)290, "Key used to load the current save without opening the manager."); _enableQuickLoad = ((BaseUnityPlugin)this).Config.Bind<bool>("Controls", "EnableQuickLoad", true, "Allow the quick-load key to reload the current save."); _autoLoadCheckpointOnDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "AutoLoadCheckpointOnDeath", true, "Automatically load the current checkpoint instead of advancing the failure flow when all players die."); _preventGameDeletes = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "PreventGameDeletes", true, "Block R.E.P.O. from deleting saves after death or through vanilla flows."); _allowManagerDeletes = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "AllowManagerDeletes", true, "Allow saves to be deleted from the SaveMaster manager after confirmation."); _harmony = new Harmony("MechGaming.REPO.SaveMaster"); _harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"SaveMaster 1.1.3 loaded."); } private void Update() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (HotkeyInput.GetKeyDown(_menuKey.Value)) { SetMenuOpen(!_menuOpen); } else if (_menuOpen) { if (HotkeyInput.GetKeyDown((KeyCode)27)) { if (_dialogMode != 0) { CloseDialog(); } else { SetMenuOpen(open: false); } } } else if (_enableQuickLoad.Value && HotkeyInput.GetKeyDown(_quickLoadKey.Value) && NoTextInputActive()) { QuickLoadCurrentSave(); } } private void LateUpdate() { if (_menuOpen) { Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; } } private void OnDestroy() { SetMenuOpen(open: false); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Instance = null; } private void OnGUI() { //IL_0016: 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_0031: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) DrawStatusMessage(); if (_menuOpen) { _windowRect = ClampWindow(GUI.Window(((Object)this).GetInstanceID(), _windowRect, new WindowFunction(DrawManagerWindow), "SaveMaster")); } DrawDialog(); } private void SetMenuOpen(bool open) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (_menuOpen != open) { _menuOpen = open; CloseDialog(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("SaveMaster manager " + (open ? "opened" : "closed") + ".")); if (open) { _storedCursorVisible = Cursor.visible; _storedCursorLockMode = Cursor.lockState; Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; RefreshSaves(force: true); } else { Cursor.visible = _storedCursorVisible; Cursor.lockState = _storedCursorLockMode; } } } private void DrawManagerWindow(int windowId) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); GUILayout.Space(6f); DrawCurrentSaveHeader(); if (!CanManageSaves()) { DrawWarning("Save operations are host/singleplayer only."); } GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("New Checkpoint Save", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { _newSaveName = BuildDefaultTeamName(); _dialogMode = DialogMode.NewSave; } if (GUILayout.Button("Refresh", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { RefreshSaves(force: true); } GUILayout.FlexibleSpace(); if (GUILayout.Button($"Close [{_menuKey.Value} / ESC]", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(190f), GUILayout.Height(30f) })) { SetMenuOpen(open: false); } GUILayout.EndHorizontal(); GUILayout.Space(8f); if (_loadingSaves) { GUILayout.Label("Loading saves...", SaveMasterStyles.CenterLabel, Array.Empty<GUILayoutOption>()); } else if (_saveEntries.Count == 0) { GUILayout.Label("No save files found.", SaveMasterStyles.CenterLabel, Array.Empty<GUILayoutOption>()); } else { GUILayout.Label($"Available Saves ({_saveEntries.Count})", SaveMasterStyles.Subtitle, Array.Empty<GUILayoutOption>()); _scrollPosition = GUILayout.BeginScrollView(_scrollPosition, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(445f) }); foreach (SaveEntry saveEntry in _saveEntries) { DrawSaveEntry(saveEntry); } GUILayout.EndScrollView(); } if (!string.IsNullOrWhiteSpace(_statusMessage) && Time.unscaledTime < _statusMessageUntil) { GUILayout.Space(4f); GUILayout.Label(_statusMessage, SaveMasterStyles.StatusLabel, Array.Empty<GUILayoutOption>()); } GUI.DragWindow(new Rect(0f, 0f, 840f, 24f)); } private void DrawCurrentSaveHeader() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) string currentSaveDisplayName = GetCurrentSaveDisplayName(); string text = (string.IsNullOrWhiteSpace(currentSaveDisplayName) ? "Current Save: none" : ("Current Save: " + currentSaveDisplayName)); Rect rect = GUILayoutUtility.GetRect(818f, 36f); Color color = GUI.color; GUI.color = new Color(0.05f, 0.32f, 0.08f, 0.86f); GUI.Box(rect, GUIContent.none); GUI.color = color; GUI.Label(new Rect(((Rect)(ref rect)).x + 12f, ((Rect)(ref rect)).y + 8f, ((Rect)(ref rect)).width - 24f, 22f), text, SaveMasterStyles.HeaderLabel); } private void DrawWarning(string message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(818f, 30f); Color color = GUI.color; GUI.color = new Color(0.42f, 0.18f, 0.02f, 0.86f); GUI.Box(rect, GUIContent.none); GUI.color = color; GUI.Label(new Rect(((Rect)(ref rect)).x + 10f, ((Rect)(ref rect)).y + 6f, ((Rect)(ref rect)).width - 20f, 20f), message, SaveMasterStyles.HeaderLabel); } private void DrawSaveEntry(SaveEntry entry) { SaveEntry entry2 = entry; GUILayout.BeginVertical((GUIStyle)(string.Equals(entry2.FileName, GetCurrentSaveName(), StringComparison.Ordinal) ? ((object)SaveMasterStyles.CurrentSaveBox) : ((object)GUI.skin.box)), Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(545f) }); GUILayout.Label(entry2.DisplayName, SaveMasterStyles.SaveTitle, Array.Empty<GUILayoutOption>()); GUILayout.Label(entry2.FileName + " | " + entry2.DateText, SaveMasterStyles.SmallLabel, Array.Empty<GUILayoutOption>()); GUILayout.Space(4f); GUILayout.Label("Level: " + entry2.LevelText + " Money: " + entry2.CurrencyText + " Time: " + FormatTime(entry2.TimePlayedSeconds), SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); GUILayout.Label($"Total Haul: {entry2.TotalHaulText} Backups: {entry2.BackupCount}", SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); GUILayout.Label("Players: " + entry2.PlayerNamesText, SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); if (!entry2.IsValid) { GUILayout.Label("Main save file is missing or unreadable; backups may still exist.", SaveMasterStyles.WarningLabel, Array.Empty<GUILayoutOption>()); } GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(245f) }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawEntryButton("Load", delegate { ConfirmLoad(entry2, RestorePoint.Current); }, null, entry2.IsValid); DrawEntryButton("Prev", delegate { ConfirmLoad(entry2, RestorePoint.LatestBackup); }, null, entry2.BackupCount > 0); DrawEntryButton("Start", delegate { ConfirmLoad(entry2, RestorePoint.OldestBackup); }, null, entry2.BackupCount > 0); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); DrawEntryButton("Clone", delegate { CloneExistingSave(entry2); }); DrawEntryButton("Rename", delegate { _dialogSave = entry2; _renameSaveName = entry2.DisplayName; _dialogMode = DialogMode.Rename; }); DrawEntryButton("Delete", delegate { _dialogSave = entry2; _dialogMode = DialogMode.Delete; }, SaveMasterStyles.DeleteButton); GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private static void DrawEntryButton(string label, Action onClick, GUIStyle? style = null, bool enabled = true) { bool enabled2 = GUI.enabled; GUI.enabled = enabled2 && enabled; if (GUILayout.Button(label, style ?? GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { onClick(); } GUI.enabled = enabled2; } private void DrawDialog() { //IL_009b: 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_00ae: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (_menuOpen && _dialogMode != 0) { Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - 470f) / 2f, ((float)Screen.height - 230f) / 2f, 470f, 230f); string text = _dialogMode switch { DialogMode.NewSave => "New Save", DialogMode.Rename => "Rename Save", DialogMode.Delete => "Delete Save", DialogMode.Load => "Load Save", _ => "SaveMaster", }; GUI.Window(((Object)this).GetInstanceID() + 7141, val, new WindowFunction(DrawDialogWindow), text); } } private void DrawDialogWindow(int windowId) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); GUILayout.Space(12f); switch (_dialogMode) { case DialogMode.NewSave: DrawNewSaveDialog(); break; case DialogMode.Rename: DrawRenameDialog(); break; case DialogMode.Delete: DrawDeleteDialog(); break; case DialogMode.Load: DrawLoadDialog(); break; } GUI.DragWindow(new Rect(0f, 0f, 470f, 24f)); } private void DrawNewSaveDialog() { GUILayout.Label("Create a new save from the current checkpoint.", SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); GUILayout.Space(8f); GUILayout.Label("Save name", SaveMasterStyles.SmallLabel, Array.Empty<GUILayoutOption>()); GUI.SetNextControlName("SaveMasterNewSaveName"); _newSaveName = GUILayout.TextField(_newSaveName, 40, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Create", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { CreateSnapshotFromCurrent(_newSaveName); CloseDialog(); } if (GUILayout.Button("Cancel", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { CloseDialog(); } GUILayout.EndHorizontal(); GUI.FocusControl("SaveMasterNewSaveName"); } private void DrawRenameDialog() { if (_dialogSave == null) { CloseDialog(); return; } GUILayout.Label("Renaming: " + _dialogSave.FileName, SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); GUILayout.Space(8f); GUILayout.Label("New display name", SaveMasterStyles.SmallLabel, Array.Empty<GUILayoutOption>()); GUI.SetNextControlName("SaveMasterRenameName"); _renameSaveName = GUILayout.TextField(_renameSaveName, 40, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Rename", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { RenameSave(_dialogSave.FileName, _renameSaveName); CloseDialog(); } if (GUILayout.Button("Cancel", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { CloseDialog(); } GUILayout.EndHorizontal(); GUI.FocusControl("SaveMasterRenameName"); } private void DrawDeleteDialog() { if (_dialogSave == null) { CloseDialog(); return; } GUILayout.Label("Delete this save permanently?", SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); GUILayout.Space(8f); GUILayout.Label(_dialogSave.FileName, SaveMasterStyles.SaveTitle, Array.Empty<GUILayoutOption>()); GUILayout.Label("This removes the whole save folder, including backups.", SaveMasterStyles.WarningLabel, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Delete Forever", SaveMasterStyles.DeleteButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { DeleteSave(_dialogSave.FileName); CloseDialog(); } if (GUILayout.Button("Cancel", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { CloseDialog(); } GUILayout.EndHorizontal(); } private void DrawLoadDialog() { if (_dialogSave == null) { CloseDialog(); return; } GUILayout.Label("Load this save now?", SaveMasterStyles.RowLabel, Array.Empty<GUILayoutOption>()); GUILayout.Space(8f); GUILayout.Label(_dialogSave.FileName, SaveMasterStyles.SaveTitle, Array.Empty<GUILayoutOption>()); GUILayout.Label(GetRestorePointDescription(_dialogRestorePoint), SaveMasterStyles.WarningLabel, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Load", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { ((MonoBehaviour)this).StartCoroutine(LoadSaveRoutine(_dialogSave, _dialogRestorePoint)); CloseDialog(); } if (GUILayout.Button("Cancel", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(36f) })) { CloseDialog(); } GUILayout.EndHorizontal(); } private void ConfirmLoad(SaveEntry entry, RestorePoint restorePoint) { _dialogSave = entry; _dialogRestorePoint = restorePoint; _dialogMode = DialogMode.Load; } private void CloseDialog() { _dialogMode = DialogMode.None; _dialogSave = null; _dialogRestorePoint = RestorePoint.Current; } private void QuickLoadCurrentSave() { if (_operationRunning) { PublishStatus("Save operation already running."); return; } string currentSave = GetCurrentSaveName(); if (string.IsNullOrWhiteSpace(currentSave)) { PublishStatus("No current save to load."); return; } SaveEntry saveEntry = _saveEntries.FirstOrDefault((SaveEntry save) => save.FileName == currentSave) ?? BuildEntryForSave(currentSave); if (!saveEntry.IsValid) { PublishStatus("Current save is not valid: " + currentSave); } else { ((MonoBehaviour)this).StartCoroutine(LoadSaveRoutine(saveEntry, RestorePoint.Current)); } } [IteratorStateMachine(typeof(<LoadSaveRoutine>d__65))] private IEnumerator LoadSaveRoutine(SaveEntry entry, RestorePoint restorePoint) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadSaveRoutine>d__65(0) { <>4__this = this, entry = entry, restorePoint = restorePoint }; } private bool PrepareRestorePoint(SaveEntry entry, RestorePoint restorePoint) { object obj; switch (restorePoint) { case RestorePoint.Current: return true; default: obj = entry.Backups.LastOrDefault() ?? string.Empty; break; case RestorePoint.LatestBackup: obj = entry.Backups.FirstOrDefault() ?? string.Empty; break; } string text = (string)obj; if (string.IsNullOrWhiteSpace(text)) { PublishStatus("No backup exists for that restore point."); return false; } try { RestoreBackupToMain(entry.FileName, text); PublishStatus("Restored " + text + " before loading."); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not restore backup {text}: {ex}"); PublishStatus("Could not restore " + text + ": " + ex.Message); return false; } } private void RefreshSaves(bool force = false) { if (!_loadingSaves && !((Object)(object)StatsManager.instance == (Object)null) && (force || !(Time.unscaledTime - _lastRefreshTime < 0.5f))) { _lastRefreshTime = Time.unscaledTime; RefreshSavesAsync(); } } private async void RefreshSavesAsync() { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return; } _loadingSaves = true; try { List<SaveFolder> obj = await instance.SaveFileGetAllAsync(); _saveEntries.Clear(); foreach (SaveFolder item in obj) { _saveEntries.Add(BuildEntry(item)); } ((BaseUnityPlugin)this).Logger.LogDebug((object)$"Loaded {_saveEntries.Count} save entries."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not refresh saves: {ex}"); PublishStatus("Could not refresh saves: " + ex.Message); } finally { _loadingSaves = false; } } private SaveEntry BuildEntry(SaveFolder folder) { SaveEntry saveEntry = new SaveEntry(folder.name) { Backups = ((folder.backups != null) ? new List<string>(folder.backups) : new List<string>()), IsValid = folder.isValid }; FillEntryDetails(saveEntry); return saveEntry; } private SaveEntry BuildEntryForSave(string fileName) { SaveEntry saveEntry = new SaveEntry(fileName) { IsValid = File.Exists(GetMainSavePath(fileName)), Backups = GetBackups(fileName) }; FillEntryDetails(saveEntry); return saveEntry; } private void FillEntryDetails(SaveEntry entry) { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return; } try { entry.DisplayName = FirstNonEmpty(instance.SaveFileGetTeamName(entry.FileName, (string)null), entry.FileName); entry.DateText = FirstNonEmpty(instance.SaveFileGetDateAndTime(entry.FileName, (string)null), "Unknown date"); entry.LevelText = FirstNonEmpty(instance.SaveFileGetRunLevel(entry.FileName, (string)null), "0"); entry.CurrencyText = FormatMoney(instance.SaveFileGetRunCurrency(entry.FileName, (string)null)); entry.TotalHaulText = FormatMoney(instance.SaveFileGetTotalHaul(entry.FileName, (string)null)); entry.TimePlayedSeconds = instance.SaveFileGetTimePlayed(entry.FileName, (string)null); entry.PlayerNames = instance.SaveFileGetPlayerNames(entry.FileName, (string)null) ?? new List<string>(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Could not read save details for " + entry.FileName + ": " + ex.Message)); entry.DisplayName = entry.FileName + " (unreadable)"; entry.DateText = "Unknown date"; entry.LevelText = "?"; entry.CurrencyText = "?"; entry.TotalHaulText = "?"; entry.PlayerNames = new List<string>(); } } private void CreateSnapshotFromCurrent(string displayName) { if (!CanManageSaves()) { PublishStatus("Save creation is host/singleplayer only."); return; } if (_operationRunning) { PublishStatus("Save operation already running."); return; } string displayName2 = SanitizeDisplayName(displayName); string text = CreateUniqueSaveFileName(); string currentSaveName = GetCurrentSaveName(); try { if (!string.IsNullOrWhiteSpace(currentSaveName) && Directory.Exists(GetSaveDirectory(currentSaveName))) { CopySaveDirectory(currentSaveName, text); } else { StatsManager.instance.SaveGame(text); } RenameStoredSave(text, displayName2, updateAllBackups: true); RefreshSaves(force: true); PublishStatus("Created save " + text + "."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not create save snapshot: {ex}"); PublishStatus("Could not create save: " + ex.Message); } } private void CloneExistingSave(SaveEntry entry) { if (!CanManageSaves()) { PublishStatus("Save cloning is host/singleplayer only."); return; } string text = CreateUniqueSaveFileName(); try { CopySaveDirectory(entry.FileName, text); RenameStoredSave(text, entry.DisplayName + " Copy", updateAllBackups: true); RefreshSaves(force: true); PublishStatus("Cloned " + entry.FileName + "."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not clone save {entry.FileName}: {ex}"); PublishStatus("Could not clone save: " + ex.Message); } } private void RenameSave(string fileName, string displayName) { if (!CanManageSaves()) { PublishStatus("Save renaming is host/singleplayer only."); return; } string text = SanitizeDisplayName(displayName); try { RenameStoredSave(fileName, text, updateAllBackups: true); if (string.Equals(GetCurrentSaveName(), fileName, StringComparison.Ordinal)) { TeamNameField?.SetValue(StatsManager.instance, text); TeamNameChangedField?.SetValue(StatsManager.instance, !IsDefaultTeamName(text)); } RefreshSaves(force: true); PublishStatus("Renamed " + fileName + "."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not rename save {fileName}: {ex}"); PublishStatus("Could not rename save: " + ex.Message); } } private void DeleteSave(string fileName) { if (!CanManageSaves()) { PublishStatus("Save deletion is host/singleplayer only."); return; } if (!_allowManagerDeletes.Value) { PublishStatus("Manager deletes are disabled in config."); return; } try { ManagedDeleteInProgress = true; StatsManager.instance.SaveFileDelete(fileName); RefreshSaves(force: true); PublishStatus("Deleted " + fileName + "."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not delete save {fileName}: {ex}"); PublishStatus("Could not delete save: " + ex.Message); } finally { ManagedDeleteInProgress = false; } } internal bool ShouldBlockVanillaDelete(string saveFileName) { if (!_preventGameDeletes.Value || ManagedDeleteInProgress) { return false; } PublishStatus("Blocked game delete: " + saveFileName); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Blocked vanilla save deletion for " + saveFileName + ".")); RefreshSaves(force: true); return true; } internal bool TryAutoLoadCheckpointOnLevelFailure(ChangeLevelType changeLevelType) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!_autoLoadCheckpointOnDeath.Value || _operationRunning) { return false; } if ((int)changeLevelType != 0 || !CanManageSaves()) { return false; } if (SafeGameCheck((Func<bool>)SemiFunc.RunIsTutorial) || SafeGameCheck((Func<bool>)SemiFunc.RunIsArena) || SafeGameCheck((Func<bool>)SemiFunc.MenuLevel)) { return false; } string currentSaveName = GetCurrentSaveName(); if (string.IsNullOrWhiteSpace(currentSaveName)) { return false; } SaveEntry saveEntry = BuildEntryForSave(currentSaveName); RestorePoint restorePoint = ((!saveEntry.IsValid) ? RestorePoint.LatestBackup : RestorePoint.Current); if (!saveEntry.IsValid && saveEntry.BackupCount == 0) { return false; } PublishStatus("Death detected; loading checkpoint " + currentSaveName + "."); ((MonoBehaviour)this).StartCoroutine(LoadSaveRoutine(saveEntry, restorePoint)); return true; } internal void LogInfo(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } internal static void EnsureVanillaSaveMenuCanCreateSaves(MenuPageSaves? menuPageSaves) { if ((Object)(object)menuPageSaves == (Object)null) { return; } try { MenuPageSavesMaxSaveFilesField?.SetValue(menuPageSaves, 0); Plugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogDebug((object)"Disabled vanilla save slot cap for the save menu."); } } catch (Exception ex) { Plugin? instance2 = Instance; if (instance2 != null) { ((BaseUnityPlugin)instance2).Logger.LogDebug((object)("Could not adjust save slot cap: " + ex.Message)); } } } private static void CopySaveDirectory(string sourceSaveName, string destinationSaveName) { string saveDirectory = GetSaveDirectory(sourceSaveName); string saveDirectory2 = GetSaveDirectory(destinationSaveName); if (!Directory.Exists(saveDirectory)) { throw new DirectoryNotFoundException(saveDirectory); } if (Directory.Exists(saveDirectory2)) { throw new IOException("Destination save already exists: " + destinationSaveName); } Directory.CreateDirectory(saveDirectory2); string[] directories = Directory.GetDirectories(saveDirectory, "*", SearchOption.AllDirectories); for (int i = 0; i < directories.Length; i++) { string path = directories[i].Substring(saveDirectory.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); Directory.CreateDirectory(Path.Combine(saveDirectory2, path)); } directories = Directory.GetFiles(saveDirectory, "*", SearchOption.AllDirectories); foreach (string text in directories) { string path2 = text.Substring(saveDirectory.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string path3 = Path.GetDirectoryName(path2) ?? string.Empty; string path4 = RenameSaveFile(Path.GetFileName(path2), sourceSaveName, destinationSaveName); string destFileName = Path.Combine(saveDirectory2, path3, path4); File.Copy(text, destFileName, overwrite: false); } } private static string RenameSaveFile(string fileName, string sourceSaveName, string destinationSaveName) { if (!fileName.EndsWith(".es3", StringComparison.OrdinalIgnoreCase) || !fileName.StartsWith(sourceSaveName, StringComparison.OrdinalIgnoreCase)) { return fileName; } return destinationSaveName + fileName.Substring(sourceSaveName.Length); } private static void RestoreBackupToMain(string saveFileName, string backupName) { string mainSavePath = GetMainSavePath(saveFileName); string text = Path.Combine(GetSaveDirectory(saveFileName), backupName + ".es3"); if (!File.Exists(text)) { throw new FileNotFoundException("Backup file not found.", text); } if (File.Exists(mainSavePath)) { string destFileName = Path.Combine(GetSaveDirectory(saveFileName), $"{saveFileName}_SAVEMASTER_{DateTime.Now:yyyyMMdd_HHmmss}.es3"); File.Copy(mainSavePath, destFileName, overwrite: false); } File.Copy(text, mainSavePath, overwrite: true); } private static void RenameStoredSave(string saveFileName, string displayName, bool updateAllBackups) { string saveDirectory = GetSaveDirectory(saveFileName); if (!Directory.Exists(saveDirectory)) { throw new DirectoryNotFoundException(saveDirectory); } List<string> list = new List<string> { GetMainSavePath(saveFileName) }; if (updateAllBackups) { list.AddRange(Directory.GetFiles(saveDirectory, saveFileName + "_BACKUP*.es3", SearchOption.TopDirectoryOnly)); } foreach (string item in list.Distinct()) { if (File.Exists(item)) { WriteTeamName(item, displayName); } } } private static void WriteTeamName(string savePath, string displayName) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown string savePassword = GetSavePassword(); ES3Settings val = new ES3Settings(savePath, (EncryptionType)1, savePassword, (ES3Settings)null); ES3.Save<string>("teamName", displayName, val); ES3.Save<bool>("teamNameChanged", !IsDefaultTeamName(displayName), val); } private static List<string> GetBackups(string saveFileName) { try { return ((Object)(object)StatsManager.instance != (Object)null) ? StatsManager.instance.SaveFileGetBackups(saveFileName, SavesRoot) : new List<string>(); } catch { return new List<string>(); } } private void TryReloadInMemory(string saveFileName) { try { StatsManager.instance.LoadGame(saveFileName, (List<string>)null); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Could not reload save in memory: " + ex.Message)); } } private static void ClearRunFailureFlags() { RunManager instance = RunManager.instance; if (!((Object)(object)instance == (Object)null)) { SetFieldValue(RunManagerAllPlayersDeadField, instance, value: false); SetFieldValue(RunManagerLevelFailedField, instance, value: false); SetFieldValue(RunManagerGameOverField, instance, value: false); } } private static string CreateUniqueSaveFileName(string suffixToken = "") { string text = $"REPO_SAVE_{DateTime.Now:yyyy_MM_dd_HH_mm_ss}{suffixToken}"; string text2 = text; int num = 2; while (Directory.Exists(GetSaveDirectory(text2))) { text2 = $"{text}_{num}"; num++; } return text2; } private string BuildDefaultTeamName() { string current = GetCurrentSaveName(); SaveEntry saveEntry = _saveEntries.FirstOrDefault((SaveEntry save) => save.FileName == current); string currentSaveDisplayName = GetCurrentSaveDisplayName(); string arg = ((saveEntry != null) ? saveEntry.DisplayName : FirstNonEmpty(currentSaveDisplayName, "SaveMaster")); return $"{arg} {DateTime.Now:HH:mm}"; } private string GetCurrentSaveDisplayName() { string currentSave = GetCurrentSaveName(); if (string.IsNullOrWhiteSpace(currentSave)) { return string.Empty; } SaveEntry saveEntry = _saveEntries.FirstOrDefault((SaveEntry save) => string.Equals(save.FileName, currentSave, StringComparison.Ordinal)); if (saveEntry != null && !string.IsNullOrWhiteSpace(saveEntry.DisplayName)) { return saveEntry.DisplayName; } StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return currentSave; } try { return FirstNonEmpty(instance.SaveFileGetTeamName(currentSave, (string)null), currentSave); } catch { return currentSave; } } private static string SanitizeDisplayName(string displayName) { string text = (displayName ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { return "R.E.P.O."; } if (text.Length > 40) { return text.Substring(0, 40); } return text; } private static string GetCurrentSaveName() { try { return (SaveFileCurrentField?.GetValue(StatsManager.instance) as string) ?? string.Empty; } catch { return string.Empty; } } private static string GetSavePassword() { try { return (Es3PasswordField?.GetValue(StatsManager.instance) as string) ?? string.Empty; } catch { return string.Empty; } } private static string GetSaveDirectory(string saveFileName) { return Path.Combine(SavesRoot, saveFileName); } private static string GetMainSavePath(string saveFileName) { return Path.Combine(GetSaveDirectory(saveFileName), saveFileName + ".es3"); } private static string FirstNonEmpty(string? value, string fallback) { if (string.IsNullOrWhiteSpace(value)) { return fallback; } return value; } private static string FormatMoney(string rawValue) { if (!int.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return FirstNonEmpty(rawValue, "0"); } if (result >= 1000000) { return $"{(float)result / 1000000f:0.#}m"; } if (result >= 1000) { return $"{(float)result / 1000f:0.#}k"; } return result.ToString(CultureInfo.InvariantCulture); } private static string FormatTime(float seconds) { int num = Mathf.Max(0, Mathf.RoundToInt(seconds / 60f)); int num2 = num / 60; int num3 = num % 60; if (num2 <= 0) { return $"{num3}m"; } return $"{num2}h {num3}m"; } private static string GetRestorePointDescription(RestorePoint restorePoint) { return restorePoint switch { RestorePoint.Current => "Loads the latest saved state.", RestorePoint.LatestBackup => "Restores the newest backup first, then loads it. SaveMaster keeps the current file as a safety copy.", RestorePoint.OldestBackup => "Restores the oldest backup first, useful for returning to the start of a saved map. SaveMaster keeps the current file as a safety copy.", _ => string.Empty, }; } private bool CanManageSaves() { if ((Object)(object)StatsManager.instance != (Object)null) { return SafeGameCheck((Func<bool>)SemiFunc.IsMasterClientOrSingleplayer); } return false; } private static bool IsMultiplayer() { return SafeGameCheck((Func<bool>)SemiFunc.IsMultiplayer); } private static bool NoTextInputActive() { return HotkeyInput.NoTextInputActive((Func<bool>)SemiFunc.NoTextInputsActive); } private static bool SafeGameCheck(Func<bool> check) { try { return check(); } catch { return false; } } private static T GetFieldValue<T>(FieldInfo? field, object? instance, T fallback) { if (field == null || instance == null) { return fallback; } try { return (T)((field.GetValue(instance) is T val) ? ((object)val) : ((object)fallback)); } catch { return fallback; } } private static void SetFieldValue<T>(FieldInfo? field, object? instance, T value) { if (field == null || instance == null) { return; } try { field.SetValue(instance, value); } catch { } } private static bool IsDefaultTeamName(string displayName) { return string.Equals(displayName, "R.E.P.O.", StringComparison.Ordinal); } private void PublishStatus(string message) { _statusMessage = message; _statusMessageUntil = Time.unscaledTime + 4f; ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } private void DrawStatusMessage() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (!(Time.unscaledTime >= _statusMessageUntil) && !string.IsNullOrWhiteSpace(_statusMessage)) { Rect val = new Rect(Mathf.Max(12f, ((float)Screen.width - 560f) / 2f), 42f, Mathf.Min(560f, (float)Screen.width - 24f), 42f); Color color = GUI.color; GUI.color = new Color(0f, 0f, 0f, 0.72f); GUI.Box(val, GUIContent.none); GUI.color = color; GUI.Label(val, _statusMessage, SaveMasterStyles.ToastLabel); } } private static Rect ClampWindow(Rect rect) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) ((Rect)(ref rect)).x = Mathf.Clamp(((Rect)(ref rect)).x, 8f, Mathf.Max(8f, (float)Screen.width - ((Rect)(ref rect)).width - 8f)); ((Rect)(ref rect)).y = Mathf.Clamp(((Rect)(ref rect)).y, 8f, Mathf.Max(8f, (float)Screen.height - ((Rect)(ref rect)).height - 8f)); return rect; } private static void EnsureStyles() { SaveMasterStyles.Ensure(); } } internal sealed class SaveEntry { internal string FileName { get; } internal string DisplayName { get; set; } internal string DateText { get; set; } internal string LevelText { get; set; } internal string CurrencyText { get; set; } internal string TotalHaulText { get; set; } internal float TimePlayedSeconds { get; set; } internal bool IsValid { get; set; } internal List<string> Backups { get; set; } internal List<string> PlayerNames { get; set; } internal int BackupCount => Backups.Count; internal string PlayerNamesText { get { if (PlayerNames.Count <= 0) { return "Unknown"; } return string.Join(", ", PlayerNames); } } internal SaveEntry(string fileName) { FileName = fileName; DisplayName = fileName; DateText = "Unknown date"; LevelText = "?"; CurrencyText = "?"; TotalHaulText = "?"; Backups = new List<string>(); PlayerNames = new List<string>(); } } internal enum DialogMode { None, NewSave, Rename, Delete, Load } internal enum RestorePoint { Current, LatestBackup, OldestBackup } internal static class SaveMasterStyles { internal static GUIStyle HeaderLabel; internal static GUIStyle Subtitle; internal static GUIStyle SaveTitle; internal static GUIStyle RowLabel; internal static GUIStyle SmallLabel; internal static GUIStyle CenterLabel; internal static GUIStyle StatusLabel; internal static GUIStyle WarningLabel; internal static GUIStyle ToastLabel; internal static GUIStyle DeleteButton; internal static GUIStyle CurrentSaveBox; private static bool _initialized; internal static void Ensure() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0087: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_0179: 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_018a: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: 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_01c8: Expected O, but got Unknown //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_0267: 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_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Expected O, but got Unknown //IL_02cb: Unknown result type (might be due to invalid IL or missing references) if (!_initialized) { HeaderLabel = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1, alignment = (TextAnchor)3 }; HeaderLabel.normal.textColor = Color.white; Subtitle = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; Subtitle.normal.textColor = new Color(0.94f, 0.96f, 1f, 1f); SaveTitle = new GUIStyle(GUI.skin.label) { fontSize = 16, fontStyle = (FontStyle)1 }; SaveTitle.normal.textColor = Color.white; RowLabel = new GUIStyle(GUI.skin.label) { fontSize = 13, wordWrap = true }; RowLabel.normal.textColor = new Color(0.9f, 0.92f, 0.94f, 1f); SmallLabel = new GUIStyle(GUI.skin.label) { fontSize = 11, wordWrap = true }; SmallLabel.normal.textColor = new Color(0.72f, 0.76f, 0.8f, 1f); CenterLabel = new GUIStyle(RowLabel) { alignment = (TextAnchor)4 }; StatusLabel = new GUIStyle(RowLabel) { fontStyle = (FontStyle)1 }; StatusLabel.normal.textColor = new Color(0.7f, 1f, 0.7f, 1f); WarningLabel = new GUIStyle(RowLabel) { fontStyle = (FontStyle)1 }; WarningLabel.normal.textColor = new Color(1f, 0.82f, 0.35f, 1f); ToastLabel = new GUIStyle(GUI.skin.label) { fontSize = 16, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, wordWrap = true }; ToastLabel.normal.textColor = new Color(0.84f, 1f, 0.82f, 1f); DeleteButton = new GUIStyle(GUI.skin.button); DeleteButton.normal.textColor = Color.red; DeleteButton.hover.textColor = new Color(1f, 0.35f, 0.35f, 1f); DeleteButton.active.textColor = Color.red; CurrentSaveBox = new GUIStyle(GUI.skin.box); CurrentSaveBox.normal.textColor = Color.white; _initialized = true; } } } [HarmonyPatch(typeof(StatsManager), "SaveFileDelete")] internal static class StatsManagerSaveFileDeletePatch { private static bool Prefix(string saveFileName) { return !(Plugin.Instance?.ShouldBlockVanillaDelete(saveFileName) ?? false); } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class RunManagerChangeLevelPatch { private static bool Prefix(bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (!_levelFailed || _completedLevel) { return true; } return !(Plugin.Instance?.TryAutoLoadCheckpointOnLevelFailure(_changeLevelType) ?? false); } } [HarmonyPatch(typeof(MenuPageSaves), "Start")] internal static class MenuPageSavesStartPatch { private static void Postfix(MenuPageSaves __instance) { Plugin.EnsureVanillaSaveMenuCanCreateSaves(__instance); } } [HarmonyPatch(typeof(MenuPageSaves), "OnNewGame")] internal static class MenuPageSavesOnNewGamePatch { private static void Prefix(MenuPageSaves __instance) { Plugin.EnsureVanillaSaveMenuCanCreateSaves(__instance); } } }