Decompiled source of SaveGuard v0.1.2

SaveGuard.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Mirror;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using YAPYAP;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("SaveGuard")]
[assembly: AssemblyDescription("YAPYAP save protection and configurable failed-extraction recovery")]
[assembly: AssemblyCompany("SaveGuard")]
[assembly: AssemblyProduct("SaveGuard")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace SaveGuard
{
	internal static class CompatibilityGuard
	{
		internal static bool Validate(string expectedHash, bool enforce, out string reason)
		{
			try
			{
				using FileStream inputStream = File.OpenRead(typeof(GameManager).Assembly.Location);
				using SHA256 sHA = SHA256.Create();
				string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty).ToLowerInvariant();
				if (string.Equals(text, expectedHash, StringComparison.OrdinalIgnoreCase))
				{
					reason = "Assembly hash verified.";
					return true;
				}
				reason = "Expected " + expectedHash + ", found " + text + ".";
				if (!enforce)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("YAPYAP build differs from the verified build; continuing because EnforceBuildGuard is disabled. " + reason));
					}
					return true;
				}
				return false;
			}
			catch (Exception ex)
			{
				reason = "Unable to hash Assembly-CSharp.dll: " + ex.Message;
				return !enforce;
			}
		}
	}
	internal static class FailureContext
	{
		internal static bool RestartScopeActive { get; private set; }

		internal static bool SoftFailureOccurred { get; private set; }

		internal static bool GameOverExecutionScope { get; private set; }

		internal static void BeginRestart(bool reachedQuota)
		{
			RestartScopeActive = !reachedQuota && (Plugin.ProtectQuotaFailure?.Value ?? false);
			if (reachedQuota)
			{
				SoftFailureOccurred = false;
			}
		}

		internal static void MarkSoftFailure()
		{
			SoftFailureOccurred = true;
		}

		internal static void EndRestart()
		{
			RestartScopeActive = false;
			SoftFailureOccurred = false;
		}

		internal static void BeginGameOverExecution()
		{
			GameOverExecutionScope = SoftFailureOccurred;
		}

		internal static void EndGameOverExecution()
		{
			GameOverExecutionScope = false;
			SoftFailureOccurred = false;
		}

		internal static void Reset()
		{
			RestartScopeActive = false;
			SoftFailureOccurred = false;
			GameOverExecutionScope = false;
		}
	}
	[BepInPlugin("com.saveguard.yapyap", "SaveGuard", "0.1.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.saveguard.yapyap";

		public const string PluginName = "SaveGuard";

		public const string PluginVersion = "0.1.2";

		internal const string SupportedAssemblyHash = "7b6ef048e716ce4cf87bf5c6f190b3c11d39c50aa18a81467770f13ceed3c542";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> ProtectQuotaFailure;

		internal static ConfigEntry<int> RecoveryPercent;

		internal static ConfigEntry<bool> CreateEmergencyBackup;

		internal static ConfigEntry<int> MaxEmergencyBackups;

		internal static ConfigEntry<bool> EnforceBuildGuard;

		internal static ConfigEntry<bool> DebugLog;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ProtectQuotaFailure = ((BaseUnityPlugin)this).Config.Bind<bool>("Quota Failure", "ProtectSave", true, "Keep the current save and restart the same quota session from night one after failure.");
			RecoveryPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Recovery", "RecoveryPercent", 100, new ConfigDescription("Chance for each eligible dropped item to return to the lobby.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[5] { 0, 25, 50, 75, 100 }), Array.Empty<object>()));
			CreateEmergencyBackup = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "CreateEmergencyBackup", true, "Copy the current save before applying the quota-failure soft reset.");
			MaxEmergencyBackups = ((BaseUnityPlugin)this).Config.Bind<int>("Safety", "MaxEmergencyBackups", 5, new ConfigDescription("Maximum SaveGuard backup files retained per profile.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			EnforceBuildGuard = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "EnforceBuildGuard", true, "Only install gameplay patches on the locally verified YAPYAP Assembly-CSharp build.");
			DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, "Enable verbose SaveGuard logging.");
			if (!CompatibilityGuard.Validate("7b6ef048e716ce4cf87bf5c6f190b3c11d39c50aa18a81467770f13ceed3c542", EnforceBuildGuard.Value, out var reason))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Compatibility validation failed; SaveGuard gameplay patches were not installed. " + reason));
				return;
			}
			_harmony = new Harmony("com.saveguard.yapyap");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SaveGuard loaded: quota-failure protection and configurable item recovery are active.");
		}

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

		internal static void Debug(string message)
		{
			ConfigEntry<bool> debugLog = DebugLog;
			if (debugLog != null && debugLog.Value)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)message);
				}
			}
		}
	}
	internal static class SaveBackupService
	{
		private static readonly MethodInfo SaveGameDataMethod = AccessTools.Method(typeof(GameManager), "SaveGameData", (Type[])null, (Type[])null);

		internal static void TryCreateQuotaFailureBackup(GameManager gameManager)
		{
			ConfigEntry<bool> createEmergencyBackup = Plugin.CreateEmergencyBackup;
			SaveManager val = default(SaveManager);
			if (createEmergencyBackup == null || !createEmergencyBackup.Value || (Object)(object)gameManager == (Object)null || !Service.Get<SaveManager>(ref val))
			{
				return;
			}
			try
			{
				if (SaveGameDataMethod == null)
				{
					throw new MissingMethodException(typeof(GameManager).FullName, "SaveGameData");
				}
				SaveGameDataMethod.Invoke(gameManager, null);
				int currentSlot = val.CurrentSlot;
				string path = Path.Combine(Application.persistentDataPath, "saves");
				string text = Path.Combine(path, $"save_slot_{currentSlot}.json");
				if (!File.Exists(text))
				{
					Plugin.Debug($"No on-disk save found for slot {currentSlot}; emergency backup skipped.");
					return;
				}
				string text2 = Path.Combine(path, "SaveGuardBackups");
				Directory.CreateDirectory(text2);
				string text3 = Path.Combine(text2, string.Format(arg1: DateTime.Now.ToString("yyyyMMdd-HHmmss-fff"), format: "save_slot_{0}_{1}.json", arg0: currentSlot));
				File.Copy(text, text3, overwrite: false);
				PruneBackups(text2, Plugin.MaxEmergencyBackups.Value);
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("Created quota-failure emergency backup: " + text3));
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Unable to create quota-failure emergency backup: " + ex.Message));
				}
			}
		}

		private static void PruneBackups(string directory, int maximum)
		{
			FileInfo[] array = (from file in new DirectoryInfo(directory).GetFiles("save_slot_*.json")
				orderby file.LastWriteTimeUtc descending
				select file).ToArray();
			for (int num = maximum; num < array.Length; num++)
			{
				try
				{
					array[num].Delete();
				}
				catch (Exception ex)
				{
					Plugin.Debug("Unable to prune old backup " + array[num].FullName + ": " + ex.Message);
				}
			}
		}
	}
	internal static class SaveGuardPolicy
	{
		internal static int ClampRecoveryPercent(int value)
		{
			if (value < 0)
			{
				return 0;
			}
			if (value > 100)
			{
				return 100;
			}
			return value;
		}

		internal static int NormalizeRecoveryPercent(int value)
		{
			int num = ClampRecoveryPercent(value);
			if (num < 13)
			{
				return 0;
			}
			if (num < 38)
			{
				return 25;
			}
			if (num < 63)
			{
				return 50;
			}
			if (num < 88)
			{
				return 75;
			}
			return 100;
		}

		internal static float ToRecoveryChance(int percent)
		{
			return (float)NormalizeRecoveryPercent(percent) / 100f;
		}

		internal static bool ShouldUseSoftReset(bool enabled, bool scopeActive, bool reachedQuota)
		{
			if (enabled && scopeActive)
			{
				return !reachedQuota;
			}
			return false;
		}

		internal static bool ShouldSuppressGameOverDelete(bool enabled, bool softFailureOccurred, bool executionScope)
		{
			return enabled && softFailureOccurred && executionScope;
		}
	}
	internal static class SettingsUiInjector
	{
		private const string SectionName = "SaveGuard_Section";

		private const string TabName = "SaveGuard_Tab";

		private static readonly FieldInfo SectionsField = AccessTools.Field(typeof(UISettings), "sections");

		private static readonly int[] RecoveryOptions = new int[5] { 0, 25, 50, 75, 100 };

		internal static void TryInject(UISettings settings)
		{
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: 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_01aa: Expected O, but got Unknown
			if ((Object)(object)settings == (Object)null || !(SectionsField?.GetValue(settings) is SettingsSection[] array) || array.Length == 0 || array.Any((SettingsSection section) => (Object)(object)section?.SectionObj != (Object)null && ((Object)section.SectionObj).name == "SaveGuard_Section") || (!NetworkServer.active && NetworkClient.active))
			{
				return;
			}
			SettingsSection val = array[0];
			if ((Object)(object)val?.SectionObj == (Object)null || (Object)(object)val.TabButton == (Object)null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"Unable to inject SaveGuard settings: no usable settings section template was found.");
				}
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(val.SectionObj, val.SectionObj.transform.parent);
			((Object)val2).name = "SaveGuard_Section";
			ClearSection(val2);
			Transform content = BuildScrollContent(val2);
			GameObject val3 = Object.Instantiate<GameObject>(((Component)val.TabButton).gameObject, ((Component)val.TabButton).transform.parent);
			((Object)val3).name = "SaveGuard_Tab";
			Button component = val3.GetComponent<Button>();
			UIFader componentInChildren = val3.GetComponentInChildren<UIFader>(true);
			if ((Object)(object)component == (Object)null || (Object)(object)componentInChildren == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				Object.Destroy((Object)(object)val3);
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)"Unable to inject SaveGuard settings: cloned tab was incomplete.");
				}
				return;
			}
			SetTabLabel(val3, Localized("存档守护", "SaveGuard"));
			BuildControls(content);
			val2.SetActive(false);
			val3.SetActive(true);
			SettingsSection[] array2 = (SettingsSection[])(object)new SettingsSection[array.Length + 1];
			Array.Copy(array, array2, array.Length);
			array2[array.Length] = new SettingsSection
			{
				SectionObj = val2,
				TabButton = component,
				Indictor = componentInChildren
			};
			SectionsField.SetValue(settings, array2);
			ManualLogSource log3 = Plugin.Log;
			if (log3 != null)
			{
				log3.LogInfo((object)"Injected the SaveGuard tab into the in-game Settings panel.");
			}
		}

		private static void ClearSection(GameObject sectionObject)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			MonoBehaviour[] components = sectionObject.GetComponents<MonoBehaviour>();
			foreach (MonoBehaviour val in components)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val);
				}
			}
			List<GameObject> list = new List<GameObject>();
			foreach (Transform item in sectionObject.transform)
			{
				Transform val2 = item;
				list.Add(((Component)val2).gameObject);
			}
			foreach (GameObject item2 in list)
			{
				Object.DestroyImmediate((Object)(object)item2);
			}
		}

		private static Transform BuildScrollContent(GameObject sectionObject)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Expected O, but got Unknown
			GameObject val = new GameObject("Scroll View", new Type[2]
			{
				typeof(RectTransform),
				typeof(ScrollRect)
			});
			val.transform.SetParent(sectionObject.transform, false);
			SetFullRect(val.GetComponent<RectTransform>(), new Vector2(-40f, -40f));
			GameObject val2 = new GameObject("Viewport", new Type[2]
			{
				typeof(RectTransform),
				typeof(RectMask2D)
			});
			val2.transform.SetParent(val.transform, false);
			RectTransform component = val2.GetComponent<RectTransform>();
			SetFullRect(component, Vector2.zero);
			GameObject val3 = new GameObject("Content", new Type[3]
			{
				typeof(RectTransform),
				typeof(VerticalLayoutGroup),
				typeof(ContentSizeFitter)
			});
			val3.transform.SetParent(val2.transform, false);
			RectTransform component2 = val3.GetComponent<RectTransform>();
			component2.anchorMin = new Vector2(0f, 1f);
			component2.anchorMax = new Vector2(1f, 1f);
			component2.pivot = new Vector2(0.5f, 1f);
			component2.sizeDelta = Vector2.zero;
			VerticalLayoutGroup component3 = val3.GetComponent<VerticalLayoutGroup>();
			((LayoutGroup)component3).childAlignment = (TextAnchor)1;
			((HorizontalOrVerticalLayoutGroup)component3).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)component3).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)component3).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)component3).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)component3).spacing = 12f;
			((LayoutGroup)component3).padding = new RectOffset(30, 30, 25, 30);
			val3.GetComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
			ScrollRect component4 = val.GetComponent<ScrollRect>();
			component4.content = component2;
			component4.viewport = component;
			component4.horizontal = false;
			component4.vertical = true;
			component4.scrollSensitivity = 30f;
			component4.movementType = (MovementType)2;
			return (Transform)(object)component2;
		}

		private static void BuildControls(Transform content)
		{
			TMP_Text? obj = ((IEnumerable<TMP_Text>)Object.FindObjectsByType<TMP_Text>((FindObjectsInactive)1, (FindObjectsSortMode)0)).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => (Object)(object)text != (Object)null && (Object)(object)text.font != (Object)null));
			TMP_FontAsset font = ((obj != null) ? obj.font : null) ?? TMP_Settings.defaultFontAsset;
			CreateHeader(content, font);
			UISettingToggle val = ((IEnumerable<UISettingToggle>)Object.FindObjectsByType<UISettingToggle>((FindObjectsInactive)1, (FindObjectsSortMode)0)).FirstOrDefault((Func<UISettingToggle, bool>)((UISettingToggle control) => (Object)(object)control != (Object)null && !((Object)((Component)control).gameObject).name.StartsWith("SaveGuard", StringComparison.Ordinal)));
			UISettingDropdown val2 = ((IEnumerable<UISettingDropdown>)Object.FindObjectsByType<UISettingDropdown>((FindObjectsInactive)1, (FindObjectsSortMode)0)).FirstOrDefault((Func<UISettingDropdown, bool>)((UISettingDropdown control) => (Object)(object)control != (Object)null && !((Object)((Component)control).gameObject).name.StartsWith("SaveGuard", StringComparison.Ordinal)));
			if ((Object)(object)val != (Object)null)
			{
				CreateToggle(((Component)val).gameObject, content, "SaveGuard_ProtectQuotaFailure", Localized("任务失败存档", "Keep Save After Mission Failure"), Plugin.ProtectQuotaFailure.Value, delegate(bool value)
				{
					Plugin.ProtectQuotaFailure.Value = value;
				}, font);
			}
			if ((Object)(object)val2 != (Object)null)
			{
				List<string> options = RecoveryOptions.Select((int value) => value + "%").ToList();
				int initialValue = FindNearestRecoveryIndex(Plugin.RecoveryPercent.Value);
				CreateDropdown(((Component)val2).gameObject, content, "SaveGuard_RecoveryPercent", Localized("物品回收率", "Item Recovery Rate"), options, initialValue, delegate(int index)
				{
					Plugin.RecoveryPercent.Value = RecoveryOptions[Mathf.Clamp(index, 0, RecoveryOptions.Length - 1)];
				}, font);
			}
		}

		private static void CreateHeader(Transform parent, TMP_FontAsset font)
		{
			//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_0077: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SaveGuard_Title", new Type[3]
			{
				typeof(RectTransform),
				typeof(TextMeshProUGUI),
				typeof(LayoutElement)
			});
			val.transform.SetParent(parent, false);
			TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = Localized("存档守护 (SaveGuard)", "SaveGuard");
			((TMP_Text)component).fontSize = 30f;
			((TMP_Text)component).alignment = (TextAlignmentOptions)514;
			((Graphic)component).color = Color.white;
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)component).font = font;
			}
			val.GetComponent<LayoutElement>().preferredHeight = 55f;
		}

		private static void CreateToggle(GameObject template, Transform parent, string name, string label, bool initialValue, UnityAction<bool> callback, TMP_FontAsset font)
		{
			GameObject val = Object.Instantiate<GameObject>(template, parent);
			((Object)val).name = name;
			val.SetActive(true);
			UISettingToggle component = val.GetComponent<UISettingToggle>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			((UISettingElement<bool>)(object)component).SetSettingKey(string.Empty);
			((UnityEventBase)((UISettingElement<bool>)(object)component).OnSettingChanged).RemoveAllListeners();
			RemoveLocalisation(val);
			TMP_Text valueLabel = default(TMP_Text);
			ref TMP_Text reference = ref valueLabel;
			object? obj = AccessTools.Field(typeof(UISettingElement<bool>), "valueLabel")?.GetValue(component);
			reference = (TMP_Text)((obj is TMP_Text) ? obj : null);
			TMP_Text val2 = ((IEnumerable<TMP_Text>)val.GetComponentsInChildren<TMP_Text>(true)).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => (Object)(object)text != (Object)(object)valueLabel));
			if ((Object)(object)val2 != (Object)null)
			{
				val2.text = label;
				if ((Object)(object)font != (Object)null)
				{
					val2.font = font;
				}
			}
			((UISettingElement<bool>)(object)component).SetValueNoNotify(initialValue);
			((UISettingElement<bool>)(object)component).DisplayValue(initialValue);
			((UISettingElement<bool>)(object)component).OnSettingChanged.AddListener(callback);
		}

		private static void CreateDropdown(GameObject template, Transform parent, string name, string label, List<string> options, int initialValue, UnityAction<int> callback, TMP_FontAsset font)
		{
			GameObject val = Object.Instantiate<GameObject>(template, parent);
			((Object)val).name = name;
			val.SetActive(true);
			UISettingDropdown component = val.GetComponent<UISettingDropdown>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			((UISettingElement<int>)(object)component).SetSettingKey(string.Empty);
			((UnityEventBase)((UISettingElement<int>)(object)component).OnSettingChanged).RemoveAllListeners();
			RemoveLocalisation(val);
			TMP_Text valueLabel = default(TMP_Text);
			ref TMP_Text reference = ref valueLabel;
			object? obj = AccessTools.Field(typeof(UISettingElement<int>), "valueLabel")?.GetValue(component);
			reference = (TMP_Text)((obj is TMP_Text) ? obj : null);
			object? obj2 = AccessTools.Field(typeof(UISettingDropdown), "dropdown")?.GetValue(component);
			TMP_Dropdown val2 = (TMP_Dropdown)((obj2 is TMP_Dropdown) ? obj2 : null);
			TMP_Text captionText = ((val2 != null) ? val2.captionText : null);
			TMP_Text val3 = ((IEnumerable<TMP_Text>)val.GetComponentsInChildren<TMP_Text>(true)).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => (Object)(object)text != (Object)(object)valueLabel && (Object)(object)text != (Object)(object)captionText));
			if ((Object)(object)val3 != (Object)null)
			{
				val3.text = label;
				if ((Object)(object)font != (Object)null)
				{
					val3.font = font;
				}
			}
			component.PopulateOptions(options);
			((UISettingElement<int>)(object)component).SetValueNoNotify(initialValue);
			((UISettingElement<int>)(object)component).DisplayValue(initialValue);
			((UISettingElement<int>)(object)component).OnSettingChanged.AddListener(callback);
		}

		private static void RemoveLocalisation(GameObject gameObject)
		{
			LocalisedTMP[] componentsInChildren = gameObject.GetComponentsInChildren<LocalisedTMP>(true);
			foreach (LocalisedTMP val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val);
				}
			}
		}

		private static void SetTabLabel(GameObject tabObject, string label)
		{
			RemoveLocalisation(tabObject);
			TMP_Text[] componentsInChildren = tabObject.GetComponentsInChildren<TMP_Text>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].text = label;
			}
		}

		private static int FindNearestRecoveryIndex(int value)
		{
			int num = SaveGuardPolicy.ClampRecoveryPercent(value);
			int result = 0;
			int num2 = int.MaxValue;
			for (int i = 0; i < RecoveryOptions.Length; i++)
			{
				int num3 = Math.Abs(RecoveryOptions[i] - num);
				if (num3 < num2)
				{
					num2 = num3;
					result = i;
				}
			}
			return result;
		}

		private static string Localized(string chinese, string english)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			SystemLanguage systemLanguage = Application.systemLanguage;
			if ((int)systemLanguage != 40 && (int)systemLanguage != 41 && (int)systemLanguage != 6)
			{
				return english;
			}
			return chinese;
		}

		private static void SetFullRect(RectTransform rect, Vector2 sizeDelta)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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)
			rect.anchorMin = Vector2.zero;
			rect.anchorMax = Vector2.one;
			rect.offsetMin = Vector2.zero;
			rect.offsetMax = Vector2.zero;
			rect.sizeDelta = sizeDelta;
		}
	}
}
namespace SaveGuard.Patches
{
	[HarmonyPatch]
	internal static class QuotaFailurePatch
	{
		[HarmonyPatch(typeof(GameManager), "OnStartServer")]
		[HarmonyPrefix]
		private static void OnStartServerPrefix()
		{
			FailureContext.Reset();
		}

