Decompiled source of REPOInstantSaveRolling v2.1.2

BepInEx/plugins/Helix65-REPO-Instant-Save/REPO-Instant-Save.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Photon.Pun;
using REPO_Instant_Save.Config;
using REPO_Instant_Save.Hotkeys;
using REPO_Instant_Save.Save;
using REPO_Instant_Save.UI;
using REPO_Instant_Save.Util;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("REPO-Instant-Save")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.1.2.0")]
[assembly: AssemblyInformationalVersion("2.1.2")]
[assembly: AssemblyProduct("REPO-Instant-Save")]
[assembly: AssemblyTitle("REPO-Instant-Save")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.1.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 REPO_Instant_Save
{
	internal static class ModInfo
	{
		public const string Guid = "Helix65.REPOInstantSave";

		public const string Name = "REPO Instant Save";

		public const string Version = "2.1.2";
	}
	[BepInPlugin("Helix65.REPOInstantSave", "REPO Instant Save", "2.1.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private readonly Harmony _harmony = new Harmony("Helix65.REPOInstantSave");

		internal static Plugin? Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal PluginConfig ModConfig { get; private set; }

		private void Awake()
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"REPO Instant Save v2.1.2 initialising…");
			try
			{
				ModConfig = new PluginConfig(((BaseUnityPlugin)this).Config);
				_harmony.PatchAll();
				((Component)this).gameObject.AddComponent<HotkeyManager>();
				((Component)this).gameObject.AddComponent<SnapshotScheduler>();
				((Component)this).gameObject.AddComponent<SnapshotMenu>();
				Log.LogInfo((object)(string.Format("{0} loaded. Save key: {1}, ", "REPO Instant Save", ModConfig.FullSaveKey.Value) + $"menu key: {ModConfig.MenuKey.Value}, auto-snapshot every " + $"{ModConfig.AutoSnapshotIntervalSeconds.Value}s " + $"(entry snapshot: {ModConfig.SnapshotOnLevelEntry.Value}, " + $"max {ModConfig.MaxSnapshots.Value})."));
			}
			catch (Exception arg)
			{
				Log.LogError((object)string.Format("{0} failed to initialise: {1}", "REPO Instant Save", arg));
			}
		}
	}
}
namespace REPO_Instant_Save.Util
{
	internal static class GameState
	{
		public static bool IsHost => SemiFunc.IsMasterClientOrSingleplayer();

		public static bool InRunLevel => SemiFunc.RunIsLevel();

		public static bool StatsReady => (Object)(object)StatsManager.instance != (Object)null;

		public static bool LevelGenerated
		{
			get
			{
				LevelGenerator instance = LevelGenerator.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					return instance.Generated;
				}
				return false;
			}
		}

		public static bool LocalPlayerAlive
		{
			get
			{
				GameDirector instance = GameDirector.instance;
				if ((Object)(object)instance == (Object)null || instance.PlayerList == null)
				{
					return false;
				}
				bool result = false;
				foreach (PlayerAvatar player in instance.PlayerList)
				{
					if (!((Object)(object)player == (Object)null) && player.isLocal)
					{
						result = true;
						if (player.deadSet)
						{
							return false;
						}
						if ((Object)(object)player.playerHealth != (Object)null && player.playerHealth.health <= 0)
						{
							return false;
						}
					}
				}
				return result;
			}
		}
	}
	internal static class HudNotify
	{
		private static readonly Color SuccessColor = new Color(0.45f, 1f, 0.45f);

		private static readonly Color FailureColor = new Color(1f, 0.45f, 0.45f);

		public static void Toast(string message, bool success)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)MissionUI.instance == (Object)null))
				{
					SemiFunc.UIFocusText(message, success ? SuccessColor : FailureColor, Color.white, 3f);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("HUD toast failed: " + ex.Message));
			}
		}
	}
}
namespace REPO_Instant_Save.UI
{
	internal sealed class SnapshotMenu : MonoBehaviour
	{
		private const int WindowId = 23063;

		private const float WindowWidth = 620f;

		private const float WindowHeight = 600f;

		private const float CursorUnlockSeconds = 2f;

		private bool _open;

		private Rect _windowRect = new Rect(0f, 0f, 620f, 600f);

		private Vector2 _scroll;

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

		private string _saveName = "";

		private float _nextRefresh;

		private string _status = "";

		private bool _prevCursorVisible;

		private CursorLockMode _prevLockState;

		private bool _prevDisableInput;

		private float _prevInputDisableTimer;

		private bool _inputFrozen;

		private Texture2D? _cursorTexture;

		private Rect _cursorUv = new Rect(0f, 0f, 1f, 1f);

		private bool _cursorResolved;

		private bool _usingGameCursor;

		private Texture2D? _transparentCursor;

		private long _confirmDeleteTicks;

		private bool _confirmClearAll;

		public void Toggle()
		{
			SetOpen(!_open);
		}

		private void SetOpen(bool open)
		{
			//IL_0028: 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)
			if (_open == open)
			{
				return;
			}
			_open = open;
			if (_open)
			{
				_prevCursorVisible = Cursor.visible;
				_prevLockState = Cursor.lockState;
				GameDirector instance = GameDirector.instance;
				_prevDisableInput = (Object)(object)instance != (Object)null && instance.DisableInput;
				_prevInputDisableTimer = (((Object)(object)PlayerController.instance != (Object)null) ? PlayerController.instance.InputDisableTimer : 0f);
				_usingGameCursor = ResolveGameCursor();
				if (_usingGameCursor)
				{
					EnsureTransparentCursor();
				}
				ApplyCursor();
				_nextRefresh = 0f;
				_confirmDeleteTicks = 0L;
				_confirmClearAll = false;
				Refresh();
			}
			else
			{
				RestoreCursor();
				RestoreInput();
				_confirmDeleteTicks = 0L;
				_confirmClearAll = false;
			}
		}

		private void OnDisable()
		{
			if (_open)
			{
				RestoreCursor();
				RestoreInput();
				_open = false;
			}
		}

		private void Update()
		{
			if (!_open)
			{
				return;
			}
			if (Input.GetKeyDown((KeyCode)27))
			{
				SetOpen(open: false);
				return;
			}
			ApplyCursor();
			FreezeInput();
			if (Time.unscaledTime >= _nextRefresh)
			{
				Refresh();
				_nextRefresh = Time.unscaledTime + 2f;
			}
		}

		private void EnsureTransparentCursor()
		{
			//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_0026: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_transparentCursor != (Object)null))
			{
				_transparentCursor = new Texture2D(1, 1, (TextureFormat)4, false)
				{
					hideFlags = (HideFlags)61
				};
				_transparentCursor.SetPixel(0, 0, new Color(0f, 0f, 0f, 0f));
				_transparentCursor.Apply();
			}
		}

		private void ApplyCursor()
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (_usingGameCursor)
			{
				if ((Object)(object)CursorManager.instance != (Object)null)
				{
					CursorManager.instance.Unlock(2f);
				}
				if ((Object)(object)MenuCursor.instance != (Object)null)
				{
					if ((Object)(object)_cursorTexture != (Object)null)
					{
						MenuCursor.instance.OverrideHide(0.2f);
					}
					else
					{
						MenuCursor.instance.Show();
					}
				}
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = false;
				if ((Object)(object)_transparentCursor != (Object)null)
				{
					Cursor.SetCursor(_transparentCursor, Vector2.zero, (CursorMode)0);
				}
			}
			else
			{
				if ((Object)(object)MenuCursor.instance != (Object)null)
				{
					MenuCursor.instance.OverrideHide(0.2f);
				}
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
			}
		}

