Decompiled source of Automatic Backups v1.3.0

AutomaticBackups_IL2Cpp.dll

Decompiled 3 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using AutomaticBackups;
using HarmonyLib;
using Il2CppFishNet;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Persistence;
using Il2CppScheduleOne.UI.MainMenu;
using Il2CppScheduleOne.UI.Settings;
using Il2CppSystem.IO;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(Core), "AutomaticBackups", "1.3.0-beta", "coderTrevor", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AutomaticBackups_IL2Cpp")]
[assembly: AssemblyConfiguration("IL2CPP")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+708d6547b1197fe84da47c41d4cfe3ad111b9073")]
[assembly: AssemblyProduct("AutomaticBackups_IL2Cpp")]
[assembly: AssemblyTitle("AutomaticBackups_IL2Cpp")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace AutomaticBackups
{
	[RegisterTypeInIl2Cpp]
	public class AutoSaveToggle : SettingsToggle
	{
		public GameObject saveTimeSlider = null;

		public void Start()
		{
			bool value = Core.enableAutoSave.Value;
			Toggle component = ((Component)this).gameObject.GetComponent<Toggle>();
			component.isOn = value;
			saveTimeSlider.SetActive(value);
		}

		public override void OnValueChanged(bool value)
		{
			saveTimeSlider.SetActive(value);
			Core.enableAutoSave.Value = value;
		}
	}
	[RegisterTypeInIl2Cpp]
	public class AutoSaveTimeSlider : SettingsSlider
	{
		protected virtual void OnEnable()
		{
			((SettingsSlider)this).slider.minValue = Core.autoSaveSliderMinTime.Value;
			((SettingsSlider)this).slider.maxValue = Core.autoSaveSliderMaxTime.Value;
			((SettingsSlider)this).slider.value = Core.autoSaveTime.Value;
		}

		public override void OnValueChanged(float value)
		{
			((SettingsSlider)this).timeOnValueChange = Time.time;
			if (((SettingsSlider)this).DisplayValue)
			{
				((TMP_Text)((SettingsSlider)this).valueLabel).text = ((SettingsSlider)this).GetDisplayValue(value);
				((Behaviour)((SettingsSlider)this).valueLabel).enabled = true;
			}
			Core.autoSaveTime.Value = (int)value;
		}
	}
	[RegisterTypeInIl2Cpp]
	public class RetentionAmountSlider : SettingsSlider
	{
		protected int multiplier;

		protected virtual void OnEnable()
		{
			multiplier = Core.retentionSliderMultiplier.Value;
			int num = DivideAndFloor(Core.retentionSliderMinFiles.Value, multiplier);
			int num2 = DivideAndFloor(Core.retentionSliderMaxFiles.Value, multiplier);
			int num3 = DivideAndFloor(Core.autoDeleteRetentionCount.Value, multiplier);
			((SettingsSlider)this).slider.minValue = num;
			((SettingsSlider)this).slider.maxValue = num2;
			((SettingsSlider)this).slider.value = num3;
		}

		private int DivideAndFloor(int numerator, int denominator)
		{
			float num = (float)numerator / (float)denominator;
			return (int)Mathf.Floor(num);
		}

		public override string GetDisplayValue(float value)
		{
			return ((int)Mathf.Round(value * (float)multiplier)).ToString();
		}

		public override void OnValueChanged(float value)
		{
			((SettingsSlider)this).timeOnValueChange = Time.time;
			if (((SettingsSlider)this).DisplayValue)
			{
				((TMP_Text)((SettingsSlider)this).valueLabel).text = ((SettingsSlider)this).GetDisplayValue(value);
				((Behaviour)((SettingsSlider)this).valueLabel).enabled = true;
			}
			Core.autoDeleteRetentionCount.Value = (int)value * multiplier;
		}
	}
	[HarmonyPatch(typeof(MainMenuScreen), "Awake")]
	internal static class MainMenuScreenPatch
	{
		public static void Prefix(MainMenuScreen __instance)
		{
			if (!(((Object)((Component)__instance).gameObject).name != "RestoreBackup"))
			{
				__instance.OpenOnStart = true;
				__instance.PreviousScreen = (MainMenuScreen)(object)ContinueMenuMod.continueScreen;
				__instance.Group = ((Component)__instance).gameObject.GetComponent<CanvasGroup>();
			}
		}
	}
	[RegisterTypeInIl2Cpp]
	public class ContinueMenuMod : MonoBehaviour
	{
		protected Transform continueMenu;

		protected Transform loadMenu = null;

		protected MainMenuScreen restoreBackupScreen = null;

		public static ContinueScreen continueScreen;

		public ImportScreen importScreen;

		private void Awake()
		{
			Init();
		}

		private void Init()
		{
			GameObject gameObject = ((Component)this).gameObject;
			continueMenu = gameObject.transform.Find("Continue");
			continueScreen = ((Component)continueMenu).GetComponent<ContinueScreen>();
			Transform val = continueMenu.Find("Container");
			for (int i = 0; i < val.childCount; i++)
			{
				AddButtonToSlot(val.GetChild(i), i);
			}
			importScreen = ((Component)gameObject.transform.Find("ImportScreen")).GetComponent<ImportScreen>();
		}

		private void AddButtonToSlot(Transform slot, int slotNumber)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			Transform val = slot.Find("Container");
			Transform val2 = val.Find("Info");
			Transform val3 = val2.Find("Export");
			Transform val4 = Object.Instantiate<Transform>(val3, val2);
			((Object)val4).name = "Restore";
			Button component = ((Component)val4).GetComponent<Button>();
			((UnityEventBase)component.onClick).RemoveAllListeners();
			((UnityEvent)component.onClick).AddListener(UnityAction.op_Implicit((Action)delegate
			{
				LoadClicked(slotNumber);
			}));
			val4.localPosition = new Vector3(-215f, 20f, 0f);
			Transform child = val4.GetChild(0);
			TextMeshProUGUI component2 = ((Component)child).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component2).text = "Restore";
			Object.DestroyImmediate((Object)(object)((Component)val4).GetComponent<SaveExportButton>());
		}

		public void LoadClicked(int slot)
		{
			if (!Object.op_Implicit((Object)(object)loadMenu))
			{
				CreateLoadMenu();
			}
			PopulateLoadMenu(slot);
			restoreBackupScreen.Open(true);
		}

		private void CreateLoadMenu()
		{
			loadMenu = Object.Instantiate<GameObject>(Core.backupRestorePanelPrefab).transform;
			Transform val = continueMenu.Find("Background");
			Transform val2 = Object.Instantiate<Transform>(val, loadMenu);
			((Object)val2).name = "Background";
			Transform val3 = continueMenu.Find("Title");
			Transform val4 = Object.Instantiate<Transform>(val3, loadMenu);
			((Object)val4).name = "Title";
			TextMeshProUGUI component = ((Component)val4).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = "Restore Backup";
			loadMenu.SetParent(((Component)this).transform, false);
			((Object)loadMenu).name = "RestoreBackup";
			((Component)loadMenu).gameObject.SetActive(false);
			restoreBackupScreen = ((Component)loadMenu).gameObject.AddComponent<MainMenuScreen>();
			Transform val5 = loadMenu.Find("Scroll View");
			val5.SetAsLastSibling();
		}

		private void PopulateLoadMenu(int slot)
		{
			Transform val = loadMenu.Find("Scroll View").Find("Viewport").Find("Content");
			SettingsMenuMod.DestroyAllChildren(val);
			string backupsPath = GetBackupsPath(slot);
			if (!Directory.Exists(backupsPath))
			{
				Core.Log("No backups folder found at " + backupsPath);
				return;
			}
			DirectoryInfo directoryInfo = new DirectoryInfo(backupsPath);
			IOrderedEnumerable<FileInfo> orderedEnumerable = from f in directoryInfo.GetFiles("*.zip")
				orderby Math.Min(f.CreationTimeUtc.Ticks, f.LastWriteTimeUtc.Ticks)
				select f;
			foreach (FileInfo file in orderedEnumerable)
			{
				Transform val2 = Object.Instantiate<Transform>(Core.backupRestoreButtonPrefab.transform, val);
				TimeSpan fileAge = DateTime.Now - file.CreationTime;
				if (file.LastWriteTimeUtc.Ticks < file.CreationTimeUtc.Ticks)
				{
					fileAge = DateTime.Now - file.LastWriteTime;
				}
				UpdateButtonText(val2, file.Name, fileAge);
				Button component = ((Component)val2.GetChild(0)).GetComponent<Button>();
				((UnityEvent)component.onClick).AddListener(UnityAction.op_Implicit((Action)delegate
				{
					BackupSelected(file.FullName, slot);
				}));
			}
		}

		private string GetBackupsPath(int slot)
		{
			string backupFolderPath = Singleton<SaveManager>.Instance.BackupFolderPath;
			return Path.Combine(backupFolderPath, $"SaveGame_{slot + 1}");
		}

		private void UpdateButtonText(Transform buttonParent, string fileName, TimeSpan fileAge)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			Transform child = buttonParent.GetChild(0).GetChild(0);
			TextMeshProUGUI val = ((Component)child).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val).fontSize = 16f;
			((Graphic)val).color = Color.white;
			((TMP_Text)val).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val).text = "   " + fileName;
			Transform child2 = buttonParent.GetChild(0).GetChild(1);
			Transform child3 = child2.GetChild(0);
			TextMeshProUGUI val2 = ((Component)child3).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).fontSize = 14f;
			((Graphic)val2).color = Color.white;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val2).text = "Created";
			Transform child4 = child2.GetChild(1);
			TextMeshProUGUI val3 = ((Component)child4).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val3).fontSize = 14f;
			((Graphic)val3).color = Color.gray;
			((TMP_Text)val3).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val3).text = " " + TimeLabel(fileAge);
		}

		private string TimeLabel(TimeSpan fileAge)
		{
			int num = Mathf.RoundToInt((float)fileAge.TotalHours);
			int num2 = num / 24;
			if (num2 == 0)
			{
				return num switch
				{
					0 => "In the last hour", 
					1 => "One hour ago", 
					_ => $"{num} hours ago", 
				};
			}
			if (num2 == 1)
			{
				return "Yesterday";
			}
			if (num2 > 365)
			{
				return "More than a year ago";
			}
			return num2 + " days ago";
		}

		private void BackupSelected(string fileName, int slot)
		{
			if (!string.IsNullOrEmpty(fileName))
			{
				string tempImportPath = SaveImportButton.TempImportPath;
				if (Directory.Exists(tempImportPath))
				{
					Directory.Delete(tempImportPath, recursive: true);
				}
				SaveImportButton.UnzipSaveFile(fileName, tempImportPath);
				importScreen.Initialize(slot, (MainMenuScreen)(object)((Component)continueMenu).gameObject.GetComponent<ContinueScreen>());
				restoreBackupScreen.Close(false);
				((MainMenuScreen)importScreen).Open(true);
			}
		}
	}
	[RegisterTypeInIl2Cpp]
	public class SettingsMenuMod : MonoBehaviour
	{
		private void Awake()
		{
			Init();
		}

		private void Init()
		{
			GameObject gameObject = ((Component)this).gameObject;
			Transform settings = gameObject.transform.Find("Settings");
			Button modSettingsButton = CreateBackupsButton(settings);
			GameObject modSettingsPanel = CreateBackupsPanel(settings);
			AddSettingsCategory(settings, modSettingsButton, modSettingsPanel);
		}

		private Button CreateBackupsButton(Transform settings)
		{
			Transform val = settings.Find("Buttons");
			Transform val2 = val.Find("Controls");
			Transform val3 = Object.Instantiate<Transform>(val2, val);
			((Object)val3).name = "Backups";
			TextMeshProUGUI component = ((Component)((Component)val3).transform.Find("Name")).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = "Backups";
			return ((Component)val3).GetComponent<Button>();
		}

		private GameObject CreateBackupsPanel(Transform settings)
		{
			Transform val = settings.Find("Content");
			Transform val2 = val.Find("Controls");
			Transform val3 = Object.Instantiate<Transform>(val2, val);
			((Object)val3).name = "Backups";
			Transform val4 = SetupRetentionSlider(val3);
			Transform val5 = SetupDeleteOldestToggle(val3);
			val5.SetParent((Transform)null);
			val4.SetParent((Transform)null);
			DestroyAllChildren(val3);
			val5.SetParent(val3);
			val4.SetParent(val3);
			AddAutoSaveControls(val3, val5, val4);
			return ((Component)val3).gameObject;
		}

		private Transform SetupDeleteOldestToggle(Transform settingsPanel)
		{
			Transform val = settingsPanel.Find("InvertY");
			((Object)val).name = "DeleteOldSaves";
			HorizontalLayoutGroup val2 = ((Component)val).gameObject.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)val2).spacing = 25f;
			SetLabelText(val, "Limit Backups");
			Transform val3 = val.Find("Toggle");
			val3.SetAsFirstSibling();
			Toggle component = ((Component)val3).GetComponent<Toggle>();
			component.isOn = Core.enableAutoDelete.Value;
			Object.DestroyImmediate((Object)(object)((Component)val3).GetComponent<InvertYToggle>());
			((Component)val3).gameObject.AddComponent<DeleteOldestToggle>();
			return val;
		}

		private Transform SetupRetentionSlider(Transform settingsPanel)
		{
			Transform val = settingsPanel.Find("Sensitivity");
			((Object)val).name = "RetainedCount";
			SetLabelText(val, "Max Files Per Save Slot");
			Transform val2 = val.Find("Slider");
			((UnityEventBase)((Component)val2).GetComponent<Slider>().onValueChanged).RemoveAllListeners();
			Object.DestroyImmediate((Object)(object)((Component)val2).GetComponent<SensitivitySlider>());
			((Component)val2).gameObject.AddComponent<RetentionAmountSlider>();
			return val;
		}

		private void SetLabelText(Transform parent, string text)
		{
			Transform val = parent.Find("Label");
			((Component)val).GetComponent<TMP_Text>().text = text;
		}

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

		private void AddAutoSaveControls(Transform modSettingsPanel, Transform deleteOldestToggle, Transform retentionSlider)
		{
			Transform val = Object.Instantiate<Transform>(deleteOldestToggle, modSettingsPanel);
			Transform val2 = Object.Instantiate<Transform>(retentionSlider, modSettingsPanel);
			((Object)val).name = "Enable AutoSave";
			((Object)val2).name = "AutoSave Time";
			Object.DestroyImmediate((Object)(object)((Component)val.Find("Toggle")).GetComponent<DeleteOldestToggle>());
			Object.DestroyImmediate((Object)(object)((Component)val2.Find("Slider")).GetComponent<RetentionAmountSlider>());
			SetLabelText(val, "Enable AutoSave");
			SetLabelText(val2, "Minutes Before Saving");
			AutoSaveToggle autoSaveToggle = ((Component)val.Find("Toggle")).gameObject.AddComponent<AutoSaveToggle>();
			autoSaveToggle.saveTimeSlider = ((Component)val2).gameObject;
			((Component)val2.Find("Slider")).gameObject.AddComponent<AutoSaveTimeSlider>();
		}

		private void AddSettingsCategory(Transform settings, Button modSettingsButton, GameObject modSettingsPanel)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			SettingsScreen component = ((Component)settings).GetComponent<SettingsScreen>();
			EnlargeCategories(component);
			SettingsCategory val = new SettingsCategory();
			val.Button = modSettingsButton;
			val.Panel = modSettingsPanel;
			int num = ((Il2CppArrayBase<SettingsCategory>)(object)component.Categories).Length - 1;
			((Il2CppArrayBase<SettingsCategory>)(object)component.Categories)[num] = val;
		}

		private void EnlargeCategories(SettingsScreen settingsScreen)
		{
			Il2CppReferenceArray<SettingsCategory> categories = settingsScreen.Categories;
			SettingsCategory[] array = (SettingsCategory[])(object)new SettingsCategory[((Il2CppArrayBase<SettingsCategory>)(object)categories).Length + 1];
			for (int i = 0; i < ((Il2CppArrayBase<SettingsCategory>)(object)categories).Length; i++)
			{
				array[i] = ((Il2CppArrayBase<SettingsCategory>)(object)categories)[i];
			}
			settingsScreen.Categories = Il2CppReferenceArray<SettingsCategory>.op_Implicit(array);
		}
	}
	[RegisterTypeInIl2Cpp]
	public class DeleteOldestToggle : SettingsToggle
	{
		protected GameObject retainedCountSlider = null;

		public void Start()
		{
			retainedCountSlider = GameObject.Find("RetainedCount");
			retainedCountSlider.SetActive(Core.enableAutoDelete.Value);
		}

		public override void OnValueChanged(bool value)
		{
			retainedCountSlider.SetActive(value);
			Core.enableAutoDelete.Value = value;
		}
	}
	public class Core : MelonMod
	{
		[HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(string) })]
		private static class SavePatch
		{
			public static void Postfix(string saveFolderPath)
			{
				string backupPath = GetBackupPath(saveFolderPath);
				string text = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
				string text2 = (autoSaving ? "auto_" : "");
				string text3 = Path.Combine(backupPath, text2 + text + ".zip");
				SaveExportButton.ZipSaveFolder(saveFolderPath, text3);
				logger.Msg("Exported\n" + saveFolderPath + " to\n" + text3);
				AddNewBackupFile(text3);
				ResetAutoSaveTimer();
			}
		}

		public static Instance logger;

		public static MelonPreferences_Entry<bool> enableAutoDelete;

		public static MelonPreferences_Entry<int> autoDeleteRetentionCount;

		public const int DEFAULT_RETENTION_COUNT = 125;

		private MelonPreferences_Category autoDeleteCategory;

		public static MelonPreferences_Entry<bool> enableAutoSave;

		public static MelonPreferences_Entry<int> autoSaveTime;

		public const int DEFAULT_SAVE_TIME = 10;

		private MelonPreferences_Category autoSaveCategory;

		public const int RETENTION_SLIDER_MULTIPLIER = 5;

		public const int RETENTION_SLIDER_MIN_FILES = 25;

		public const int RETENTION_SLIDER_MAX_FILES = 250;

		public static MelonPreferences_Entry<int> retentionSliderMultiplier;

		public static MelonPreferences_Entry<int> retentionSliderMinFiles;

		public static MelonPreferences_Entry<int> retentionSliderMaxFiles;

		public const int AUTOSAVE_TIME_MIN_MINUTES = 1;

		public const int AUTOSAVE_TIME_MAX_MINUTES = 60;

		public static MelonPreferences_Entry<int> autoSaveSliderMinTime;

		public static MelonPreferences_Entry<int> autoSaveSliderMaxTime;

		protected static DirectoryInfo backupDirInfo;

		protected static Queue<FileInfo> orderedBackups;

		protected static float timeBeforeAutoSave = 600f;

		protected static bool autoSaving = false;

		protected static bool inMainScene = false;

		public Il2CppAssetBundle assetBundle = null;

		public static GameObject scrollViewPrefab = null;

		public static GameObject backupRestorePanelPrefab = null;

		public static GameObject backupRestoreButtonPrefab = null;

		public override void OnInitializeMelon()
		{
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Expected O, but got Unknown
			logger = ((MelonBase)this).LoggerInstance;
			autoDeleteCategory = MelonPreferences.CreateCategory("AutoDeleteCategory");
			enableAutoDelete = autoDeleteCategory.CreateEntry<bool>("EnableAutoDelete", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			retentionSliderMultiplier = autoDeleteCategory.CreateEntry<int>("RetentionSliderMultiplier", 5, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			retentionSliderMinFiles = autoDeleteCategory.CreateEntry<int>("RetentionSliderMinFiles", 25, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			retentionSliderMaxFiles = autoDeleteCategory.CreateEntry<int>("RetentionSliderMaxFiles", 250, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoDeleteRetentionCount = autoDeleteCategory.CreateEntry<int>("AutoDeleteRetentionCount", 125, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveCategory = MelonPreferences.CreateCategory("AutoSaveCategory");
			enableAutoSave = autoSaveCategory.CreateEntry<bool>("EnableAutoSave", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveSliderMinTime = autoSaveCategory.CreateEntry<int>("timeSliderMinMinutes", 1, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveSliderMaxTime = autoSaveCategory.CreateEntry<int>("timeSliderMaxMinutes", 60, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveTime = autoSaveCategory.CreateEntry<int>("autoSaveTime", 10, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			Stream manifestResourceStream = ((MelonBase)this).MelonAssembly.Assembly.GetManifestResourceStream("AutomaticBackups.scrollview");
			if (manifestResourceStream == null)
			{
				((MelonBase)this).LoggerInstance.Msg("Failed to load scrollview asset bundle!");
				return;
			}
			byte[] array;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				manifestResourceStream.CopyTo(memoryStream);
				array = memoryStream.ToArray();
			}
			Stream val = (Stream)new MemoryStream(Il2CppStructArray<byte>.op_Implicit(array));
			assetBundle = Il2CppAssetBundleManager.LoadFromStream(val);
			if (assetBundle == null)
			{
				((MelonBase)this).LoggerInstance.Msg("Failed to load asset bundle");
				return;
			}
			manifestResourceStream.Close();
			val.Close();
			string[] array2 = Il2CppArrayBase<string>.op_Implicit((Il2CppArrayBase<string>)(object)assetBundle.GetAllAssetNames());
			try
			{
				scrollViewPrefab = assetBundle.LoadAsset<GameObject>("assets/assets/trevorsassets/modmenu/prefabs/scroll view.prefab");
				if ((Object)(object)scrollViewPrefab == (Object)null)
				{
					((MelonBase)this).LoggerInstance.Msg("Failed to load assets/assets/trevorsassets/modmenu/prefabs/scroll view.prefab");
				}
				backupRestorePanelPrefab = assetBundle.LoadAsset<GameObject>("assets/assets/trevorsassets/modmenu/prefabs/backuprestore.prefab");
				if ((Object)(object)backupRestorePanelPrefab == (Object)null)
				{
					((MelonBase)this).LoggerInstance.Msg("Failed to load assets/assets/trevorsassets/modmenu/prefabs/backuprestore.prefab");
				}
				backupRestoreButtonPrefab = assetBundle.LoadAsset<GameObject>("assets/assets/trevorsassets/modmenu/prefabs/backup restore button.prefab");
				if ((Object)(object)backupRestoreButtonPrefab == (Object)null)
				{
					((MelonBase)this).LoggerInstance.Msg("Failed to load assets/assets/trevorsassets/modmenu/prefabs/backup restore button.prefab");
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Msg("Failed to load " + ex.Message);
			}
			logger.Msg("Automatic Backups Initialized.");
		}

		public static void Log(string msg)
		{
			logger.Msg(msg);
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			inMainScene = false;
			if (sceneName == "Menu")
			{
				GameObject val = GameObject.Find("MainMenu");
				if (Object.op_Implicit((Object)(object)val.GetComponent<SettingsMenuMod>()))
				{
					Log("Backups menu has already been created.");
					return;
				}
				val.AddComponent<SettingsMenuMod>();
				val.AddComponent<ContinueMenuMod>();
			}
			else if (sceneName == "Main")
			{
				ScanBackupsFolder();
				ResetAutoSaveTimer();
				inMainScene = true;
			}
		}

		protected void ScanBackupsFolder()
		{
			string loadedGameFolderPath = Singleton<LoadManager>.Instance.LoadedGameFolderPath;
			string backupPath = GetBackupPath(loadedGameFolderPath);
			Log("Backups will be saved to\n" + backupPath);
			backupDirInfo = new DirectoryInfo(backupPath);
			IOrderedEnumerable<FileInfo> collection = from f in backupDirInfo.GetFiles("*.zip")
				orderby Math.Min(f.CreationTimeUtc.Ticks, f.LastWriteTimeUtc.Ticks)
				select f;
			orderedBackups = new Queue<FileInfo>(collection);
			if (orderedBackups.Count > 0)
			{
				long num = orderedBackups.Sum((FileInfo f) => f.Length);
				double value = (double)num / 1048576.0;
				Log($"{orderedBackups.Count} backup files found, using {value:F1} MB of disk space");
			}
			DeleteExpiredBackups();
		}

		protected static void DeleteExpiredBackups()
		{
			if (enableAutoDelete.Value && orderedBackups.Count > autoDeleteRetentionCount.Value)
			{
				Log("Number of backup files exceeds user preferences");
				while (orderedBackups.Count > autoDeleteRetentionCount.Value)
				{
					FileInfo fileInfo = orderedBackups.Dequeue();
					Log(fileInfo.Name + " from " + fileInfo.CreationTime.ToShortDateString() + " will be deleted");
					fileInfo.Delete();
				}
			}
		}

		public static void AddNewBackupFile(string path)
		{
			orderedBackups.Enqueue(new FileInfo(path));
			DeleteExpiredBackups();
		}

		private static string GetBackupPath(string saveFolderPath)
		{
			saveFolderPath = saveFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			string fileName = Path.GetFileName(saveFolderPath);
			string path = Directory.GetParent(saveFolderPath)?.FullName ?? throw new InvalidOperationException("No parent directory for '" + saveFolderPath + "'.");
			string text = Path.Combine(path, "Backups", fileName);
			Directory.CreateDirectory(text);
			return text;
		}

		public override void OnUpdate()
		{
			((MelonBase)this).OnUpdate();
			if (enableAutoSave.Value && InstanceFinder.IsHost && inMainScene)
			{
				timeBeforeAutoSave -= Time.deltaTime;
				if (!(timeBeforeAutoSave > 0f))
				{
					Log("Autosaving...");
					autoSaving = true;
					Singleton<SaveManager>.Instance.Save();
				}
			}
		}

		private static void ResetAutoSaveTimer()
		{
			timeBeforeAutoSave = 60f * (float)autoSaveTime.Value;
			autoSaving = false;
		}
	}
}

AutomaticBackups_Mono.dll

Decompiled 3 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using AutomaticBackups;
using FishNet;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using ScheduleOne.DevUtilities;
using ScheduleOne.Persistence;
using ScheduleOne.UI.MainMenu;
using ScheduleOne.UI.Settings;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(Core), "AutomaticBackups", "1.3.0-beta", "coderTrevor", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AutomaticBackups_Mono")]
[assembly: AssemblyConfiguration("MONO")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+708d6547b1197fe84da47c41d4cfe3ad111b9073")]
[assembly: AssemblyProduct("AutomaticBackups_Mono")]
[assembly: AssemblyTitle("AutomaticBackups_Mono")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace AutomaticBackups
{
	[RegisterTypeInIl2Cpp]
	public class AutoSaveToggle : SettingsToggle
	{
		public GameObject saveTimeSlider = null;

		public void Start()
		{
			bool value = Core.enableAutoSave.Value;
			Toggle component = ((Component)this).gameObject.GetComponent<Toggle>();
			component.isOn = value;
			saveTimeSlider.SetActive(value);
		}

		protected override void OnValueChanged(bool value)
		{
			saveTimeSlider.SetActive(value);
			Core.enableAutoSave.Value = value;
		}
	}
	[RegisterTypeInIl2Cpp]
	public class AutoSaveTimeSlider : SettingsSlider
	{
		protected virtual void OnEnable()
		{
			base.slider.minValue = Core.autoSaveSliderMinTime.Value;
			base.slider.maxValue = Core.autoSaveSliderMaxTime.Value;
			base.slider.value = Core.autoSaveTime.Value;
		}

		protected override void OnValueChanged(float value)
		{
			base.timeOnValueChange = Time.time;
			if (base.DisplayValue)
			{
				((TMP_Text)base.valueLabel).text = ((SettingsSlider)this).GetDisplayValue(value);
				((Behaviour)base.valueLabel).enabled = true;
			}
			Core.autoSaveTime.Value = (int)value;
		}
	}
	[RegisterTypeInIl2Cpp]
	public class RetentionAmountSlider : SettingsSlider
	{
		protected int multiplier;

		protected virtual void OnEnable()
		{
			multiplier = Core.retentionSliderMultiplier.Value;
			int num = DivideAndFloor(Core.retentionSliderMinFiles.Value, multiplier);
			int num2 = DivideAndFloor(Core.retentionSliderMaxFiles.Value, multiplier);
			int num3 = DivideAndFloor(Core.autoDeleteRetentionCount.Value, multiplier);
			base.slider.minValue = num;
			base.slider.maxValue = num2;
			base.slider.value = num3;
		}

		private int DivideAndFloor(int numerator, int denominator)
		{
			float num = (float)numerator / (float)denominator;
			return (int)Mathf.Floor(num);
		}

		protected override string GetDisplayValue(float value)
		{
			return ((int)Mathf.Round(value * (float)multiplier)).ToString();
		}

		protected override void OnValueChanged(float value)
		{
			base.timeOnValueChange = Time.time;
			if (base.DisplayValue)
			{
				((TMP_Text)base.valueLabel).text = ((SettingsSlider)this).GetDisplayValue(value);
				((Behaviour)base.valueLabel).enabled = true;
			}
			Core.autoDeleteRetentionCount.Value = (int)value * multiplier;
		}
	}
	[HarmonyPatch(typeof(MainMenuScreen), "Awake")]
	internal static class MainMenuScreenPatch
	{
		public static void Prefix(MainMenuScreen __instance)
		{
			if (!(((Object)((Component)__instance).gameObject).name != "RestoreBackup"))
			{
				__instance.OpenOnStart = true;
				__instance.PreviousScreen = (MainMenuScreen)(object)ContinueMenuMod.continueScreen;
				__instance.Group = ((Component)__instance).gameObject.GetComponent<CanvasGroup>();
			}
		}
	}
	[RegisterTypeInIl2Cpp]
	public class ContinueMenuMod : MonoBehaviour
	{
		protected Transform continueMenu;

		protected Transform loadMenu = null;

		protected MainMenuScreen restoreBackupScreen = null;

		public static ContinueScreen continueScreen;

		public ImportScreen importScreen;

		private void Awake()
		{
			Init();
		}

		private void Init()
		{
			GameObject gameObject = ((Component)this).gameObject;
			continueMenu = gameObject.transform.Find("Continue");
			continueScreen = ((Component)continueMenu).GetComponent<ContinueScreen>();
			Transform val = continueMenu.Find("Container");
			for (int i = 0; i < val.childCount; i++)
			{
				AddButtonToSlot(val.GetChild(i), i);
			}
			importScreen = ((Component)gameObject.transform.Find("ImportScreen")).GetComponent<ImportScreen>();
		}

		private void AddButtonToSlot(Transform slot, int slotNumber)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			Transform val = slot.Find("Container");
			Transform val2 = val.Find("Info");
			Transform val3 = val2.Find("Export");
			Transform val4 = Object.Instantiate<Transform>(val3, val2);
			((Object)val4).name = "Restore";
			Button component = ((Component)val4).GetComponent<Button>();
			((UnityEventBase)component.onClick).RemoveAllListeners();
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				LoadClicked(slotNumber);
			});
			val4.localPosition = new Vector3(-215f, 20f, 0f);
			Transform child = val4.GetChild(0);
			TextMeshProUGUI component2 = ((Component)child).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component2).text = "Restore";
			Object.DestroyImmediate((Object)(object)((Component)val4).GetComponent<SaveExportButton>());
		}

		public void LoadClicked(int slot)
		{
			if (!Object.op_Implicit((Object)(object)loadMenu))
			{
				CreateLoadMenu();
			}
			PopulateLoadMenu(slot);
			restoreBackupScreen.Open(true);
		}

		private void CreateLoadMenu()
		{
			loadMenu = Object.Instantiate<GameObject>(Core.backupRestorePanelPrefab).transform;
			Transform val = continueMenu.Find("Background");
			Transform val2 = Object.Instantiate<Transform>(val, loadMenu);
			((Object)val2).name = "Background";
			Transform val3 = continueMenu.Find("Title");
			Transform val4 = Object.Instantiate<Transform>(val3, loadMenu);
			((Object)val4).name = "Title";
			TextMeshProUGUI component = ((Component)val4).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = "Restore Backup";
			loadMenu.SetParent(((Component)this).transform, false);
			((Object)loadMenu).name = "RestoreBackup";
			((Component)loadMenu).gameObject.SetActive(false);
			restoreBackupScreen = ((Component)loadMenu).gameObject.AddComponent<MainMenuScreen>();
			Transform val5 = loadMenu.Find("Scroll View");
			val5.SetAsLastSibling();
		}

		private void PopulateLoadMenu(int slot)
		{
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			Transform val = loadMenu.Find("Scroll View").Find("Viewport").Find("Content");
			SettingsMenuMod.DestroyAllChildren(val);
			string backupsPath = GetBackupsPath(slot);
			if (!Directory.Exists(backupsPath))
			{
				Core.Log("No backups folder found at " + backupsPath);
				return;
			}
			DirectoryInfo directoryInfo = new DirectoryInfo(backupsPath);
			IOrderedEnumerable<FileInfo> orderedEnumerable = from f in directoryInfo.GetFiles("*.zip")
				orderby Math.Min(f.CreationTimeUtc.Ticks, f.LastWriteTimeUtc.Ticks)
				select f;
			foreach (FileInfo file in orderedEnumerable)
			{
				Transform val2 = Object.Instantiate<Transform>(Core.backupRestoreButtonPrefab.transform, val);
				TimeSpan fileAge = DateTime.Now - file.CreationTime;
				if (file.LastWriteTimeUtc.Ticks < file.CreationTimeUtc.Ticks)
				{
					fileAge = DateTime.Now - file.LastWriteTime;
				}
				UpdateButtonText(val2, file.Name, fileAge);
				Button component = ((Component)val2.GetChild(0)).GetComponent<Button>();
				((UnityEvent)component.onClick).AddListener((UnityAction)delegate
				{
					BackupSelected(file.FullName, slot);
				});
			}
		}

		private string GetBackupsPath(int slot)
		{
			string backupFolderPath = Singleton<SaveManager>.Instance.BackupFolderPath;
			return Path.Combine(backupFolderPath, $"SaveGame_{slot + 1}");
		}

		private void UpdateButtonText(Transform buttonParent, string fileName, TimeSpan fileAge)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			Transform child = buttonParent.GetChild(0).GetChild(0);
			TextMeshProUGUI val = ((Component)child).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val).fontSize = 16f;
			((Graphic)val).color = Color.white;
			((TMP_Text)val).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val).text = "   " + fileName;
			Transform child2 = buttonParent.GetChild(0).GetChild(1);
			Transform child3 = child2.GetChild(0);
			TextMeshProUGUI val2 = ((Component)child3).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).fontSize = 14f;
			((Graphic)val2).color = Color.white;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val2).text = "Created";
			Transform child4 = child2.GetChild(1);
			TextMeshProUGUI val3 = ((Component)child4).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val3).fontSize = 14f;
			((Graphic)val3).color = Color.gray;
			((TMP_Text)val3).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val3).text = " " + TimeLabel(fileAge);
		}

		private string TimeLabel(TimeSpan fileAge)
		{
			int num = Mathf.RoundToInt((float)fileAge.TotalHours);
			int num2 = num / 24;
			if (num2 == 0)
			{
				return num switch
				{
					0 => "In the last hour", 
					1 => "One hour ago", 
					_ => $"{num} hours ago", 
				};
			}
			if (num2 == 1)
			{
				return "Yesterday";
			}
			if (num2 > 365)
			{
				return "More than a year ago";
			}
			return num2 + " days ago";
		}

		private void BackupSelected(string fileName, int slot)
		{
			if (!string.IsNullOrEmpty(fileName))
			{
				string tempImportPath = SaveImportButton.TempImportPath;
				if (Directory.Exists(tempImportPath))
				{
					Directory.Delete(tempImportPath, recursive: true);
				}
				SaveImportButton.UnzipSaveFile(fileName, tempImportPath);
				importScreen.Initialize(slot, (MainMenuScreen)(object)((Component)continueMenu).gameObject.GetComponent<ContinueScreen>());
				restoreBackupScreen.Close(false);
				((MainMenuScreen)importScreen).Open(true);
			}
		}
	}
	[RegisterTypeInIl2Cpp]
	public class SettingsMenuMod : MonoBehaviour
	{
		private void Awake()
		{
			Init();
		}

		private void Init()
		{
			GameObject gameObject = ((Component)this).gameObject;
			Transform settings = gameObject.transform.Find("Settings");
			Button modSettingsButton = CreateBackupsButton(settings);
			GameObject modSettingsPanel = CreateBackupsPanel(settings);
			AddSettingsCategory(settings, modSettingsButton, modSettingsPanel);
		}

		private Button CreateBackupsButton(Transform settings)
		{
			Transform val = settings.Find("Buttons");
			Transform val2 = val.Find("Controls");
			Transform val3 = Object.Instantiate<Transform>(val2, val);
			((Object)val3).name = "Backups";
			TextMeshProUGUI component = ((Component)((Component)val3).transform.Find("Name")).GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = "Backups";
			return ((Component)val3).GetComponent<Button>();
		}

		private GameObject CreateBackupsPanel(Transform settings)
		{
			Transform val = settings.Find("Content");
			Transform val2 = val.Find("Controls");
			Transform val3 = Object.Instantiate<Transform>(val2, val);
			((Object)val3).name = "Backups";
			Transform val4 = SetupRetentionSlider(val3);
			Transform val5 = SetupDeleteOldestToggle(val3);
			val5.SetParent((Transform)null);
			val4.SetParent((Transform)null);
			DestroyAllChildren(val3);
			val5.SetParent(val3);
			val4.SetParent(val3);
			AddAutoSaveControls(val3, val5, val4);
			return ((Component)val3).gameObject;
		}

		private Transform SetupDeleteOldestToggle(Transform settingsPanel)
		{
			Transform val = settingsPanel.Find("InvertY");
			((Object)val).name = "DeleteOldSaves";
			HorizontalLayoutGroup val2 = ((Component)val).gameObject.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)val2).spacing = 25f;
			SetLabelText(val, "Limit Backups");
			Transform val3 = val.Find("Toggle");
			val3.SetAsFirstSibling();
			Toggle component = ((Component)val3).GetComponent<Toggle>();
			component.isOn = Core.enableAutoDelete.Value;
			Object.DestroyImmediate((Object)(object)((Component)val3).GetComponent<InvertYToggle>());
			((Component)val3).gameObject.AddComponent<DeleteOldestToggle>();
			return val;
		}

		private Transform SetupRetentionSlider(Transform settingsPanel)
		{
			Transform val = settingsPanel.Find("Sensitivity");
			((Object)val).name = "RetainedCount";
			SetLabelText(val, "Max Files Per Save Slot");
			Transform val2 = val.Find("Slider");
			((UnityEventBase)((Component)val2).GetComponent<Slider>().onValueChanged).RemoveAllListeners();
			Object.DestroyImmediate((Object)(object)((Component)val2).GetComponent<SensitivitySlider>());
			((Component)val2).gameObject.AddComponent<RetentionAmountSlider>();
			return val;
		}

		private void SetLabelText(Transform parent, string text)
		{
			Transform val = parent.Find("Label");
			((Component)val).GetComponent<TMP_Text>().text = text;
		}

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

		private void AddAutoSaveControls(Transform modSettingsPanel, Transform deleteOldestToggle, Transform retentionSlider)
		{
			Transform val = Object.Instantiate<Transform>(deleteOldestToggle, modSettingsPanel);
			Transform val2 = Object.Instantiate<Transform>(retentionSlider, modSettingsPanel);
			((Object)val).name = "Enable AutoSave";
			((Object)val2).name = "AutoSave Time";
			Object.DestroyImmediate((Object)(object)((Component)val.Find("Toggle")).GetComponent<DeleteOldestToggle>());
			Object.DestroyImmediate((Object)(object)((Component)val2.Find("Slider")).GetComponent<RetentionAmountSlider>());
			SetLabelText(val, "Enable AutoSave");
			SetLabelText(val2, "Minutes Before Saving");
			AutoSaveToggle autoSaveToggle = ((Component)val.Find("Toggle")).gameObject.AddComponent<AutoSaveToggle>();
			autoSaveToggle.saveTimeSlider = ((Component)val2).gameObject;
			((Component)val2.Find("Slider")).gameObject.AddComponent<AutoSaveTimeSlider>();
		}

		private void AddSettingsCategory(Transform settings, Button modSettingsButton, GameObject modSettingsPanel)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			SettingsScreen component = ((Component)settings).GetComponent<SettingsScreen>();
			EnlargeCategories(component);
			SettingsCategory val = new SettingsCategory();
			val.Button = modSettingsButton;
			val.Panel = modSettingsPanel;
			int num = component.Categories.Length - 1;
			component.Categories[num] = val;
		}

		private void EnlargeCategories(SettingsScreen settingsScreen)
		{
			SettingsCategory[] categories = settingsScreen.Categories;
			SettingsCategory[] array = (SettingsCategory[])(object)new SettingsCategory[categories.Length + 1];
			for (int i = 0; i < categories.Length; i++)
			{
				array[i] = categories[i];
			}
			settingsScreen.Categories = array;
		}
	}
	[RegisterTypeInIl2Cpp]
	public class DeleteOldestToggle : SettingsToggle
	{
		protected GameObject retainedCountSlider = null;

		public void Start()
		{
			retainedCountSlider = GameObject.Find("RetainedCount");
			retainedCountSlider.SetActive(Core.enableAutoDelete.Value);
		}

		protected override void OnValueChanged(bool value)
		{
			retainedCountSlider.SetActive(value);
			Core.enableAutoDelete.Value = value;
		}
	}
	public class Core : MelonMod
	{
		[HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(string) })]
		private static class SavePatch
		{
			public static void Postfix(string saveFolderPath)
			{
				string backupPath = GetBackupPath(saveFolderPath);
				string text = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
				string text2 = (autoSaving ? "auto_" : "");
				string text3 = Path.Combine(backupPath, text2 + text + ".zip");
				SaveExportButton.ZipSaveFolder(saveFolderPath, text3);
				logger.Msg("Exported\n" + saveFolderPath + " to\n" + text3);
				AddNewBackupFile(text3);
				ResetAutoSaveTimer();
			}
		}

		public static Instance logger;

		public static MelonPreferences_Entry<bool> enableAutoDelete;

		public static MelonPreferences_Entry<int> autoDeleteRetentionCount;

		public const int DEFAULT_RETENTION_COUNT = 125;

		private MelonPreferences_Category autoDeleteCategory;

		public static MelonPreferences_Entry<bool> enableAutoSave;

		public static MelonPreferences_Entry<int> autoSaveTime;

		public const int DEFAULT_SAVE_TIME = 10;

		private MelonPreferences_Category autoSaveCategory;

		public const int RETENTION_SLIDER_MULTIPLIER = 5;

		public const int RETENTION_SLIDER_MIN_FILES = 25;

		public const int RETENTION_SLIDER_MAX_FILES = 250;

		public static MelonPreferences_Entry<int> retentionSliderMultiplier;

		public static MelonPreferences_Entry<int> retentionSliderMinFiles;

		public static MelonPreferences_Entry<int> retentionSliderMaxFiles;

		public const int AUTOSAVE_TIME_MIN_MINUTES = 1;

		public const int AUTOSAVE_TIME_MAX_MINUTES = 60;

		public static MelonPreferences_Entry<int> autoSaveSliderMinTime;

		public static MelonPreferences_Entry<int> autoSaveSliderMaxTime;

		protected static DirectoryInfo backupDirInfo;

		protected static Queue<FileInfo> orderedBackups;

		protected static float timeBeforeAutoSave = 600f;

		protected static bool autoSaving = false;

		protected static bool inMainScene = false;

		public AssetBundle assetBundle = null;

		public static GameObject scrollViewPrefab = null;

		public static GameObject backupRestorePanelPrefab = null;

		public static GameObject backupRestoreButtonPrefab = null;

		public override void OnInitializeMelon()
		{
			logger = ((MelonBase)this).LoggerInstance;
			autoDeleteCategory = MelonPreferences.CreateCategory("AutoDeleteCategory");
			enableAutoDelete = autoDeleteCategory.CreateEntry<bool>("EnableAutoDelete", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			retentionSliderMultiplier = autoDeleteCategory.CreateEntry<int>("RetentionSliderMultiplier", 5, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			retentionSliderMinFiles = autoDeleteCategory.CreateEntry<int>("RetentionSliderMinFiles", 25, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			retentionSliderMaxFiles = autoDeleteCategory.CreateEntry<int>("RetentionSliderMaxFiles", 250, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoDeleteRetentionCount = autoDeleteCategory.CreateEntry<int>("AutoDeleteRetentionCount", 125, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveCategory = MelonPreferences.CreateCategory("AutoSaveCategory");
			enableAutoSave = autoSaveCategory.CreateEntry<bool>("EnableAutoSave", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveSliderMinTime = autoSaveCategory.CreateEntry<int>("timeSliderMinMinutes", 1, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveSliderMaxTime = autoSaveCategory.CreateEntry<int>("timeSliderMaxMinutes", 60, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			autoSaveTime = autoSaveCategory.CreateEntry<int>("autoSaveTime", 10, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			Stream manifestResourceStream = ((MelonBase)this).MelonAssembly.Assembly.GetManifestResourceStream("AutomaticBackups.scrollview");
			if (manifestResourceStream == null)
			{
				((MelonBase)this).LoggerInstance.Msg("Failed to load scrollview asset bundle!");
				return;
			}
			assetBundle = AssetBundle.LoadFromStream(manifestResourceStream);
			if ((Object)(object)assetBundle == (Object)null)
			{
				((MelonBase)this).LoggerInstance.Msg("Failed to load asset bundle");
				return;
			}
			manifestResourceStream.Close();
			string[] allAssetNames = assetBundle.GetAllAssetNames();
			try
			{
				scrollViewPrefab = assetBundle.LoadAsset<GameObject>("assets/assets/trevorsassets/modmenu/prefabs/scroll view.prefab");
				if ((Object)(object)scrollViewPrefab == (Object)null)
				{
					((MelonBase)this).LoggerInstance.Msg("Failed to load assets/assets/trevorsassets/modmenu/prefabs/scroll view.prefab");
				}
				backupRestorePanelPrefab = assetBundle.LoadAsset<GameObject>("assets/assets/trevorsassets/modmenu/prefabs/backuprestore.prefab");
				if ((Object)(object)backupRestorePanelPrefab == (Object)null)
				{
					((MelonBase)this).LoggerInstance.Msg("Failed to load assets/assets/trevorsassets/modmenu/prefabs/backuprestore.prefab");
				}
				backupRestoreButtonPrefab = assetBundle.LoadAsset<GameObject>("assets/assets/trevorsassets/modmenu/prefabs/backup restore button.prefab");
				if ((Object)(object)backupRestoreButtonPrefab == (Object)null)
				{
					((MelonBase)this).LoggerInstance.Msg("Failed to load assets/assets/trevorsassets/modmenu/prefabs/backup restore button.prefab");
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Msg("Failed to load " + ex.Message);
			}
			logger.Msg("Automatic Backups Initialized.");
		}

		public static void Log(string msg)
		{
			logger.Msg(msg);
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			inMainScene = false;
			if (sceneName == "Menu")
			{
				GameObject val = GameObject.Find("MainMenu");
				if (Object.op_Implicit((Object)(object)val.GetComponent<SettingsMenuMod>()))
				{
					Log("Backups menu has already been created.");
					return;
				}
				val.AddComponent<SettingsMenuMod>();
				val.AddComponent<ContinueMenuMod>();
			}
			else if (sceneName == "Main")
			{
				ScanBackupsFolder();
				ResetAutoSaveTimer();
				inMainScene = true;
			}
		}

		protected void ScanBackupsFolder()
		{
			string loadedGameFolderPath = Singleton<LoadManager>.Instance.LoadedGameFolderPath;
			string backupPath = GetBackupPath(loadedGameFolderPath);
			Log("Backups will be saved to\n" + backupPath);
			backupDirInfo = new DirectoryInfo(backupPath);
			IOrderedEnumerable<FileInfo> collection = from f in backupDirInfo.GetFiles("*.zip")
				orderby Math.Min(f.CreationTimeUtc.Ticks, f.LastWriteTimeUtc.Ticks)
				select f;
			orderedBackups = new Queue<FileInfo>(collection);
			if (orderedBackups.Count > 0)
			{
				long num = orderedBackups.Sum((FileInfo f) => f.Length);
				double num2 = (double)num / 1048576.0;
				Log($"{orderedBackups.Count} backup files found, using {num2:F1} MB of disk space");
			}
			DeleteExpiredBackups();
		}

		protected static void DeleteExpiredBackups()
		{
			if (enableAutoDelete.Value && orderedBackups.Count > autoDeleteRetentionCount.Value)
			{
				Log("Number of backup files exceeds user preferences");
				while (orderedBackups.Count > autoDeleteRetentionCount.Value)
				{
					FileInfo fileInfo = orderedBackups.Dequeue();
					Log(fileInfo.Name + " from " + fileInfo.CreationTime.ToShortDateString() + " will be deleted");
					fileInfo.Delete();
				}
			}
		}

		public static void AddNewBackupFile(string path)
		{
			orderedBackups.Enqueue(new FileInfo(path));
			DeleteExpiredBackups();
		}

		private static string GetBackupPath(string saveFolderPath)
		{
			saveFolderPath = saveFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			string fileName = Path.GetFileName(saveFolderPath);
			string path = Directory.GetParent(saveFolderPath)?.FullName ?? throw new InvalidOperationException("No parent directory for '" + saveFolderPath + "'.");
			string text = Path.Combine(path, "Backups", fileName);
			Directory.CreateDirectory(text);
			return text;
		}

		public override void OnUpdate()
		{
			((MelonBase)this).OnUpdate();
			if (enableAutoSave.Value && InstanceFinder.IsHost && inMainScene)
			{
				timeBeforeAutoSave -= Time.deltaTime;
				if (!(timeBeforeAutoSave > 0f))
				{
					Log("Autosaving...");
					autoSaving = true;
					Singleton<SaveManager>.Instance.Save();
				}
			}
		}

		private static void ResetAutoSaveTimer()
		{
			timeBeforeAutoSave = 60f * (float)autoSaveTime.Value;
			autoSaving = false;
		}
	}
}