Decompiled source of SaveBackups v1.0.0

BepInEx/plugins/SaveBackups.dll

Decompiled 20 minutes ago
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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[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("SaveBackups")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SaveBackups")]
[assembly: AssemblyTitle("SaveBackups")]
[assembly: AssemblyVersion("1.0.0.0")]
[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 SaveBackups
{
	public class BackupInfo
	{
		public string Path;

		public string SaveName;

		public DateTime TakenUtc;

		public bool Automatic;

		public string Summary = "";

		public long Bytes;
	}
	public static class BackupStore
	{
		private const string Stamp = "yyyyMMdd-HHmmss";

		public static string SaveFolder => Path.Combine(Application.persistentDataPath, "Saves");

		public static string Root => Path.Combine(Application.persistentDataPath, "SaveBackups");

		public static string SavePath(string saveName)
		{
			return Path.Combine(SaveFolder, saveName + ".txt");
		}

		public static string FolderFor(string saveName)
		{
			return Path.Combine(Root, Sanitise(saveName));
		}

		private static string Sanitise(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "unnamed";
			}
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char oldChar in invalidFileNameChars)
			{
				name = name.Replace(oldChar, '_');
			}
			return name;
		}

		public static List<BackupInfo> List(string saveName)
		{
			List<BackupInfo> list = new List<BackupInfo>();
			if (string.IsNullOrEmpty(saveName))
			{
				return list;
			}
			try
			{
				string path = FolderFor(saveName);
				if (!Directory.Exists(path))
				{
					return list;
				}
				string[] files = Directory.GetFiles(path, "*.txt");
				for (int i = 0; i < files.Length; i++)
				{
					BackupInfo backupInfo = Describe(files[i], saveName);
					if (backupInfo != null)
					{
						list.Add(backupInfo);
					}
				}
				list.Sort((BackupInfo a, BackupInfo b) => b.TakenUtc.CompareTo(a.TakenUtc));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not list backups: " + ex));
			}
			return list;
		}

		private static BackupInfo Describe(string path, string saveName)
		{
			try
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
				bool flag = !fileNameWithoutExtension.EndsWith("-manual", StringComparison.OrdinalIgnoreCase);
				DateTime takenUtc = (DateTime.TryParseExact(flag ? fileNameWithoutExtension : fileNameWithoutExtension.Substring(0, fileNameWithoutExtension.Length - "-manual".Length), "yyyyMMdd-HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out takenUtc) ? DateTime.SpecifyKind(takenUtc, DateTimeKind.Utc) : File.GetLastWriteTimeUtc(path));
				return new BackupInfo
				{
					Path = path,
					SaveName = saveName,
					TakenUtc = takenUtc,
					Automatic = flag,
					Bytes = new FileInfo(path).Length,
					Summary = Summarise(File.ReadAllText(path))
				};
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Skipping unreadable backup " + path + ": " + ex.Message));
				return null;
			}
		}

		private static string Summarise(string json)
		{
			try
			{
				ServerSaveObject val = JsonUtility.FromJson<ServerSaveObject>(json);
				if (val == null)
				{
					return "unreadable";
				}
				TimeSpan timeSpan = TimeSpan.FromSeconds(val.Playtime);
				return $"{val.Money} money, island {val.SpawnedIsland + 1}, {timeSpan.Hours}h {timeSpan.Minutes:D2}m played";
			}
			catch (Exception)
			{
				return "unreadable";
			}
		}

		public static bool Capture(string saveName, bool automatic)
		{
			if (string.IsNullOrEmpty(saveName))
			{
				return false;
			}
			try
			{
				string path = SavePath(saveName);
				if (!File.Exists(path))
				{
					Plugin.Log.LogWarning((object)("No save file on disk for '" + saveName + "', nothing to back up."));
					return false;
				}
				string text = File.ReadAllText(path);
				if (string.IsNullOrEmpty(text))
				{
					Plugin.Log.LogWarning((object)("Save file for '" + saveName + "' is empty, refusing to back it up."));
					return false;
				}
				string text2 = FolderFor(saveName);
				Directory.CreateDirectory(text2);
				if (automatic && Plugin.SkipUnchanged.Value && MatchesNewest(text2, text))
				{
					return false;
				}
				string path2 = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + (automatic ? "" : "-manual") + ".txt";
				string path3 = Path.Combine(text2, path2);
				if (File.Exists(path3))
				{
					path3 = Path.Combine(text2, DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N").Substring(0, 4) + ".txt");
				}
				File.WriteAllText(path3, text);
				Prune(saveName);
				Plugin.Log.LogInfo((object)("Backed up '" + saveName + "' (" + (automatic ? "automatic" : "manual") + ")."));
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Backup failed: " + ex));
				return false;
			}
		}

		private static bool MatchesNewest(string folder, string content)
		{
			try
			{
				string text = null;
				DateTime dateTime = DateTime.MinValue;
				string[] files = Directory.GetFiles(folder, "*.txt");
				foreach (string text2 in files)
				{
					DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text2);
					if (lastWriteTimeUtc > dateTime)
					{
						dateTime = lastWriteTimeUtc;
						text = text2;
					}
				}
				return text != null && File.ReadAllText(text) == content;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static void Prune(string saveName)
		{
			int value = Plugin.MaxAutomaticBackups.Value;
			if (value <= 0)
			{
				return;
			}
			List<BackupInfo> list = new List<BackupInfo>();
			foreach (BackupInfo item in List(saveName))
			{
				if (item.Automatic)
				{
					list.Add(item);
				}
			}
			for (int i = value; i < list.Count; i++)
			{
				try
				{
					File.Delete(list[i].Path);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Could not prune old backup: " + ex.Message));
				}
			}
		}

		public static bool Restore(BackupInfo info)
		{
			if (info == null)
			{
				return false;
			}
			try
			{
				if (!File.Exists(info.Path))
				{
					Plugin.Log.LogWarning((object)"That backup no longer exists on disk.");
					return false;
				}
				string text = File.ReadAllText(info.Path);
				if (string.IsNullOrEmpty(text))
				{
					Plugin.Log.LogError((object)"That backup is empty, refusing to restore it.");
					return false;
				}
				Capture(info.SaveName, automatic: false);
				Directory.CreateDirectory(SaveFolder);
				File.WriteAllText(SavePath(info.SaveName), text);
				Plugin.Log.LogInfo((object)$"Restored '{info.SaveName}' from {info.TakenUtc.ToLocalTime():g}.");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Restore failed: " + ex));
				return false;
			}
		}

		public static bool Delete(BackupInfo info)
		{
			try
			{
				if (info != null && File.Exists(info.Path))
				{
					File.Delete(info.Path);
					return true;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not delete backup: " + ex));
			}
			return false;
		}
	}
	public static class BackupUI
	{
		private static bool _failed;

		private static bool _built;

		private static ButtonManager _buttons;

		private static GameObject _infoHolder;

		private static Transform _screenParent;

		private static GameObject _buttonTemplate;

		private static GameObject _labelTemplate;

		private static GameObject _openButton;

		private static GameObject _panel;

		private static Transform _content;

		private static TextMeshProUGUI _status;

		private static string _selectedSave;

		private static bool _refreshing;

		public static void OnSaveSelected(string saveName)
		{
			if (_refreshing)
			{
				return;
			}
			_selectedSave = saveName;
			if (_failed)
			{
				return;
			}
			try
			{
				if (EnsureBuilt())
				{
					_openButton.SetActive(!string.IsNullOrEmpty(saveName));
					Close();
				}
			}
			catch (Exception ex)
			{
				_failed = true;
				Plugin.Log.LogError((object)("Backup UI failed, falling back to no UI: " + ex));
			}
		}

		public static void Close()
		{
			if (!_refreshing)
			{
				if ((Object)(object)_panel != (Object)null)
				{
					_panel.SetActive(false);
				}
				if ((Object)(object)_infoHolder != (Object)null && !string.IsNullOrEmpty(_selectedSave))
				{
					_infoHolder.SetActive(true);
				}
			}
		}

		private static bool EnsureBuilt()
		{
			if (_built && (Object)(object)_openButton != (Object)null && (Object)(object)_panel != (Object)null)
			{
				return true;
			}
			_buttons = Object.FindAnyObjectByType<ButtonManager>();
			if ((Object)(object)_buttons == (Object)null)
			{
				return false;
			}
			object? obj = AccessTools.Field(typeof(ButtonManager), "_selectedSaveInfoHolder")?.GetValue(_buttons);
			_infoHolder = (GameObject)((obj is GameObject) ? obj : null);
			if ((Object)(object)_infoHolder == (Object)null)
			{
				Plugin.Log.LogError((object)"Could not find the save info panel in the load game screen.");
				_failed = true;
				return false;
			}
			_screenParent = _infoHolder.transform.parent;
			_buttonTemplate = FindButtonTemplate(((Object)(object)_screenParent != (Object)null) ? _screenParent : _infoHolder.transform);
			if ((Object)(object)_buttonTemplate == (Object)null)
			{
				Plugin.Log.LogError((object)"Could not find a menu button to copy the style from.");
				_failed = true;
				return false;
			}
			TextMeshProUGUI componentInChildren = _buttonTemplate.GetComponentInChildren<TextMeshProUGUI>(true);
			_labelTemplate = (((Object)(object)componentInChildren != (Object)null) ? ((Component)componentInChildren).gameObject : null);
			_openButton = CloneButton(_infoHolder.transform, "Backups", Open);
			PlaceOpenButton();
			_panel = BuildPanel();
			_built = true;
			Plugin.Log.LogInfo((object)"Backups button added to the load game screen.");
			return true;
		}

		private static void PlaceOpenButton()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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_0087: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			LayoutElement val = _openButton.GetComponent<LayoutElement>();
			if ((Object)(object)val == (Object)null)
			{
				val = _openButton.AddComponent<LayoutElement>();
			}
			val.ignoreLayout = true;
			RectTransform component = _buttonTemplate.GetComponent<RectTransform>();
			float num;
			if ((Object)(object)component != (Object)null)
			{
				Rect rect = component.rect;
				if (((Rect)(ref rect)).height > 1f)
				{
					rect = component.rect;
					num = ((Rect)(ref rect)).height;
					goto IL_0066;
				}
			}
			num = 44f;
			goto IL_0066;
			IL_0066:
			float num2 = num;
			RectTransform component2 = _openButton.GetComponent<RectTransform>();
			if (!((Object)(object)component2 == (Object)null))
			{
				component2.anchorMin = new Vector2(0.5f, 0f);
				component2.anchorMax = new Vector2(0.5f, 0f);
				component2.pivot = new Vector2(0.5f, 0f);
				component2.sizeDelta = new Vector2(Plugin.OpenButtonWidth.Value, num2);
				component2.anchoredPosition = new Vector2(0f, Plugin.OpenButtonBottomMargin.Value);
			}
		}

		private static GameObject FindButtonTemplate(Transform root)
		{
			Button[] componentsInChildren = ((Component)root).GetComponentsInChildren<Button>(true);
			Button[] array = componentsInChildren;
			foreach (Button val in array)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).GetComponentInChildren<TextMeshProUGUI>(true) != (Object)null)
				{
					return ((Component)val).gameObject;
				}
			}
			if (componentsInChildren.Length == 0)
			{
				return null;
			}
			return ((Component)componentsInChildren[0]).gameObject;
		}

		private static GameObject BuildPanel()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SaveBackups_Panel", new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val.transform.SetParent(((Object)(object)_screenParent != (Object)null) ? _screenParent : _infoHolder.transform.parent, false);
			RectTransform component = _infoHolder.GetComponent<RectTransform>();
			RectTransform component2 = val.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				component2.anchorMin = component.anchorMin;
				component2.anchorMax = component.anchorMax;
				component2.pivot = component.pivot;
				component2.anchoredPosition = component.anchoredPosition;
				component2.sizeDelta = component.sizeDelta;
				((Transform)component2).localScale = ((Transform)component).localScale;
			}
			((Graphic)val.GetComponent<Image>()).color = new Color(0f, 0f, 0f, 0.35f);
			VerticalLayoutGroup obj = val.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj).childAlignment = (TextAnchor)1;
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f;
			((LayoutGroup)obj).padding = new RectOffset(14, 14, 14, 14);
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			CreateLabel(val.transform, "Backups", 30f, (TextAlignmentOptions)514);
			_status = CreateLabel(val.transform, "", 16f, (TextAlignmentOptions)514);
			if ((Object)(object)_status != (Object)null)
			{
				((TMP_Text)_status).textWrappingMode = (TextWrappingModes)1;
				((TMP_Text)_status).overflowMode = (TextOverflowModes)0;
				LayoutElement component3 = ((Component)_status).GetComponent<LayoutElement>();
				if ((Object)(object)component3 != (Object)null)
				{
					component3.minWidth = -1f;
					component3.preferredWidth = -1f;
					component3.flexibleWidth = 1f;
					component3.minHeight = 24f;
					component3.preferredHeight = 24f;
				}
			}
			GameObject val2 = new GameObject("Content", new Type[1] { typeof(RectTransform) });
			val2.transform.SetParent(val.transform, false);
			VerticalLayoutGroup obj2 = val2.AddComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj2).spacing = 4f;
			((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true;
			_content = val2.transform;
			val.SetActive(false);
			return val;
		}

		private static TextMeshProUGUI CreateLabel(Transform parent, string text, float size, TextAlignmentOptions alignment)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			GameObject val;
			TextMeshProUGUI val2;
			if ((Object)(object)_labelTemplate != (Object)null)
			{
				val = Object.Instantiate<GameObject>(_labelTemplate, parent, false);
				StripLocalization(val);
				val2 = val.GetComponent<TextMeshProUGUI>();
			}
			else
			{
				val = new GameObject("Label", new Type[1] { typeof(RectTransform) });
				val.transform.SetParent(parent, false);
				val2 = val.AddComponent<TextMeshProUGUI>();
			}
			((Object)val).name = "Label";
			val.SetActive(true);
			if ((Object)(object)val2 != (Object)null)
			{
				((TMP_Text)val2).text = text;
				((TMP_Text)val2).fontSize = size;
				((TMP_Text)val2).alignment = alignment;
				((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
				((Graphic)val2).raycastTarget = false;
			}
			LayoutElement val3 = val.GetComponent<LayoutElement>();
			if ((Object)(object)val3 == (Object)null)
			{
				val3 = val.AddComponent<LayoutElement>();
			}
			val3.minHeight = size + 6f;
			val3.preferredHeight = size + 6f;
			return val2;
		}

		private static GameObject CloneButton(Transform parent, string text, Action onClick)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			GameObject obj = Object.Instantiate<GameObject>(_buttonTemplate, parent, false);
			((Object)obj).name = "SB_Button_" + text;
			obj.SetActive(true);
			StripLocalization(obj);
			Button component = obj.GetComponent<Button>();
			if ((Object)(object)component != (Object)null)
			{
				component.onClick = new ButtonClickedEvent();
				((UnityEvent)component.onClick).AddListener((UnityAction)delegate
				{
					onClick();
				});
				((Selectable)component).interactable = true;
			}
			TextMeshProUGUI componentInChildren = obj.GetComponentInChildren<TextMeshProUGUI>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				((TMP_Text)componentInChildren).text = text;
			}
			return obj;
		}

		private static void StripLocalization(GameObject go)
		{
			MonoBehaviour[] componentsInChildren = go.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && ((object)val).GetType().Name.IndexOf("Localize", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private static void ClearChildren(Transform parent)
		{
			for (int num = parent.childCount - 1; num >= 0; num--)
			{
				Object.Destroy((Object)(object)((Component)parent.GetChild(num)).gameObject);
			}
		}

		private static Transform CreateRow(Transform parent, float height)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: 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_005d: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Row", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((LayoutGroup)obj).childAlignment = (TextAnchor)4;
			LayoutElement obj2 = val.AddComponent<LayoutElement>();
			obj2.minHeight = height;
			obj2.preferredHeight = height;
			return val.transform;
		}

		private static void SetWidth(GameObject go, float width)
		{
			LayoutElement val = go.GetComponent<LayoutElement>();
			if ((Object)(object)val == (Object)null)
			{
				val = go.AddComponent<LayoutElement>();
			}
			val.minWidth = width;
			val.preferredWidth = width;
			val.flexibleWidth = 0f;
		}

		private static void SetFlexible(GameObject go, float minWidth)
		{
			LayoutElement val = go.GetComponent<LayoutElement>();
			if ((Object)(object)val == (Object)null)
			{
				val = go.AddComponent<LayoutElement>();
			}
			val.minWidth = minWidth;
			val.preferredWidth = minWidth;
			val.flexibleWidth = 1f;
		}

		private static void Open()
		{
			if (!((Object)(object)_panel == (Object)null) && !string.IsNullOrEmpty(_selectedSave))
			{
				Refresh("");
				_infoHolder.SetActive(false);
				_panel.SetActive(true);
			}
		}

		private static void ReloadAndReselect()
		{
			_refreshing = true;
			try
			{
				SaveManager.LoadAllServers();
				if (AccessTools.Field(typeof(ButtonManager), "_savedServerTexts")?.GetValue(_buttons) is List<TextMeshProUGUI> list)
				{
					foreach (TextMeshProUGUI item in list)
					{
						if ((Object)(object)item != (Object)null && ((Component)item).gameObject.activeInHierarchy && ((TMP_Text)item).text == _selectedSave)
						{
							_buttons.SelectServer(item);
							break;
						}
					}
				}
				if (SaveManager.CurServerSave == null)
				{
					SaveManager.SelectServer(_selectedSave);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not refresh the save list after restoring: " + ex));
			}
			finally
			{
				_refreshing = false;
			}
			if (SaveManager.CurServerSave == null)
			{
				Plugin.Log.LogWarning((object)"The save list refreshed but no save is selected. Pick the save again before loading.");
			}
			if ((Object)(object)_infoHolder != (Object)null)
			{
				_infoHolder.SetActive(false);
			}
			if ((Object)(object)_panel != (Object)null)
			{
				_panel.SetActive(true);
			}
		}

		private static void Refresh(string status)
		{
			ClearChildren(_content);
			List<BackupInfo> list = BackupStore.List(_selectedSave);
			int num = 0;
			foreach (BackupInfo item in list)
			{
				if (num >= Plugin.MaxRowsShown.Value)
				{
					break;
				}
				BackupInfo captured = item;
				Transform parent = CreateRow(_content, 34f);
				string when = captured.TakenUtc.ToLocalTime().ToString("MMM d  HH:mm");
				TextMeshProUGUI val = CreateLabel(parent, string.Concat(str1: captured.Automatic ? "" : "  (manual)", str0: when, str2: "   ", str3: captured.Summary), 16f, (TextAlignmentOptions)513);
				if ((Object)(object)val != (Object)null)
				{
					SetFlexible(((Component)val).gameObject, 120f);
					((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
					((TMP_Text)val).overflowMode = (TextOverflowModes)1;
				}
				SetWidth(CloneButton(parent, "Restore", delegate
				{
					if (!BackupStore.Restore(captured))
					{
						Refresh("Restore failed, see the log.");
					}
					else
					{
						ReloadAndReselect();
						Refresh("Restored " + when + ".");
					}
				}), 104f);
				SetWidth(CloneButton(parent, "Delete", delegate
				{
					BackupStore.Delete(captured);
					Refresh("Deleted " + when + ".");
				}), 94f);
				num++;
			}
			if (num == 0)
			{
				CreateLabel(_content, "No backups yet for this save.", 19f, (TextAlignmentOptions)514);
			}
			Transform parent2 = CreateRow(_content, 40f);
			SetWidth(CloneButton(parent2, "Back up now", delegate
			{
				Refresh(BackupStore.Capture(_selectedSave, automatic: false) ? "Backup taken." : "Could not take a backup, see the log.");
			}), 168f);
			SetWidth(CloneButton(parent2, "Open folder", delegate
			{
				Application.OpenURL("file://" + BackupStore.FolderFor(_selectedSave));
				Refresh("Opened the backup folder.");
			}), 150f);
			SetWidth(CloneButton(parent2, "Back", Close), 104f);
			if ((Object)(object)_status != (Object)null)
			{
				((TMP_Text)_status).text = (string.IsNullOrEmpty(status) ? $"{list.Count} backup(s). Restoring saves the current file first." : status);
			}
		}
	}
	[BepInPlugin("dazed.howtofish.savebackups", "SaveBackups", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "dazed.howtofish.savebackups";

		public const string Name = "SaveBackups";

		public const string Version = "1.0.0";

		public static ManualLogSource Log;

		public static ConfigEntry<bool> BackupOnSave;

		public static ConfigEntry<bool> BackupOnLoad;

		public static ConfigEntry<bool> SkipUnchanged;

		public static ConfigEntry<int> MaxAutomaticBackups;

		public static ConfigEntry<int> MaxRowsShown;

		public static ConfigEntry<float> OpenButtonWidth;

		public static ConfigEntry<float> OpenButtonBottomMargin;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			BackupOnSave = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "BackupOnSave", true, "Take a backup every time the game saves, which includes the five minute autosave.");
			BackupOnLoad = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "BackupOnLoad", true, "Take a backup when a save is loaded, so you always have the state you started a session with.");
			SkipUnchanged = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SkipUnchanged", true, "Do not write an automatic backup when the save file is identical to the newest one already stored.");
			MaxAutomaticBackups = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxAutomaticBackups", 10, "How many automatic backups to keep per save. The oldest are deleted beyond this. Manual backups are never pruned. 0 keeps everything.");
			MaxRowsShown = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxRowsShown", 7, "How many backups to list in the menu at once, so the panel does not overflow.");
			OpenButtonWidth = ((BaseUnityPlugin)this).Config.Bind<float>("General", "OpenButtonWidth", 240f, "Width of the Backups button on the save info panel, so it matches the Delete and Load buttons instead of stretching.");
			OpenButtonBottomMargin = ((BaseUnityPlugin)this).Config.Bind<float>("General", "OpenButtonBottomMargin", 22f, "Gap in pixels between the Backups button and the bottom edge of the panel. Raise it if the button sits too low.");
			_harmony = new Harmony("dazed.howtofish.savebackups");
			Patches.ApplyAll(_harmony);
			Log.LogInfo((object)("SaveBackups 1.0.0 loaded. Backups are kept in " + BackupStore.Root));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	public static class Patches
	{
		public static void ApplyAll(Harmony harmony)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			harmony.Patch((MethodBase)AccessTools.Method(typeof(ButtonManager), "SelectServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SelectServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "SaveServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SaveServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "SelectServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SelectedForLoadPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(ButtonManager), "UpdateSavedServerButtons", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "ListRefreshedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.Log.LogInfo((object)"Hooked the load game screen and the save routine.");
		}

		private static void SelectServerPostfix(TextMeshProUGUI text)
		{
			BackupUI.OnSaveSelected(((Object)(object)text != (Object)null) ? ((TMP_Text)text).text : null);
		}

		private static void ListRefreshedPostfix()
		{
			BackupUI.Close();
		}

		private static void SaveServerPostfix()
		{
			if (Plugin.BackupOnSave.Value && SaveManager.CurServerSave != null)
			{
				BackupStore.Capture(SaveManager.CurServerSave.Name, automatic: true);
			}
		}

		private static void SelectedForLoadPostfix()
		{
			if (Plugin.BackupOnLoad.Value && SaveManager.CurServerSave != null)
			{
				BackupStore.Capture(SaveManager.CurServerSave.Name, automatic: true);
			}
		}
	}
}