Decompiled source of PersistentScene v1.1.0

Mods/PersistentScene.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BoneLib;
using BoneLib.BoneMenu;
using BoneLib.BoneMenu.UI;
using BoneLib.Notifications;
using HarmonyLib;
using Il2CppCysharp.Threading.Tasks;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Bonelab;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.AI;
using Il2CppSLZ.Marrow.Combat;
using Il2CppSLZ.Marrow.Data;
using Il2CppSLZ.Marrow.Interaction;
using Il2CppSLZ.Marrow.Pool;
using Il2CppSLZ.Marrow.PuppetMasta;
using Il2CppSLZ.Marrow.SceneStreaming;
using Il2CppSLZ.Marrow.Warehouse;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppTMPro;
using LabFusion.Data;
using LabFusion.Data.Serializables;
using LabFusion.Entities;
using LabFusion.Marrow.Messages;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.RPC;
using MelonLoader;
using Microsoft.CodeAnalysis;
using PersistentScene;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "PersistentScene", "1.1.0", "CAitStudio", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonOptionalDependencies(new string[] { "LabFusion" })]
[assembly: AssemblyCompany("PersistentScene")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("PersistentScene")]
[assembly: AssemblyTitle("PersistentScene")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PersistentScene
{
	internal static class Barcodes
	{
		private const string ConstrainerBarcode = "c1534c5a-3813-49d6-a98c-f595436f6e73";

		public static string Get(SceneSaveBarcode barcode)
		{
			if (barcode == SceneSaveBarcode.CONSTRAINER)
			{
				return "c1534c5a-3813-49d6-a98c-f595436f6e73";
			}
			throw new ArgumentOutOfRangeException("barcode", barcode, null);
		}

		public static Spawnable Make(string barcode)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			//IL_0018: Expected O, but got Unknown
			Spawnable val = new Spawnable
			{
				crateRef = new SpawnableCrateReference(barcode)
			};
			AssetSpawner.Register(val);
			return val;
		}
	}
	public class ComponentBoolRecord
	{
		public string TypeName { get; set; }

		public int TypeSlot { get; set; }

		public string Name { get; set; }

		public bool Value { get; set; }
	}
	public class ComponentFloatRecord
	{
		public string TypeName { get; set; }

		public int TypeSlot { get; set; }

		public string Name { get; set; }

		public float Value { get; set; }
	}
	public class ComponentIntRecord
	{
		public string Name { get; set; }

		public int Value { get; set; }
	}
	public class ComponentStringRecord
	{
		public string Name { get; set; }

		public string Value { get; set; }
	}
	public class ComponentToggleRecord
	{
		public string TypeName { get; set; }

		public int TypeSlot { get; set; }

		public bool Enabled { get; set; }
	}
	public class ComponentVectorRecord
	{
		public string Name { get; set; }

		public Vec3 Value { get; set; }
	}
	public class ConstraintRecord
	{
		public string Mode { get; set; }

		public int FirstObjectIndex { get; set; }

		public string FirstPath { get; set; }

		public string FirstSiblingPath { get; set; }

		public int SecondObjectIndex { get; set; }

		public string SecondPath { get; set; }

		public string SecondSiblingPath { get; set; }

		public Vec3 FirstPoint { get; set; }

		public Vec3 SecondPoint { get; set; }

		public Vec3 FirstNormal { get; set; }

		public Vec3 SecondNormal { get; set; }

		public bool HasFirstTargetPose { get; set; }

		public Vec3 FirstTargetPosition { get; set; }

		public Vec3 FirstTargetRotation { get; set; }

		public bool HasSecondTargetPose { get; set; }

		public Vec3 SecondTargetPosition { get; set; }

		public Vec3 SecondTargetRotation { get; set; }
	}
	internal struct ConstraintState
	{
		public Vector3 PointStart;

		public Vector3 PointEnd;

		public Vector3 NormalStart;

		public Vector3 NormalEnd;
	}
	public class Core : MelonMod
	{
		public const string ModVersion = "1.1.0";

		private const string ConfigFileName = "config.json";

		private const string DefaultPlayerRigBarcode = "SLZ.BONELAB.Core.DefaultPlayerRig";

		private const string BlankRigBarcode = "SLZ.BONELAB.Core.Spawnable.RigManagerBlank";

		private const float SystemObjectDistance = 500f;

		private const float LoadInitialDelaySeconds = 0.05f;

		private const float AutoLoadInitialDelaySeconds = 1.25f;

		private const double RestoreBudgetMs = 4.5;

		private const float SpawnItemTimeoutSeconds = 1f;

		private const int PcSpawnBatchSize = 6;

		private const float ZoneMissingPreserveDistance = 12f;

		private const float CrateSpawnerMatchDistance = 4f;

		private const float CrateSpawnerDeleteMatchDistance = 25f;

		private const float LoadKinematicHoldSeconds = 2f;

		private const float PlayerLoadLift = 0.18f;

		private const float PlayerLoadSettleLift = 0.04f;

		private const string MainMenuBarcode = "c2534c5a-80e1-4a29-93ca-f3254d656e75";

		private const string VoidG114Barcode = "fa534c5a868247138f50c62e424c4144.Level.VoidG114";

		private static readonly (string Name, string Barcode)[] defaultBlacklist = new(string, string)[24]
		{
			("00-MainMenu", "c2534c5a-80e1-4a29-93ca-f3254d656e75"),
			("01-Descent", "c2534c5a-4197-4879-8cd3-4a695363656e"),
			("03-LongRun", "c2534c5a-56a6-40ab-a8ce-23074c657665"),
			("04-MineDive", "c2534c5a-54df-470b-baaf-741f4c657665"),
			("05-BigAnomaly", "c2534c5a-7601-4443-bdfe-7f235363656e"),
			("06-StreetPuncher", "SLZ.BONELAB.Content.Level.LevelStreetPunch"),
			("07-SprintBridge", "SLZ.BONELAB.Content.Level.SprintBridge04"),
			("08-MagmaGate", "SLZ.BONELAB.Content.Level.SceneMagmaGate"),
			("09-Moonbase", "SLZ.BONELAB.Content.Level.MoonBase"),
			("10-MonogonMotorway", "SLZ.BONELAB.Content.Level.LevelKartRace"),
			("11-PillarClimb", "c2534c5a-c056-4883-ac79-e051426f6964"),
			("12-BigAnomaly2", "SLZ.BONELAB.Content.Level.LevelBigAnomalyB"),
			("13-Ascent", "c2534c5a-db71-49cf-b694-24584c657665"),
			("14-Home", "SLZ.BONELAB.Content.Level.LevelOutro"),
			("NeonTrial", "c2534c5a-4f3b-480e-ad2f-69175363656e"),
			("DropPit", "c2534c5a-de61-4df9-8f6c-416954726547"),
			("TunnelTipper", "c2534c5a-c180-40e0-b2b7-325c5363656e"),
			("FantasyArena", "fa534c5a868247138f50c62e424c4144.Level.LevelArenaMin"),
			("ContainerYard", "c2534c5a-162f-4661-a04d-975d5363656e"),
			("DungeonWarrior", "c2534c5a-5c2f-4eef-a851-66214c657665"),
			("Rooftops", "c2534c5a-c6ac-48b4-9c5f-b5cd5363656e"),
			("NeonParkour", "fa534c5a83ee4ec6bd641fec424c4142.Level.SceneparkourDistrictLogic"),
			("LoadDefault", "fa534c5a83ee4ec6bd641fec424c4142.Level.DefaultLoad"),
			("LoadMod", "SLZ.BONELAB.CORE.Level.LevelModLevelLoad")
		};

		private static readonly HashSet<string> dontSaveThese = new HashSet<string>(StringComparer.Ordinal) { "Lakatrazz.FusionContent.Spawnable.AchievementBoard", "Lakatrazz.FusionContent.Spawnable.BitMart", "Lakatrazz.FusionContent.Spawnable.InfoBoard" };

		private static readonly JsonSerializerOptions jsonOpts = new JsonSerializerOptions
		{
			WriteIndented = true,
			IncludeFields = false
		};

		private string _homeDir;

		private string _defaultSaveFolder;

		private string _saveFolder;

		private string _cfg;

		private SceneSaveConfig _config = new SceneSaveConfig();

		private Page _page;

		private Page _setting;

		private Page _savePage;

		private Page _banPage;

		private Page _manual;

		private Page _slotPage;

		private StringElement _slotNameElement;

		private string _levelBarcode;

		private string _levelTitle;

		private Constrainer _dummyConstrainer;

		private bool _inLevel;

		private bool _savingNow;

		private bool _loadingNow;

		private bool _savedThisTrip;

		private bool _quitSaved;

		private bool _jumpTried;

		private bool _blockDefaultCrateSpawners;

		private bool _skipSceneSpawnerRouteForLoad;

		private Action _quitHook;

		private Poolee[] _poolCache;

		private float _poolCacheAt = -999f;

		private CrateSpawner[] _spawnerCache;

		private float _spawnerCacheAt = -999f;

		private readonly Dictionary<IntPtr, string> _savedIds = new Dictionary<IntPtr, string>();

		private readonly Dictionary<string, Poolee> _loadedIds = new Dictionary<string, Poolee>(StringComparer.Ordinal);

		private readonly HashSet<string> _deletedIds = new HashSet<string>(StringComparer.Ordinal);

		private readonly HashSet<IntPtr> _deadPtrs = new HashSet<IntPtr>();

		private static Transform _constraintDummyTransform;

		private bool _holdPlayer;

		private GameObject _saveQuitObj;

		private Button _saveQuitBtn;

		private static readonly string[] switchyBoolNames = new string[26]
		{
			"isOn", "_isOn", "m_isOn", "IsOn", "on", "_on", "m_On", "isToggled", "_isToggled", "toggled",
			"_toggled", "isActivated", "_isActivated", "activated", "_activated", "isActive", "_isActive", "m_IsActive", "powered", "_powered",
			"isPowered", "_isPowered", "lightOn", "_lightOn", "flashlightOn", "_flashlightOn"
		};

		private static readonly string[] switchyFloatNames = new string[3] { "intensity", "_intensity", "m_Intensity" };

		private const string holodeckRecordType = "PersistentScene.GameControl_Holodeck";

		private static readonly string[] logicHints = new string[15]
		{
			"ButtonController", "Il2CppSLZ.Marrow.Circuits", "Il2CppSLZ.Marrow.VoidLogic", "VoidLogic", "RPCBool", "RPCFloat", "RPCInt", "RPCString", "RPCVector3", "RPCVariable",
			"MaterialDecorator", "MaterialSwitch", "MaterialSwitcher", "MaterialSelector", "MaterialSelect"
		};

		private static readonly string[] logicBoolNames = new string[27]
		{
			"_charged", "charged", "Charged", "_toggleCharged", "toggleCharged", "ToggleCharged", "_isOn", "isOn", "IsOn", "_isActive",
			"isActive", "IsActive", "_active", "active", "Active", "_enabled", "enabled", "Enabled", "_value", "value",
			"Value", "_boolValue", "boolValue", "BoolValue", "_state", "state", "State"
		};

		private static readonly string[] logicFloatNames = new string[16]
		{
			"_value", "value", "Value", "_floatValue", "floatValue", "FloatValue", "_lastSensorValue", "lastSensorValue", "_sensorValue", "sensorValue",
			"_charge", "charge", "_input", "inputValue", "_output", "outputValue"
		};

		private static readonly string[] logicIntNames = new string[17]
		{
			"_value", "value", "Value", "_intValue", "intValue", "IntValue", "_state", "state", "State", "_index",
			"index", "_currentIndex", "currentIndex", "_selection", "selection", "_setting", "setting"
		};

		private static readonly string[] logicStringNames = new string[8] { "_value", "value", "Value", "_stringValue", "stringValue", "StringValue", "_text", "text" };

		private static readonly string[] logicVectorNames = new string[6] { "_value", "value", "Value", "_vector3Value", "vector3Value", "Vector3Value" };

		private static double restoreBudget => 4.5;

		internal static Core Current { get; private set; }

		public override void OnInitializeMelon()
		{
			Current = this;
			_homeDir = SaveRoot();
			_defaultSaveFolder = Path.Combine(_homeDir, "PersistentScene_SaveFiles");
			_saveFolder = _defaultSaveFolder;
			_cfg = Path.Combine(_defaultSaveFolder, "config.json");
			Directory.CreateDirectory(_defaultSaveFolder);
			LoadConfig();
			SpawnBits.Init(((MelonBase)this).HarmonyInstance);
			CrateSpawnerAwakeBlocker.Init(((MelonBase)this).HarmonyInstance);
			PooleeHooks.Init(((MelonBase)this).HarmonyInstance);
			JointBits.Init(((MelonBase)this).HarmonyInstance);
			NpcBits.Init(((MelonBase)this).HarmonyInstance);
			HolodeckBits.Init(((MelonBase)this).HarmonyInstance);
			LevelHooks.Init(((MelonBase)this).HarmonyInstance);
			BuildBoneMenu();
			Hooking.OnLevelLoading += OnLevelLoading;
			Hooking.OnLevelLoaded += OnLevelLoaded;
			Hooking.OnLevelUnloaded += OnLevelUnloaded;
			Hooking.OnUIRigCreated += OnUIRigCreated;
			_quitHook = OnUnityQuitting;
			Application.quitting += Action.op_Implicit(_quitHook);
			((MelonBase)this).LoggerInstance.Msg("Initialized. Saves: " + _saveFolder);
		}

		private static string SaveRoot()
		{
			string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
			return Path.Combine(folderPath, "AppData", "LocalLow", "Stress Level Zero", "BONELAB", "Saves");
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			if (_config.ModEnabled && !_jumpTried && !string.IsNullOrWhiteSpace(sceneName) && sceneName.Contains("BOOTSTRAP", StringComparison.OrdinalIgnoreCase))
			{
				_jumpTried = true;
				if (HasLastLevelToOpen())
				{
					AssetWarehouse.OnReady(Action.op_Implicit((Action)StartupJump));
				}
			}
		}

		public override void OnDeinitializeMelon()
		{
			if (!_quitSaved && _config.ModEnabled)
			{
				SaveOnTheWayOut("melon shutdown");
			}
			if (_quitHook != null)
			{
				Application.quitting -= Action.op_Implicit(_quitHook);
				_quitHook = null;
			}
			Hooking.OnUIRigCreated -= OnUIRigCreated;
			if (Current == this)
			{
				Current = null;
			}
		}

		public override void OnApplicationQuit()
		{
			SaveOnTheWayOut("application quit");
		}

		private void OnUnityQuitting()
		{
			SaveOnTheWayOut("unity quitting");
		}

		private void OnLevelLoading(LevelInfo nextLevel)
		{
			//IL_005c: 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 (!_config.ModEnabled)
			{
				_blockDefaultCrateSpawners = false;
				_skipSceneSpawnerRouteForLoad = false;
			}
			else if (!_savedThisTrip && _inLevel && !string.IsNullOrWhiteSpace(_levelBarcode))
			{
				SaveCurrentLevel("level loading");
				_savedThisTrip = true;
			}
			_blockDefaultCrateSpawners = _config.ModEnabled && ShouldBlockDefaultSpawns(nextLevel);
			_skipSceneSpawnerRouteForLoad = false;
			if (_blockDefaultCrateSpawners)
			{
				((MelonBase)this).LoggerInstance.Msg("Blocking default CrateSpawner Awake for saved level: " + nextLevel.barcode);
			}
			ClearRuntimeJunk();
			_savedIds.Clear();
			_loadedIds.Clear();
			_deletedIds.Clear();
			_deadPtrs.Clear();
			_inLevel = false;
			KillSaveQuitButton();
		}

		private void OnLevelLoaded(LevelInfo info)
		{
			//IL_0032: 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_00f2: Unknown result type (might be due to invalid IL or missing references)
			ClearRuntimeJunk();
			_savedIds.Clear();
			_loadedIds.Clear();
			_deletedIds.Clear();
			_deadPtrs.Clear();
			_levelBarcode = info.barcode;
			_levelTitle = info.title;
			_inLevel = true;
			_savedThisTrip = false;
			_quitSaved = false;
			DropFindCache();
			RefreshBoneMenu();
			MelonCoroutines.Start(RefreshSaveQuitSoon());
			if (!_config.ModEnabled)
			{
				_blockDefaultCrateSpawners = false;
				((MelonBase)this).LoggerInstance.Msg("Mod disabled, skipping load.");
				return;
			}
			if (FusionCompat.ClientInRoom)
			{
				_blockDefaultCrateSpawners = false;
				((MelonBase)this).LoggerInstance.Msg("Fusion client detected, host will handle PersistentScene load.");
				return;
			}
			if (IsBlacklisted(_levelBarcode))
			{
				_blockDefaultCrateSpawners = false;
				ForgetLast();
				((MelonBase)this).LoggerInstance.Msg("Level is blacklisted, skipping load: " + _levelBarcode);
				return;
			}
			RememberLevel(info);
			string savePath = GetSavePath(_levelBarcode);
			if (string.IsNullOrWhiteSpace(savePath) || !Directory.Exists(savePath))
			{
				_blockDefaultCrateSpawners = false;
				((MelonBase)this).LoggerInstance.Msg("No save for level: " + _levelBarcode);
			}
			else
			{
				MelonCoroutines.Start(LoadCurrentLevelSave(savePath, automatic: true));
			}
		}

		private void OnLevelUnloaded()
		{
			if (!_savedThisTrip && _inLevel && !string.IsNullOrWhiteSpace(_levelBarcode))
			{
				((MelonBase)this).LoggerInstance.Warning("Skipping late level-unloaded save; the scene is already tearing down.");
			}
			ClearRuntimeJunk();
			DropFindCache();
			_blockDefaultCrateSpawners = false;
			_skipSceneSpawnerRouteForLoad = false;
			_savedIds.Clear();
			_loadedIds.Clear();
			_deletedIds.Clear();
			_deadPtrs.Clear();
			_inLevel = false;
			_levelTitle = null;
			_savedThisTrip = false;
			KillSaveQuitButton();
		}

		internal void SaveBeforeSceneStreamerLoad(string reason)
		{
			if (_config.ModEnabled && !_savedThisTrip && _inLevel && !string.IsNullOrWhiteSpace(_levelBarcode) && SaveCurrentLevel(reason))
			{
				_savedThisTrip = true;
			}
		}

		private static void ClearRuntimeJunk()
		{
			SpawnBits.Clear();
			NpcBits.Clear();
			JointBits.Clear();
			HolodeckBits.Clear();
		}

		private bool ShouldBlockDefaultSpawns(LevelInfo nextLevel)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (!_config.ModEnabled)
			{
				return false;
			}
			if (FusionCompat.ClientInRoom)
			{
				return false;
			}
			string barcode = nextLevel.barcode;
			if (string.IsNullOrWhiteSpace(barcode))
			{
				return false;
			}
			if (IsBlacklisted(barcode))
			{
				return false;
			}
			string savePath = GetSavePath(barcode, nextLevel.title);
			if (string.IsNullOrWhiteSpace(savePath) || !Directory.Exists(savePath))
			{
				return false;
			}
			SceneSaveFile sceneSaveFile = ReadSaveMaybe(savePath);
			if (sceneSaveFile != null)
			{
				return string.Equals(sceneSaveFile.LevelBarcode, barcode, StringComparison.Ordinal);
			}
			return false;
		}

		internal bool ShouldBlockCrateSpawnerAwake(CrateSpawner spawner)
		{
			if (!_blockDefaultCrateSpawners || (Object)(object)spawner == (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)spawner).GetComponentInParent<Poolee>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)spawner).GetComponentInParent<RigManager>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)spawner).GetComponentInParent<UIRig>() != (Object)null)
			{
				return false;
			}
			return true;
		}

		private IEnumerator RestoreConstraintsWithRetryTimeSliced(SceneSaveFile save, List<Poolee> made, Constrainer constrainer, Action<int> onDone)
		{
			HashSet<int> doneJoints = new HashSet<int>();
			int got = 0;
			for (int pass = 0; pass < 3; pass++)
			{
				if (got >= save.Constraints.Count)
				{
					break;
				}
				if (pass > 0)
				{
					yield return (object)new WaitForSecondsRealtime(0.35f);
				}
				if ((Object)(object)constrainer == (Object)null || ((Il2CppObjectBase)constrainer).WasCollected)
				{
					yield return GetOrSpawnConstrainer(delegate(Constrainer constrainerInstance)
					{
						constrainer = constrainerInstance;
					});
				}
				yield return RestoreConstraintsTimeSliced(save, made, constrainer, doneJoints, delegate(int count)
				{
					got = count;
				});
			}
			if (got < save.Constraints.Count)
			{
				((MelonBase)this).LoggerInstance.Warning($"Restored {got}/{save.Constraints.Count} constraints after delayed retry.");
			}
			onDone?.Invoke(got);
		}

		private IEnumerator RestoreConstraintsTimeSliced(SceneSaveFile save, List<Poolee> made, Constrainer constrainer, HashSet<int> doneJoints, Action<int> onDone)
		{
			if ((Object)(object)constrainer == (Object)null)
			{
				onDone?.Invoke(doneJoints?.Count ?? 0);
				yield break;
			}
			int got = doneJoints?.Count ?? 0;
			Stopwatch stopwatch = Stopwatch.StartNew();
			for (int i = 0; i < save.Constraints.Count; i++)
			{
				if (doneJoints != null && doneJoints.Contains(i))
				{
					continue;
				}
				ConstraintRecord record = save.Constraints[i];
				try
				{
					if (RestoreConstraint(record, save, made, constrainer))
					{
						got++;
						doneJoints?.Add(i);
					}
				}
				catch (Exception ex)
				{
					((MelonBase)this).LoggerInstance.Warning("Failed to restore constraint: " + ex.Message);
				}
				if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
				{
					yield return null;
					stopwatch.Restart();
				}
			}
			onDone?.Invoke(got);
		}

		private bool RestoreConstraint(ConstraintRecord record, SceneSaveFile save, List<Poolee> made, Constrainer constrainer)
		{
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_031e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_032e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0337: Unknown result type (might be due to invalid IL or missing references)
			//IL_0351: Unknown result type (might be due to invalid IL or missing references)
			//IL_0353: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_035e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0373: Unknown result type (might be due to invalid IL or missing references)
			//IL_036c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0381: Unknown result type (might be due to invalid IL or missing references)
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0388: Unknown result type (might be due to invalid IL or missing references)
			Transform val = ResolveConstraintTransform(record.FirstObjectIndex, record.FirstPath, record.FirstSiblingPath, save, made);
			Transform val2 = ResolveConstraintTransform(record.SecondObjectIndex, record.SecondPath, record.SecondSiblingPath, save, made);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				((MelonBase)this).LoggerInstance.Warning($"Constraint endpoint missing: {record.FirstObjectIndex}:{record.FirstSiblingPath ?? record.FirstPath} -> {record.SecondObjectIndex}:{record.SecondSiblingPath ?? record.SecondPath}");
				return false;
			}
			Vector3 val3 = record.FirstPoint.ToUnity();
			Vector3 val4 = record.SecondPoint.ToUnity();
			Vector3 val5 = record.FirstNormal.ToUnity();
			Vector3 val6 = record.SecondNormal.ToUnity();
			if (val5 == Vector3.zero)
			{
				val5 = Vector3.up;
			}
			if (val6 == Vector3.zero)
			{
				val6 = Vector3.up;
			}
			bool flag = record.HasFirstTargetPose;
			bool flag2 = record.HasSecondTargetPose;
			Vector3 val7 = record.FirstTargetPosition.ToUnity();
			Quaternion val8 = Quaternion.Euler(record.FirstTargetRotation.ToUnity());
			Vector3 val9 = record.SecondTargetPosition.ToUnity();
			Quaternion val10 = Quaternion.Euler(record.SecondTargetRotation.ToUnity());
			bool flag3 = record.FirstObjectIndex >= 0;
			bool flag4 = record.SecondObjectIndex >= 0;
			if (record.FirstObjectIndex < 0)
			{
				val.position = val3;
				val.rotation = Quaternion.identity;
			}
			if (record.SecondObjectIndex < 0)
			{
				val2.position = val4;
				val2.rotation = Quaternion.identity;
			}
			MarrowBody val11 = FindBodyForConstraint(val);
			MarrowBody val12 = FindBodyForConstraint(val2);
			if ((Object)(object)val11 == (Object)null && (Object)(object)val12 == (Object)null)
			{
				((MelonBase)this).LoggerInstance.Warning("Constraint bodies missing: " + ((Object)val).name + " -> " + ((Object)val2).name);
				return false;
			}
			if ((Object)(object)val11 == (Object)null)
			{
				Transform obj = val2;
				val2 = val;
				val = obj;
				MarrowBody obj2 = val12;
				val12 = val11;
				val11 = obj2;
				Vector3 val13 = val4;
				val4 = val3;
				val3 = val13;
				Vector3 val14 = val6;
				val6 = val5;
				val5 = val14;
				bool num = flag2;
				flag2 = flag;
				flag = num;
				Vector3 val15 = val9;
				val9 = val7;
				val7 = val15;
				Quaternion val16 = val10;
				val10 = val8;
				val8 = val16;
				bool num2 = flag4;
				flag4 = flag3;
				flag3 = num2;
			}
			if (!Enum.TryParse<ConstraintMode>(record.Mode, out ConstraintMode result))
			{
				result = (ConstraintMode)1;
			}
			Vector3 position = val.position;
			Quaternion rotation = val.rotation;
			Vector3 position2 = val2.position;
			Quaternion rotation2 = val2.rotation;
			bool flag5 = flag3 && flag;
			bool flag6 = flag4 && flag2;
			try
			{
				if (flag)
				{
					val.SetPositionAndRotation(val7, val8);
				}
				if (flag2)
				{
					val2.SetPositionAndRotation(val9, val10);
				}
				constrainer.mode = result;
				constrainer._gO1 = ((Component)val).gameObject;
				constrainer._gO2 = ((Component)val2).gameObject;
				constrainer._mb1 = val11;
				constrainer._mb2 = val12;
				constrainer._point1 = val3;
				constrainer._point2 = val4;
				constrainer._normal1 = val5;
				constrainer._normal2 = val6;
				constrainer.PrimaryButtonUp();
				FusionCompat.SendConstraint(((Component)val).gameObject, ((Component)val2).gameObject, result, val3, val4, val5, val6, flag ? val7 : val.position, flag ? val8 : val.rotation, flag2 ? val9 : val2.position, flag2 ? val10 : val2.rotation);
			}
			finally
			{
				if (flag5 && (Object)(object)val != (Object)null)
				{
					val.SetPositionAndRotation(position, rotation);
				}
				if (flag6 && (Object)(object)val2 != (Object)null)
				{
					val2.SetPositionAndRotation(position2, rotation2);
				}
			}
			return true;
		}

		private static MarrowBody FindBodyForConstraint(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return null;
			}
			MarrowBody val = MarrowBody.Cache.Get(((Component)transform).gameObject);
			if (val == null)
			{
				val = ((Component)transform).GetComponent<MarrowBody>();
			}
			if (val == null)
			{
				val = ((Component)transform).GetComponentInParent<MarrowBody>();
			}
			if (val == null)
			{
				val = ((Component)transform).GetComponentInChildren<MarrowBody>(true);
			}
			if ((Object)(object)val == (Object)null)
			{
				Rigidbody val2 = ((Component)transform).GetComponent<Rigidbody>() ?? ((Component)transform).GetComponentInParent<Rigidbody>() ?? ((Component)transform).GetComponentInChildren<Rigidbody>(true);
				if ((Object)(object)val2 != (Object)null)
				{
					val = MarrowBody.Cache.Get(((Component)val2).gameObject) ?? ((Component)val2).GetComponent<MarrowBody>() ?? ((Component)val2).GetComponentInParent<MarrowBody>();
				}
			}
			return val;
		}

		private Transform ResolveConstraintTransform(int objectIndex, string relativePath, string siblingPath, SceneSaveFile save, List<Poolee> made)
		{
			if (objectIndex < 0 || objectIndex >= made.Count)
			{
				object obj2;
				if (!string.IsNullOrWhiteSpace(relativePath))
				{
					GameObject obj = GameObject.Find(relativePath);
					obj2 = ((obj != null) ? obj.transform : null);
				}
				else
				{
					obj2 = null;
				}
				if (obj2 == null)
				{
					obj2 = GetConstraintDummyTransform();
				}
				return (Transform)obj2;
			}
			if ((Object)(object)made[objectIndex] == (Object)null && save?.Objects != null && objectIndex < save.Objects.Count)
			{
				Poolee val = FindLoadedPooleeForSavedObject(save.Objects[objectIndex]);
				if ((Object)(object)val != (Object)null)
				{
					made[objectIndex] = val;
				}
			}
			if ((Object)(object)made[objectIndex] == (Object)null)
			{
				return GetConstraintDummyTransform();
			}
			Transform transform = ((Component)made[objectIndex]).transform;
			Transform val2 = ResolveBySiblingPath(transform, siblingPath);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2;
			}
			return ResolveRelativePath(transform, relativePath);
		}

		private static Transform GetConstraintDummyTransform()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			if ((Object)(object)_constraintDummyTransform == (Object)null)
			{
				GameObject val = new GameObject("SceneSave Constraint Dummy");
				Object.DontDestroyOnLoad((Object)val);
				_constraintDummyTransform = val.transform;
			}
			return _constraintDummyTransform;
		}

		private static Transform ResolveRelativePath(Transform root, string relativePath)
		{
			if ((Object)(object)root == (Object)null)
			{
				return null;
			}
			if (string.IsNullOrEmpty(relativePath))
			{
				return root;
			}
			Transform val = root;
			string[] array = relativePath.Split('/');
			foreach (string text in array)
			{
				if (!string.IsNullOrEmpty(text))
				{
					Transform val2 = val.Find(text);
					if ((Object)(object)val2 == (Object)null)
					{
						return null;
					}
					val = val2;
				}
			}
			return val;
		}

		private static Transform ResolveBySiblingPath(Transform root, string siblingPath)
		{
			if ((Object)(object)root == (Object)null || string.IsNullOrWhiteSpace(siblingPath))
			{
				return null;
			}
			Transform val = root;
			string[] array = siblingPath.Split('/');
			for (int i = 0; i < array.Length; i++)
			{
				if (!int.TryParse(array[i], out var result) || result < 0 || result >= val.childCount)
				{
					return null;
				}
				val = val.GetChild(result);
			}
			return val;
		}

		private IEnumerator GetOrSpawnConstrainer(Action<Constrainer> onResolved)
		{
			if ((Object)(object)_dummyConstrainer != (Object)null && !((Il2CppObjectBase)_dummyConstrainer).WasCollected)
			{
				onResolved?.Invoke(_dummyConstrainer);
				yield break;
			}
			Constrainer made = null;
			Task<Poolee> spawnTask = null;
			try
			{
				string barcode = Barcodes.Get(SceneSaveBarcode.CONSTRAINER);
				spawnTask = SpawnBits.SpawnAsync(barcode, new Vector3(1000f, 1000f, 1000f), Quaternion.identity, Vector3.one);
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Could not spawn dummy constrainer: " + ex.Message);
			}
			float timeoutAt = Time.realtimeSinceStartup + 12f;
			while (spawnTask != null && !spawnTask.IsCompleted && Time.realtimeSinceStartup < timeoutAt)
			{
				yield return null;
			}
			if (spawnTask != null && spawnTask.IsCompletedSuccessfully)
			{
				Poolee result = spawnTask.Result;
				if ((Object)(object)result != (Object)null)
				{
					SuppressDummyVisualEffects(((Component)result).gameObject);
					made = ((Component)result).GetComponent<Constrainer>() ?? ((Component)result).GetComponentInChildren<Constrainer>(true);
				}
			}
			_dummyConstrainer = made ?? Object.FindObjectOfType<Constrainer>();
			if ((Object)(object)_dummyConstrainer != (Object)null)
			{
				SuppressDummyVisualEffects(((Component)_dummyConstrainer).gameObject);
			}
			onResolved?.Invoke(_dummyConstrainer);
		}

		private static void SuppressDummyVisualEffects(GameObject root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return;
			}
			foreach (ParticleSystem componentsInChild in root.GetComponentsInChildren<ParticleSystem>(true))
			{
				componentsInChild.emission.enabled = false;
				componentsInChild.Stop(true, (ParticleSystemStopBehavior)0);
			}
		}

		private HealthRecord CaptureHealth(Poolee poolee)
		{
			AIBrain componentInChildren = ((Component)poolee).GetComponentInChildren<AIBrain>(true);
			object obj;
			if (componentInChildren == null)
			{
				obj = null;
			}
			else
			{
				BehaviourBaseNav behaviour = componentInChildren.behaviour;
				obj = ((behaviour != null) ? behaviour.health : null);
			}
			SubBehaviourHealth val = (SubBehaviourHealth)obj;
			if ((Object)(object)componentInChildren == (Object)null && val == null)
			{
				return null;
			}
			NpcRuntimeState state = NpcBits.GetState(componentInChildren, val);
			if (val == null)
			{
				HealthRecord healthRecord = new HealthRecord();
				healthRecord.IsDead = state.IsDead || TryReadBoolMember(componentInChildren, "_isDead", "isDead") == true;
				healthRecord.AppliedDamage = ((state.DamageTaken > 0f) ? new float?(state.DamageTaken) : ((float?)null));
				return healthRecord;
			}
			float? num = TryReadFloatMember(val, "curr_Health", "currentHealth", "_health", "health");
			float? maxHealth = TryReadFloatMember(val, "max_Health", "maxHealth");
			bool? isDead = TryReadBoolMember(componentInChildren, "_isDead", "isDead") ?? TryReadBoolMember(val, "_isDead", "isDead");
			if (state.IsDead || num <= 0f)
			{
				isDead = true;
			}
			return new HealthRecord
			{
				CurrentHealth = num,
				MaxHealth = maxHealth,
				IsDead = isDead,
				AppliedDamage = ((state.DamageTaken > 0f) ? new float?(state.DamageTaken) : ((float?)null))
			};
		}

		private DestructibleRecord CaptureDestructible(Poolee poolee)
		{
			ObjectDestructible componentInChildren = ((Component)poolee).GetComponentInChildren<ObjectDestructible>(true);
			if ((Object)(object)componentInChildren == (Object)null)
			{
				return null;
			}
			return new DestructibleRecord
			{
				Health = componentInChildren._health,
				MaxHealth = componentInChildren.maxHealth,
				IsDead = componentInChildren._isDead
			};
		}

		private void RestoreHealth(Poolee poolee, HealthRecord record)
		{
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Expected O, but got Unknown
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Expected O, but got Unknown
			if (record == null)
			{
				return;
			}
			AIBrain componentInChildren = ((Component)poolee).GetComponentInChildren<AIBrain>(true);
			object obj;
			if (componentInChildren == null)
			{
				obj = null;
			}
			else
			{
				BehaviourBaseNav behaviour = componentInChildren.behaviour;
				obj = ((behaviour != null) ? behaviour.health : null);
			}
			SubBehaviourHealth val = (SubBehaviourHealth)obj;
			if (val == null)
			{
				return;
			}
			if (record.MaxHealth.HasValue)
			{
				TryWriteFloatMember(val, record.MaxHealth.Value, "max_Health", "maxHealth");
			}
			if (record.IsDead == true)
			{
				KillNpc(componentInChildren, val);
			}
			else if (record.CurrentHealth.HasValue)
			{
				if (!TryWriteFloatMember(val, record.CurrentHealth.Value, "curr_Health", "currentHealth", "_health", "health"))
				{
					float num = record.MaxHealth ?? 100f;
					float num2 = Mathf.Max(0f, num - record.CurrentHealth.Value);
					if (num2 > 0f)
					{
						val.TakeDamage(1, new Attack
						{
							damage = num2
						});
					}
				}
			}
			else if (record.AppliedDamage.HasValue && record.AppliedDamage.Value > 0f)
			{
				val.TakeDamage(1, new Attack
				{
					damage = record.AppliedDamage.Value
				});
			}
		}

		private static void KillNpc(AIBrain brain, SubBehaviourHealth health)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			try
			{
				health.TakeDamage(1, new Attack
				{
					damage = 999999f
				});
			}
			catch
			{
			}
			try
			{
				if (brain != null)
				{
					brain.OnDeath();
				}
			}
			catch
			{
			}
		}

		private void RestoreDestructible(Poolee poolee, DestructibleRecord record)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if (record == null)
			{
				return;
			}
			ObjectDestructible componentInChildren = ((Component)poolee).GetComponentInChildren<ObjectDestructible>(true);
			if (!((Object)(object)componentInChildren == (Object)null))
			{
				componentInChildren.maxHealth = record.MaxHealth;
				componentInChildren._health = record.Health;
				if (record.IsDead && !componentInChildren._isDead)
				{
					componentInChildren.TakeDamage(Vector3.up, componentInChildren._health + 1f, false, (AttackType)64);
				}
				else
				{
					componentInChildren._isDead = record.IsDead;
				}
			}
		}

		private IEnumerator DespawnExistingPooleesTimeSliced()
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			Poolee[] array = PooleesNow();
			Poolee[] array2 = array;
			foreach (Poolee val in array2)
			{
				if (!CanClearPoolee(val))
				{
					continue;
				}
				SpawnableCrate spawnableCrate = val.SpawnableCrate;
				object value;
				if (spawnableCrate == null)
				{
					value = null;
				}
				else
				{
					Barcode barcode = ((Scannable)spawnableCrate).Barcode;
					value = ((barcode != null) ? barcode.ID : null);
				}
				if (!string.IsNullOrWhiteSpace((string?)value))
				{
					try
					{
						val.Despawn();
						Dirty();
					}
					catch
					{
						Object.Destroy((Object)(object)((Component)val).gameObject);
						Dirty();
					}
					if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
					{
						yield return null;
						stopwatch.Restart();
					}
				}
			}
		}

		private IEnumerator DespawnIgnoredPooleesTimeSliced()
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			Poolee[] array = PooleesNow();
			Poolee[] array2 = array;
			foreach (Poolee val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				SpawnableCrate spawnableCrate = val.SpawnableCrate;
				if (((spawnableCrate != null) ? ((Scannable)spawnableCrate).Barcode : null) == (Barcode)null)
				{
					continue;
				}
				string iD = ((Scannable)val.SpawnableCrate).Barcode.ID;
				if (IsSpentCasing(val, iD) || IsLooseEmptyMagazine(val))
				{
					try
					{
						val.Despawn();
						Dirty();
					}
					catch
					{
						Object.Destroy((Object)(object)((Component)val).gameObject);
						Dirty();
					}
					if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
					{
						yield return null;
						stopwatch.Restart();
					}
				}
			}
		}

		private static void WalkHierarchy(Transform root, List<Transform> transforms)
		{
			transforms.Add(root);
			for (int i = 0; i < root.childCount; i++)
			{
				Transform child = root.GetChild(i);
				if (!ShouldIgnoreTransform(child))
				{
					WalkHierarchy(child, transforms);
				}
			}
		}

		private static bool ShouldIgnoreTransform(Transform transform)
		{
			string name = ((Object)transform).name;
			if (!name.StartsWith("jPt", StringComparison.Ordinal))
			{
				return name.StartsWith("SavingBounds", StringComparison.Ordinal);
			}
			return true;
		}

		private static string GetRelativePath(Transform root, Transform transform)
		{
			if ((Object)(object)root == (Object)(object)transform)
			{
				return string.Empty;
			}
			List<string> list = new List<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
			{
				list.Add(((Object)val).name);
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}

		private static string GetSiblingPath(Transform root, Transform transform)
		{
			if ((Object)(object)root == (Object)null || (Object)(object)transform == (Object)null)
			{
				return null;
			}
			List<int> list = new List<int>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
			{
				list.Add(val.GetSiblingIndex());
				val = val.parent;
			}
			if ((Object)(object)val != (Object)(object)root)
			{
				return null;
			}
			list.Reverse();
			return string.Join("/", list);
		}

		private static Transform NormalizeConstraintTransform(Transform transform)
		{
			if ((Object)(object)transform != (Object)null && ((Object)transform).name.StartsWith("jPt", StringComparison.Ordinal) && (Object)(object)transform.parent != (Object)null)
			{
				return transform.parent;
			}
			return transform;
		}

		private static Poolee GetPooleeUpwards(Transform transform)
		{
			if ((Object)(object)transform != (Object)null)
			{
				Poolee val = Poolee.Cache.Get(((Component)transform.root).gameObject);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			while ((Object)(object)transform != (Object)null)
			{
				Poolee val2 = Poolee.Cache.Get(((Component)transform).gameObject);
				if ((Object)(object)val2 != (Object)null)
				{
					return val2;
				}
				transform = transform.parent;
			}
			return null;
		}

		private static bool IsProbablyMenuOrSystemObject(Transform transform)
		{
			while ((Object)(object)transform != (Object)null)
			{
				string name = ((Object)transform).name;
				if (name.Contains("BoneMenu", StringComparison.OrdinalIgnoreCase) || name.Contains("UIRig", StringComparison.OrdinalIgnoreCase) || name.Contains("PopUpMenu", StringComparison.OrdinalIgnoreCase) || name.Contains("PreferencesPanel", StringComparison.OrdinalIgnoreCase) || name.Contains("QuickMenu", StringComparison.OrdinalIgnoreCase) || name.Contains("SceneSave Dummy", StringComparison.OrdinalIgnoreCase) || name.Contains("SSBL Dummy", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				transform = transform.parent;
			}
			return false;
		}

		private IEnumerator LoadCurrentLevelSave(string path, bool automatic = false)
		{
			if (!_config.ModEnabled)
			{
				_blockDefaultCrateSpawners = false;
				_skipSceneSpawnerRouteForLoad = false;
			}
			else if (!FusionCompat.CanTouchScene)
			{
				_blockDefaultCrateSpawners = false;
				_skipSceneSpawnerRouteForLoad = false;
				((MelonBase)this).LoggerInstance.Msg("Fusion client detected, skipping load.");
			}
			else
			{
				if (_loadingNow)
				{
					yield break;
				}
				SceneSaveFile save;
				try
				{
					save = LoadSaveFile(path);
				}
				catch (Exception value)
				{
					((MelonBase)this).LoggerInstance.Error($"Load failed: {value}");
					yield break;
				}
				if (save == null || save.LevelBarcode != _levelBarcode)
				{
					_blockDefaultCrateSpawners = false;
					((MelonBase)this).LoggerInstance.Warning("Save file does not match current level, skipping: " + path);
					yield break;
				}
				EnsureSaveIds(save);
				CleanSave(save);
				if (!IsBlacklisted(_levelBarcode) && !IsBlacklisted(save.LevelBarcode))
				{
					_loadingNow = true;
					try
					{
						bool interceptedAutoLoad = (_skipSceneSpawnerRouteForLoad = automatic && _blockDefaultCrateSpawners);
						NotifySaveLoadStarted();
						yield return (object)new WaitForSecondsRealtime(GetLoadDelay(automatic));
						yield return RestorePlayerAmmoWhenReady(save.PlayerAmmo);
						yield return RestorePlayerInventoryWhenReady(save.PlayerInventory);
						bool movePlayer = _config.SavePlayerLocation && save.PlayerPose != null;
						if (movePlayer)
						{
							_holdPlayer = true;
							MelonCoroutines.Start(KeepPlayerThere(save.PlayerPose));
							yield return MovePlayerWhenReady(save.PlayerPose);
						}
						if (!interceptedAutoLoad)
						{
							MarkExistingDeletedPoolees(save);
							yield return DespawnIgnoredPooleesTimeSliced();
							yield return DespawnExistingPooleesTimeSliced();
						}
						else
						{
							((MelonBase)this).LoggerInstance.Msg("Skipped scene spawnable scan/clear because default CrateSpawners were blocked for this auto-load.");
						}
						_blockDefaultCrateSpawners = false;
						Dictionary<Rigidbody, bool> frozen = new Dictionary<Rigidbody, bool>(UnityObjectComparer<Rigidbody>.Instance);
						List<Poolee> made = Enumerable.Repeat<Poolee>(null, save.Objects.Count).ToList();
						bool[] array = new bool[save.Objects.Count];
						List<int> spawnOrder = GetSpawnOrder(save);
						foreach (int item in Enumerable.Range(0, save.Objects.Count).Except(spawnOrder))
						{
							array[item] = true;
						}
						yield return SpawnSavedObjectsOneAtATime(save, made, array, frozen, spawnOrder);
						yield return (object)new WaitForSecondsRealtime(0.05f);
						yield return ApplyNpcWorldPosePass(save, made);
						yield return (object)new WaitForSecondsRealtime(0.1f);
						yield return ApplyNpcWorldPosePass(save, made);
						yield return RestoreSavedRigidbodyStatesTimeSliced(save, made, frozen, keepKinematic: true);
						RegrabLoadedRigidbodies(save, made, frozen);
						((MelonBase)this).LoggerInstance.Msg($"Holding loaded rigidbodies kinematic for {2f:0.#} seconds.");
						yield return HoldKinematicLoadWindow(2f, frozen);
						yield return ApplyNpcWorldPosePass(save, made);
						RegrabLoadedRigidbodies(save, made, frozen);
						yield return RestoreSavedRigidbodyStatesTimeSliced(save, made, frozen, keepKinematic: true);
						Constrainer constrainer = null;
						yield return GetOrSpawnConstrainer(delegate(Constrainer constrainerInstance)
						{
							constrainer = constrainerInstance;
						});
						int joints = 0;
						yield return RestoreConstraintsWithRetryTimeSliced(save, made, constrainer, delegate(int got)
						{
							joints = got;
						});
						yield return RestoreNpcHealthTimeSliced(save, made);
						yield return RestoreSceneLogicStates(save);
						yield return RestoreSavedRigidbodyStatesTimeSliced(save, made, frozen, keepKinematic: false);
						yield return StabilizeDeadNpcPosesTimeSliced(save, made);
						UnfreezeRemainingRigidbodies(frozen);
						if (movePlayer)
						{
							_holdPlayer = false;
							yield return null;
							yield return PutPlayerBackAfterLoad(save.PlayerPose);
						}
						save.SupportedVersion = "1.0.0";
						WriteSaveOver(path, save);
						NotifySaveLoadFinished(made.Count, joints);
						((MelonBase)this).LoggerInstance.Msg($"Loaded {made.Count} saved spawnables and got {joints}/{save.Constraints.Count} constraints for {_levelBarcode}.");
						yield break;
					}
					finally
					{
						Core core = this;
						core._blockDefaultCrateSpawners = false;
						core._skipSceneSpawnerRouteForLoad = false;
						core._holdPlayer = false;
						core._loadingNow = false;
					}
				}
				_blockDefaultCrateSpawners = false;
				((MelonBase)this).LoggerInstance.Msg("Skipping load for blacklisted level: " + _levelBarcode);
			}
		}

		private static float GetLoadDelay(bool automatic)
		{
			if (!automatic)
			{
				return 0.05f;
			}
			return 1.25f;
		}

		private static SceneSaveFile LoadSaveFile(string path)
		{
			return ReadSaveMaybe(path);
		}

		private static List<int> GetSpawnOrder(SceneSaveFile save)
		{
			return (from i in Enumerable.Range(0, save.Objects.Count)
				where CanLoadSavedObject(save.Objects[i])
				select i).OrderBy(delegate(int i)
			{
				if (!save.Objects[i].HasAIBrain)
				{
					HealthRecord health = save.Objects[i].Health;
					if (health == null || health.IsDead != true)
					{
						return 1;
					}
				}
				return 0;
			}).ThenBy((int i) => save.Objects[i].Transforms?.Count ?? 0).ThenBy((int i) => i)
				.ToList();
		}

		private static float? TryReadFloatMember(object target, params string[] names)
		{
			foreach (string name in names)
			{
				object obj = TryReadMember(target, name);
				if (obj is float)
				{
					return (float)obj;
				}
				if (obj is int num)
				{
					return num;
				}
			}
			return null;
		}

		private static bool? TryReadBoolMember(object target, params string[] names)
		{
			foreach (string name in names)
			{
				object obj = TryReadMember(target, name);
				if (obj is bool)
				{
					return (bool)obj;
				}
			}
			return null;
		}

		private static int? TryReadIntMember(object target, params string[] names)
		{
			foreach (string name in names)
			{
				object obj = TryReadMember(target, name);
				if (obj is int)
				{
					return (int)obj;
				}
				if (obj is byte)
				{
					return (byte)obj;
				}
				if (obj is short)
				{
					return (short)obj;
				}
				if (obj is long num)
				{
					return (int)((num > int.MaxValue) ? int.MaxValue : ((num < int.MinValue) ? int.MinValue : num));
				}
			}
			return null;
		}

		private static object TryReadMember(object target, string name)
		{
			if (target == null)
			{
				return null;
			}
			Type type = target.GetType();
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(target);
			}
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null && property.CanRead)
			{
				return property.GetValue(target);
			}
			return null;
		}

		private static bool TryWriteFloatMember(object target, float value, params string[] names)
		{
			foreach (string name in names)
			{
				if (TryWriteMember(target, name, value))
				{
					return true;
				}
			}
			return false;
		}

		private static bool TryWriteMember(object target, string name, object value)
		{
			if (target == null)
			{
				return false;
			}
			Type type = target.GetType();
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				field.SetValue(target, value);
				return true;
			}
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null && property.CanWrite)
			{
				property.SetValue(target, value);
				return true;
			}
			return false;
		}

		private IEnumerator SpawnSavedObjectsOneAtATime(SceneSaveFile save, List<Poolee> made, bool[] done, Dictionary<Rigidbody, bool> frozen, List<int> order)
		{
			Dictionary<int, Task<Poolee>> running = new Dictionary<int, Task<Poolee>>();
			Dictionary<int, float> started = new Dictionary<int, float>();
			List<int> active = new List<int>();
			List<int> slow = new List<int>();
			Queue<int> todo = new Queue<int>(order);
			while (todo.Count > 0 || active.Count > 0)
			{
				while (todo.Count > 0 && active.Count < 6)
				{
					int num = todo.Dequeue();
					if (num >= 0 && num < save.Objects.Count && !done[num])
					{
						Task<Poolee> value = SpawnSavedObject(save.Objects[num]);
						running[num] = value;
						started[num] = Time.realtimeSinceStartup;
						active.Add(num);
					}
				}
				if (!PumpSpawnQueue(save, made, done, frozen, running, started, active, slow, retry: false))
				{
					yield return null;
				}
			}
			if (slow.Count <= 0)
			{
				yield break;
			}
			((MelonBase)this).LoggerInstance.Msg($"Retrying {slow.Count} slow spawnables after the first pass.");
			Queue<int> retry = new Queue<int>(slow);
			slow.Clear();
			while (retry.Count > 0 || active.Count > 0)
			{
				while (retry.Count > 0 && active.Count < 6)
				{
					int num2 = retry.Dequeue();
					if (num2 >= 0 && num2 < save.Objects.Count && !done[num2])
					{
						if (!running.TryGetValue(num2, out var value2) || value2.IsFaulted || (value2.IsCompleted && (Object)(object)value2.Result == (Object)null))
						{
							Task<Poolee> task = (running[num2] = SpawnSavedObject(save.Objects[num2]));
							value2 = task;
						}
						started[num2] = Time.realtimeSinceStartup;
						active.Add(num2);
					}
				}
				if (!PumpSpawnQueue(save, made, done, frozen, running, started, active, null, retry: true))
				{
					yield return null;
				}
			}
		}

		private bool PumpSpawnQueue(SceneSaveFile save, List<Poolee> made, bool[] done, Dictionary<Rigidbody, bool> frozen, Dictionary<int, Task<Poolee>> running, Dictionary<int, float> started, List<int> active, List<int> slow, bool retry)
		{
			bool result = false;
			for (int num = active.Count - 1; num >= 0; num--)
			{
				int num2 = active[num];
				if (!running.TryGetValue(num2, out var value))
				{
					active.RemoveAt(num);
					result = true;
				}
				else if (!value.IsCompleted)
				{
					if (started.TryGetValue(num2, out var value2) && Time.realtimeSinceStartup - value2 >= 1f)
					{
						active.RemoveAt(num);
						if (!retry)
						{
							slow?.Add(num2);
						}
						else
						{
							((MelonBase)this).LoggerInstance.Warning("Still waiting on slow spawnable after retry: " + save.Objects[num2].Barcode);
						}
						result = true;
					}
				}
				else
				{
					active.RemoveAt(num);
					started.Remove(num2);
					if (value.IsFaulted)
					{
						((MelonBase)this).LoggerInstance.Warning("Failed to spawn " + save.Objects[num2].Barcode + ": " + value.Exception?.GetBaseException().Message);
						done[num2] = true;
						result = true;
					}
					else
					{
						Poolee result2 = value.Result;
						if ((Object)(object)result2 == (Object)null)
						{
							if (!retry)
							{
								slow?.Add(num2);
							}
							else
							{
								((MelonBase)this).LoggerInstance.Warning("Spawn returned null for " + save.Objects[num2].Barcode);
								done[num2] = true;
							}
							result = true;
						}
						else
						{
							made[num2] = result2;
							RememberLoadedSaveId(result2, save.Objects[num2]);
							TouchLoadedPoolee(result2, save.Objects[num2], frozen);
							RestoreObjectState(result2, save.Objects[num2], keepRigidbodiesFrozen: true);
							done[num2] = true;
							result = true;
						}
					}
				}
			}
			return result;
		}

		private IEnumerator SpawnOneSavedObject(SceneSaveFile save, List<Poolee> made, bool[] done, Dictionary<Rigidbody, bool> frozen, Dictionary<int, Task<Poolee>> running, List<int> slow, int i, bool retry)
		{
			if (i < 0 || i >= save.Objects.Count || done[i])
			{
				yield break;
			}
			Task<Poolee> task = null;
			running.TryGetValue(i, out task);
			if (task == null || task.IsFaulted || (retry && task.IsCompleted && (Object)(object)task.Result == (Object)null))
			{
				Task<Poolee> task2 = (running[i] = SpawnSavedObject(save.Objects[i]));
				task = task2;
			}
			float timeoutAt = Time.realtimeSinceStartup + 1f;
			while (!task.IsCompleted && Time.realtimeSinceStartup < timeoutAt)
			{
				yield return null;
			}
			if (!task.IsCompleted)
			{
				if (!retry)
				{
					slow?.Add(i);
				}
				else
				{
					((MelonBase)this).LoggerInstance.Warning("Still waiting on slow spawnable after retry: " + save.Objects[i].Barcode);
				}
				yield break;
			}
			if (task.IsFaulted)
			{
				((MelonBase)this).LoggerInstance.Warning("Failed to spawn " + save.Objects[i].Barcode + ": " + task.Exception?.GetBaseException().Message);
				done[i] = true;
				yield break;
			}
			Poolee result = task.Result;
			if ((Object)(object)result == (Object)null)
			{
				if (!retry)
				{
					slow?.Add(i);
					yield break;
				}
				((MelonBase)this).LoggerInstance.Warning("Spawn returned null for " + save.Objects[i].Barcode);
				done[i] = true;
			}
			else
			{
				made[i] = result;
				RememberLoadedSaveId(result, save.Objects[i]);
				TouchLoadedPoolee(result, save.Objects[i], frozen);
				RestoreObjectState(result, save.Objects[i], keepRigidbodiesFrozen: true);
				done[i] = true;
			}
		}

		private void MarkExistingDeletedPoolees(SceneSaveFile save)
		{
			if (save?.DeletedSaveIds == null || save.DeletedSaveIds.Count == 0)
			{
				return;
			}
			HashSet<string> hashSet = save.DeletedSaveIds.Where((string id) => !string.IsNullOrWhiteSpace(id)).ToHashSet<string>(StringComparer.Ordinal);
			if (hashSet.Count == 0)
			{
				return;
			}
			CrateSpawner[] spawners = SpawnersNow();
			int num = 0;
			Poolee[] array = PooleesNow();
			foreach (Poolee val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Il2CppObjectBase)val).WasCollected && IsDeletedPoolee(val, save.LevelBarcode, spawners, hashSet))
				{
					_deadPtrs.Add(((Il2CppObjectBase)val).Pointer);
					num++;
				}
			}
			if (num > 0)
			{
				((MelonBase)this).LoggerInstance.Msg($"Marked {num} existing scene poolees as deleted before loading the save.");
			}
		}

		private static void TouchLoadedPoolee(Poolee poolee, SavedObject thing, Dictionary<Rigidbody, bool> frozen)
		{
			//IL_001b: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)poolee == (Object)null) && thing != null)
			{
				((Component)poolee).transform.position = thing.Position.ToUnity();
				((Component)poolee).transform.rotation = Quaternion.Euler(thing.Rotation.ToUnity());
				((Component)poolee).transform.localScale = thing.Scale.ToUnity();
				FreezePooleeRigidbodies(poolee, frozen);
				ClearPooleeRigidbodyMotion(poolee);
			}
		}

		private void RememberLoadedSaveId(Poolee poolee, SavedObject thing)
		{
			if (!((Object)(object)poolee == (Object)null) && thing != null && !string.IsNullOrWhiteSpace(thing.SaveId))
			{
				_savedIds[((Il2CppObjectBase)poolee).Pointer] = thing.SaveId;
				_loadedIds[thing.SaveId] = poolee;
			}
		}

		private IEnumerator ApplyNpcWorldPosePass(SceneSaveFile save, List<Poolee> made)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			for (int i = 0; i < save.Objects.Count && i < made.Count; i++)
			{
				if (save.Objects[i].HasAIBrain || ((save.Objects[i].Health?.IsDead == true) ?? false))
				{
					ApplySavedTransforms(made[i], save.Objects[i], restoreWorldPose: true, keepRigidbodiesFrozen: true);
				}
				if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
				{
					yield return null;
					stopwatch.Restart();
				}
			}
		}

		private IEnumerator RestoreNpcHealthTimeSliced(SceneSaveFile save, List<Poolee> made)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			for (int i = 0; i < save.Objects.Count && i < made.Count; i++)
			{
				SavedObject savedObject = save.Objects[i];
				if (savedObject?.Health != null && !((Object)(object)made[i] == (Object)null))
				{
					RestoreHealth(made[i], savedObject.Health);
					if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
					{
						yield return null;
						stopwatch.Restart();
					}
				}
			}
		}

		private async Task<Poolee> SpawnSavedObject(SavedObject thing)
		{
			_ = 2;
			try
			{
				Poolee val = await SpawnSavedObjectFromSceneCrateSpawner(thing);
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).transform.localScale = thing.Scale.ToUnity();
					return val;
				}
				Vector3 val2 = thing.Position.ToUnity();
				Quaternion val3 = Quaternion.Euler(thing.Rotation.ToUnity());
				Vector3 scale = thing.Scale.ToUnity();
				Poolee val4 = ((FusionCompat.InRoom && FusionCompat.Hosting) ? (await FusionCompat.SpawnSceneThing(thing.Barcode, val2, val3, scale)) : (await SpawnBits.SpawnAsync(thing.Barcode, val2, val3, scale)));
				val = val4;
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).transform.localScale = thing.Scale.ToUnity();
				}
				return val;
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Failed to spawn " + thing.Barcode + ": " + ex.Message);
				return null;
			}
		}

		private Task<Poolee> SpawnSavedObjectFromSceneCrateSpawner(SavedObject thing)
		{
			TaskCompletionSource<Poolee> tcs = new TaskCompletionSource<Poolee>();
			if (_skipSceneSpawnerRouteForLoad)
			{
				tcs.TrySetResult(null);
				return tcs.Task;
			}
			if (thing == null || (!IsSceneSpawnerSavedObject(thing) && !LooksLikeSceneSpawnerSaveId(thing.SaveId)))
			{
				tcs.TrySetResult(null);
				return tcs.Task;
			}
			CrateSpawner spawner = FindSceneCrateSpawner(thing);
			if ((Object)(object)spawner == (Object)null)
			{
				tcs.TrySetResult(null);
				return tcs.Task;
			}
			try
			{
				UniTask<Poolee> val = spawner.SpawnSpawnableAsync(false);
				Awaiter<Poolee> awaiter = val.GetAwaiter();
				awaiter.OnCompleted(Action.op_Implicit((Action)delegate
				{
					//IL_0041: Unknown result type (might be due to invalid IL or missing references)
					try
					{
						Poolee result = awaiter.GetResult();
						if ((Object)(object)result != (Object)null)
						{
							tcs.TrySetResult(result);
						}
						else
						{
							tcs.TrySetResult(FindRecentlySpawnedScenePoolee(thing, ((Component)spawner).transform.position));
						}
					}
					catch (Exception ex2)
					{
						((MelonBase)this).LoggerInstance.Warning("Scene CrateSpawner spawn failed for " + thing.Name + ": " + ex2.Message);
						tcs.TrySetResult(null);
					}
				}));
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Scene CrateSpawner spawn failed for " + thing.Name + ": " + ex.Message);
				tcs.TrySetResult(null);
			}
			return tcs.Task;
		}

		private CrateSpawner FindSceneCrateSpawner(SavedObject thing)
		{
			if (thing == null || string.IsNullOrWhiteSpace(thing.Barcode))
			{
				return null;
			}
			string text = ((!string.IsNullOrWhiteSpace(thing.SceneSpawnerId)) ? thing.SceneSpawnerId : thing.SaveId);
			if (string.IsNullOrWhiteSpace(text))
			{
				return null;
			}
			CrateSpawner[] array = SpawnersNow();
			foreach (CrateSpawner val in array)
			{
				if (TryGetCrateSpawnerBarcode(val, out var barcode) && string.Equals(barcode, thing.Barcode, StringComparison.Ordinal))
				{
					string sceneCrateSpawnerSaveId = GetSceneCrateSpawnerSaveId(_levelBarcode, val, barcode);
					if (string.Equals(sceneCrateSpawnerSaveId, text, StringComparison.Ordinal) || string.Equals(sceneCrateSpawnerSaveId, thing.SaveId, StringComparison.Ordinal))
					{
						return val;
					}
				}
			}
			return null;
		}

		private Poolee FindRecentlySpawnedScenePoolee(SavedObject thing, Vector3 spawnerPosition)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if (thing == null || string.IsNullOrWhiteSpace(thing.Barcode))
			{
				return null;
			}
			Poolee result = null;
			float num = float.PositiveInfinity;
			Poolee[] array = PooleesNow();
			foreach (Poolee val in array)
			{
				if ((Object)(object)val == (Object)null || ((Il2CppObjectBase)val).WasCollected)
				{
					continue;
				}
				SpawnableCrate spawnableCrate = val.SpawnableCrate;
				if (!(((spawnableCrate != null) ? ((Scannable)spawnableCrate).Barcode : null) == (Barcode)null) && string.Equals(((Scannable)val.SpawnableCrate).Barcode.ID, thing.Barcode, StringComparison.Ordinal))
				{
					float num2 = Vector3.Distance(((Component)val).transform.position, spawnerPosition);
					if (num2 < num)
					{
						result = val;
						num = num2;
					}
				}
			}
			if (!(num <= 4f))
			{
				return null;
			}
			return result;
		}

		private void RestoreObjectState(Poolee poolee, SavedObject thing, bool keepRigidbodiesFrozen = false)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)poolee == (Object)null) && !((Il2CppObjectBase)poolee).WasCollected)
			{
				ApplySavedTransforms(poolee, thing, restoreWorldPose: false, keepRigidbodiesFrozen);
				if (thing.HasAIBrain)
				{
					((Component)poolee).transform.position = thing.Position.ToUnity();
					((Component)poolee).transform.rotation = Quaternion.Euler(thing.Rotation.ToUnity());
				}
				RestoreHealth(poolee, thing.Health);
				RestoreDestructible(poolee, thing.Destructible);
			}
		}

		private static IEnumerator RestorePlayerAmmoWhenReady(PlayerAmmoRecord ammo)
		{
			if (ammo == null)
			{
				yield break;
			}
			AmmoInventory inv = null;
			for (int i = 0; i < 180; i++)
			{
				inv = AmmoInventory.Instance;
				if ((Object)(object)inv != (Object)null)
				{
					break;
				}
				yield return null;
			}
			if ((Object)(object)inv == (Object)null)
			{
				yield break;
			}
			try
			{
				inv.ClearAmmo();
				inv.AddCartridge(inv.lightAmmoGroup, Math.Max(0, ammo.LightAmmo));
				inv.AddCartridge(inv.mediumAmmoGroup, Math.Max(0, ammo.MediumAmmo));
				inv.AddCartridge(inv.heavyAmmoGroup, Math.Max(0, ammo.HeavyAmmo));
			}
			catch
			{
			}
		}

		private IEnumerator RestorePlayerInventoryWhenReady(PlayerInventoryRecord inv)
		{
			if (inv?.Items == null || inv.Items.Count == 0)
			{
				yield break;
			}
			RigManager rig = null;
			for (int i = 0; i < 180; i++)
			{
				rig = TryGetRigManager();
				RigManager obj = rig;
				object obj2;
				if (obj == null)
				{
					obj2 = null;
				}
				else
				{
					Inventory inventory = obj.inventory;
					obj2 = ((inventory != null) ? inventory.bodySlots : null);
				}
				if (obj2 != null)
				{
					break;
				}
				yield return null;
			}
			RigManager obj3 = rig;
			object obj4;
			if (obj3 == null)
			{
				obj4 = null;
			}
			else
			{
				Inventory inventory2 = obj3.inventory;
				obj4 = ((inventory2 != null) ? inventory2.bodySlots : null);
			}
			if (obj4 == null)
			{
				yield break;
			}
			int restored = 0;
			foreach (PlayerInventoryItemRecord item in inv.Items)
			{
				if (item != null && !string.IsNullOrWhiteSpace(item.SlotName) && !string.IsNullOrWhiteSpace(item.Barcode))
				{
					SlotContainer obj5 = FindBodySlot(rig, item.SlotName);
					InventorySlotReceiver val = ((obj5 != null) ? obj5.inventorySlotReceiver : null);
					if (!((Object)(object)val == (Object)null))
					{
						yield return SpawnInventoryItem(val, item);
						restored++;
					}
				}
			}
			if (restored > 0)
			{
				((MelonBase)this).LoggerInstance.Msg($"Restored {restored} inventory slot items.");
			}
		}

		private IEnumerator SpawnInventoryItem(InventorySlotReceiver receiver, PlayerInventoryItemRecord item)
		{
			if ((Object)(object)receiver == (Object)null || item == null)
			{
				yield break;
			}
			try
			{
				if ((Object)(object)receiver._slottedWeapon != (Object)null)
				{
					receiver.DespawnContents();
				}
			}
			catch
			{
			}
			try
			{
				receiver.SpawnInSlotAsync(new Barcode(item.Barcode));
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Could not spawn inventory item " + item.Barcode + ": " + ex.Message);
			}
			float timeout = Time.realtimeSinceStartup + 2f;
			while ((Object)(object)receiver._slottedWeapon == (Object)null && Time.realtimeSinceStartup < timeout)
			{
				yield return null;
			}
			yield return null;
			_ = (Object)(object)FindPooleeInReceiver(receiver) == (Object)null;
		}

		private static SlotContainer FindBodySlot(RigManager rig, string slotName)
		{
			object obj;
			if (rig == null)
			{
				obj = null;
			}
			else
			{
				Inventory inventory = rig.inventory;
				obj = ((inventory != null) ? inventory.bodySlots : null);
			}
			if (obj == null)
			{
				return null;
			}
			foreach (SlotContainer item in (Il2CppArrayBase<SlotContainer>)(object)rig.inventory.bodySlots)
			{
				if ((Object)(object)item != (Object)null && ((Object)((Component)item).gameObject).name == slotName)
				{
					return item;
				}
			}
			return null;
		}

		private static Poolee FindPooleeInReceiver(InventorySlotReceiver receiver)
		{
			try
			{
				if ((Object)(object)receiver == (Object)null || (Object)(object)receiver._slottedWeapon == (Object)null)
				{
					return null;
				}
				InteractableHost interactableHost = receiver._slottedWeapon.interactableHost;
				object obj = TryReadMember(TryReadMember(interactableHost, "marrowEntity"), "_poolee");
				Poolee val = (Poolee)((obj is Poolee) ? obj : null);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
				GameObject val2 = (((Object)(object)interactableHost == (Object)null) ? null : ((Component)interactableHost).gameObject);
				return ((Object)(object)val2 == (Object)null) ? null : val2.GetComponentInParent<Poolee>();
			}
			catch
			{
				return null;
			}
		}

		private void ApplySavedTransforms(Poolee poolee, SavedObject thing, bool restoreWorldPose, bool keepRigidbodiesFrozen = false)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: 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)
			if ((Object)(object)poolee == (Object)null || thing == null)
			{
				return;
			}
			bool flag = OnlyRootPoseForThis(thing);
			Dictionary<string, Transform> dictionary = null;
			foreach (TransformRecord transform in thing.Transforms)
			{
				if (flag && !RootRecord(transform))
				{
					continue;
				}
				Transform value = ResolveBySiblingPath(((Component)poolee).transform, transform.SiblingPath);
				if ((Object)(object)value == (Object)null)
				{
					if (dictionary == null)
					{
						dictionary = BuildRuntimeTransformMap(((Component)poolee).transform);
					}
					if (!dictionary.TryGetValue(transform.Path, out value))
					{
						continue;
					}
				}
				value.localPosition = transform.LocalPosition.ToUnity();
				value.localRotation = Quaternion.Euler(transform.LocalRotation.ToUnity());
				value.localScale = transform.LocalScale.ToUnity();
				bool flag2 = restoreWorldPose && transform.HasWorldPose && !string.IsNullOrEmpty(transform.SiblingPath);
				if (flag2)
				{
					value.position = transform.WorldPosition.ToUnity();
					value.rotation = Quaternion.Euler(transform.WorldRotation.ToUnity());
				}
				PutSmallObjectStateBack(value, transform);
				if (transform.Rigidbody == null)
				{
					continue;
				}
				Rigidbody component = ((Component)value).GetComponent<Rigidbody>();
				if (!((Object)(object)component == (Object)null))
				{
					if (flag2)
					{
						component.position = transform.WorldPosition.ToUnity();
						component.rotation = Quaternion.Euler(transform.WorldRotation.ToUnity());
					}
					component.isKinematic = keepRigidbodiesFrozen || transform.Rigidbody.IsKinematic;
					component.useGravity = transform.Rigidbody.UseGravity;
					component.velocity = transform.Rigidbody.Velocity.ToUnity();
					component.angularVelocity = transform.Rigidbody.AngularVelocity.ToUnity();
				}
			}
		}

		private static void PutSmallObjectStateBack(Transform transform, TransformRecord record)
		{
			if ((Object)(object)transform == (Object)null || record == null)
			{
				return;
			}
			if (record.ComponentToggles != null && record.ComponentToggles.Count > 0)
			{
				foreach (ComponentToggleRecord componentToggle in record.ComponentToggles)
				{
					if (DontGrabTinyState(componentToggle.TypeName))
					{
						continue;
					}
					Component val = FindSavedComponent(transform, componentToggle.TypeName, componentToggle.TypeSlot);
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					try
					{
						Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null);
						if (val2 != null)
						{
							val2.enabled = componentToggle.Enabled;
							continue;
						}
						Renderer val3 = (Renderer)(object)((val is Renderer) ? val : null);
						if (val3 != null)
						{
							val3.enabled = componentToggle.Enabled;
							continue;
						}
						Collider val4 = (Collider)(object)((val is Collider) ? val : null);
						if (val4 != null)
						{
							val4.enabled = componentToggle.Enabled;
						}
					}
					catch
					{
					}
				}
			}
			if (record.ComponentBools != null && record.ComponentBools.Count > 0)
			{
				foreach (ComponentBoolRecord componentBool in record.ComponentBools)
				{
					if (string.IsNullOrWhiteSpace(componentBool.Name) || DontGrabTinyState(componentBool.TypeName))
					{
						continue;
					}
					Component val5 = FindSavedComponent(transform, componentBool.TypeName, componentBool.TypeSlot);
					if (!((Object)(object)val5 == (Object)null))
					{
						try
						{
							TryWriteMember(val5, componentBool.Name, componentBool.Value);
						}
						catch
						{
						}
					}
				}
			}
			if (record.ComponentFloats != null && record.ComponentFloats.Count > 0)
			{
				foreach (ComponentFloatRecord componentFloat in record.ComponentFloats)
				{
					if (string.IsNullOrWhiteSpace(componentFloat.Name) || DontGrabTinyState(componentFloat.TypeName))
					{
						continue;
					}
					Component val6 = FindSavedComponent(transform, componentFloat.TypeName, componentFloat.TypeSlot);
					if (!((Object)(object)val6 == (Object)null))
					{
						try
						{
							TryWriteMember(val6, componentFloat.Name, componentFloat.Value);
						}
						catch
						{
						}
					}
				}
			}
			try
			{
				if (((Component)transform).gameObject.activeSelf != record.ActiveSelf)
				{
					((Component)transform).gameObject.SetActive(record.ActiveSelf);
				}
			}
			catch
			{
			}
		}

		private static Component FindSavedComponent(Transform transform, string typeName, int slot)
		{
			if ((Object)(object)transform == (Object)null || string.IsNullOrWhiteSpace(typeName))
			{
				return null;
			}
			Component[] array;
			try
			{
				array = Il2CppArrayBase<Component>.op_Implicit(((Component)transform).GetComponents<Component>());
			}
			catch
			{
				return null;
			}
			int num = 0;
			Component[] array2 = array;
			foreach (Component val in array2)
			{
				if (!((Object)(object)val == (Object)null) && string.Equals(GetCompName(val), typeName, StringComparison.Ordinal))
				{
					if (num == slot)
					{
						return val;
					}
					num++;
				}
			}
			return null;
		}

		private static bool DontGrabTinyState(Component comp, string typeName)
		{
			if ((Object)(object)comp == (Object)null)
			{
				return true;
			}
			if (comp is Transform)
			{
				return true;
			}
			return DontGrabTinyState(typeName);
		}

		private static bool DontGrabTinyState(string typeName)
		{
			if (string.IsNullOrWhiteSpace(typeName))
			{
				return true;
			}
			if (typeName == "UnityEngine.Transform")
			{
				return true;
			}
			if (typeName == "UnityEngine.RectTransform")
			{
				return true;
			}
			return false;
		}

		private static Dictionary<string, Transform> BuildRuntimeTransformMap(Transform root)
		{
			Dictionary<string, Transform> dictionary = new Dictionary<string, Transform>(StringComparer.Ordinal);
			List<Transform> list = new List<Transform>();
			WalkHierarchy(root, list);
			foreach (Transform item in list)
			{
				dictionary[GetRelativePath(root, item)] = item;
			}
			return dictionary;
		}

		private static void FreezePooleeRigidbodies(Poolee poolee, Dictionary<Rigidbody, bool> frozen)
		{
			if ((Object)(object)poolee == (Object)null)
			{
				return;
			}
			foreach (Rigidbody componentsInChild in ((Component)poolee).GetComponentsInChildren<Rigidbody>(true))
			{
				if (!((Object)(object)componentsInChild == (Object)null) && !frozen.ContainsKey(componentsInChild))
				{
					frozen[componentsInChild] = componentsInChild.isKinematic;
					componentsInChild.isKinematic = true;
				}
			}
		}

		private void RegrabLoadedRigidbodies(SceneSaveFile save, List<Poolee> made, Dictionary<Rigidbody, bool> frozen)
		{
			if (save == null || frozen == null)
			{
				return;
			}
			if (made != null)
			{
				for (int i = 0; i < made.Count; i++)
				{
					if ((Object)(object)made[i] == (Object)null && save.Objects != null && i < save.Objects.Count)
					{
						made[i] = FindLoadedPooleeForSavedObject(save.Objects[i]);
					}
					FreezePooleeRigidbodies(made[i], frozen);
				}
			}
			HashSet<string> hashSet = (save.Objects?.Where((SavedObject obj) => obj != null && !string.IsNullOrWhiteSpace(obj.SaveId))).Select((SavedObject obj) => obj.SaveId).ToHashSet<string>(StringComparer.Ordinal) ?? new HashSet<string>(StringComparer.Ordinal);
			if (hashSet.Count == 0)
			{
				return;
			}
			Poolee[] array = PooleesNow();
			foreach (Poolee val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Il2CppObjectBase)val).WasCollected && _savedIds.TryGetValue(((Il2CppObjectBase)val).Pointer, out var value) && hashSet.Contains(value))
				{
					FreezePooleeRigidbodies(val, frozen);
				}
			}
		}

		private Poolee FindLoadedPooleeForSavedObject(SavedObject obj)
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			if (obj == null)
			{
				return null;
			}
			Poolee[] array;
			if (!string.IsNullOrWhiteSpace(obj.SaveId))
			{
				if (_loadedIds.TryGetValue(obj.SaveId, out var value))
				{
					if ((Object)(object)value != (Object)null && !((Il2CppObjectBase)value).WasCollected)
					{
						return value;
					}
					_loadedIds.Remove(obj.SaveId);
				}
				array = PooleesNow();
				foreach (Poolee val in array)
				{
					if (!((Object)(object)val == (Object)null) && !((Il2CppObjectBase)val).WasCollected && _savedIds.TryGetValue(((Il2CppObjectBase)val).Pointer, out var value2) && string.Equals(value2, obj.SaveId, StringComparison.Ordinal))
					{
						_loadedIds[obj.SaveId] = val;
						return val;
					}
				}
			}
			if (string.IsNullOrWhiteSpace(obj.Barcode))
			{
				return null;
			}
			Vector3 val2 = obj.Position.ToUnity();
			Poolee val3 = null;
			float num = 3f;
			array = PooleesNow();
			foreach (Poolee val4 in array)
			{
				if ((Object)(object)val4 == (Object)null || ((Il2CppObjectBase)val4).WasCollected)
				{
					continue;
				}
				SpawnableCrate spawnableCrate = val4.SpawnableCrate;
				if (!(((spawnableCrate != null) ? ((Scannable)spawnableCrate).Barcode : null) == (Barcode)null) && string.Equals(((Scannable)val4.SpawnableCrate).Barcode.ID, obj.Barcode, StringComparison.Ordinal))
				{
					float num2 = Vector3.Distance(((Component)val4).transform.position, val2);
					if (!(num2 > num))
					{
						val3 = val4;
						num = num2;
					}
				}
			}
			if ((Object)(object)val3 != (Object)null && !string.IsNullOrWhiteSpace(obj.SaveId))
			{
				_savedIds[((Il2CppObjectBase)val3).Pointer] = obj.SaveId;
				_loadedIds[obj.SaveId] = val3;
			}
			return val3;
		}

		private static IEnumerator RestoreSavedRigidbodyStatesTimeSliced(SceneSaveFile save, List<Poolee> made, Dictionary<Rigidbody, bool> frozen, bool keepKinematic)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			for (int i = 0; i < save.Objects.Count && i < made.Count; i++)
			{
				Poolee poolee = made[i];
				SavedObject savedObject = save.Objects[i];
				if ((Object)(object)poolee == (Object)null || savedObject == null)
				{
					continue;
				}
				bool keepDeadNpcFrozen = IsDeadNpcObject(savedObject);
				bool rootOnly = OnlyRootPoseForThis(savedObject);
				foreach (TransformRecord transform in savedObject.Transforms)
				{
					if ((rootOnly && !RootRecord(transform)) || transform.Rigidbody == null)
					{
						continue;
					}
					Transform val = ResolveBySiblingPath(((Component)poolee).transform, transform.SiblingPath);
					if ((Object)(object)val == (Object)null)
					{
						val = ResolveRelativePath(((Component)poolee).transform, transform.Path);
					}
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Rigidbody component = ((Component)val).GetComponent<Rigidbody>();
					if ((Object)(object)component == (Object)null)
					{
						continue;
					}
					component.isKinematic = keepKinematic || keepDeadNpcFrozen || transform.Rigidbody.IsKinematic;
					component.useGravity = transform.Rigidbody.UseGravity;
					Vector3 val2 = transform.Rigidbody.Velocity.ToUnity();
					Vector3 val3 = transform.Rigidbody.AngularVelocity.ToUnity();
					component.velocity = ((keepKinematic || keepDeadNpcFrozen) ? Vector3.zero : val2);
					component.angularVelocity = ((keepKinematic || keepDeadNpcFrozen) ? Vector3.zero : val3);
					if (keepKinematic)
					{
						ClearRigidbodyMotion(component);
					}
					else
					{
						frozen.Remove(component);
						if (!keepDeadNpcFrozen && !transform.Rigidbody.IsKinematic)
						{
							component.WakeUp();
							component.velocity = val2;
							component.angularVelocity = val3;
						}
					}
					if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
					{
						yield return null;
						stopwatch.Restart();
					}
				}
			}
		}

		private static IEnumerator HoldKinematicLoadWindow(float seconds, Dictionary<Rigidbody, bool> frozen)
		{
			if (seconds <= 0f)
			{
				yield break;
			}
			float endTime = Time.realtimeSinceStartup + seconds;
			float nextReassertTime = 0f;
			while (Time.realtimeSinceStartup < endTime)
			{
				if (Time.realtimeSinceStartup >= nextReassertTime)
				{
					ReassertFrozenRigidbodies(frozen);
					nextReassertTime = Time.realtimeSinceStartup + 0.25f;
				}
				try
				{
					Physics.SyncTransforms();
				}
				catch
				{
				}
				yield return null;
			}
			ReassertFrozenRigidbodies(frozen);
		}

		private static void ReassertFrozenRigidbodies(Dictionary<Rigidbody, bool> frozen)
		{
			if (frozen == null)
			{
				return;
			}
			foreach (Rigidbody item in frozen.Keys.ToList())
			{
				if ((Object)(object)item == (Object)null)
				{
					frozen.Remove(item);
					continue;
				}
				item.isKinematic = true;
				ClearRigidbodyMotion(item);
			}
		}

		private IEnumerator StabilizeDeadNpcPosesTimeSliced(SceneSaveFile save, List<Poolee> made)
		{
			for (int pass = 0; pass < 10; pass++)
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				for (int i = 0; i < save.Objects.Count && i < made.Count; i++)
				{
					if (IsDeadNpcObject(save.Objects[i]) && !((Object)(object)made[i] == (Object)null))
					{
						ApplySavedTransforms(made[i], save.Objects[i], restoreWorldPose: true, keepRigidbodiesFrozen: true);
						ClearPooleeRigidbodyMotion(made[i]);
						if (stopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
						{
							yield return null;
							stopwatch.Restart();
						}
					}
				}
				try
				{
					Physics.SyncTransforms();
				}
				catch
				{
				}
				yield return null;
			}
			Stopwatch releaseStopwatch = Stopwatch.StartNew();
			for (int pass = 0; pass < save.Objects.Count && pass < made.Count; pass++)
			{
				if (IsDeadNpcObject(save.Objects[pass]) && !((Object)(object)made[pass] == (Object)null))
				{
					ReleaseDeadNpcRigidbodies(made[pass], save.Objects[pass]);
					if (releaseStopwatch.Elapsed.TotalMilliseconds >= restoreBudget)
					{
						yield return null;
						releaseStopwatch.Restart();
					}
				}
			}
		}

		private static bool IsDeadNpcObject(SavedObject obj)
		{
			if (obj != null && obj.HasAIBrain)
			{
				return (obj.Health?.IsDead == true) ?? false;
			}
			return false;
		}

		private static void ClearPooleeRigidbodyMotion(Poolee poolee)
		{
			if ((Object)(object)poolee == (Object)null)
			{
				return;
			}
			foreach (Rigidbody componentsInChild in ((Component)poolee).GetComponentsInChildren<Rigidbody>(true))
			{
				ClearRigidbodyMotion(componentsInChild);
			}
		}

		private static void ClearRigidbodyMotion(Rigidbody rb)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)rb == (Object)null))
			{
				rb.velocity = Vector3.zero;
				rb.angularVelocity = Vector3.zero;
				rb.Sleep();
			}
		}

		private static void ReleaseDeadNpcRigidbodies(Poolee poolee, SavedObject obj)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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)
			foreach (TransformRecord transform in obj.Transforms)
			{
				if (transform.Rigidbody == null)
				{
					continue;
				}
				Transform val = ResolveBySiblingPath(((Component)poolee).transform, transform.SiblingPath);
				if ((Object)(object)val == (Object)null)
				{
					val = ResolveRelativePath(((Component)poolee).transform, transform.Path);
				}
				if (!((Object)(object)val == (Object)null))
				{
					Rigidbody component = ((Component)val).GetComponent<Rigidbody>();
					if (!((Object)(object)component == (Object)null))
					{
						component.position = transform.WorldPosition.ToUnity();
						component.rotation = Quaternion.Euler(transform.WorldRotation.ToUnity());
						component.isKinematic = transform.Rigidbody.IsKinematic;
						component.useGravity = transform.Rigidbody.UseGravity;
						ClearRigidbodyMotion(component);
					}
				}
			}
		}

		private static void UnfreezeRemainingRigidbodies(Dictionary<Rigidbody, bool> frozen)
		{
			KeyValuePair<Rigidbody, bool>[] array = frozen.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<Rigidbody, bool> keyValuePair = array[i];
				if ((Object)(object)keyValuePair.Key != (Object)null)
				{
					keyValuePair.Key.isKinematic = keyValuePair.Value;
				}
			}
			frozen.Clear();
		}

		private static void NotifySaveLoadStarted()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit("Loading Save"),
				Message = NotificationText.op_Implicit("Asynchronously loading saved spawnables. Please wait."),
				Type = (NotificationType)0,
				ShowTitleOnPopup = true,
				PopupLength = 3f
			});
		}

		private static void NotifySaveLoadFinished(int spawnables, int constraints)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit("Save Loaded Successfully:"),
				Message = NotificationText.op_Implicit($"Loaded {spawnables} spawnables, {constraints} constraints."),
				Type = (NotificationType)3,
				ShowTitleOnPopup = true,
				PopupLength = 5f
			});
		}

		private static void ShowModVersion()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit("PersistentScene"),
				Message = NotificationText.op_Implicit("Mod Version: 1.1.0"),
				Type = (NotificationType)0,
				ShowTitleOnPopup = true,
				PopupLength = 5f
			});
		}

		private void OnUIRigCreated()
		{
			MakeSaveQuitButton();
		}

		private IEnumerator RefreshSaveQuitSoon()
		{
			for (int i = 0; i < 30; i++)
			{
				MakeSaveQuitButton();
				if (!((Object)(object)_saveQuitObj != (Object)null) || ((Il2CppObjectBase)_saveQuitObj).WasCollected)
				{
					yield return null;
					continue;
				}
				break;
			}
		}

		private void MakeSaveQuitButton()
		{
			if (!ShowSaveQuit())
			{
				KillSaveQuitButton();
			}
			else
			{
				if ((Object)(object)_saveQuitObj != (Object)null && !((Il2CppObjectBase)_saveQuitObj).WasCollected)
				{
					return;
				}
				Button val = FindRealQuitButton();
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				Transform parent = ((Component)val).transform.parent;
				if (!((Object)(object)parent == (Object)null))
				{
					Transform val2 = parent.Find("button_SaveAndQuit");
					if ((Object)(object)val2 != (Object)null)
					{
						((Object)val2).name = "button_SaveAndQuit_old";
						Object.Destroy((Object)(object)((Component)val2).gameObject);
					}
					GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent, false);
					((Object)val3).name = "button_SaveAndQuit";
					val3.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex());
					_saveQuitObj = val3;
					_saveQuitBtn = val3.GetComponent<Button>();
					HookSaveQuitButton();
					PutButtonText(val3, "Save & Quit");
				}
			}
		}

		private void HookSaveQuitButton()
		{
			if (!((Object)(object)_saveQuitBtn == (Object)null))
			{
				((UnityEvent)_saveQuitBtn.onClick).AddListener(UnityAction.op_Implicit((Action)OnSaveQuitButtonPressed));
			}
		}

		private void OnSaveQuitButtonPressed()
		{
			if (ShowSaveQuit())
			{
				MelonCoroutines.Start(FixSaveQuitPopup());
			}
		}

		private IEnumerator FixSaveQuitPopup()
		{
			for (int i = 0; i < 60; i++)
			{
				yield return null;
				Button val = FindYesButton();
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				RetitlePopup(((Component)val).gameObject);
				val.onClick = new ButtonClickedEvent();
				((UnityEvent)val.onClick).AddListener(UnityAction.op_Implicit((Action)delegate
				{
					if (!SaveCurrentLevel("SaveAndQuit"))
					{
						((MelonBase)this).LoggerInstance.Warning("falied to save,pls try again");
					}
					else
					{
						_quitSaved = true;
						Application.Quit();
					}
				}));
				break;
			}
		}

		private Button FindYesButton()
		{
			UIRig uIRig = Player.UIRig;
			object obj;
			if (uIRig == null)
			{
				obj = null;
			}
			else
			{
				PopUpMenuView popUpMenu = uIRig.popUpMenu;
				obj = ((popUpMenu != null) ? popUpMenu.preferencesPanelView : null);
			}
			PreferencesPanelView val = (PreferencesPanelView)obj;
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Button result = null;
			foreach (Button componentsInChild in ((Component)val).GetComponentsInChildren<Button>(true))
			{
				if ((Object)(object)componentsInChild == (Object)null || !((Component)componentsInChild).gameObject.activeInHierarchy || ((Object)(object)_saveQuitBtn != (Object)null && (Object)(object)componentsInChild == (Object)(object)_saveQuitBtn) || (Object)(object)componentsInChild == (Object)(object)FindRealQuitButton() || InOptionsGrid(((Component)componentsInChild).transform))
				{
					continue;
				}
				string text = ButtonText(((Component)componentsInChild).gameObject);
				string name = ((Object)componentsInChild).name ?? string.Empty;
				if (!LooksNoish(name, text) && LooksYesish(name, text))
				{
					result = componentsInChild;
					if (text.Contains("yes", StringComparison.OrdinalIgnoreCase) || text.Contains("confirm", StringComparison.OrdinalIgnoreCase) || text.Contains("main menu", StringComparison.OrdinalIgnoreCase))
					{
						return componentsInChild;
					}
				}
			}
			return result;
		}

		private static bool InOptionsGrid(Transform t)
		{
			while ((Object)(object)t != (Object)null)
			{
				string a = ((Object)t).name ?? string.Empty;
				if (string.Equals(a, "grid_Options", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				if (string.Equals(a, "page_OPTIONS", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				t = t.parent;
			}
			return false;
		}

		private static bool LooksYesish(string name, string text)
		{
			string text2 = name + " " + text;
			if (!text2.Contains("confirm", StringComparison.OrdinalIgnoreCase) && !text2.Contains("accept", StringComparison.OrdinalIgnoreCase) && !text2.Contains("yes", StringComparison.OrdinalIgnoreCase) && !text2.Contains("ok", StringComparison.OrdinalIgnoreCase))
			{
				return text2.Contains("main menu", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool LooksNoish(string name, string text)
		{
			string text2 = name + " " + text;
			if (!text2.Contains("cancel", StringComparison.OrdinalIgnoreCase) && !text2.Contains("deny", StringComparison.OrdinalIgnoreCase) && !text2.Contains("no", StringComparison.OrdinalIgnoreCase) && !text2.Contains("back", StringComparison.OrdinalIgnoreCase))
			{
				return text2.Contains("close", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private void RetitlePopup(GameObject confirmButto