		private void RestoreCursor()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			if (_usingGameCursor)
			{
				Cursor.SetCursor((Texture2D)null, Vector2.zero, (CursorMode)0);
				if ((Object)(object)CursorManager.instance != (Object)null)
				{
					CursorManager.instance.Unlock(((int)_prevLockState == 1) ? 0.01f : 9999f);
				}
			}
			else
			{
				Cursor.visible = _prevCursorVisible;
				Cursor.lockState = _prevLockState;
			}
		}

		private void FreezeInput()
		{
			if (GameState.InRunLevel)
			{
				GameDirector instance = GameDirector.instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.SetDisableInput(0.25f);
					_inputFrozen = true;
				}
				if ((Object)(object)PlayerController.instance != (Object)null)
				{
					PlayerController.instance.InputDisableTimer = 0.25f;
				}
			}
		}

		private void RestoreInput()
		{
			if (_inputFrozen)
			{
				GameDirector instance = GameDirector.instance;
				if ((Object)(object)instance != (Object)null && !_prevDisableInput)
				{
					instance.DisableInput = false;
				}
				if ((Object)(object)PlayerController.instance != (Object)null)
				{
					PlayerController.instance.InputDisableTimer = _prevInputDisableTimer;
				}
				_inputFrozen = false;
			}
		}

		private bool ResolveGameCursor()
		{
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: 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_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			if (_cursorResolved)
			{
				return _usingGameCursor;
			}
			_cursorResolved = true;
			if (!(Plugin.Instance?.ModConfig?.UseGameCursor.Value ?? true))
			{
				Plugin.Log.LogInfo((object)"Menu cursor: game cursor disabled in config; using OS cursor.");
				return false;
			}
			try
			{
				MenuCursor instance = MenuCursor.instance;
				if ((Object)(object)instance == (Object)null)
				{
					Plugin.Log.LogInfo((object)"Menu cursor: MenuCursor.instance not found; using OS cursor.");
					return false;
				}
				Renderer val = null;
				if (((Component)instance).transform.childCount > 0)
				{
					val = ((Component)((Component)instance).transform.GetChild(0)).GetComponent<Renderer>();
					if ((Object)(object)val == (Object)null)
					{
						val = ((Component)((Component)instance).transform.GetChild(0)).GetComponentInChildren<Renderer>(true);
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					val = ((Component)instance).GetComponentInChildren<Renderer>(true);
				}
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Menu cursor: no Renderer found; using OS cursor.");
					return false;
				}
				Material val2 = (((Object)(object)val.sharedMaterial != (Object)null) ? val.sharedMaterial : val.material);
				if ((Object)(object)val2 == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Menu cursor: no material found; using OS cursor.");
					return false;
				}
				Texture mainTexture = val2.mainTexture;
				Texture2D val3 = (Texture2D)(object)((mainTexture is Texture2D) ? mainTexture : null);
				if (val3 == null)
				{
					Plugin.Log.LogWarning((object)("Menu cursor: material '" + ((Object)val2).name + "' has no Texture2D mainTexture (" + (((object)val2.mainTexture)?.GetType().Name ?? "null") + "); using OS cursor."));
					return false;
				}
				_cursorTexture = val3;
				Vector2 mainTextureOffset = val2.mainTextureOffset;
				Vector2 mainTextureScale = val2.mainTextureScale;
				_cursorUv = new Rect(mainTextureOffset.x, mainTextureOffset.y, mainTextureScale.x, mainTextureScale.y);
				Plugin.Log.LogInfo((object)("Menu cursor: using game cursor texture '" + ((Object)val3).name + "' " + $"({((Texture)val3).width}x{((Texture)val3).height}), renderer {((object)val).GetType().Name}, " + $"material '{((Object)val2).name}', uv {_cursorUv}."));
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Menu cursor: failed to resolve game cursor (" + ex.Message + "); using OS cursor."));
				return false;
			}
		}

		private void DrawGameCursor()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			if (_usingGameCursor && !((Object)(object)_cursorTexture == (Object)null))
			{
				Vector2 mousePosition = Event.current.mousePosition;
				float num = Mathf.Clamp((float)Screen.height * 0.04f, 24f, 48f);
				Rect val = new Rect(mousePosition.x - num / 2f, mousePosition.y - num / 2f, num, num);
				int depth = GUI.depth;
				GUI.depth = -1000;
				GUI.DrawTextureWithTexCoords(val, (Texture)(object)_cursorTexture, _cursorUv);
				GUI.depth = depth;
			}
		}

		private void Refresh()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string text = SnapshotHistory.CurrentOrLatestSaveName();
				if (text != _saveName)
				{
					_saveName = text;
					_scroll = Vector2.zero;
				}
				_entries = SnapshotHistory.List(_saveName);
			}
			catch (Exception ex)
			{
				_status = "List error: " + ex.Message;
			}
		}

		private void OnGUI()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (_open)
			{
				ApplyCursor();
				((Rect)(ref _windowRect)).x = ((float)Screen.width - 620f) / 2f;
				((Rect)(ref _windowRect)).y = ((float)Screen.height - 600f) / 2f;
				_windowRect = GUI.Window(23063, _windowRect, new WindowFunction(DrawWindow), "R.E.P.O. Instant Save - backups");
				DrawGameCursor();
			}
		}

		private void DrawWindow(int id)
		{
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_052d: Unknown result type (might be due to invalid IL or missing references)
			bool valueOrDefault = Plugin.Instance?.ModConfig?.ShowSeconds.Value == true;
			int seconds = Plugin.Instance?.ModConfig?.AutoSnapshotIntervalSeconds.Value ?? 600;
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Сейв: " + (string.IsNullOrEmpty(_saveName) ? "(не найден)" : _saveName), Array.Empty<GUILayoutOption>());
			GUILayout.Label("Автоснимок каждые " + FormatInterval(seconds) + ". Снимок делается, пока вы живы в уровне.", Array.Empty<GUILayoutOption>());
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Сохранить сейчас (F6)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
			{
				bool flag = InstantSaveService.SaveNow();
				_status = (flag ? "Снимок сохранён." : "Не удалось сохранить (смотрите лог).");
				Refresh();
			}
			if (GUILayout.Button("Обновить", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(110f),
				GUILayout.Height(28f)
			}))
			{
				Refresh();
			}
			GUILayout.EndHorizontal();
			if (!string.IsNullOrEmpty(_status))
			{
				GUILayout.Label(_status, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(6f);
			float num = Mathf.Max(150f, ((Rect)(ref _windowRect)).height - 290f);
			_scroll = GUILayout.BeginScrollView(_scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num) });
			if (_entries.Count == 0)
			{
				GUILayout.Label("Пока нет снимков. Зайдите в уровень и подождите первый автоснимок.", Array.Empty<GUILayoutOption>());
			}
			foreach (SnapshotEntry entry in _entries)
			{
				GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
				GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
				GUILayout.Label(TimeText(entry, valueOrDefault) + "   (" + AgeText(entry, valueOrDefault) + ")", Array.Empty<GUILayoutOption>());
				GUILayout.Label($"{entry.levelName}   сбор {entry.currentHaul}/{entry.haulGoal}   " + "уровень " + (entry.runLevel?.ToString() ?? "?"), Array.Empty<GUILayoutOption>());
				GUILayout.Label($"{entry.valuableCount} предметов, {entry.playerCount} игрок(ов)" + (entry.hasProgression ? "" : "   [нет копии .es3]"), Array.Empty<GUILayoutOption>());
				GUILayout.EndVertical();
				GUI.enabled = entry.hasProgression;
				if (GUILayout.Button("Восстановить", (GUILayoutOption[])(object)new GUILayoutOption[2]
				{
					GUILayout.Width(130f),
					GUILayout.Height(52f)
				}))
				{
					Restore(entry);
				}
				GUI.enabled = true;
				GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) });
				if (_confirmDeleteTicks == entry.ticks)
				{
					if (GUILayout.Button("Удалить?", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f) }))
					{
						DeleteEntry(entry);
					}
					if (GUILayout.Button("Отмена", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f) }))
					{
						_confirmDeleteTicks = 0L;
					}
				}
				else if (GUILayout.Button("Удалить", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(52f) }))
				{
					_confirmDeleteTicks = entry.ticks;
					_confirmClearAll = false;
				}
				GUILayout.EndVertical();
				GUILayout.EndHorizontal();
			}
			GUILayout.EndScrollView();
			GUILayout.Space(4f);
			if (_entries.Count > 0)
			{
				if (_confirmClearAll)
				{
					GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
					if (GUILayout.Button("Точно удалить ВСЕ снимки?", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
					{
						ClearAll();
					}
					if (GUILayout.Button("Отмена", (GUILayoutOption[])(object)new GUILayoutOption[2]
					{
						GUILayout.Width(110f),
						GUILayout.Height(28f)
					}))
					{
						_confirmClearAll = false;
					}
					GUILayout.EndHorizontal();
				}
				else if (GUILayout.Button("Очистить всю историю", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
				{
					_confirmClearAll = true;
					_confirmDeleteTicks = 0L;
				}
				GUILayout.Space(4f);
			}
			GUILayout.Label("Восстановление подставит .es3 снимка (деньги/квоты) и перезагрузит сохранённый уровень. Играйте дальше с этой точки.", Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Закрыть (F8 / Esc)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
			{
				SetOpen(open: false);
			}
			GUILayout.EndVertical();
			GUI.DragWindow(new Rect(0f, 0f, 620f, 24f));
		}

		private void Restore(SnapshotEntry entry)
		{
			if (string.IsNullOrEmpty(_saveName))
			{
				_status = "Не найден сейв для восстановления.";
				return;
			}
			bool flag = RestoreService.RestoreEntry(_saveName, entry);
			_status = (flag ? "Восстановление запущено..." : "Не удалось восстановить (смотрите лог).");
			if (flag)
			{
				SetOpen(open: false);
			}
		}

		private void DeleteEntry(SnapshotEntry entry)
		{
			_confirmDeleteTicks = 0L;
			if (string.IsNullOrEmpty(_saveName))
			{
				_status = "Не найден сейв.";
				return;
			}
			bool flag = SnapshotHistory.Delete(_saveName, entry.ticks);
			_status = (flag ? "Снимок удалён." : "Не удалось удалить снимок (смотрите лог).");
			Refresh();
		}

		private void ClearAll()
		{
			_confirmClearAll = false;
			_confirmDeleteTicks = 0L;
			if (string.IsNullOrEmpty(_saveName))
			{
				_status = "Не найден сейв.";
				return;
			}
			int num = SnapshotHistory.Clear(_saveName);
			_status = ((num > 0) ? $"Удалено снимков: {num}." : "История уже пуста.");
			Refresh();
		}

		private static string FormatInterval(int seconds)
		{
			if (seconds < 60)
			{
				return $"{seconds} сек";
			}
			int num = seconds / 60;
			int num2 = seconds % 60;
			if (num2 == 0)
			{
				if (num % 60 != 0 || num < 60)
				{
					return $"{num} мин";
				}
				return $"{num / 60} ч";
			}
			return $"{num} мин {num2} сек";
		}

		private static string TimeText(SnapshotEntry entry, bool showSeconds)
		{
			DateTime dateTime = entry.TakenUtc.ToLocalTime();
			string text = (showSeconds ? dateTime.ToString("HH:mm:ss") : dateTime.ToString("HH:mm"));
			if (!(dateTime.Date != DateTime.Now.Date))
			{
				return text;
			}
			return dateTime.ToString("dd.MM ") + text;
		}

		private static string AgeText(SnapshotEntry entry, bool showSeconds)
		{
			TimeSpan timeSpan = DateTime.UtcNow - entry.TakenUtc;
			if (timeSpan.TotalSeconds < 0.0)
			{
				timeSpan = TimeSpan.Zero;
			}
			if (timeSpan.TotalMinutes < 1.0)
			{
				if (!showSeconds)
				{
					return "только что";
				}
				return $"{(int)timeSpan.TotalSeconds} сек назад";
			}
			if (timeSpan.TotalHours < 1.0)
			{
				if (!showSeconds)
				{
					return $"{(int)timeSpan.TotalMinutes} мин назад";
				}
				return $"{(int)timeSpan.TotalMinutes} мин {timeSpan.Seconds} сек назад";
			}
			if (!showSeconds)
			{
				return $"{(int)timeSpan.TotalHours} ч {timeSpan.Minutes} мин назад";
			}
			return $"{(int)timeSpan.TotalHours} ч {timeSpan.Minutes} мин {timeSpan.Seconds} сек назад";
		}
	}
}
namespace REPO_Instant_Save.Save
{
	internal static class CrossSession
	{
		public static WorldSnapshot? Pending { get; private set; }

		public static bool RebuildActive { get; set; }

		public static Level? TargetLevel { get; private set; }

		public static long? PendingSelectionTicks { get; set; }

		public static void Arm(WorldSnapshot snapshot)
		{
			Pending = snapshot;
			RebuildActive = false;
			TargetLevel = FindLevel(snapshot.levelName);
			Plugin.Log.LogInfo((object)("Cross-session restore armed for '" + snapshot.levelName + "' " + $"(grid {snapshot.grid.tiles.Count} tiles, {snapshot.level.Count} level objects); " + "target level " + (((Object)(object)TargetLevel != (Object)null) ? "found" : "NOT FOUND") + "."));
		}

		private static Level? FindLevel(string levelName)
		{
			RunManager instance = RunManager.instance;
			if ((Object)(object)instance == (Object)null || instance.levels == null)
			{
				return null;
			}
			foreach (Level level in instance.levels)
			{
				if ((Object)(object)level != (Object)null && ((Object)level).name == levelName)
				{
					return level;
				}
			}
			return null;
		}

		public static void Clear()
		{
			Pending = null;
			RebuildActive = false;
			TargetLevel = null;
		}

		public static bool ShouldRebuildCurrentLevel()
		{
			if (Pending == null || !Pending.grid.present)
			{
				return false;
			}
			Level val = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelCurrent : null);
			if ((Object)(object)val != (Object)null)
			{
				return ((Object)val).name == Pending.levelName;
			}
			return false;
		}
	}
	internal static class InstantSaveService
	{
		public static bool SaveNow(bool auto = false)
		{
			if (!GameState.IsHost)
			{
				Plugin.Log.LogWarning((object)"Instant Save ignored: only the host can save.");
				return false;
			}
			if (!GameState.InRunLevel)
			{
				Plugin.Log.LogWarning((object)"Instant Save ignored: you must be inside a level.");
				return false;
			}
			if (!GameState.StatsReady)
			{
				Plugin.Log.LogWarning((object)"Instant Save ignored: StatsManager not ready.");
				return false;
			}
			if (!GameState.LevelGenerated)
			{
				if (!auto)
				{
					Plugin.Log.LogWarning((object)"Instant Save ignored: the level is still generating.");
				}
				return false;
			}
			if (!GameState.LocalPlayerAlive)
			{
				if (!auto)
				{
					Plugin.Log.LogWarning((object)"Instant Save ignored: the local player is dead.");
				}
				return false;
			}
			try
			{
				string saveFileCurrent = StatsManager.instance.saveFileCurrent;
				if (string.IsNullOrEmpty(saveFileCurrent))
				{
					Plugin.Log.LogWarning((object)"Instant Save ignored: no active save file yet.");
					return false;
				}
				WorldSnapshot worldSnapshot = WorldCapture.Capture();
				StatsManager.instance.SaveGame(saveFileCurrent);
				StatsManager.instance.PlayersAddAll();
				StatsManager.instance.saveFileReady = true;
				string es3SourcePath = Path.Combine(Application.persistentDataPath, "saves", saveFileCurrent, saveFileCurrent + ".es3");
				int maxEntries = Plugin.Instance?.ModConfig?.MaxSnapshots.Value ?? 20;
				SnapshotHistory.Add(saveFileCurrent, worldSnapshot, es3SourcePath, maxEntries);
				Plugin.Log.LogInfo((object)("Instant Save captured for '" + saveFileCurrent + "' [" + worldSnapshot.levelName + "]: " + $"{worldSnapshot.level.Count} level objects, {worldSnapshot.valuables.Count} valuables, " + $"{worldSnapshot.extraction.Count} extraction points, {worldSnapshot.players.Count} players" + (auto ? " (auto)." : ".")));
				if (!auto)
				{
					Plugin? instance = Plugin.Instance;
					if (instance != null)
					{
						((Component)instance).GetComponent<SnapshotScheduler>()?.OnManualSave();
					}
				}
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Instant Save failed: {arg}");
				return false;
			}
		}
	}
	internal static class InstantSaveStore
	{
		private const string FileName = "instantsave.json";

		public static string FolderFor(string saveName)
		{
			return Path.Combine(Application.persistentDataPath, "saves", saveName);
		}

		public static string PathFor(string saveName)
		{
			return Path.Combine(FolderFor(saveName), "instantsave.json");
		}

		public static void Write(string saveName, WorldSnapshot snapshot)
		{
			Directory.CreateDirectory(FolderFor(saveName));
			string contents = JsonConvert.SerializeObject((object)snapshot, (Formatting)1);
			File.WriteAllText(PathFor(saveName), contents);
		}

		public static WorldSnapshot? Read(string saveName)
		{
			string path = PathFor(saveName);
			if (!File.Exists(path))
			{
				return null;
			}
			return JsonConvert.DeserializeObject<WorldSnapshot>(File.ReadAllText(path));
		}

		public static bool Exists(string saveName)
		{
			return File.Exists(PathFor(saveName));
		}

		public static void Delete(string saveName)
		{
			string path = PathFor(saveName);
			if (File.Exists(path))
			{
				File.Delete(path);
			}
		}
	}
	internal static class LevelRebuild
	{
		private static Dictionary<string, PrefabRef> _modulePrefabs = new Dictionary<string, PrefabRef>();

		private static void BuildPrefabMap(LevelGenerator lg)
		{
			_modulePrefabs = new Dictionary<string, PrefabRef>();
			Level level = lg.Level;
			if (!((Object)(object)level == (Object)null))
			{
				AddAll(level.StartRooms);
				AddAll(level.ModulesNormal1);
				AddAll(level.ModulesNormal2);
				AddAll(level.ModulesNormal3);
				AddAll(level.ModulesPassage1);
				AddAll(level.ModulesPassage2);
				AddAll(level.ModulesPassage3);
				AddAll(level.ModulesDeadEnd1);
				AddAll(level.ModulesDeadEnd2);
				AddAll(level.ModulesDeadEnd3);
				AddAll(level.ModulesExtraction1);
				AddAll(level.ModulesExtraction2);
				AddAll(level.ModulesExtraction3);
				AddAll(level.ModulesMega1);
				AddAll(level.ModulesMega2);
				AddAll(level.ModulesMega3);
				AddAll(level.ModulesSpecial);
			}
		}

		private static void AddAll(List<PrefabRef> list)
		{
			if (list == null)
			{
				return;
			}
			foreach (PrefabRef item in list)
			{
				if (item == null)
				{
					continue;
				}
				string text = ((PrefabRef<GameObject>)(object)item).PrefabName;
				if (string.IsNullOrEmpty(text) && (Object)(object)((PrefabRef<GameObject>)(object)item).Prefab != (Object)null)
				{
					text = ((Object)((PrefabRef<GameObject>)(object)item).Prefab).name;
				}
				if (!string.IsNullOrEmpty(text))
				{
					string key = Clean(text);
					if (!_modulePrefabs.ContainsKey(key))
					{
						_modulePrefabs[key] = item;
					}
				}
			}
		}

		public static IEnumerator RebuildTiles(LevelGenerator lg, WorldSnapshot snap)
		{
			lg.waitingForSubCoroutine = true;
			BuildPrefabMap(lg);
			GridDto grid = snap.grid;
			lg.LevelWidth = Mathf.Max(1, grid.width);
			lg.LevelHeight = Mathf.Max(1, grid.height);
			lg.LevelGrid = new Tile[lg.LevelWidth, lg.LevelHeight];
			for (int i = 0; i < lg.LevelWidth; i++)
			{
				for (int j = 0; j < lg.LevelHeight; j++)
				{
					lg.LevelGrid[i, j] = new Tile
					{
						x = i,
						y = j,
						active = false
					};
				}
			}
			foreach (TileDto tile in grid.tiles)
			{
				if (tile.x >= 0 && tile.y >= 0 && tile.x < lg.LevelWidth && tile.y < lg.LevelHeight)
				{
					lg.LevelGrid[tile.x, tile.y] = new Tile
					{
						x = tile.x,
						y = tile.y,
						active = tile.active,
						first = tile.first,
						type = (Type)tile.type
					};
				}
			}
			lg.ModuleAmount = grid.tiles.Count;
			RebuildMegaTiles(lg, snap);
			List<PrefabRef> refs = new List<PrefabRef>();
			List<string> list = new List<string>();
			foreach (SceneObjectDto item in snap.level)
			{
				if (!(item.kind != "module") && !IsStartRoom(item.name) && _modulePrefabs.TryGetValue(item.name, out PrefabRef value) && value != null)
				{
					string resourcePath = ((PrefabRef<GameObject>)(object)value).ResourcePath;
					if (!string.IsNullOrEmpty(resourcePath) && !list.Contains(resourcePath))
					{
						refs.Add(value);
						list.Add(resourcePath);
					}
				}
			}
			if (refs.Count > 0)
			{
				yield return lg.PrefabsPrepare((IEnumerable<PrefabRef>)refs, (IEnumerable<string>)list, (IEnumerable<string>)null);
			}
			Plugin.Log.LogInfo((object)($"Rebuild: grid restored ({grid.tiles.Count} active tiles, {refs.Count} module prefabs, " + $"{lg.MegaTiles.Count} mega block(s))."));
			yield return null;
			lg.waitingForSubCoroutine = false;
		}

		private static void RebuildMegaTiles(LevelGenerator lg, WorldSnapshot snap)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			lg.MegaTiles.Clear();
			foreach (SceneObjectDto item in snap.level)
			{
				if (!(item.kind != "module") && item.moduleType == 5)
				{
					Tile[] array = MegaTilesAt(lg, item.gridX, item.gridY);
					if (array != null)
					{
						lg.MegaTiles.Add(new MegaTile
						{
							tiles = array
						});
					}
				}
			}
		}

		public static IEnumerator RebuildStartRoom(LevelGenerator lg, WorldSnapshot snap)
		{
			lg.waitingForSubCoroutine = true;
			SceneObjectDto sceneObjectDto = snap.level.FirstOrDefault((SceneObjectDto o) => o.kind == "module" && IsStartRoom(o.name));
			if (sceneObjectDto != null && _modulePrefabs.TryGetValue(sceneObjectDto.name, out PrefabRef value) && value != null)
			{
				GameObject val = Spawn(value, sceneObjectDto.pos.ToVector3(), Quaternion.Euler(sceneObjectDto.euler.ToVector3()));
				if ((Object)(object)val != (Object)null)
				{
					val.transform.parent = lg.LevelParent.transform;
				}
			}
			else
			{
				Plugin.Log.LogWarning((object)("Rebuild: start room prefab '" + sceneObjectDto?.name + "' not found."));
			}
			yield return null;
			lg.waitingForSubCoroutine = false;
		}

		public static IEnumerator RebuildModules(LevelGenerator lg, WorldSnapshot snap)
		{
			lg.waitingForSubCoroutine = true;
			lg.State = (LevelState)4;
			BuildPrefabMap(lg);
			int num = 0;
			int num2 = 0;
			foreach (SceneObjectDto item in snap.level)
			{
				if (!(item.kind != "module") && !IsStartRoom(item.name))
				{
					if (SpawnSavedModule(lg, item))
					{
						num++;
						continue;
					}
					num2++;
					Plugin.Log.LogWarning((object)$"Rebuild: could not spawn module '{item.name}' at ({item.gridX},{item.gridY}).");
				}
			}
			Plugin.Log.LogInfo((object)$"Rebuild: spawned {num} module(s), {num2} failed.");
			yield return null;
			lg.waitingForSubCoroutine = false;
		}

		public static bool SpawnSavedModule(LevelGenerator lg, SceneObjectDto dto)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (!_modulePrefabs.TryGetValue(dto.name, out PrefabRef value) || value == null)
			{
				return false;
			}
			GameObject val = Spawn(value, dto.pos.ToVector3(), Quaternion.Euler(dto.euler.ToVector3()));
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			val.transform.parent = lg.LevelParent.transform;
			Module component = val.GetComponent<Module>();
			if ((Object)(object)component == (Object)null)
			{
				return false;
			}
			int gridX = dto.gridX;
			int gridY = dto.gridY;
			component.GridX = gridX;
			component.GridY = gridY;
			if (dto.moduleType == 5)
			{
				Tile[] array = MegaTilesAt(lg, gridX, gridY);
				if (array != null)
				{
					Tile[] array2 = array;
					for (int i = 0; i < array2.Length; i++)
					{
						array2[i].module = component;
					}
					SetMegaConnections(lg, component, array);
					lg.ModulesSpawnedTarget++;
					return true;
				}
			}
			if (gridX >= 0 && gridY >= 0 && gridX < lg.LevelWidth && gridY < lg.LevelHeight)
			{
				Tile obj = lg.LevelGrid[gridX, gridY];
				bool first = obj.first;
				component.ModuleConnectionSet(lg.GridCheckActive(gridX, gridY + 1), lg.GridCheckActive(gridX, gridY - 1) || first, lg.GridCheckActive(gridX + 1, gridY), lg.GridCheckActive(gridX - 1, gridY), first);
				obj.module = component;
			}
			else
			{
				component.ModuleConnectionSet(true, true, true, true, false);
			}
			lg.ModulesSpawnedTarget++;
			return true;
		}

		private static Tile[]? MegaTilesAt(LevelGenerator lg, int x, int y)
		{
			if (x < 0 || y <= 0 || x + 1 >= lg.LevelWidth || y >= lg.LevelHeight)
			{
				return null;
			}
			return (Tile[]?)(object)new Tile[4]
			{
				lg.LevelGrid[x, y],
				lg.LevelGrid[x, y - 1],
				lg.LevelGrid[x + 1, y],
				lg.LevelGrid[x + 1, y - 1]
			};
		}

		private static void SetMegaConnections(LevelGenerator lg, Module module, Tile[] tiles)
		{
			bool flag = false;
			bool flag2 = false;
			bool flag3 = false;
			bool flag4 = false;
			bool flag5 = false;
			bool flag6 = false;
			bool flag7 = false;
			bool flag8 = false;
			bool flag9 = false;
			if (EdgeOpen(lg, tiles, tiles[0].x, tiles[0].y + 1))
			{
				flag2 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[2].x, tiles[2].y + 1))
			{
				flag3 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[2].x + 1, tiles[2].y))
			{
				flag4 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[3].x + 1, tiles[3].y))
			{
				flag5 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[3].x, tiles[3].y - 1))
			{
				flag6 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[1].x, tiles[1].y - 1))
			{
				flag7 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[1].x - 1, tiles[1].y))
			{
				flag8 = true;
			}
			if (EdgeOpen(lg, tiles, tiles[0].x - 1, tiles[0].y))
			{
				flag9 = true;
			}
			if (tiles[0].first || tiles[1].first || tiles[2].first || tiles[3].first)
			{
				flag = true;
				if (tiles[1].x == lg.LevelWidth / 2 && tiles[1].y == 0)
				{
					flag7 = true;
				}
				else if (tiles[3].x == lg.LevelWidth / 2 && tiles[3].y == 0)
				{
					flag6 = true;
				}
			}
			module.ModuleConnectionSetMega(flag3, flag2, flag4, flag5, flag6, flag7, flag9, flag8, flag);
		}

		private static bool EdgeOpen(LevelGenerator lg, Tile[] megaTiles, int x, int y)
		{
			if (!lg.GridCheckActive(x, y))
			{
				return false;
			}
			Tile val = lg.GridGetTile(x, y);
			if (val != null)
			{
				return !megaTiles.Contains(val);
			}
			return true;
		}

		private static GameObject? Spawn(PrefabRef pr, Vector3 pos, Quaternion rot)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (pr == null)
			{
				return null;
			}
			if (GameManager.instance.gameMode != 0)
			{
				return PhotonNetwork.InstantiateRoomObject(((PrefabRef<GameObject>)(object)pr).ResourcePath, pos, rot, (byte)0, (object[])null);
			}
			GameObject prefab = ((PrefabRef<GameObject>)(object)pr).Prefab;
			if (!((Object)(object)prefab != (Object)null))
			{
				return null;
			}
			return Object.Instantiate<GameObject>(prefab, pos, rot);
		}

		private static bool IsStartRoom(string name)
		{
			return name.StartsWith("Start Room", StringComparison.Ordinal);
		}

		private static string Clean(string name)
		{
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd();
		}
	}
	internal static class ProgressionSaver
	{
		public static bool SaveNow()
		{
			if (!GameState.IsHost)
			{
				Plugin.Log.LogWarning((object)"Save ignored: only the host can save the run.");
				return false;
			}
			if (!GameState.StatsReady)
			{
				Plugin.Log.LogWarning((object)"Save ignored: StatsManager is not ready yet.");
				return false;
			}
			try
			{
				StatsManager.instance.SaveFileSave();
				Plugin.Log.LogInfo((object)("Run saved to '" + StatsManager.instance.saveFileCurrent + "'."));
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to save run: {arg}");
				return false;
			}
		}
	}
	internal static class RestoreService
	{
		private static bool _restoring;

		public static bool RestoreNow()
		{
			if (!GameState.IsHost)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: only the host can load.");
				return false;
			}
			if (!GameState.StatsReady)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: StatsManager not ready.");
				return false;
			}
			string saveFileCurrent = StatsManager.instance.saveFileCurrent;
			WorldSnapshot worldSnapshot = InstantSaveStore.Read(saveFileCurrent);
			if (worldSnapshot == null)
			{
				Plugin.Log.LogWarning((object)("No Instant Save snapshot found for '" + saveFileCurrent + "'."));
				return false;
			}
			try
			{
				if ((Object)(object)Plugin.Instance == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Restore ignored: plugin instance not ready.");
					return false;
				}
				((MonoBehaviour)Plugin.Instance).StartCoroutine(WorldRestore.RestoreRoutine(worldSnapshot));
				Plugin.Log.LogInfo((object)("Instant Save restore started from '" + saveFileCurrent + "' [" + worldSnapshot.levelName + "]."));
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Instant Save restore failed: {arg}");
				return false;
			}
		}

		public static bool RestoreEntry(string saveName, SnapshotEntry entry)
		{
			if (_restoring)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: another restore is already running.");
				return false;
			}
			if (!GameState.IsHost)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: only the host can load.");
				return false;
			}
			if (!GameState.StatsReady)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: StatsManager not ready.");
				return false;
			}
			if (string.IsNullOrEmpty(saveName) || entry == null)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: no save/snapshot selected.");
				return false;
			}
			if (!SnapshotHistory.HasProgression(saveName, entry.ticks))
			{
				Plugin.Log.LogWarning((object)$"Restore ignored: snapshot {entry.ticks} has no .es3 copy (quotas/money cannot roll back).");
				return false;
			}
			if ((Object)(object)Plugin.Instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Restore ignored: plugin instance not ready.");
				return false;
			}
			if (!SnapshotHistory.CopyProgression(saveName, entry.ticks))
			{
				Plugin.Log.LogError((object)$"Restore failed: could not copy .es3 for snapshot {entry.ticks}.");
				return false;
			}
			SnapshotHistory.SetSelected(saveName, entry.ticks);
			_restoring = true;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(RestoreCoroutine(saveName, entry.ticks));
			Plugin.Log.LogInfo((object)$"Restore: requested snapshot {entry.ticks} [{entry.levelName}] for save '{saveName}'.");
			return true;
		}

		private static IEnumerator RestoreCoroutine(string saveName, long ticks)
		{
			try
			{
				float deadline = Time.realtimeSinceStartup + 20f;
				bool ready = false;
				while (Time.realtimeSinceStartup < deadline)
				{
					RunManager instance = RunManager.instance;
					if ((Object)(object)instance != (Object)null && !instance.restarting && !instance.SceneHandoffActive)
					{
						ready = true;
						break;
					}
					yield return (object)new WaitForSecondsRealtime(0.25f);
				}
				if (!ready)
				{
					Plugin.Log.LogWarning((object)"Restore: game stayed busy; restore aborted (selection kept).");
					SnapshotHistory.ClearSelected(saveName);
					yield break;
				}
				yield return null;
				if (!SnapshotHistory.CopyProgression(saveName, ticks))
				{
					Plugin.Log.LogError((object)"Restore: .es3 copy disappeared before load; aborting.");
					SnapshotHistory.ClearSelected(saveName);
					yield break;
				}
				if (!SemiFunc.IsMultiplayer())
				{
					SemiFunc.MenuActionSingleplayerGame(saveName, (List<string>)null);
				}
				else
				{
					RunManager.instance.ResetProgress();
					GameManager.instance.SetConnectRandom(false);
					StatsManager.instance.LoadGame(saveName, (List<string>)null);
					RunManager.instance.ChangeLevel(true, false, (ChangeLevelType)1);
				}
				Plugin.Log.LogInfo((object)$"Restore: load triggered from snapshot {ticks}.");
			}
			finally
			{
				_restoring = false;
			}
		}
	}
	internal static class SnapshotHistory
	{
		private const string HistoryDirName = "instant_history";

		private const string SnapshotPrefix = "snap_";

		private const string ProgressionPrefix = "save_";

		private const string MetaExtension = ".meta";

		private const string JsonExtension = ".json";

		private const string SelectedFileName = "selected.txt";

		private const string PreRestoreFileName = "save_prerestore.es3";

		public static string SaveFolder(string saveName)
		{
			return Path.Combine(Application.persistentDataPath, "saves", saveName);
		}

		public static string SaveFilePath(string saveName)
		{
			return Path.Combine(SaveFolder(saveName), saveName + ".es3");
		}

		public static string HistoryFolder(string saveName)
		{
			return Path.Combine(SaveFolder(saveName), "instant_history");
		}

		public static string ProgressionPath(string saveName, long ticks)
		{
			return Path.Combine(HistoryFolder(saveName), "save_" + ticks + ".es3");
		}

		public static SnapshotEntry? Add(string saveName, WorldSnapshot snapshot, string es3SourcePath, int maxEntries)
		{
			if (string.IsNullOrEmpty(saveName) || snapshot == null)
			{
				return null;
			}
			string text = HistoryFolder(saveName);
			Directory.CreateDirectory(text);
			long num = (snapshot.takenUtcTicks = ((snapshot.takenUtcTicks != 0L) ? snapshot.takenUtcTicks : DateTime.UtcNow.Ticks));
			string path = Path.Combine(text, "snap_" + num + ".json");
			string path2 = Path.Combine(text, "snap_" + num + ".meta");
			string destFileName = ProgressionPath(saveName, num);
			File.WriteAllText(path, JsonConvert.SerializeObject((object)snapshot, (Formatting)1));
			if (!string.IsNullOrEmpty(es3SourcePath) && File.Exists(es3SourcePath))
			{
				File.Copy(es3SourcePath, destFileName, overwrite: true);
			}
			else
			{
				Plugin.Log.LogWarning((object)$"Instant Save: no native .es3 at '{es3SourcePath}' - snapshot {num} has no progression copy.");
			}
			SnapshotEntry snapshotEntry = BuildEntry(saveName, num, snapshot);
			File.WriteAllText(path2, JsonConvert.SerializeObject((object)ToMeta(snapshotEntry), (Formatting)1));
			try
			{
				File.WriteAllText(Path.Combine(SaveFolder(saveName), "instantsave.json"), JsonConvert.SerializeObject((object)snapshot, (Formatting)1));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Instant Save: could not update instantsave.json: " + ex.Message));
			}
			Prune(saveName, maxEntries);
			return snapshotEntry;
		}

		public static List<SnapshotEntry> List(string saveName)
		{
			List<SnapshotEntry> list = new List<SnapshotEntry>();
			if (string.IsNullOrEmpty(saveName))
			{
				return list;
			}
			string path = HistoryFolder(saveName);
			if (!Directory.Exists(path))
			{
				return list;
			}
			string[] files = Directory.GetFiles(path, "snap_*.meta");
			foreach (string text in files)
			{
				try
				{
					SnapshotMeta snapshotMeta = JsonConvert.DeserializeObject<SnapshotMeta>(File.ReadAllText(text));
					if (snapshotMeta != null)
					{
						list.Add(new SnapshotEntry
						{
							ticks = snapshotMeta.ticks,
							levelName = (snapshotMeta.levelName ?? ""),
							runLevel = snapshotMeta.runLevel,
							currentHaul = snapshotMeta.currentHaul,
							haulGoal = snapshotMeta.haulGoal,
							valuableCount = snapshotMeta.valuableCount,
							playerCount = snapshotMeta.playerCount,
							hasProgression = HasProgression(saveName, snapshotMeta.ticks)
						});
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Instant Save: could not read '" + text + "': " + ex.Message));
				}
			}
			return list.OrderByDescending((SnapshotEntry e) => e.ticks).ToList();
		}

		public static WorldSnapshot? Read(string saveName, long ticks)
		{
			string path = Path.Combine(HistoryFolder(saveName), "snap_" + ticks + ".json");
			if (!File.Exists(path))
			{
				return null;
			}
			return JsonConvert.DeserializeObject<WorldSnapshot>(File.ReadAllText(path));
		}

		public static bool HasProgression(string saveName, long ticks)
		{
			return File.Exists(ProgressionPath(saveName, ticks));
		}

		public static bool CopyProgression(string saveName, long ticks)
		{
			string text = ProgressionPath(saveName, ticks);
			string text2 = SaveFilePath(saveName);
			if (!File.Exists(text))
			{
				return false;
			}
			try
			{
				Directory.CreateDirectory(SaveFolder(saveName));
				if (File.Exists(text2))
				{
					File.Copy(text2, Path.Combine(HistoryFolder(saveName), "save_prerestore.es3"), overwrite: true);
				}
				File.Copy(text, text2, overwrite: true);
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Instant Save: could not restore .es3 {ticks}: {arg}");
				return false;
			}
		}

		public static long? GetSelected(string saveName)
		{
			try
			{
				string path = Path.Combine(HistoryFolder(saveName), "selected.txt");
				if (!File.Exists(path))
				{
					return null;
				}
				long result;
				return long.TryParse(File.ReadAllText(path).Trim(), out result) ? new long?(result) : ((long?)null);
			}
			catch
			{
				return null;
			}
		}

		public static void SetSelected(string saveName, long ticks)
		{
			try
			{
				string text = HistoryFolder(saveName);
				Directory.CreateDirectory(text);
				File.WriteAllText(Path.Combine(text, "selected.txt"), ticks.ToString());
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Instant Save: could not persist selection: " + ex.Message));
			}
		}

		public static void ClearSelected(string saveName)
		{
			try
			{
				string path = Path.Combine(HistoryFolder(saveName), "selected.txt");
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}

		public static bool Delete(string saveName, long ticks, bool log = true)
		{
			if (string.IsNullOrEmpty(saveName) || ticks <= 0)
			{
				return false;
			}
			string path = HistoryFolder(saveName);
			string path2 = Path.Combine(path, "snap_" + ticks + ".json");
			string path3 = Path.Combine(path, "snap_" + ticks + ".meta");
			string path4 = ProgressionPath(saveName, ticks);
			TryDelete(path3);
			TryDelete(path2);
			TryDelete(path4);
			if (GetSelected(saveName) == ticks)
			{
				ClearSelected(saveName);
			}
			RefreshLegacyLatest(saveName);
			bool flag = !File.Exists(path3) && !File.Exists(path2) && !File.Exists(path4);
			if (log)
			{
				if (flag)
				{
					Plugin.Log.LogInfo((object)$"Instant Save: deleted snapshot {ticks} for '{saveName}'.");
				}
				else
				{
					Plugin.Log.LogWarning((object)$"Instant Save: could not fully delete snapshot {ticks} for '{saveName}'.");
				}
			}
			return flag;
		}

		public static int Clear(string saveName)
		{
			if (string.IsNullOrEmpty(saveName))
			{
				return 0;
			}
			string path = HistoryFolder(saveName);
			if (!Directory.Exists(path))
			{
				return 0;
			}
			int num = 0;
			try
			{
				string[] files = Directory.GetFiles(path, "snap_*.meta");
				for (int i = 0; i < files.Length; i++)
				{
					long num2 = ParseTicks(Path.GetFileNameWithoutExtension(files[i]));
					if (num2 > 0 && Delete(saveName, num2, log: false))
					{
						num++;
					}
				}
				files = Directory.GetFiles(path, "snap_*");
				for (int i = 0; i < files.Length; i++)
				{
					TryDelete(files[i]);
				}
				files = Directory.GetFiles(path, "save_*.es3");
				foreach (string path2 in files)
				{
					if (!string.Equals(Path.GetFileName(path2), "save_prerestore.es3", StringComparison.OrdinalIgnoreCase))
					{
						TryDelete(path2);
					}
				}
				ClearSelected(saveName);
				TryDelete(Path.Combine(SaveFolder(saveName), "instantsave.json"));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Instant Save: clear history failed: " + ex.Message));
			}
			Plugin.Log.LogInfo((object)$"Instant Save: cleared {num} snapshot(s) for '{saveName}'.");
			return num;
		}

		private static void RefreshLegacyLatest(string saveName)
		{
			try
			{
				string text = Path.Combine(SaveFolder(saveName), "instantsave.json");
				SnapshotEntry snapshotEntry = List(saveName).FirstOrDefault();
				if (snapshotEntry == null)
				{
					if (File.Exists(text))
					{
						File.Delete(text);
					}
					return;
				}
				string text2 = Path.Combine(HistoryFolder(saveName), "snap_" + snapshotEntry.ticks + ".json");
				if (File.Exists(text2))
				{
					File.Copy(text2, text, overwrite: true);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Instant Save: could not refresh instantsave.json: " + ex.Message));
			}
		}

		public static string CurrentOrLatestSaveName()
		{
			try
			{
				StatsManager instance = StatsManager.instance;
				string text = (((Object)(object)instance != (Object)null) ? instance.saveFileCurrent : null);
				if (!string.IsNullOrEmpty(text) && Directory.Exists(HistoryFolder(text)))
				{
					return text;
				}
			}
			catch
			{
			}
			string path = Path.Combine(Application.persistentDataPath, "saves");
			if (!Directory.Exists(path))
			{
				return "";
			}
			string result = "";
			DateTime dateTime = DateTime.MinValue;
			string[] directories = Directory.GetDirectories(path);
			foreach (string text2 in directories)
			{
				string path2 = Path.Combine(text2, "instant_history");
				if (Directory.Exists(path2))
				{
					DateTime lastWriteTimeUtc = Directory.GetLastWriteTimeUtc(path2);
					if (lastWriteTimeUtc > dateTime)
					{
						dateTime = lastWriteTimeUtc;
						result = Path.GetFileName(text2);
					}
				}
			}
			return result;
		}

		private static void Prune(string saveName, int maxEntries)
		{
			if (maxEntries <= 0)
			{
				return;
			}
			try
			{
				string text = HistoryFolder(saveName);
				var list = (from path in Directory.GetFiles(text, "snap_*.meta")
					select new
					{
						Path = path,
						Ticks = ParseTicks(Path.GetFileNameWithoutExtension(path))
					} into x
					where x.Ticks > 0
					orderby x.Ticks descending
					select x).ToList();
				for (int num = maxEntries; num < list.Count; num++)
				{
					long ticks = list[num].Ticks;
					TryDelete(list[num].Path);
					TryDelete(Path.Combine(text, "snap_" + ticks + ".json"));
					TryDelete(ProgressionPath(saveName, ticks));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Instant Save: history prune failed: " + ex.Message));
			}
		}

		private static void TryDelete(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}

		private static long ParseTicks(string fileNameWithoutExtension)
		{
			if (fileNameWithoutExtension.StartsWith("snap_", StringComparison.Ordinal))
			{
				if (!long.TryParse(fileNameWithoutExtension.Substring("snap_".Length), out var result))
				{
					return 0L;
				}
				return result;
			}
			return 0L;
		}

		private static SnapshotEntry BuildEntry(string saveName, long ticks, WorldSnapshot snapshot)
		{
			int currentHaul = 0;
			int haulGoal = 0;
			if (snapshot.round != null && snapshot.round.present)
			{
				currentHaul = snapshot.round.currentHaul;
				haulGoal = snapshot.round.haulGoal;
			}
			return new SnapshotEntry
			{
				ticks = ticks,
				levelName = (snapshot.levelName ?? ""),
				runLevel = snapshot.runLevel,
				currentHaul = currentHaul,
				haulGoal = haulGoal,
				valuableCount = ((snapshot.valuables != null) ? snapshot.valuables.Count : 0),
				playerCount = ((snapshot.players != null) ? snapshot.players.Count : 0),
				hasProgression = HasProgression(saveName, ticks)
			};
		}

		private static SnapshotMeta ToMeta(SnapshotEntry entry)
		{
			return new SnapshotMeta
			{
				ticks = entry.ticks,
				levelName = entry.levelName,
				runLevel = entry.runLevel,
				currentHaul = entry.currentHaul,
				haulGoal = entry.haulGoal,
				valuableCount = entry.valuableCount,
				playerCount = entry.playerCount
			};
		}
	}
	public sealed class SnapshotMeta
	{
		public long ticks { get; set; }

		public string levelName { get; set; } = "";

		public int? runLevel { get; set; }

		public int currentHaul { get; set; }

		public int haulGoal { get; set; }

		public int valuableCount { get; set; }

		public int playerCount { get; set; }
	}
	public sealed class SnapshotEntry
	{
		public long ticks;

		public string levelName = "";

		public int? runLevel;

		public int currentHaul;

		public int haulGoal;

		public int valuableCount;

		public int playerCount;

		public bool hasProgression;

		public DateTime TakenUtc => new DateTime(ticks, DateTimeKind.Utc);
	}
	public sealed class WorldSnapshot
	{
		public int version { get; set; } = 1;

		public string levelName { get; set; } = "";

		public long takenUtcTicks { get; set; }

		public int? runLevel { get; set; }

		public List<SceneObjectDto> level { get; set; } = new List<SceneObjectDto>();

		public List<ValuableDto> valuables { get; set; } = new List<ValuableDto>();

		public List<HaulerDto> haulers { get; set; } = new List<HaulerDto>();

		public List<ExtractionDto> extraction { get; set; } = new List<ExtractionDto>();

		public List<PlayerDto> players { get; set; } = new List<PlayerDto>();

		public RoundDto round { get; set; } = new RoundDto();

		public List<HingeDto> hinges { get; set; } = new List<HingeDto>();

		public GridDto grid { get; set; } = new GridDto();

		public ChargingStationDto? chargingStation { get; set; }
	}
	public sealed class ChargingStationDto
	{
		public int charge { get; set; }

		public int chargeTotal { get; set; }

		public int crystals { get; set; }
	}
	public sealed class SceneObjectDto
	{
		public string name { get; set; } = "";

		public string kind { get; set; } = "other";

		public Vec3Dto pos { get; set; } = new Vec3Dto();

		public Vec3Dto euler { get; set; } = new Vec3Dto();

		public int gridX { get; set; } = -1;

		public int gridY { get; set; } = -1;

		public int moduleType { get; set; } = -1;
	}
	public sealed class GridDto
	{
		public bool present { get; set; }

		public int width { get; set; }

		public int height { get; set; }

		public List<TileDto> tiles { get; set; } = new List<TileDto>();
	}
	public sealed class TileDto
	{
		public int x { get; set; }

		public int y { get; set; }

		public bool active { get; set; }

		public bool first { get; set; }

		public int connections { get; set; }

		public bool cTop { get; set; }

		public bool cRight { get; set; }

		public bool cBot { get; set; }

		public bool cLeft { get; set; }

		public int type { get; set; }
	}
	public sealed class ValuableDto
	{
		public string name { get; set; } = "";

		public string kind { get; set; } = "valuable";

		public Vec3Dto pos { get; set; } = new Vec3Dto();

		public Vec3Dto euler { get; set; } = new Vec3Dto();

		public float value { get; set; }

		public bool inHaul { get; set; }

		public string? itemAsset { get; set; }

		public string? instanceName { get; set; }

		public int battery { get; set; } = -1;

		public int batteryPct { get; set; } = -1;

		public string? inCart { get; set; }

		public Vec3Dto? inCartPos { get; set; }

		public Vec3Dto? inCartEuler { get; set; }
	}
	public sealed class HaulerDto
	{
		public string name { get; set; } = "";

		public Vec3Dto pos { get; set; } = new Vec3Dto();

		public float value { get; set; }

		public bool inHaul { get; set; }
	}
	public sealed class ExtractionDto
	{
		public string name { get; set; } = "";

		public Vec3Dto pos { get; set; } = new Vec3Dto();

		public int haulCurrent { get; set; }

		public int haulGoal { get; set; }

		public int state { get; set; }
	}
	public sealed class RoundDto
	{
		public bool present { get; set; }

		public int currentHaul { get; set; }

		public int totalHaul { get; set; }

		public int currentHaulMax { get; set; }

		public int haulGoal { get; set; }

		public int haulGoalMax { get; set; }

		public int extractionPoints { get; set; }

		public int extractionPointSurplus { get; set; }

		public int extractionPointsCompleted { get; set; }

		public int extractionHaulGoal { get; set; }

		public bool extractionPointActive { get; set; }

		public bool allExtractionPointsCompleted { get; set; }
	}
	public sealed class HingeDto
	{
		public string name { get; set; } = "";

		public Vec3Dto pivot { get; set; } = new Vec3Dto();

		public Vec3Dto pos { get; set; } = new Vec3Dto();

		public Vec3Dto euler { get; set; } = new Vec3Dto();

		public bool closed { get; set; }

		public bool broken { get; set; }

		public bool dead { get; set; }
	}
	public sealed class PlayerDto
	{
		public string steamId { get; set; } = "";

		public string name { get; set; } = "";

		public Vec3Dto pos { get; set; } = new Vec3Dto();

		public Vec3Dto euler { get; set; } = new Vec3Dto();

		public int health { get; set; }

		public List<InventoryItemDto> inventory { get; set; } = new List<InventoryItemDto>();
	}
	public sealed class InventoryItemDto
	{
		public int spot { get; set; }

		public string instanceName { get; set; } = "";

		public string itemAsset { get; set; } = "";

		public int batteryPct { get; set; } = -1;
	}
	public sealed class Vec3Dto
	{
		public float x { get; set; }

		public float y { get; set; }

		public float z { get; set; }

		public Vec3Dto()
		{
		}

		public Vec3Dto(Vector3 v)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			x = v.x;
			y = v.y;
			z = v.z;
		}

		public Vector3 ToVector3()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(x, y, z);
		}
	}
	internal sealed class SnapshotScheduler : MonoBehaviour
	{
		private const float EntrySnapshotDelay = 20f;

		private const float DeadRetryDelay = 30f;

		private float _nextCaptureAt;

		private string _levelKey = "";

		private bool _entrySnapshotPending;

		private bool _loggedWaiting;

		private void Update()
		{
			PluginConfig pluginConfig = Plugin.Instance?.ModConfig;
			if (pluginConfig == null || !pluginConfig.AutoSnapshotEnabled.Value)
			{
				return;
			}
			float num = Mathf.Max(60f, (float)pluginConfig.AutoSnapshotIntervalSeconds.Value);
			string text = CurrentLevelKey();
			if (text != _levelKey)
			{
				_levelKey = text;
				_entrySnapshotPending = pluginConfig.SnapshotOnLevelEntry.Value;
				_nextCaptureAt = Time.time + (_entrySnapshotPending ? 20f : num);
				_loggedWaiting = false;
			}
			else
			{
				if (!GameState.IsHost || !GameState.StatsReady || !GameState.InRunLevel || !GameState.LevelGenerated)
				{
					return;
				}
				if (!GameState.LocalPlayerAlive)
				{
					_nextCaptureAt = Mathf.Max(_nextCaptureAt, Time.time + 30f);
				}
				else if (_nextCaptureAt <= 0f)
				{
					_nextCaptureAt = Time.time + num;
				}
				else
				{
					if (Time.time < _nextCaptureAt)
					{
						return;
					}
					if (InstantSaveService.SaveNow(auto: true))
					{
						_loggedWaiting = false;
						_entrySnapshotPending = false;
						_nextCaptureAt = Time.time + num;
						return;
					}
					_nextCaptureAt = Time.time + Mathf.Min(30f, num);
					if (!_loggedWaiting)
					{
						_loggedWaiting = true;
						Plugin.Log.LogWarning((object)"Auto-snapshot is waiting for a saveable moment (host in a level, save file ready).");
					}
				}
			}
		}

		public void OnManualSave()
		{
			float num = Mathf.Max(60f, (float)((Plugin.Instance?.ModConfig)?.AutoSnapshotIntervalSeconds.Value ?? 600));
			_entrySnapshotPending = false;
			_nextCaptureAt = Time.time + num;
		}

		private static string CurrentLevelKey()
		{
			RunManager instance = RunManager.instance;
			return (((Object)(object)instance != (Object)null && (Object)(object)instance.levelCurrent != (Object)null) ? ((Object)instance.levelCurrent).name : "?") + "#" + (((Object)(object)StatsManager.instance != (Object)null) ? StatsManager.instance.GetRunStatLevel() : (-1));
		}
	}
	internal static class WorldCapture
	{
		public static WorldSnapshot Capture()
		{
			WorldSnapshot obj = new WorldSnapshot
			{
				levelName = (((Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent != (Object)null) ? ((Object)RunManager.instance.levelCurrent).name : "?"),
				takenUtcTicks = DateTime.UtcNow.Ticks,
				runLevel = (((Object)(object)StatsManager.instance != (Object)null) ? new int?(StatsManager.instance.GetRunStatLevel()) : ((int?)null))
			};
			CaptureLevelObjects(obj);
			CaptureGrid(obj);
			CaptureGrabbables(obj);
			CaptureHaulers(obj);
			CaptureExtraction(obj);
			CaptureRound(obj);
			CaptureHinges(obj);
			CapturePlayers(obj);
			CaptureChargingStation(obj);
			return obj;
		}

		private static int BatteryPercent(Component? component)
		{
			if ((Object)(object)component != (Object)null)
			{
				ItemBattery component2 = component.GetComponent<ItemBattery>();
				if (component2 != null)
				{
					return Mathf.RoundToInt(Mathf.Clamp(component2.batteryLife, 0f, 100f));
				}
			}
			return -1;
		}

		private static void CaptureChargingStation(WorldSnapshot snap)
		{
			ChargingStation instance = ChargingStation.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				snap.chargingStation = new ChargingStationDto
				{
					charge = instance.chargeInt,
					chargeTotal = instance.chargeTotal,
					crystals = ((instance.crystals != null) ? instance.crystals.Count : 0)
				};
				Plugin.Log.LogInfo((object)($"Instant Save: captured charging station ({snap.chargingStation.charge} crystal(s), " + $"{snap.chargingStation.chargeTotal}% charge)."));
			}
		}

		private static void CaptureLevelObjects(WorldSnapshot snap)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			LevelGenerator instance = LevelGenerator.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.LevelParent == (Object)null)
			{
				return;
			}
			foreach (Transform item in instance.LevelParent.transform)
			{
				Transform val = item;
				Module component = ((Component)val).GetComponent<Module>();
				string kind = (((Object)(object)component != (Object)null) ? "module" : (((Object)(object)((Component)val).GetComponent<ModuleConnectObject>() != (Object)null) ? "connect" : "other"));
				snap.level.Add(new SceneObjectDto
				{
					name = Clean(((Object)val).name),
					kind = kind,
					pos = new Vec3Dto(val.position),
					euler = new Vec3Dto(val.eulerAngles),
					gridX = (((Object)(object)component != (Object)null) ? component.GridX : (-1)),
					gridY = (((Object)(object)component != (Object)null) ? component.GridY : (-1)),
					moduleType = ModuleTileType(instance, component)
				});
			}
		}

		private static int ModuleTileType(LevelGenerator lg, Module? module)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected I4, but got Unknown
			if ((Object)(object)module == (Object)null || lg.LevelGrid == null)
			{
				return -1;
			}
			int gridX = module.GridX;
			int gridY = module.GridY;
			if (gridX < 0 || gridY < 0 || gridX >= lg.LevelWidth || gridY >= lg.LevelHeight)
			{
				return -1;
			}
			Tile val = lg.LevelGrid[gridX, gridY];
			if (val == null)
			{
				return -1;
			}
			return (int)val.type;
		}

		private static void CaptureGrid(WorldSnapshot snap)
		{
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Expected I4, but got Unknown
			LevelGenerator instance = LevelGenerator.Instance;
			if ((Object)(object)instance == (Object)null || instance.LevelGrid == null)
			{
				return;
			}
			snap.grid.present = true;
			snap.grid.width = instance.LevelWidth;
			snap.grid.height = instance.LevelHeight;
			for (int i = 0; i < instance.LevelWidth; i++)
			{
				for (int j = 0; j < instance.LevelHeight; j++)
				{
					Tile val = instance.LevelGrid[i, j];
					if (val != null && val.active)
					{
						snap.grid.tiles.Add(new TileDto
						{
							x = i,
							y = j,
							active = val.active,
							first = val.first,
							connections = val.connections,
							cTop = val.connectedTop,
							cRight = val.connectedRight,
							cBot = val.connectedBot,
							cLeft = val.connectedLeft,
							type = (int)val.type
						});
					}
				}
			}
		}

		private static void CaptureGrabbables(WorldSnapshot snap)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<PhysGrabObject, PhysGrabObject> dictionary = BuildCartContainment();
			PhysGrabObject[] array = Object.FindObjectsOfType<PhysGrabObject>();
			foreach (PhysGrabObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ValuableObject component = ((Component)val).GetComponent<ValuableObject>();
				ItemAttributes component2 = ((Component)val).GetComponent<ItemAttributes>();
				string text = (((Object)(object)((Component)val).GetComponent<PhysGrabCart>() != (Object)null) ? "cart" : (((Object)(object)component != (Object)null) ? "valuable" : (((Object)(object)component2 != (Object)null) ? "item" : null)));
				if (text != null)
				{
					ItemBattery component3 = ((Component)val).GetComponent<ItemBattery>();
					ValuableDto valuableDto = new ValuableDto
					{
						name = Clean(((Object)val).name),
						kind = text,
						pos = new Vec3Dto(((Component)val).transform.position),
						euler = new Vec3Dto(((Component)val).transform.eulerAngles),
						value = (((Object)(object)component != (Object)null) ? component.dollarValueCurrent : 0f),
						inHaul = (text == "valuable" && (Object)(object)RoundDirector.instance != (Object)null && RoundDirector.instance.dollarHaulList != null && RoundDirector.instance.dollarHaulList.Contains(((Component)val).gameObject)),
						itemAsset = (((Object)(object)component2 != (Object)null && (Object)(object)component2.item != (Object)null) ? ((Object)component2.item).name : null),
						instanceName = (((Object)(object)component2 != (Object)null) ? component2.instanceName : null),
						battery = (((Object)(object)component3 != (Object)null) ? component3.batteryLifeInt : (-1)),
						batteryPct = (((Object)(object)component3 != (Object)null) ? Mathf.RoundToInt(Mathf.Clamp(component3.batteryLife, 0f, 100f)) : (-1))
					};
					if (text != "cart" && dictionary.TryGetValue(val, out var value) && (Object)(object)value != (Object)null)
					{
						Transform transform = ((Component)value).transform;
						valuableDto.inCart = Clean(((Object)value).name);
						valuableDto.inCartPos = new Vec3Dto(transform.InverseTransformPoint(((Component)val).transform.position));
						Quaternion val2 = Quaternion.Inverse(transform.rotation) * ((Component)val).transform.rotation;
						valuableDto.inCartEuler = new Vec3Dto(((Quaternion)(ref val2)).eulerAngles);
					}
					snap.valuables.Add(valuableDto);
				}
			}
		}

		private static void CaptureHaulers(WorldSnapshot snap)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			ItemValuableBox[] array = Object.FindObjectsOfType<ItemValuableBox>();
			foreach (ItemValuableBox val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					snap.haulers.Add(new HaulerDto
					{
						name = Clean(((Object)val).name),
						pos = new Vec3Dto(((Component)val).transform.position),
						value = val.CurrentValue,
						inHaul = ((Object)(object)RoundDirector.instance != (Object)null && RoundDirector.instance.valuableBoxHaulList != null && RoundDirector.instance.valuableBoxHaulList.Contains(val))
					});
				}
			}
			Plugin.Log.LogInfo((object)$"Instant Save: captured {snap.haulers.Count} Hauler(s).");
		}

		private static Dictionary<PhysGrabObject, PhysGrabObject> BuildCartContainment()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<PhysGrabObject, PhysGrabObject> dictionary = new Dictionary<PhysGrabObject, PhysGrabObject>();
			PhysGrabCart[] array = Object.FindObjectsOfType<PhysGrabCart>();
			foreach (PhysGrabCart val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.inCart == (Object)null)
				{
					continue;
				}
				PhysGrabObject component = ((Component)val).GetComponent<PhysGrabObject>();
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				Transform inCart = val.inCart;
				Collider[] array2 = Physics.OverlapBox(inCart.position, inCart.localScale / 2f, inCart.rotation);
				foreach (Collider val2 in array2)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						PhysGrabObject componentInParent = ((Component)val2).GetComponentInParent<PhysGrabObject>();
						if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent != (Object)(object)component && !dictionary.ContainsKey(componentInParent))
						{
							dictionary[componentInParent] = component;
						}
					}
				}
			}
			return dictionary;
		}

		private static void CaptureExtraction(WorldSnapshot snap)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected I4, but got Unknown
			ExtractionPoint[] array = Object.FindObjectsOfType<ExtractionPoint>();
			foreach (ExtractionPoint val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					snap.extraction.Add(new ExtractionDto
					{
						name = Clean(((Object)val).name),
						pos = new Vec3Dto(((Component)val).transform.position),
						haulCurrent = val.haulCurrent,
						haulGoal = val.haulGoal,
						state = (int)val.currentState
					});
				}
			}
		}

		private static void CaptureRound(WorldSnapshot snap)
		{
			RoundDirector instance = RoundDirector.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				snap.round = new RoundDto
				{
					present = true,
					currentHaul = instance.currentHaul,
					totalHaul = instance.totalHaul,
					currentHaulMax = instance.currentHaulMax,
					haulGoal = instance.haulGoal,
					haulGoalMax = instance.haulGoalMax,
					extractionPoints = instance.extractionPoints,
					extractionPointSurplus = instance.extractionPointSurplus,
					extractionPointsCompleted = instance.extractionPointsCompleted,
					extractionHaulGoal = instance.extractionHaulGoal,
					extractionPointActive = instance.extractionPointActive,
					allExtractionPointsCompleted = instance.allExtractionPointsCompleted
				};
			}
		}

		private static void CaptureHinges(WorldSnapshot snap)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			PhysGrabHinge[] array = Object.FindObjectsOfType<PhysGrabHinge>();
			foreach (PhysGrabHinge val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Vector3 v = (((Object)(object)val.hingePoint != (Object)null) ? val.hingePoint.position : ((Component)val).transform.position);
					snap.hinges.Add(new HingeDto
					{
						name = Clean(((Object)val).name),
						pivot = new Vec3Dto(v),
						pos = new Vec3Dto(((Component)val).transform.position),
						euler = new Vec3Dto(((Component)val).transform.eulerAngles),
						closed = val.closed,
						broken = val.broken,
						dead = val.dead
					});
				}
			}
		}

		private static void CapturePlayers(WorldSnapshot snap)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				return;
			}
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if (!((Object)(object)player == (Object)null))
				{
					PlayerDto playerDto = new PlayerDto
					{
						steamId = (player.steamID ?? ""),
						name = (player.playerName ?? ""),
						pos = new Vec3Dto(((Component)player).transform.position),
						euler = new Vec3Dto(((Component)player).transform.eulerAngles),
						health = (((Object)(object)player.playerHealth != (Object)null) ? player.playerHealth.health : 0)
					};
					CaptureInventory(player, playerDto);
					snap.players.Add(playerDto);
				}
			}
		}

		private static void CaptureInventory(PlayerAvatar player, PlayerDto dto)
		{
			Inventory instance = Inventory.instance;
			if (player.isLocal && (Object)(object)instance != (Object)null)
			{
				foreach (InventorySpot allSpot in instance.GetAllSpots())
				{
					if (!((Object)(object)allSpot == (Object)null) && allSpot.IsOccupied())
					{
						ItemEquippable currentItem = allSpot.CurrentItem;
						ItemAttributes val = (((Object)(object)currentItem != (Object)null) ? ((Component)currentItem).GetComponent<ItemAttributes>() : null);
						if (!((Object)(object)val == (Object)null))
						{
							dto.inventory.Add(new InventoryItemDto
							{
								spot = allSpot.inventorySpotIndex,
								instanceName = (val.instanceName ?? ""),
								itemAsset = (((Object)(object)val.item != (Object)null) ? ((Object)val.item).name : ""),
								batteryPct = BatteryPercent((Component?)(object)currentItem)
							});
						}
					}
				}
				if (dto.inventory.Count > 0)
				{
					return;
				}
			}
			StatsManager instance2 = StatsManager.instance;
			if ((Object)(object)instance2 == (Object)null || string.IsNullOrEmpty(player.steamID))
			{
				return;
			}
			ItemEquippable[] array = Object.FindObjectsOfType<ItemEquippable>();
			for (int i = 0; i < 3; i++)
			{
				if (!(i switch
				{
					1 => instance2.playerInventorySpot2, 
					0 => instance2.playerInventorySpot1, 
					_ => instance2.playerInventorySpot3, 
				}).TryGetValue(player.steamID, out var value) || value == -1)
				{
					continue;
				}
				ItemEquippable[] array2 = array;
				foreach (ItemEquippable val2 in array2)
				{
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					ItemAttributes component = ((Component)val2).GetComponent<ItemAttributes>();
					if (!((Object)(object)component == (Object)null) && !string.IsNullOrEmpty(component.instanceName) && component.instanceName.GetHashCode() == value)
					{
						PlayerAvatar ownerPlayerAvatar = val2.GetOwnerPlayerAvatar();
						if (!((Object)(object)ownerPlayerAvatar != (Object)null) || !((Object)(object)ownerPlayerAvatar != (Object)(object)player))
						{
							dto.inventory.Add(new InventoryItemDto
							{
								spot = i,
								instanceName = component.instanceName,
								itemAsset = (((Object)(object)component.item != (Object)null) ? ((Object)component.item).name : ""),
								batteryPct = BatteryPercent((Component?)(object)val2)
							});
							break;
						}
					}
				}
			}
		}

		private static string Clean(string name)
		{
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd();
		}
	}
	internal static class WorldRestore
	{
		private readonly struct ValuableSource
		{
			public readonly GameObject Prefab;

			public readonly string ResourcePath;

			public ValuableSource(GameObject prefab, string resourcePath)
			{
				Prefab = prefab;
				ResourcePath = resourcePath;
			}
		}

		private const float ImpactGraceSeconds = 3f;

		private const int PlayerHoldFrames = 12;

		private static readonly List<(PhysGrabObject pgo, Vector3 pos, Quaternion rot)> HeldHinges = new List<(PhysGrabObject, Vector3, Quaternion)>();

		private static readonly List<(PhysGrabObject inst, PhysGrabObject cart, Vector3 localPos, Quaternion localRot)> HeldCartItems = new List<(PhysGrabObject, PhysGrabObject, Vector3, Quaternion)>();

		public static IEnumerator RestoreRoutine(WorldSnapshot snap)
		{
			RestoreGrabbables(snap);
			RestoreHaulers(snap);
			RefreshEnemies();
			RestoreExtraction(snap);
			RestoreChargingStation(snap);
			RestoreHinges(snap);
			RestoreHealth(snap);
			RestorePlayers(snap);
			yield return (object)new WaitForSeconds(0.5f);
			RestoreInventories(snap);
			for (int i = 0; i < 12; i++)
			{
				HoldLocalPlayer(snap);
				HoldHinges();
				HoldCartItems();
				yield return (object)new WaitForFixedUpdate();
			}
			ReapplyRoundAccounting(snap);
			for (int i = 0; i < 8; i++)
			{
				yield return (object)new WaitForSecondsRealtime(0.25f);
				ReapplyRoundAccounting(snap, log: false);
			}
			Plugin.Log.LogInfo((object)"Restore: complete.");
		}

		public static IEnumerator CrossSessionRestore(WorldSnapshot snap)
		{
			ClearSpawnedValuables();
			yield return null;
			yield return RestoreRoutine(snap);
		}

		private static void ClearSpawnedValuables()
		{
			int num = 0;
			ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>();
			foreach (ValuableObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (SemiFunc.IsMultiplayer())
				{
					if (PhotonNetwork.IsMasterClient)
					{
						PhotonNetwork.Destroy(((Component)val).gameObject);
						num++;
					}
				}
				else
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
					num++;
				}
			}
			Plugin.Log.LogInfo((object)$"Cross-session: cleared {num} generator-spawned valuable(s).");
		}

		public static void RestorePlayers(WorldSnapshot snap)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				return;
			}
			int num = 0;
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if (!((Object)(object)player == (Object)null))
				{
					PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID);
					if (playerDto != null)
					{
						player.Spawn(playerDto.pos.ToVector3(), Quaternion.Euler(playerDto.euler.ToVector3()), false);
						ZeroVelocity(((Component)player).GetComponent<Rigidbody>());
						num++;
					}
				}
			}
			Plugin.Log.LogInfo((object)$"Restore: teleported {num} player(s) to saved positions.");
		}

		private static void HoldLocalPlayer(WorldSnapshot snap)
		{
			//IL_0087: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				return;
			}
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if ((Object)(object)player == (Object)null || !player.isLocal)
				{
					continue;
				}
				PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID);
				if (playerDto != null)
				{
					Vector3 position = playerDto.pos.ToVector3();
					Quaternion rotation = Quaternion.Euler(playerDto.euler.ToVector3());
					if ((Object)(object)PlayerController.instance != (Object)null)
					{
						((Component)PlayerController.instance).transform.position = position;
						((Component)PlayerController.instance).transform.rotation = rotation;
					}
					Rigidbody component = ((Component)player).GetComponent<Rigidbody>();
					if ((Object)(object)component != (Object)null)
					{
						component.position = position;
						ZeroVelocity(component);
					}
					((Component)player).transform.position = position;
				}
			}
		}

		public static void RestoreHealth(WorldSnapshot snap)
		{
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				return;
			}
			int num = 0;
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if ((Object)(object)player == (Object)null || (Object)(object)player.playerHealth == (Object)null)
				{
					continue;
				}
				PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID);
				if (playerDto != null)
				{
					int health = player.playerHealth.health;
					int health2 = playerDto.health;
					if (health2 > health)
					{
						player.playerHealth.Heal(health2 - health, false);
					}
					else if (health2 < health && health2 > 0)
					{
						player.playerHealth.Hurt(health - health2, false, -1, true);
					}
					num++;
				}
			}
			Plugin.Log.LogInfo((object)$"Restore: set health on {num} player(s).");
		}

		public static void RestoreInventories(WorldSnapshot snap)
		{
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				return;
			}
			List<ItemEquippable> list = Object.FindObjectsOfType<ItemEquippable>().ToList();
			HashSet<ItemEquippable> hashSet = new HashSet<ItemEquippable>();
			int num = 0;
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if ((Object)(object)player == (Object)null)
				{
					continue;
				}
				PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID);
				if (playerDto?.inventory == null || playerDto.inventory.Count == 0)
				{
					continue;
				}
				foreach (InventoryItemDto item in playerDto.inventory)
				{
					if (item.spot < 0 || item.spot > 2)
					{
						continue;
					}
					ItemEquippable val = FindInventoryItem(list, hashSet, item, player);
					if ((Object)(object)val == (Object)null && !string.IsNullOrEmpty(item.itemAsset))
					{
						val = RecreateInventoryItem(item, player);
						if ((Object)(object)val != (Object)null)
						{
							list.Add(val);
						}
					}
					if ((Object)(object)val == (Object)null)
					{
						Plugin.Log.LogWarning((object)("Restore: could not find/re-create item '" + item.instanceName + "' " + $"({item.itemAsset}) for slot {item.spot} of '{player.playerName}'."));
						continue;
					}
					hashSet.Add(val);
					ItemAttributes component = ((Component)val).GetComponent<ItemAttributes>();
					if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(item.instanceName))
					{
						if (string.IsNullOrEmpty(component.instanceName))
						{
							component.instanceName = item.instanceName;
						}
						else if (component.instanceName != item.instanceName)
						{
							Plugin.Log.LogWarning((object)($"Restore: slot {item.spot} matched '{component.instanceName}' " + "instead of saved '" + item.instanceName + "'."));
						}
					}
					ApplyBattery(((Component)val).GetComponent<ItemBattery>(), item.batteryPct, -1, ((Object)(object)component != (Object)null) ? component.instanceName : item.instanceName);
					if (player.isLocal && (Object)(object)Inventory.instance != (Object)null)
					{
						InventorySpot spotByIndex = Inventory.instance.GetSpotByIndex(item.spot);
						if ((Object)(object)spotByIndex != (Object)null && spotByIndex.IsOccupied() && (Object)(object)spotByIndex.CurrentItem == (Object)(object)val)
						{
							continue;
						}
					}
					int num2 = -1;
					if (SemiFunc.IsMultiplayer() && (Object)(object)player.physGrabber != (Object)null && (Object)(object)player.physGrabber.photonView != (Object)null)
					{
						num2 = player.physGrabber.photonView.ViewID;
					}
					val.RequestEquip(item.spot, num2);
					if (!string.IsNullOrEmpty(item.instanceName))
					{
						StatsManager.instance.PlayerInventoryUpdate(player.steamID, item.instanceName, item.spot, SemiFunc.IsMultiplayer());
					}
					num++;
				}
			}
			Plugin.Log.LogInfo((object)$"Restore: re-equipped {num} inventory item(s).");
		}

		private static ItemEquippable? FindInventoryItem(List<ItemEquippable> items, HashSet<ItemEquippable> used, InventoryItemDto dto, PlayerAvatar player)
		{
			ItemEquippable val = null;
			foreach (ItemEquippable item in items)
			{
				if ((Object)(object)item == (Object)null || used.Contains(item))
				{
					continue;
				}
				ItemAttributes component = ((Component)item).GetComponent<ItemAttributes>();
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				PlayerAvatar ownerPlayerAvatar = item.GetOwnerPlayerAvatar();
				if (!((Object)(object)ownerPlayerAvatar != (Object)null) || !((Object)(object)ownerPlayerAvatar != (Object)(object)player))
				{
					if (!string.IsNullOrEmpty(dto.instanceName) && component.instanceName == dto.instanceName)
					{
						return item;
					}
					if ((Object)(object)val == (Object)null && !string.IsNullOrEmpty(dto.itemAsset) && (Object)(object)component.item != (Object)null && ((Object)component.item).name == dto.itemAsset)
					{
						val = item;
					}
				}
			}
			return val;
		}

		private static ItemEquippable? RecreateInventoryItem(InventoryItemDto dto, PlayerAvatar player)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)StatsManager.instance == (Object)null || !StatsManager.instance.itemDictionary.TryGetValue(dto.itemAsset, out var value) || (Object)(object)value == (Object)null || value.prefab == null)
			{
				return null;
			}
			Vector3 val = (((Object)(object)player != (Object)null) ? ((Component)player).transform.position : Vector3.zero);
			Quaternion val2 = (((Object)(object)player != (Object)null) ? ((Component)player).transform.rotation : Quaternion.identity);
			GameObject val3 = (SemiFunc.IsMultiplayer() ? PhotonNetwork.InstantiateRoomObject(((PrefabRef<GameObject>)(object)value.prefab).ResourcePath, val, val2, (byte)0, (object[])null) : Object.Instantiate<GameObject>(((PrefabRef<GameObject>)(object)value.prefab).Prefab, val, val2));
			if ((Object)(object)val3 == (Object)null)
			{
				return null;
			}
			ItemAttributes component = val3.GetComponent<ItemAttributes>();
			if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(dto.instanceName))
			{
				component.instanceName = dto.instanceName;
			}
			ApplyBattery(val3.GetComponent<ItemBattery>(), dto.batteryPct, -1, dto.instanceName);
			return val3.GetComponent<ItemEquippable>();
		}

		public static void RefreshEnemies()
		{
			EnemyDirector instance = EnemyDirector.instance;
			if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null)
			{
				return;
			}
			int num = 0;
			foreach (EnemyParent item in instance.enemiesSpawned.ToList())
			{
				if (!((Object)(object)item == (Object)null))
				{
					try
					{
						item.Despawn();
						num++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Restore: enemy despawn failed: " + ex.Message));
					}
				}
			}
			Plugin.Log.LogInfo((object)$"Restore: refreshed (despawned) {num} enemy group(s).");
		}

		public static void RestoreExtraction(WorldSnapshot snap)
		{
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: 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_0150: 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_0157: Unknown result type (might be due to invalid IL or missing references)
			RoundDirector instance = RoundDirector.instance;
			if ((Object)(object)instance != (Object)null && snap.round != null && snap.round.present)
			{
				instance.currentHaul = snap.round.currentHaul;
				instance.totalHaul = snap.round.totalHaul;
				instance.currentHaulMax = snap.round.currentHaulMax;
				instance.haulGoal = snap.round.haulGoal;
				instance.haulGoalMax = snap.round.haulGoalMax;
				instance.extractionPoints = snap.round.extractionPoints;
				instance.extractionPointSurplus = snap.round.extractionPointSurplus;
				instance.extractionPointsCompleted = snap.round.extractionPointsCompleted;
				instance.extractionHaulGoal = snap.round.extractionHaulGoal;
				instance.allExtractionPointsCompleted = snap.round.allExtractionPointsCompleted;
			}
			List<ExtractionPoint> list = Object.FindObjectsOfType<ExtractionPoint>().ToList();
			HashSet<ExtractionPoint> hashSet = new HashSet<ExtractionPoint>();
			int num = 0;
			foreach (ExtractionDto item in snap.extraction)
			{
				Vector3 val = item.pos.ToVector3();
				ExtractionPoint val2 = null;
				float num2 = float.MaxValue;
				foreach (ExtractionPoint item2 in list)
				{
					if (!((Object)(object)item2 == (Object)null) && !hashSet.Contains(item2))
					{
						Vector3 val3 = ((Component)item2).transform.position - val;
						float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
						if (sqrMagnitude < num2)
						{
							num2 = sqrMagnitude;
							val2 = item2;
						}
					}
				}
				if ((Object)(object)val2 != (Object)null)
				{
					val2.UpdateLock(false);
					val2.haulGoal = item.haulGoal;
					if (item.state == 7)
					{
						MarkExtractionComplete(val2);
					}
					else
					{
						DriveExtractionState(val2, (State)1);
					}
					hashSet.Add(val2);
					num++;
				}
			}
			foreach (ExtractionPoint item3 in list)
			{
				if ((Object)(object)item3 != (Object)null)
				{
					item3.UpdateLock(false);
				}
			}
			if ((Object)(object)instance != (Object)null)
			{
				instance.extractionPointActive = false;
				instance.extractionPointCurrent = null;
			}
			Plugin.Log.LogInfo((object)$"Restore: reset {num} extraction point(s) + round accounting.");
		}

		private static void MarkExtractionComplete(ExtractionPoint ep)
		{
			ep.taxReturn = true;
			DriveExtractionState(ep, (State)7);
			if ((Object)(object)ep.platform != (Object)null)
			{
				((Component)ep.platform).gameObject.SetActive(false);
			}
		}

		private static void DriveExtractionState(ExtractionPoint ep, State state)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PhotonView component = ((Component)ep).GetComponent<PhotonView>();
				if ((Object)(object)component != (Object)null && SemiFunc.IsMultiplayer())
				{
					component.RPC("StateSetRPC", (RpcTarget)0, new object[1] { state });
				}
				else
				{
					ep.StateSetRPC(state, default(PhotonMessageInfo));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Restore: extraction StateSet failed: " + ex.Message));
			}
		}

		public static void ReapplyRoundAccounting(WorldSnapshot snap, bool log = true)
		{
			RoundDirector instance = RoundDirector.instance;
			if (!((Object)(object)instance == (Object)null) && snap.round != null && snap.round.present)
			{
				instance.haulGoal = snap.round.haulGoal;
				instance.haulGoalMax = snap.round.haulGoalMax;
				instance.extractionPoints = snap.round.extractionPoints;
				instance.extractionPointSurplus = snap.round.extractionPointSurplus;
				instance.extractionPointsCompleted = snap.round.extractionPointsCompleted;
				instance.extractionHaulGoal = snap.round.extractionHaulGoal;
				instance.allExtractionPointsCompleted = snap.round.allExtractionPointsCompleted;
				if (log)
				{
					Plugin.Log.LogInfo((object)$"Restore: re-applied round accounting (goal {instance.haulGoal}, completed {instance.extractionPointsCompleted}).");
				}
			}
		}

		public static void RestoreHinges(WorldSnapshot snap)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: 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_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: 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)
			List<PhysGrabHinge> list = Object.FindObjectsOfType<PhysGrabHinge>().ToList();
			HashSet<PhysGrabHinge> hashSet = new HashSet<PhysGrabHinge>();
			int num = 0;
			HeldHinges.Clear();
			foreach (Hin