Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Keepsake v0.1.0
Keepsake.dll
Decompiled 15 hours agousing System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using RiskOfOptions; using RiskOfOptions.Options; using RoR2; using RoR2.UI; using TMPro; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace FluxSquido.Keepsake; internal sealed class Kept { public readonly string Section; public readonly string Key; public readonly string Value; public Kept(string section, string key, string value) { Section = section; Key = key; Value = value; } } internal sealed class OverrideStore { public const string Separator = "::"; internal const string LegacyAutoSprintGuid = "com.score.AutoSprint"; private readonly object gate = new object(); private readonly SortedDictionary<string, SortedDictionary<string, Kept>> byGuid = new SortedDictionary<string, SortedDictionary<string, Kept>>(StringComparer.Ordinal); public string FilePath { get; private set; } public int Count { get { lock (gate) { return byGuid.Values.Sum((SortedDictionary<string, Kept> d) => d.Count); } } } public OverrideStore(string filePath) { FilePath = filePath; } public int Load(Action<string> warn) { int num = 0; lock (gate) { byGuid.Clear(); if (!File.Exists(FilePath)) { return 0; } string text = null; int num2 = 0; string[] array = File.ReadAllLines(FilePath); foreach (string text2 in array) { num2++; string text3 = text2.Trim(); if (text3.Length == 0 || text3[0] == '#' || text3[0] == ';') { continue; } if (text3[0] == '[' && text3[text3.Length - 1] == ']') { text = text3.Substring(1, text3.Length - 2).Trim(); continue; } int num3 = text3.IndexOf('='); if (num3 < 0 || text == null) { Warn(warn, num2, text2); continue; } string text4 = text3.Substring(0, num3).Trim(); string text5 = text3.Substring(num3 + 1).Trim(); int num4 = text4.IndexOf("::", StringComparison.Ordinal); if (num4 < 0) { if (string.Equals(text, "AutoSprint", StringComparison.OrdinalIgnoreCase) && string.Equals(text4, "Enable Auto Sprint", StringComparison.OrdinalIgnoreCase)) { SetLocked("com.score.AutoSprint", "General", "Enable Mod", text5.ToLowerInvariant()); num++; } else { Warn(warn, num2, text2); } continue; } string text6 = text4.Substring(0, num4).Trim(); string text7 = text4.Substring(num4 + "::".Length).Trim(); if (text6.Length == 0 || text7.Length == 0) { Warn(warn, num2, text2); } else { SetLocked(text, text6, text7, text5); } } } if (num > 0) { Save(); } return num; } private static void Warn(Action<string> warn, int lineNo, string raw) { warn?.Invoke("keepsake.cfg line " + lineNo + " ignored (expected \"Section :: Key = value\" under a [PluginGUID] header): " + raw); } public bool Set(string guid, string section, string key, string value) { lock (gate) { return SetLocked(guid, section, key, value); } } private bool SetLocked(string guid, string section, string key, string value) { if (!byGuid.TryGetValue(guid, out var value2)) { value2 = new SortedDictionary<string, Kept>(StringComparer.Ordinal); byGuid[guid] = value2; } string key2 = section + "\n" + key; if (value2.TryGetValue(key2, out var value3) && value3.Value == value) { return false; } value2[key2] = new Kept(section, key, value); return true; } public bool TryGet(string guid, string section, string key, out string value) { value = null; lock (gate) { if (!byGuid.TryGetValue(guid, out var value2) || !value2.TryGetValue(section + "\n" + key, out var value3)) { return false; } value = value3.Value; return true; } } public List<Kept> For(string guid) { lock (gate) { SortedDictionary<string, Kept> value; return (!byGuid.TryGetValue(guid, out value)) ? new List<Kept>() : value.Values.ToList(); } } public void DeleteAll() { lock (gate) { byGuid.Clear(); if (File.Exists(FilePath)) { File.Delete(FilePath); } string path = FilePath + ".tmp"; if (File.Exists(path)) { File.Delete(path); } } } public void Save() { lock (gate) { string directoryName = Path.GetDirectoryName(FilePath); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } string text = FilePath + ".tmp"; File.WriteAllText(text, Render(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); if (File.Exists(FilePath)) { File.Delete(FilePath); } File.Move(text, FilePath); } } private string Render() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# This is where your Keepsake settings are stored :]"); stringBuilder.AppendLine("# When you change one of those settings in game, it is saved here."); stringBuilder.AppendLine("# This file lives in the game folder, outside your mod manager profiles."); foreach (KeyValuePair<string, SortedDictionary<string, Kept>> item in byGuid) { if (item.Value.Count == 0) { continue; } stringBuilder.AppendLine(); stringBuilder.Append('[').Append(item.Key).AppendLine("]"); foreach (Kept value in item.Value.Values) { stringBuilder.Append(value.Section).Append(" :: ").Append(value.Key) .Append(" = ") .AppendLine(value.Value); } } return stringBuilder.ToString(); } } internal sealed class TrackList { private readonly List<string[]> rules = new List<string[]>(); public int Count => rules.Count; public static TrackList Parse(string raw, Action<string> warn) { TrackList trackList = new TrackList(); if (string.IsNullOrEmpty(raw)) { return trackList; } string[] array = raw.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length != 0) { string[] array2 = text2.Split(new char[1] { ':' }, 3); string text3 = array2[0].Trim(); string text4 = ((array2.Length <= 1) ? "" : array2[1].Trim()); string text5 = ((array2.Length <= 2) ? "" : array2[2].Trim()); if (text3.Length == 0) { warn?.Invoke("Tracked setting \"" + text2 + "\" has no plugin GUID; ignored."); continue; } trackList.rules.Add(new string[3] { text3, (text4.Length != 0) ? text4 : "*", (text5.Length != 0) ? text5 : "*" }); } } return trackList; } public void Add(string guid, string section, string key) { if (!Matches(guid, section, key)) { rules.Add(new string[3] { guid, section, key }); } } public bool TouchesPlugin(string guid) { foreach (string[] rule in rules) { if (Glob.Match(rule[0], guid)) { return true; } } return false; } public bool Matches(string guid, string section, string key) { foreach (string[] rule in rules) { if (Glob.Match(rule[0], guid) && Glob.Match(rule[1], section) && Glob.Match(rule[2], key)) { return true; } } return false; } public override string ToString() { return string.Join(", ", rules.Select((string[] r) => r[0] + ":" + r[1] + ":" + r[2]).ToArray()); } } internal static class Glob { public static bool Match(string pattern, string text) { if (pattern == "*") { return true; } if (pattern == null || text == null) { return false; } int i = 0; int num = 0; int num2 = -1; int num3 = 0; while (num < text.Length) { if (i < pattern.Length && pattern[i] == '*') { num2 = i++; num3 = num; continue; } if (i < pattern.Length && char.ToUpperInvariant(pattern[i]) == char.ToUpperInvariant(text[num])) { i++; num++; continue; } if (num2 >= 0) { i = num2 + 1; num = ++num3; continue; } return false; } for (; i < pattern.Length && pattern[i] == '*'; i++) { } return i == pattern.Length; } } internal sealed class OverrideEngine { private sealed class Target { public string Guid; public ConfigFile File; } [ThreadStatic] private static int applyingOnThisThread; private readonly OverrideStore store; private readonly TrackList tracked; private readonly Action<string> info; private readonly Action<string> warn; private readonly object applyGate = new object(); private readonly object stateGate = new object(); private readonly List<Target> targets = new List<Target>(); private readonly Dictionary<ConfigFile, int> reloading = new Dictionary<ConfigFile, int>(); private readonly HashSet<string> warnedOnce = new HashSet<string>(); private readonly object saveGate = new object(); private bool savePending; private readonly Dictionary<string, Dictionary<string, Kept>> fallbacks = new Dictionary<string, Dictionary<string, Kept>>(StringComparer.Ordinal); public Action<Action> ScheduleSave { get; set; } public int TargetCount { get { lock (stateGate) { return targets.Count; } } } public OverrideEngine(OverrideStore store, TrackList tracked, Action<string> info, Action<string> warn) { this.store = store; this.tracked = tracked; this.info = info ?? ((Action<string>)delegate { }); this.warn = warn ?? ((Action<string>)delegate { }); } public bool Attach(string guid, ConfigFile file) { if (file == null || string.IsNullOrEmpty(guid) || !tracked.TouchesPlugin(guid)) { return false; } Target target; lock (stateGate) { foreach (Target target2 in targets) { if (object.ReferenceEquals(target2.File, file) || target2.Guid == guid) { return false; } } target = new Target { Guid = guid, File = file }; targets.Add(target); } file.SettingChanged += delegate(object sender, SettingChangedEventArgs e) { OnSettingChanged(target, e); }; return true; } public void SetFallback(string guid, string section, string key, string value) { lock (stateGate) { if (!fallbacks.TryGetValue(guid, out var value2)) { value2 = new Dictionary<string, Kept>(StringComparer.Ordinal); fallbacks[guid] = value2; } value2[section + "\n" + key] = new Kept(section, key, value); } } public int ApplyAll() { Target[] array; lock (stateGate) { array = targets.ToArray(); } int num = 0; Target[] array2 = array; foreach (Target t in array2) { num += Apply(t); } return num; } private int Apply(Target t) { lock (applyGate) { int num = 0; bool flag = false; List<KeyValuePair<Kept, bool>> list = new List<KeyValuePair<Kept, bool>>(); foreach (Kept item in store.For(t.Guid)) { list.Add(new KeyValuePair<Kept, bool>(item, value: false)); } lock (stateGate) { if (fallbacks.TryGetValue(t.Guid, out var value)) { foreach (Kept value4 in value.Values) { if (!store.TryGet(t.Guid, value4.Section, value4.Key, out var _)) { list.Add(new KeyValuePair<Kept, bool>(value4, value: true)); } } } } foreach (KeyValuePair<Kept, bool> item2 in list) { Kept key = item2.Key; bool value3 = item2.Value; if (!tracked.Matches(t.Guid, key.Section, key.Key)) { continue; } ConfigEntryBase val = Find(t.File, key.Section, key.Key); if (val == null) { continue; } string text; try { text = TomlTypeConverter.ConvertToString(TomlTypeConverter.ConvertToValue(key.Value, val.SettingType), val.SettingType); } catch (Exception ex) { WarnOnce("parse|" + t.Guid + "|" + key.Section + "|" + key.Key, "Kept value \"" + key.Value + "\" for " + Describe(t.Guid, key.Section, key.Key) + " can't be read as " + val.SettingType.Name + "; leaving the setting alone. (" + ex.Message + ")"); continue; } if (text != key.Value && !value3) { flag |= store.Set(t.Guid, key.Section, key.Key, text); } string serializedValue = val.GetSerializedValue(); if (serializedValue == text) { continue; } applyingOnThisThread++; try { val.SetSerializedValue(text); } catch (Exception ex2) { warn("Failed to set " + Describe(t.Guid, key.Section, key.Key) + ": " + ex2.Message); } finally { applyingOnThisThread--; } if (val.GetSerializedValue() != text) { WarnOnce("clamp|" + t.Guid + "|" + key.Section + "|" + key.Key, "The mod rejected or clamped the kept value " + text + " for " + Describe(t.Guid, key.Section, key.Key) + "."); continue; } num++; if (value3) { info("Applied pack default " + Describe(t.Guid, key.Section, key.Key) + " = " + text + " (player hasn't chosen yet; config had " + serializedValue + ")."); } else { info("Restored " + Describe(t.Guid, key.Section, key.Key) + " = " + text + " (config had " + serializedValue + ")."); } } if (num > 0 && !t.File.SaveOnConfigSet) { try { t.File.Save(); } catch (Exception ex3) { warn("Couldn't save " + t.Guid + "'s config: " + ex3.Message); } } if (flag) { SaveStore(); } return num; } } private void OnSettingChanged(Target t, SettingChangedEventArgs e) { if (applyingOnThisThread > 0) { return; } lock (stateGate) { if (reloading.ContainsKey(t.File)) { return; } } ConfigEntryBase val = ((e != null) ? e.ChangedSetting : null); if (val == null || val.Definition == null) { return; } string section = val.Definition.Section; string key = val.Definition.Key; if (tracked.Matches(t.Guid, section, key)) { string serializedValue = val.GetSerializedValue(); if (store.Set(t.Guid, section, key, serializedValue)) { SaveStore(); info("Kept " + Describe(t.Guid, section, key) + " = " + serializedValue + "."); } } } public void BeginReload(ConfigFile file) { if (file == null) { return; } lock (stateGate) { reloading.TryGetValue(file, out var value); reloading[file] = value + 1; } } public void EndReload(ConfigFile file) { if (file == null) { return; } Target target = null; lock (stateGate) { if (!reloading.TryGetValue(file, out var value)) { return; } if (value > 1) { reloading[file] = value - 1; return; } reloading.Remove(file); foreach (Target target2 in targets) { if (object.ReferenceEquals(target2.File, file)) { target = target2; break; } } } if (target != null) { Apply(target); } } private static ConfigEntryBase Find(ConfigFile file, string section, string key) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown ConfigDefinition val; try { val = new ConfigDefinition(section, key); } catch (ArgumentException) { return null; } return (!file.ContainsKey(val)) ? null : file[val]; } private void SaveStore() { Action<Action> scheduleSave = ScheduleSave; if (scheduleSave == null) { WriteStore(); return; } lock (saveGate) { if (savePending) { return; } savePending = true; } try { scheduleSave(FlushPending); } catch (Exception ex) { warn("Couldn't schedule a keepsake.cfg save, writing now: " + ex.Message); FlushPending(); } } private void FlushPending() { lock (saveGate) { if (!savePending) { return; } savePending = false; } WriteStore(); } public int ResetAll() { lock (saveGate) { savePending = false; } store.DeleteAll(); return ApplyAll(); } public void Flush() { FlushPending(); } private void WriteStore() { try { store.Save(); } catch (Exception ex) { warn("Couldn't write " + store.FilePath + ": " + ex.Message); } } private void WarnOnce(string id, string message) { lock (warnedOnce) { if (!warnedOnce.Add(id)) { return; } } warn(message); } private static string Describe(string guid, string section, string key) { return guid + " [" + section + "] " + key; } } internal sealed class Integration { public readonly string Id; public readonly string PluginGuid; public readonly string Section; public readonly string Key; public readonly string Label; public readonly string Description; public readonly string ConfigName; public readonly bool ShippedDefaultOn; public readonly string RequiresName; public bool DefaultOn; public static readonly Integration AutoSprint = new Integration("AutoSprint", "com.score.AutoSprint", "General", "Enable Mod", "Auto Sprint", "Sprint automatically whenever you can (score's AutoSprint). Your choice is kept by Keepsake, so modpack updates won't reset it.", "Auto Sprint", shippedDefaultOn: false, "Score's AutoSprint"); public static readonly Integration SprintCrosshair = new Integration("SprintCrosshair", "FluxSquido.SquidosSprintCrosshair", "General", "Enabled", "Squido's Sprint Crosshair", "Keep your crosshair when sprinting, more options below. Your choice is kept by Keepsake, so modpack updates won't reset it.", "Sprint Crosshair", shippedDefaultOn: true, "Squido's Sprint Crosshair"); public static readonly Integration SprintCrosshairColorAll = new Integration("SprintCrosshairColorAll", "FluxSquido.SquidosSprintCrosshair", "Global Crosshair Behaviors", "Color Crosshair For All Characters", "Color Crosshair (All)", "Tint every character's crosshair to the sprint color while sprinting. Has no effect unless \"Squido's Sprint Crosshair\" is on.", "Sprint Crosshair Color All", shippedDefaultOn: false, "Squido's Sprint Crosshair"); public static readonly Integration SprintCrosshairSpreadAll = new Integration("SprintCrosshairSpreadAll", "FluxSquido.SquidosSprintCrosshair", "Global Crosshair Behaviors", "Increase Spread For All Characters", "Spread Crosshair (All)", "Visually widen every character's crosshair while sprinting. Has no effect unless \"Squido's Sprint Crosshair\" is on.", "Sprint Crosshair Spread All", shippedDefaultOn: false, "Squido's Sprint Crosshair"); public static readonly Integration SprintCrosshairRemoveAll = new Integration("SprintCrosshairRemoveAll", "FluxSquido.SquidosSprintCrosshair", "Global Crosshair Behaviors", "Remove Sprint Crosshair For All Characters", "Remove Sprint Crosshair (All)", "Hide the sprint arrow for every character, so you just keep your normal crosshair while sprinting. Has no effect unless \"Squido's Sprint Crosshair\" is on.", "Sprint Crosshair Remove All", shippedDefaultOn: false, "Squido's Sprint Crosshair"); public static readonly Integration[] All = new Integration[5] { AutoSprint, SprintCrosshair, SprintCrosshairRemoveAll, SprintCrosshairColorAll, SprintCrosshairSpreadAll }; public ConfigEntryBase Entry { get; private set; } public string RowId => "keepsake." + Id.ToLowerInvariant(); public bool IsOn { get { if (Entry == null) { return false; } object boxedValue = Entry.BoxedValue; return boxedValue is bool && (bool)boxedValue; } } public Integration(string id, string pluginGuid, string section, string key, string label, string description, string configName, bool shippedDefaultOn, string requiresName = null) { RequiresName = requiresName ?? pluginGuid; Id = id; PluginGuid = pluginGuid; Section = section; Key = key; Label = label; Description = description; ConfigName = configName; ShippedDefaultOn = shippedDefaultOn; DefaultOn = shippedDefaultOn; } public bool Resolve(ConfigFile file, Action<string> warn) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Entry = null; if (file == null) { return false; } ConfigDefinition val = new ConfigDefinition(Section, Key); if (!file.ContainsKey(val)) { warn?.Invoke(Id + ": " + PluginGuid + " has no [" + Section + "] " + Key + " setting (an older version of the mod?); row skipped."); return false; } ConfigEntryBase val2 = file[val]; if (val2.SettingType != typeof(bool)) { warn?.Invoke(Id + ": [" + Section + "] " + Key + " is " + val2.SettingType.Name + ", not a bool; row skipped."); return false; } Entry = val2; return true; } public void Set(bool on) { if (Entry != null && IsOn != on) { Entry.BoxedValue = on; } } } internal static class SettingsRows { private const string RowPrefix = "keepsake."; private static readonly Dictionary<string, Integration> byRowId = new Dictionary<string, Integration>(StringComparer.Ordinal); private static readonly List<Integration> inOrder = new List<Integration>(); public static int Count => byRowId.Count; public static IList<Integration> InOrder => inOrder; public static void Register(Integration integration) { if (!byRowId.ContainsKey(integration.RowId)) { inOrder.Add(integration); } byRowId[integration.RowId] = integration; } public static Integration For(BaseSettingsControl control) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if ((Object)(object)control == (Object)null || (int)control.settingSource != 2) { return null; } string settingName = control.settingName; if (settingName == null || !settingName.StartsWith("keepsake.", StringComparison.Ordinal)) { return null; } Integration value; return (!byRowId.TryGetValue(settingName, out value) || value.Entry == null) ? null : value; } public static string ChoiceValue(BaseSettingsControl control, bool on) { CarouselController val = (CarouselController)(object)((control is CarouselController) ? control : null); Choice[] array = ((!((Object)(object)val != (Object)null)) ? null : val.choices); if (array != null && array.Length >= 2) { return array[on ? 1 : 0].convarValue; } return (!on) ? "0" : "1"; } public static void MakeRow(GameObject clone, Integration integration) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) ((Object)clone).name = "Keepsake_" + integration.Id; CarouselController component = clone.GetComponent<CarouselController>(); ((BaseSettingsControl)component).settingSource = (SettingSource)2; ((BaseSettingsControl)component).settingName = integration.RowId; ((BaseSettingsControl)component).nameToken = integration.Label; if ((Object)(object)((BaseSettingsControl)component).nameLabel != (Object)null) { ((BaseSettingsControl)component).nameLabel.token = integration.Label; } ((BaseSettingsControl)component).useConfirmationDialog = false; ((BaseSettingsControl)component).updateControlsInUpdate = true; component.controlsSubsection = false; component.subsectionHelper = null; ((BaseSettingsControl)component).onValueChanged = null; HGButton[] componentsInChildren = clone.GetComponentsInChildren<HGButton>(true); foreach (HGButton val in componentsInChildren) { if (!string.IsNullOrEmpty(val.hoverToken)) { val.hoverToken = integration.Description; } } } } internal sealed class KeepsakeTab : MonoBehaviour { internal const string HeaderName = "Keepsake"; internal const string HeaderText = "KEEPSAKE"; private const string TemplateSetting = "toggleArtificerHover"; private static readonly string[] PanelPrefabs = new string[2] { "RoR2/Base/UI/SettingsPanel.prefab", "RoR2/Base/UI/SettingsPanelTitle.prefab" }; private static bool installed; private static bool loggedBuild; internal static void Install() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (installed) { return; } installed = true; string[] panelPrefabs = PanelPrefabs; foreach (string text in panelPrefabs) { try { GameObject val = Addressables.LoadAssetAsync<GameObject>((object)text).WaitForCompletion(); if ((Object)(object)val == (Object)null) { KeepsakePlugin.Caution("Couldn't load " + text + "; no Keepsake tab there."); } else if ((Object)(object)val.GetComponent<KeepsakeTab>() == (Object)null) { val.AddComponent<KeepsakeTab>(); } } catch (Exception ex) { KeepsakePlugin.Caution("Couldn't hook " + text + " for the Keepsake tab: " + ex.Message); } } } private void Awake() { try { Build(); } catch (Exception ex) { KeepsakePlugin.Caution("Couldn't build the Keepsake settings tab: " + ex); } } private void Build() { //IL_00b4: 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_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Expected O, but got Unknown if (SettingsRows.Count == 0) { return; } HGHeaderNavigationController nav = ((Component)this).GetComponent<HGHeaderNavigationController>(); if ((Object)(object)nav == (Object)null) { nav = ((Component)this).GetComponentInChildren<HGHeaderNavigationController>(true); } if ((Object)(object)nav == (Object)null || nav.headers == null || nav.headers.Count == 0) { KeepsakePlugin.Caution("Settings panel has no tab bar; no Keepsake tab."); return; } BaseSettingsControl val = FindTemplate(((Component)this).gameObject); if ((Object)(object)val == (Object)null) { KeepsakePlugin.Caution("Couldn't find the game's toggleArtificerHover setting to copy; no Keepsake tab."); return; } int num = -1; for (int i = 0; i < nav.headers.Count; i++) { GameObject headerRoot = nav.headers[i].headerRoot; if ((Object)(object)headerRoot != (Object)null && ((Component)val).transform.IsChildOf(headerRoot.transform)) { num = i; break; } } if (num < 0) { KeepsakePlugin.Caution("Couldn't tell which settings tab the template is on; no Keepsake tab."); return; } GameObject val2 = BuildPage(nav.headers[num].headerRoot); if ((Object)(object)val2 == (Object)null) { return; } MPButton button = BuildHeaderButton(nav); if ((Object)(object)button == (Object)null) { Object.DestroyImmediate((Object)(object)val2); return; } Header item = nav.headers[num]; item.headerButton = button; item.headerName = "Keepsake"; item.tmpHeaderText = ((Component)button).GetComponentInChildren<TextMeshProUGUI>(true); item.headerRoot = val2; nav.headers.Add(item); ((UnityEvent)((Button)button).onClick).AddListener((UnityAction)delegate { nav.ChooseHeaderByButton(button); }); if (!loggedBuild) { loggedBuild = true; KeepsakePlugin.Say("Added the Keepsake settings tab (" + SettingsRows.Count + " row(s))."); } } private static BaseSettingsControl FindTemplate(GameObject root) { BaseSettingsControl[] componentsInChildren = root.GetComponentsInChildren<BaseSettingsControl>(true); foreach (BaseSettingsControl val in componentsInChildren) { if (val is CarouselController && val.settingName == "toggleArtificerHover") { return val; } } return null; } private GameObject BuildPage(GameObject source) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown Transform parent = source.transform.parent; GameObject val = new GameObject("Keepsake (building)", new Type[1] { typeof(RectTransform) }); val.SetActive(false); val.transform.SetParent(parent, false); try { GameObject val2 = Object.Instantiate<GameObject>(source, val.transform); ((Object)val2).name = "SettingsSubPanel, Keepsake"; val2.SetActive(false); SettingsPanelController[] componentsInChildren = val2.GetComponentsInChildren<SettingsPanelController>(true); foreach (SettingsPanelController val3 in componentsInChildren) { val3.ensureElementsEnabled = (GameObject[])(object)new GameObject[0]; } BaseSettingsControl val4 = FindTemplate(val2); Transform parent2 = ((Component)val4).transform.parent; for (int num = parent2.childCount - 1; num >= 0; num--) { Transform child = parent2.GetChild(num); if ((Object)(object)child != (Object)(object)((Component)val4).transform) { Object.DestroyImmediate((Object)(object)((Component)child).gameObject); } } BaseSettingsControl[] componentsInChildren2 = val2.GetComponentsInChildren<BaseSettingsControl>(true); foreach (BaseSettingsControl val5 in componentsInChildren2) { if ((Object)(object)val5 != (Object)(object)val4) { Object.DestroyImmediate((Object)(object)((Component)val5).gameObject); } } foreach (Integration item in SettingsRows.InOrder) { if (item.Entry != null) { try { GameObject clone = Object.Instantiate<GameObject>(((Component)val4).gameObject, parent2); SettingsRows.MakeRow(clone, item); } catch (Exception ex) { KeepsakePlugin.Caution("Couldn't add the " + item.Label + " row: " + ex); } } } Object.DestroyImmediate((Object)(object)((Component)val4).gameObject); val2.transform.SetParent(parent, false); val2.transform.SetSiblingIndex(source.transform.GetSiblingIndex() + 1); return val2; } finally { Object.DestroyImmediate((Object)(object)val); } } private static MPButton BuildHeaderButton(HGHeaderNavigationController nav) { //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_0127: Unknown result type (might be due to invalid IL or missing references) MPButton val = null; int num = -1; Transform val2 = null; for (int num2 = nav.headers.Count - 1; num2 >= 0; num2--) { MPButton headerButton = nav.headers[num2].headerButton; if (!((Object)(object)headerButton == (Object)null)) { if ((Object)(object)val2 == (Object)null) { val2 = ((Component)headerButton).transform.parent; } if ((Object)(object)((Component)headerButton).transform.parent == (Object)(object)val2) { num = Math.Max(num, ((Component)headerButton).transform.GetSiblingIndex()); } bool flag = (Object)(object)nav.headerHighlightObject != (Object)null && nav.headerHighlightObject.transform.IsChildOf(((Component)headerButton).transform); if ((Object)(object)val == (Object)null && !flag) { val = headerButton; } } } if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { KeepsakePlugin.Caution("Couldn't copy a settings tab button; no Keepsake tab."); return null; } GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, val2); ((Object)val3).name = "GenericHeaderButton (Keepsake)"; val3.transform.SetSiblingIndex(num + 1); val3.transform.localScale = Vector3.one; ViewableTag[] componentsInChildren = val3.GetComponentsInChildren<ViewableTag>(true); foreach (ViewableTag val4 in componentsInChildren) { Object.DestroyImmediate((Object)(object)val4); } LanguageTextMeshController componentInChildren = val3.GetComponentInChildren<LanguageTextMeshController>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.token = "KEEPSAKE"; } MPButton val5 = val3.GetComponent<MPButton>(); if ((Object)(object)val5 == (Object)null) { val5 = val3.GetComponentInChildren<MPButton>(true); } for (int j = 0; j < ((UnityEventBase)((Button)val5).onClick).GetPersistentEventCount(); j++) { ((UnityEventBase)((Button)val5).onClick).SetPersistentListenerState(j, (UnityEventCallState)0); } ((UnityEventBase)((Button)val5).onClick).RemoveAllListeners(); return val5; } } [HarmonyPatch(typeof(BaseSettingsControl), "GetCurrentValue")] internal static class SettingsReadPatch { private static void Postfix(BaseSettingsControl __instance, ref string __result) { Integration integration = SettingsRows.For(__instance); if (integration != null) { __result = SettingsRows.ChoiceValue(__instance, integration.IsOn); } } } [HarmonyPatch(typeof(BaseSettingsControl), "SubmitSettingInternal")] internal static class SettingsWritePatch { private static void Postfix(BaseSettingsControl __instance, string newValue) { Integration integration = SettingsRows.For(__instance); if (integration != null && newValue != null) { integration.Set(newValue == SettingsRows.ChoiceValue(__instance, on: true)); } } } [HarmonyPatch(typeof(BaseSettingsControl), "ResetToDefault")] internal static class SettingsResetPatch { private static bool Prefix(BaseSettingsControl __instance) { Integration integration = SettingsRows.For(__instance); if (integration == null) { return true; } __instance.SubmitSetting(SettingsRows.ChoiceValue(__instance, integration.DefaultOn)); return false; } } internal static class RiskOfOptionsCompat { private const string Guid = "FluxSquido.Keepsake"; private const string Name = "Keepsake"; [MethodImpl(MethodImplOptions.NoInlining)] internal static void Register() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown ModSettingsManager.SetModDescription("Keeps your choices for supported mods across modpack updates. They're saved in keepsake.cfg in the game folder.", "FluxSquido.Keepsake", "Keepsake"); ModSettingsManager.AddOption((BaseOption)new GenericButtonOption("Delete keepsake.cfg", "Saved Choices", "Deletes keepsake.cfg and forgets every choice Keepsake has saved. Asks to confirm first.", "Delete", new UnityAction(ConfirmDelete)), "FluxSquido.Keepsake", "Keepsake"); } private static void ConfirmDelete() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown SimpleDialogBox val; try { val = SimpleDialogBox.Create((MPEventSystem)null); } catch (Exception ex) { KeepsakePlugin.Caution("Couldn't open the confirmation dialog; keepsake.cfg was not deleted. " + ex.Message); return; } val.headerToken = new TokenParamsPair("Delete keepsake.cfg?", Array.Empty<object>()); val.descriptionToken = new TokenParamsPair("This forgets every choice Keepsake has saved for you and puts those settings back to your modpack's defaults. It can't be undone!!!", Array.Empty<object>()); val.AddActionButton(new UnityAction(DeleteNow), "Delete", true, Array.Empty<object>()); val.AddCancelButton("Cancel", Array.Empty<object>()); } private static void DeleteNow() { if (KeepsakePlugin.Engine == null) { return; } try { int num = KeepsakePlugin.Engine.ResetAll(); KeepsakePlugin.Say("Deleted " + KeepsakePlugin.KeepsakeCfgPath + "; " + num + " setting(s) went back to the pack default."); } catch (Exception ex) { KeepsakePlugin.Caution("Couldn't delete keepsake.cfg: " + ex.Message); } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("FluxSquido.Keepsake", "Keepsake", "2.4.0")] public class KeepsakePlugin : BaseUnityPlugin { private sealed class IntegrationSettings { public Integration Integration; public ConfigEntry<bool> Enabled; public ConfigEntry<bool> DefaultOn; } public const string GUID = "FluxSquido.Keepsake"; public const string NAME = "Keepsake"; public const string VERSION = "2.4.0"; internal const string RiskOfOptionsGuid = "com.rune580.riskofoptions"; internal static string KeepsakeCfgPath; internal static OverrideEngine Engine; private static ManualLogSource log; private OverrideStore store; private TrackList tracked; private Harmony harmony; private IntegrationSettings[] integrationSettings; private void Awake() { //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown log = ((BaseUnityPlugin)this).Logger; integrationSettings = new IntegrationSettings[Integration.All.Length]; for (int i = 0; i < Integration.All.Length; i++) { Integration integration = Integration.All[i]; integrationSettings[i] = new IntegrationSettings { Integration = integration, Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Integrations", integration.ConfigName, true, "Adds a \"" + integration.Label + "\" row to the game's settings menu and keeps each player's choice across modpack updates. Needs " + integration.RequiresName + "; does nothing if it isn't installed."), DefaultOn = ((BaseUnityPlugin)this).Config.Bind<bool>("Integrations", integration.ConfigName + " Default", integration.ShippedDefaultOn, "What players get for \"" + integration.Label + "\" until they pick for themselves.") }; } tracked = new TrackList(); string text = (KeepsakeCfgPath = Path.Combine(Path.Combine(Application.dataPath, "Config"), "keepsake.cfg")); store = new OverrideStore(text); try { int num = store.Load(Caution); if (num > 0) { Say("Migrated " + num + " value(s) from the Keepsake 1.x format."); } } catch (Exception ex) { log.LogError((object)("Couldn't read " + text + "; starting with no kept values. " + ex)); } Engine = new OverrideEngine(store, tracked, Say, Caution); Engine.ScheduleSave = delegate(Action save) { RoR2Application.onNextUpdate += save; }; harmony = new Harmony("FluxSquido.Keepsake"); Patch(typeof(ConfigReloadPatch)); Say("Keepsake 2.4.0. Player values: " + text + " (" + store.Count + " kept)."); } private void Start() { if (Engine == null) { return; } IntegrationSettings[] array = integrationSettings; foreach (IntegrationSettings upIntegration in array) { SetUpIntegration(upIntegration); } foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { if (!(pluginInfo.Key == "FluxSquido.Keepsake") && pluginInfo.Value != null) { BaseUnityPlugin instance = pluginInfo.Value.Instance; if (!((Object)(object)instance == (Object)null)) { Engine.Attach(pluginInfo.Key, instance.Config); } } } int num = Engine.ApplyAll(); Say("Tracking: " + ((tracked.Count != 0) ? tracked.ToString() : "nothing") + ". Managing " + Engine.TargetCount + " mod config(s); restored " + num + " setting(s) at startup."); if (SettingsRows.Count > 0) { Patch(typeof(SettingsReadPatch)); Patch(typeof(SettingsWritePatch)); Patch(typeof(SettingsResetPatch)); try { KeepsakeTab.Install(); } catch (Exception ex) { Caution("Keepsake settings tab failed: " + ex); } } if (Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions")) { try { RiskOfOptionsCompat.Register(); } catch (Exception ex2) { Caution("Risk of Options page failed: " + ex2); } } RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(ReapplyAll)); SceneManager.activeSceneChanged += OnSceneChanged; } private void SetUpIntegration(IntegrationSettings s) { Integration integration = s.Integration; integration.DefaultOn = s.DefaultOn.Value; PluginInfo value; if (!s.Enabled.Value) { Say(integration.Label + " integration is turned off in Keepsake's config."); } else if (!Chainloader.PluginInfos.TryGetValue(integration.PluginGuid, out value) || value == null || (Object)(object)value.Instance == (Object)null) { Say(integration.Label + ": " + integration.PluginGuid + " isn't installed, so no row."); } else if (integration.Resolve(value.Instance.Config, Caution)) { tracked.Add(integration.PluginGuid, integration.Section, integration.Key); Engine.SetFallback(integration.PluginGuid, integration.Section, integration.Key, (!integration.DefaultOn) ? "false" : "true"); SettingsRows.Register(integration); } } private void Patch(Type patchClass) { try { harmony.PatchAll(patchClass); } catch (Exception ex) { Caution("Patch " + patchClass.Name + " failed: " + ex.Message); } } private void OnApplicationQuit() { if (Engine != null) { Engine.Flush(); } } private static void ReapplyAll() { if (Engine != null) { Engine.ApplyAll(); } } private static void OnSceneChanged(Scene from, Scene to) { if (Engine != null) { Engine.ApplyAll(); } } internal static void Say(string message) { if (log != null) { log.LogInfo((object)message); } } internal static void Caution(string message) { if (log != null) { log.LogWarning((object)message); } } } [HarmonyPatch(typeof(ConfigFile), "Reload")] internal static class ConfigReloadPatch { private static void Prefix(ConfigFile __instance) { if (KeepsakePlugin.Engine != null) { KeepsakePlugin.Engine.BeginReload(__instance); } } private static Exception Finalizer(ConfigFile __instance, Exception __exception) { if (KeepsakePlugin.Engine != null) { KeepsakePlugin.Engine.EndReload(__instance); } return __exception; } }