Decompiled source of PersistentScene v1.1.0
Mods/PersistentScene.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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