using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using CG.Cloud; using CG.Profile; using Gameplay.Hub; using Gameplay.Quests; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using ResourceAssets; using UI; using UI.Core; using UI.Core.Audio; using UnityEngine; using UnityEngine.UIElements; using VoidManager; using VoidManager.MPModChecks; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("fingoldfish")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Provides multiple save slots and allows you to choose which one to restore.")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2+6f381efdfdb2398cd4fca7729c9c99fde131906a")] [assembly: AssemblyProduct("Save Slots")] [assembly: AssemblyTitle("Provides multiple save slots and allows you to choose which one to restore.")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 SaveSlots { [Serializable] public class PreservedSessionWithMetadata { public PreservedGameSession Session; public DateTime Timestamp; public PreservedSessionWithMetadata(PreservedGameSession session, DateTime timestamp) { Session = session; Timestamp = timestamp; } } internal class VoidManagerPlugin : VoidPlugin { public override MultiplayerType MPType => (MultiplayerType)8; } [BepInPlugin("com.gummyboars.voidcrew.saveslots", "Save Slots", "1.0.2")] public class SaveSlotsPlugin : BaseUnityPlugin { private const string pluginGUID = "com.gummyboars.voidcrew.saveslots"; private const string pluginName = "Save Slots"; private const string pluginVersion = "1.0.2"; private Harmony HarmonyInstance = null; public static ManualLogSource logger = Logger.CreateLogSource("Save Slots"); private void Awake() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown logger.LogInfo((object)"Loading plugin Save Slots."); try { HarmonyInstance = new Harmony("com.gummyboars.voidcrew.saveslots"); Assembly executingAssembly = Assembly.GetExecutingAssembly(); HarmonyInstance.PatchAll(executingAssembly); logger.LogInfo((object)"Plugin Save Slots version 1.0.2 loaded."); ModdingUtils.RegisterLocalMod(); } catch (Exception arg) { logger.LogError((object)string.Format("Could not load plugin {0}: {1}", "Save Slots", arg)); } } } [HarmonyPatch] public static class SessionSaver { public static Dictionary Sessions = new Dictionary(); public static string SessionsText = null; public static void Set(PreservedGameSession session) { DateTime utcNow = DateTime.UtcNow; SaveSlotsPlugin.logger.LogInfo((object)$"Saving session with id {session.GameSessionID} at time {utcNow}"); Sessions[session.GameSessionID] = new PreservedSessionWithMetadata(session, utcNow); } [HarmonyPostfix] [HarmonyPatch(typeof(CloudProfileReader), "GetProfile")] private static Task GetPreservedSessionsWithProfile(Task origTask) { return origTask.ContinueWith>(LoadSessions).Unwrap(); } private static async Task LoadSessions(Task orig) { SaveSlotsPlugin.logger.LogInfo((object)"Reading preserved sessions from cloud"); SessionsText = await CloudSyncController.ReadFromCloud("PreservedSessions"); return orig.Result; } [HarmonyPrefix] [HarmonyPatch(typeof(ProfileDataValidator), "PostSetupValidate")] private static void PatchAfterProfileLoad() { if (SessionsText == null) { SaveSlotsPlugin.logger.LogError((object)"Profile was loaded before preserved sessions."); return; } PlayerProfile instance = PlayerProfile.Instance; CloudLocalProfile val = (CloudLocalProfile)(object)((instance is CloudLocalProfile) ? instance : null); if (val == null) { return; } if (string.IsNullOrEmpty(SessionsText)) { SaveSlotsPlugin.logger.LogInfo((object)"No preserved sessions found"); if (((PlayerProfile)val).PreservedSession.PreservedSession != null && !string.IsNullOrEmpty(((PlayerProfile)val).PreservedSession.PreservedSession.GameSessionID)) { SaveSlotsPlugin.logger.LogInfo((object)"Populating preserved sessions from preserved session"); Set(((PlayerProfile)val).PreservedSession.PreservedSession); } return; } Sessions = JsonConvert.DeserializeObject>(SessionsText); SaveSlotsPlugin.logger.LogInfo((object)$"Loaded {Sessions.Count} preserved sessions"); if (((PlayerProfile)val).PreservedSession.PreservedSession != null && !string.IsNullOrEmpty(((PlayerProfile)val).PreservedSession.PreservedSession.GameSessionID)) { if (!Sessions.ContainsKey(((PlayerProfile)val).PreservedSession.PreservedSession.GameSessionID)) { SaveSlotsPlugin.logger.LogInfo((object)("Preserved session " + ((PlayerProfile)val).PreservedSession.PreservedSession.GameSessionID + " missing from preserved sessions; adding it")); Set(((PlayerProfile)val).PreservedSession.PreservedSession); } else { SetSessionIfNewer(((PlayerProfile)val).PreservedSession.PreservedSession, Sessions[((PlayerProfile)val).PreservedSession.PreservedSession.GameSessionID].Session); } } } private static void SetSessionIfNewer(PreservedGameSession currentSession, PreservedGameSession modSession) { if (currentSession.PreservedQuest != null && currentSession.PreservedQuest.CompletedSectors != null && modSession.PreservedQuest != null && modSession.PreservedQuest.CompletedSectors != null && currentSession.PreservedQuest.CompletedSectors.Count > modSession.PreservedQuest.CompletedSectors.Count) { Set(currentSession); } } [HarmonyPrefix] [HarmonyPatch(typeof(CloudPlayerPreservedSessionSync), "ClearSession")] private static void PatchBeforeSessionClear(CloudPlayerPreservedSessionSync __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown PropertyInfo propertyInfo = AccessTools.Property(typeof(CloudPlayerPreservedSessionSync), "PreservedSession"); PreservedGameSession val = (PreservedGameSession)propertyInfo.GetValue(__instance); if (val != null && !string.IsNullOrEmpty(val.GameSessionID)) { SaveSlotsPlugin.logger.LogInfo((object)("Removing session with id " + val.GameSessionID)); Sessions.Remove(val.GameSessionID); } } [HarmonyPostfix] [HarmonyPatch(typeof(CloudPlayerPreservedSessionSync), "ClearSession")] private static void PatchAfterSessionClear() { CloudSyncController.Instance.Write("PreservedSessions", JsonConvert.SerializeObject((object)Sessions), true, (Action)null); SaveSlotsPlugin.logger.LogInfo((object)$"Wrote {Sessions.Count} preserved sessions"); } [HarmonyPrefix] [HarmonyPatch(typeof(CloudPlayerPreservedSessionSync), "StoreSession")] private static void PatchBeforeSessionSave(PreservedGameSession session) { if (session != null && !string.IsNullOrEmpty(session.GameSessionID)) { Set(session); } } [HarmonyPostfix] [HarmonyPatch(typeof(CloudPlayerPreservedSessionSync), "StoreSession")] private static void PatchAfterSessionSave() { CloudSyncController.Instance.Write("PreservedSessions", JsonConvert.SerializeObject((object)Sessions), true, (Action)null); SaveSlotsPlugin.logger.LogInfo((object)$"Wrote {Sessions.Count} preserved sessions"); MethodInfo methodInfo = AccessTools.Method(typeof(PlayerProfileLocalSave), "SaveFile", (Type[])null, (Type[])null); methodInfo.Invoke(null, new object[2] { "PRESERVED_SESSIONS", JsonConvert.SerializeObject((object)Sessions) }); } } [HarmonyPatch(typeof(GalaxyMapUIController), "UpdateStyles")] public static class Patch_UpdateStyles { private static void Postfix(VisualElement ____mutatorsMenuRoot, GalaxyMapUIController __instance) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 Type type = AccessTools.Inner(typeof(GalaxyMapUIController), "GameModeEntry"); FieldInfo fieldInfo = AccessTools.Field(typeof(GalaxyMapUIController), "_shownGameModeEntry"); FieldInfo fieldInfo2 = AccessTools.Field(type, "GamemodeType"); object value = fieldInfo.GetValue(__instance); if (value != null) { GamemodeType val = (GamemodeType)fieldInfo2.GetValue(value); if ((int)val == 3) { UItoolkitExtensionMethods.SetDisplay(____mutatorsMenuRoot, true); } } } } [HarmonyPatch(typeof(GalaxyMapUIController), "UpdateAvailableGamemodes")] public static class Patch_UpdateAvailableGamemodes { private static GalaxyMapUIController Instance; public static void UpdatePreservedSessionDisplay() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Invalid comparison between Unknown and I4 if (!((Object)(object)Instance != (Object)null)) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(GalaxyMapUIController), "UpdatePreservedSessionInfo", (Type[])null, (Type[])null); methodInfo.Invoke(Instance, new object[0]); PreservedGameSession preservedSession = PlayerProfile.Instance.PreservedSession.PreservedSession; if (preservedSession != null) { FieldInfo fieldInfo = AccessTools.Field(typeof(GalaxyMapUIController), "_includedMutatorsList"); MutatorsElementListVE val = (MutatorsElementListVE)fieldInfo.GetValue(Instance); UItoolkitExtensionMethods.SetDisplay(((VisualElement)val).parent, preservedSession.Mutators.Count > 0); val.UpdateMutatorsList(preservedSession.Mutators.AsReadOnly()); if ((int)HubQuestManager.Instance.QuestStartType == 2) { HubShipManager.Instance.SelectShip(((GUIDUnion)(ref preservedSession.Ship)).AsIntArray()); MethodInfo methodInfo2 = AccessTools.Method(typeof(HubQuestManager), "NotifyPreservedQuestSelected", (Type[])null, (Type[])null); methodInfo2.Invoke(HubQuestManager.Instance, new object[0]); } } } public static void UpdateJustTheMutatorsList() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown PreservedGameSession preservedSession = PlayerProfile.Instance.PreservedSession.PreservedSession; if ((Object)(object)Instance != (Object)null && preservedSession != null) { FieldInfo fieldInfo = AccessTools.Field(typeof(GalaxyMapUIController), "_includedMutatorsList"); MutatorsElementListVE val = (MutatorsElementListVE)fieldInfo.GetValue(Instance); UItoolkitExtensionMethods.SetDisplay(((VisualElement)val).parent, preservedSession.Mutators.Count > 0); val.UpdateMutatorsList(preservedSession.Mutators.AsReadOnly()); } } private static void Prefix() { if (PlayerProfile.Instance.PreservedSession.PreservedSession != null && !string.IsNullOrEmpty(PlayerProfile.Instance.PreservedSession.PreservedSession.GameSessionID)) { return; } PreservedSessionWithMetadata preservedSessionWithMetadata = null; foreach (KeyValuePair session in SessionSaver.Sessions) { if (preservedSessionWithMetadata == null || preservedSessionWithMetadata.Timestamp.CompareTo(session.Value.Timestamp) <= 0) { preservedSessionWithMetadata = session.Value; } } if (preservedSessionWithMetadata == null) { return; } SaveSlotsPlugin.logger.LogInfo((object)("Using latest saved session " + preservedSessionWithMetadata.Session.GameSessionID)); PlayerProfile.Instance.PreservedSession.PreservedSession = preservedSessionWithMetadata.Session; foreach (KeyValuePair safe in Patch_MutatorsMenu.saves) { if (safe.Key == preservedSessionWithMetadata.Session.GameSessionID) { ((BaseField)(object)safe.Value).SetValueWithoutNotify(true); break; } } } private static void Postfix(GalaxyMapUIController __instance, MutatorsSelectionMenu ____mutatorsSelectionMenu) { Instance = __instance; FieldInfo fieldInfo = AccessTools.Field(typeof(GalaxyMapUIController), "_gamemodeEntries"); IDictionary dictionary = (IDictionary)fieldInfo.GetValue(__instance); object obj = dictionary[(object)(GamemodeType)3]; if (obj == null) { SaveSlotsPlugin.logger.LogInfo((object)"Preserved game mode entry was null"); return; } Type type = AccessTools.Inner(typeof(GalaxyMapUIController), "GameModeEntry"); FieldInfo fieldInfo2 = AccessTools.Field(type, "OnShown"); Action a = (Action)fieldInfo2.GetValue(obj); a = (Action)Delegate.Combine(a, (Action)delegate { ____mutatorsSelectionMenu.OnShownQuestChangedInGalaxyMap((QuestAsset)null); UpdateJustTheMutatorsList(); }); fieldInfo2.SetValue(obj, a); } } [HarmonyPatch(typeof(MutatorsSelectionMenu))] public static class Patch_MutatorsMenu { public static Dictionary saves = new Dictionary(); public static Label mutatorsDropdownLabel; public static string origText; public static string GetSaveText(PreservedSessionWithMetadata session) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) DateTime dateTime = session.Timestamp.ToLocalTime(); string text = dateTime.ToShortDateString() + " " + dateTime.ToShortTimeString(); string text2 = "SAVED SESSION"; ShipLoadoutDataDef val = default(ShipLoadoutDataDef); if (!((ResourceAssetContainer)(object)ResourceAssetContainer.Instance).TryGetByGuid(session.Session.Ship, ref val)) { return (text2 + " " + text).ToUpper(); } if (Object.op_Implicit((Object)(object)val.ShipContextInfo) && val.ContextInfo != null) { text2 = val.ShipContextInfo.HeaderText + ": " + val.ContextInfo.HeaderText; } string text3 = (text2 + " " + text).ToUpper(); if (text3.StartsWith("METEM ")) { text3 = text3.Substring("METEM ".Length); } return text3; } [HarmonyPrefix] [HarmonyPatch("UpdateMutatorStates")] private static void BeforeUpdate(QuestAsset ____shownQuest, Dictionary ____mutatorToggles) { if ((Object)(object)____shownQuest == (Object)null) { return; } foreach (KeyValuePair ____mutatorToggle in ____mutatorToggles) { UItoolkitExtensionMethods.SetDisplay(((VisualElement)____mutatorToggle.Value).parent, true); } foreach (KeyValuePair safe in saves) { UItoolkitExtensionMethods.SetDisplay(((VisualElement)safe.Value).parent, false); } } [HarmonyPostfix] [HarmonyPatch("UpdateMutatorStates")] private static void AfterUpdate(QuestAsset ____shownQuest, Dictionary ____mutatorToggles, Label ____mutatorsSelectedLabel, Label ____unseenCounter, MutatorsSelectionMenu __instance, MutatorsElementListVE ____selectedMutatorsList) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown mutatorsDropdownLabel = (Label)((VisualElement)____mutatorsSelectedLabel).parent[0]; if ((Object)(object)____shownQuest != (Object)null) { if (!string.IsNullOrEmpty(origText)) { ((TextElement)mutatorsDropdownLabel).text = origText; origText = null; } UItoolkitExtensionMethods.SetDisplay(((VisualElement)____selectedMutatorsList).parent, true); __instance.CheckForUnseenMutators(); return; } if (string.IsNullOrEmpty(origText)) { origText = ((TextElement)mutatorsDropdownLabel).text; } string text = "SAVED SESSION"; if (PlayerProfile.Instance.PreservedSession.PreservedSession != null && PlayerProfile.Instance.PreservedSession.PreservedSession.GameSessionID != null && SessionSaver.Sessions.ContainsKey(PlayerProfile.Instance.PreservedSession.PreservedSession.GameSessionID)) { text = GetSaveText(SessionSaver.Sessions[PlayerProfile.Instance.PreservedSession.PreservedSession.GameSessionID]); } ((TextElement)mutatorsDropdownLabel).text = text; ((TextElement)____mutatorsSelectedLabel).text = ""; UItoolkitExtensionMethods.SetDisplay((VisualElement)(object)____unseenCounter, false); UItoolkitExtensionMethods.SetDisplay(((VisualElement)____selectedMutatorsList).parent, false); foreach (KeyValuePair ____mutatorToggle in ____mutatorToggles) { UItoolkitExtensionMethods.SetDisplay(((VisualElement)____mutatorToggle.Value).parent, false); } foreach (KeyValuePair safe in saves) { UItoolkitExtensionMethods.SetDisplay(((VisualElement)safe.Value).parent, true); } } [HarmonyPostfix] [HarmonyPatch("CheckForUnseenMutators")] private static void ReUpdateMutatorStates(QuestAsset ____shownQuest, Dictionary ____mutatorToggles, Label ____mutatorsSelectedLabel, Label ____unseenCounter, MutatorsSelectionMenu __instance, MutatorsElementListVE ____selectedMutatorsList) { if ((Object)(object)____shownQuest == (Object)null) { AfterUpdate(____shownQuest, ____mutatorToggles, ____mutatorsSelectedLabel, ____unseenCounter, __instance, ____selectedMutatorsList); } } [HarmonyPostfix] [HarmonyPatch("UpdateSelectedMutatorsCount")] private static void ReUpdateMutatorsCount(QuestAsset ____shownQuest, Dictionary ____mutatorToggles, Label ____mutatorsSelectedLabel, Label ____unseenCounter, MutatorsSelectionMenu __instance, MutatorsElementListVE ____selectedMutatorsList) { if ((Object)(object)____shownQuest == (Object)null) { AfterUpdate(____shownQuest, ____mutatorToggles, ____mutatorsSelectedLabel, ____unseenCounter, __instance, ____selectedMutatorsList); } } [HarmonyPrefix] [HarmonyPatch("FillMutatorsList")] private static void CreateSaveVisualElements(MutatorsSelectionMenu __instance, ScrollView ____mutatorsView, SelectableVE ____mutatorsMenuToggle) { foreach (KeyValuePair safe in saves) { UItoolkitExtensionMethods.SetDisplay(((VisualElement)safe.Value).parent, false); } saves.Clear(); foreach (KeyValuePair session in SessionSaver.Sessions) { DateTime dateTime = session.Value.Timestamp.ToLocalTime(); string saveText = GetSaveText(session.Value); Toggle val = MakeSaveEntry(((VisualElement)____mutatorsView).contentContainer, session.Key, saveText, ____mutatorsMenuToggle); if (PlayerProfile.Instance.PreservedSession.PreservedSession != null && PlayerProfile.Instance.PreservedSession.PreservedSession.GameSessionID == session.Key) { ((BaseField)(object)val).SetValueWithoutNotify(true); } } } private static Toggle MakeSaveEntry(VisualElement parent, string sessionID, string labelText, SelectableVE menuToggle) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown SelectableVE val = new SelectableVE(); ((VisualElement)val).name = sessionID; ((Focusable)val).focusable = true; ((VisualElement)val).AddToClassList("mutator-entry"); UIAudioProvider.AddSounds((VisualElement)(object)val, "list"); parent.Add((VisualElement)(object)val); VisualElement val2 = VEFactoryHelper.SetupVE((VisualElement)(object)val, "Overlays", new string[1] { "mutator-overlays" }); VEFactoryHelper.SetupVE(val2, "UnseenMarker", new string[1] { "mutator-unseen-marker" }); VEFactoryHelper.SetupVE(val2, "LockedIcon", new string[1] { "mutator-locked-icon" }); VEFactoryHelper.SetupVE((VisualElement)(object)val, "MutatorIcon", new string[1] { "mutator-icon" }).style.backgroundImage = new StyleBackground(DataTable.Instance.SaveProgressContextInfo.Icon); Toggle toggle = new Toggle(labelText); UIAudioProvider.AddSounds((VisualElement)(object)toggle, "list"); ((VisualElement)toggle).AddToClassList("metem-toggle"); ((VisualElement)toggle).AddToClassList("mutator-toggle"); INotifyValueChangedExtensions.RegisterValueChangedCallback((INotifyValueChanged)(object)toggle, (EventCallback>)delegate(ChangeEvent evt) { if (evt.newValue) { if (!SessionSaver.Sessions.ContainsKey(sessionID) || string.IsNullOrEmpty(SessionSaver.Sessions[sessionID].Session.GameSessionID)) { SaveSlotsPlugin.logger.LogError((object)("Tried to select session not in saves: " + sessionID)); ((BaseField)(object)toggle).SetValueWithoutNotify(false); } else { foreach (KeyValuePair safe in saves) { if (safe.Key != sessionID) { ((BaseField)(object)safe.Value).SetValueWithoutNotify(false); } } SaveSlotsPlugin.logger.LogInfo((object)("Setting profile preserved session to " + sessionID)); PlayerProfile.Instance.PreservedSession.PreservedSession = SessionSaver.Sessions[sessionID].Session; ((TextElement)mutatorsDropdownLabel).text = ((BaseField)(object)toggle).label; Patch_UpdateAvailableGamemodes.UpdatePreservedSessionDisplay(); menuToggle.Unselect(); } } else { string text = null; foreach (KeyValuePair safe2 in saves) { if (safe2.Key != sessionID && ((BaseField)(object)safe2.Value).value) { text = safe2.Key; break; } } if (text == null) { SaveSlotsPlugin.logger.LogInfo((object)"Tried to deselect the last session"); ((BaseField)(object)toggle).SetValueWithoutNotify(true); } else if (PlayerProfile.Instance.PreservedSession.PreservedSession == null || PlayerProfile.Instance.PreservedSession.PreservedSession.GameSessionID == sessionID) { SaveSlotsPlugin.logger.LogInfo((object)("Deselected " + sessionID + ". Setting profile preserved session to " + text)); PlayerProfile.Instance.PreservedSession.PreservedSession = SessionSaver.Sessions[text].Session; if (saves.ContainsKey(text)) { ((TextElement)mutatorsDropdownLabel).text = ((BaseField)(object)saves[text]).label; Patch_UpdateAvailableGamemodes.UpdatePreservedSessionDisplay(); menuToggle.Unselect(); } } } }); ((VisualElement)val).Add((VisualElement)(object)toggle); saves.Add(sessionID, toggle); return toggle; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "SaveSlots"; public const string PLUGIN_NAME = "Save Slots"; public const string PLUGIN_VERSION = "1.0.2"; } }