using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SaveBackups")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SaveBackups")]
[assembly: AssemblyTitle("SaveBackups")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace SaveBackups
{
public class BackupInfo
{
public string Path;
public string SaveName;
public DateTime TakenUtc;
public bool Automatic;
public string Summary = "";
public long Bytes;
}
public static class BackupStore
{
private const string Stamp = "yyyyMMdd-HHmmss";
public static string SaveFolder => Path.Combine(Application.persistentDataPath, "Saves");
public static string Root => Path.Combine(Application.persistentDataPath, "SaveBackups");
public static string SavePath(string saveName)
{
return Path.Combine(SaveFolder, saveName + ".txt");
}
public static string FolderFor(string saveName)
{
return Path.Combine(Root, Sanitise(saveName));
}
private static string Sanitise(string name)
{
if (string.IsNullOrEmpty(name))
{
return "unnamed";
}
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (char oldChar in invalidFileNameChars)
{
name = name.Replace(oldChar, '_');
}
return name;
}
public static List<BackupInfo> List(string saveName)
{
List<BackupInfo> list = new List<BackupInfo>();
if (string.IsNullOrEmpty(saveName))
{
return list;
}
try
{
string path = FolderFor(saveName);
if (!Directory.Exists(path))
{
return list;
}
string[] files = Directory.GetFiles(path, "*.txt");
for (int i = 0; i < files.Length; i++)
{
BackupInfo backupInfo = Describe(files[i], saveName);
if (backupInfo != null)
{
list.Add(backupInfo);
}
}
list.Sort((BackupInfo a, BackupInfo b) => b.TakenUtc.CompareTo(a.TakenUtc));
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not list backups: " + ex));
}
return list;
}
private static BackupInfo Describe(string path, string saveName)
{
try
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
bool flag = !fileNameWithoutExtension.EndsWith("-manual", StringComparison.OrdinalIgnoreCase);
DateTime takenUtc = (DateTime.TryParseExact(flag ? fileNameWithoutExtension : fileNameWithoutExtension.Substring(0, fileNameWithoutExtension.Length - "-manual".Length), "yyyyMMdd-HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out takenUtc) ? DateTime.SpecifyKind(takenUtc, DateTimeKind.Utc) : File.GetLastWriteTimeUtc(path));
return new BackupInfo
{
Path = path,
SaveName = saveName,
TakenUtc = takenUtc,
Automatic = flag,
Bytes = new FileInfo(path).Length,
Summary = Summarise(File.ReadAllText(path))
};
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Skipping unreadable backup " + path + ": " + ex.Message));
return null;
}
}
private static string Summarise(string json)
{
try
{
ServerSaveObject val = JsonUtility.FromJson<ServerSaveObject>(json);
if (val == null)
{
return "unreadable";
}
TimeSpan timeSpan = TimeSpan.FromSeconds(val.Playtime);
return $"{val.Money} money, island {val.SpawnedIsland + 1}, {timeSpan.Hours}h {timeSpan.Minutes:D2}m played";
}
catch (Exception)
{
return "unreadable";
}
}
public static bool Capture(string saveName, bool automatic)
{
if (string.IsNullOrEmpty(saveName))
{
return false;
}
try
{
string path = SavePath(saveName);
if (!File.Exists(path))
{
Plugin.Log.LogWarning((object)("No save file on disk for '" + saveName + "', nothing to back up."));
return false;
}
string text = File.ReadAllText(path);
if (string.IsNullOrEmpty(text))
{
Plugin.Log.LogWarning((object)("Save file for '" + saveName + "' is empty, refusing to back it up."));
return false;
}
string text2 = FolderFor(saveName);
Directory.CreateDirectory(text2);
if (automatic && Plugin.SkipUnchanged.Value && MatchesNewest(text2, text))
{
return false;
}
string path2 = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + (automatic ? "" : "-manual") + ".txt";
string path3 = Path.Combine(text2, path2);
if (File.Exists(path3))
{
path3 = Path.Combine(text2, DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N").Substring(0, 4) + ".txt");
}
File.WriteAllText(path3, text);
Prune(saveName);
Plugin.Log.LogInfo((object)("Backed up '" + saveName + "' (" + (automatic ? "automatic" : "manual") + ")."));
return true;
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Backup failed: " + ex));
return false;
}
}
private static bool MatchesNewest(string folder, string content)
{
try
{
string text = null;
DateTime dateTime = DateTime.MinValue;
string[] files = Directory.GetFiles(folder, "*.txt");
foreach (string text2 in files)
{
DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text2);
if (lastWriteTimeUtc > dateTime)
{
dateTime = lastWriteTimeUtc;
text = text2;
}
}
return text != null && File.ReadAllText(text) == content;
}
catch (Exception)
{
return false;
}
}
private static void Prune(string saveName)
{
int value = Plugin.MaxAutomaticBackups.Value;
if (value <= 0)
{
return;
}
List<BackupInfo> list = new List<BackupInfo>();
foreach (BackupInfo item in List(saveName))
{
if (item.Automatic)
{
list.Add(item);
}
}
for (int i = value; i < list.Count; i++)
{
try
{
File.Delete(list[i].Path);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not prune old backup: " + ex.Message));
}
}
}
public static bool Restore(BackupInfo info)
{
if (info == null)
{
return false;
}
try
{
if (!File.Exists(info.Path))
{
Plugin.Log.LogWarning((object)"That backup no longer exists on disk.");
return false;
}
string text = File.ReadAllText(info.Path);
if (string.IsNullOrEmpty(text))
{
Plugin.Log.LogError((object)"That backup is empty, refusing to restore it.");
return false;
}
Capture(info.SaveName, automatic: false);
Directory.CreateDirectory(SaveFolder);
File.WriteAllText(SavePath(info.SaveName), text);
Plugin.Log.LogInfo((object)$"Restored '{info.SaveName}' from {info.TakenUtc.ToLocalTime():g}.");
return true;
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Restore failed: " + ex));
return false;
}
}
public static bool Delete(BackupInfo info)
{
try
{
if (info != null && File.Exists(info.Path))
{
File.Delete(info.Path);
return true;
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not delete backup: " + ex));
}
return false;
}
}
public static class BackupUI
{
private static bool _failed;
private static bool _built;
private static ButtonManager _buttons;
private static GameObject _infoHolder;
private static Transform _screenParent;
private static GameObject _buttonTemplate;
private static GameObject _labelTemplate;
private static GameObject _openButton;
private static GameObject _panel;
private static Transform _content;
private static TextMeshProUGUI _status;
private static string _selectedSave;
private static bool _refreshing;
public static void OnSaveSelected(string saveName)
{
if (_refreshing)
{
return;
}
_selectedSave = saveName;
if (_failed)
{
return;
}
try
{
if (EnsureBuilt())
{
_openButton.SetActive(!string.IsNullOrEmpty(saveName));
Close();
}
}
catch (Exception ex)
{
_failed = true;
Plugin.Log.LogError((object)("Backup UI failed, falling back to no UI: " + ex));
}
}
public static void Close()
{
if (!_refreshing)
{
if ((Object)(object)_panel != (Object)null)
{
_panel.SetActive(false);
}
if ((Object)(object)_infoHolder != (Object)null && !string.IsNullOrEmpty(_selectedSave))
{
_infoHolder.SetActive(true);
}
}
}
private static bool EnsureBuilt()
{
if (_built && (Object)(object)_openButton != (Object)null && (Object)(object)_panel != (Object)null)
{
return true;
}
_buttons = Object.FindAnyObjectByType<ButtonManager>();
if ((Object)(object)_buttons == (Object)null)
{
return false;
}
object? obj = AccessTools.Field(typeof(ButtonManager), "_selectedSaveInfoHolder")?.GetValue(_buttons);
_infoHolder = (GameObject)((obj is GameObject) ? obj : null);
if ((Object)(object)_infoHolder == (Object)null)
{
Plugin.Log.LogError((object)"Could not find the save info panel in the load game screen.");
_failed = true;
return false;
}
_screenParent = _infoHolder.transform.parent;
_buttonTemplate = FindButtonTemplate(((Object)(object)_screenParent != (Object)null) ? _screenParent : _infoHolder.transform);
if ((Object)(object)_buttonTemplate == (Object)null)
{
Plugin.Log.LogError((object)"Could not find a menu button to copy the style from.");
_failed = true;
return false;
}
TextMeshProUGUI componentInChildren = _buttonTemplate.GetComponentInChildren<TextMeshProUGUI>(true);
_labelTemplate = (((Object)(object)componentInChildren != (Object)null) ? ((Component)componentInChildren).gameObject : null);
_openButton = CloneButton(_infoHolder.transform, "Backups", Open);
PlaceOpenButton();
_panel = BuildPanel();
_built = true;
Plugin.Log.LogInfo((object)"Backups button added to the load game screen.");
return true;
}
private static void PlaceOpenButton()
{
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
LayoutElement val = _openButton.GetComponent<LayoutElement>();
if ((Object)(object)val == (Object)null)
{
val = _openButton.AddComponent<LayoutElement>();
}
val.ignoreLayout = true;
RectTransform component = _buttonTemplate.GetComponent<RectTransform>();
float num;
if ((Object)(object)component != (Object)null)
{
Rect rect = component.rect;
if (((Rect)(ref rect)).height > 1f)
{
rect = component.rect;
num = ((Rect)(ref rect)).height;
goto IL_0066;
}
}
num = 44f;
goto IL_0066;
IL_0066:
float num2 = num;
RectTransform component2 = _openButton.GetComponent<RectTransform>();
if (!((Object)(object)component2 == (Object)null))
{
component2.anchorMin = new Vector2(0.5f, 0f);
component2.anchorMax = new Vector2(0.5f, 0f);
component2.pivot = new Vector2(0.5f, 0f);
component2.sizeDelta = new Vector2(Plugin.OpenButtonWidth.Value, num2);
component2.anchoredPosition = new Vector2(0f, Plugin.OpenButtonBottomMargin.Value);
}
}
private static GameObject FindButtonTemplate(Transform root)
{
Button[] componentsInChildren = ((Component)root).GetComponentsInChildren<Button>(true);
Button[] array = componentsInChildren;
foreach (Button val in array)
{
if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).GetComponentInChildren<TextMeshProUGUI>(true) != (Object)null)
{
return ((Component)val).gameObject;
}
}
if (componentsInChildren.Length == 0)
{
return null;
}
return ((Component)componentsInChildren[0]).gameObject;
}
private static GameObject BuildPanel()
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Expected O, but got Unknown
//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
//IL_010f: Unknown result type (might be due to invalid IL or missing references)
//IL_0119: Expected O, but got Unknown
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
//IL_020b: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("SaveBackups_Panel", new Type[3]
{
typeof(RectTransform),
typeof(CanvasRenderer),
typeof(Image)
});
val.transform.SetParent(((Object)(object)_screenParent != (Object)null) ? _screenParent : _infoHolder.transform.parent, false);
RectTransform component = _infoHolder.GetComponent<RectTransform>();
RectTransform component2 = val.GetComponent<RectTransform>();
if ((Object)(object)component != (Object)null)
{
component2.anchorMin = component.anchorMin;
component2.anchorMax = component.anchorMax;
component2.pivot = component.pivot;
component2.anchoredPosition = component.anchoredPosition;
component2.sizeDelta = component.sizeDelta;
((Transform)component2).localScale = ((Transform)component).localScale;
}
((Graphic)val.GetComponent<Image>()).color = new Color(0f, 0f, 0f, 0.35f);
VerticalLayoutGroup obj = val.AddComponent<VerticalLayoutGroup>();
((LayoutGroup)obj).childAlignment = (TextAnchor)1;
((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f;
((LayoutGroup)obj).padding = new RectOffset(14, 14, 14, 14);
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
CreateLabel(val.transform, "Backups", 30f, (TextAlignmentOptions)514);
_status = CreateLabel(val.transform, "", 16f, (TextAlignmentOptions)514);
if ((Object)(object)_status != (Object)null)
{
((TMP_Text)_status).textWrappingMode = (TextWrappingModes)1;
((TMP_Text)_status).overflowMode = (TextOverflowModes)0;
LayoutElement component3 = ((Component)_status).GetComponent<LayoutElement>();
if ((Object)(object)component3 != (Object)null)
{
component3.minWidth = -1f;
component3.preferredWidth = -1f;
component3.flexibleWidth = 1f;
component3.minHeight = 24f;
component3.preferredHeight = 24f;
}
}
GameObject val2 = new GameObject("Content", new Type[1] { typeof(RectTransform) });
val2.transform.SetParent(val.transform, false);
VerticalLayoutGroup obj2 = val2.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj2).spacing = 4f;
((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true;
_content = val2.transform;
val.SetActive(false);
return val;
}
private static TextMeshProUGUI CreateLabel(Transform parent, string text, float size, TextAlignmentOptions alignment)
{
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Expected O, but got Unknown
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
GameObject val;
TextMeshProUGUI val2;
if ((Object)(object)_labelTemplate != (Object)null)
{
val = Object.Instantiate<GameObject>(_labelTemplate, parent, false);
StripLocalization(val);
val2 = val.GetComponent<TextMeshProUGUI>();
}
else
{
val = new GameObject("Label", new Type[1] { typeof(RectTransform) });
val.transform.SetParent(parent, false);
val2 = val.AddComponent<TextMeshProUGUI>();
}
((Object)val).name = "Label";
val.SetActive(true);
if ((Object)(object)val2 != (Object)null)
{
((TMP_Text)val2).text = text;
((TMP_Text)val2).fontSize = size;
((TMP_Text)val2).alignment = alignment;
((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
((Graphic)val2).raycastTarget = false;
}
LayoutElement val3 = val.GetComponent<LayoutElement>();
if ((Object)(object)val3 == (Object)null)
{
val3 = val.AddComponent<LayoutElement>();
}
val3.minHeight = size + 6f;
val3.preferredHeight = size + 6f;
return val2;
}
private static GameObject CloneButton(Transform parent, string text, Action onClick)
{
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Expected O, but got Unknown
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Expected O, but got Unknown
GameObject obj = Object.Instantiate<GameObject>(_buttonTemplate, parent, false);
((Object)obj).name = "SB_Button_" + text;
obj.SetActive(true);
StripLocalization(obj);
Button component = obj.GetComponent<Button>();
if ((Object)(object)component != (Object)null)
{
component.onClick = new ButtonClickedEvent();
((UnityEvent)component.onClick).AddListener((UnityAction)delegate
{
onClick();
});
((Selectable)component).interactable = true;
}
TextMeshProUGUI componentInChildren = obj.GetComponentInChildren<TextMeshProUGUI>(true);
if ((Object)(object)componentInChildren != (Object)null)
{
((TMP_Text)componentInChildren).text = text;
}
return obj;
}
private static void StripLocalization(GameObject go)
{
MonoBehaviour[] componentsInChildren = go.GetComponentsInChildren<MonoBehaviour>(true);
foreach (MonoBehaviour val in componentsInChildren)
{
if ((Object)(object)val != (Object)null && ((object)val).GetType().Name.IndexOf("Localize", StringComparison.OrdinalIgnoreCase) >= 0)
{
Object.Destroy((Object)(object)val);
}
}
}
private static void ClearChildren(Transform parent)
{
for (int num = parent.childCount - 1; num >= 0; num--)
{
Object.Destroy((Object)(object)((Component)parent.GetChild(num)).gameObject);
}
}
private static Transform CreateRow(Transform parent, float height)
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("Row", new Type[1] { typeof(RectTransform) });
val.transform.SetParent(parent, false);
HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
((LayoutGroup)obj).childAlignment = (TextAnchor)4;
LayoutElement obj2 = val.AddComponent<LayoutElement>();
obj2.minHeight = height;
obj2.preferredHeight = height;
return val.transform;
}
private static void SetWidth(GameObject go, float width)
{
LayoutElement val = go.GetComponent<LayoutElement>();
if ((Object)(object)val == (Object)null)
{
val = go.AddComponent<LayoutElement>();
}
val.minWidth = width;
val.preferredWidth = width;
val.flexibleWidth = 0f;
}
private static void SetFlexible(GameObject go, float minWidth)
{
LayoutElement val = go.GetComponent<LayoutElement>();
if ((Object)(object)val == (Object)null)
{
val = go.AddComponent<LayoutElement>();
}
val.minWidth = minWidth;
val.preferredWidth = minWidth;
val.flexibleWidth = 1f;
}
private static void Open()
{
if (!((Object)(object)_panel == (Object)null) && !string.IsNullOrEmpty(_selectedSave))
{
Refresh("");
_infoHolder.SetActive(false);
_panel.SetActive(true);
}
}
private static void ReloadAndReselect()
{
_refreshing = true;
try
{
SaveManager.LoadAllServers();
if (AccessTools.Field(typeof(ButtonManager), "_savedServerTexts")?.GetValue(_buttons) is List<TextMeshProUGUI> list)
{
foreach (TextMeshProUGUI item in list)
{
if ((Object)(object)item != (Object)null && ((Component)item).gameObject.activeInHierarchy && ((TMP_Text)item).text == _selectedSave)
{
_buttons.SelectServer(item);
break;
}
}
}
if (SaveManager.CurServerSave == null)
{
SaveManager.SelectServer(_selectedSave);
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not refresh the save list after restoring: " + ex));
}
finally
{
_refreshing = false;
}
if (SaveManager.CurServerSave == null)
{
Plugin.Log.LogWarning((object)"The save list refreshed but no save is selected. Pick the save again before loading.");
}
if ((Object)(object)_infoHolder != (Object)null)
{
_infoHolder.SetActive(false);
}
if ((Object)(object)_panel != (Object)null)
{
_panel.SetActive(true);
}
}
private static void Refresh(string status)
{
ClearChildren(_content);
List<BackupInfo> list = BackupStore.List(_selectedSave);
int num = 0;
foreach (BackupInfo item in list)
{
if (num >= Plugin.MaxRowsShown.Value)
{
break;
}
BackupInfo captured = item;
Transform parent = CreateRow(_content, 34f);
string when = captured.TakenUtc.ToLocalTime().ToString("MMM d HH:mm");
TextMeshProUGUI val = CreateLabel(parent, string.Concat(str1: captured.Automatic ? "" : " (manual)", str0: when, str2: " ", str3: captured.Summary), 16f, (TextAlignmentOptions)513);
if ((Object)(object)val != (Object)null)
{
SetFlexible(((Component)val).gameObject, 120f);
((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
((TMP_Text)val).overflowMode = (TextOverflowModes)1;
}
SetWidth(CloneButton(parent, "Restore", delegate
{
if (!BackupStore.Restore(captured))
{
Refresh("Restore failed, see the log.");
}
else
{
ReloadAndReselect();
Refresh("Restored " + when + ".");
}
}), 104f);
SetWidth(CloneButton(parent, "Delete", delegate
{
BackupStore.Delete(captured);
Refresh("Deleted " + when + ".");
}), 94f);
num++;
}
if (num == 0)
{
CreateLabel(_content, "No backups yet for this save.", 19f, (TextAlignmentOptions)514);
}
Transform parent2 = CreateRow(_content, 40f);
SetWidth(CloneButton(parent2, "Back up now", delegate
{
Refresh(BackupStore.Capture(_selectedSave, automatic: false) ? "Backup taken." : "Could not take a backup, see the log.");
}), 168f);
SetWidth(CloneButton(parent2, "Open folder", delegate
{
Application.OpenURL("file://" + BackupStore.FolderFor(_selectedSave));
Refresh("Opened the backup folder.");
}), 150f);
SetWidth(CloneButton(parent2, "Back", Close), 104f);
if ((Object)(object)_status != (Object)null)
{
((TMP_Text)_status).text = (string.IsNullOrEmpty(status) ? $"{list.Count} backup(s). Restoring saves the current file first." : status);
}
}
}
[BepInPlugin("dazed.howtofish.savebackups", "SaveBackups", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "dazed.howtofish.savebackups";
public const string Name = "SaveBackups";
public const string Version = "1.0.0";
public static ManualLogSource Log;
public static ConfigEntry<bool> BackupOnSave;
public static ConfigEntry<bool> BackupOnLoad;
public static ConfigEntry<bool> SkipUnchanged;
public static ConfigEntry<int> MaxAutomaticBackups;
public static ConfigEntry<int> MaxRowsShown;
public static ConfigEntry<float> OpenButtonWidth;
public static ConfigEntry<float> OpenButtonBottomMargin;
private Harmony _harmony;
private void Awake()
{
//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
//IL_0104: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
BackupOnSave = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "BackupOnSave", true, "Take a backup every time the game saves, which includes the five minute autosave.");
BackupOnLoad = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "BackupOnLoad", true, "Take a backup when a save is loaded, so you always have the state you started a session with.");
SkipUnchanged = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SkipUnchanged", true, "Do not write an automatic backup when the save file is identical to the newest one already stored.");
MaxAutomaticBackups = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxAutomaticBackups", 10, "How many automatic backups to keep per save. The oldest are deleted beyond this. Manual backups are never pruned. 0 keeps everything.");
MaxRowsShown = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxRowsShown", 7, "How many backups to list in the menu at once, so the panel does not overflow.");
OpenButtonWidth = ((BaseUnityPlugin)this).Config.Bind<float>("General", "OpenButtonWidth", 240f, "Width of the Backups button on the save info panel, so it matches the Delete and Load buttons instead of stretching.");
OpenButtonBottomMargin = ((BaseUnityPlugin)this).Config.Bind<float>("General", "OpenButtonBottomMargin", 22f, "Gap in pixels between the Backups button and the bottom edge of the panel. Raise it if the button sits too low.");
_harmony = new Harmony("dazed.howtofish.savebackups");
Patches.ApplyAll(_harmony);
Log.LogInfo((object)("SaveBackups 1.0.0 loaded. Backups are kept in " + BackupStore.Root));
}
private void OnDestroy()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
public static class Patches
{
public static void ApplyAll(Harmony harmony)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Expected O, but got Unknown
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Expected O, but got Unknown
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Expected O, but got Unknown
//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
//IL_00d7: Expected O, but got Unknown
harmony.Patch((MethodBase)AccessTools.Method(typeof(ButtonManager), "SelectServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SelectServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "SaveServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SaveServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "SelectServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "SelectedForLoadPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeof(ButtonManager), "UpdateSavedServerButtons", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "ListRefreshedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Plugin.Log.LogInfo((object)"Hooked the load game screen and the save routine.");
}
private static void SelectServerPostfix(TextMeshProUGUI text)
{
BackupUI.OnSaveSelected(((Object)(object)text != (Object)null) ? ((TMP_Text)text).text : null);
}
private static void ListRefreshedPostfix()
{
BackupUI.Close();
}
private static void SaveServerPostfix()
{
if (Plugin.BackupOnSave.Value && SaveManager.CurServerSave != null)
{
BackupStore.Capture(SaveManager.CurServerSave.Name, automatic: true);
}
}
private static void SelectedForLoadPostfix()
{
if (Plugin.BackupOnLoad.Value && SaveManager.CurServerSave != null)
{
BackupStore.Capture(SaveManager.CurServerSave.Name, automatic: true);
}
}
}
}