Decompiled source of SaveBackups v1.4.0

BepInEx/plugins/SaveBackups.dll

Decompiled a month ago
using System;
using System.Collections;
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.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
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.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0")]
[assembly: AssemblyProduct("SaveBackups")]
[assembly: AssemblyTitle("SaveBackups")]
[assembly: AssemblyVersion("1.4.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 enum SnapshotKind
	{
		Automatic,
		Manual,
		PreRestore
	}
	public class CompanionRecord
	{
		public string Id = "";

		public string DisplayName = "";

		public string StoredName = "";
	}
	public class SnapshotFile
	{
		public string SaveName = "";

		public string TakenUtc = "";

		public bool Automatic = true;

		public string Kind = "";

		public List<CompanionRecord> Companions = new List<CompanionRecord>();
	}
	public class BackupInfo
	{
		public string Path;

		public string SaveName;

		public DateTime TakenUtc;

		public SnapshotKind Kind;

		public bool Legacy;

		public string Summary = "";

		public string SaveLabel = "";

		public int Island = -1;

		public int Money = -1;

		public TimeSpan Played = TimeSpan.Zero;

		public bool Readable;

		public long Bytes;

		public List<CompanionRecord> Companions = new List<CompanionRecord>();

		public bool Favorite;

		public bool Automatic => Kind == SnapshotKind.Automatic;

		public string SaveFilePath
		{
			get
			{
				if (!Legacy)
				{
					return System.IO.Path.Combine(Path, "save.txt");
				}
				return Path;
			}
		}
	}
	public class RestoreResult
	{
		public bool Success;

		public List<string> Missing = new List<string>();

		public List<string> Cleared = new List<string>();

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

		private const string SaveEntry = "save.txt";

		private const string SnapshotEntry = "snapshot.json";

		private const string ManualTag = "manual";

		private const string PreRestoreTag = "prerestore";

		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, SanitiseSaveName(saveName));
		}

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

		private static string TagFor(SnapshotKind kind)
		{
			return kind switch
			{
				SnapshotKind.Manual => "-manual", 
				SnapshotKind.PreRestore => "-prerestore", 
				_ => "", 
			};
		}

		private static void WriteAtomic(string path, string content)
		{
			string directoryName = Path.GetDirectoryName(path);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			string text = path + ".writing";
			File.WriteAllText(text, content);
			if (!File.Exists(path))
			{
				File.Move(text, path);
				return;
			}
			try
			{
				File.Replace(text, path, null);
			}
			catch (Exception)
			{
				File.Delete(path);
				File.Move(text, path);
			}
		}

		public static List<BackupInfo> List(string saveName)
		{
			return List(saveName, includeSummary: true);
		}

		public static List<BackupInfo> List(string saveName, bool includeSummary)
		{
			List<BackupInfo> list = new List<BackupInfo>();
			if (string.IsNullOrEmpty(saveName))
			{
				return list;
			}
			try
			{
				string path = FolderFor(saveName);
				if (!Directory.Exists(path))
				{
					return list;
				}
				string[] directories = Directory.GetDirectories(path);
				for (int i = 0; i < directories.Length; i++)
				{
					BackupInfo backupInfo = DescribeSnapshot(directories[i], saveName, includeSummary);
					if (backupInfo != null)
					{
						list.Add(backupInfo);
					}
				}
				directories = Directory.GetFiles(path, "*.txt");
				for (int i = 0; i < directories.Length; i++)
				{
					BackupInfo backupInfo2 = DescribeLegacy(directories[i], saveName, includeSummary);
					if (backupInfo2 != null)
					{
						list.Add(backupInfo2);
					}
				}
				foreach (BackupInfo item in list)
				{
					item.Favorite = Favorites.IsFavorite(item);
				}
				list.Sort((BackupInfo a, BackupInfo b) => (a.Favorite != b.Favorite) ? ((!a.Favorite) ? 1 : (-1)) : b.TakenUtc.CompareTo(a.TakenUtc));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not list backups: " + ex));
			}
			return list;
		}

		private static bool ParseStamp(string name, out DateTime taken, out SnapshotKind kind)
		{
			kind = SnapshotKind.Automatic;
			taken = DateTime.MinValue;
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			string[] array = name.Split('-');
			if (array.Length < 2)
			{
				return false;
			}
			for (int i = 2; i < array.Length; i++)
			{
				if (string.Equals(array[i], "manual", StringComparison.OrdinalIgnoreCase))
				{
					kind = SnapshotKind.Manual;
				}
				else if (string.Equals(array[i], "prerestore", StringComparison.OrdinalIgnoreCase))
				{
					kind = SnapshotKind.PreRestore;
				}
			}
			if (DateTime.TryParseExact(array[0] + "-" + array[1], "yyyyMMdd-HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out taken))
			{
				taken = DateTime.SpecifyKind(taken, DateTimeKind.Utc);
				return true;
			}
			taken = DateTime.MinValue;
			return false;
		}

		public static bool RemoveCompanion(BackupInfo info, CompanionRecord record, out string message)
		{
			message = "";
			if (info == null || record == null)
			{
				message = "There is nothing to remove.";
				return false;
			}
			if (info.Legacy)
			{
				message = "Older backups do not hold mod files.";
				return false;
			}
			try
			{
				string path = Path.Combine(info.Path, record.StoredName);
				if (File.Exists(path))
				{
					File.Delete(path);
				}
				string path2 = Path.Combine(info.Path, "snapshot.json");
				if (File.Exists(path2))
				{
					SnapshotFile snapshotFile = JsonConvert.DeserializeObject<SnapshotFile>(File.ReadAllText(path2));
					if (snapshotFile != null)
					{
						if (snapshotFile.Companions == null)
						{
							snapshotFile.Companions = new List<CompanionRecord>();
						}
						snapshotFile.Companions.RemoveAll((CompanionRecord entry) => entry != null && entry.Id == record.Id);
						WriteAtomic(path2, JsonConvert.SerializeObject((object)snapshotFile, (Formatting)1));
					}
				}
				info.Companions.RemoveAll((CompanionRecord entry) => entry != null && entry.Id == record.Id);
				string text = (string.IsNullOrEmpty(record.DisplayName) ? record.Id : record.DisplayName);
				Plugin.Log.LogInfo((object)$"Removed the {text} file from the backup taken {info.TakenUtc.ToLocalTime():g}.");
				message = "Removed the " + text + " file from that backup.";
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not remove a mod file from a backup: " + ex));
				message = "Could not remove that file, see the log.";
				return false;
			}
		}

		private static SnapshotKind KindFrom(SnapshotFile snapshot, SnapshotKind fallback)
		{
			if (snapshot == null)
			{
				return fallback;
			}
			if (!string.IsNullOrEmpty(snapshot.Kind))
			{
				if (string.Equals(snapshot.Kind, SnapshotKind.Manual.ToString(), StringComparison.OrdinalIgnoreCase))
				{
					return SnapshotKind.Manual;
				}
				if (string.Equals(snapshot.Kind, SnapshotKind.PreRestore.ToString(), StringComparison.OrdinalIgnoreCase))
				{
					return SnapshotKind.PreRestore;
				}
				if (string.Equals(snapshot.Kind, SnapshotKind.Automatic.ToString(), StringComparison.OrdinalIgnoreCase))
				{
					return SnapshotKind.Automatic;
				}
			}
			if (!snapshot.Automatic)
			{
				return SnapshotKind.Manual;
			}
			return SnapshotKind.Automatic;
		}

		private static BackupInfo DescribeSnapshot(string folder, string saveName, bool includeSummary)
		{
			try
			{
				string text = Path.Combine(folder, "save.txt");
				if (!File.Exists(text))
				{
					return null;
				}
				if (!ParseStamp(Path.GetFileName(folder), out var taken, out var kind))
				{
					taken = Directory.GetLastWriteTimeUtc(folder);
				}
				BackupInfo backupInfo = new BackupInfo
				{
					Path = folder,
					SaveName = saveName,
					TakenUtc = taken,
					Kind = kind,
					Legacy = false,
					Bytes = new FileInfo(text).Length
				};
				string path = Path.Combine(folder, "snapshot.json");
				if (File.Exists(path))
				{
					SnapshotFile snapshotFile = JsonConvert.DeserializeObject<SnapshotFile>(File.ReadAllText(path));
					if (snapshotFile != null)
					{
						if (!string.IsNullOrEmpty(snapshotFile.SaveName) && snapshotFile.SaveName != saveName)
						{
							return null;
						}
						backupInfo.Kind = KindFrom(snapshotFile, kind);
						backupInfo.Companions = snapshotFile.Companions ?? new List<CompanionRecord>();
						if (DateTime.TryParse(snapshotFile.TakenUtc, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var result))
						{
							backupInfo.TakenUtc = DateTime.SpecifyKind(result, DateTimeKind.Utc);
						}
					}
				}
				if (includeSummary)
				{
					string json = File.ReadAllText(text);
					backupInfo.Summary = Summarise(json);
					ReadDetails(backupInfo, json);
				}
				return backupInfo;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Skipping unreadable snapshot " + folder + ": " + ex.Message));
				return null;
			}
		}

		private static BackupInfo DescribeLegacy(string path, string saveName, bool includeSummary)
		{
			try
			{
				if (!ParseStamp(Path.GetFileNameWithoutExtension(path), out var taken, out var kind))
				{
					taken = File.GetLastWriteTimeUtc(path);
				}
				BackupInfo backupInfo = new BackupInfo
				{
					Path = path,
					SaveName = saveName,
					TakenUtc = taken,
					Kind = kind,
					Legacy = true,
					Bytes = new FileInfo(path).Length
				};
				if (includeSummary)
				{
					string json = File.ReadAllText(path);
					backupInfo.Summary = Summarise(json);
					ReadDetails(backupInfo, json);
				}
				return backupInfo;
			}
			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";
			}
		}

		private static void ReadDetails(BackupInfo info, string json)
		{
			try
			{
				ServerSaveObject val = JsonUtility.FromJson<ServerSaveObject>(json);
				if (val != null)
				{
					info.SaveLabel = val.Name ?? "";
					info.Island = val.SpawnedIsland + 1;
					info.Money = val.Money;
					info.Played = TimeSpan.FromSeconds(val.Playtime);
					info.Readable = true;
				}
			}
			catch (Exception)
			{
			}
		}

		private static string Signature(string saveContent, List<KeyValuePair<CompanionRecord, string>> companions)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(saveContent);
			companions.Sort((KeyValuePair<CompanionRecord, string> a, KeyValuePair<CompanionRecord, string> b) => string.CompareOrdinal(a.Key.Id, b.Key.Id));
			foreach (KeyValuePair<CompanionRecord, string> companion in companions)
			{
				stringBuilder.Append('\0').Append(companion.Key.Id).Append('\0')
					.Append(companion.Value);
			}
			return stringBuilder.ToString();
		}

		private static string SignatureOf(BackupInfo info)
		{
			try
			{
				string saveContent = File.ReadAllText(info.SaveFilePath);
				List<KeyValuePair<CompanionRecord, string>> list = new List<KeyValuePair<CompanionRecord, string>>();
				foreach (CompanionRecord companion in info.Companions)
				{
					string path = Path.Combine(info.Path, companion.StoredName);
					if (File.Exists(path))
					{
						list.Add(new KeyValuePair<CompanionRecord, string>(companion, File.ReadAllText(path)));
					}
				}
				return Signature(saveContent, list);
			}
			catch (Exception)
			{
				return null;
			}
		}

		public static bool Capture(string saveName, bool automatic)
		{
			return Capture(saveName, (!automatic) ? SnapshotKind.Manual : SnapshotKind.Automatic);
		}

		public static bool Capture(string saveName, SnapshotKind kind)
		{
			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;
				}
				List<KeyValuePair<CompanionRecord, string>> list = CollectCompanions(saveName);
				if (kind == SnapshotKind.Automatic && Plugin.SkipUnchanged.Value && MatchesNewest(saveName, Signature(text, list)))
				{
					return false;
				}
				string text2 = FolderFor(saveName);
				Directory.CreateDirectory(text2);
				string text3 = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
				string text4 = Path.Combine(text2, text3 + TagFor(kind));
				int num = 2;
				while (Directory.Exists(text4) && num < 100)
				{
					text4 = Path.Combine(text2, text3 + "-" + num + TagFor(kind));
					num++;
				}
				Directory.CreateDirectory(text4);
				WriteAtomic(Path.Combine(text4, "save.txt"), text);
				SnapshotFile snapshotFile = new SnapshotFile
				{
					SaveName = saveName,
					TakenUtc = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture),
					Automatic = (kind == SnapshotKind.Automatic),
					Kind = kind.ToString()
				};
				foreach (KeyValuePair<CompanionRecord, string> item in list)
				{
					WriteAtomic(Path.Combine(text4, item.Key.StoredName), item.Value);
					snapshotFile.Companions.Add(item.Key);
				}
				WriteAtomic(Path.Combine(text4, "snapshot.json"), JsonConvert.SerializeObject((object)snapshotFile, (Formatting)1));
				Prune(saveName);
				string arg = ((list.Count > 0) ? $" with {list.Count} companion file(s)" : "");
				Plugin.Log.LogInfo((object)$"Backed up '{saveName}' ({kind}){arg}.");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Backup failed: " + ex));
				return false;
			}
		}

		private static List<KeyValuePair<CompanionRecord, string>> CollectCompanions(string saveName)
		{
			List<KeyValuePair<CompanionRecord, string>> list = new List<KeyValuePair<CompanionRecord, string>>();
			foreach (CompanionProvider item in Companions.All)
			{
				string text = Companions.ResolvePath(item, saveName);
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				try
				{
					if (!File.Exists(text))
					{
						continue;
					}
					string value = File.ReadAllText(text);
					if (!string.IsNullOrEmpty(value))
					{
						string text2 = Path.GetExtension(text);
						if (string.IsNullOrEmpty(text2))
						{
							text2 = ".dat";
						}
						list.Add(new KeyValuePair<CompanionRecord, string>(new CompanionRecord
						{
							Id = item.Id,
							DisplayName = item.DisplayName,
							StoredName = "companion." + item.Id + text2
						}, value));
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Could not read companion file for '" + item.Id + "': " + ex.Message));
				}
			}
			return list;
		}

		private static bool MatchesNewest(string saveName, string signature)
		{
			List<BackupInfo> list = List(saveName, includeSummary: false);
			if (list.Count == 0)
			{
				return false;
			}
			return SignatureOf(list[0]) == signature;
		}

		private static void Prune(string saveName)
		{
			List<BackupInfo> all = List(saveName, includeSummary: false);
			PruneKind(all, SnapshotKind.Automatic, Plugin.MaxAutomaticBackups.Value);
			PruneKind(all, SnapshotKind.PreRestore, Plugin.MaxPreRestoreBackups.Value);
		}

		private static void PruneKind(List<BackupInfo> all, SnapshotKind kind, int keep)
		{
			if (keep <= 0)
			{
				return;
			}
			List<BackupInfo> list = new List<BackupInfo>();
			foreach (BackupInfo item in all)
			{
				if (item.Kind == kind && !item.Favorite)
				{
					list.Add(item);
				}
			}
			for (int i = keep; i < list.Count; i++)
			{
				Delete(list[i]);
			}
		}

		public static RestoreResult Restore(BackupInfo info)
		{
			RestoreResult restoreResult = new RestoreResult();
			if (info == null)
			{
				return restoreResult;
			}
			try
			{
				string saveFilePath = info.SaveFilePath;
				if (!File.Exists(saveFilePath))
				{
					Plugin.Log.LogWarning((object)"That backup no longer exists on disk.");
					return restoreResult;
				}
				string text = File.ReadAllText(saveFilePath);
				if (string.IsNullOrEmpty(text))
				{
					Plugin.Log.LogError((object)"That backup is empty, refusing to restore it.");
					return restoreResult;
				}
				Capture(info.SaveName, SnapshotKind.PreRestore);
				Directory.CreateDirectory(SaveFolder);
				WriteAtomic(SavePath(info.SaveName), text);
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				foreach (CompanionRecord companion in info.Companions)
				{
					string path = Path.Combine(info.Path, companion.StoredName);
					if (!File.Exists(path))
					{
						continue;
					}
					CompanionProvider companionProvider = Companions.Get(companion.Id);
					if (companionProvider == null)
					{
						string text2 = (string.IsNullOrEmpty(companion.DisplayName) ? companion.Id : companion.DisplayName);
						restoreResult.Missing.Add(text2);
						Plugin.Log.LogWarning((object)("Snapshot contains data for '" + text2 + "' but that mod is not loaded, skipping it."));
						continue;
					}
					string text3 = Companions.ResolvePath(companionProvider, info.SaveName);
					if (string.IsNullOrEmpty(text3))
					{
						restoreResult.Missing.Add(companionProvider.DisplayName);
						continue;
					}
					WriteAtomic(text3, File.ReadAllText(path));
					hashSet.Add(companionProvider.Id);
					restoreResult.CompanionsRestored++;
				}
				ClearUnrecordedCompanions(info, hashSet, restoreResult);
				restoreResult.Success = true;
				Plugin.Log.LogInfo((object)$"Restored '{info.SaveName}' from {info.TakenUtc.ToLocalTime():g}, {restoreResult.CompanionsRestored} companion file(s) written, {restoreResult.Cleared.Count} removed.");
				return restoreResult;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Restore failed: " + ex));
				return restoreResult;
			}
		}

		private static void ClearUnrecordedCompanions(BackupInfo info, HashSet<string> restored, RestoreResult result)
		{
			foreach (CompanionProvider item in Companions.All)
			{
				if (restored.Contains(item.Id))
				{
					continue;
				}
				try
				{
					string text = Companions.ResolvePath(item, info.SaveName);
					if (!string.IsNullOrEmpty(text) && File.Exists(text))
					{
						File.Delete(text);
						result.Cleared.Add(item.DisplayName);
						Plugin.Log.LogInfo((object)("Removed current data for '" + item.DisplayName + "' because this snapshot predates it. The state before restoring was saved first."));
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Could not remove stale data for '" + item.Id + "': " + ex.Message));
				}
			}
		}

		public static bool Delete(BackupInfo info)
		{
			try
			{
				if (info == null)
				{
					return false;
				}
				Favorites.Forget(info);
				if (info.Legacy)
				{
					if (File.Exists(info.Path))
					{
						File.Delete(info.Path);
						return true;
					}
					return false;
				}
				if (Directory.Exists(info.Path))
				{
					Directory.Delete(info.Path, recursive: true);
					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 TMP_InputField _inputTemplate;

		private static Transform _content;

		private static TextMeshProUGUI _status;

		private static string _selectedSave;

		private static bool _refreshing;

		private static int _page;

		private static string _armedPath;

		private static string _armedCompanion;

		private static TextMeshProUGUI _pageLabel;

		private const float StarWidth = 26f;

		private const float TakenWidth = 116f;

		private const float NameWidth = 150f;

		private const float IslandWidth = 56f;

		private const float MoneyWidth = 74f;

		private const float PlayedWidth = 82f;

		private const float ModsWidth = 52f;

		private const float ActionsWidth = 112f;

		public static void OnSaveSelected(string saveName)
		{
			if (_refreshing)
			{
				return;
			}
			_selectedSave = saveName;
			_page = 0;
			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)
			{
				_armedPath = null;
				if ((Object)(object)_panel != (Object)null)
				{
					_panel.SetActive(false);
				}
				if ((Object)(object)_infoHolder != (Object)null && !string.IsNullOrEmpty(_selectedSave) && SaveManager.CurServerSave != null)
				{
					_infoHolder.SetActive(true);
				}
			}
		}

		public static void Tick()
		{
			if (!((Object)(object)_panel == (Object)null) && _panel.activeSelf && ((Object)(object)_infoHolder == (Object)null || !((Component)_infoHolder.transform.parent).gameObject.activeInHierarchy))
			{
				Close();
			}
		}

		public static void OnListRefreshed()
		{
			if (!_refreshing)
			{
				if (SaveManager.CurServerSave == null)
				{
					_selectedSave = null;
				}
				Close();
			}
		}

		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_0100: 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_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: 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_02ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0361: Unknown result type (might be due to invalid IL or missing references)
			//IL_036e: Unknown result type (might be due to invalid IL or missing references)
			//IL_037b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f1: Unknown result type (might be due to invalid IL or missing references)
			List<RectTransform> list = new List<RectTransform>();
			Transform val = null;
			Button[] componentsInChildren = _infoHolder.GetComponentsInChildren<Button>(true);
			foreach (Button val2 in componentsInChildren)
			{
				if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)(object)_openButton || ((Object)val2).name.StartsWith("SB_Button_"))
				{
					continue;
				}
				RectTransform component = ((Component)val2).GetComponent<RectTransform>();
				if (!((Object)(object)component == (Object)null))
				{
					if ((Object)(object)val == (Object)null)
					{
						val = ((Transform)component).parent;
					}
					if ((Object)(object)((Transform)component).parent == (Object)(object)val)
					{
						list.Add(component);
					}
				}
			}
			if (list.Count < 2 || (Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)$"Found {list.Count} existing button(s) on the save info panel, falling back to a floating Backups button.");
				FloatOpenButton();
				return;
			}
			_openButton.transform.SetParent(val, false);
			GridLayoutGroup component2 = ((Component)val).GetComponent<GridLayoutGroup>();
			if ((Object)(object)component2 != (Object)null)
			{
				int num = list.Count + 1;
				float num2 = (component2.cellSize.x * (float)list.Count + component2.spacing.x * (float)(list.Count - 1) - component2.spacing.x * (float)(num - 1)) / (float)num;
				component2.constraint = (Constraint)1;
				component2.constraintCount = num;
				component2.cellSize = new Vector2(Mathf.Max(40f, num2), component2.cellSize.y);
				LayoutElement component3 = _openButton.GetComponent<LayoutElement>();
				if ((Object)(object)component3 != (Object)null)
				{
					component3.ignoreLayout = false;
				}
				Plugin.Log.LogInfo((object)$"Backups button placed beside Delete and Load in the game's grid, {num} columns at {component2.cellSize.x:F0} wide.");
				return;
			}
			if ((Object)(object)((Component)val).GetComponent<HorizontalLayoutGroup>() != (Object)null)
			{
				LayoutElement val3 = _openButton.GetComponent<LayoutElement>();
				if ((Object)(object)val3 == (Object)null)
				{
					val3 = _openButton.AddComponent<LayoutElement>();
				}
				val3.ignoreLayout = false;
				val3.flexibleWidth = 1f;
				val3.minWidth = -1f;
				val3.preferredWidth = -1f;
				Plugin.Log.LogInfo((object)"Backups button placed alongside Delete and Load in the game's horizontal layout.");
				return;
			}
			if ((Object)(object)((Component)val).GetComponent<LayoutGroup>() != (Object)null)
			{
				Plugin.Log.LogWarning((object)"The save info buttons use a layout this mod cannot share, falling back to a floating Backups button.");
				FloatOpenButton();
				return;
			}
			list.Sort((RectTransform a, RectTransform b) => a.anchoredPosition.x.CompareTo(b.anchoredPosition.x));
			RectTransform val4 = list[0];
			RectTransform val5 = list[list.Count - 1];
			float x = val4.anchoredPosition.x;
			Rect rect = val4.rect;
			float num3 = x - ((Rect)(ref rect)).width * 0.5f;
			float x2 = val5.anchoredPosition.x;
			rect = val5.rect;
			float num4 = x2 + ((Rect)(ref rect)).width * 0.5f - num3;
			rect = val4.rect;
			float height = ((Rect)(ref rect)).height;
			int num5 = list.Count + 1;
			float value = Plugin.ButtonGap.Value;
			float num6 = (num4 - value * (float)(num5 - 1)) / (float)num5;
			if (num6 < 40f)
			{
				Plugin.Log.LogWarning((object)"Not enough room beside Delete and Load, falling back to a floating Backups button.");
				FloatOpenButton();
				return;
			}
			RectTransform component4 = _openButton.GetComponent<RectTransform>();
			LayoutElement val6 = _openButton.GetComponent<LayoutElement>();
			if ((Object)(object)val6 == (Object)null)
			{
				val6 = _openButton.AddComponent<LayoutElement>();
			}
			val6.ignoreLayout = true;
			component4.anchorMin = val4.anchorMin;
			component4.anchorMax = val4.anchorMax;
			component4.pivot = val4.pivot;
			for (int num7 = 0; num7 < num5; num7++)
			{
				RectTransform obj = ((num7 < list.Count) ? list[num7] : component4);
				float num8 = num3 + num6 * 0.5f + (float)num7 * (num6 + value);
				obj.sizeDelta = new Vector2(num6, height);
				obj.anchoredPosition = new Vector2(num8, (num7 < list.Count) ? list[num7].anchoredPosition.y : val4.anchoredPosition.y);
			}
			Plugin.Log.LogInfo((object)$"Backups button placed beside {list.Count} existing button(s); each is now {num6:F0} wide across a {num4:F0} span.");
		}

		private static void FloatOpenButton()
		{
			//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_0071: 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_008e: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: 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>();
			Rect rect;
			float num;
			if ((Object)(object)component != (Object)null)
			{
				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_009c:
			float num3;
			float num2 = num3;
			RectTransform component2 = _openButton.GetComponent<RectTransform>();
			float num4;
			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(num2, num4);
				component2.anchoredPosition = new Vector2(0f, num4 * 0.5f);
			}
			return;
			IL_0066:
			num4 = num;
			if ((Object)(object)component != (Object)null)
			{
				rect = component.rect;
				if (((Rect)(ref rect)).width > 1f)
				{
					rect = component.rect;
					num3 = ((Rect)(ref rect)).width;
					goto IL_009c;
				}
			}
			num3 = 240f;
			goto IL_009c;
		}

		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 Transform PanelHost()
		{
			Transform result = (((Object)(object)_screenParent != (Object)null) ? _screenParent : _infoHolder.transform.parent);
			Canvas componentInParent = _infoHolder.GetComponentInParent<Canvas>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				return result;
			}
			return ((Component)(((Object)(object)componentInParent.rootCanvas != (Object)null) ? componentInParent.rootCanvas : componentInParent)).transform;
		}

		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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: 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_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Expected O, but got Unknown
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Expected O, but got Unknown
			//IL_0230: 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_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0304: 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(PanelHost(), false);
			RectTransform component = val.GetComponent<RectTransform>();
			((Transform)component).localScale = Vector3.one;
			component.anchorMin = new Vector2(0.51f, 0.04f);
			component.anchorMax = new Vector2(0.98f, 0.96f);
			component.pivot = new Vector2(0.5f, 0.5f);
			component.offsetMin = Vector2.zero;
			component.offsetMax = Vector2.zero;
			AdoptChrome(val.GetComponent<Image>());
			VerticalLayoutGroup obj = val.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj).childAlignment = (TextAnchor)1;
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 4f;
			((LayoutGroup)obj).padding = new RectOffset(12, 12, 10, 10);
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			Transform val2 = CreateRow(val.transform, 34f);
			_pageLabel = CreateLabel(val2, "", 16f, (TextAlignmentOptions)513);
			if ((Object)(object)_pageLabel != (Object)null)
			{
				SetWidth(((Component)_pageLabel).gameObject, 56f);
				((Graphic)_pageLabel).color = new Color(((Graphic)_pageLabel).color.r, ((Graphic)_pageLabel).color.g, ((Graphic)_pageLabel).color.b, 0.7f);
			}
			TextMeshProUGUI val3 = CreateLabel(val2, "Backups", 27f, (TextAlignmentOptions)514);
			if ((Object)(object)val3 != (Object)null)
			{
				SetFlexible(((Component)val3).gameObject, 120f);
			}
			GameObject val4 = new GameObject("Spacer", new Type[1] { typeof(RectTransform) });
			val4.transform.SetParent(val2, false);
			SetWidth(val4, 56f);
			CreateDivider(val.transform);
			_status = CreateLabel(val.transform, "", 15f, (TextAlignmentOptions)514);
			if ((Object)(object)_status != (Object)null)
			{
				((Graphic)_status).color = new Color(((Graphic)_status).color.r, ((Graphic)_status).color.g, ((Graphic)_status).color.b, 0.7f);
			}
			if ((Object)(object)_status != (Object)null)
			{
				((TMP_Text)_status).textWrappingMode = (TextWrappingModes)1;
				((TMP_Text)_status).overflowMode = (TextOverflowModes)0;
				LayoutElement component2 = ((Component)_status).GetComponent<LayoutElement>();
				if ((Object)(object)component2 != (Object)null)
				{
					component2.minWidth = -1f;
					component2.preferredWidth = -1f;
					component2.flexibleWidth = 1f;
					component2.minHeight = 20f;
					component2.preferredHeight = 20f;
				}
			}
			GameObject val5 = new GameObject("Content", new Type[1] { typeof(RectTransform) });
			val5.transform.SetParent(val.transform, false);
			VerticalLayoutGroup obj2 = val5.AddComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj2).spacing = 3f;
			((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true;
			_content = val5.transform;
			val.SetActive(false);
			return val;
		}

		private static string CompanionTooltip(BackupInfo info)
		{
			if (info == null || info.Companions.Count == 0)
			{
				return null;
			}
			string text = "Mod files in this backup";
			foreach (CompanionRecord companion in info.Companions)
			{
				string text2 = (string.IsNullOrEmpty(companion.DisplayName) ? companion.Id : companion.DisplayName);
				text = text + "\n  " + text2;
			}
			return text;
		}

		private static void CreateStar(Transform row, BackupInfo info)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			TextMeshProUGUI val = CreateLabel(row, "", 20f, (TextAlignmentOptions)514);
			if (!((Object)(object)val == (Object)null))
			{
				GameObject gameObject = ((Component)val).gameObject;
				((Object)gameObject).name = "Favourite";
				((Graphic)val).raycastTarget = true;
				PaintStar(val, info.Favorite);
				SetWidth(gameObject, 26f);
				Button val2 = gameObject.GetComponent<Button>();
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = gameObject.AddComponent<Button>();
				}
				((Selectable)val2).transition = (Transition)0;
				((Selectable)val2).targetGraphic = (Graphic)(object)val;
				((UnityEventBase)val2.onClick).RemoveAllListeners();
				((UnityEvent)val2.onClick).AddListener((UnityAction)delegate
				{
					Favorites.Toggle(info);
					Refresh(info.Favorite ? "Pinned that backup, it will not be deleted automatically." : "Unpinned that backup.");
				});
				Hover hover = gameObject.GetComponent<Hover>();
				if ((Object)(object)hover == (Object)null)
				{
					hover = gameObject.AddComponent<Hover>();
				}
				hover.Scale = 1.18f;
				hover.Text = () => (!info.Favorite) ? "Click to keep this backup, so it is never deleted to make room." : "Favourite. This backup is kept until you unpin or delete it.";
			}
		}

		private static void PaintStar(TextMeshProUGUI glyph, bool favorite)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			((TMP_Text)glyph).text = (favorite ? "★" : "☆");
			((Graphic)glyph).color = (favorite ? new Color(1f, 0.83f, 0.2f, 1f) : new Color(1f, 1f, 1f, 0.45f));
		}

		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 + 4f;
			val3.preferredHeight = size + 4f;
			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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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();
				if (onClick != null)
				{
					((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 GameObject CloneIconButton(Transform parent, Sprite icon, string fallback, string tooltip, Action onClick)
		{
			//IL_0088: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = CloneButton(parent, ((Object)(object)icon != (Object)null) ? "" : fallback, onClick);
			((Object)val).name = "SB_Icon_" + fallback;
			SetWidth(val, 34f);
			if ((Object)(object)icon != (Object)null)
			{
				TextMeshProUGUI componentInChildren = val.GetComponentInChildren<TextMeshProUGUI>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					GameObject val2 = new GameObject("Glyph", new Type[3]
					{
						typeof(RectTransform),
						typeof(CanvasRenderer),
						typeof(Image)
					});
					val2.transform.SetParent(val.transform, false);
					Image component = val2.GetComponent<Image>();
					component.sprite = icon;
					component.preserveAspect = true;
					((Graphic)component).raycastTarget = false;
					((Graphic)component).color = ((Graphic)componentInChildren).color;
					RectTransform component2 = val2.GetComponent<RectTransform>();
					component2.anchorMin = new Vector2(0.5f, 0.5f);
					component2.anchorMax = new Vector2(0.5f, 0.5f);
					component2.pivot = new Vector2(0.5f, 0.5f);
					component2.anchoredPosition = Vector2.zero;
					component2.sizeDelta = new Vector2(19f, 19f);
					((Component)componentInChildren).gameObject.SetActive(false);
				}
			}
			if (!string.IsNullOrEmpty(tooltip))
			{
				Hover hover = val.GetComponent<Hover>();
				if ((Object)(object)hover == (Object)null)
				{
					hover = val.AddComponent<Hover>();
				}
				string captured = tooltip;
				hover.Text = () => captured;
			}
			return val;
		}

		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 void AdoptChrome(Image target)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)target == (Object)null))
			{
				Image val = (((Object)(object)_infoHolder != (Object)null) ? _infoHolder.GetComponent<Image>() : null);
				if ((Object)(object)val != (Object)null && (Object)(object)val.sprite != (Object)null)
				{
					target.sprite = val.sprite;
					target.type = val.type;
					((Graphic)target).color = ((Graphic)val).color;
					((Graphic)target).material = ((Graphic)val).material;
					target.pixelsPerUnitMultiplier = val.pixelsPerUnitMultiplier;
				}
				else
				{
					((Graphic)target).color = new Color(0.05f, 0.06f, 0.08f, 0.92f);
				}
			}
		}

		private static void CreateDivider(Transform parent)
		{
			//IL_0032: 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_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Divider", new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val.transform.SetParent(parent, false);
			Image component = val.GetComponent<Image>();
			((Graphic)component).color = new Color(1f, 1f, 1f, 0.18f);
			((Graphic)component).raycastTarget = false;
			LayoutElement obj = val.AddComponent<LayoutElement>();
			obj.minHeight = 2f;
			obj.preferredHeight = 2f;
			obj.flexibleWidth = 1f;
		}

		private static void TintRow(Transform row, int index)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			GameObject gameObject = ((Component)row).gameObject;
			Image val = gameObject.GetComponent<Image>();
			if ((Object)(object)val == (Object)null)
			{
				val = gameObject.AddComponent<Image>();
			}
			((Graphic)val).color = ((index % 2 == 0) ? new Color(1f, 1f, 1f, 0.05f) : new Color(0f, 0f, 0f, 0.12f));
			((Graphic)val).raycastTarget = false;
		}

		private static Transform CreateRow(Transform parent, float height)
		{
			return CreateRow(parent, height, (TextAnchor)4);
		}

		private static Transform CreateRow(Transform parent, float height, TextAnchor alignment)
		{
			//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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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 = 8f;
			((LayoutGroup)obj).padding = new RectOffset(8, 8, 0, 0);
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((LayoutGroup)obj).childAlignment = alignment;
			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))
			{
				_page = 0;
				Refresh("");
				_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)_panel != (Object)null)
			{
				_panel.SetActive(true);
			}
		}

		private static void ColumnHeader(Transform row, string text, float width, TextAlignmentOptions alignment)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: 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)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = CreateLabel(row, text, 13f, alignment);
			if (!((Object)(object)val == (Object)null))
			{
				SetWidth(((Component)val).gameObject, width);
				((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
				((Graphic)val).color = new Color(((Graphic)val).color.r, ((Graphic)val).color.g, ((Graphic)val).color.b, 0.55f);
			}
		}

		private static void DrawEditor()
		{
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
			EditableSave draft = SaveEditor.Draft;
			BackupInfo target = SaveEditor.Target;
			CreateDivider(_content);
			Transform obj = CreateRow(_content, 30f, (TextAnchor)3);
			TextMeshProUGUI val = CreateLabel(obj, "Editing the backup from " + target.TakenUtc.ToLocalTime().ToString("MMM d  HH:mm"), 18f, (TextAlignmentOptions)513);
			if ((Object)(object)val != (Object)null)
			{
				SetFlexible(((Component)val).gameObject, 240f);
			}
			TimeSpan timeSpan = TimeSpan.FromSeconds(draft.Playtime);
			Cell(obj, (int)timeSpan.TotalHours + "h " + timeSpan.Minutes.ToString("D2") + "m played", 150f, (TextAlignmentOptions)516, 0.6f);
			Cell(obj, (draft.Players == 1) ? "1 player" : (draft.Players + " players"), 110f, (TextAlignmentOptions)516, 0.6f);
			Transform row = CreateRow(_content, 36f, (TextAnchor)3);
			EditText(row, "Name", draft.Name, 200f, delegate(string value)
			{
				draft.Name = value;
			});
			EditNumber(row, "Money", draft.Money, 100f, delegate(int value)
			{
				draft.Money = value;
			});
			EditNumber(row, "Island", draft.Island, 74f, delegate(int value)
			{
				draft.Island = value;
			});
			EditNumber(row, "Furthest", draft.MaxIsland, 74f, delegate(int value)
			{
				draft.MaxIsland = value;
			});
			Transform obj2 = CreateRow(_content, 36f, (TextAnchor)3);
			EditNumber(obj2, "Motor", draft.MotorIndex, 74f, delegate(int value)
			{
				draft.MotorIndex = value;
			});
			EditNumber(obj2, "Boat skin", draft.BoatSkin, 74f, delegate(int value)
			{
				draft.BoatSkin = value;
			});
			TextMeshProUGUI val2 = CreateLabel(obj2, "Difficulty", 14f, (TextAlignmentOptions)516);
			if ((Object)(object)val2 != (Object)null)
			{
				SetWidth(((Component)val2).gameObject, 76f);
				((Graphic)val2).color = new Color(((Graphic)val2).color.r, ((Graphic)val2).color.g, ((Graphic)val2).color.b, 0.6f);
			}
			GameObject obj3 = CloneButton(obj2, SaveEditor.DifficultyName(draft.Difficulty), delegate
			{
				draft.Difficulty = (draft.Difficulty + 1) % 3;
				Refresh("");
			});
			SetWidth(obj3, 118f);
			PinLabel(obj3, 1f);
			Spacer(obj2);
			Transform row2 = CreateRow(_content, 36f, (TextAnchor)3);
			EditToggle(row2, "Boat", draft.UnlockedBoat, delegate(bool value)
			{
				draft.UnlockedBoat = value;
			});
			EditToggle(row2, "Radar", draft.UnlockedBoatRadar, delegate(bool value)
			{
				draft.UnlockedBoatRadar = value;
			});
			EditToggle(row2, "Grill", draft.UnlockedGrill, delegate(bool value)
			{
				draft.UnlockedGrill = value;
			});
			EditToggle(row2, "Boss", draft.FinalBossKilled, delegate(bool value)
			{
				draft.FinalBossKilled = value;
			});
			EditToggle(row2, "Finished", draft.HasFinishedGame, delegate(bool value)
			{
				draft.HasFinishedGame = value;
			});
			Spacer(row2);
			DrawCompanions(target);
			Transform parent = CreateRow(_content, 38f, (TextAnchor)3);
			TextMeshProUGUI val3 = CreateLabel(parent, "Skins are stored in the game local file, which backups do not cover.", 13f, (TextAlignmentOptions)513);
			if ((Object)(object)val3 != (Object)null)
			{
				SetFlexible(((Component)val3).gameObject, 200f);
				((Graphic)val3).color = new Color(((Graphic)val3).color.r, ((Graphic)val3).color.g, ((Graphic)val3).color.b, 0.5f);
			}
			GameObject obj4 = CloneButton(parent, "Save changes", delegate
			{
				if (SaveEditor.Commit(out var message))
				{
					SaveEditor.Cancel();
				}
				Refresh(message);
			});
			SetWidth(obj4, 150f);
			PinLabel(obj4, 1f);
			GameObject obj5 = CloneButton(parent, "Cancel", delegate
			{
				SaveEditor.Cancel();
				Refresh("");
			});
			SetWidth(obj5, 104f);
			PinLabel(obj5, 1f);
		}

		private static void EditText(Transform row, string caption, string value, float width, Action<string> onChange)
		{
			Caption(row, caption);
			TMP_InputField val = CloneInput(row, value, width, numeric: false);
			if ((Object)(object)val != (Object)null)
			{
				((UnityEvent<string>)(object)val.onEndEdit).AddListener((UnityAction<string>)delegate(string typed)
				{
					onChange(typed);
				});
			}
		}

		private static void EditNumber(Transform row, string caption, int value, float width, Action<int> onChange)
		{
			Caption(row, caption);
			TMP_InputField val = CloneInput(row, value.ToString(), width, numeric: true);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			((UnityEvent<string>)(object)val.onEndEdit).AddListener((UnityAction<string>)delegate(string typed)
			{
				if (int.TryParse(typed, out var result))
				{
					onChange(result);
				}
			});
		}

		private static void Caption(Transform row, string caption)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: 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)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = CreateLabel(row, caption, 14f, (TextAlignmentOptions)516);
			if ((Object)(object)val != (Object)null)
			{
				SetWidth(((Component)val).gameObject, 76f);
				((Graphic)val).color = new Color(((Graphic)val).color.r, ((Graphic)val).color.g, ((Graphic)val).color.b, 0.6f);
			}
		}

		private static void EditToggle(Transform row, string caption, bool value, Action<bool> onChange)
		{
			GameObject obj = CloneButton(row, (value ? "[x] " : "[  ] ") + caption, delegate
			{
				onChange(!value);
				Refresh("");
			});
			SetWidth(obj, 132f);
			PinLabel(obj, value ? 1f : 0.5f);
		}

		private static void DrawCompanions(BackupInfo target)
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			CreateDivider(_content);
			Transform row = CreateRow(_content, 24f, (TextAnchor)3);
			ColumnHeader(row, "Mod files", 200f, (TextAlignmentOptions)513);
			Spacer(row);
			if (target.Companions.Count == 0)
			{
				TextMeshProUGUI val = CreateLabel(CreateRow(_content, 26f, (TextAnchor)3), "This backup holds no mod files.", 14f, (TextAlignmentOptions)513);
				if ((Object)(object)val != (Object)null)
				{
					SetFlexible(((Component)val).gameObject, 200f);
					((Graphic)val).color = new Color(((Graphic)val).color.r, ((Graphic)val).color.g, ((Graphic)val).color.b, 0.5f);
				}
				return;
			}
			for (int i = 0; i < target.Companions.Count; i++)
			{
				CompanionRecord companionRecord = target.Companions[i];
				if (companionRecord == null)
				{
					continue;
				}
				Transform obj = CreateRow(_content, 28f, (TextAnchor)3);
				TintRow(obj, i);
				string text = (string.IsNullOrEmpty(companionRecord.DisplayName) ? companionRecord.Id : companionRecord.DisplayName);
				Cell(obj, text, 200f, (TextAlignmentOptions)513, 0.9f);
				Cell(obj, companionRecord.StoredName, 260f, (TextAlignmentOptions)513, 0.5f);
				Cell(obj, SizeOf(target, companionRecord), 84f, (TextAlignmentOptions)516, 0.5f);
				Spacer(obj);
				CompanionRecord captured = companionRecord;
				bool flag = _armedCompanion != null && _armedCompanion == captured.Id;
				GameObject val2 = CloneIconButton(obj, Icons.Delete, "Del", flag ? "Click again to remove this file" : "Remove this mod file from the backup", delegate
				{
					if (_armedCompanion != captured.Id)
					{
						_armedCompanion = captured.Id;
						Refresh("Click the bin again to remove that mod file.");
					}
					else
					{
						_armedCompanion = null;
						BackupStore.RemoveCompanion(target, captured, out var message);
						Refresh(message);
					}
				});
				if (flag)
				{
					Image componentInChildren = val2.GetComponentInChildren<Image>(true);
					if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.sprite != (Object)null)
					{
						((Graphic)componentInChildren).color = new Color(1f, 0.45f, 0.4f, 1f);
					}
				}
			}
		}

		private static string SizeOf(BackupInfo target, CompanionRecord record)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(Path.Combine(target.Path, record.StoredName));
				if (!fileInfo.Exists)
				{
					return "missing";
				}
				return (fileInfo.Length < 1024) ? (fileInfo.Length + " B") : (((float)fileInfo.Length / 1024f).ToString("F1") + " KB");
			}
			catch
			{
				return "";
			}
		}

		private static void PinLabel(GameObject button, float alpha)
		{
			//IL_004a: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI componentInChildren = button.GetComponentInChildren<TextMeshProUGUI>(true);
			if (!((Object)(object)componentInChildren == (Object)null))
			{
				((TMP_Text)componentInChildren).enableAutoSizing = false;
				((TMP_Text)componentInChildren).fontSize = 17f;
				((TMP_Text)componentInChildren).fontSizeMin = 17f;
				((TMP_Text)componentInChildren).fontSizeMax = 17f;
				((TMP_Text)componentInChildren).textWrappingMode = (TextWrappingModes)0;
				((TMP_Text)componentInChildren).overflowMode = (TextOverflowModes)0;
				((Graphic)componentInChildren).color = new Color(((Graphic)componentInChildren).color.r, ((Graphic)componentInChildren).color.g, ((Graphic)componentInChildren).color.b, alpha);
			}
		}

		private static TMP_InputField CloneInput(Transform parent, string value, float width, bool numeric)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_inputTemplate == (Object)null && (Object)(object)_buttons != (Object)null)
			{
				object? obj = AccessTools.Field(typeof(ButtonManager), "_serverNameInputField")?.GetValue(_buttons);
				_inputTemplate = (TMP_InputField)((obj is TMP_InputField) ? obj : null);
			}
			if ((Object)(object)_inputTemplate == (Object)null)
			{
				Cell(parent, value, width, (TextAlignmentOptions)513, 1f);
				return null;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)_inputTemplate).gameObject, parent, false);
			((Object)val).name = "SB_Input";
			val.SetActive(true);
			StripLocalization(val);
			TMP_InputField component = val.GetComponent<TMP_InputField>();
			if ((Object)(object)component != (Object)null)
			{
				component.onEndEdit = new SubmitEvent();
				component.onValueChanged = new OnChangeEvent();
				component.onValidateInput = null;
				component.inputValidator = null;
				component.characterValidation = (CharacterValidation)(numeric ? 2 : 0);
				component.contentType = (ContentType)9;
				component.lineType = (LineType)0;
				component.characterLimit = (numeric ? 9 : 40);
				component.text = value;
				((Selectable)component).interactable = true;
				if ((Object)(object)component.placeholder != (Object)null)
				{
					((Component)component.placeholder).gameObject.SetActive(false);
				}
			}
			TextMeshProUGUI[] componentsInChildren = val.GetComponentsInChildren<TextMeshProUGUI>(true);
			foreach (TextMeshProUGUI obj2 in componentsInChildren)
			{
				((TMP_Text)obj2).enableAutoSizing = false;
				((TMP_Text)obj2).fontSize = 17f;
				((TMP_Text)obj2).fontSizeMin = 17f;
				((TMP_Text)obj2).fontSizeMax = 17f;
				((TMP_Text)obj2).textWrappingMode = (TextWrappingModes)0;
				((TMP_Text)obj2).overflowMode = (TextOverflowModes)0;
				((TMP_Text)obj2).margin = new Vector4(6f, 0f, 6f, 0f);
			}
			Image val2 = val.GetComponent<Image>();
			if ((Object)(object)val2 == (Object)null)
			{
				val2 = val.AddComponent<Image>();
			}
			((Behaviour)val2).enabled = true;
			((Graphic)val2).color = new Color(1f, 1f, 1f, 0.12f);
			SetWidth(val, width);
			LayoutElement component2 = val.GetComponent<LayoutElement>();
			if ((Object)(object)component2 != (Object)null)
			{
				component2.minHeight = 28f;
				component2.preferredHeight = 28f;
			}
			return component;
		}

		private static void Spacer(Transform row)
		{
			//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_0034: Expected O, but got Unknown
			GameObject val = new GameObject("Gap", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(row, false);
			SetFlexible(val, 0f);
		}

		private static TextMeshProUGUI Cell(Transform row, string text, float width, TextAlignmentOptions alignment, float alpha)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = CreateLabel(row, text, 16f, alignment);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			SetWidth(((Component)val).gameObject, width);
			((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)val).overflowMode = (TextOverflowModes)1;
			((Graphic)val).color = new Color(((Graphic)val).color.r, ((Graphic)val).color.g, ((Graphic)val).color.b, alpha);
			return val;
		}

		private static void Refresh(string status)
		{
			//IL_059f: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05bc: Expected O, but got Unknown
			//IL_066c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0671: Unknown result type (might be due to invalid IL or missing references)
			//IL_0689: Expected O, but got Unknown
			//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
			ClearChildren(_content);
			List<BackupInfo> list = BackupStore.List(_selectedSave);
			int num = Mathf.Max(1, Plugin.BackupsPerPage.Value);
			int num2 = Mathf.Max(1, Mathf.CeilToInt((float)list.Count / (float)num));
			_page = Mathf.Clamp(_page, 0, num2 - 1);
			int num3 = _page * num;
			int num4 = Mathf.Min(num3 + num, list.Count);
			if ((Object)(object)_pageLabel != (Object)null)
			{
				((TMP_Text)_pageLabel).text = ((list.Count > 0) ? $"{_page + 1}/{num2}" : "");
			}
			if (list.Count > 0)
			{
				Transform row = CreateRow(_content, 24f, (TextAnchor)3);
				ColumnHeader(row, "", 26f, (TextAlignmentOptions)514);
				ColumnHeader(row, "Taken", 116f, (TextAlignmentOptions)513);
				ColumnHeader(row, "Name", 150f, (TextAlignmentOptions)513);
				ColumnHeader(row, "Island", 56f, (TextAlignmentOptions)514);
				ColumnHeader(row, "Money", 74f, (TextAlignmentOptions)514);
				ColumnHeader(row, "Played", 82f, (TextAlignmentOptions)514);
				ColumnHeader(row, "Mods", 52f, (TextAlignmentOptions)514);
				Spacer(row);
				ColumnHeader(row, "", 112f, (TextAlignmentOptions)514);
			}
			for (int i = num3; i < num4; i++)
			{
				BackupInfo captured = list[i];
				Transform obj = CreateRow(_content, 30f, (TextAnchor)3);
				TintRow(obj, i);
				string when = captured.TakenUtc.ToLocalTime().ToString("MMM d  HH:mm");
				CreateStar(obj, captured);
				Cell(obj, when, 116f, (TextAlignmentOptions)513, 1f);
				string text = ((!string.IsNullOrEmpty(captured.SaveLabel)) ? captured.SaveLabel : captured.SaveName);
				string text2 = KindLabel(captured.Kind);
				if (!string.IsNullOrEmpty(text2))
				{
					text = text + "  " + text2.Trim();
				}
				Cell(obj, text, 150f, (TextAlignmentOptions)513, 0.85f);
				Cell(obj, captured.Readable ? captured.Island.ToString() : "?", 56f, (TextAlignmentOptions)514, 0.78f);
				Cell(obj, captured.Readable ? captured.Money.ToString() : "?", 74f, (TextAlignmentOptions)514, 0.78f);
				Cell(obj, captured.Readable ? $"{(int)captured.Played.TotalHours}h {captured.Played.Minutes:D2}m" : "?", 82f, (TextAlignmentOptions)514, 0.78f);
				bool flag = captured.Companions.Count > 0;
				TextMeshProUGUI val = Cell(obj, flag ? ("+" + captured.Companions.Count) : "", 52f, (TextAlignmentOptions)514, 0.78f);
				if ((Object)(object)val != (Object)null && flag)
				{
					((Graphic)val).raycastTarget = true;
					Hover hover = ((Component)val).gameObject.GetComponent<Hover>();
					if ((Object)(object)hover == (Object)null)
					{
						hover = ((Component)val).gameObject.AddComponent<Hover>();
					}
					hover.Text = () => CompanionTooltip(captured);
				}
				Spacer(obj);
				Transform obj2 = CreateRow(obj, 30f, (TextAnchor)5);
				SetWidth(((Component)obj2).gameObject, 112f);
				CloneIconButton(obj2, Icons.Edit, "Edit", "Open this backup for editing", delegate
				{
					if (SaveEditor.Open && SaveEditor.Target == captured)
					{
						SaveEditor.Cancel();
						Refresh("");
					}
					else
					{
						_armedPath = null;
						_armedCompanion = null;
						SaveEditor.Begin(captured);
						Refresh(SaveEditor.Open ? "" : "That backup could not be opened for editing.");
					}
				});
				CloneIconButton(obj2, Icons.Restore, "Restore", "Restore this backup", delegate
				{
					RestoreResult restoreResult = BackupStore.Restore(captured);
					if (!restoreResult.Success)
					{
						Refresh("Restore failed, see the log.");
					}
					else
					{
						ReloadAndReselect();
						Refresh(DescribeRestore(when, restoreResult));
					}
				});
				bool flag2 = _armedPath != null && _armedPath == captured.Path;
				GameObject val2 = CloneIconButton(obj2, Icons.Delete, "Del", flag2 ? "Click again to delete" : "Delete this backup", delegate
				{
					if (_armedPath != captured.Path)
					{
						_armedPath = captured.Path;
						Refresh("Click the bin again to delete this backup.");
					}
					else
					{
						_armedPath = null;
						BackupStore.Delete(captured);
						Refresh("Deleted " + when + ".");
					}
				});
				if (flag2)
				{
					Image componentInChildren = val2.GetComponentInChildren<Image>(true);
					if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.sprite != (Object)null)
					{
						((Graphic)componentInChildren).color = new Color(1f, 0.45f, 0.4f, 1f);
					}
				}
			}
			if (list.Count == 0)
			{
				CreateLabel(_content, "No backups yet for this save.", 19f, (TextAlignmentOptions)514);
			}
			if (list.Count > 0)
			{
				CreateDivider(_content);
				Transform val3 = CreateRow(_content, 32f);
				if (_page > 0)
				{
					SetWidth(CloneButton(val3, "<", delegate
					{
						_page--;
						_armedPath = null;
						Refresh("");
					}), 56f);
				}
				else
				{
					GameObject val4 = new GameObject("Pad", new Type[1] { typeof(RectTransform) });
					val4.transform.SetParent(val3, false);
					SetWidth(val4, 56f);
				}
				TextMeshProUGUI val5 = CreateLabel(val3, (list.Count == 1) ? "1 backup" : (list.Count + " backups"), 17f, (TextAlignmentOptions)514);
				if ((Object)(object)val5 != (Object)null)
				{
					SetFlexible(((Component)val5).gameObject, 80f);
				}
				if (_page < num2 - 1)
				{
					SetWidth(CloneButton(val3, ">", delegate
					{
						_page++;
						_armedPath = null;
						Refresh("");
					}), 56f);
				}
				else
				{
					GameObject val6 = new GameObject("Pad", new Type[1] { typeof(RectTransform) });
					val6.transform.SetParent(val3, false);
					SetWidth(val6, 56f);
				}
			}
			if (SaveEditor.Open)
			{
				DrawEditor();
			}
			CreateDivider(_content);
			Transform parent = CreateRow(_content, 38f);
			SetWidth(CloneButton(parent, "Back up now", delegate
			{
				_page = 0;
				_armedPath = null;
				Refresh(BackupStore.Capture(_selectedSave, SnapshotKind.Manual) ? "Backup taken." : "Could not take a backup, see the log.");
			}), 168f);
			SetWidth(CloneButton(parent, "Open folder", delegate
			{
				OpenFolder(BackupStore.FolderFor(_selectedSave));
				Refresh("Opened the backup folder.");
			}), 150f);
			SetWidth(CloneButton(parent, "Back", Close), 104f);
			if ((Object)(object)_status != (Object)null)
			{
				((TMP_Text)_status).text = (string.IsNullOrEmpty(status) ? "Restoring saves the current state first." : status);
			}
		}

		private static string KindLabel(SnapshotKind kind)
		{
			return kind switch
			{
				SnapshotKind.Manual => "  (manual)", 
				SnapshotKind.PreRestore => "  (before restore)", 
				_ => "", 
			};
		}

		private static string DescribeRestore(string when, RestoreResult outcome)
		{
			string text = "Restored " + when;
			if (outcome.CompanionsRestored > 0)
			{
				text = text + " with " + outcome.CompanionsRestored + " companion file(s)";
			}
			text += ".";
			if (outcome.Cleared.Count > 0)
			{
				text = text + " Cleared newer data for " + string.Join(", ", outcome.Cleared.ToArray()) + ".";
			}
			if (outcome.Missing.Count > 0)
			{
				text = text + " Data for " + string.Join(", ", outcome.Missing.ToArray()) + " was skipped because that mod is not loaded.";
			}
			return text;
		}

		private static void OpenFolder(string path)
		{
			try
			{
				Directory.CreateDirectory(path);
				Application.OpenURL(new Uri(path).AbsoluteUri);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not open the backup folder: " + ex.Message));
			}
		}
	}
	public class CompanionProvider
	{
		public string Id;

		public string DisplayName;

		public Func<string, string> PathForSave;
	}
	public static class Companions
	{
		private static readonly Dictionary<string, CompanionProvider> _providers = new Dictionary<string, CompanionProvider>(StringComparer.OrdinalIgnoreCase);

		public static IEnumerable<CompanionProvider> All => _providers.Values;

		public static int Count => _providers.Count;

		public static bool IsAvailable => true;

		public static void Register(string id, string displayName, Func<string, string> pathForSave)
		{
			if (string.IsNullOrEmpty(id) || pathForSave == null)
			{
				Plugin.Log.LogWarning((object)"A mod tried to register a companion file with no id or no path builder, ignoring it.");
				return;
			}
			string text = SanitiseId(id);
			_providers[text] = new CompanionProvider
			{
				Id = text,
				DisplayName = (string.IsNullOrEmpty(displayName) ? text : displayName),
				PathForSave = pathForSave
			};
			Plugin.Log.LogInfo((object)("Companion registered: " + displayName + " (" + text + "). Its files will be included in every snapshot."));
		}

		public static void Unregister(string id)
		{
			if (!string.IsNullOrEmpty(id) && _providers.Remove(SanitiseId(id)))
			{
				Plugin.Log.LogInfo((object)("Companion unregistered: " + id));
			}
		}

		public static bool IsRegistered(string id)
		{
			if (!string.IsNullOrEmpty(id))
			{
				return _providers.ContainsKey(SanitiseId(id));
			}
			return false;
		}

		public static CompanionProvider Get(string id)
		{
			if (string.IsNullOrEmpty(id) || !_providers.TryGetValue(SanitiseId(id), out var value))
			{
				return null;
			}
			return value;
		}

		public static string ResolvePath(CompanionProvider provider, string saveName)
		{
			if (provider == null || provider.PathForSave == null || string.IsNullOrEmpty(saveName))
			{
				return null;
			}
			try
			{
				return provider.PathForSave(saveName);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Companion '" + provider.Id + "' threw while resolving its path: " + ex));
				return null;
			}
		}

		public static string SanitiseSaveName(string saveName)
		{
			return BackupStore.SanitiseSaveName(saveName);
		}

		private static string SanitiseId(string id)
		{
			char[] array = id.ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (!char.IsLetterOrDigit(array[i]))
				{
					array[i] = '_';
				}
			}
			return new string(array).ToLowerInvariant();
		}
	}
	internal class FavoritesFile
	{
		public int Version = 1;

		public List<string> Names = new List<string>();
	}
	internal static class Favorites
	{
		private const string FileName = "favorites.json";

		private static readonly Dictionary<string, HashSet<string>> _bySave = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

		private static string PathFor(string saveName)
		{
			string text = BackupStore.FolderFor(saveName);
			if (!string.IsNullOrEmpty(text))
			{
				return Path.Combine(text, "favorites.json");
			}
			return null;
		}

		private static string KeyOf(BackupInfo info)
		{
			if (info != null && !string.IsNullOrEmpty(info.Path))
			{
				return Path.GetFileName(info.Path.TrimEnd(Path.DirectorySeparatorChar));
			}
			return null;
		}

		private static HashSet<string> For(string saveName)
		{
			if (string.IsNullOrEmpty(saveName))
			{
				return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			}
			if (_bySave.TryGetValue(saveName, out var value))
			{
				return value;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			try
			{
				string text = PathFor(saveName);
				if (text != null && File.Exists(text))
				{
					FavoritesFile favoritesFile = JsonConvert.DeserializeObject<FavoritesFile>(File.ReadAllText(text));
					if (favoritesFile?.Names != null)
					{
						foreach (string name in favoritesFile.Names)
						{
							hashSet.Add(name);
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not read the favourites list, starting empty: " + ex.Message));
			}
			_bySave[saveName] = hashSet;
			return hashSet;
		}

		private static void Save(string saveName)
		{
			string text = PathFor(saveName);
			if (text == null)
			{
				return;
			}
			try
			{
				Directory.CreateDirectory(BackupStore.FolderFor(saveName));
				FavoritesFile favoritesFile = new FavoritesFile();
				favoritesFile.Names.AddRange(For(saveName));
				string text2 = text + ".writing";
				File.WriteAllText(text2, JsonConvert.SerializeObject((object)favoritesFile, (Formatting)1));
				if (File.Exists(text))
				{
					File.Replace(text2, text, null);
				}
				else
				{
					File.Move(text2, text);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not write the favourites list: " + ex.Message));
			}
		}

		public static bool IsFavorite(BackupInfo info)
		{
			string text = KeyOf(info);
			if (text != null)
			{
				return For(info.SaveName).Contains(text);
			}
			return false;
		}

		public static bool Toggle(BackupInfo info)
		{
			string text = KeyOf(info);
			if (text == null)
			{
				return false;
			}
			HashSet<string> hashSet = For(info.SaveName);
			bool flag;
			if (hashSet.Contains(text))
			{
				hashSet.Remove(text);
				flag = false;
			}
			else
			{
				hashSet.Add(text);
				flag = true;
			}
			Save(info.SaveName);
			info.Favorite = flag;
			Plugin.Log.LogInfo((object)("Backup " + text + " is " + (flag ? "now a favourite and will not be deleted automatically" : "no longer a favourite") + "."));
			return flag;
		}

		public static void Forget(BackupInfo info)
		{
			string text = KeyOf(info);
			if (text != null && For(info.SaveName).Remove(text))
			{
				Save(info.SaveName);
			}
		}

		public static void Invalidate()
		{
			_bySave.Clear();
		}
	}
	internal class Hover : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler
	{
		public Func<string> Text;

		public float Scale = 1f;

		public float ScaleSpeed = 12f;

		private Vector3 _rest = Vector3.one;

		private bool _over;

		private bool _known;

		private void Awake()
		{
			Remember();
		}

		private void OnEnable()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			Remember();
			_over = false;
			((Component)this).transform.localScale = _rest;
		}

		private void Remember()
		{
			//IL_000f: 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)
			if (!_known)
			{
				_rest = ((Component)this).transform.localScale;
				_known = true;
			}
		}

		public void OnPointerEnter(PointerEventData eventData)
		{
			_over = true;
			if (Text != null)
			{
				string text = Text();
				if (!string.IsNullOrEmpty(text))
				{
					Transform transform = ((Component)this).transform;
					Tooltip.Show(text, (RectTransform)(object)((transform is RectTransform) ? transform : null));
				}
			}
		}

		public void OnPointerExit(PointerEventData eventData)
		{
			_over = false;
			Tooltip.Hide();
		}

		private void Update()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_0053: Unknown result type (might be due to invalid IL or missing references)
			if (!Mathf.Approximately(Scale, 1f))
			{
				Vector3 val = (_over ? (_rest * Scale) : _rest);
				((Component)this).transform.localScale = Vector3.Lerp(((Component)this).transform.localScale, val, Time.unscaledDeltaTime * ScaleSpeed);
			}
		}

		private void OnDisable()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			_over = false;
			((Component)this).transform.localScale = _rest;
			Tooltip.Hide();
		}
	}
	internal static class Icons
	{
		private static readonly Dictionary<string, Sprite> _cache = new Dictionary<string, Sprite>(StringComparer.Ordinal);

		public static Sprite Edit => Get("edit");

		public static Sprite Restore => Get("restore");

		public static Sprite Delete => Get("delete");

		private static Sprite Get(string name)
		{
			if (_cache.TryGetValue(name, out var value))
			{
				return value;
			}
			Sprite val = Load(name);
			_cache[name] = val;
			return val;
		}

		private static Sprite Load(string name)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				using Stream stream = typeof(Icons).Assembly.GetManifestResourceStream("SaveBackups." + name + ".png");
				if (stream == null)
				{
					Plugin.Log.LogWarning((object)("Icon '" + name + "' is missing from the mod, so that button will show text instead."));
					return null;
				}
				byte[] array = new byte[stream.Length];
				int num;
				for (int i = 0; i < array.Length; i += num)
				{
					num = stream.Read(array, i, array.Length - i);
					if (num <= 0)
					{
						break;
					}
				}
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				((Object)val).hideFlags = (HideFlags)61;
				if (!ImageConversion.LoadImage(val, array))
				{
					Plugin.Log.LogWarning((object)("Icon '" + name + "' could not be decoded."));
					return null;
				}
				((Texture)val).filterMode = (FilterMode)1;
				((Texture)val).wrapMode = (TextureWrapMode)1;
				Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
				((Object)obj).hideFlags = (HideFlags)61;
				return obj;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Icon '" + name + "' failed to load: " + ex.Message));
				return null;
			}
		}
	}
	[BepInPlugin("dazed.howtofish.savebackups", "SaveBackups", "1.4.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "dazed.howtofish.savebackups";

		public const string Name = "SaveBackups";

		public const string Version = "1.4.0";

		public static ManualLogSource Log = Logger.CreateLogSource("SaveBackups");

		public static ConfigEntry<bool> BackupOnSave;

		public static ConfigEntry<bool> BackupOnLoad;

		public static ConfigEntry<bool> SkipUnchanged;

		public static ConfigEntry<int> MaxAutomaticBackups;

		public static ConfigEntry<int> MaxPreRestoreBackups;

		public static ConfigEntry<int> BackupsPerPage;

		public static ConfigEntry<float> ButtonGap;

		public static ConfigEntry<bool> DeferCapture;

		private static Plugin _instance;

		private readonly Dictionary<string, bool> _pending = new Dictionary<string, bool>();

		private bool _draining;

		private Harmony _harmony;

		private void Update()
		{
			BackupUI.Tick();
		}

		private void Awake()
		{
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			_instance = this;
			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 and its companion files are identical to the newest snapshot 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.");
			MaxPreRestoreBackups = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxPreRestoreBackups", 5, "How many of the safety snapshots taken just before a restore to keep per save. These are made automatically every time you restore, so they are pruned like automatic backups rather than kept forever. 0 keeps them all.");
			BackupsPerPage = ((BaseUnityPlugin)this).Config.Bind<int>("General", "BackupsPerPage", 10, "How many backups to list per page. Use the arrows at the bottom of the panel to reach older ones.");
			ButtonGap = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ButtonGap", 14f, "Gap in pixels between Delete, Load and Backups on the save info panel. The three share the width the game gave to two, so raise this if they look cramped.");
			DeferCapture = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DeferCapture", true, "Wait until the end of the frame before copying files. Other mods write their own save data from the same game event and the order they run in is not guaranteed, so waiting makes sure a snapshot holds the save and its companion files from the same moment. Only turn this off to diagnose a problem.");
			_harmony = new Harmony("dazed.howtofish.savebackups");
			Patches.ApplyAll(_harmony);
			Log.LogInfo((object)("SaveBackups 1.4.0 loaded. Backups are kept in " + BackupStore.Root));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		public static void QueueCapture(string saveName, bool automatic)
		{
			if (!string.IsNullOrEmpty(saveName))
			{
				if ((Object)(object)_instance == (Object)null || DeferCapture == null || !DeferCapture.Value)
				{
					BackupStore.Capture(saveName, automatic);
				}
				else
				{
					_instance.Enqueue(saveName, automatic);
				}
			}
		}

		private void Enqueue(string saveName, bool automatic)
		{
			if (_pending.TryGetValue(saveName, out var value))
			{
				_pending[saveName] = value && automatic;
			}
			else
			{
				_pending[saveName] = automatic;
			}
			if (!_draining)
			{
				_draining = true;
				((MonoBehaviour)this).StartCoroutine(Drain());
			}
		}

		private IEnumerator Drain()
		{
			yield return (object)new WaitForEndOfFrame();
			DrainNow();
		}

		public static void DrainPending()
		{
			_instance?.DrainNow();
		}

		private void DrainNow()
		{
			if (_pending.Count == 0)
			{
				_draining = false;
				return;
			}
			Dictionary<string, bool> dictionary = new Dictionary<string, bool>(_pending);
			_pending.Clear();
			_draining = false;
			foreach (KeyValuePair<string, bool> item in dictionary)
			{
				BackupStore.Capture(item.Key, item.Value);
			}
		}
	}
	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_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: 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)
			{
				priority = 0
			}, (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(SaveManager), "OnApplicationQuit", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "ApplicationQuitPostfix", (Type[])null)
			{
				priority = 0
			}, (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 ApplicationQuitPostfix()
		{
			Plugin.DrainPending();
		}

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

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

		private static void SelectedForLoadPostfix()
		{
			if (Plugin.BackupOnLoad.Value && SaveManager.CurServerSave != null)
			{
				Plugin.QueueCapture(SaveManager.CurServerSave.Name, automatic: true);
			}
		}
	}
	internal class EditableSave
	{
		public string Name = "";

		public int Money;

		public int Island = 1;

		public int MaxIsland = 1;

		public int Difficulty = 1;

		public int MotorIndex;

		public int BoatSkin;

		public bool UnlockedBoat;

		public bool UnlockedBoatRadar;

		public bool UnlockedGrill;

		public bool FinalBossKilled;

		public bool HasFinishedGame;

		public float Playtime;

		public int Players;
	}
	internal static class SaveEditor
	{
		public static BackupInfo Target { get; private set; }

		public static EditableSave Draft { get; private set; }

		public static bool Open
		{
			get
			{
				if (Target != null)
				{
					return Draft != null;
				}
				return false;
			}
		}

		public static void Begin(BackupInfo backup)
		{
			Target = null;
			Draft = null;
			if (backup != null)
			{
				EditableSave editableSave = Read(backup);
				if (editableSave != null)
				{
					Target = backup;
					Draft = editableSave;
				}
			}
		}

		public static void Cancel()
		{
			Target = null;
			Draft = null;
		}

		private static EditableSave Read(BackupInfo backup)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected I4, but got Unknown
			try
			{
				string saveFilePath = backup.SaveFilePath;
				if (!File.Exists(saveFilePath))
				{
					Plugin.Log.LogWarning((object)"That backup has no save file to edit.");
					return null;
				}
				ServerSaveObject val = JsonUtility.FromJson<ServerSaveObject>(File.ReadAllText(saveFilePath));
				if (val == null)
				{
					Plugin.Log.LogWarning((object)"That backup's save file could not be read, so it cannot be edited.");
					return null;
				}
				return new EditableSave
				{
					Name = (val.Name ?? ""),
					Money = val.Money,
					Island = val.SpawnedIsland + 1,
					MaxIsland = val.MaxIsland + 1,
					Difficulty = (int)val.Difficulty,
					MotorIndex = val.MotorIndex,
					BoatSkin = val.BoatSkin,
					UnlockedBoat = val.UnlockedBoat,
					UnlockedBoatRadar = val.UnlockedBoatRadar,
					UnlockedGrill = val.UnlockedGrill,
					FinalBossKilled = val.FinalBossKilled,
					HasFinishedGame = val.HasFinishedGame,
					Playtime = val.Playtime,
					Players = ((val.Players != null) ? val.Players.Count : 0)
				};
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not read that backup for editing: " + ex));
				return null;
			}
		}

		public static bool Commit(out string message)
		{
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			message = "";
			if (!Open)
			{
				message = "Nothing is being edited.";
				return false;
			}
			string saveFilePath = Target.SaveFilePath;
			try
			{
				ServerSaveObject val = JsonUtility.FromJson<ServerSaveObject>(File.ReadAllText(saveFilePath));
				if (val == null)
				{
					message = "That save could not be read, nothing was changed.";
					return false;
				}
				val.Name = (string.IsNullOrEmpty(Draft.Name) ? val.Name : Draft.Name);
				val.Money = Mathf.Max(0, Draft.Money);
				val.SpawnedIsland = (byte)Mathf.Clamp(Draft.Island - 1, 0, 255);
				val.MaxIsland = (byte)Mathf.Clamp(Draft.MaxIsland - 1, 0, 255);
				val.Difficulty = (Difficulty)Mathf.Clamp(Draft.Difficulty, 0, 2);
				val.MotorIndex = (byte)Mathf.Clamp(Draft.MotorIndex, 0, 255);
				val.BoatSkin = (byte)Mathf.Clamp(Draft.BoatSkin, 0, 255);
				val.UnlockedBoat = Draft.UnlockedBoat;
				val.UnlockedBoatRadar = Draft.UnlockedBoatRadar;
				val.UnlockedGrill = Draft.UnlockedGrill;
				val.FinalBossKilled = Draft.FinalBossKilled;
				val.HasFinishedGame = Draft.HasFinishedGame;
				string text = JsonUtility.ToJson((object)val);
				if (string.IsNullOrEmpty(text) || text.Length < 20)
				{
					message = "The edited save came out empty, nothing was written.";
					return false;
				}
				string text2 = saveFilePath + ".pre-edit";
				File.Copy(saveFilePath, text2, overwrite: true);
				string text3 = saveFilePath + ".writing";
				File.WriteAllText(text3, text);
				if (JsonUtility.FromJson<ServerSaveObject>(File.ReadAllText(text3)) == null)
				{
					File.Delete(text3);
					message = "The edited save did not read back correctly, nothing was changed.";
					return false;
				}
				if (File.Exists(saveFilePath))
				{
					File.Delete(saveFilePath);
				}
				File.Move(text3, saveFilePath);
				Plugin.Log.LogInfo((object)$"Edited backup '{Target.SaveName}' taken {Target.TakenUtc.ToLocalTime():g}. The original was kept as {Path.GetFileName(text2)}.");
				message = "Saved. The original was kept alongside it.";
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not write the edited save: " + ex));
				message = "Could not write that save, see the log.";
				return false;
			}
		}

		public static string DifficultyName(int value)
		{
			return value switch
			{
				0 => "Easy", 
				2 => "Hard", 
				_ => "Default", 
			};
		}
	}
	internal static class Tooltip
	{
		private static GameObject _root;

		private static RectTransform _rect;

		private static TextMeshProUGUI _text;

		public static void Reset()
		{
			_root = null;
			_rect = null;
			_text = null;
		}

		public static void Show(string body, RectTransform near)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)near == (Object)null)
			{
				return;
			}
			Canvas componentInParent = ((Component)near).GetComponentInParent<Canvas>();
			if (!((Object)(object)componentInParent == (Object)null))
			{
				Build(componentInParent);
				if (!((Object)(object)_root == (Object)null))
				{
					((TMP_Text)_text).text = body;
					_root.SetActive(true);
					_root.transform.SetAsLastSibling();
					LayoutRebuilder.ForceRebuildLayoutImmediate(_rect);
					Vector3[] array = (Vector3[])(object)new Vector3[4];
					near.GetWorldCorners(array);
					Vector3 position = array[1];
					((Transform)_rect).position = position;
					Vector2 anchoredPosition = _rect.anchoredPosition;
					anchoredPosition.y += _rect.sizeDelta.y * 0.5f + 6f;
					_rect.anchoredPosition = anchor