Decompiled source of PEAK Quick Resume v0.3.0

PEAKQuickResume.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Zorro.Core;

[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("PEAKQuickResume")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0+5e090d0cade98bc70c00c8010589562ffca17a0f")]
[assembly: AssemblyProduct("PEAKQuickResume")]
[assembly: AssemblyTitle("PEAKQuickResume")]
[assembly: AssemblyVersion("0.3.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 PEAKQuickResume
{
	public class CheckpointInterop
	{
		private readonly ManualLogSource _log;

		private Type _pluginType;

		private FieldInfo _instanceField;

		private FieldInfo _selectedAscentField;

		private FieldInfo _currentlyLoadingField;

		private MethodInfo _preStartSetSegment;

		private MethodInfo _loadPlayerOffline;

		private MethodInfo _loadPlayerCoop;

		private MethodInfo _showMessage;

		private MethodInfo _checkReadyStatus;

		private FieldInfo _readyCheckConfigField;

		private bool _resolved;

		public bool IsAvailable => _resolved;

		public Type CheckpointType => _pluginType;

		private object Instance => _instanceField?.GetValue(null);

		public bool IsCurrentlyLoading
		{
			get
			{
				try
				{
					return _currentlyLoadingField != null && (bool)_currentlyLoadingField.GetValue(Instance);
				}
				catch
				{
					return false;
				}
			}
		}

		public CheckpointInterop(ManualLogSource log)
		{
			_log = log;
		}

		public bool Probe()
		{
			try
			{
				_pluginType = AccessTools.TypeByName("PEAK_Checkpoint_Save.Plugin");
				if (_pluginType == null)
				{
					_log.LogWarning((object)"Checkpoint mod type 'PEAK_Checkpoint_Save.Plugin' not found. Is 'PEAK Checkpoint Save' installed and loaded? Quick Resume will be inert.");
					_resolved = false;
					return false;
				}
				_instanceField = AccessTools.Field(_pluginType, "Instance");
				_selectedAscentField = AccessTools.Field(_pluginType, "selectedAscent");
				_currentlyLoadingField = AccessTools.Field(_pluginType, "currentlyLoading");
				_preStartSetSegment = AccessTools.Method(_pluginType, "PreStartSetSegment", (Type[])null, (Type[])null);
				_loadPlayerOffline = AccessTools.Method(_pluginType, "LoadPlayerOffline", (Type[])null, (Type[])null);
				_loadPlayerCoop = AccessTools.Method(_pluginType, "LoadPlayerCoop", (Type[])null, (Type[])null);
				_showMessage = AccessTools.Method(_pluginType, "ShowMessage", new Type[4]
				{
					typeof(string),
					typeof(Color),
					typeof(float),
					typeof(bool)
				}, (Type[])null);
				_checkReadyStatus = AccessTools.Method(_pluginType, "CheckReadyStatusForPlayers", (Type[])null, (Type[])null);
				_readyCheckConfigField = AccessTools.Field(_pluginType, "configAdvancedEnableClientReadyStatusCheck");
				_log.LogInfo((object)"Checkpoint interop probe:");
				_log.LogInfo((object)("  Instance field ....... " + ((_instanceField != null) ? "OK" : "MISSING")));
				_log.LogInfo((object)("  selectedAscent ....... " + ((_selectedAscentField != null) ? "OK" : "MISSING")));
				_log.LogInfo((object)("  currentlyLoading ..... " + ((_currentlyLoadingField != null) ? "OK" : "MISSING (non-fatal)")));
				_log.LogInfo((object)("  PreStartSetSegment ... " + ((_preStartSetSegment != null) ? "OK" : "MISSING")));
				_log.LogInfo((object)("  LoadPlayerOffline .... " + ((_loadPlayerOffline != null) ? "OK" : "MISSING")));
				_log.LogInfo((object)("  LoadPlayerCoop ....... " + ((_loadPlayerCoop != null) ? "OK" : "MISSING")));
				_log.LogInfo((object)("  ShowMessage .......... " + ((_showMessage != null) ? "OK" : "MISSING (non-fatal, on-screen text only)")));
				_log.LogInfo((object)("  CheckReadyStatus ..... " + ((_checkReadyStatus != null) ? "OK" : "MISSING (non-fatal, coop readiness)")));
				_log.LogInfo((object)("  readyStatusConfig .... " + ((_readyCheckConfigField != null) ? "OK" : "MISSING (non-fatal, coop readiness)")));
				_resolved = _instanceField != null && _selectedAscentField != null && _preStartSetSegment != null && _loadPlayerOffline != null && _loadPlayerCoop != null;
				if (!_resolved)
				{
					_log.LogError((object)"One or more required checkpoint members are missing, the checkpoint mod likely changed. See docs/RESEARCH.md and update CheckpointInterop.cs.");
				}
				return _resolved;
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"Checkpoint interop probe threw: {arg}");
				_resolved = false;
				return false;
			}
		}

		public bool TrySetSelectedAscent(int ascent)
		{
			try
			{
				object instance = Instance;
				if (instance == null)
				{
					_log.LogError((object)"Checkpoint Instance is null (mod not initialized yet?).");
					return false;
				}
				_selectedAscentField.SetValue(instance, ascent);
				return true;
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"TrySetSelectedAscent failed: {arg}");
				return false;
			}
		}

		public bool TryPreStartSetSegment()
		{
			try
			{
				object instance = Instance;
				if (instance == null)
				{
					_log.LogError((object)"Checkpoint Instance is null.");
					return false;
				}
				object obj = _preStartSetSegment.Invoke(instance, null);
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"TryPreStartSetSegment failed: {arg}");
				return false;
			}
		}

		public bool TryLoadPlayer()
		{
			try
			{
				object instance = Instance;
				if (instance == null)
				{
					_log.LogError((object)"Checkpoint Instance is null.");
					return false;
				}
				if (PhotonNetwork.OfflineMode)
				{
					_loadPlayerOffline.Invoke(instance, null);
				}
				else
				{
					_loadPlayerCoop.Invoke(instance, null);
				}
				return true;
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"TryLoadPlayer failed: {arg}");
				return false;
			}
		}

		public bool ReadyCheckEnabled()
		{
			try
			{
				if (_readyCheckConfigField == null)
				{
					return true;
				}
				object value = _readyCheckConfigField.GetValue(Instance);
				if (value == null)
				{
					return true;
				}
				return !(value.GetType().GetProperty("Value")?.GetValue(value) is bool flag) || flag;
			}
			catch
			{
				return true;
			}
		}

		public bool AllClientsReady()
		{
			try
			{
				if (_checkReadyStatus == null)
				{
					return true;
				}
				return !(_checkReadyStatus.Invoke(Instance, null) is bool flag) || flag;
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("AllClientsReady failed (assuming ready): " + ex.Message));
				return true;
			}
		}

		public void TryShowMessage(string text, Color color, float duration = 2.5f)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				object instance = Instance;
				if (instance != null && !(_showMessage == null))
				{
					_showMessage.Invoke(instance, new object[4] { text, color, duration, false });
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("TryShowMessage failed (non-fatal): " + ex.Message));
			}
		}
	}
	internal enum ButtonLabel
	{
		Restart,
		ReturnToAirport
	}
	internal enum ConfirmDialog
	{
		Restart,
		ReturnToAirport
	}
	internal static class PauseMenuLocalization
	{
		private static readonly Dictionary<ButtonLabel, string[]> _table = new Dictionary<ButtonLabel, string[]>
		{
			[ButtonLabel.Restart] = new string[15]
			{
				"RESTART", "REDÉMARRER", "RIAVVIA", "NEUSTART", "REINICIAR", "REINICIAR", "REINICIAR", "ПЕРЕЗАПУСК", "ПЕРЕЗАПУСК", "重新开始",
				"", "リスタート", "재시작", "RESTART", "YENİDEN BAŞLAT"
			},
			[ButtonLabel.ReturnToAirport] = new string[15]
			{
				"RETURN TO AIRPORT", "RETOUR À L'AÉROPORT", "TORNA ALL'AEROPORTO", "ZURÜCK ZUM FLUGHAFEN", "VOLVER AL AEROPUERTO", "VOLVER AL AEROPUERTO", "VOLTAR AO AEROPORTO", "ВЕРНУТЬСЯ В АЭРОПОРТ", "ПОВЕРНУТИСЯ В АЕРОПОРТ", "返回机场",
				"", "空港に戻る", "공항으로 돌아가기", "WRÓĆ NA LOTNISKO", "HAVAALANINA DÖN"
			}
		};

		private static readonly Dictionary<ConfirmDialog, string[]> _dialogTable = new Dictionary<ConfirmDialog, string[]>
		{
			[ConfirmDialog.Restart] = new string[15]
			{
				"Restart this run? Everyone will return to the Airport and a fresh run of the same difficulty will start immediately (no checkpoint will be loaded).", "Recommencer cette partie ? Tout le monde retournera à l'aéroport et une nouvelle partie de la même difficulté commencera immédiatement (aucune sauvegarde ne sera chargée).", "Riavviare questa partita? Tutti torneranno all'aeroporto e una nuova partita della stessa difficoltà inizierà immediatamente (nessun checkpoint verrà caricato).", "Diesen Lauf neu starten? Alle kehren zum Flughafen zurück und ein neuer Lauf mit demselben Schwierigkeitsgrad beginnt sofort (es wird kein Speicherstand geladen).", "¿Reiniciar esta partida? Todos volverán al aeropuerto y comenzará de inmediato una nueva partida de la misma dificultad (no se cargará ningún punto de guardado).", "¿Reiniciar esta partida? Todos volverán al aeropuerto y comenzará de inmediato una nueva partida de la misma dificultad (no se cargará ningún punto de guardado).", "Reiniciar esta corrida? Todos vão voltar para o Aeroporto e uma nova corrida da mesma dificuldade vai começar imediatamente (nenhum checkpoint será carregado).", "Перезапустить этот забег? Все вернутся в аэропорт, и сразу начнётся новый забег той же сложности (сохранение не будет загружено).", "Перезапустити цей забіг? Усі повернуться в аеропорт, і одразу почнеться новий забіг тієї ж складності (збереження не буде завантажено).", "重新开始本局游戏?所有人将返回机场,并立即开始相同难度的新一局(不会加载任何存档)。",
				"", "このランを再開始しますか?全員が空港に戻り、同じ難易度の新しいランがすぐに始まります(チェックポイントは読み込まれません)。", "이번 런을 재시작하시겠습니까? 모두 공항으로 돌아가고 동일한 난이도의 새로운 런이 즉시 시작됩니다 (체크포인트는 불러오지 않습니다).", "Zrestartować ten przebieg? Wszyscy wrócą na lotnisko i natychmiast rozpocznie się nowy przebieg tej samej trudności (żaden zapis nie zostanie wczytany).", "Bu koşuyu yeniden başlat? Herkes Havaalanı'na dönecek ve aynı zorlukta yeni bir koşu hemen başlayacak (herhangi bir kayıt yüklenmeyecek)."
			},
			[ConfirmDialog.ReturnToAirport] = new string[15]
			{
				"Return everyone to the Airport now?", "Renvoyer tout le monde à l'aéroport maintenant ?", "Riportare tutti all'aeroporto ora?", "Jetzt alle zum Flughafen zurückbringen?", "¿Volver todos al aeropuerto ahora?", "¿Volver todos al aeropuerto ahora?", "Voltar todos para o Aeroporto agora?", "Вернуть всех в аэропорт сейчас?", "Повернути всіх в аеропорт зараз?", "现在让所有人返回机场?",
				"", "今すぐ全員を空港に戻しますか?", "지금 모두를 공항으로 돌려보내시겠습니까?", "Odesłać teraz wszystkich na lotnisko?", "Herkes şimdi Havaalanı'na döndürülsün mü?"
			}
		};

		public static string Get(ButtonLabel label)
		{
			return Resolve(_table[label]);
		}

		public static string Get(ConfirmDialog dialog)
		{
			return Resolve(_dialogTable[dialog]);
		}

		private static string Resolve(string[] arr)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected I4, but got Unknown
			int num = (int)LocalizedText.CURRENT_LANGUAGE;
			if (num >= 0 && num < arr.Length && !string.IsNullOrEmpty(arr[num]))
			{
				return arr[num];
			}
			return arr[0];
		}
	}
	public static class PauseMenuPatch
	{
		private class ButtonEntry
		{
			public GameObject GameObject;

			public TextMeshProUGUI Text;

			public Func<string> Localize;
		}

		private class Buttons
		{
			public ButtonEntry Restart;

			public ButtonEntry ReturnToAirport;

			public ButtonEntry OpenKiosk;
		}

		private static ManualLogSource _log;

		private static PluginConfig _cfg;

		private static MethodInfo _windowOpen;

		private static MethodInfo _windowClose;

		private static FieldInfo _templateButtonField;

		private static FieldInfo _quitButtonField;

		private static FieldInfo _confirmOkField;

		private static FieldInfo _confirmCancelField;

		private static readonly ConditionalWeakTable<object, Buttons> _built = new ConditionalWeakTable<object, Buttons>();

		public static void Apply(Harmony harmony, PluginConfig cfg, ManualLogSource log)
		{
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Expected O, but got Unknown
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Expected O, but got Unknown
			_log = log;
			_cfg = cfg;
			try
			{
				_windowOpen = AccessTools.Method(typeof(MenuWindow), "Open", (Type[])null, (Type[])null);
				_windowClose = AccessTools.Method(typeof(MenuWindow), "Close", (Type[])null, (Type[])null);
				_templateButtonField = AccessTools.Field(typeof(PauseMenuMainPage), "m_accoladesButton");
				_quitButtonField = AccessTools.Field(typeof(PauseMenuMainPage), "m_quitButton");
				_confirmOkField = AccessTools.Field(typeof(PauseMenuMainPage), "m_confirmOkButton");
				_confirmCancelField = AccessTools.Field(typeof(PauseMenuMainPage), "m_confirmCancelButton");
				if (_windowOpen == null || _windowClose == null || _templateButtonField == null || _quitButtonField == null || _confirmOkField == null || _confirmCancelField == null)
				{
					log.LogWarning((object)"PauseMenuPatch: one or more pause menu members not found; Restart / Return to Airport / Board Flight buttons will not be added. The pause menu itself is unaffected.");
					return;
				}
				MethodInfo methodInfo = AccessTools.Method(typeof(PauseMenuMainPage), "Start", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(PauseMenuMainPage), "OnEnable", (Type[])null, (Type[])null);
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PauseMenuPatch), "StartPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(PauseMenuPatch), "OnEnablePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				log.LogInfo((object)"PauseMenuPatch: patched PauseMenuMainPage.Start/OnEnable (Restart / Return to Airport / Board Flight buttons).");
			}
			catch (Exception arg)
			{
				log.LogError((object)$"PauseMenuPatch.Apply failed (non-fatal, pause menu unaffected): {arg}");
			}
		}

		private static void StartPostfix(PauseMenuMainPage __instance)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Expected O, but got Unknown
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Expected O, but got Unknown
			try
			{
				if (_built.TryGetValue(__instance, out var _))
				{
					return;
				}
				Button val = (Button)_templateButtonField.GetValue(__instance);
				Button val2 = (Button)_quitButtonField.GetValue(__instance);
				if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
				{
					_log.LogWarning((object)"PauseMenuPatch: template/anchor button missing on this instance; skipping.");
					return;
				}
				Transform parent = ((Component)val).transform.parent;
				int siblingIndex = ((Component)val2).transform.GetSiblingIndex();
				Buttons buttons = new Buttons
				{
					Restart = MakeButton(val, parent, siblingIndex++, (Func<string>)(() => PauseMenuLocalization.Get(ButtonLabel.Restart)), (UnityAction)delegate
					{
						OnRestartClicked(__instance);
					}, (Color?)new Color(0.8f, 0.2f, 0.15f)),
					ReturnToAirport = MakeButton(val, parent, siblingIndex++, (Func<string>)(() => PauseMenuLocalization.Get(ButtonLabel.ReturnToAirport)), (UnityAction)delegate
					{
						OnReturnToAirportClicked(__instance);
					}, (Color?)new Color(0.12f, 0.55f, 0.58f)),
					OpenKiosk = MakeButton(val, parent, siblingIndex++, () => LocalizedText.GetText("BOARDFLIGHT", true).ToUpperInvariant(), (UnityAction)delegate
					{
						OnOpenKioskClicked(__instance);
					})
				};
				_built.Add(__instance, buttons);
				RectTransform val3 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
				if (val3 != null)
				{
					LayoutRebuilder.ForceRebuildLayoutImmediate(val3);
				}
				UpdateVisibility(buttons);
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"PauseMenuPatch.StartPostfix failed (non-fatal): {arg}");
			}
		}

		private static void OnEnablePostfix(PauseMenuMainPage __instance)
		{
			try
			{
				if (_built.TryGetValue(__instance, out var value))
				{
					UpdateVisibility(value);
				}
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"PauseMenuPatch.OnEnablePostfix failed (non-fatal): {arg}");
			}
		}

		private static void UpdateVisibility(Buttons b)
		{
			bool flag = RunLauncher.IsHost && RunLauncher.InLevel;
			SetActiveAndRefresh(b.Restart, flag && _cfg.ShowRestartButton.Value);
			SetActiveAndRefresh(b.ReturnToAirport, flag && _cfg.ShowReturnToAirportButton.Value);
			SetActiveAndRefresh(b.OpenKiosk, RunLauncher.InAirport && _cfg.ShowBoardFlightButton.Value);
		}

		private static void SetActiveAndRefresh(ButtonEntry entry, bool active)
		{
			entry.GameObject.SetActive(active);
			if (active && (Object)(object)entry.Text != (Object)null)
			{
				((TMP_Text)entry.Text).text = entry.Localize();
			}
		}

		private static ButtonEntry MakeButton(Button template, Transform parent, int siblingIndex, Func<string> localize, UnityAction onClick, Color? bannerColor = null)
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			string text = localize();
			GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, parent);
			((Object)val).name = "PEAKQuickResume_" + text.Replace(" ", "");
			val.transform.SetSiblingIndex(siblingIndex);
			Button component = val.GetComponent<Button>();
			((UnityEventBase)component.onClick).RemoveAllListeners();
			((UnityEvent)component.onClick).AddListener(onClick);
			if (bannerColor.HasValue)
			{
				Image fill = default(Image);
				ref Image reference = ref fill;
				Graphic targetGraphic = ((Selectable)component).targetGraphic;
				reference = (Image)(((object)((targetGraphic is Image) ? targetGraphic : null)) ?? ((object)val.GetComponentInChildren<Image>(true)));
				if ((Object)(object)fill != (Object)null)
				{
					Color color = ((Graphic)fill).color;
					List<Image> list = (from i in val.GetComponentsInChildren<Image>(true)
						where (Object)(object)i != (Object)(object)fill
						select i).ToList();
					List<Color> list2 = list.Select((Image i) => ((Graphic)i).color).ToList();
					((Graphic)fill).color = bannerColor.Value;
					for (int num = 0; num < list.Count; num++)
					{
						Color val2 = list2[num];
						float num2 = ((color.r > 0.001f) ? (val2.r / color.r) : 1f);
						float num3 = ((color.g > 0.001f) ? (val2.g / color.g) : 1f);
						float num4 = ((color.b > 0.001f) ? (val2.b / color.b) : 1f);
						((Graphic)list[num]).color = new Color(Mathf.Clamp01(bannerColor.Value.r * num2), Mathf.Clamp01(bannerColor.Value.g * num3), Mathf.Clamp01(bannerColor.Value.b * num4), val2.a);
					}
				}
			}
			LocalizedText componentInChildren = val.GetComponentInChildren<LocalizedText>(true);
			TextMeshProUGUI val3;
			if ((Object)(object)componentInChildren != (Object)null)
			{
				TMP_Text tmp = componentInChildren.tmp;
				val3 = (TextMeshProUGUI)(((object)((tmp is TextMeshProUGUI) ? tmp : null)) ?? ((object)val.GetComponentInChildren<TextMeshProUGUI>(true)));
				componentInChildren.SetText(text);
				((Behaviour)componentInChildren).enabled = false;
			}
			else
			{
				val3 = val.GetComponentInChildren<TextMeshProUGUI>(true);
				if ((Object)(object)val3 != (Object)null)
				{
					((TMP_Text)val3).text = text;
				}
			}
			return new ButtonEntry
			{
				GameObject = val,
				Text = val3,
				Localize = localize
			};
		}

		private static void OnRestartClicked(PauseMenuMainPage instance)
		{
			if (RunLauncher.IsHost)
			{
				OpenConfirm(instance, PauseMenuLocalization.Get(ConfirmDialog.Restart), delegate
				{
					ClosePauseMenu(instance);
					Plugin.Instance?.RequestRestart();
				});
			}
		}

		private static void OnReturnToAirportClicked(PauseMenuMainPage instance)
		{
			if (RunLauncher.IsHost)
			{
				OpenConfirm(instance, PauseMenuLocalization.Get(ConfirmDialog.ReturnToAirport), delegate
				{
					ClosePauseMenu(instance);
					Plugin.Instance?.RequestReturnToAirport();
				});
			}
		}

		private static void OnOpenKioskClicked(PauseMenuMainPage instance)
		{
			ClosePauseMenu(instance);
			Plugin.Instance?.RequestOpenGateKiosk();
		}

		private static void OpenConfirm(PauseMenuMainPage instance, string text, Action onConfirm)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			try
			{
				object confirmWindow = instance.confirmWindow;
				_windowOpen.Invoke(confirmWindow, null);
				instance.confirmText.SetText(text);
				Button val = (Button)_confirmOkField.GetValue(instance);
				Button val2 = (Button)_confirmCancelField.GetValue(instance);
				((UnityEventBase)val.onClick).RemoveAllListeners();
				((UnityEvent)val.onClick).AddListener((UnityAction)delegate
				{
					_windowClose.Invoke(confirmWindow, null);
					onConfirm();
				});
				((Selectable)val2).Select();
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"PauseMenuPatch.OpenConfirm failed (non-fatal): {arg}");
			}
		}

		private static void ClosePauseMenu(PauseMenuMainPage instance)
		{
			try
			{
				PauseMenuHandler componentInParent = ((Component)instance).GetComponentInParent<PauseMenuHandler>();
				if ((Object)(object)componentInParent != (Object)null)
				{
					((Component)componentInParent).gameObject.SetActive(false);
				}
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"PauseMenuPatch.ClosePauseMenu failed (non-fatal): {arg}");
			}
		}
	}
	[BepInPlugin("OnlyCook.PEAKQuickResume", "PEAK Quick Resume", "0.3.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private PluginConfig _cfg;

		private CheckpointInterop _checkpoint;

		private ResumeOrchestrator _orchestrator;

		private RestartOrchestrator _restart;

		private SavePicker _picker;

		internal static Plugin Instance { get; private set; }

		internal string ResumeKeyText
		{
			get
			{
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				if (_cfg == null)
				{
					return "F7";
				}
				return ((object)_cfg.ResumeKey.Value/*cast due to .constrained prefix*/).ToString();
			}
		}

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			_cfg = new PluginConfig(((BaseUnityPlugin)this).Config);
			_checkpoint = new CheckpointInterop(((BaseUnityPlugin)this).Logger);
			_checkpoint.Probe();
			Harmony harmony = new Harmony("OnlyCook.PEAKQuickResume");
			if (_checkpoint.CheckpointType != null)
			{
				TutorialPatch.Apply(harmony, _checkpoint.CheckpointType, ((BaseUnityPlugin)this).Logger);
				SavePatch.Apply(harmony, _checkpoint.CheckpointType, ((BaseUnityPlugin)this).Logger);
			}
			PauseMenuPatch.Apply(harmony, _cfg, ((BaseUnityPlugin)this).Logger);
			GameObject val = new GameObject("PEAKQuickResume.Orchestrator");
			Object.DontDestroyOnLoad((Object)(object)val);
			((Object)val).hideFlags = (HideFlags)61;
			_orchestrator = val.AddComponent<ResumeOrchestrator>();
			_orchestrator.Init(((BaseUnityPlugin)this).Logger, _cfg, _checkpoint);
			_restart = val.AddComponent<RestartOrchestrator>();
			_restart.Init(((BaseUnityPlugin)this).Logger, _cfg, _checkpoint);
			_picker = val.AddComponent<SavePicker>();
			_picker.Init(((BaseUnityPlugin)this).Logger, _cfg);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("PEAK Quick Resume 0.3.0 loaded. " + $"Resume key: {_cfg.ResumeKey.Value}. Checkpoint interop: " + (_checkpoint.IsAvailable ? "READY" : "UNAVAILABLE")));
		}

		private void Update()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (_cfg == null)
			{
				return;
			}
			if ((Object)(object)_picker != (Object)null && _picker.IsOpen && (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271)))
			{
				ConfirmLoad();
				return;
			}
			KeyboardShortcut value = _cfg.ResumeKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				OnResumeKey();
			}
		}

		private void OnResumeKey()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_picker != (Object)null && _picker.IsOpen)
			{
				ConfirmLoad();
				return;
			}
			if (RunLauncher.InTitle)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Resume key ignored on the Title screen.");
				return;
			}
			if (!RunLauncher.IsHost)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Resume key ignored: only the host can resume.");
				_checkpoint.TryShowMessage("Only the host can resume the save!", new Color(1f, 0.5f, 0.5f, 1f), 3f);
				return;
			}
			if (_orchestrator.IsRunning)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Resume key ignored: a resume is already in progress.");
				return;
			}
			if (RunLauncher.InLevel && !PlayerIsDead() && !_cfg.AllowMidGame.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Mid-game resume is disabled (allowMidGame=false).");
				return;
			}
			bool flag;
			try
			{
				flag = PhotonNetwork.OfflineMode;
			}
			catch
			{
				flag = true;
			}
			SaveTarget? preferred = null;
			if (!RunLauncher.InAirport)
			{
				try
				{
					preferred = (RunLauncher.IsCustomRun ? SaveTarget.Custom() : SaveTarget.Normal(Ascents.currentAscent));
				}
				catch
				{
					preferred = null;
				}
			}
			if (!_picker.Open(flag, preferred))
			{
				_checkpoint.TryShowMessage("No " + (flag ? "solo" : "co-op") + " saves found yet.", new Color(1f, 0.5f, 0.5f, 1f), 3f);
			}
		}

		private void ConfirmLoad()
		{
			ArchivedSave selected = _picker.Selected;
			_picker.Close();
			if (selected != null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Resume confirmed: loading {selected.DifficultyLabel} save from {selected.SortTime:u}.");
				_orchestrator.RequestResume(selected);
			}
		}

		internal void RequestRestart()
		{
			_restart?.RequestRestart();
		}

		internal void RequestReturnToAirport()
		{
			if (!RunLauncher.IsHost)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Return to Airport ignored: only the host can do this.");
			}
			else
			{
				RunLauncher.ReturnToAirport(((BaseUnityPlugin)this).Logger);
			}
		}

		internal void RequestOpenGateKiosk()
		{
			RunLauncher.OpenGateKiosk(((BaseUnityPlugin)this).Logger);
		}

		private static bool PlayerIsDead()
		{
			try
			{
				Character localCharacter = Character.localCharacter;
				return (Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead || localCharacter.data.fullyPassedOut;
			}
			catch
			{
				return false;
			}
		}
	}
	public class PluginConfig
	{
		public readonly ConfigEntry<KeyboardShortcut> ResumeKey;

		public readonly ConfigEntry<bool> RequireDoublePress;

		public readonly ConfigEntry<float> DoublePressWindow;

		public readonly ConfigEntry<bool> AllowMidGame;

		public readonly ConfigEntry<bool> EnableDebugLogging;

		public readonly ConfigEntry<bool> ShowRestartButton;

		public readonly ConfigEntry<bool> ShowReturnToAirportButton;

		public readonly ConfigEntry<bool> ShowBoardFlightButton;

		public readonly ConfigEntry<float> SettleAfterAirport;

		public readonly ConfigEntry<float> SettleAfterLevel;

		public readonly ConfigEntry<float> StepTimeout;

		public readonly ConfigEntry<float> CoopAirportSettle;

		public PluginConfig(ConfigFile cfg)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			ResumeKey = cfg.Bind<KeyboardShortcut>("General", "resumeKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Opens the save picker (a menu of your checkpoints for the current solo/co-op category). Use the arrow keys to choose, then press this key again (or Enter) to load. The newest save is preselected, so pressing it twice loads your latest checkpoint. Delete removes the highlighted save; Escape closes the menu.");
			RequireDoublePress = cfg.Bind<bool>("General", "requireDoublePress_DEPRECATED", true, "[DEPRECATED] No longer used: the F7 save picker now provides the confirmation step (open, then press again to load). Kept only so old config files don't error.");
			DoublePressWindow = cfg.Bind<float>("General", "doublePressWindow_DEPRECATED", 5f, "[DEPRECATED] No longer used (superseded by the F7 save picker). Kept for config compatibility.");
			AllowMidGame = cfg.Bind<bool>("General", "allowMidGame", true, "If enabled, the resume key also works while you are alive in a level (returns to the Airport, starts a fresh run and loads the save). If disabled, it only works after death / at the Airport.");
			EnableDebugLogging = cfg.Bind<bool>("Debug", "enableDebugLogging", true, "Verbose logging of every step of the resume sequence. Very useful while the mod is young, please keep this on when reporting issues.");
			ShowRestartButton = cfg.Bind<bool>("PauseMenu", "showRestartButton", true, "Show the \"Restart\" button in the pause menu while mid-run (host only). Instantly returns everyone to the Airport and starts a fresh run of the same difficulty, no checkpoint is loaded.");
			ShowReturnToAirportButton = cfg.Bind<bool>("PauseMenu", "showReturnToAirportButton", true, "Show the \"Return to Airport\" button in the pause menu while mid-run (host only). Sends everyone back to the Airport without starting a new run.");
			ShowBoardFlightButton = cfg.Bind<bool>("PauseMenu", "showBoardFlightButton", true, "Show the \"Board Flight\" button in the pause menu while at the Airport (any player). Opens the gate-kiosk UI directly, skipping the walk over to it.");
			SettleAfterAirport = cfg.Bind<float>("Timing", "settleAfterAirport", 0.75f, "Seconds to wait after the Airport scene loads before starting the new run (advanced).");
			SettleAfterLevel = cfg.Bind<float>("Timing", "settleAfterLevel", 1.5f, "Seconds to wait after the level scene loads (and the local character exists) before triggering the checkpoint load (advanced).");
			StepTimeout = cfg.Bind<float>("Timing", "stepTimeout", 30f, "Max seconds to wait for each stage (Airport load, kiosk, level load, character spawn) before aborting the resume sequence (advanced).");
			CoopAirportSettle = cfg.Bind<float>("Timing", "coopAirportSettle", 2f, "COOP ONLY: extra seconds to wait at the Airport before starting the fresh run, so other players have finished loading the Airport and will receive the run-start (advanced). Raise this if a client occasionally gets left behind on a slow connection.");
		}
	}
	public static class PluginInfo
	{
		public const string Guid = "OnlyCook.PEAKQuickResume";

		public const string Name = "PEAK Quick Resume";

		public const string Version = "0.3.0";

		public const string CheckpointSaveGuid = "PEAK_Checkpoint_Save";

		public const string CheckpointSaveTypeName = "PEAK_Checkpoint_Save.Plugin";
	}
	public class RestartOrchestrator : MonoBehaviour
	{
		private ManualLogSource _log;

		private PluginConfig _cfg;

		private CheckpointInterop _checkpoint;

		private bool _running;

		private bool _lastWaitOk;

		private static readonly Color MsgInfo = new Color(0.6f, 0.8f, 1f, 1f);

		private static readonly Color MsgSuccess = new Color(0.5f, 1f, 0.5f, 1f);

		private static readonly Color MsgError = new Color(1f, 0.5f, 0.5f, 1f);

		public bool IsRunning => _running;

		public void Init(ManualLogSource log, PluginConfig cfg, CheckpointInterop checkpoint)
		{
			_log = log;
			_cfg = cfg;
			_checkpoint = checkpoint;
		}

		public void RequestRestart()
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (_running)
			{
				_log.LogInfo((object)"Restart already in progress; ignoring request.");
				return;
			}
			if (!RunLauncher.IsHost)
			{
				_log.LogWarning((object)"Cannot restart: only the host / offline player can start a new run.");
				Msg("Only the host can restart the run!", MsgError);
				return;
			}
			if (!RunLauncher.InLevel)
			{
				_log.LogWarning((object)("Restart requested outside a level (scene='" + RunLauncher.ActiveSceneName + "'); ignoring."));
				return;
			}
			int ascent;
			try
			{
				ascent = Ascents.currentAscent;
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"Could not read Ascents.currentAscent: {arg}");
				ascent = 0;
			}
			bool isCustomRun = RunLauncher.IsCustomRun;
			((MonoBehaviour)this).StartCoroutine(RestartRoutine(ascent, isCustomRun));
		}

		private IEnumerator RestartRoutine(int ascent, bool custom)
		{
			_running = true;
			float timeout = Mathf.Max(1f, _cfg.StepTimeout.Value);
			_log.LogInfo((object)$"=== Restart: sequence START (ascent={ascent}, custom={custom}) ===");
			Msg("Restarting run...", MsgInfo);
			if (!RunLauncher.IsLoading && !RunLauncher.ReturnToAirport(_log))
			{
				Fail("ReturnToAirport failed");
				yield break;
			}
			yield return WaitFor(() => RunLauncher.InAirport, timeout, "Airport scene");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the Airport scene");
				yield break;
			}
			yield return WaitFor(() => !RunLauncher.IsLoading, timeout, "airport loading to finish");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the airport loading screen to clear");
				yield break;
			}
			yield return (object)new WaitForSeconds(Mathf.Max(0f, _cfg.SettleAfterAirport.Value));
			yield return WaitFor(() => (Object)(object)Object.FindObjectOfType<AirportCheckInKiosk>() != (Object)null, timeout, "AirportCheckInKiosk");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the check-in kiosk");
				yield break;
			}
			if (!RunLauncher.TrySetCustomRun(custom, _log))
			{
				Fail("Could not set custom-run flag before starting");
				yield break;
			}
			if (!PhotonNetwork.OfflineMode)
			{
				float num = Mathf.Max(0f, _cfg.CoopAirportSettle.Value);
				if (num > 0f)
				{
					_log.LogInfo((object)$"[stage] Coop: waiting {num:F1}s for other players to reach the Airport.");
					yield return (object)new WaitForSeconds(num);
				}
			}
			yield return WaitFor(() => !RunLauncher.IsLoading, timeout, "loading to finish before StartRun");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for loading to clear before StartRun");
				yield break;
			}
			if (!RunLauncher.StartRun(ascent, _log))
			{
				Fail("StartRun failed");
				yield break;
			}
			_log.LogInfo((object)"=== Restart: sequence COMPLETE (fresh run started) ===");
			Msg("Run restarted!", MsgSuccess);
			_running = false;
		}

		private void Msg(string text, Color color)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			_checkpoint?.TryShowMessage(text, color, 4f);
		}

		private IEnumerator WaitFor(Func<bool> condition, float timeoutSeconds, string what)
		{
			float elapsed = 0f;
			while (elapsed < timeoutSeconds)
			{
				bool flag;
				try
				{
					flag = condition();
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"WaitFor({what}) predicate threw: {arg}");
					flag = false;
				}
				if (flag)
				{
					_lastWaitOk = true;
					yield break;
				}
				elapsed += Time.deltaTime;
				yield return null;
			}
			_log.LogWarning((object)$"WaitFor({what}) timed out after {timeoutSeconds:F1}s.");
			_lastWaitOk = false;
		}

		private void Fail(string reason)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			_log.LogError((object)("Restart aborted: " + reason + "."));
			Msg("Restart failed, see the log for details.", MsgError);
			_running = false;
		}
	}
	public class ResumeOrchestrator : MonoBehaviour
	{
		private ManualLogSource _log;

		private PluginConfig _cfg;

		private CheckpointInterop _checkpoint;

		private bool _running;

		private bool _lastWaitOk;

		private ArchivedSave _chosen;

		private static readonly Color MsgInfo = new Color(0.6f, 0.8f, 1f, 1f);

		private static readonly Color MsgSuccess = new Color(0.5f, 1f, 0.5f, 1f);

		private static readonly Color MsgError = new Color(1f, 0.5f, 0.5f, 1f);

		public bool IsRunning => _running;

		public void Init(ManualLogSource log, PluginConfig cfg, CheckpointInterop checkpoint)
		{
			_log = log;
			_cfg = cfg;
			_checkpoint = checkpoint;
		}

		public void RequestResume()
		{
			RequestResume(null);
		}

		public void RequestResume(ArchivedSave chosen)
		{
			//IL_005b: 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)
			if (_running)
			{
				_log.LogInfo((object)"Resume already in progress; ignoring request.");
				return;
			}
			_chosen = chosen;
			if (!_checkpoint.IsAvailable)
			{
				_log.LogError((object)"Cannot resume: checkpoint mod interop is not available.");
			}
			else if (!RunLauncher.IsHost)
			{
				_log.LogWarning((object)"Cannot resume: only the host / offline player can start and load a run.");
				Msg("Only the host can resume the save!", MsgError);
			}
			else if (RunLauncher.InTitle)
			{
				_log.LogWarning((object)"Cannot resume from the Title screen. Load into the game first.");
				Msg("Load into the game first.", MsgError);
			}
			else
			{
				((MonoBehaviour)this).StartCoroutine(ResumeRoutine());
			}
		}

		private IEnumerator ResumeRoutine()
		{
			_running = true;
			float timeout = Mathf.Max(1f, _cfg.StepTimeout.Value);
			_log.LogInfo((object)"=== Quick Resume: sequence START ===");
			Msg("Quick Resume: starting...", MsgInfo);
			SaveTarget target = ((_chosen != null) ? _chosen.Target : ResolveTarget());
			int ascent = target.Ascent;
			_log.LogInfo((object)($"[stage] Target={target} (ascent={ascent}, custom={target.IsCustom}, " + $"chosen={_chosen != null}). Starting scene='{RunLauncher.ActiveSceneName}'."));
			if (!RunLauncher.InAirport)
			{
				_log.LogInfo((object)"[stage] Not at Airport; requesting return to Airport.");
				if (!RunLauncher.IsLoading)
				{
					if (!RunLauncher.ReturnToAirport(_log))
					{
						Fail("ReturnToAirport failed");
						yield break;
					}
				}
				else
				{
					_log.LogInfo((object)"[stage] A load is already in progress; waiting for the Airport instead of forcing a return.");
				}
				yield return WaitFor(() => RunLauncher.InAirport, timeout, "Airport scene");
				if (!_lastWaitOk)
				{
					Fail("Timed out waiting for the Airport scene");
					yield break;
				}
			}
			_log.LogInfo((object)"[stage] At Airport.");
			yield return WaitFor(() => !RunLauncher.IsLoading, timeout, "airport loading to finish");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the airport loading screen to clear");
				yield break;
			}
			yield return (object)new WaitForSeconds(Mathf.Max(0f, _cfg.SettleAfterAirport.Value));
			yield return WaitFor(() => (Object)(object)Object.FindObjectOfType<AirportCheckInKiosk>() != (Object)null, timeout, "AirportCheckInKiosk");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the check-in kiosk");
				yield break;
			}
			_log.LogInfo((object)"[stage] Found check-in kiosk.");
			if (_chosen != null && !SaveArchive.Restore(_chosen, _log))
			{
				Fail("Could not restore the chosen save over the checkpoint file");
				yield break;
			}
			if (!RunLauncher.TrySetCustomRun(target.IsCustom, _log))
			{
				Fail("Could not set custom-run flag before starting");
				yield break;
			}
			if (!_checkpoint.TrySetSelectedAscent(ascent))
			{
				Fail("Could not set selected ascent on checkpoint mod");
				yield break;
			}
			if (!_checkpoint.TryPreStartSetSegment())
			{
				Fail($"No checkpoint save found for {target} (PreStartSetSegment returned false)");
				Msg("No save found for this " + (target.IsCustom ? "custom run" : $"difficulty (ascent {ascent})") + ".", MsgError);
				yield break;
			}
			_log.LogInfo((object)"[stage] Save confirmed for this difficulty; starting fresh run.");
			Msg("Starting a fresh run of your saved difficulty...", MsgInfo);
			if (!PhotonNetwork.OfflineMode)
			{
				float num = Mathf.Max(0f, _cfg.CoopAirportSettle.Value);
				if (num > 0f)
				{
					_log.LogInfo((object)$"[stage] Coop: waiting {num:F1}s for other players to reach the Airport.");
					yield return (object)new WaitForSeconds(num);
				}
			}
			yield return WaitFor(() => !RunLauncher.IsLoading, timeout, "loading to finish before StartRun");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for loading to clear before StartRun");
				yield break;
			}
			if (!RunLauncher.StartRun(ascent, _log))
			{
				Fail("StartRun failed");
				yield break;
			}
			_log.LogInfo((object)"[stage] StartRun invoked; waiting for the level to load.");
			yield return WaitFor(() => !RunLauncher.InAirport, timeout, "leaving the Airport");
			if (!_lastWaitOk)
			{
				Fail("Run did not start (still at the Airport after StartRun)");
				yield break;
			}
			yield return WaitFor(() => RunLauncher.InLevel, timeout, "level scene");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the level scene to load");
				yield break;
			}
			_log.LogInfo((object)("[stage] Level scene loaded: '" + RunLauncher.ActiveSceneName + "'."));
			yield return WaitFor(() => !RunLauncher.IsLoading, timeout, "level loading to finish");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the level loading screen to clear");
				yield break;
			}
			yield return WaitFor(() => LocalCharacterExists(), timeout, "local character");
			if (!_lastWaitOk)
			{
				Fail("Timed out waiting for the local character to spawn");
				yield break;
			}
			_log.LogInfo((object)"[stage] Local character present.");
			yield return (object)new WaitForSeconds(Mathf.Max(0f, _cfg.SettleAfterLevel.Value));
			if (!PhotonNetwork.OfflineMode && _checkpoint.ReadyCheckEnabled())
			{
				_log.LogInfo((object)"[stage] Coop: waiting for all clients to report ready...");
				Msg("Waiting for other players to load...", MsgInfo);
				yield return WaitFor(() => _checkpoint.AllClientsReady(), timeout, "all clients ready");
				if (!_lastWaitOk)
				{
					Fail("Timed out waiting for all clients to be ready (some players may still be loading)");
					Msg("Some players didn't finish loading in time. Try again.", MsgError);
					yield break;
				}
				_log.LogInfo((object)"[stage] Coop: all clients ready.");
			}
			float guard = 0f;
			while (_checkpoint.IsCurrentlyLoading && guard < timeout)
			{
				guard += Time.deltaTime;
				yield return null;
			}
			_log.LogInfo((object)"[stage] Triggering checkpoint restore.");
			if (!_checkpoint.TryLoadPlayer())
			{
				Fail("Checkpoint load call failed");
				yield break;
			}
			_log.LogInfo((object)"=== Quick Resume: sequence COMPLETE (checkpoint load invoked) ===");
			Msg("Save loaded. Welcome back!", MsgSuccess);
			_chosen = null;
			_running = false;
		}

		private void Msg(string text, Color color)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			_checkpoint?.TryShowMessage(text, color, 4f);
		}

		private SaveTarget ResolveTarget()
		{
			int num;
			try
			{
				num = Ascents.currentAscent;
			}
			catch (Exception arg)
			{
				_log.LogError((object)$"Could not read Ascents.currentAscent: {arg}");
				num = 0;
			}
			if (!RunLauncher.InAirport)
			{
				if (RunLauncher.IsCustomRun)
				{
					_log.LogInfo((object)"[stage] In a custom run: resuming the custom-run save.");
					return SaveTarget.Custom();
				}
				_log.LogInfo((object)$"[stage] In a run: resuming current difficulty (ascent {num}).");
				return SaveTarget.Normal(num);
			}
			bool offlineMode;
			try
			{
				offlineMode = PhotonNetwork.OfflineMode;
			}
			catch
			{
				offlineMode = true;
			}
			if (SaveDiscovery.TryGetLatestSave(_log, offlineMode, out var target))
			{
				_log.LogInfo((object)$"[stage] At Airport: using latest save on disk ({target}).");
				return target;
			}
			_log.LogWarning((object)$"[stage] At Airport: no saves found on disk; falling back to currentAscent ({num}).");
			return SaveTarget.Normal(num);
		}

		private static bool LocalCharacterExists()
		{
			try
			{
				return (Object)(object)Character.localCharacter != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private IEnumerator WaitFor(Func<bool> condition, float timeoutSeconds, string what)
		{
			float elapsed = 0f;
			while (elapsed < timeoutSeconds)
			{
				bool flag;
				try
				{
					flag = condition();
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"WaitFor({what}) predicate threw: {arg}");
					flag = false;
				}
				if (flag)
				{
					_lastWaitOk = true;
					yield break;
				}
				elapsed += Time.deltaTime;
				yield return null;
			}
			_log.LogWarning((object)$"WaitFor({what}) timed out after {timeoutSeconds:F1}s.");
			_lastWaitOk = false;
		}

		private void Fail(string reason)
		{
			_log.LogError((object)("Quick Resume aborted: " + reason + "."));
			_chosen = null;
			_running = false;
		}
	}
	public static class RunLauncher
	{
		private static readonly MethodInfo _menuWindowOpen = AccessTools.Method(typeof(MenuWindow), "Open", (Type[])null, (Type[])null);

		public const string AirportScene = "Airport";

		public const string TitleScene = "Title";

		public const string LevelScenePrefix = "Level";

		public static string ActiveSceneName
		{
			get
			{
				//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)
				Scene activeScene = SceneManager.GetActiveScene();
				return ((Scene)(ref activeScene)).name;
			}
		}

		public static bool InAirport => ActiveSceneName == "Airport";

		public static bool InLevel => ActiveSceneName.StartsWith("Level");

		public static bool InTitle => ActiveSceneName == "Title";

		public static bool IsLoading
		{
			get
			{
				try
				{
					return LoadingScreenHandler.loading;
				}
				catch
				{
					return false;
				}
			}
		}

		public static bool IsHost
		{
			get
			{
				if (!PhotonNetwork.IsMasterClient)
				{
					return PhotonNetwork.OfflineMode;
				}
				return true;
			}
		}

		public static bool IsCustomRun
		{
			get
			{
				try
				{
					return RunSettings.IsCustomRun;
				}
				catch
				{
					return false;
				}
			}
		}

		public static bool TrySetCustomRun(bool value, ManualLogSource log)
		{
			try
			{
				RunSettings.IsCustomRun = value;
				return true;
			}
			catch (Exception arg)
			{
				log.LogError((object)$"TrySetCustomRun({value}) failed: {arg}");
				return false;
			}
		}

		public static bool ReturnToAirport(ManualLogSource log)
		{
			try
			{
				GameOverHandler instance = Singleton<GameOverHandler>.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					log.LogInfo((object)"ReturnToAirport: GameOverHandler.LoadAirport() (synchronized RPC-to-all).");
					instance.LoadAirport();
					return true;
				}
				log.LogWarning((object)"ReturnToAirport: GameOverHandler.Instance is null; using fallback (in coop this may not bring clients).");
			}
			catch (Exception ex)
			{
				log.LogError((object)("ReturnToAirport via GameOverHandler failed (" + ex.Message + "); using fallback."));
			}
			try
			{
				EndScreen val = Object.FindObjectOfType<EndScreen>();
				if ((Object)(object)val != (Object)null)
				{
					log.LogInfo((object)"ReturnToAirport: fallback EndScreen.ReturnToAirport().");
					val.ReturnToAirport();
					return true;
				}
				log.LogInfo((object)"ReturnToAirport: fallback direct networked Airport load.");
				return LoadAirportDirect(log);
			}
			catch (Exception arg)
			{
				log.LogError((object)$"ReturnToAirport fallback failed: {arg}");
				return false;
			}
		}

		private static bool LoadAirportDirect(ManualLogSource log)
		{
			try
			{
				LoadingScreenHandler instance = RetrievableResourceSingleton<LoadingScreenHandler>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					log.LogError((object)"LoadAirportDirect: LoadingScreenHandler.Instance is null; falling back to local scene load.");
					SceneManager.LoadScene("Airport");
					return true;
				}
				log.LogInfo((object)"LoadAirportDirect: networked LoadingScreenHandler load of Airport.");
				instance.Load((LoadingScreenType)0, (Action)null, new IEnumerator[1] { instance.LoadSceneProcess("Airport", true, true, 3f) });
				return true;
			}
			catch (Exception ex)
			{
				log.LogError((object)("LoadAirportDirect failed (" + ex.Message + "); falling back to local scene load."));
				SceneManager.LoadScene("Airport");
				return true;
			}
		}

		public static bool StartRun(int ascent, ManualLogSource log)
		{
			try
			{
				if (!InAirport)
				{
					log.LogError((object)("StartRun called while not in Airport (scene='" + ActiveSceneName + "')."));
					return false;
				}
				AirportCheckInKiosk val = Object.FindObjectOfType<AirportCheckInKiosk>();
				if ((Object)(object)val == (Object)null)
				{
					log.LogError((object)"StartRun: no AirportCheckInKiosk found in the Airport scene.");
					return false;
				}
				if (IsLoading)
				{
					log.LogError((object)"StartRun: a loading screen is still active; StartGame would no-op. Aborting.");
					return false;
				}
				log.LogInfo((object)$"StartRun: kiosk.StartGame(ascent={ascent}).");
				val.StartGame(ascent);
				return true;
			}
			catch (Exception arg)
			{
				log.LogError((object)$"StartRun failed: {arg}");
				return false;
			}
		}

		public static bool OpenGateKiosk(ManualLogSource log)
		{
			try
			{
				if (!InAirport)
				{
					log.LogError((object)("OpenGateKiosk called while not in Airport (scene='" + ActiveSceneName + "')."));
					return false;
				}
				if (IsLoading)
				{
					log.LogError((object)"OpenGateKiosk: a loading screen is still active. Aborting.");
					return false;
				}
				AirportCheckInKiosk val = Object.FindObjectOfType<AirportCheckInKiosk>();
				if ((Object)(object)val == (Object)null)
				{
					log.LogError((object)"OpenGateKiosk: no AirportCheckInKiosk found in the Airport scene.");
					return false;
				}
				if ((Object)(object)GUIManager.instance == (Object)null || (Object)(object)GUIManager.instance.boardingPass == (Object)null)
				{
					log.LogError((object)"OpenGateKiosk: GUIManager.instance.boardingPass is null.");
					return false;
				}
				BoardingPass boardingPass = GUIManager.instance.boardingPass;
				_menuWindowOpen.Invoke(boardingPass, null);
				boardingPass.kiosk = val;
				return true;
			}
			catch (Exception arg)
			{
				log.LogError((object)$"OpenGateKiosk failed: {arg}");
				return false;
			}
		}
	}
	public class ArchivedSave
	{
		public string FilePath;

		public bool Offline;

		public SaveTarget Target;

		public DateTime SortTime;

		public string SaveDate = "";

		public string CampfireName = "";

		public float Playtime;

		public string BiomesSummary = "";

		public string Players = "";

		public string DifficultyLabel => SaveArchive.DifficultyLabel(Target);
	}
	public static class SaveArchive
	{
		private class SaveMeta
		{
			public string saveDate;

			public string campfireName;

			public float timePlayed;

			public List<string> biome_names;

			public List<string> playerNames;
		}

		private const string TsFormat = "yyyyMMdd_HHmmss_fff";

		private const string Sep = "__";

		private static bool _migrated;

		private static string CanonicalBase => Path.Combine(Paths.PluginPath, "Checkpoint_Save");

		private static string CanonicalCoop => Path.Combine(CanonicalBase, "Coop");

		private static string ArchiveRoot => Path.Combine(Paths.PluginPath, "QuickResume", "Archive");

		private static string ArchiveDir(bool offline)
		{
			return Path.Combine(ArchiveRoot, offline ? "Offline" : "Coop");
		}

		private static string CanonicalDir(bool offline)
		{
			if (!offline)
			{
				return CanonicalCoop;
			}
			return CanonicalBase;
		}

		public static void Sync(bool offline, ManualLogSource log)
		{
			try
			{
				MigrateLegacyFlatArchive(log);
				string path = CanonicalDir(offline);
				if (!Directory.Exists(path))
				{
					return;
				}
				string text = ArchiveDir(offline);
				Directory.CreateDirectory(text);
				string[] files = Directory.GetFiles(path, "peak_save_*.json");
				foreach (string text2 in files)
				{
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2);
					if (!SaveDiscovery.TryParseStem(fileNameWithoutExtension, offline, out var _))
					{
						continue;
					}
					string text3 = Path.Combine(text, fileNameWithoutExtension + "__" + File.GetLastWriteTimeUtc(text2).ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture) + ".json");
					if (!File.Exists(text3))
					{
						File.Copy(text2, text3, overwrite: false);
						if (log != null)
						{
							log.LogInfo((object)("[archive] Archived '" + Path.GetFileName(text2) + "' -> '" + Path.GetFileName(text3) + "'."));
						}
					}
				}
			}
			catch (Exception arg)
			{
				if (log != null)
				{
					log.LogError((object)$"[archive] Sync failed: {arg}");
				}
			}
		}

		private static void MigrateLegacyFlatArchive(ManualLogSource log)
		{
			if (_migrated)
			{
				return;
			}
			_migrated = true;
			try
			{
				if (!Directory.Exists(ArchiveRoot))
				{
					return;
				}
				string[] files = Directory.GetFiles(ArchiveRoot, "peak_save_*.json");
				foreach (string text in files)
				{
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
					int num = fileNameWithoutExtension.LastIndexOf("__", StringComparison.Ordinal);
					bool flag = ((num > 0) ? fileNameWithoutExtension.Substring(0, num) : fileNameWithoutExtension).EndsWith("_offline", StringComparison.Ordinal);
					string text2 = ArchiveDir(flag);
					Directory.CreateDirectory(text2);
					string text3 = Path.Combine(text2, Path.GetFileName(text));
					if (!File.Exists(text3))
					{
						File.Move(text, text3);
						if (log != null)
						{
							log.LogInfo((object)("[archive] Migrated '" + Path.GetFileName(text) + "' -> " + (flag ? "Offline" : "Coop") + "/."));
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("[archive] Legacy archive migration skipped: " + ex.Message));
				}
			}
		}

		public static List<ArchivedSave> List(bool offline, ManualLogSource log)
		{
			List<ArchivedSave> list = new List<ArchivedSave>();
			try
			{
				Sync(offline, log);
				string path = ArchiveDir(offline);
				if (!Directory.Exists(path))
				{
					return list;
				}
				string text = (offline ? null : LocalUserId());
				string[] files = Directory.GetFiles(path, "peak_save_*.json");
				foreach (string text2 in files)
				{
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2);
					int num = fileNameWithoutExtension.LastIndexOf("__", StringComparison.Ordinal);
					if (num <= 0)
					{
						continue;
					}
					string text3 = fileNameWithoutExtension.Substring(0, num);
					string s = fileNameWithoutExtension.Substring(num + "__".Length);
					if (text3.EndsWith("_offline", StringComparison.Ordinal) == offline && (offline || string.IsNullOrEmpty(text) || (TryGetCoopUserId(text3, out var userId) && !(userId != text))) && SaveDiscovery.TryParseStem(text3, offline, out var target))
					{
						if (!DateTime.TryParseExact(s, "yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
						{
							result = File.GetLastWriteTimeUtc(text2);
						}
						ArchivedSave archivedSave = new ArchivedSave
						{
							FilePath = text2,
							Offline = offline,
							Target = target,
							SortTime = result
						};
						ReadMetadata(archivedSave, log);
						list.Add(archivedSave);
					}
				}
				list.Sort((ArchivedSave a, ArchivedSave b) => b.SortTime.CompareTo(a.SortTime));
			}
			catch (Exception arg)
			{
				if (log != null)
				{
					log.LogError((object)$"[archive] List failed: {arg}");
				}
			}
			return list;
		}

		public static bool Restore(ArchivedSave save, ManualLogSource log)
		{
			try
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(save.FilePath);
				int num = fileNameWithoutExtension.LastIndexOf("__", StringComparison.Ordinal);
				string text = ((num > 0) ? fileNameWithoutExtension.Substring(0, num) : fileNameWithoutExtension);
				string text2 = CanonicalDir(save.Offline);
				Directory.CreateDirectory(text2);
				string destFileName = Path.Combine(text2, text + ".json");
				File.Copy(save.FilePath, destFileName, overwrite: true);
				if (log != null)
				{
					log.LogInfo((object)("[archive] Restored '" + Path.GetFileName(save.FilePath) + "' -> canonical '" + text + ".json'."));
				}
				return true;
			}
			catch (Exception arg)
			{
				if (log != null)
				{
					log.LogError((object)$"[archive] Restore failed: {arg}");
				}
				return false;
			}
		}

		public static bool Delete(ArchivedSave save, ManualLogSource log)
		{
			try
			{
				if (File.Exists(save.FilePath))
				{
					File.Delete(save.FilePath);
				}
				if (log != null)
				{
					log.LogInfo((object)("[archive] Deleted '" + Path.GetFileName(save.FilePath) + "'."));
				}
				return true;
			}
			catch (Exception arg)
			{
				if (log != null)
				{
					log.LogError((object)$"[archive] Delete failed: {arg}");
				}
				return false;
			}
		}

		public static string DifficultyLabel(SaveTarget t)
		{
			if (t.IsCustom)
			{
				return "Custom Run";
			}
			return t.Ascent switch
			{
				-1 => "Tenderfoot", 
				0 => "PEAK", 
				_ => $"Ascent {t.Ascent}", 
			};
		}

		private static void ReadMetadata(ArchivedSave entry, ManualLogSource log)
		{
			try
			{
				SaveMeta saveMeta = JsonConvert.DeserializeObject<SaveMeta>(File.ReadAllText(entry.FilePath));
				if (saveMeta != null)
				{
					entry.SaveDate = saveMeta.saveDate ?? "";
					entry.CampfireName = saveMeta.campfireName ?? "";
					entry.Playtime = saveMeta.timePlayed;
					if (saveMeta.biome_names != null && saveMeta.biome_names.Count > 0)
					{
						entry.BiomesSummary = saveMeta.biome_names[saveMeta.biome_names.Count - 1];
					}
					if (saveMeta.playerNames != null && saveMeta.playerNames.Count > 0)
					{
						entry.Players = string.Join(", ", saveMeta.playerNames);
					}
				}
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogWarning((object)("[archive] Could not read metadata for '" + Path.GetFileName(entry.FilePath) + "': " + ex.Message));
				}
			}
		}

		private static string LocalUserId()
		{
			try
			{
				Player localPlayer = PhotonNetwork.LocalPlayer;
				return ((localPlayer != null) ? localPlayer.UserId : null) ?? "";
			}
			catch
			{
				return "";
			}
		}

		private static bool TryGetCoopUserId(string stem, out string userId)
		{
			userId = "";
			int num = stem.LastIndexOf('_');
			if (num <= 0 || num >= stem.Length - 1)
			{
				return false;
			}
			userId = stem.Substring(num + 1);
			return userId.Length > 0;
		}
	}
	public struct SaveTarget
	{
		public bool IsCustom;

		public int Ascent;

		public static SaveTarget Normal(int ascent)
		{
			return new SaveTarget
			{
				IsCustom = false,
				Ascent = ascent
			};
		}

		public static SaveTarget Custom()
		{
			return new SaveTarget
			{
				IsCustom = true,
				Ascent = 0
			};
		}

		public override string ToString()
		{
			if (!IsCustom)
			{
				return $"ascent {Ascent}";
			}
			return "custom run";
		}
	}
	public static class SaveDiscovery
	{
		private const string CustomToken = "CustomRun";

		private static string BaseDir => Path.Combine(Paths.PluginPath, "Checkpoint_Save");

		private static string CoopDir => Path.Combine(BaseDir, "Coop");

		public static bool TryGetLatestSave(ManualLogSource log, bool offlineMode, out SaveTarget target)
		{
			target = SaveTarget.Normal(0);
			try
			{
				string text = (offlineMode ? BaseDir : CoopDir);
				if (!Directory.Exists(text))
				{
					log.LogInfo((object)("[savescan] Save directory does not exist yet: " + text));
					return false;
				}
				DateTime dateTime = DateTime.MinValue;
				bool flag = false;
				string arg = null;
				string[] files = Directory.GetFiles(text, "peak_save_*.json");
				foreach (string path in files)
				{
					if (TryParseTarget(Path.GetFileName(path), offlineMode, out var target2))
					{
						DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(path);
						if (lastWriteTimeUtc > dateTime)
						{
							dateTime = lastWriteTimeUtc;
							target = target2;
							arg = Path.GetFileName(path);
							flag = true;
						}
					}
				}
				if (flag)
				{
					log.LogInfo((object)(string.Format("[savescan] Latest {0} save: {1} ", offlineMode ? "offline" : "coop", target) + $"('{arg}', modified {dateTime:u})."));
				}
				else
				{
					log.LogInfo((object)("[savescan] No recognizable " + (offlineMode ? "offline" : "coop") + " saves found in " + text + "."));
				}
				return flag;
			}
			catch (Exception arg2)
			{
				log.LogError((object)$"[savescan] TryGetLatestSave failed: {arg2}");
				return false;
			}
		}

		private static bool TryParseTarget(string fileName, bool offlineMode, out SaveTarget target)
		{
			return TryParseStem(Path.GetFileNameWithoutExtension(fileName), offlineMode, out target);
		}

		public static bool TryParseStem(string stem, bool offlineMode, out SaveTarget target)
		{
			target = SaveTarget.Normal(0);
			if (string.IsNullOrEmpty(stem) || !stem.StartsWith("peak_save_"))
			{
				return false;
			}
			string text = stem.Substring("peak_save_".Length);
			string text2;
			if (offlineMode)
			{
				if (!text.EndsWith("_offline"))
				{
					return false;
				}
				text2 = text.Substring(0, text.Length - "_offline".Length);
			}
			else
			{
				int num = text.IndexOf('_');
				if (num <= 0)
				{
					return false;
				}
				text2 = text.Substring(0, num);
			}
			if (text2 == "CustomRun")
			{
				target = SaveTarget.Custom();
				return true;
			}
			if (int.TryParse(text2, out var result))
			{
				target = SaveTarget.Normal(result);
				return true;
			}
			return false;
		}
	}
	public static class SavePatch
	{
		private static ManualLogSource _log;

		public static void Apply(Harmony harmony, Type checkpointType, ManualLogSource log)
		{
			_log = log;
			TryPatch(harmony, checkpointType, "SavePlayerOffline", "PostfixOffline", log);
			TryPatch(harmony, checkpointType, "SavePlayerCoop", "PostfixCoop", log);
		}

		private static void TryPatch(Harmony harmony, Type type, string method, string postfix, ManualLogSource log)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					log.LogWarning((object)("SavePatch: " + method + " not found; new saves of that type won't auto-archive."));
					return;
				}
				HarmonyMethod val = new HarmonyMethod(typeof(SavePatch).GetMethod(postfix, BindingFlags.Static | BindingFlags.NonPublic));
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				log.LogInfo((object)("SavePatch: patched " + method + " (auto-archiving saves)."));
			}
			catch (Exception arg)
			{
				log.LogError((object)$"SavePatch.Apply({method}) failed (non-fatal): {arg}");
			}
		}

		private static void PostfixOffline()
		{
			SaveArchive.Sync(offline: true, _log);
		}

		private static void PostfixCoop()
		{
			SaveArchive.Sync(offline: false, _log);
		}
	}
	public class SavePicker : MonoBehaviour
	{
		private ManualLogSource _log;

		private PluginConfig _cfg;

		private List<ArchivedSave> _entries = new List<ArchivedSave>();

		private int _selected;

		private bool _offline;

		private int _pendingDeleteIndex = -1;

		private float _pendingDeleteDeadline;

		private GUIStyle _panel;

		private GUIStyle _title;

		private GUIStyle _row;

		private GUIStyle _rowSel;

		private GUIStyle _footer;

		private GUIStyle _warn;

		public bool IsOpen { get; private set; }

		public ArchivedSave Selected
		{
			get
			{
				if (!IsOpen || _selected < 0 || _selected >= _entries.Count)
				{
					return null;
				}
				return _entries[_selected];
			}
		}

		public void Init(ManualLogSource log, PluginConfig cfg)
		{
			_log = log;
			_cfg = cfg;
		}

		public bool Open(bool offline, SaveTarget? preferred)
		{
			_offline = offline;
			_entries = SaveArchive.List(offline, _log);
			if (_entries.Count == 0)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)("[picker] No " + (offline ? "offline" : "coop") + " saves to show."));
				}
				return false;
			}
			_selected = 0;
			if (preferred.HasValue)
			{
				for (int i = 0; i < _entries.Count; i++)
				{
					if (_entries[i].Target.IsCustom == preferred.Value.IsCustom && (preferred.Value.IsCustom || _entries[i].Target.Ascent == preferred.Value.Ascent))
					{
						_selected = i;
						break;
					}
				}
			}
			ClearPendingDelete();
			IsOpen = true;
			ManualLogSource log2 = _log;
			if (log2 != null)
			{
				log2.LogInfo((object)string.Format("[picker] Opened with {0} {1} save(s); selected #{2}.", _entries.Count, offline ? "offline" : "coop", _selected));
			}
			return true;
		}

		public void Close()
		{
			if (IsOpen)
			{
				IsOpen = false;
				ClearPendingDelete();
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)"[picker] Closed.");
				}
			}
		}

		private void Update()
		{
			if (IsOpen)
			{
				if (Input.GetKeyDown((KeyCode)273))
				{
					Move(-1);
				}
				else if (Input.GetKeyDown((KeyCode)274))
				{
					Move(1);
				}
				else if (Input.GetKeyDown((KeyCode)27))
				{
					Close();
				}
				else if (Input.GetKeyDown((KeyCode)127))
				{
					OnDeletePressed();
				}
				if (_pendingDeleteIndex >= 0 && Time.unscaledTime > _pendingDeleteDeadline)
				{
					ClearPendingDelete();
				}
			}
		}

		private void Move(int delta)
		{
			ClearPendingDelete();
			if (_entries.Count != 0)
			{
				_selected = Mathf.Clamp(_selected + delta, 0, _entries.Count - 1);
			}
		}

		private void OnDeletePressed()
		{
			ArchivedSave selected = Selected;
			if (selected == null)
			{
				return;
			}
			if (_pendingDeleteIndex == _selected && Time.unscaledTime <= _pendingDeleteDeadline)
			{
				SaveArchive.Delete(selected, _log);
				_entries.RemoveAt(_selected);
				ClearPendingDelete();
				if (_entries.Count == 0)
				{
					Close();
				}
				else
				{
					_selected = Mathf.Clamp(_selected, 0, _entries.Count - 1);
				}
			}
			else
			{
				_pendingDeleteIndex = _selected;
				_pendingDeleteDeadline = Time.unscaledTime + 3f;
			}
		}

		private void ClearPendingDelete()
		{
			_pendingDeleteIndex = -1;
			_pendingDeleteDeadline = 0f;
		}

		private void EnsureStyles()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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_002c: Expected O, but got Unknown
			//IL_0031: Expected O, but got Unknown
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: 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_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Expected O, but got Unknown
			if (_panel == null)
			{
				_panel = new GUIStyle(GUI.skin.box)
				{
					padding = new RectOffset(16, 16, 12, 12)
				};
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					fontSize = 22,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)4
				};
				val.normal.textColor = new Color(0.6f, 0.85f, 1f);
				_title = val;
				GUIStyle val2 = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					alignment = (TextAnchor)3,
					richText = true
				};
				val2.normal.textColor = new Color(0.85f, 0.85f, 0.85f);
				_row = val2;
				GUIStyle val3 = new GUIStyle(_row)
				{
					fontStyle = (FontStyle)1
				};
				val3.normal.textColor = new Color(1f, 0.95f, 0.4f);
				_rowSel = val3;
				GUIStyle val4 = new GUIStyle(GUI.skin.label)
				{
					fontSize = 14,
					alignment = (TextAnchor)4
				};
				val4.normal.textColor = new Color(0.75f, 0.85f, 0.95f);
				_footer = val4;
				GUIStyle val5 = new GUIStyle(_footer)
				{
					fontStyle = (FontStyle)1
				};
				val5.normal.textColor = new Color(1f, 0.5f, 0.5f);
				_warn = val5;
			}
		}

		private void OnGUI()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			if (!IsOpen)
			{
				return;
			}
			EnsureStyles();
			float num = Mathf.Min(760f, (float)Screen.width - 80f);
			float num2 = Mathf.Min(60f + (float)_entries.Count * 30f + 70f, (float)Screen.height - 80f);
			float num3 = ((float)Screen.width - num) / 2f;
			float num4 = ((float)Screen.height - num2) / 2f;
			Color color = GUI.color;
			GUI.color = new Color(0f, 0f, 0f, 0.55f);
			GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
			GUILayout.BeginArea(new Rect(num3, num4, num, num2), _panel);
			GUILayout.Label("Quick Resume  Load Save  (" + (_offline ? "Solo" : "Co-op") + ")", _title, Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			for (int i = 0; i < _entries.Count; i++)
			{
				ArchivedSave archivedSave = _entries[i];
				bool flag = i == _selected;
				string text = (flag ? "▶ " : "   ");
				string text2 = (string.IsNullOrEmpty(archivedSave.CampfireName) ? "—" : archivedSave.CampfireName);
				string text3 = (string.IsNullOrEmpty(archivedSave.SaveDate) ? archivedSave.SortTime.ToLocalTime().ToString("dd.MM.yyyy HH:mm") : archivedSave.SaveDate);
				string text4 = text + "<b>" + archivedSave.DifficultyLabel + "</b>   " + text2 + "   " + text3 + "   " + FormatPlaytime(archivedSave.Playtime);
				if (!_offline && !string.IsNullOrEmpty(archivedSave.Players))
				{
					text4 = text4 + "   (" + archivedSave.Players + ")";
				}
				GUILayout.Label(text4, flag ? _rowSel : _row, Array.Empty<GUILayoutOption>());
			}
			GUILayout.FlexibleSpace();
			if (_pendingDeleteIndex >= 0 && _pendingDeleteIndex == _selected)
			{
				GUILayout.Label("Press Delete again to permanently remove this save.", _warn, Array.Empty<GUILayoutOption>());
			}
			string text5 = ((_cfg != null) ? ((object)_cfg.ResumeKey.Value/*cast due to .constrained prefix*/).ToString() : "F7");
			GUILayout.Label("↑/↓ Select     " + text5 + " / Enter  Load     Del  Delete     Esc  Cancel", _footer, Array.Empty<GUILayoutOption>());
			GUILayout.EndArea();
		}

		private static string FormatPlaytime(float seconds)
		{
			if (seconds <= 0f)
			{
				return "";
			}
			TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
			if (!(timeSpan.TotalHours >= 1.0))
			{
				return $"{timeSpan.Minutes}m played";
			}
			return $"{(int)timeSpan.TotalHours}h {timeSpan.Minutes}m played";
		}
	}
	public static class TutorialPatch
	{
		private static ManualLogSource _log;

		private static PropertyInfo _textProp;

		private static FieldInfo _tutorialTmpField;

		public static void Apply(Harmony harmony, Type checkpointType, ManualLogSource log)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			_log = log;
			try
			{
				MethodInfo methodInfo = AccessTools.Method(checkpointType, "ShowTutorialMessage", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					log.LogWarning((object)"TutorialPatch: ShowTutorialMessage not found; F1 tutorial will not mention F7.");
					return;
				}
				_tutorialTmpField = AccessTools.Field(checkpointType, "_tutorialTMP");
				HarmonyMethod val = new HarmonyMethod(typeof(TutorialPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic));
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				log.LogInfo((object)"TutorialPatch: patched ShowTutorialMessage (F1 tutorial will mention F7).");
			}
			catch (Exception arg)
			{
				log.LogError((object)$"TutorialPatch.Apply failed (non-fatal): {arg}");
			}
		}

		private static void Postfix(bool active, string message, object __instance)
		{
			try
			{
				if (!active || !string.IsNullOrEmpty(message) || _tutorialTmpField == null)
				{
					return;
				}
				object value = _tutorialTmpField.GetValue(__instance);
				if (value == null)
				{
					return;
				}
				if (_textProp == null)
				{
					_textProp = value.GetType().GetProperty("text");
				}
				if (_textProp == null)
				{
					return;
				}
				string text = _textProp.GetValue(value) as string;
				if (!string.IsNullOrEmpty(text))
				{
					string text2 = CompactPadding(text);
					text2 = InsertF7Line(text2);
					text2 = RewriteVersionLine(text2);
					if (!string.Equals(text2, text, StringComparison.Ordinal))
					{
						_textProp.SetValue(value, text2);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("TutorialPatch.Postfix failed (non-fatal): " + ex.Message));
				}
			}
		}

		private static string CompactPadding(string text)
		{
			try
			{
				return Regex.Replace(text, "\n{3,}", "\n\n");
			}
			catch
			{
				return text;
			}
		}

		private static string InsertF7Line(string text)
		{
			int num = text.IndexOf("just start a level and press (", StringComparison.OrdinalIgnoreCase);
			if (num < 0)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)"TutorialPatch: F6 anchor not found; skipping F7 line.");
				}
				return text;
			}
			int num2 = text.IndexOf(')', num);
			if (num2 < 0)
			{
				return text;
			}
			string text2 = Plugin.Instance?.ResumeKeyText ?? "F7";
			string value = "\n\nQuick Resume: press (" + text2 + ") ANYWHERE to open the save picker; arrow-keys\nto choose a checkpoint, (" + text2 + ")/Enter to load (or press (" + text2 + ") twice for the latest).";
			return text.Insert(num2 + 1, value);
		}

		private static string RewriteVersionLine(string text)
		{
			int num = text.LastIndexOf("Mod version:", StringComparison.OrdinalIgnoreCase);
			if (num < 0)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)"TutorialPatch: version marker not found; leaving version line.");
				}
				return text;
			}
			string text2 = text.Substring(num + "Mod version:".Length).Trim();
			string text3 = "0.3.0";
			string text4 = "PCS Mod Version: " + text2 + " / Quick Resume Mod Version: " + text3;
			return text.Substring(0, num) + text4;
		}
	}
}