Decompiled source of ModSettingsExtender v0.1.1

BepInEx/plugins/pharmacomaniac-ModSettingsExtender/ModSettingsExtender.dll

Decompiled 7 months ago
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Panik;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ModSettingsExtender")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+417e853f06e65c92646aab7d0a27ccf43c0f04e0")]
[assembly: AssemblyProduct("ModSettingsExtender")]
[assembly: AssemblyTitle("ModSettingsExtender")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ModSettingsExtender
{
	[BepInPlugin("com.pharmacomaniac.modsettingsextender", "Mod Settings Extender", "0.1.1")]
	public class ModSettingsExtenderPlugin : BaseUnityPlugin
	{
		internal static ManualLogSource? Log;

		internal static Harmony? Harmony;

		private void Awake()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Harmony = new Harmony("com.pharmacomaniac.modsettingsextender");
			Harmony.UnpatchSelf();
			Harmony.PatchAll(typeof(MainMenu_Patches));
			Log.LogInfo((object)"Mod Settings Extender loaded (v0.1.0)");
		}

		private void OnDestroy()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class HubState
	{
		public enum View
		{
			None,
			HubIndex,
			Page
		}

		public static View Current = View.None;

		public static int HubPageOffset = 0;

		public static int PageItemOffset = 0;

		public static int ActivePage = -1;

		public static void Reset()
		{
			Current = View.None;
			HubPageOffset = 0;
			PageItemOffset = 0;
			ActivePage = -1;
		}
	}
	internal static class MainMenu_Patches
	{
		private readonly struct HubLayout
		{
			public int Capacity { get; }

			public int ListSlots { get; }

			public int NextRow { get; }

			public int BackRow { get; }

			public int Step { get; }

			public HubLayout(int capacity)
			{
				Capacity = capacity;
				BackRow = capacity - 1;
				NextRow = ((capacity >= 2) ? (capacity - 2) : (-1));
				ListSlots = Mathf.Max(0, capacity - 2);
				Step = Math.Max(1, ListSlots);
			}
		}

		private const int ModsSlotWithoutTwitch = 4;

		private const int ModsSlotWithTwitch = 5;

		private static bool _rowValidationLogged;

		private static readonly FieldInfo DesiredNavigationIndexField = AccessTools.Field(typeof(MainMenuScript), "desiredNavigationIndex");

		private static readonly FieldInfo ControllerElementsField = AccessTools.Field(typeof(DiegeticMenuController), "elements");

		private static readonly FieldInfo RightNavigationPressField = AccessTools.Field(typeof(MainMenuScript), "rightNavigationPress");

		private static readonly FieldInfo LeftNavigationPressField = AccessTools.Field(typeof(MainMenuScript), "leftNavigationPress");

		private static readonly FieldInfo MenuIndexField = AccessTools.Field(typeof(MainMenuScript), "menuIndex");

		private static bool IsInHub()
		{
			return HubState.Current != HubState.View.None;
		}

		private static bool HasPages()
		{
			return ModSettingsRegistry.Pages.Count > 0;
		}

		private static int GetModsSlotIndex()
		{
			if (!TwitchMaster.IsTwitchSupported())
			{
				return 4;
			}
			return 5;
		}

		private static int GetBackSlotIndex()
		{
			return GetModsSlotIndex() + 1;
		}

		private static void SetDesiredNavigationIndex(MainMenuScript menu, int value)
		{
			DesiredNavigationIndexField?.SetValue(menu, value);
		}

		private static MenuIndex GetMenuIndex(MainMenuScript menu)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			if (MenuIndexField == null)
			{
				return (MenuIndex)(-1);
			}
			object value = MenuIndexField.GetValue(menu);
			if (value is MenuIndex)
			{
				return (MenuIndex)value;
			}
			return (MenuIndex)(-1);
		}

		private static void SetMenuIndex(MainMenuScript menu, MenuIndex value)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			MenuIndexField?.SetValue(menu, value);
		}

		private static List<DiegeticMenuElement>? GetElementsList(DiegeticMenuController? controller)
		{
			if ((Object)(object)controller == (Object)null || ControllerElementsField == null)
			{
				return null;
			}
			return ControllerElementsField.GetValue(controller) as List<DiegeticMenuElement>;
		}

		private static bool GetFlag(FieldInfo field, MainMenuScript menu)
		{
			bool flag = default(bool);
			int num;
			if (field != null && (Object)(object)menu != (Object)null)
			{
				object value = field.GetValue(menu);
				if (value is bool)
				{
					flag = (bool)value;
					num = 1;
				}
				else
				{
					num = 0;
				}
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static void SetTextSafe(TextMeshProUGUI[] texts, int index, string? value)
		{
			if (texts != null && index >= 0 && index < texts.Length && (Object)(object)texts[index] != (Object)null)
			{
				((TMP_Text)texts[index]).text = value ?? string.Empty;
			}
		}

		private static DiegeticMenuElement? GetMenuElement(MainMenuScript? menu, int index)
		{
			if (menu?.menuElements == null || index < 0 || index >= menu.menuElements.Length)
			{
				return null;
			}
			return menu.menuElements[index];
		}

		private static bool TryGetOptionTexts(MainMenuScript menu, out TextMeshProUGUI[] optionTexts)
		{
			if (menu?.optionTexts == null || menu.optionTexts.Length == 0)
			{
				optionTexts = Array.Empty<TextMeshProUGUI>();
				return false;
			}
			optionTexts = menu.optionTexts;
			return true;
		}

		private static bool TryGetHubLayout(MainMenuScript menu, out HubLayout layout)
		{
			layout = default(HubLayout);
			if ((Object)(object)menu == (Object)null)
			{
				return false;
			}
			int num = VisibleCapacity(menu);
			if (num <= 0)
			{
				return false;
			}
			layout = new HubLayout(num);
			return true;
		}

		private static bool TryGetActivePage(out ModSettingsRegistry.Page page)
		{
			int activePage = HubState.ActivePage;
			if (activePage >= 0 && activePage < ModSettingsRegistry.Pages.Count)
			{
				page = ModSettingsRegistry.Pages[activePage];
				return true;
			}
			page = null;
			return false;
		}

		private static void RenderListEntries(TextMeshProUGUI[] optionTexts, int listSlots, int start, int total, Func<int, string> labelSelector)
		{
			int i = 0;
			int num = start;
			while (num < total && i < listSlots)
			{
				SetTextSafe(optionTexts, i, labelSelector(num));
				num++;
				i++;
			}
			for (; i < listSlots; i++)
			{
				SetTextSafe(optionTexts, i, string.Empty);
			}
		}

		private static void RenderPaginationFooter(TextMeshProUGUI[] optionTexts, HubLayout layout, int renderedEnd, int total)
		{
			if (layout.NextRow >= 0)
			{
				SetTextSafe(optionTexts, layout.NextRow, (renderedEnd < total) ? "NEXT" : string.Empty);
			}
			SetTextSafe(optionTexts, layout.BackRow, "BACK");
		}

		private static int ClampPageOffset(int requested, int total)
		{
			if (total <= 0)
			{
				return 0;
			}
			int num = Math.Max(0, total - 1);
			return Mathf.Clamp(requested, 0, num);
		}

		private static int GetDesiredNavigationIndex(MainMenuScript menu)
		{
			if (DesiredNavigationIndexField == null)
			{
				return -1;
			}
			object value = DesiredNavigationIndexField.GetValue(menu);
			if (value is int)
			{
				return (int)value;
			}
			return -1;
		}

		private static void EnsureSlotState(MainMenuScript menu, int index, bool enabled)
		{
			if ((Object)(object)menu?.menuController == (Object)null)
			{
				return;
			}
			DiegeticMenuElement menuElement = GetMenuElement(menu, index);
			if ((Object)(object)menuElement == (Object)null)
			{
				return;
			}
			Transform transform = ((Component)menuElement).transform;
			object obj;
			if (transform == null)
			{
				obj = null;
			}
			else
			{
				Transform parent = transform.parent;
				obj = ((parent != null) ? ((Component)parent).gameObject : null);
			}
			GameObject val = (GameObject)obj;
			if (val != null)
			{
				val.SetActive(enabled);
			}
			List<DiegeticMenuElement> elementsList = GetElementsList(menu.menuController);
			if (elementsList == null)
			{
				return;
			}
			if (enabled)
			{
				if (!elementsList.Contains(menuElement))
				{
					int index2 = Mathf.Clamp(index, 0, elementsList.Count);
					elementsList.Insert(index2, menuElement);
					menuElement.SetMyController(menu.menuController);
				}
				if (!VirtualCursors.IsCursorVisible(0, true) && GetDesiredNavigationIndex(menu) == index)
				{
					menu.menuController.HoveredElement = menuElement;
				}
			}
			else
			{
				elementsList.Remove(menuElement);
				if (menu.menuController.HoveredElement == menuElement)
				{
					menu.menuController.HoveredElement = null;
				}
			}
		}

		private static void EnsureSlotEnabled(MainMenuScript menu, int index)
		{
			EnsureSlotState(menu, index, enabled: true);
		}

		private static void EnsureSlotDisabled(MainMenuScript menu, int index)
		{
			EnsureSlotState(menu, index, enabled: false);
		}

		private static bool ValidateMenuRows(MainMenuScript menu, int requiredIndex)
		{
			if ((Object)(object)menu == (Object)null)
			{
				return false;
			}
			DiegeticMenuElement[] menuElements = menu.menuElements;
			int num = ((menuElements != null) ? menuElements.Length : 0);
			TextMeshProUGUI[] optionTexts = menu.optionTexts;
			int num2 = ((optionTexts != null) ? optionTexts.Length : 0);
			bool flag = num > requiredIndex;
			bool flag2 = num2 > requiredIndex;
			if ((!flag || !flag2) && !_rowValidationLogged)
			{
				_rowValidationLogged = true;
				ManualLogSource? log = ModSettingsExtenderPlugin.Log;
				if (log != null)
				{
					log.LogFatal((object)$"ModSettingsExtender detected missing menu rows. Expected index {requiredIndex} but found menuElements={num}, optionTexts={num2}. The base menu prefab likely changed; mods menu injection is disabled.");
				}
			}
			return flag && flag2;
		}

		private static int VisibleCapacity(MainMenuScript menu)
		{
			return Mathf.Max(0, GetElementsList(menu?.menuController)?.Count ?? 0);
		}

		private static bool InputSuppressed(MainMenuScript menu)
		{
			bool flag = Controls.MouseButton_PressedGet(0, (MouseElement)1);
			bool flag2 = GetFlag(RightNavigationPressField, menu);
			bool flag3 = GetFlag(LeftNavigationPressField, menu);
			return flag || flag2 || flag3;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(MainMenuScript), "OptionsUpdateText_Desktop")]
		private static void OptionsUpdateText_Desktop_Postfix(MainMenuScript __instance)
		{
			AfterOptionsUpdateText(__instance);
		}

		private static void AfterOptionsUpdateText(MainMenuScript menu)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			if ((Object)(object)menu == (Object)null)
			{
				return;
			}
			try
			{
				if ((int)GetMenuIndex(menu) == 1)
				{
					EnsureModsRow(menu);
				}
				else
				{
					DisableExtraRow(menu);
				}
				if (IsInHub())
				{
					RenderHub(menu);
				}
				else if (HubState.Current != 0 && !HasPages())
				{
					HubState.Reset();
				}
			}
			catch (Exception arg)
			{
				ManualLogSource? log = ModSettingsExtenderPlugin.Log;
				if (log != null)
				{
					log.LogError((object)$"OptionsUpdate postfix failed: {arg}");
				}
			}
		}

		private static void DisableExtraRow(MainMenuScript menu)
		{
			if (!((Object)(object)menu == (Object)null))
			{
				int backSlotIndex = GetBackSlotIndex();
				if (ValidateMenuRows(menu, backSlotIndex))
				{
					SetTextSafe(menu.optionTexts, backSlotIndex, string.Empty);
					EnsureSlotDisabled(menu, backSlotIndex);
				}
			}
		}

		private static void EnsureModsRow(MainMenuScript menu)
		{
			TextMeshProUGUI[] optionTexts = menu.optionTexts;
			if (optionTexts == null)
			{
				return;
			}
			int modsSlotIndex = GetModsSlotIndex();
			int backSlotIndex = GetBackSlotIndex();
			bool flag = HasPages();
			if (!ValidateMenuRows(menu, Math.Max(modsSlotIndex, backSlotIndex)))
			{
				return;
			}
			if (!flag)
			{
				SetTextSafe(optionTexts, modsSlotIndex, Translation.Get("MENU_OPTION_BACK"));
				SetTextSafe(optionTexts, backSlotIndex, string.Empty);
				EnsureSlotDisabled(menu, backSlotIndex);
			}
			else if (modsSlotIndex < optionTexts.Length && backSlotIndex < optionTexts.Length)
			{
				TextMeshProUGUI obj = optionTexts[modsSlotIndex];
				string value = ((obj != null) ? ((TMP_Text)obj).text : null) ?? Translation.Get("MENU_OPTION_BACK");
				if (string.IsNullOrEmpty(value))
				{
					value = Translation.Get("MENU_OPTION_BACK");
				}
				EnsureSlotEnabled(menu, modsSlotIndex);
				EnsureSlotEnabled(menu, backSlotIndex);
				SetTextSafe(optionTexts, backSlotIndex, value);
				SetTextSafe(optionTexts, modsSlotIndex, "MODS");
			}
		}

		private static void RenderHub(MainMenuScript menu)
		{
			if (TryGetOptionTexts(menu, out TextMeshProUGUI[] optionTexts) && TryGetHubLayout(menu, out var layout))
			{
				ModSettingsRegistry.Page page;
				if (HubState.Current == HubState.View.HubIndex)
				{
					RenderHubIndex(menu, optionTexts, layout);
				}
				else if (HubState.Current == HubState.View.Page && TryGetActivePage(out page))
				{
					RenderHubPage(menu, optionTexts, layout, page);
				}
			}
		}

		private static void RenderHubIndex(MainMenuScript menu, TextMeshProUGUI[] optionTexts, HubLayout layout)
		{
			if ((Object)(object)menu.titleText != (Object)null)
			{
				((TMP_Text)menu.titleText).text = "MOD SETTINGS";
			}
			int hubPageOffset = HubState.HubPageOffset;
			int count = ModSettingsRegistry.Pages.Count;
			int renderedEnd = Mathf.Min(hubPageOffset + layout.ListSlots, count);
			RenderListEntries(optionTexts, layout.ListSlots, hubPageOffset, count, (int index) => ModSettingsRegistry.GetDisplayTitle(ModSettingsRegistry.Pages[index]));
			RenderPaginationFooter(optionTexts, layout, renderedEnd, count);
		}

		private static void RenderHubPage(MainMenuScript menu, TextMeshProUGUI[] optionTexts, HubLayout layout, ModSettingsRegistry.Page page)
		{
			ModSettingsRegistry.Page page2 = page;
			if ((Object)(object)menu.titleText != (Object)null)
			{
				((TMP_Text)menu.titleText).text = ModSettingsRegistry.GetDisplayTitle(page2);
			}
			int pageItemOffset = HubState.PageItemOffset;
			int count = page2.Items.Count;
			int renderedEnd = Mathf.Min(pageItemOffset + layout.ListSlots, count);
			RenderListEntries(optionTexts, layout.ListSlots, pageItemOffset, count, (int index) => page2.Items[index].Label?.Invoke() ?? "(item)");
			RenderPaginationFooter(optionTexts, layout, renderedEnd, count);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(MainMenuScript), "Select_Desktop")]
		private static bool Select_Desktop_Prefix(MainMenuScript __instance, MenuIndex _menuIndex, int selectionIndex)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Invalid comparison between Unknown and I4
			if (IsInHub() && (int)_menuIndex == 0 && selectionIndex == 0)
			{
				if (HubState.Current == HubState.View.Page)
				{
					Sound.Play("SoundMenuBack", 1f, 1f);
					HubState.Current = HubState.View.HubIndex;
					HubState.PageItemOffset = 0;
				}
				else
				{
					Sound.Play("SoundMenuBack", 1f, 1f);
					HubState.Reset();
					SetDesiredNavigationIndex(__instance, GetModsSlotIndex());
				}
				__instance.OptionsUpdate();
				return false;
			}
			if (IsInHub())
			{
				HandleHubSelection(__instance, selectionIndex);
				__instance.OptionsUpdate();
				return false;
			}
			if ((int)_menuIndex == 1 && HasPages())
			{
				int modsSlotIndex = GetModsSlotIndex();
				int backSlotIndex = GetBackSlotIndex();
				bool flag = InputSuppressed(__instance);
				if (selectionIndex == modsSlotIndex && !flag)
				{
					Sound.Play("SoundMenuSelect", 1f, 1f);
					HubState.Current = HubState.View.HubIndex;
					HubState.HubPageOffset = 0;
					HubState.ActivePage = -1;
					HubState.PageItemOffset = 0;
					SetDesiredNavigationIndex(__instance, 0);
					__instance.OptionsUpdate();
					return false;
				}
				if (selectionIndex == backSlotIndex && !flag)
				{
					Sound.Play("SoundMenuBack", 1f, 1f);
					SetMenuIndex(__instance, (MenuIndex)0);
					int value = ((!Master.IsDemo) ? 2 : 3);
					SetDesiredNavigationIndex(__instance, value);
					HubState.Reset();
					__instance.OptionsUpdate();
					return false;
				}
			}
			return true;
		}

		private static void HandleHubSelection(MainMenuScript menu, int selectionIndex)
		{
			if (TryGetHubLayout(menu, out var layout))
			{
				if (HubState.Current == HubState.View.HubIndex)
				{
					HandleHubIndexSelection(menu, selectionIndex, layout);
				}
				else if (HubState.Current == HubState.View.Page)
				{
					int navigationDirection = GetNavigationDirection(menu);
					HandleHubPageSelection(menu, selectionIndex, layout, navigationDirection);
				}
			}
		}

		private static void HandleHubIndexSelection(MainMenuScript menu, int selectionIndex, HubLayout layout)
		{
			int hubPageOffset = HubState.HubPageOffset;
			int count = ModSettingsRegistry.Pages.Count;
			int num = Mathf.Min(hubPageOffset + layout.ListSlots, count);
			if (selectionIndex >= 0 && selectionIndex < layout.ListSlots)
			{
				int num2 = hubPageOffset + selectionIndex;
				if (num2 >= hubPageOffset && num2 < num)
				{
					Sound.Play("SoundMenuSelect", 1f, 1f);
					HubState.ActivePage = num2;
					HubState.PageItemOffset = 0;
					HubState.Current = HubState.View.Page;
					SetDesiredNavigationIndex(menu, 0);
				}
			}
			else if (selectionIndex == layout.NextRow && num < count)
			{
				Sound.Play("SoundMenuSelect", 1f, 1f);
				HubState.HubPageOffset = ClampPageOffset(HubState.HubPageOffset + layout.Step, count);
			}
			else if (selectionIndex == layout.BackRow)
			{
				Sound.Play("SoundMenuBack", 1f, 1f);
				HubState.Reset();
				SetDesiredNavigationIndex(menu, GetModsSlotIndex());
			}
		}

		private static void HandleHubPageSelection(MainMenuScript menu, int selectionIndex, HubLayout layout, int direction)
		{
			if (!TryGetActivePage(out ModSettingsRegistry.Page page))
			{
				return;
			}
			int pageItemOffset = HubState.PageItemOffset;
			int count = page.Items.Count;
			int num = Mathf.Min(pageItemOffset + layout.ListSlots, count);
			if (selectionIndex >= 0 && selectionIndex < layout.ListSlots)
			{
				int num2 = pageItemOffset + selectionIndex;
				if (num2 >= pageItemOffset && num2 < num)
				{
					ModSettingsRegistry.Item item = page.Items[num2];
					if ((direction != 0) ? TryInvokeAdjust(page, num2, item, direction) : TryInvokeSelect(page, num2, item))
					{
						Sound.Play("SoundMenuSelect", 1f, 1f);
					}
				}
			}
			else if (selectionIndex == layout.NextRow && num < count)
			{
				Sound.Play("SoundMenuSelect", 1f, 1f);
				HubState.PageItemOffset = ClampPageOffset(HubState.PageItemOffset + layout.Step, count);
			}
			else if (selectionIndex == layout.BackRow)
			{
				Sound.Play("SoundMenuBack", 1f, 1f);
				HubState.Current = HubState.View.HubIndex;
				HubState.PageItemOffset = 0;
				HubState.ActivePage = -1;
				SetDesiredNavigationIndex(menu, 0);
			}
		}

		private static int GetNavigationDirection(MainMenuScript menu)
		{
			bool flag = GetFlag(RightNavigationPressField, menu);
			bool flag2 = GetFlag(LeftNavigationPressField, menu);
			int num = (flag ? 1 : (flag2 ? (-1) : 0));
			if (num == 0 && Controls.MouseButton_PressedGet(0, (MouseElement)1))
			{
				num = -1;
			}
			return num;
		}

		private static bool TryInvokeSelect(ModSettingsRegistry.Page page, int itemIndex, ModSettingsRegistry.Item item)
		{
			if (item == null || item.OnSelect == null)
			{
				return true;
			}
			return TryInvokeItemHandler(page, itemIndex, "OnSelect", item.OnSelect);
		}

		private static bool TryInvokeAdjust(ModSettingsRegistry.Page page, int itemIndex, ModSettingsRegistry.Item item, int direction)
		{
			ModSettingsRegistry.Item item2 = item;
			if (item2 == null || item2.OnAdjust == null)
			{
				return true;
			}
			return TryInvokeItemHandler(page, itemIndex, "OnAdjust", delegate
			{
				item2.OnAdjust(direction);
			});
		}

		private static bool TryInvokeItemHandler(ModSettingsRegistry.Page page, int itemIndex, string handler, Action invoke)
		{
			try
			{
				invoke();
				return true;
			}
			catch (Exception ex)
			{
				LogHandlerException(handler, page, itemIndex, ex);
				return false;
			}
		}

		private static void LogHandlerException(string handler, ModSettingsRegistry.Page page, int itemIndex, Exception ex)
		{
			string text = page?.Name ?? "(unnamed page)";
			ManualLogSource? log = ModSettingsExtenderPlugin.Log;
			if (log != null)
			{
				log.LogError((object)$"Exception during {handler} for page '{text}' item index {itemIndex}: {ex}");
			}
		}
	}
	public static class ModSettingsRegistry
	{
		public enum ToggleAdjustMode
		{
			Toggle,
			Directional
		}

		public sealed class Item
		{
			public Func<string>? Label { get; set; }

			public Action? OnSelect { get; set; }

			public Action<int>? OnAdjust { get; set; }

			public Item()
			{
			}

			internal Item(Func<string>? label, Action? onSelect, Action<int>? onAdjust)
			{
				Label = label;
				OnSelect = onSelect;
				OnAdjust = onAdjust;
			}
		}

		public sealed class Page
		{
			private readonly List<Item> items = new List<Item>();

			public string Name { get; }

			internal string OwnerGuid { get; }

			internal string OwnerName { get; }

			internal string NormalizedName { get; }

			public List<Item> Items => items;

			internal Page(string name, string ownerGuid, string ownerName, string normalizedName)
			{
				if (string.IsNullOrWhiteSpace(name))
				{
					throw new ArgumentException("Page name must not be empty.", "name");
				}
				if (string.IsNullOrWhiteSpace(ownerGuid))
				{
					throw new ArgumentException("Page owner GUID must not be empty.", "ownerGuid");
				}
				if (string.IsNullOrWhiteSpace(normalizedName))
				{
					throw new ArgumentException("Normalized page name must not be empty.", "normalizedName");
				}
				Name = name;
				OwnerGuid = ownerGuid;
				OwnerName = ownerName;
				NormalizedName = normalizedName;
			}

			internal Item AddItem(Func<string>? label, Action? onSelect, Action<int>? onAdjust)
			{
				return AddItem(new Item(label, onSelect, onAdjust));
			}

			internal Item AddItem(Item item)
			{
				if (item == null)
				{
					throw new ArgumentNullException("item");
				}
				items.Add(item);
				return item;
			}
		}

		public sealed class PageBuilder
		{
			private static readonly IReadOnlyList<float> DefaultMultiplierSteps = Array.AsReadOnly(new float[6] { 0f, 0.5f, 1f, 2f, 3f, 4f });

			private readonly Page page;

			public Page BuiltPage => page;

			internal PageBuilder(Page page)
			{
				this.page = page ?? throw new ArgumentNullException("page");
			}

			public PageBuilder AddItem(Func<string>? label, Action? onSelect = null, Action<int>? onAdjust = null)
			{
				page.AddItem(label, onSelect, onAdjust);
				return this;
			}

			public PageBuilder AddToggle(string label, Func<bool> getter, Action<bool> setter, string onLabel = "On", string offLabel = "Off", Action<bool>? onChanged = null, ToggleAdjustMode adjustMode = ToggleAdjustMode.Toggle)
			{
				string label2 = label;
				Func<bool> getter2 = getter;
				string onLabel2 = onLabel;
				string offLabel2 = offLabel;
				Action<bool> setter2 = setter;
				Action<bool> onChanged2 = onChanged;
				if (label2 == null)
				{
					throw new ArgumentNullException("label");
				}
				if (getter2 == null)
				{
					throw new ArgumentNullException("getter");
				}
				if (setter2 == null)
				{
					throw new ArgumentNullException("setter");
				}
				return AddItem(Format, delegate
				{
					SetValue(!getter2());
				}, delegate(int dir)
				{
					int num = NormalizeDirection(dir);
					if (num != 0)
					{
						if (adjustMode == ToggleAdjustMode.Directional)
						{
							SetValue(num > 0);
						}
						else
						{
							SetValue(!getter2());
						}
					}
				});
				string Format()
				{
					return label2 + ": " + (getter2() ? onLabel2 : offLabel2);
				}
				void SetValue(bool value)
				{
					UpdateIfChanged(getter2, setter2, value, onChanged2);
				}
			}

			public PageBuilder AddToggle(string label, ConfigEntry<bool> entry, string onLabel = "On", string offLabel = "Off", Action<bool>? onChanged = null, ToggleAdjustMode adjustMode = ToggleAdjustMode.Toggle)
			{
				ConfigEntry<bool> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return AddToggle(label, () => entry2.Value, delegate(bool value)
				{
					entry2.Value = value;
				}, onLabel, offLabel, onChanged, adjustMode);
			}

			public PageBuilder OnOff(string label, Func<bool> getter, Action<bool> setter, string onLabel = "On", string offLabel = "Off", Action<bool>? onChanged = null, ToggleAdjustMode adjustMode = ToggleAdjustMode.Toggle)
			{
				return AddToggle(label, getter, setter, onLabel, offLabel, onChanged, adjustMode);
			}

			public PageBuilder OnOff(string label, ConfigEntry<bool> entry, string onLabel = "On", string offLabel = "Off", Action<bool>? onChanged = null, ToggleAdjustMode adjustMode = ToggleAdjustMode.Toggle)
			{
				ConfigEntry<bool> entry2 = entry;
				return OnOff(label, () => entry2.Value, delegate(bool value)
				{
					entry2.Value = value;
				}, onLabel, offLabel, onChanged, adjustMode);
			}

			public PageBuilder AddIntStepper(string label, Func<int> getter, Action<int> setter, int step = 1, int? min = null, int? max = null, bool wrap = false, Func<int, int>? normalizer = null, Func<int, string>? valueFormatter = null, Action<int>? onChanged = null)
			{
				Func<int, int> normalizer2 = normalizer;
				Func<int> getter2 = getter;
				Action<int> setter2 = setter;
				Action<int> onChanged2 = onChanged;
				Func<int, string> valueFormatter2 = valueFormatter;
				string label2 = label;
				if (label2 == null)
				{
					throw new ArgumentNullException("label");
				}
				if (getter2 == null)
				{
					throw new ArgumentNullException("getter");
				}
				if (setter2 == null)
				{
					throw new ArgumentNullException("setter");
				}
				if (step <= 0)
				{
					throw new ArgumentOutOfRangeException("step", "Step must be positive.");
				}
				return AddItem(Format, delegate
				{
					SetValue(getter2() + step);
				}, delegate(int dir)
				{
					int num5 = NormalizeDirection(dir);
					if (num5 != 0)
					{
						SetValue(getter2() + num5 * step);
					}
				});
				int ApplyBounds(int value)
				{
					if (wrap && min.HasValue && max.HasValue && min.Value <= max.Value)
					{
						int num = Math.Max(1, step);
						int num2 = max.Value - min.Value + num;
						if (num2 > 0)
						{
							int num3 = ((value - min.Value) % num2 + num2) % num2;
							return min.Value + num3;
						}
					}
					if (min.HasValue && value < min.Value)
					{
						value = min.Value;
					}
					if (max.HasValue && value > max.Value)
					{
						value = max.Value;
					}
					return value;
				}
				string Format()
				{
					int arg = getter2();
					string text = valueFormatter2?.Invoke(arg) ?? arg.ToString();
					return label2 + ": " + text;
				}
				int Normalize(int value)
				{
					int num4 = ApplyBounds(value);
					if (normalizer2 != null)
					{
						num4 = ApplyBounds(normalizer2(num4));
					}
					return num4;
				}
				void SetValue(int rawValue)
				{
					int value2 = Normalize(rawValue);
					UpdateIfChanged(getter2, setter2, value2, onChanged2);
				}
			}

			public PageBuilder AddIntStepper(string label, ConfigEntry<int> entry, int step = 1, int? min = null, int? max = null, bool wrap = false, Func<int, int>? normalizer = null, Func<int, string>? valueFormatter = null, Action<int>? onChanged = null)
			{
				ConfigEntry<int> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return AddIntStepper(label, () => entry2.Value, delegate(int value)
				{
					entry2.Value = value;
				}, step, min, max, wrap, normalizer, valueFormatter, onChanged);
			}

			public PageBuilder Int(string label, Func<int> getter, Action<int> setter, int? min = null, int? max = null, int step = 1, bool wrap = false, Func<int, int>? normalizer = null, Func<int, string>? valueFormatter = null, Action<int>? onChanged = null)
			{
				return AddIntStepper(label, getter, setter, step, min, max, wrap, normalizer, valueFormatter, onChanged);
			}

			public PageBuilder Int(string label, ConfigEntry<int> entry, int? min = null, int? max = null, int step = 1, bool wrap = false, Func<int, int>? normalizer = null, Func<int, string>? valueFormatter = null, Action<int>? onChanged = null)
			{
				ConfigEntry<int> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return Int(label, () => entry2.Value, delegate(int value)
				{
					entry2.Value = value;
				}, min, max, step, wrap, normalizer, valueFormatter, onChanged);
			}

			public PageBuilder Percent(string label, Func<int> getter, Action<int> setter, int minPercent = 0, int maxPercent = 100, int step = 5, bool wrap = false, Action<int>? onChanged = null)
			{
				if (maxPercent < minPercent)
				{
					throw new ArgumentOutOfRangeException("maxPercent", "maxPercent must be greater than or equal to minPercent.");
				}
				return AddIntStepper(label, getter, setter, step, minPercent, maxPercent, wrap, null, Formatter, onChanged);
				static string Formatter(int value)
				{
					return $"{value}%";
				}
			}

			public PageBuilder Percent(string label, ConfigEntry<int> entry, int minPercent = 0, int maxPercent = 100, int step = 5, bool wrap = false, Action<int>? onChanged = null)
			{
				ConfigEntry<int> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return Percent(label, () => entry2.Value, delegate(int value)
				{
					entry2.Value = value;
				}, minPercent, maxPercent, step, wrap, onChanged);
			}

			public PageBuilder Percent(string label, Func<float> getter, Action<float> setter, float minPercent = 0f, float maxPercent = 100f, float step = 5f, bool wrap = false, int decimalPlaces = 1, Action<float>? onChanged = null)
			{
				Action<float> onChanged2 = onChanged;
				Func<float> getter2 = getter;
				Action<float> setter2 = setter;
				if (label == null)
				{
					throw new ArgumentNullException("label");
				}
				if (getter2 == null)
				{
					throw new ArgumentNullException("getter");
				}
				if (setter2 == null)
				{
					throw new ArgumentNullException("setter");
				}
				if (step <= 0f)
				{
					throw new ArgumentOutOfRangeException("step", "Step must be positive.");
				}
				if (decimalPlaces < 0)
				{
					throw new ArgumentOutOfRangeException("decimalPlaces", "decimalPlaces must be zero or greater.");
				}
				float num = getter2();
				int requiredDecimalPlaces = GetRequiredDecimalPlaces(minPercent, maxPercent, step, num);
				int effectiveDecimalPlaces = Math.Max(decimalPlaces, requiredDecimalPlaces);
				int scale = Pow10(effectiveDecimalPlaces);
				int num2 = Scale(step);
				if (num2 <= 0)
				{
					throw new ArgumentOutOfRangeException("step", "Step is too small for the configured decimalPlaces.");
				}
				int num3 = Scale(minPercent);
				int num4 = Scale(maxPercent);
				if (num4 < num3)
				{
					throw new ArgumentOutOfRangeException("maxPercent", "maxPercent must be greater than or equal to minPercent.");
				}
				Action<int> onChanged3 = null;
				if (onChanged2 != null)
				{
					onChanged3 = delegate(int value)
					{
						onChanged2(Unscale(value));
					};
				}
				return AddIntStepper(label, () => Scale(getter2()), delegate(int value)
				{
					setter2(Unscale(value));
				}, num2, num3, num4, wrap, null, FormatPercentLabel, onChanged3);
				string FormatPercentLabel(int scaledValue)
				{
					float num5 = Unscale(scaledValue);
					string text = ((effectiveDecimalPlaces > 0) ? $"F{effectiveDecimalPlaces}" : "F0");
					string text2 = num5.ToString(text, CultureInfo.InvariantCulture);
					if (effectiveDecimalPlaces > 0)
					{
						text2 = text2.TrimEnd('0').TrimEnd('.');
					}
					return text2 + "%";
				}
				int Scale(float value)
				{
					decimal d = (decimal)value * (decimal)scale;
					return (int)Math.Round(d, MidpointRounding.AwayFromZero);
				}
				float Unscale(int value)
				{
					return (float)value / (float)scale;
				}
			}

			public PageBuilder Percent(string label, ConfigEntry<float> entry, float minPercent = 0f, float maxPercent = 100f, float step = 5f, bool wrap = false, int decimalPlaces = 1, Action<float>? onChanged = null)
			{
				ConfigEntry<float> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return Percent(label, () => entry2.Value, delegate(float value)
				{
					entry2.Value = value;
				}, minPercent, maxPercent, step, wrap, decimalPlaces, onChanged);
			}

			public PageBuilder Multiplier(string label, Func<float> getter, Action<float> setter, float minMultiplier, float maxMultiplier, float step, bool wrap = false, int decimalPlaces = 2, Func<float, string>? valueFormatter = null, Action<float>? onChanged = null)
			{
				Func<float, string> valueFormatter2 = valueFormatter;
				Action<float> onChanged2 = onChanged;
				Func<float> getter2 = getter;
				Action<float> setter2 = setter;
				if (label == null)
				{
					throw new ArgumentNullException("label");
				}
				if (getter2 == null)
				{
					throw new ArgumentNullException("getter");
				}
				if (setter2 == null)
				{
					throw new ArgumentNullException("setter");
				}
				if (step <= 0f)
				{
					throw new ArgumentOutOfRangeException("step", "Step must be positive.");
				}
				if (decimalPlaces < 0)
				{
					throw new ArgumentOutOfRangeException("decimalPlaces", "decimalPlaces must be zero or greater.");
				}
				float num = getter2();
				int requiredDecimalPlaces = GetRequiredDecimalPlaces(minMultiplier, maxMultiplier, step, num);
				int effectiveDecimalPlaces = Math.Max(decimalPlaces, requiredDecimalPlaces);
				int scale = Pow10(effectiveDecimalPlaces);
				int num2 = Scale(step);
				if (num2 <= 0)
				{
					throw new ArgumentOutOfRangeException("step", "Step is too small for the configured decimalPlaces.");
				}
				int num3 = Scale(minMultiplier);
				int num4 = Scale(maxMultiplier);
				if (num4 < num3)
				{
					throw new ArgumentOutOfRangeException("maxMultiplier", "maxMultiplier must be greater than or equal to minMultiplier.");
				}
				Action<int> onChanged3 = null;
				if (onChanged2 != null)
				{
					onChanged3 = delegate(int value)
					{
						onChanged2(Unscale(value));
					};
				}
				return AddIntStepper(label, () => Scale(getter2()), delegate(int value)
				{
					setter2(Unscale(value));
				}, num2, num3, num4, wrap, null, FormatMultiplierLabel, onChanged3);
				string FormatMultiplierLabel(int scaledValue)
				{
					float arg = Unscale(scaledValue);
					if (valueFormatter2 != null)
					{
						return valueFormatter2(arg);
					}
					string text = ((effectiveDecimalPlaces > 0) ? $"F{effectiveDecimalPlaces}" : "F0");
					string text2 = arg.ToString(text, CultureInfo.InvariantCulture);
					if (effectiveDecimalPlaces > 0)
					{
						text2 = text2.TrimEnd('0').TrimEnd('.');
					}
					return text2 + "x";
				}
				int Scale(float value)
				{
					decimal d = (decimal)value * (decimal)scale;
					return (int)Math.Round(d, MidpointRounding.AwayFromZero);
				}
				float Unscale(int value)
				{
					return (float)value / (float)scale;
				}
			}

			public PageBuilder Multiplier(string label, ConfigEntry<float> entry, float minMultiplier, float maxMultiplier, float step, bool wrap = false, int decimalPlaces = 2, Func<float, string>? valueFormatter = null, Action<float>? onChanged = null)
			{
				ConfigEntry<float> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return Multiplier(label, () => entry2.Value, delegate(float value)
				{
					entry2.Value = value;
				}, minMultiplier, maxMultiplier, step, wrap, decimalPlaces, valueFormatter, onChanged);
			}

			public PageBuilder Multiplier(string label, Func<float> getter, Action<float> setter, IReadOnlyList<float>? options = null, Func<float, string>? valueFormatter = null, Action<float>? onChanged = null)
			{
				Func<float, string> valueFormatter2 = valueFormatter;
				IReadOnlyList<float> values = options ?? DefaultMultiplierSteps;
				int optionDecimalPlaces = ((valueFormatter2 == null) ? GetRequiredDecimalPlaces(values) : 0);
				return Cycle(label, getter, setter, values, formatter, onChanged);
				string formatter(float value)
				{
					if (valueFormatter2 != null)
					{
						return valueFormatter2(value);
					}
					string text = ((optionDecimalPlaces > 0) ? $"F{optionDecimalPlaces}" : "F0");
					string text2 = value.ToString(text, CultureInfo.InvariantCulture);
					if (optionDecimalPlaces > 0)
					{
						text2 = text2.TrimEnd('0').TrimEnd('.');
					}
					return text2 + "x";
				}
			}

			public PageBuilder Multiplier(string label, ConfigEntry<float> entry, IReadOnlyList<float>? options = null, Func<float, string>? valueFormatter = null, Action<float>? onChanged = null)
			{
				ConfigEntry<float> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return Multiplier(label, () => entry2.Value, delegate(float value)
				{
					entry2.Value = value;
				}, options, valueFormatter, onChanged);
			}

			public PageBuilder Cycle<T>(string label, Func<T> getter, Action<T> setter, IReadOnlyList<T> values, Func<T, string>? valueFormatter = null, Action<T>? onChanged = null, IEqualityComparer<T>? comparer = null)
			{
				return CycleInternal(label, getter, setter, values, valueFormatter, onChanged, comparer);
			}

			public PageBuilder Cycle<T>(string label, Func<T> getter, Action<T> setter, params T[] values)
			{
				if (values == null)
				{
					throw new ArgumentNullException("values");
				}
				return CycleInternal(label, getter, setter, Array.AsReadOnly(values), null, null, null);
			}

			public PageBuilder Cycle<T>(string label, ConfigEntry<T> entry, IReadOnlyList<T> values, Func<T, string>? valueFormatter = null, Action<T>? onChanged = null, IEqualityComparer<T>? comparer = null)
			{
				ConfigEntry<T> entry2 = entry;
				if (entry2 == null)
				{
					throw new ArgumentNullException("entry");
				}
				return Cycle(label, () => entry2.Value, delegate(T value)
				{
					entry2.Value = value;
				}, values, valueFormatter, onChanged, comparer);
			}

			public PageBuilder Cycle<T>(string label, ConfigEntry<T> entry, params T[] values)
			{
				if (values == null)
				{
					throw new ArgumentNullException("values");
				}
				return Cycle(label, entry, (IReadOnlyList<T>)Array.AsReadOnly(values), (Func<T, string>?)null, (Action<T>?)null, (IEqualityComparer<T>?)null);
			}

			private PageBuilder CycleInternal<T>(string label, Func<T> getter, Action<T> setter, IReadOnlyList<T> values, Func<T, string>? valueFormatter, Action<T>? onChanged, IEqualityComparer<T>? comparer)
			{
				IReadOnlyList<T> values2 = values;
				Func<T, string> valueFormatter2 = valueFormatter;
				Func<T> getter2 = getter;
				string label2 = label;
				Action<T> setter2 = setter;
				Action<T> onChanged2 = onChanged;
				if (label2 == null)
				{
					throw new ArgumentNullException("label");
				}
				if (getter2 == null)
				{
					throw new ArgumentNullException("getter");
				}
				if (setter2 == null)
				{
					throw new ArgumentNullException("setter");
				}
				if (values2 == null)
				{
					throw new ArgumentNullException("values");
				}
				if (values2.Count == 0)
				{
					throw new ArgumentException("At least one value must be supplied.", "values");
				}
				IEqualityComparer<T> equalityComparer = comparer ?? EqualityComparer<T>.Default;
				return AddItem(Format, delegate
				{
					Advance(1);
				}, delegate(int dir)
				{
					Advance(dir);
				});
				void Advance(int direction)
				{
					int num = NormalizeDirection(direction);
					if (num != 0 && values2.Count != 1)
					{
						T candidate2 = getter2();
						int num2 = FindIndex(candidate2);
						int index = ((num2 >= 0) ? ((num2 + num + values2.Count) % values2.Count) : ((num <= 0) ? (values2.Count - 1) : 0));
						T value2 = values2[index];
						UpdateIfChanged(getter2, setter2, value2, onChanged2);
					}
				}
				int FindIndex(T candidate)
				{
					for (int i = 0; i < values2.Count; i++)
					{
						if (equalityComparer.Equals(values2[i], candidate))
						{
							return i;
						}
					}
					return -1;
				}
				string Format()
				{
					T value3 = getter2();
					return label2 + ": " + RenderValue(value3);
				}
				string RenderValue(T value)
				{
					if (valueFormatter2 != null)
					{
						return valueFormatter2(value);
					}
					if (!((object)value is IFormattable formattable))
					{
						object obj = value?.ToString();
						if (obj == null)
						{
							obj = string.Empty;
						}
						return (string)obj;
					}
					return formattable.ToString(null, CultureInfo.InvariantCulture);
				}
			}

			private static int NormalizeDirection(int value)
			{
				if (value == 0)
				{
					return 0;
				}
				if (value <= 0)
				{
					return -1;
				}
				return 1;
			}

			private static int Pow10(int exponent)
			{
				if (exponent < 0)
				{
					throw new ArgumentOutOfRangeException("exponent");
				}
				int num = 1;
				for (int i = 0; i < exponent; i++)
				{
					num = checked(num * 10);
				}
				return num;
			}

			private static int GetRequiredDecimalPlaces(IReadOnlyList<float> values)
			{
				if (values == null || values.Count == 0)
				{
					return 0;
				}
				int num = 0;
				for (int i = 0; i < values.Count; i++)
				{
					int num2 = CountDecimalPlaces(values[i]);
					if (num2 > num)
					{
						num = num2;
					}
				}
				return num;
			}

			private static int GetRequiredDecimalPlaces(params float[] values)
			{
				if (values == null || values.Length == 0)
				{
					return 0;
				}
				int num = 0;
				foreach (float value in values)
				{
					int num2 = CountDecimalPlaces(value);
					if (num2 > num)
					{
						num = num2;
					}
				}
				return num;
			}

			private static int CountDecimalPlaces(float value)
			{
				decimal value2 = decimal.Round((decimal)value, 6, MidpointRounding.AwayFromZero);
				value2 = Math.Abs(value2);
				for (int i = 0; i <= 6; i++)
				{
					decimal num = value2 * (decimal)Pow10(i);
					if (decimal.Truncate(num) == num)
					{
						return i;
					}
				}
				return 6;
			}

			private static void UpdateIfChanged<T>(Func<T> getter, Action<T> setter, T value, Action<T>? onChanged)
			{
				if (getter == null)
				{
					throw new ArgumentNullException("getter");
				}
				if (setter == null)
				{
					throw new ArgumentNullException("setter");
				}
				T x = getter();
				if (!EqualityComparer<T>.Default.Equals(x, value))
				{
					setter(value);
					onChanged?.Invoke(value);
				}
			}
		}

		private const int MaxAutoDecimalPlaces = 6;

		private static readonly List<Page> pages = new List<Page>();

		private static readonly Dictionary<string, Dictionary<string, Page>> pagesByOwner = new Dictionary<string, Dictionary<string, Page>>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, HashSet<string>> ownersByDisplayName = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

		public static IReadOnlyList<Page> Pages => pages;

		internal static string GetDisplayTitle(Page page)
		{
			if (page == null)
			{
				return string.Empty;
			}
			string text = page.Name ?? string.Empty;
			string key = page.NormalizedName ?? NormalizePageName(text);
			if (!ownersByDisplayName.TryGetValue(key, out HashSet<string> value) || value.Count <= 1)
			{
				return text;
			}
			string text2 = page.OwnerName;
			if (string.IsNullOrWhiteSpace(text2))
			{
				text2 = page.OwnerGuid;
			}
			if (!string.IsNullOrEmpty(text2) && text2.Length > 8)
			{
				text2 = text2.Substring(0, 8);
			}
			if (!string.IsNullOrWhiteSpace(text2))
			{
				return text + " (" + text2 + ")";
			}
			return text;
		}

		private static Page AddOrReplacePage(Page page)
		{
			if (page == null)
			{
				throw new ArgumentNullException("page");
			}
			if (string.IsNullOrWhiteSpace(page.OwnerGuid))
			{
				throw new ArgumentException("Page must have an owner GUID.", "page");
			}
			if (string.IsNullOrWhiteSpace(page.NormalizedName))
			{
				throw new ArgumentException("Page must have a normalized name.", "page");
			}
			TrackDisplayName(page);
			string ownerGuid = page.OwnerGuid;
			string normalizedName = page.NormalizedName;
			if (!pagesByOwner.TryGetValue(ownerGuid, out Dictionary<string, Page> value))
			{
				value = new Dictionary<string, Page>(StringComparer.OrdinalIgnoreCase);
				pagesByOwner[ownerGuid] = value;
			}
			if (value.TryGetValue(normalizedName, out var value2))
			{
				int num = pages.IndexOf(value2);
				if (num >= 0)
				{
					pages.RemoveAt(num);
					pages.Insert(num, page);
				}
				else
				{
					pages.Add(page);
				}
				value[normalizedName] = page;
				return page;
			}
			value[normalizedName] = page;
			pages.Add(page);
			return page;
		}

		private static void TrackDisplayName(Page page)
		{
			string key = page.NormalizedName ?? NormalizePageName(page.Name ?? string.Empty);
			string text = page.OwnerGuid ?? string.Empty;
			if (!ownersByDisplayName.TryGetValue(key, out HashSet<string> value))
			{
				value = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				ownersByDisplayName[key] = value;
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				value.Add(text);
			}
		}

		private static string NormalizePageName(string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}
			string text = name.Trim();
			if (text.Length == 0)
			{
				throw new ArgumentException("Page name must not be empty.", "name");
			}
			return text;
		}

		private static string ResolveOwnerGuid(BaseUnityPlugin owner)
		{
			if ((Object)(object)owner == (Object)null)
			{
				throw new ArgumentNullException("owner");
			}
			PluginInfo info = owner.Info;
			object obj;
			if (info == null)
			{
				obj = null;
			}
			else
			{
				BepInPlugin metadata = info.Metadata;
				obj = ((metadata != null) ? metadata.GUID : null);
			}
			string text = (string)obj;
			if (string.IsNullOrWhiteSpace(text))
			{
				throw new InvalidOperationException("Plugin '" + ((object)owner).GetType().FullName + "' must define a BepInEx GUID.");
			}
			return text;
		}

		private static string ResolveOwnerName(BaseUnityPlugin owner)
		{
			if ((Object)(object)owner == (Object)null)
			{
				throw new ArgumentNullException("owner");
			}
			PluginInfo info = owner.Info;
			object obj;
			if (info == null)
			{
				obj = null;
			}
			else
			{
				BepInPlugin metadata = info.Metadata;
				obj = ((metadata != null) ? metadata.Name : null);
			}
			string text = (string)obj;
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return ((object)owner).GetType().Name;
		}

		private static Page CreatePageShell(BaseUnityPlugin owner, string name)
		{
			if ((Object)(object)owner == (Object)null)
			{
				throw new ArgumentNullException("owner");
			}
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}
			string normalizedName = NormalizePageName(name);
			string name2 = name.Trim();
			string ownerGuid = ResolveOwnerGuid(owner);
			string ownerName = ResolveOwnerName(owner);
			return new Page(name2, ownerGuid, ownerName, normalizedName);
		}

		public static Page RegisterPage(BaseUnityPlugin owner, string name)
		{
			Page page = CreatePageShell(owner, name);
			return AddOrReplacePage(page);
		}

		public static Page RegisterPage(string name)
		{
			BaseUnityPlugin owner = ResolveOwnerForLegacyCall(Assembly.GetCallingAssembly());
			return RegisterPage(owner, name);
		}

		public static PageBuilder CreatePage(BaseUnityPlugin owner, string name)
		{
			Page page = CreatePageShell(owner, name);
			AddOrReplacePage(page);
			return new PageBuilder(page);
		}

		public static PageBuilder CreatePage(string name)
		{
			BaseUnityPlugin owner = ResolveOwnerForLegacyCall(Assembly.GetCallingAssembly());
			return CreatePage(owner, name);
		}

		public static PageBuilder RegisterPage(BaseUnityPlugin owner, string name, Action<PageBuilder> configure)
		{
			if (configure == null)
			{
				throw new ArgumentNullException("configure");
			}
			Page page = CreatePageShell(owner, name);
			PageBuilder pageBuilder = new PageBuilder(page);
			try
			{
				configure(pageBuilder);
			}
			catch (Exception arg)
			{
				ManualLogSource? log = ModSettingsExtenderPlugin.Log;
				if (log != null)
				{
					log.LogError((object)$"Exception while configuring settings page '{page.Name}': {arg}");
				}
				throw;
			}
			AddOrReplacePage(page);
			return pageBuilder;
		}

		public static PageBuilder RegisterPage(string name, Action<PageBuilder> configure)
		{
			if (configure == null)
			{
				throw new ArgumentNullException("configure");
			}
			BaseUnityPlugin owner = ResolveOwnerForLegacyCall(Assembly.GetCallingAssembly());
			return RegisterPage(owner, name, configure);
		}

		public static Item RegisterItem(Page page, Func<string>? label, Action? onSelect = null, Action<int>? onAdjust = null)
		{
			if (page == null)
			{
				throw new ArgumentNullException("page");
			}
			return page.AddItem(label, onSelect, onAdjust);
		}

		private static BaseUnityPlugin ResolveOwnerForLegacyCall(Assembly caller)
		{
			if (caller == null)
			{
				throw new ArgumentNullException("caller");
			}
			BaseUnityPlugin val = TryResolveOwnerFromAssembly(caller);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			throw new InvalidOperationException("Unable to infer the plugin owning this settings page. Call the overload that accepts a BaseUnityPlugin instance.");
		}

		private static BaseUnityPlugin? TryResolveOwnerFromAssembly(Assembly assembly)
		{
			if (assembly == null)
			{
				return null;
			}
			foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
			{
				PluginInfo value = pluginInfo.Value;
				if (!((Object)(object)((value != null) ? value.Instance : null) == (Object)null) && ((object)value.Instance).GetType().Assembly == assembly)
				{
					return value.Instance;
				}
			}
			return null;
		}
	}
}
namespace System.Diagnostics.CodeAnalysis
{
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ConstantExpectedAttribute : Attribute
	{
		public object? Min { get; set; }

		public object? Max { get; set; }
	}
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ExperimentalAttribute : Attribute
	{
		public string DiagnosticId { get; }

		public string? UrlFormat { get; set; }

		public ExperimentalAttribute(string diagnosticId)
		{
			DiagnosticId = diagnosticId;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullAttribute : Attribute
	{
		public string[] Members { get; }

		public MemberNotNullAttribute(string member)
		{
			Members = new string[1] { member };
		}

		public MemberNotNullAttribute(params string[] members)
		{
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullWhenAttribute : Attribute
	{
		public bool ReturnValue { get; }

		public string[] Members { get; }

		public MemberNotNullWhenAttribute(bool returnValue, string member)
		{
			ReturnValue = returnValue;
			Members = new string[1] { member };
		}

		public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
		{
			ReturnValue = returnValue;
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SetsRequiredMembersAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class StringSyntaxAttribute : Attribute
	{
		public const string CompositeFormat = "CompositeFormat";

		public const string DateOnlyFormat = "DateOnlyFormat";

		public const string DateTimeFormat = "DateTimeFormat";

		public const string EnumFormat = "EnumFormat";

		public const string GuidFormat = "GuidFormat";

		public const string Json = "Json";

		public const string NumericFormat = "NumericFormat";

		public const string Regex = "Regex";

		public const string TimeOnlyFormat = "TimeOnlyFormat";

		public const string TimeSpanFormat = "TimeSpanFormat";

		public const string Uri = "Uri";

		public const string Xml = "Xml";

		public string Syntax { get; }

		public object?[] Arguments { get; }

		public StringSyntaxAttribute(string syntax)
		{
			Syntax = syntax;
			Arguments = new object[0];
		}

		public StringSyntaxAttribute(string syntax, params object?[] arguments)
		{
			Syntax = syntax;
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class UnscopedRefAttribute : Attribute
	{
	}
}
namespace System.Runtime.Versioning
{
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresPreviewFeaturesAttribute : Attribute
	{
		public string? Message { get; }

		public string? Url { get; set; }

		public RequiresPreviewFeaturesAttribute()
		{
		}

		public RequiresPreviewFeaturesAttribute(string? message)
		{
			Message = message;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CallerArgumentExpressionAttribute : Attribute
	{
		public string ParameterName { get; }

		public CallerArgumentExpressionAttribute(string parameterName)
		{
			ParameterName = parameterName;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CollectionBuilderAttribute : Attribute
	{
		public Type BuilderType { get; }

		public string MethodName { get; }

		public CollectionBuilderAttribute(Type builderType, string methodName)
		{
			BuilderType = builderType;
			MethodName = methodName;
		}
	}
	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CompilerFeatureRequiredAttribute : Attribute
	{
		public const string RefStructs = "RefStructs";

		public const string RequiredMembers = "RequiredMembers";

		public string FeatureName { get; }

		public bool IsOptional { get; set; }

		public CompilerFeatureRequiredAttribute(string featureName)
		{
			FeatureName = featureName;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
	{
		public string[] Arguments { get; }

		public InterpolatedStringHandlerArgumentAttribute(string argument)
		{
			Arguments = new string[1] { argument };
		}

		public InterpolatedStringHandlerArgumentAttribute(params string[] arguments)
		{
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerAttribute : Attribute
	{
	}
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal static class IsExternalInit
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ModuleInitializerAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class OverloadResolutionPriorityAttribute : Attribute
	{
		public int Priority { get; }

		public OverloadResolutionPriorityAttribute(int priority)
		{
			Priority = priority;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ParamCollectionAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiredMemberAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresLocationAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SkipLocalsInitAttribute : Attribute
	{
	}
}