Decompiled source of SULFUR Config v0.3.0

SULFUR Config.dll

Decompiled 5 days ago
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.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("SULFUR Config")]
[assembly: AssemblyDescription("In-game configuration editor for SULFUR BepInEx mods.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Ryuka")]
[assembly: AssemblyProduct("SULFUR Config")]
[assembly: AssemblyCopyright("Copyright © Ryuka 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("5a9d2c4e-e1a5-45a4-a09b-2e2945f22014")]
[assembly: AssemblyFileVersion("0.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.3.0.0")]
namespace Ryuka.SulfurConfig;

internal struct ApplyResult
{
	public int Applied;

	public int Invalid;

	public int Failed;
}
internal sealed class ConfigApplier
{
	private readonly ManualLogSource logger;

	private readonly HashSet<ConfigFile> unsavedFiles = new HashSet<ConfigFile>();

	public ConfigApplier(ManualLogSource logger)
	{
		this.logger = logger;
	}

	public ApplyResult Apply(IEnumerable<ConfigEntryModel> entries, DraftStore drafts)
	{
		ApplyResult result = default(ApplyResult);
		Dictionary<ConfigFile, List<ConfigEntryModel>> dictionary = new Dictionary<ConfigFile, List<ConfigEntryModel>>();
		List<ConfigFile> list = new List<ConfigFile>();
		foreach (ConfigEntryModel entry in entries)
		{
			if (drafts.IsPending(entry))
			{
				ConfigFile configFile = entry.Entry.ConfigFile;
				if (!dictionary.TryGetValue(configFile, out var value))
				{
					value = new List<ConfigEntryModel>();
					dictionary.Add(configFile, value);
					list.Add(configFile);
				}
				value.Add(entry);
			}
		}
		foreach (ConfigFile item in list)
		{
			try
			{
				ApplyFile(item, dictionary[item], drafts, ref result);
			}
			catch (Exception ex)
			{
				result.Failed += dictionary[item].Count;
				logger.LogWarning((object)("Failed to apply settings of " + item.ConfigFilePath + ": " + ex));
			}
		}
		foreach (ConfigFile item2 in new List<ConfigFile>(unsavedFiles))
		{
			if (!dictionary.ContainsKey(item2) && !TrySave(item2))
			{
				result.Failed++;
			}
		}
		return result;
	}

	private void ApplyFile(ConfigFile file, List<ConfigEntryModel> entries, DraftStore drafts, ref ApplyResult result)
	{
		int num = 0;
		bool saveOnConfigSet = file.SaveOnConfigSet;
		try
		{
			file.SaveOnConfigSet = false;
			foreach (ConfigEntryModel entry in entries)
			{
				entry.RefreshApplied();
				if (!entry.TryParse(drafts.GetValue(entry), out var value))
				{
					result.Invalid++;
					continue;
				}
				string text = entry.SerializeValue(value);
				if (text == entry.AppliedValue)
				{
					drafts.Discard(entry);
					continue;
				}
				try
				{
					entry.Entry.BoxedValue = value;
				}
				catch (Exception ex)
				{
					logger.LogWarning((object)("Failed to apply " + Describe(entry) + ": " + ex.GetType().Name + ": " + ex.Message));
				}
				entry.RefreshApplied();
				if (entry.AppliedValue != text)
				{
					result.Failed++;
					continue;
				}
				drafts.Discard(entry);
				num++;
			}
		}
		finally
		{
			file.SaveOnConfigSet = saveOnConfigSet;
		}
		if (num != 0 || unsavedFiles.Contains(file))
		{
			if (TrySave(file))
			{
				result.Applied += num;
			}
			else
			{
				result.Failed += num;
			}
		}
	}

	private bool TrySave(ConfigFile file)
	{
		try
		{
			file.Save();
			unsavedFiles.Remove(file);
			return true;
		}
		catch (Exception ex)
		{
			unsavedFiles.Add(file);
			logger.LogWarning((object)("Failed to save " + file.ConfigFilePath + ": " + ex.GetType().Name + ": " + ex.Message));
			return false;
		}
	}

	private static string Describe(ConfigEntryModel entry)
	{
		return entry.PluginGuid + " / " + entry.Section + " / " + entry.Key;
	}
}
internal enum EntryEditorKind
{
	Toggle,
	Cycle,
	Number,
	Text
}
internal sealed class ConfigEntryModel
{
	private const int FloatWidgetSignificantDigits = 7;

	private const double FloatWidgetLimit = 9999999.0;

	private const int DoubleWidgetSignificantDigits = 15;

	private const double DoubleWidgetLimit = 999999999999999.0;

	private const int MinFloatDecimals = 3;

	private const int MaxFloatDecimals = 6;

	private static readonly string[] RestartTags = new string[5] { "SULFURConfigRestartRequired", "SulfurConfigRestartRequired", "RyukaConfigRestartRequired", "RequiresRestart", "RestartRequired" };

	private static readonly string[] LiveApplyTags = new string[5] { "SULFURConfigLiveApply", "SulfurConfigLiveApply", "RyukaConfigLiveApply", "LiveApply", "RuntimeEditable" };

	private static readonly string[] AdvancedTags = new string[4] { "SULFURConfigAdvanced", "SulfurConfigAdvanced", "RyukaConfigAdvanced", "Advanced" };

	private static readonly string[] HiddenTags = new string[10] { "SULFURConfigHidden", "SulfurConfigHidden", "RyukaConfigHidden", "Hidden", "HideFromSULFURConfig", "HideFromSulfurConfig", "HideFromRyukaConfig", "HideFromConfigPanel", "HideFromREPOConfig", "HideREPOConfig" };

	private static readonly string[] DangerousTags = new string[5] { "SULFURConfigDangerous", "SulfurConfigDangerous", "RyukaConfigDangerous", "Dangerous", "Unsafe" };

	public string Id { get; private set; }

	public string PluginGuid { get; private set; }

	public string PluginName { get; private set; }

	public string Section { get; private set; }

	public string Key { get; private set; }

	public string DisplayName { get; private set; }

	public string Description { get; private set; }

	public ConfigEntryBase Entry { get; private set; }

	public Type SettingType { get; private set; }

	public string DefaultValue { get; private set; }

	public string AppliedValue { get; private set; }

	public bool RequiresRestart { get; private set; }

	public bool LiveApply { get; private set; }

	public bool Advanced { get; private set; }

	public bool Hidden { get; private set; }

	public bool Dangerous { get; private set; }

	public EntryEditorKind Kind { get; private set; }

	public bool IsFlagsEnum { get; private set; }

	public bool IsIntegral { get; private set; }

	public IReadOnlyList<string> Options { get; private set; }

	public double? RangeMin { get; private set; }

	public double? RangeMax { get; private set; }

	public double NumberMin { get; private set; }

	public double NumberMax { get; private set; }

	public bool UsesDoubleWidget { get; private set; }

	private ConfigEntryModel()
	{
	}

	public static string MakeId(string pluginGuid, string section, string key)
	{
		return pluginGuid + "\u001f" + section + "\u001f" + key;
	}

	public static ConfigEntryModel Create(string pluginGuid, string pluginName, ConfigEntryBase entry, Func<string, string, string> localize)
	{
		string section = entry.Definition.Section;
		string key = entry.Definition.Key;
		ConfigDescription description = entry.Description;
		ConfigEntryModel configEntryModel = new ConfigEntryModel();
		configEntryModel.Id = MakeId(pluginGuid, section, key);
		configEntryModel.PluginGuid = pluginGuid;
		configEntryModel.PluginName = pluginName;
		configEntryModel.Section = section;
		configEntryModel.Key = key;
		configEntryModel.Entry = entry;
		configEntryModel.SettingType = entry.SettingType;
		configEntryModel.DisplayName = localize("entry." + section + "." + key + ".name", key);
		configEntryModel.Description = localize("entry." + section + "." + key + ".description", (description != null) ? (description.Description ?? "") : "");
		configEntryModel.DefaultValue = Serialize(entry.DefaultValue, entry.SettingType);
		configEntryModel.RequiresRestart = HasAnyTag(description, RestartTags);
		configEntryModel.LiveApply = HasAnyTag(description, LiveApplyTags);
		configEntryModel.Advanced = HasAnyTag(description, AdvancedTags);
		configEntryModel.Hidden = HasAnyTag(description, HiddenTags);
		configEntryModel.Dangerous = HasAnyTag(description, DangerousTags);
		configEntryModel.Options = new string[0];
		configEntryModel.AppliedValue = entry.GetSerializedValue();
		configEntryModel.ChooseEditor((description != null) ? description.AcceptableValues : null);
		return configEntryModel;
	}

	public bool RefreshApplied()
	{
		string serializedValue;
		try
		{
			serializedValue = Entry.GetSerializedValue();
		}
		catch (Exception)
		{
			return false;
		}
		if (serializedValue == AppliedValue)
		{
			return false;
		}
		AppliedValue = serializedValue;
		return true;
	}

	public bool TryParse(string serialized, out object value)
	{
		value = null;
		if (SettingType == typeof(KeyboardShortcut) && !IsReadableShortcut(serialized))
		{
			return false;
		}
		try
		{
			value = TomlTypeConverter.ConvertToValue(serialized ?? "", SettingType);
		}
		catch (Exception)
		{
			return false;
		}
		if (value == null && SettingType.IsValueType)
		{
			return false;
		}
		AcceptableValueBase val = ((Entry.Description != null) ? Entry.Description.AcceptableValues : null);
		try
		{
			if (val != null && !val.IsValid(value))
			{
				return false;
			}
		}
		catch (Exception)
		{
			return false;
		}
		return true;
	}

	public bool IsValid(string serialized)
	{
		object value;
		return TryParse(serialized, out value);
	}

	public string SerializeValue(object value)
	{
		return Serialize(value, SettingType);
	}

	public bool TryGetNumber(string serialized, out double number)
	{
		number = 0.0;
		if (!TryParse(serialized, out var value) && !TryParseLoose(serialized, out value))
		{
			return false;
		}
		try
		{
			number = ToDecimalDouble(value);
			return true;
		}
		catch (Exception)
		{
			return false;
		}
	}

	public int GetDecimals(string currentSerialized)
	{
		if (IsIntegral)
		{
			return 0;
		}
		int val = 3;
		if (TryGetNumber(currentSerialized, out var number))
		{
			val = Math.Max(val, CountDecimals(number));
		}
		if (TryGetNumber(DefaultValue, out number))
		{
			val = Math.Max(val, CountDecimals(number));
		}
		if (RangeMin.HasValue)
		{
			val = Math.Max(val, CountDecimals(RangeMin.Value));
		}
		if (RangeMax.HasValue)
		{
			val = Math.Max(val, CountDecimals(RangeMax.Value));
		}
		return Math.Min(val, 6);
	}

	public bool TrySerializeNumber(double number, int decimals, out string serialized)
	{
		serialized = null;
		if (double.IsNaN(number) || double.IsInfinity(number))
		{
			return false;
		}
		double num = (IsIntegral ? Math.Round(number) : Math.Round(number, decimals));
		if (RangeMin.HasValue && num < RangeMin.Value)
		{
			num = RangeMin.Value;
		}
		if (RangeMax.HasValue && num > RangeMax.Value)
		{
			num = RangeMax.Value;
		}
		string serialized2 = (IsIntegral ? num.ToString("0", CultureInfo.InvariantCulture) : num.ToString("0.#########", CultureInfo.InvariantCulture));
		if (!TryParse(serialized2, out var value))
		{
			return false;
		}
		serialized = SerializeValue(value);
		return true;
	}

	public string CanonicalizeIfApplied(string value)
	{
		if (value != AppliedValue && TryParse(value, out var value2) && SerializeValue(value2) == AppliedValue)
		{
			return AppliedValue;
		}
		return value;
	}

	public string GetValueLabel(string serialized)
	{
		return SulfurLanguage.FixRtl(SulfurLocalization.Get(PluginGuid, "value." + Section + "." + Key + "." + serialized, serialized));
	}

	private void ChooseEditor(AcceptableValueBase acceptable)
	{
		Type settingType = SettingType;
		if (settingType == typeof(bool))
		{
			Kind = EntryEditorKind.Toggle;
			return;
		}
		List<string> list = ReadAcceptableList(acceptable, settingType);
		if (list.Count > 0)
		{
			Kind = EntryEditorKind.Cycle;
			Options = list;
			return;
		}
		if (settingType.IsEnum)
		{
			IsFlagsEnum = settingType.IsDefined(typeof(FlagsAttribute), inherit: false);
			if (IsFlagsEnum)
			{
				Kind = EntryEditorKind.Text;
				Options = Enum.GetNames(settingType);
			}
			else
			{
				Kind = EntryEditorKind.Cycle;
				Options = Enum.GetNames(settingType);
			}
			return;
		}
		ReadAcceptableRange(acceptable);
		if (TryGetNumericSpan(settingType, out var min, out var max, out var integral))
		{
			IsIntegral = integral;
			bool flag = settingType == typeof(double) || settingType == typeof(decimal);
			int num = (flag ? 15 : 7);
			double num2 = (RangeMin.HasValue ? Math.Max(RangeMin.Value, min) : min);
			double num3 = (RangeMax.HasValue ? Math.Min(RangeMax.Value, max) : max);
			if (num2 <= num3 && TryGetNumber(AppliedValue, out var number) && FitsWidget(number, num2, num3, num) && (!TryGetNumber(DefaultValue, out var number2) || FitsWidget(number2, num2, num3, num)) && (!RangeMin.HasValue || SignificantDigits(RangeMin.Value) <= num) && (!RangeMax.HasValue || SignificantDigits(RangeMax.Value) <= num))
			{
				Kind = EntryEditorKind.Number;
				UsesDoubleWidget = flag;
				NumberMin = num2;
				NumberMax = num3;
				return;
			}
		}
		Kind = EntryEditorKind.Text;
	}

	private static bool TryGetNumericSpan(Type type, out double min, out double max, out bool integral)
	{
		integral = true;
		if (type == typeof(int) || type == typeof(long) || type == typeof(uint) || type == typeof(ulong))
		{
			min = ((type == typeof(uint) || type == typeof(ulong)) ? 0.0 : (-9999999.0));
			max = 9999999.0;
			return true;
		}
		if (type == typeof(short))
		{
			min = -32768.0;
			max = 32767.0;
			return true;
		}
		if (type == typeof(ushort))
		{
			min = 0.0;
			max = 65535.0;
			return true;
		}
		if (type == typeof(byte))
		{
			min = 0.0;
			max = 255.0;
			return true;
		}
		if (type == typeof(sbyte))
		{
			min = -128.0;
			max = 127.0;
			return true;
		}
		integral = false;
		if (type == typeof(float))
		{
			min = -9999999.0;
			max = 9999999.0;
			return true;
		}
		if (type == typeof(double) || type == typeof(decimal))
		{
			min = -999999999999999.0;
			max = 999999999999999.0;
			return true;
		}
		min = 0.0;
		max = 0.0;
		return false;
	}

	private List<string> ReadAcceptableList(AcceptableValueBase acceptable, Type type)
	{
		List<string> list = new List<string>();
		if (acceptable == null)
		{
			return list;
		}
		PropertyInfo property = ((object)acceptable).GetType().GetProperty("AcceptableValues");
		if (property == null)
		{
			return list;
		}
		Array array;
		try
		{
			array = property.GetValue(acceptable, null) as Array;
		}
		catch (Exception)
		{
			return list;
		}
		if (array == null)
		{
			return list;
		}
		foreach (object item2 in array)
		{
			if (item2 != null)
			{
				string item = Serialize(item2, type);
				if (!list.Contains(item))
				{
					list.Add(item);
				}
			}
		}
		return list;
	}

	private void ReadAcceptableRange(AcceptableValueBase acceptable)
	{
		if (acceptable == null)
		{
			return;
		}
		Type type = ((object)acceptable).GetType();
		PropertyInfo property = type.GetProperty("MinValue");
		PropertyInfo property2 = type.GetProperty("MaxValue");
		if (property == null || property2 == null)
		{
			return;
		}
		try
		{
			RangeMin = ToDecimalDouble(property.GetValue(acceptable, null));
			RangeMax = ToDecimalDouble(property2.GetValue(acceptable, null));
		}
		catch (Exception)
		{
			RangeMin = null;
			RangeMax = null;
		}
	}

	private bool TryParseLoose(string serialized, out object value)
	{
		value = null;
		try
		{
			value = TomlTypeConverter.ConvertToValue(serialized ?? "", SettingType);
			return value != null;
		}
		catch (Exception)
		{
			return false;
		}
	}

	private static string Serialize(object value, Type type)
	{
		if (value == null)
		{
			return "";
		}
		try
		{
			return TomlTypeConverter.ConvertToString(value, type);
		}
		catch (Exception)
		{
			return Convert.ToString(value, CultureInfo.InvariantCulture) ?? "";
		}
	}

	private static bool FitsWidget(double value, double min, double max, int digits)
	{
		if (value >= min && value <= max && SignificantDigits(value) <= digits)
		{
			return CountDecimals(value) <= 6;
		}
		return false;
	}

	private static int SignificantDigits(double value)
	{
		return Math.Abs(value).ToString("0.#########", CultureInfo.InvariantCulture).Replace(".", "")
			.Trim(new char[1] { '0' })
			.Length;
	}

	private static bool IsReadableShortcut(string serialized)
	{
		if (string.IsNullOrWhiteSpace(serialized))
		{
			return true;
		}
		string[] array = serialized.Split(new char[5] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries);
		foreach (string value in array)
		{
			try
			{
				Enum.Parse(typeof(KeyCode), value);
			}
			catch (Exception)
			{
				return false;
			}
		}
		return true;
	}

	private static double ToDecimalDouble(object value)
	{
		if (value is float num)
		{
			return double.Parse(num.ToString("R", CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture);
		}
		return Convert.ToDouble(value, CultureInfo.InvariantCulture);
	}

	private static int CountDecimals(double value)
	{
		string text = value.ToString("0.#########", CultureInfo.InvariantCulture);
		int num = text.IndexOf('.');
		if (num >= 0)
		{
			return text.Length - num - 1;
		}
		return 0;
	}

	private static bool HasAnyTag(ConfigDescription description, string[] tagNames)
	{
		if (description == null || description.Tags == null)
		{
			return false;
		}
		object[] tags = 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 SectionView
{
	public ConfigSectionGroup Section;

	public List<ConfigEntryModel> Entries = new List<ConfigEntryModel>();

	public int PendingCount;
}
internal sealed class PluginView
{
	public ConfigPluginGroup Plugin;

	public List<SectionView> Sections = new List<SectionView>();

	public int EntryCount;

	public int PendingCount;
}
internal sealed class ConfigFilter
{
	private static readonly string[] AuthorGuidPrefixes = new string[3] { "ryuka.", "kumo.sulfur.", "lingyun67." };

	public string Text = "";

	public bool OnlyPending;

	public bool OnlyAuthorMods;

	public bool IncludeAdvanced;

	public bool IncludeHidden;

	public bool OnlyRestartRequired;

	public bool OnlyLiveApply;

	public bool OnlyDangerous;

	public bool HasText => !string.IsNullOrWhiteSpace(Text);

	public bool NarrowsResults
	{
		get
		{
			if (!HasText && !OnlyPending && !OnlyRestartRequired && !OnlyLiveApply)
			{
				return OnlyDangerous;
			}
			return true;
		}
	}

	public List<PluginView> BuildView(List<ConfigPluginGroup> plugins, DraftStore drafts)
	{
		List<PluginView> list = new List<PluginView>();
		string text = (HasText ? Text.Trim() : null);
		foreach (ConfigPluginGroup plugin in plugins)
		{
			if (OnlyAuthorMods && !IsAuthorPlugin(plugin))
			{
				continue;
			}
			bool flag = text == null || Contains(plugin.DisplayName, text) || Contains(plugin.Guid, text) || Contains(plugin.Name, text) || Contains(plugin.Description, text);
			PluginView pluginView = null;
			foreach (ConfigSectionGroup section in plugin.Sections)
			{
				bool flag2 = flag || Contains(section.DisplayName, text) || Contains(section.Name, text);
				SectionView sectionView = null;
				foreach (ConfigEntryModel entry in section.Entries)
				{
					if (PassesSwitches(entry, drafts) && (flag2 || EntryTextMatches(entry, text)))
					{
						if (sectionView == null)
						{
							sectionView = new SectionView
							{
								Section = section
							};
						}
						sectionView.Entries.Add(entry);
						if (drafts.IsPending(entry))
						{
							sectionView.PendingCount++;
						}
					}
				}
				if (sectionView != null)
				{
					if (pluginView == null)
					{
						pluginView = new PluginView
						{
							Plugin = plugin
						};
					}
					pluginView.Sections.Add(sectionView);
					pluginView.EntryCount += sectionView.Entries.Count;
					pluginView.PendingCount += sectionView.PendingCount;
				}
			}
			if (pluginView != null)
			{
				list.Add(pluginView);
			}
		}
		return list;
	}

	private bool PassesSwitches(ConfigEntryModel entry, DraftStore drafts)
	{
		if (entry.Advanced && !IncludeAdvanced)
		{
			return false;
		}
		if (entry.Hidden && !IncludeHidden)
		{
			return false;
		}
		if (OnlyPending && !drafts.IsPending(entry))
		{
			return false;
		}
		if (OnlyRestartRequired && !entry.RequiresRestart)
		{
			return false;
		}
		if (OnlyLiveApply && !entry.LiveApply)
		{
			return false;
		}
		if (OnlyDangerous && !entry.Dangerous)
		{
			return false;
		}
		return true;
	}

	private static bool EntryTextMatches(ConfigEntryModel entry, string text)
	{
		if (!Contains(entry.Key, text) && !Contains(entry.DisplayName, text))
		{
			return Contains(entry.Description, text);
		}
		return true;
	}

	private static bool IsAuthorPlugin(ConfigPluginGroup plugin)
	{
		string[] authorGuidPrefixes = AuthorGuidPrefixes;
		foreach (string value in authorGuidPrefixes)
		{
			if (plugin.Guid != null && plugin.Guid.StartsWith(value, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return Contains(plugin.Name, "ryuka");
	}

	private static bool Contains(string value, string text)
	{
		if (!string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(text))
		{
			return value.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0;
		}
		return false;
	}
}
internal sealed class ConfigNativePage
{
	private sealed class PendingBadgeRow
	{
		public SulfurBadgeRowHandle Handle;

		public int PendingIndex;

		public List<ConfigEntryModel> Entries;
	}

	private readonly ConfigScanner scanner;

	private readonly DraftStore drafts = new DraftStore();

	private readonly ConfigFilter filter = new ConfigFilter();

	private readonly ConfigApplier applier;

	private readonly EntryRowBuilder rows;

	private List<ConfigPluginGroup> plugins = new List<ConfigPluginGroup>();

	private List<ConfigEntryModel> allEntries = new List<ConfigEntryModel>();

	private bool needsScan = true;

	private int scannedLanguageVersion = -1;

	private string filterDraft = "";

	private string status = "";

	private bool openMatchesByDefault = true;

	private int filterGeneration;

	private readonly Dictionary<string, PendingBadgeRow> pluginBadges = new Dictionary<string, PendingBadgeRow>(StringComparer.Ordinal);

	private readonly Dictionary<string, PendingBadgeRow> sectionBadges = new Dictionary<string, PendingBadgeRow>(StringComparer.Ordinal);

	public ConfigNativePage(ManualLogSource logger)
	{
		scanner = new ConfigScanner(logger);
		applier = new ConfigApplier(logger);
		rows = new EntryRowBuilder(drafts, OnDraftChanged, SetStatusAndRebuild);
	}

	public void Build(SulfurOptionsContext ctx)
	{
		pluginBadges.Clear();
		sectionBadges.Clear();
		PrepareData();
		List<PluginView> view = filter.BuildView(plugins, drafts);
		SetFooter(ctx);
		BuildTools(ctx);
		BuildPlugins(ctx, view);
	}

	private void PrepareData()
	{
		SulfurLocalization.RefreshCurrentLanguage(false);
		if (scannedLanguageVersion != SulfurLocalization.LanguageVersion)
		{
			needsScan = true;
		}
		if (needsScan)
		{
			plugins = scanner.Scan();
			allEntries = ConfigScanner.AllEntries(plugins).ToList();
			scannedLanguageVersion = SulfurLocalization.LanguageVersion;
			needsScan = false;
		}
		else
		{
			foreach (ConfigEntryModel allEntry in allEntries)
			{
				allEntry.RefreshApplied();
			}
		}
		drafts.Reconcile(allEntries);
	}

	private void BuildTools(SulfurOptionsContext ctx)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: 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_0097: Expected O, but got Unknown
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: 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_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: 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)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dc: 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_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_024c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0251: 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_0280: Unknown result type (might be due to invalid IL or missing references)
		ctx.AddSection(ConfigText.ToolsSection);
		SulfurSettingHandle val = default(SulfurSettingHandle);
		SulfurOptionsSettingRows.AddSettingTextEx(ctx, new SulfurSettingRow
		{
			Label = ConfigText.FilterLabel,
			Description = ConfigText.FilterDescription,
			IsDirty = (filterDraft != filter.Text),
			ShowDefaultButton = false,
			DirtyText = ConfigText.PendingBadge,
			CleanText = ConfigText.UnchangedBadge
		}, filterDraft, (Action<string, SulfurSettingHandle>)delegate(string value, SulfurSettingHandle handle)
		{
			filterDraft = value ?? "";
			if (handle != null)
			{
				handle.SetDirty(filterDraft != filter.Text, ConfigText.PendingBadge, ConfigText.UnchangedBadge);
			}
			status = ConfigText.StatusFilterDraft;
			ctx.SetFooterStatus(status);
		}, ref val);
		ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[4]
		{
			new SulfurButton(ConfigText.ApplyFilterButton, (Action)delegate
			{
				filter.Text = filterDraft;
				OnFilterChanged();
				SetStatusAndRebuild(ctx, ConfigText.StatusFilterApplied);
			}),
			new SulfurButton(ConfigText.ClearFilterButton, (Action)delegate
			{
				filterDraft = "";
				filter.Text = "";
				OnFilterChanged();
				SetStatusAndRebuild(ctx, ConfigText.StatusFilterCleared);
			}),
			new SulfurButton(ConfigText.RefreshButton, (Action)delegate
			{
				needsScan = true;
				SetStatusAndRebuild(ctx, ConfigText.StatusRefreshing);
			}),
			new SulfurButton(ConfigText.CollapseAllButton, (Action)delegate
			{
				SulfurOptionsStateExtensions.ClearPageState(ctx);
				openMatchesByDefault = false;
				SetStatusAndRebuild(ctx, ConfigText.StatusCollapsed);
			})
		});
		ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[4]
		{
			Switch(ctx, ConfigText.PendingFilter(filter.OnlyPending), delegate
			{
				filter.OnlyPending = !filter.OnlyPending;
			}),
			Switch(ctx, ConfigText.AdvancedFilter(filter.IncludeAdvanced), delegate
			{
				filter.IncludeAdvanced = !filter.IncludeAdvanced;
			}),
			Switch(ctx, ConfigText.HiddenFilter(filter.IncludeHidden), delegate
			{
				filter.IncludeHidden = !filter.IncludeHidden;
			}),
			Switch(ctx, ConfigText.RyukaFilter(filter.OnlyAuthorMods), delegate
			{
				filter.OnlyAuthorMods = !filter.OnlyAuthorMods;
			})
		});
		ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[3]
		{
			Switch(ctx, ConfigText.RestartFilter(filter.OnlyRestartRequired), delegate
			{
				filter.OnlyRestartRequired = !filter.OnlyRestartRequired;
			}),
			Switch(ctx, ConfigText.LiveFilter(filter.OnlyLiveApply), delegate
			{
				filter.OnlyLiveApply = !filter.OnlyLiveApply;
			}),
			Switch(ctx, ConfigText.DangerousFilter(filter.OnlyDangerous), delegate
			{
				filter.OnlyDangerous = !filter.OnlyDangerous;
			})
		});
		ctx.AddSpacer(12f);
	}

	private void BuildPlugins(SulfurOptionsContext ctx, List<PluginView> view)
	{
		if (view.Count == 0)
		{
			ctx.AddWarning(ConfigText.NoResults);
			return;
		}
		bool narrowsResults = filter.NarrowsResults;
		bool flag = narrowsResults && openMatchesByDefault;
		string text = (narrowsResults ? ("filtered." + filterGeneration + ".") : "");
		SulfurBadgeRowHandle handle = default(SulfurBadgeRowHandle);
		foreach (PluginView item in view)
		{
			ConfigPluginGroup plugin = item.Plugin;
			bool num = SulfurOptionsFoldoutExtensions.AddPluginFoldoutWithBadges(ctx, text + "plugin." + plugin.Guid, SulfurLanguage.FixRtl(plugin.DisplayName), flag, false, (IReadOnlyList<string>)new string[2]
			{
				ConfigText.EntriesBadge + ": " + item.EntryCount,
				PendingBadge(item.PendingCount)
			}, ref handle);
			pluginBadges[plugin.Guid] = new PendingBadgeRow
			{
				Handle = handle,
				PendingIndex = 1,
				Entries = item.Sections.SelectMany((SectionView s) => s.Entries).ToList()
			};
			if (!num)
			{
				continue;
			}
			if (!string.IsNullOrWhiteSpace(plugin.Description))
			{
				ctx.AddDescription(SulfurLanguage.FixRtl(plugin.Description));
			}
			if (item.Sections.Count == 1)
			{
				DrawSection(ctx, plugin, item.Sections[0], showName: true);
				continue;
			}
			foreach (SectionView section in item.Sections)
			{
				if (SulfurOptionsFoldoutExtensions.AddSectionFoldout(ctx, text + "plugin." + plugin.Guid + ".section." + section.Section.Name, SulfurLanguage.FixRtl(section.Section.DisplayName), flag, false))
				{
					DrawSection(ctx, plugin, section, showName: false);
				}
			}
		}
	}

	private void DrawSection(SulfurOptionsContext ctx, ConfigPluginGroup plugin, SectionView sectionView, bool showName)
	{
		using (ctx.BeginThemedGroup("section.content." + plugin.Guid + "." + sectionView.Section.Name))
		{
			string text = ConfigText.EntriesBadge + ": " + sectionView.Entries.Count;
			string text2 = PendingBadge(sectionView.PendingCount);
			if (showName)
			{
				ctx.AddTextRow(SulfurLanguage.FixRtl(sectionView.Section.DisplayName));
			}
			sectionBadges[SectionKey(plugin.Guid, sectionView.Section.Name)] = new PendingBadgeRow
			{
				Handle = ctx.AddBadgeRowEx(new string[2] { text, text2 }),
				PendingIndex = 1,
				Entries = sectionView.Entries
			};
			foreach (ConfigEntryModel entry in sectionView.Entries)
			{
				rows.Draw(ctx, entry);
			}
		}
	}

	private SulfurButton Switch(SulfurOptionsContext ctx, string label, Action toggle)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		return new SulfurButton(label, (Action)delegate
		{
			toggle();
			OnFilterChanged();
			ctx.Rebuild();
		});
	}

	private void ApplyAll(SulfurOptionsContext ctx)
	{
		foreach (ConfigEntryModel allEntry in allEntries)
		{
			allEntry.RefreshApplied();
		}
		drafts.Reconcile(allEntries);
		ApplyResult applyResult = applier.Apply(allEntries, drafts);
		string text = ConfigText.StatusApplyResult + applyResult.Applied + ", " + ConfigText.StatusFailed + applyResult.Failed;
		if (applyResult.Invalid > 0)
		{
			text = text + ", " + ConfigText.StatusInvalid + applyResult.Invalid;
		}
		SetStatusAndRebuild(ctx, text + ".");
	}

	private void OnFilterChanged()
	{
		filterGeneration++;
		openMatchesByDefault = true;
	}

	private void OnDraftChanged(SulfurOptionsContext ctx, ConfigEntryModel entry, string newStatus)
	{
		status = newStatus;
		SetFooter(ctx);
		UpdatePendingBadge(pluginBadges, entry.PluginGuid);
		UpdatePendingBadge(sectionBadges, SectionKey(entry.PluginGuid, entry.Section));
	}

	private void UpdatePendingBadge(Dictionary<string, PendingBadgeRow> rows, string key)
	{
		if (!rows.TryGetValue(key, out var value) || value.Handle == null || !value.Handle.IsAlive)
		{
			return;
		}
		int num = 0;
		foreach (ConfigEntryModel entry in value.Entries)
		{
			if (drafts.IsPending(entry))
			{
				num++;
			}
		}
		value.Handle.SetBadge(value.PendingIndex, PendingBadge(num));
	}

	private static string SectionKey(string pluginGuid, string section)
	{
		return pluginGuid + "\u001f" + section;
	}

	private void SetStatusAndRebuild(SulfurOptionsContext ctx, string newStatus)
	{
		status = newStatus;
		ctx.Rebuild();
	}

	private void SetFooter(SulfurOptionsContext ctx)
	{
		ctx.SetFooter(ConfigText.EntriesBadge + ": " + allEntries.Count + " | " + ConfigText.PendingBadge + ": " + drafts.Count, string.IsNullOrWhiteSpace(status) ? ConfigText.FooterReady : status, ConfigText.ApplyButton, (Action)delegate
		{
			ApplyAll(ctx);
		});
	}

	private static string PendingBadge(int pendingCount)
	{
		if (pendingCount <= 0)
		{
			return ConfigText.UnchangedBadge;
		}
		return ConfigText.PendingBadge + ": " + pendingCount;
	}
}
internal sealed class ConfigPluginGroup
{
	public string Guid;

	public string Name;

	public string DisplayName;

	public string Description;

	public List<ConfigSectionGroup> Sections = new List<ConfigSectionGroup>();
}
internal sealed class ConfigScanner
{
	private readonly ManualLogSource logger;

	private readonly HashSet<string> localizationLoaded = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private readonly HashSet<string> failureLogged = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	public ConfigScanner(ManualLogSource logger)
	{
		this.logger = logger;
	}

	public List<ConfigPluginGroup> Scan()
	{
		List<ConfigPluginGroup> list = new List<ConfigPluginGroup>();
		foreach (PluginInfo value in Chainloader.PluginInfos.Values)
		{
			if (value == null || (Object)(object)value.Instance == (Object)null || value.Metadata == null)
			{
				continue;
			}
			string text = value.Metadata.GUID ?? "unknown.guid";
			try
			{
				ConfigPluginGroup configPluginGroup = ScanPlugin(value, text);
				if (configPluginGroup != null)
				{
					list.Add(configPluginGroup);
				}
			}
			catch (Exception ex)
			{
				if (failureLogged.Add(text))
				{
					logger.LogWarning((object)("Skipped the config of " + text + ": " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}
		list.Sort((ConfigPluginGroup a, ConfigPluginGroup b) => string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase));
		return list;
	}

	private ConfigPluginGroup ScanPlugin(PluginInfo pluginInfo, string guid)
	{
		ConfigFile config = pluginInfo.Instance.Config;
		if (config == null)
		{
			return null;
		}
		ConfigEntryBase[] configEntries = config.GetConfigEntries();
		if (configEntries == null || configEntries.Length == 0)
		{
			return null;
		}
		if (localizationLoaded.Add(guid))
		{
			SulfurLocalization.LoadPluginLocalization(guid, pluginInfo.Location ?? "");
		}
		string text = pluginInfo.Metadata.Name ?? guid;
		Func<string, string, string> func = (string key, string fallback) => SulfurLocalization.Get(guid, key, fallback);
		ConfigPluginGroup configPluginGroup = new ConfigPluginGroup
		{
			Guid = guid,
			Name = text,
			DisplayName = func("plugin.name", text) + " (" + guid + ")",
			Description = func("plugin.description", "")
		};
		Dictionary<string, ConfigSectionGroup> dictionary = new Dictionary<string, ConfigSectionGroup>(StringComparer.Ordinal);
		ConfigEntryBase[] array = configEntries;
		foreach (ConfigEntryBase val in array)
		{
			if (val == null || val.Definition == (ConfigDefinition)null)
			{
				continue;
			}
			ConfigEntryModel configEntryModel;
			try
			{
				configEntryModel = ConfigEntryModel.Create(guid, text, val, func);
			}
			catch (Exception ex)
			{
				if (failureLogged.Add(guid + "\u001f" + val.Definition.Section + "\u001f" + val.Definition.Key))
				{
					logger.LogWarning((object)("Skipped " + guid + " / " + val.Definition.Section + " / " + val.Definition.Key + ": " + ex.GetType().Name + ": " + ex.Message));
				}
				continue;
			}
			if (!dictionary.TryGetValue(configEntryModel.Section, out var value))
			{
				value = new ConfigSectionGroup
				{
					Name = configEntryModel.Section,
					DisplayName = func("section." + configEntryModel.Section, configEntryModel.Section)
				};
				dictionary.Add(configEntryModel.Section, value);
				configPluginGroup.Sections.Add(value);
			}
			value.Entries.Add(configEntryModel);
		}
		configPluginGroup.Sections.Sort((ConfigSectionGroup a, ConfigSectionGroup b) => string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase));
		foreach (ConfigSectionGroup section in configPluginGroup.Sections)
		{
			section.Entries.Sort((ConfigEntryModel a, ConfigEntryModel b) => string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase));
		}
		if (configPluginGroup.Sections.Count <= 0)
		{
			return null;
		}
		return configPluginGroup;
	}

	public static IEnumerable<ConfigEntryModel> AllEntries(IEnumerable<ConfigPluginGroup> plugins)
	{
		return plugins.SelectMany((ConfigPluginGroup p) => p.Sections).SelectMany((ConfigSectionGroup s) => s.Entries);
	}
}
internal sealed class ConfigSectionGroup
{
	public string Name;

	public string DisplayName;

	public List<ConfigEntryModel> Entries = new List<ConfigEntryModel>();
}
internal static class ConfigText
{
	public static string PageName => L("page.name", "SULFUR Config");

	public static string ToolsSection => L("section.tools", "Tools");

	public static string FilterLabel => L("filter.label", "Filter");

	public static string FilterDescription => L("filter.description", "Search by mod name, GUID, section, key, or description.");

	public static string ApplyFilterButton => L("button.apply_filter", "Apply Filter");

	public static string ClearFilterButton => L("button.clear_filter", "Clear Filter");

	public static string RefreshButton => L("button.refresh", "Refresh");

	public static string CollapseAllButton => L("button.collapse_all", "Collapse All");

	public static string ApplyButton => L("button.apply_dirty", "Apply");

	public static string EntriesBadge => L("badge.entries", "Entries");

	public static string PendingBadge => L("badge.dirty", "Pending");

	public static string UnchangedBadge => L("badge.clean", "Unchanged");

	public static string FooterReady => L("footer.ready", "Ready. Changes are not saved until you click Apply.");

	public static string StatusChanged => L("status.changed", "Pending change: ");

	public static string StatusDefaultDraft => L("status.default_draft", "Default draft set: ");

	public static string StatusApplyResult => L("status.apply_result", "Applied and saved: ");

	public static string StatusFailed => L("status.failed", "Failed: ");

	public static string StatusInvalid => L("status.invalid", "Invalid: ");

	public static string StatusFilterDraft => L("status.filter_draft", "Filter draft changed.");

	public static string StatusFilterApplied => L("status.filter_applied", "Filter applied.");

	public static string StatusFilterCleared => L("status.filter_cleared", "Filter cleared.");

	public static string StatusRefreshing => L("status.refreshing", "Refreshing config list.");

	public static string StatusCollapsed => L("status.collapsed", "All mods collapsed.");

	public static string NoResults => L("message.no_results", "No config entries match the current filters.");

	public static string PendingWarning => L("message.pending_warning", "This setting has unsaved pending changes. Click Apply to write it to cfg.");

	public static string InvalidValue => L("message.invalid_value", "This value is not valid for this setting. It will not be applied.");

	public static string AllowedRangeHint => L("message.allowed_range", "Allowed range: ");

	public static string AllowedValuesHint => L("message.allowed_values", "Allowed values: ");

	public static string PendingFilter(bool on)
	{
		if (!on)
		{
			return L("filter.dirty_off", "Pending: OFF");
		}
		return L("filter.dirty_on", "Pending: ON");
	}

	public static string RyukaFilter(bool on)
	{
		if (!on)
		{
			return L("filter.ryuka_off", "Ryuka: OFF");
		}
		return L("filter.ryuka_on", "Ryuka: ON");
	}

	public static string AdvancedFilter(bool on)
	{
		if (!on)
		{
			return L("filter.advanced_off", "Advanced: OFF");
		}
		return L("filter.advanced_on", "Advanced: ON");
	}

	public static string HiddenFilter(bool on)
	{
		if (!on)
		{
			return L("filter.hidden_off", "Hidden: OFF");
		}
		return L("filter.hidden_on", "Hidden: ON");
	}

	public static string RestartFilter(bool on)
	{
		if (!on)
		{
			return L("filter.restart_off", "Restart: OFF");
		}
		return L("filter.restart_on", "Restart: ON");
	}

	public static string LiveFilter(bool on)
	{
		if (!on)
		{
			return L("filter.live_off", "Live: OFF");
		}
		return L("filter.live_on", "Live: ON");
	}

	public static string DangerousFilter(bool on)
	{
		if (!on)
		{
			return L("filter.dangerous_off", "Dangerous: OFF");
		}
		return L("filter.dangerous_on", "Dangerous: ON");
	}

	public static string TypeName(Type type, bool isFlagsEnum)
	{
		if (type == null)
		{
			return "";
		}
		if (type.IsEnum)
		{
			if (!isFlagsEnum)
			{
				return L("type.Enum", "Option");
			}
			return L("type.Flags", "Flags");
		}
		if (type == typeof(bool))
		{
			return L("type.Boolean", "Boolean");
		}
		if (type == typeof(int))
		{
			return L("type.Int32", "Integer");
		}
		if (type == typeof(float))
		{
			return L("type.Single", "Decimal");
		}
		if (type == typeof(double))
		{
			return L("type.Double", "Decimal");
		}
		if (type == typeof(string))
		{
			return L("type.String", "Text");
		}
		if (type == typeof(KeyboardShortcut))
		{
			return L("type.KeyboardShortcut", "Hotkey");
		}
		return type.Name;
	}

	private static string L(string key, string fallback)
	{
		return SulfurLanguage.FixRtl(SulfurLocalization.Get("ryuka.sulfur.config", key, fallback));
	}
}
internal sealed class DraftStore
{
	private readonly Dictionary<string, string> drafts = new Dictionary<string, string>(StringComparer.Ordinal);

	public int Count => drafts.Count;

	public string GetValue(ConfigEntryModel entry)
	{
		if (!drafts.TryGetValue(entry.Id, out var value))
		{
			return entry.AppliedValue;
		}
		return value;
	}

	public bool IsPending(ConfigEntryModel entry)
	{
		return drafts.ContainsKey(entry.Id);
	}

	public void Set(ConfigEntryModel entry, string value)
	{
		value = value ?? "";
		if (value == entry.AppliedValue)
		{
			drafts.Remove(entry.Id);
		}
		else
		{
			drafts[entry.Id] = value;
		}
	}

	public void Discard(ConfigEntryModel entry)
	{
		drafts.Remove(entry.Id);
	}

	public void Reconcile(IEnumerable<ConfigEntryModel> entries)
	{
		if (drafts.Count == 0)
		{
			return;
		}
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		foreach (ConfigEntryModel entry in entries)
		{
			hashSet.Add(entry.Id);
			if (drafts.TryGetValue(entry.Id, out var value) && value == entry.AppliedValue)
			{
				drafts.Remove(entry.Id);
			}
		}
		List<string> list = null;
		foreach (string key in drafts.Keys)
		{
			if (!hashSet.Contains(key))
			{
				if (list == null)
				{
					list = new List<string>();
				}
				list.Add(key);
			}
		}
		if (list == null)
		{
			return;
		}
		foreach (string item in list)
		{
			drafts.Remove(item);
		}
	}
}
internal sealed class EntryRowBuilder
{
	private const string HintSeparator = "   |   ";

	private readonly DraftStore drafts;

	private readonly Action<SulfurOptionsContext, ConfigEntryModel, string> draftChanged;

	private readonly Action<SulfurOptionsContext, string> rebuildWithStatus;

	public EntryRowBuilder(DraftStore drafts, Action<SulfurOptionsContext, ConfigEntryModel, string> draftChanged, Action<SulfurOptionsContext, string> rebuildWithStatus)
	{
		this.drafts = drafts;
		this.draftChanged = draftChanged;
		this.rebuildWithStatus = rebuildWithStatus;
	}

	public void Draw(SulfurOptionsContext ctx, ConfigEntryModel entry)
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Expected O, but got Unknown
		//IL_0173: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		string value = drafts.GetValue(entry);
		bool flag = drafts.IsPending(entry);
		SulfurSettingHandle handle = null;
		Func<string, bool> showValue = null;
		SulfurSettingRow val = new SulfurSettingRow();
		val.Label = SulfurLanguage.FixRtl(entry.DisplayName);
		val.Description = SulfurLanguage.FixRtl(entry.Description);
		val.IndentLevel = 1;
		val.IsDirty = flag;
		val.RequiresRestart = entry.RequiresRestart;
		val.LiveApply = entry.LiveApply;
		val.Advanced = entry.Advanced;
		val.Hidden = entry.Hidden;
		val.Dangerous = entry.Dangerous;
		val.DirtyText = ConfigText.PendingBadge;
		val.CleanText = ConfigText.UnchangedBadge;
		val.ExtraBadges = new string[1] { ConfigText.TypeName(entry.SettingType, entry.IsFlagsEnum) };
		val.ShowDefaultButton = value != entry.DefaultValue;
		val.OnDefault = delegate
		{
			ResetToDefault(ctx, entry, handle, showValue);
		};
		SulfurSettingRow val2 = val;
		val2.Message = GetMessage(entry, value, flag, out var kind);
		val2.MessageKind = kind;
		switch (entry.Kind)
		{
		case EntryEditorKind.Toggle:
			showValue = DrawToggle(ctx, entry, val2, value, out handle);
			break;
		case EntryEditorKind.Cycle:
			showValue = DrawCycle(ctx, entry, val2, value, out handle);
			break;
		case EntryEditorKind.Number:
			showValue = DrawNumber(ctx, entry, val2, value, out handle);
			break;
		default:
			showValue = DrawText(ctx, entry, val2, value, out handle);
			break;
		}
	}

	private Func<string, bool> DrawToggle(SulfurOptionsContext ctx, ConfigEntryModel entry, SulfurSettingRow row, string current, out SulfurSettingHandle handle)
	{
		bool.TryParse(current, out var result);
		SulfurOptionsSettingRows.AddSettingToggleEx(ctx, row, result, (Action<bool, SulfurSettingHandle>)delegate(bool newValue, SulfurSettingHandle h)
		{
			Commit(ctx, entry, entry.SerializeValue(newValue), h);
		}, ref handle);
		SulfurSettingHandle rowHandle = handle;
		bool result2;
		return (string serialized) => bool.TryParse(serialized, out result2) && rowHandle != null && rowHandle.SetValueWithoutNotify(result2);
	}

	private Func<string, bool> DrawCycle(SulfurOptionsContext ctx, ConfigEntryModel entry, SulfurSettingRow row, string current, out SulfurSettingHandle handle)
	{
		List<string> options = new List<string>(entry.Options);
		if (!options.Contains(current))
		{
			options.Insert(0, current);
		}
		List<string> list = new List<string>(options.Count);
		foreach (string item in options)
		{
			list.Add(entry.GetValueLabel(item));
		}
		SulfurOptionsSettingRows.AddSettingCycleEx(ctx, row, (IReadOnlyList<string>)list, options.IndexOf(current), (Action<int, string, SulfurSettingHandle>)delegate(int index, string label, SulfurSettingHandle h)
		{
			if (index >= 0 && index < options.Count)
			{
				Commit(ctx, entry, options[index], h);
			}
		}, ref handle);
		SulfurSettingHandle rowHandle = handle;
		return delegate(string serialized)
		{
			int num = options.IndexOf(serialized);
			return num >= 0 && rowHandle != null && rowHandle.SetIndexWithoutNotify(num);
		};
	}

	private Func<string, bool> DrawNumber(SulfurOptionsContext ctx, ConfigEntryModel entry, SulfurSettingRow row, string current, out SulfurSettingHandle handle)
	{
		entry.TryGetNumber(current, out var number);
		int decimals = entry.GetDecimals(current);
		double halfStep = 0.5 * Math.Pow(10.0, -decimals);
		double number2;
		Action<double, SulfurSettingHandle> onChanged = delegate(double value, SulfurSettingHandle h)
		{
			if (!entry.TryGetNumber(drafts.GetValue(entry), out number2) || !(Math.Abs(value - number2) < halfStep))
			{
				if (!entry.TrySerializeNumber(value, decimals, out var serialized))
				{
					serialized = value.ToString("R", CultureInfo.InvariantCulture);
				}
				Commit(ctx, entry, serialized, h);
			}
		};
		if (entry.UsesDoubleWidget)
		{
			SulfurOptionsSettingRows.AddSettingNumberEx(ctx, row, number, entry.NumberMin, entry.NumberMax, decimals, (Action<double, SulfurSettingHandle>)delegate(double value, SulfurSettingHandle h)
			{
				onChanged(value, h);
			}, ref handle);
		}
		else
		{
			SulfurOptionsSettingRows.AddSettingNumberEx(ctx, row, (float)number, (float)entry.NumberMin, (float)entry.NumberMax, decimals, (Action<float, SulfurSettingHandle>)delegate(float value, SulfurSettingHandle h)
			{
				onChanged(value, h);
			}, ref handle);
		}
		SulfurSettingHandle rowHandle = handle;
		return (string serialized) => entry.TryGetNumber(serialized, out number2) && rowHandle != null && rowHandle.SetValueWithoutNotify(number2);
	}

	private Func<string, bool> DrawText(SulfurOptionsContext ctx, ConfigEntryModel entry, SulfurSettingRow row, string current, out SulfurSettingHandle handle)
	{
		SulfurOptionsSettingRows.AddSettingTextEx(ctx, row, current, (Action<string, SulfurSettingHandle>)delegate(string value, SulfurSettingHandle h)
		{
			Commit(ctx, entry, value ?? "", h);
		}, ref handle);
		SulfurSettingHandle rowHandle = handle;
		return (string serialized) => rowHandle != null && rowHandle.SetValueWithoutNotify(serialized ?? "");
	}

	private void ResetToDefault(SulfurOptionsContext ctx, ConfigEntryModel entry, SulfurSettingHandle handle, Func<string, bool> showValue)
	{
		entry.RefreshApplied();
		drafts.Set(entry, entry.DefaultValue);
		string text = ConfigText.StatusDefaultDraft + SulfurLanguage.FixRtl(entry.DisplayName);
		if (handle == null || showValue == null || !showValue(entry.DefaultValue))
		{
			rebuildWithStatus(ctx, text);
			return;
		}
		RefreshRow(entry, handle);
		draftChanged(ctx, entry, text);
	}

	private void Commit(SulfurOptionsContext ctx, ConfigEntryModel entry, string value, SulfurSettingHandle handle)
	{
		entry.RefreshApplied();
		value = entry.CanonicalizeIfApplied(value);
		if (!(value == drafts.GetValue(entry)))
		{
			drafts.Set(entry, value);
			if (handle != null)
			{
				RefreshRow(entry, handle);
			}
			draftChanged(ctx, entry, ConfigText.StatusChanged + SulfurLanguage.FixRtl(entry.DisplayName));
		}
	}

	private void RefreshRow(ConfigEntryModel entry, SulfurSettingHandle handle)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		string value = drafts.GetValue(entry);
		bool flag = drafts.IsPending(entry);
		handle.SetDirty(flag, ConfigText.PendingBadge, ConfigText.UnchangedBadge);
		handle.SetMessage(GetMessage(entry, value, flag, out var kind), kind);
		handle.SetDefaultButtonVisible(value != entry.DefaultValue);
	}

	private static string GetMessage(ConfigEntryModel entry, string current, bool pending, out SulfurMessageKind kind)
	{
		string hint = GetHint(entry);
		if (pending && !entry.IsValid(current))
		{
			kind = (SulfurMessageKind)2;
			if (hint.Length <= 0)
			{
				return ConfigText.InvalidValue;
			}
			return ConfigText.InvalidValue + "   |   " + hint;
		}
		if (pending)
		{
			kind = (SulfurMessageKind)1;
			return ConfigText.PendingWarning;
		}
		kind = (SulfurMessageKind)0;
		return hint;
	}

	private static string GetHint(ConfigEntryModel entry)
	{
		if (entry.RangeMin.HasValue && entry.RangeMax.HasValue)
		{
			return ConfigText.AllowedRangeHint + FormatNumber(entry.RangeMin.Value) + " – " + FormatNumber(entry.RangeMax.Value);
		}
		if (entry.IsFlagsEnum && entry.Options.Count > 0)
		{
			return ConfigText.AllowedValuesHint + string.Join(", ", entry.Options);
		}
		return "";
	}

	private static string FormatNumber(double value)
	{
		return value.ToString("0.######", CultureInfo.InvariantCulture);
	}
}
[BepInDependency("ryuka.sulfur.nativeui", "0.11.0")]
[BepInPlugin("ryuka.sulfur.config", "SULFUR Config", "0.3.0")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "ryuka.sulfur.config";

	public const string PluginName = "SULFUR Config";

	public const string PluginVersion = "0.3.0";

	private const string NativeUiGuid = "ryuka.sulfur.nativeui";

	private const string NativeUiMinimumVersion = "0.11.0";

	private ConfigNativePage nativePage;

	private void Awake()
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: 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)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Expected O, but got Unknown
		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 = () => ConfigText.PageName,
			BuildPage = nativePage.Build
		});
		((BaseUnityPlugin)this).Logger.LogInfo((object)"SULFUR Config 0.3.0 registered its options page.");
	}

	private void OnDestroy()
	{
		SulfurOptionsApi.UnregisterPage("ryuka.sulfur.config");
	}
}