		[HarmonyPatch(typeof(GameManager), "RestartGame")]
		[HarmonyPrefix]
		private static void RestartGamePrefix(GameManager __instance, bool reachedQuota)
		{
			FailureContext.BeginRestart(reachedQuota);
			if (FailureContext.RestartScopeActive)
			{
				SaveBackupService.TryCreateQuotaFailureBackup(__instance);
			}
		}

		[HarmonyPatch(typeof(GameManager), "RestartGame")]
		[HarmonyPostfix]
		private static void RestartGamePostfix()
		{
			FailureContext.EndRestart();
		}

		[HarmonyPatch(typeof(GameManager), "RestartGame")]
		[HarmonyFinalizer]
		private static Exception RestartGameFinalizer(Exception __exception)
		{
			FailureContext.EndRestart();
			return __exception;
		}

		[HarmonyPatch(typeof(GameManager), "SvResetGameState")]
		[HarmonyPrefix]
		private static bool ResetGameStatePrefix(GameManager __instance, bool reachedQuota)
		{
			if (!SaveGuardPolicy.ShouldUseSoftReset(Plugin.ProtectQuotaFailure.Value, FailureContext.RestartScopeActive, reachedQuota))
			{
				return true;
			}
			if ((Object)(object)DungeonTasks.Instance != (Object)null && (Object)(object)DungeonManager.Instance != (Object)null)
			{
				DungeonTasks.Instance.CreateTasks(DungeonManager.Instance.Generator);
			}
			__instance.NetworkcurrentGameState = (GameState)0;
			__instance.NetworkcurrentRound = 0;
			__instance.NetworktotalScore = 0;
			FailureContext.MarkSoftFailure();
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)"Quota failure converted to a soft reset: save, gold, inventory, quota tier, hub and grimoire were preserved.");
			}
			return false;
		}

		[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
		[HarmonyPrefix]
		private static void ExecuteGameOverPrefix()
		{
			FailureContext.BeginGameOverExecution();
		}

		[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
		[HarmonyPostfix]
		private static void ExecuteGameOverPostfix()
		{
			FailureContext.EndGameOverExecution();
		}

		[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
		[HarmonyFinalizer]
		private static Exception ExecuteGameOverFinalizer(Exception __exception)
		{
			FailureContext.EndGameOverExecution();
			return __exception;
		}

		[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> ExecuteGameOverTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(SaveManager), "DeleteSlot", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(QuotaFailurePatch), "ScopedDeleteSlot", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"Unable to resolve DeleteSlot replacement; quota-failure deletion protection disabled.");
				}
				return instructions;
			}
			List<CodeInstruction> list = new List<CodeInstruction>();
			int num = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, methodInfo))
				{
					list.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo2));
					num++;
				}
				else
				{
					list.Add(instruction);
				}
			}
			if (num != 1)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)$"Expected one SaveManager.DeleteSlot call in SvExecuteGameOver, found {num}. Deletion protection disabled.");
				}
				return instructions;
			}
			return list;
		}

		private static void ScopedDeleteSlot(SaveManager saveManager, int slot)
		{
			if (SaveGuardPolicy.ShouldSuppressGameOverDelete(Plugin.ProtectQuotaFailure.Value, FailureContext.SoftFailureOccurred, FailureContext.GameOverExecutionScope) && slot == saveManager.CurrentSlot)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"Skipped quota-failure deletion of save slot {slot} while preserving the complete Game Over flow.");
				}
			}
			else
			{
				saveManager.DeleteSlot(slot);
			}
		}
	}
	[HarmonyPatch(typeof(LostItemsTracker), "SvRecoverDroppedItemsToLobby")]
	internal static class RecoveryPatch
	{
		private static readonly FieldInfo RecoveryChanceField = AccessTools.Field(typeof(LostItemsTracker), "droppedItemsRecoveryChance");

		private static readonly FieldInfo MaxRecoveryPercentageField = AccessTools.Field(typeof(LostItemsTracker), "maxItemsRecoveryPercentage");

		private static readonly FieldInfo PerPlayerCapField = AccessTools.Field(typeof(LostItemsTracker), "maxItemsToRecoveryPerNonExtractedPlayer");

		[HarmonyPrefix]
		private static void Prefix(LostItemsTracker __instance)
		{
			int num = SaveGuardPolicy.NormalizeRecoveryPercent(Plugin.RecoveryPercent.Value);
			RecoveryChanceField.SetValue(__instance, SaveGuardPolicy.ToRecoveryChance(num));
			if (num == 0)
			{
				MaxRecoveryPercentageField.SetValue(__instance, 0f);
				PerPlayerCapField.SetValue(__instance, 0);
			}
			else
			{
				MaxRecoveryPercentageField.SetValue(__instance, 1f);
				PerPlayerCapField.SetValue(__instance, 1000000);
			}
			Plugin.Debug($"Applying failed-extraction recovery setting: {num}%.");
		}
	}
	[HarmonyPatch(typeof(UISettings), "Initialise")]
	internal static class SettingsUiPatch
	{
		[HarmonyPrefix]
		private static void Prefix(UISettings __instance)
		{
			try
			{
				SettingsUiInjector.TryInject(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Unable to inject the SaveGuard Settings tab: " + ex));
				}
			}
		}
	}
}