using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Ryuka.Sulfur.NativeUI; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("SULFUR Config")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SULFUR Config")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("5a9d2c4e-e1a5-45a4-a09b-2e2945f22014")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace Ryuka.SulfurConfig; internal sealed class ConfigEntryModel { public string PluginGuid; public string PluginName; public string Section; public string Key; public string Description; public string DisplayName; public Type SettingType; public ConfigEntryBase Entry; public string AppliedValue; public string DraftValue; public string DefaultValue; public bool RequiresRestart; public bool LiveApply; public bool Advanced; public bool Hidden; public bool Dangerous; public List Badges = new List(); public List ValueList = new List(); public float? MinValue; public float? MaxValue; public string RangeText; public bool IsDirty => AppliedValue != DraftValue; public static ConfigEntryModel Create(string pluginGuid, string pluginName, string section, string key, ConfigEntryBase entry) { ConfigEntryModel configEntryModel = new ConfigEntryModel(); string text = ((entry.Description != null) ? entry.Description.Description : ""); configEntryModel.PluginGuid = pluginGuid; configEntryModel.PluginName = pluginName; configEntryModel.Section = section; configEntryModel.Key = key; configEntryModel.DisplayName = SulfurLocalization.Get(pluginGuid, "entry." + section + "." + key + ".name", key); configEntryModel.Description = SulfurLocalization.Get(pluginGuid, "entry." + section + "." + key + ".description", text); configEntryModel.SettingType = entry.SettingType; configEntryModel.Entry = entry; configEntryModel.AppliedValue = entry.GetSerializedValue(); configEntryModel.DraftValue = configEntryModel.AppliedValue; configEntryModel.DefaultValue = SerializeDefaultValue(entry.DefaultValue, entry.SettingType); configEntryModel.RequiresRestart = HasAnyTag(entry, "SULFURConfigRestartRequired", "SulfurConfigRestartRequired", "RyukaConfigRestartRequired", "RequiresRestart", "RestartRequired"); configEntryModel.LiveApply = HasAnyTag(entry, "SULFURConfigLiveApply", "SulfurConfigLiveApply", "RyukaConfigLiveApply", "LiveApply", "RuntimeEditable"); configEntryModel.Advanced = HasAnyTag(entry, "SULFURConfigAdvanced", "SulfurConfigAdvanced", "RyukaConfigAdvanced", "Advanced"); configEntryModel.Hidden = HasAnyTag(entry, "SULFURConfigHidden", "SulfurConfigHidden", "RyukaConfigHidden", "Hidden", "HideFromSULFURConfig", "HideFromSulfurConfig", "HideFromRyukaConfig", "HideFromConfigPanel", "HideFromREPOConfig", "HideREPOConfig"); configEntryModel.Dangerous = HasAnyTag(entry, "SULFURConfigDangerous", "SulfurConfigDangerous", "RyukaConfigDangerous", "Dangerous", "Unsafe"); configEntryModel.ReadAcceptableValues(); configEntryModel.BuildBadges(); return configEntryModel; } public bool GetBoolDraft() { bool result; return bool.TryParse(DraftValue, out result) && result; } public float GetFloatDraft() { if (float.TryParse(DraftValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } return 0f; } public float GetDefaultNumericMin() { return -999999f; } public float GetDefaultNumericMax() { return 999999f; } public void SetDraft(string value) { DraftValue = value ?? ""; } public void SetDefaultDraft() { DraftValue = DefaultValue ?? ""; } public void Apply() { Entry.SetSerializedValue(DraftValue); Entry.ConfigFile.Save(); AppliedValue = Entry.GetSerializedValue(); DraftValue = AppliedValue; } public string GetLocalizedValueLabel(string rawValue) { return SulfurLocalization.Get(PluginGuid, "value." + Section + "." + Key + "." + rawValue, rawValue); } private void ReadAcceptableValues() { if (Entry.Description == null || Entry.Description.AcceptableValues == null) { return; } object acceptableValues = Entry.Description.AcceptableValues; Type type = acceptableValues.GetType(); PropertyInfo property = type.GetProperty("AcceptableValues"); if (property != null && property.GetValue(acceptableValues, null) is Array array) { foreach (object item in array) { if (item != null) { ValueList.Add(item.ToString()); } } } PropertyInfo property2 = type.GetProperty("MinValue"); PropertyInfo property3 = type.GetProperty("MaxValue"); if (property2 != null && property3 != null) { object value = property2.GetValue(acceptableValues, null); object value2 = property3.GetValue(acceptableValues, null); if (TryToFloat(value, out var result) && TryToFloat(value2, out var result2)) { MinValue = result; MaxValue = result2; } } } private void BuildBadges() { if (SettingType != null) { Badges.Add(SettingType.Name); } if (MinValue.HasValue && MaxValue.HasValue) { RangeText = "Allowed range: " + MinValue.Value.ToString("0.###", CultureInfo.InvariantCulture) + " - " + MaxValue.Value.ToString("0.###", CultureInfo.InvariantCulture); Badges.Add("Range"); } if (ValueList.Count > 0) { Badges.Add("Values: " + ValueList.Count); } } private static string SerializeDefaultValue(object value, Type type) { if (value == null) { return ""; } if (type == typeof(float)) { return ((float)value).ToString(CultureInfo.InvariantCulture); } if (type == typeof(double)) { return ((double)value).ToString(CultureInfo.InvariantCulture); } if (type == typeof(int)) { return ((int)value).ToString(CultureInfo.InvariantCulture); } if (type == typeof(bool)) { return ((bool)value) ? "true" : "false"; } return value.ToString(); } private static bool TryToFloat(object value, out float result) { result = 0f; if (value == null) { return false; } try { result = Convert.ToSingle(value, CultureInfo.InvariantCulture); return true; } catch { return false; } } private static bool HasAnyTag(ConfigEntryBase entry, params string[] tagNames) { if (entry == null || entry.Description == null || entry.Description.Tags == null) { return false; } object[] tags = entry.Description.Tags; foreach (object obj in tags) { if (obj == null) { continue; } string a = obj.ToString(); foreach (string b in tagNames) { if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } internal sealed class ConfigPluginGroup { public string Guid; public string Name; public string DisplayName; public string Description; public string Location; public List Sections = new List(); } internal static class ConfigScanner { public static List Scan(ManualLogSource logger) { List list = new List(); foreach (PluginInfo value2 in Chainloader.PluginInfos.Values) { if (value2 == null || (Object)(object)value2.Instance == (Object)null || value2.Metadata == null) { continue; } ConfigFile config = value2.Instance.Config; if (config == null) { continue; } string text = value2.Metadata.GUID ?? "unknown.guid"; string text2 = value2.Metadata.Name ?? text; string text3 = value2.Location ?? ""; SulfurLocalization.LoadPluginLocalization(text, text3); string text4 = SulfurLocalization.Get(text, "plugin.name", text2); string description = SulfurLocalization.Get(text, "plugin.description", ""); ConfigPluginGroup configPluginGroup = new ConfigPluginGroup { Guid = text, Name = text2, DisplayName = text4 + " (" + text + ")", Description = description, Location = text3 }; foreach (KeyValuePair item in config) { ConfigEntryBase value = item.Value; if (value != null) { ConfigEntryModel model = ConfigEntryModel.Create(text, text2, item.Key.Section, item.Key.Key, value); ConfigSectionGroup configSectionGroup = configPluginGroup.Sections.FirstOrDefault((ConfigSectionGroup x) => x.Name == model.Section); if (configSectionGroup == null) { configSectionGroup = new ConfigSectionGroup { Name = model.Section, DisplayName = SulfurLocalization.Get(text, "section." + model.Section, model.Section) }; configPluginGroup.Sections.Add(configSectionGroup); } configSectionGroup.Entries.Add(model); } } if (configPluginGroup.Sections.Count > 0) { list.Add(configPluginGroup); } } foreach (ConfigPluginGroup item2 in list) { item2.Sections = item2.Sections.OrderBy((ConfigSectionGroup x) => x.DisplayName).ToList(); foreach (ConfigSectionGroup section in item2.Sections) { section.Entries = section.Entries.OrderBy((ConfigEntryModel x) => x.DisplayName).ToList(); } } return list.OrderBy((ConfigPluginGroup x) => x.DisplayName).ToList(); } } internal sealed class ConfigSectionGroup { public string Name; public string DisplayName; public List Entries = new List(); } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("ryuka.sulfur.config", "SULFUR Config", "0.2.1")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "ryuka.sulfur.config"; public const string PluginName = "SULFUR Config"; public const string PluginVersion = "0.2.1"; private ConfigNativePage nativePage; internal static ManualLogSource Log { get; private set; } private void Awake() { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; SulfurLocalization.LoadPluginLocalization("ryuka.sulfur.config", ((BaseUnityPlugin)this).Info.Location); nativePage = new ConfigNativePage(((BaseUnityPlugin)this).Logger); SulfurOptionsApi.RegisterPage(new SulfurOptionsPage { PageId = "ryuka.sulfur.config", DisplayName = "SULFUR Config", SortOrder = 9999, GetDisplayName = () => L("page.name", "SULFUR Config"), BuildPage = nativePage.Build }); ((BaseUnityPlugin)this).Logger.LogInfo((object)"SULFUR Config registered native options page."); } private void OnDestroy() { SulfurOptionsApi.UnregisterPage("ryuka.sulfur.config"); if (Log == ((BaseUnityPlugin)this).Logger) { Log = null; } } internal static string L(string key, string fallback) { return SulfurLocalization.Get("ryuka.sulfur.config", key, fallback); } } internal sealed class ConfigNativePage { private readonly ManualLogSource logger; private List groups = new List(); private readonly Dictionary draftCache = new Dictionary(); private bool needsScan = true; private int lastLocalizationVersion = -1; private string filterDraft = ""; private string activeFilter = ""; private string status = ""; private bool showOnlyDirty; private bool showOnlyRyuka; private bool showAdvanced; private bool showHidden; private bool showRestartRequired; private bool showLiveApply; private bool showDangerous; public ConfigNativePage(ManualLogSource logger) { this.logger = logger; } public void Build(SulfurOptionsContext ctx) { //IL_012c: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: 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_01ba: Expected O, but got Unknown SulfurLocalization.RefreshCurrentLanguage(true); if (lastLocalizationVersion != SulfurLocalization.LanguageVersion) { lastLocalizationVersion = SulfurLocalization.LanguageVersion; needsScan = true; } EnsureScanned(); int num = CountEntries(groups); int num2 = CountDirty(groups); ctx.SetFooter(Plugin.L("badge.entries", "Entries") + ": " + num + " | " + Plugin.L("badge.dirty", "Pending") + ": " + num2, string.IsNullOrWhiteSpace(status) ? Plugin.L("footer.ready", "Ready. Changes are not saved until you click Apply.") : status, Plugin.L("button.apply_dirty", "Apply"), (Action)delegate { ApplyDirty(ctx); }); ctx.AddSection(Plugin.L("section.tools", "Tools")); SulfurOptionsSettingRows.AddSettingText(ctx, new SulfurSettingRow { Label = Plugin.L("filter.label", "Filter"), Description = Plugin.L("filter.description", "Search by mod name, GUID, section, key, or description."), IsDirty = (filterDraft != activeFilter), ShowDefaultButton = false, DirtyText = Plugin.L("badge.dirty", "Pending"), CleanText = Plugin.L("badge.clean", "Unchanged") }, filterDraft, (Action)delegate(string value) { filterDraft = value ?? ""; ctx.SetFooterStatus(Plugin.L("status.filter_draft", "Filter draft changed.")); }); ctx.AddSmallButton(Plugin.L("button.apply_filter", "Apply Filter"), (Action)delegate { activeFilter = filterDraft ?? ""; status = Plugin.L("status.filter_applied", "Filter applied."); ctx.Rebuild(); }); ctx.AddSmallButton(Plugin.L("button.clear_filter", "Clear Filter"), (Action)delegate { filterDraft = ""; activeFilter = ""; status = Plugin.L("status.filter_cleared", "Filter cleared."); ctx.Rebuild(); }); ctx.AddSmallButton(Plugin.L("button.refresh", "Refresh"), (Action)delegate { needsScan = true; status = Plugin.L("status.refreshing", "Refreshing config list."); ctx.Rebuild(); }); ctx.AddSmallButton(showOnlyDirty ? Plugin.L("filter.dirty_on", "Pending: ON") : Plugin.L("filter.dirty_off", "Pending: OFF"), (Action)delegate { showOnlyDirty = !showOnlyDirty; ctx.Rebuild(); }); ctx.AddSmallButton(showOnlyRyuka ? Plugin.L("filter.ryuka_on", "Ryuka: ON") : Plugin.L("filter.ryuka_off", "Ryuka: OFF"), (Action)delegate { showOnlyRyuka = !showOnlyRyuka; ctx.Rebuild(); }); ctx.AddSmallButton(showAdvanced ? Plugin.L("filter.advanced_on", "Advanced: ON") : Plugin.L("filter.advanced_off", "Advanced: OFF"), (Action)delegate { showAdvanced = !showAdvanced; ctx.Rebuild(); }); ctx.AddSmallButton(showHidden ? Plugin.L("filter.hidden_on", "Hidden: ON") : Plugin.L("filter.hidden_off", "Hidden: OFF"), (Action)delegate { showHidden = !showHidden; ctx.Rebuild(); }); ctx.AddSmallButton(showRestartRequired ? Plugin.L("filter.restart_on", "Restart: ON") : Plugin.L("filter.restart_off", "Restart: OFF"), (Action)delegate { showRestartRequired = !showRestartRequired; ctx.Rebuild(); }); ctx.AddSmallButton(showLiveApply ? Plugin.L("filter.live_on", "Live: ON") : Plugin.L("filter.live_off", "Live: OFF"), (Action)delegate { showLiveApply = !showLiveApply; ctx.Rebuild(); }); ctx.AddSmallButton(showDangerous ? Plugin.L("filter.dangerous_on", "Dangerous: ON") : Plugin.L("filter.dangerous_off", "Dangerous: OFF"), (Action)delegate { showDangerous = !showDangerous; ctx.Rebuild(); }); ctx.AddSpacer(12f); int num3 = 0; int num4 = 0; foreach (ConfigPluginGroup group in groups) { List visibleSections = GetVisibleSections(group); if (visibleSections.Count == 0) { continue; } int num5 = visibleSections.Sum((ConfigSectionGroup s) => s.Entries.Count(EntryMatches)); int num6 = visibleSections.Sum((ConfigSectionGroup s) => s.Entries.Count((ConfigEntryModel e) => EntryMatches(e) && e.IsDirty)); bool flag = HasFilterOrSpecialFilter(); bool flag2 = SulfurOptionsFoldoutExtensions.AddPluginFoldoutWithBadges(ctx, "plugin." + group.Guid, group.DisplayName, false, flag, new string[2] { Plugin.L("badge.entries", "Entries") + ": " + num5, (num6 > 0) ? (Plugin.L("badge.dirty", "Pending") + ": " + num6) : Plugin.L("badge.clean", "Unchanged") }); num3++; if (!flag2) { continue; } if (!string.IsNullOrWhiteSpace(group.Description)) { ctx.AddDescription(group.Description); } foreach (ConfigSectionGroup item in visibleSections) { List list = item.Entries.Where(EntryMatches).ToList(); if (list.Count == 0) { continue; } int num7 = list.Count((ConfigEntryModel x) => x.IsDirty); if (!SulfurOptionsFoldoutExtensions.AddSectionFoldout(ctx, "plugin." + group.Guid + ".section." + item.Name, item.DisplayName, false, flag)) { continue; } using (ctx.BeginThemedGroup("section.content." + group.Guid + "." + item.Name)) { ctx.AddBadgeRow(new string[2] { Plugin.L("badge.entries", "Entries") + ": " + list.Count, (num7 > 0) ? (Plugin.L("badge.dirty", "Pending") + ": " + num7) : Plugin.L("badge.clean", "Unchanged") }); foreach (ConfigEntryModel item2 in list) { DrawEntry(ctx, item2); num4++; } } } } if (num3 == 0) { ctx.AddWarning(Plugin.L("message.no_results", "No config entries match the current filters.")); } } private void EnsureScanned() { if (needsScan || groups == null || groups.Count <= 0) { groups = ConfigScanner.Scan(logger); RestoreDrafts(groups); needsScan = false; } } private void DrawEntry(SulfurOptionsContext ctx, ConfigEntryModel entry) { //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) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0112: 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) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Expected O, but got Unknown SulfurSettingHandle handle = null; SulfurSettingRow val = new SulfurSettingRow { Label = entry.DisplayName, Description = entry.Description, IndentLevel = 1, IsDirty = entry.IsDirty, RequiresRestart = entry.RequiresRestart, LiveApply = entry.LiveApply, Advanced = entry.Advanced, Hidden = entry.Hidden, Dangerous = entry.Dangerous, Message = (entry.IsDirty ? Plugin.L("message.pending_warning", "This setting has unsaved pending changes. Click Apply to write it to cfg.") : entry.RangeText), MessageKind = (SulfurMessageKind)(entry.IsDirty ? 1 : 0), DirtyText = Plugin.L("badge.dirty", "Pending"), CleanText = Plugin.L("badge.clean", "Unchanged"), RestartRequiredText = Plugin.L("badge.restart_required", "Restart Required"), LiveApplyText = Plugin.L("badge.live_apply", "Live Apply"), AdvancedText = Plugin.L("badge.advanced", "Advanced"), HiddenText = Plugin.L("badge.hidden", "Hidden"), DangerousText = Plugin.L("badge.dangerous", "Dangerous"), DefaultButtonText = Plugin.L("button.default", "Default"), ExtraBadges = entry.Badges.ToArray(), OnDefault = delegate { entry.SetDefaultDraft(); SaveDraft(entry); status = Plugin.L("status.default_draft", "Default draft set: ") + entry.Key; UpdatePendingUi(ctx, entry, handle); } }; Type type = entry.SettingType; if (type == typeof(bool)) { SulfurOptionsSettingRows.AddSettingToggleEx(ctx, val, entry.GetBoolDraft(), (Action)delegate(bool value, SulfurSettingHandle h) { entry.SetDraft(value ? "true" : "false"); SaveDraft(entry); status = Plugin.L("status.changed", "Pending change: ") + entry.Key; UpdatePendingUi(ctx, entry, h); }, ref handle); } else if (entry.ValueList.Count > 0) { List list = entry.ValueList.Select(entry.GetLocalizedValueLabel).ToList(); int num = entry.ValueList.IndexOf(entry.DraftValue); if (num < 0) { num = 0; } SulfurOptionsSettingRows.AddSettingCycleEx(ctx, val, (IReadOnlyList)list, num, (Action)delegate(int newIndex, string selectedDisplayValue, SulfurSettingHandle h) { if (newIndex >= 0 && newIndex < entry.ValueList.Count) { entry.SetDraft(entry.ValueList[newIndex]); } SaveDraft(entry); status = Plugin.L("status.changed", "Pending change: ") + entry.Key; UpdatePendingUi(ctx, entry, h); }, ref handle); } else if (type.IsEnum) { List rawValues = Enum.GetNames(type).ToList(); List list2 = rawValues.Select(entry.GetLocalizedValueLabel).ToList(); int num2 = rawValues.IndexOf(entry.DraftValue); if (num2 < 0) { num2 = 0; } SulfurOptionsSettingRows.AddSettingCycleEx(ctx, val, (IReadOnlyList)list2, num2, (Action)delegate(int newIndex, string selectedDisplayValue, SulfurSettingHandle h) { if (newIndex >= 0 && newIndex < rawValues.Count) { entry.SetDraft(rawValues[newIndex]); } SaveDraft(entry); status = Plugin.L("status.changed", "Pending change: ") + entry.Key; UpdatePendingUi(ctx, entry, h); }, ref handle); } else if (type == typeof(int) || type == typeof(float) || type == typeof(double)) { float floatDraft = entry.GetFloatDraft(); float num3 = (entry.MinValue.HasValue ? entry.MinValue.Value : entry.GetDefaultNumericMin()); float num4 = (entry.MaxValue.HasValue ? entry.MaxValue.Value : entry.GetDefaultNumericMax()); int num5 = ((!(type == typeof(int))) ? 3 : 0); SulfurOptionsSettingRows.AddSettingNumberEx(ctx, val, floatDraft, num3, num4, num5, (Action)delegate(float value, SulfurSettingHandle h) { if (type == typeof(int)) { entry.SetDraft(Mathf.RoundToInt(value).ToString(CultureInfo.InvariantCulture)); } else { entry.SetDraft(value.ToString("0.###", CultureInfo.InvariantCulture)); } SaveDraft(entry); status = Plugin.L("status.changed", "Pending change: ") + entry.Key; UpdatePendingUi(ctx, entry, h); }, ref handle); } else { SulfurOptionsSettingRows.AddSettingTextEx(ctx, val, entry.DraftValue, (Action)delegate(string value, SulfurSettingHandle h) { entry.SetDraft(value); SaveDraft(entry); status = Plugin.L("status.changed", "Pending change: ") + entry.Key; UpdatePendingUi(ctx, entry, h); }, ref handle); } } private void UpdatePendingUi(SulfurOptionsContext ctx, ConfigEntryModel entry, SulfurSettingHandle handle) { if (handle != null) { handle.SetDirty(entry.IsDirty, Plugin.L("badge.dirty", "Pending"), Plugin.L("badge.clean", "Unchanged")); handle.SetMessage(entry.IsDirty ? Plugin.L("message.pending_warning", "This setting has unsaved pending changes. Click Apply to write it to cfg.") : entry.RangeText, (SulfurMessageKind)(entry.IsDirty ? 1 : 0)); } UpdateFooter(ctx); } private void UpdateFooter(SulfurOptionsContext ctx) { if (ctx != null) { ctx.SetFooter(Plugin.L("badge.entries", "Entries") + ": " + CountEntries(groups) + " | " + Plugin.L("badge.dirty", "Pending") + ": " + CountDirty(groups), string.IsNullOrWhiteSpace(status) ? Plugin.L("footer.ready", "Ready. Changes are not saved until you click Apply.") : status, Plugin.L("button.apply_dirty", "Apply"), (Action)delegate { ApplyDirty(ctx); }); } } private void ApplyDirty(SulfurOptionsContext ctx) { int num = 0; int num2 = 0; foreach (ConfigPluginGroup group in groups) { foreach (ConfigSectionGroup section in group.Sections) { foreach (ConfigEntryModel entry in section.Entries) { if (entry.IsDirty) { try { entry.Apply(); ClearDraft(entry); num++; } catch (Exception ex) { num2++; logger.LogWarning((object)("Failed to apply " + entry.PluginGuid + " / " + entry.Section + " / " + entry.Key + ": " + ex.Message)); } } } } } status = Plugin.L("status.apply_result", "Applied and saved: ") + num + ", " + Plugin.L("status.failed", "Failed: ") + num2 + "."; ctx.Rebuild(); } private List GetVisibleSections(ConfigPluginGroup plugin) { List list = new List(); if (plugin == null) { return list; } if (showOnlyRyuka && !Contains(plugin.Guid, "ryuka") && !Contains(plugin.Name, "ryuka")) { return list; } bool pluginTextHit = MatchesFilter(plugin.DisplayName) || MatchesFilter(plugin.Guid) || MatchesFilter(plugin.Name) || MatchesFilter(plugin.Description); foreach (ConfigSectionGroup section in plugin.Sections) { bool sectionTextHit = MatchesFilter(section.DisplayName) || MatchesFilter(section.Name); if (section.Entries.Any((ConfigEntryModel entry) => EntryPassesSpecialFilters(entry) && (NoTextFilter() || pluginTextHit || sectionTextHit || EntryTextMatches(entry)))) { list.Add(section); } } return list; } private bool SectionMatches(ConfigSectionGroup section) { return section?.Entries.Any(EntryMatches) ?? false; } private bool EntryMatches(ConfigEntryModel entry) { if (entry == null) { return false; } if (!EntryPassesSpecialFilters(entry)) { return false; } if (NoTextFilter()) { return true; } return EntryTextMatches(entry); } private bool EntryPassesSpecialFilters(ConfigEntryModel entry) { if (entry == null) { return false; } if (showOnlyDirty && !entry.IsDirty) { return false; } if (showAdvanced) { if (!entry.Advanced) { return false; } } else if (entry.Advanced) { return false; } if (showHidden) { if (!entry.Hidden) { return false; } } else if (entry.Hidden) { return false; } if (showRestartRequired && !entry.RequiresRestart) { return false; } if (showLiveApply && !entry.LiveApply) { return false; } if (showDangerous && !entry.Dangerous) { return false; } return true; } private bool EntryTextMatches(ConfigEntryModel entry) { return MatchesFilter(entry.PluginName) || MatchesFilter(entry.PluginGuid) || MatchesFilter(entry.Section) || MatchesFilter(entry.Key) || MatchesFilter(entry.DisplayName) || MatchesFilter(entry.Description); } private bool MatchesFilter(string value) { if (NoTextFilter()) { return true; } return Contains(value, activeFilter); } private bool NoTextFilter() { return string.IsNullOrWhiteSpace(activeFilter); } private bool HasFilterOrSpecialFilter() { return !NoTextFilter() || showOnlyDirty || showOnlyRyuka || showAdvanced || showHidden || showRestartRequired || showLiveApply || showDangerous; } private void SaveDraft(ConfigEntryModel entry) { if (entry != null) { string draftKey = GetDraftKey(entry); if (entry.IsDirty) { draftCache[draftKey] = entry.DraftValue; } else { draftCache.Remove(draftKey); } } } private void ClearDraft(ConfigEntryModel entry) { if (entry != null) { draftCache.Remove(GetDraftKey(entry)); } } private void RestoreDrafts(List source) { if (source == null || draftCache.Count == 0) { return; } foreach (ConfigPluginGroup item in source) { foreach (ConfigSectionGroup section in item.Sections) { foreach (ConfigEntryModel entry in section.Entries) { if (draftCache.TryGetValue(GetDraftKey(entry), out var value)) { entry.SetDraft(value); } } } } } private static string GetDraftKey(ConfigEntryModel entry) { return entry.PluginGuid + "\u001f" + entry.Section + "\u001f" + entry.Key; } private static bool Contains(string text, string filter) { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(filter) && text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; } private static int CountEntries(List source) { return source?.Sum((Func)CountEntries) ?? 0; } private static int CountDirty(List source) { return source?.Sum((Func)CountDirty) ?? 0; } private static int CountEntries(ConfigPluginGroup plugin) { if (plugin == null || plugin.Sections == null) { return 0; } return plugin.Sections.Sum((ConfigSectionGroup s) => s.Entries.Count); } private static int CountDirty(ConfigPluginGroup plugin) { if (plugin == null || plugin.Sections == null) { return 0; } return plugin.Sections.Sum((ConfigSectionGroup s) => s.Entries.Count((ConfigEntryModel e) => e.IsDirty)); } }