Decompiled source of ValhallaMenuCompanion v1.9.0

BepInEx/plugins/ValhallaMenuCompanion.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SeneaL.API;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ValhallaMenuCompanion")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.9.0.0")]
[assembly: AssemblyInformationalVersion("1.9.0")]
[assembly: AssemblyProduct("ValhallaMenuCompanion")]
[assembly: AssemblyTitle("ValhallaMenuCompanion")]
[assembly: AssemblyVersion("1.9.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 ValhallaMenuCompanion
{
	[BepInPlugin("valhalla.menu.companion", "Valhalla Menu Companion", "1.9.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class ButtonLabels
		{
			internal TMP_Text[] Tmp;

			internal Text[] Legacy;
		}

		private sealed class NativeCharacterRow
		{
			public Button Source;

			public RectTransform Rect;

			public TMP_Text Text;

			public object Entry;
		}

		private struct MenuEntry
		{
			public Button Source;

			public string Text;

			public string Tag;

			public Action Action;

			public bool Disabled;
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
		private static class PasswordPatch
		{
			private static void Prefix()
			{
				if (!PendingPasswordActive)
				{
					return;
				}
				try
				{
					MethodInfo methodInfo = AccessTools.PropertySetter(typeof(FejdStartup), "ServerPassword");
					if (methodInfo != null)
					{
						methodInfo.Invoke(null, new object[1] { PendingPassword });
						Plugin instance = Instance;
						if (instance != null)
						{
							((BaseUnityPlugin)instance).Logger.LogInfo((object)"Quick Connect: password handed to Valheim's native handshake (value not logged).");
						}
						return;
					}
					FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "<ServerPassword>k__BackingField");
					if (fieldInfo != null)
					{
						fieldInfo.SetValue(null, PendingPassword);
						Plugin instance2 = Instance;
						if (instance2 != null)
						{
							((BaseUnityPlugin)instance2).Logger.LogInfo((object)"Quick Connect: password handed to Valheim via backing-field fallback (value not logged).");
						}
					}
					else
					{
						Plugin instance3 = Instance;
						if (instance3 != null)
						{
							((BaseUnityPlugin)instance3).Logger.LogWarning((object)"Quick Connect: Valheim ServerPassword setter and backing field unavailable; manual entry may be required.");
						}
					}
				}
				catch (Exception ex)
				{
					Plugin instance4 = Instance;
					if (instance4 != null)
					{
						((BaseUnityPlugin)instance4).Logger.LogWarning((object)("Automatic password entry failed: " + ex.Message));
					}
				}
				finally
				{
					PendingPassword = null;
					PendingPasswordActive = false;
				}
			}
		}

		private sealed class QuerySnapshot
		{
			public readonly string State;

			public readonly int Players;

			public readonly int Capacity;

			public readonly bool HasPlayers;

			public QuerySnapshot(string state, int players = 0, int capacity = 0, bool hasPlayers = false)
			{
				State = state;
				Players = players;
				Capacity = capacity;
				HasPlayers = hasPlayers;
			}
		}

		private Texture2D logo;

		private Sprite logoSprite;

		private Image nativeLogoImage;

		private RawImage nativeLogoRaw;

		private Sprite savedTitleSprite;

		private Texture savedTitleTexture;

		private bool savedTitlePreserveAspect;

		private float nextLogoProbe;

		private bool missingTitleLogoLogged;

		private bool missingLogoFileLogged;

		private readonly Dictionary<Graphic, bool> suppressedLogoGraphics = new Dictionary<Graphic, bool>();

		private Color savedTitleImageColor = Color.white;

		private Color savedTitleRawColor = Color.white;

		private bool logoHierarchyLogged;

		private Transform cachedTitleLogoRoot;

		private Transform cachedTitleMenu;

		private string loadedLogoPath;

		private DateTime loadedLogoUtc = DateTime.MinValue;

		private long loadedLogoLength = -1L;

		private EventInfo canvasRenderEvent;

		private Delegate canvasRenderHandler;

		private Button localButton;

		private readonly Dictionary<Button, ButtonLabels> buttonLabels = new Dictionary<Button, ButtonLabels>();

		private Button characterPrimaryButton;

		private Button characterNewButton;

		private Button characterRemoveButton;

		private ButtonClickedEvent savedStartClick;

		private ButtonClickedEvent savedNewClick;

		private ButtonClickedEvent savedRemoveClick;

		private bool nativeSlotActionsMapped;

		private readonly Dictionary<Component, string> renderedSlotLabels = new Dictionary<Component, string>();

		private float nextSlotLabelProbe;

		private int slotProbeCount;

		private readonly float[] stableCharacterCenters = new float[6];

		private bool hasStableCharacterCenters;

		private bool characterRefreshPending;

		private int characterRefreshTries;

		private int stableScreenWidth;

		private int stableScreenHeight;

		private float nextLayoutDiagnostic;

		private bool nativeCharacterRowsMatched;

		private bool nativeCharacterRowsLogged;

		private bool nativeCharacterTickSeen;

		private float nextVisibleLabelProbe;

		private int visibleLabelProbeCount;

		private TMP_Text activeVisibleStart;

		private Text activeLegacyStart;

		private RectTransform nativeLastStartRoot;

		private RectTransform nativeLastLocalRoot;

		private readonly Dictionary<string, Vector2> nativeOriginalSlots = new Dictionary<string, Vector2>();

		private float nextCharacterLookupLog;

		private readonly Dictionary<RectTransform, Vector2> characterOriginalPositions = new Dictionary<RectTransform, Vector2>();

		private bool menuBound;

		private bool menuLookHideLogged;

		private bool supportOverflowLogged;

		private bool firstMenuModelInitialized;

		internal static string PendingPassword;

		internal static bool PendingPasswordActive;

		private ConfigEntry<float> rulesNativeDialogScale;

		private object resizedNativeDialog;

		private object rulesSizingDialog;

		private int rulesSizingPage = -1;

		private float rulesSizingOriginalWidth;

		private float rulesSizingOriginalHeight;

		private float rulesSizingAppliedHeight;

		private bool rulesSizingLogComplete;

		private bool nativeResizeMissingLogged;

		private string rulesText = "Welcome to Valhalla!";

		private Dialog activeRulesDialog;

		private bool activeRulesDialogWasOpen;

		private bool rulesOpen;

		private bool pendingJoin;

		private readonly List<string> rulesPages = new List<string>();

		private int rulesPageIndex;

		private bool rulesPageTransitioning;

		private string loadedRulesPath;

		private DateTime loadedRulesUtc = DateTime.MinValue;

		private long loadedRulesLength = -1L;

		private bool rulesAvailable;

		private bool missingRulesLogged;

		private bool joinQueuedAfterRules;

		private volatile QuerySnapshot serverQuerySnapshot = new QuerySnapshot("NOT CONFIGURED");

		private QuerySnapshot cachedLabelSnapshot;

		private string cachedLabelTitle;

		private string cachedLabelText;

		private bool cachedLabelStatusEnabled;

		private bool cachedLabelShowPlayers;

		private bool cachedLabelBelowTitle;

		private volatile string lastQueryTarget = "";

		private int queryRunning;

		private float nextQueryTime;

		private float nextStatusMaintenance;

		private bool startupIntroBlocked;

		private ConfigEntry<bool> pluginEnabled;

		private ConfigEntry<bool> logoEnabled;

		private ConfigEntry<bool> rulesEnabled;

		private ConfigEntry<bool> showRulesButton;

		private ConfigEntry<bool> skipStartupCinematic;

		private ConfigEntry<bool> hideMenuLook;

		private ConfigEntry<bool> supportEnabled;

		private ConfigEntry<bool> supportAskBeforeOpen;

		private ConfigEntry<bool> statusEnabled;

		private ConfigEntry<bool> statusShowPlayers;

		private ConfigEntry<bool> statusBelowTitle;

		private ConfigEntry<bool> rulesOptimizePageSpace;

		private ConfigEntry<string> host;

		private ConfigEntry<string> password;

		private ConfigEntry<string> buttonTitle;

		private ConfigEntry<string> discord;

		private ConfigEntry<string> logoFilename;

		private ConfigEntry<string> rulesFilename;

		private ConfigEntry<string> rulesVersion;

		private ConfigEntry<string> supportTitle;

		private ConfigEntry<string> supportUrl;

		private ConfigEntry<string> supportDescription;

		private ConfigEntry<int> port;

		private ConfigEntry<int> statusQueryPort;

		private ConfigEntry<int> statusPollSeconds;

		private ConfigEntry<int> statusTimeoutMs;

		private ConfigEntry<int> rulesLinesPerPage;

		private ConfigEntry<bool> verboseDiagnostics;

		private string dataFolder;

		private float nextReload;

		private Harmony harmony;

		private float uiNextSearch;

		internal static Plugin Instance;

		private volatile bool shuttingDown;

		private void ReloadAssets()
		{
			//IL_0440: Unknown result type (might be due to invalid IL or missing references)
			//IL_044f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Expected O, but got Unknown
			try
			{
				string text = Path.Combine(dataFolder, Path.GetFileName(rulesFilename.Value ?? "rules.txt"));
				if (!File.Exists(text))
				{
					rulesAvailable = false;
					if (!missingRulesLogged)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Rules file missing; quick-connect agreement will not be offered until rules are available: " + text));
						missingRulesLogged = true;
					}
				}
				else
				{
					FileInfo fileInfo = new FileInfo(text);
					if (!rulesAvailable || loadedRulesPath != text || loadedRulesUtc != fileInfo.LastWriteTimeUtc || loadedRulesLength != fileInfo.Length)
					{
						string value = File.ReadAllText(text);
						if (string.IsNullOrWhiteSpace(value))
						{
							rulesAvailable = false;
							if (!missingRulesLogged)
							{
								((BaseUnityPlugin)this).Logger.LogWarning((object)"Rules file is empty; rules acceptance is disabled until valid rules are supplied.");
								missingRulesLogged = true;
							}
						}
						else
						{
							rulesText = value;
							loadedRulesPath = text;
							loadedRulesUtc = fileInfo.LastWriteTimeUtc;
							loadedRulesLength = fileInfo.Length;
							rulesAvailable = true;
							missingRulesLogged = false;
						}
					}
				}
			}
			catch (Exception ex)
			{
				rulesAvailable = false;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Rules file read failed: " + ex.Message));
			}
			if (!logoEnabled.Value)
			{
				RestoreNativeTitleLogo();
				return;
			}
			try
			{
				string text2 = Path.Combine(dataFolder, Path.GetFileName(logoFilename.Value ?? "logo.png"));
				if (!File.Exists(text2))
				{
					if (!missingLogoFileLogged)
					{
						missingLogoFileLogged = true;
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: logo PNG not found at " + text2 + ". Confirm Branding.LogoFile and the active BepInEx profile."));
					}
					if ((Object)(object)logo != (Object)null)
					{
						RestoreNativeTitleLogo();
						if ((Object)(object)logoSprite != (Object)null)
						{
							Object.Destroy((Object)(object)logoSprite);
						}
						if ((Object)(object)logo != (Object)null)
						{
							Object.Destroy((Object)(object)logo);
						}
						logoSprite = null;
						logo = null;
						loadedLogoPath = null;
					}
					return;
				}
				missingLogoFileLogged = false;
				FileInfo fileInfo2 = new FileInfo(text2);
				if (loadedLogoPath == text2 && loadedLogoUtc == fileInfo2.LastWriteTimeUtc && loadedLogoLength == fileInfo2.Length)
				{
					return;
				}
				if (fileInfo2.Length < 8 || fileInfo2.Length > 16777216)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Logo PNG skipped: file must be between 8 bytes and 16 MiB.");
					loadedLogoPath = text2;
					loadedLogoUtc = fileInfo2.LastWriteTimeUtc;
					loadedLogoLength = fileInfo2.Length;
					return;
				}
				byte[] array = File.ReadAllBytes(text2);
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				bool flag = false;
				try
				{
					Type type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule", throwOnError: false) ?? typeof(Texture2D).Assembly.GetType("UnityEngine.ImageConversion");
					if (type != null)
					{
						MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
						foreach (MethodInfo methodInfo in methods)
						{
							if (!(methodInfo.Name != "LoadImage"))
							{
								ParameterInfo[] parameters = methodInfo.GetParameters();
								if (parameters.Length >= 2 && parameters.Length <= 3 && parameters[0].ParameterType == typeof(Texture2D) && parameters[1].ParameterType == typeof(byte[]) && (parameters.Length == 2 || parameters[2].ParameterType == typeof(bool)))
								{
									object[] parameters2 = ((parameters.Length != 2) ? new object[3] { val, array, false } : new object[2] { val, array });
									flag = (bool)methodInfo.Invoke(null, parameters2);
									break;
								}
							}
						}
					}
					else
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"Branding: Unity ImageConversion module not found; logo PNG could not load.");
					}
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Logo decode failed: " + ex2.Message));
				}
				if (flag)
				{
					Sprite val2 = null;
					try
					{
						val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
						if ((Object)(object)val2 == (Object)null)
						{
							throw new InvalidOperationException("Sprite.Create returned null.");
						}
						((Object)val2).name = "ValhallaCompanion_TitleLogo";
						Sprite val3 = logoSprite;
						Texture2D val4 = logo;
						logo = val;
						logoSprite = val2;
						if ((Object)(object)nativeLogoImage != (Object)null)
						{
							nativeLogoImage.sprite = logoSprite;
						}
						if ((Object)(object)nativeLogoRaw != (Object)null)
						{
							nativeLogoRaw.texture = (Texture)(object)logo;
						}
						if ((Object)(object)val3 != (Object)null)
						{
							Object.Destroy((Object)(object)val3);
						}
						if ((Object)(object)val4 != (Object)null)
						{
							Object.Destroy((Object)(object)val4);
						}
						nextLogoProbe = 0f;
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: loaded title logo PNG (" + ((Texture)val).width + "x" + ((Texture)val).height + ")."));
					}
					catch (Exception ex3)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: could not create the logo sprite: " + ex3.Message));
						if ((Object)(object)val2 != (Object)null)
						{
							Object.Destroy((Object)(object)val2);
						}
						Object.Destroy((Object)(object)val);
					}
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Branding: failed to decode logo PNG; keep a transparent RGBA PNG in the config folder.");
					Object.Destroy((Object)(object)val);
				}
				loadedLogoPath = text2;
				loadedLogoUtc = fileInfo2.LastWriteTimeUtc;
				loadedLogoLength = fileInfo2.Length;
			}
			catch (Exception ex4)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Logo file read failed: " + ex4.Message));
			}
		}

		private static bool LooksLikeLogoGraphic(Graphic graphic)
		{
			if ((Object)(object)graphic == (Object)null)
			{
				return false;
			}
			string text = ((Object)graphic).name ?? "";
			if (text.Equals("LOGO", StringComparison.OrdinalIgnoreCase) || text.Equals("AshlandsLogo", StringComparison.OrdinalIgnoreCase) || text.Equals("DeepNorthLogo", StringComparison.OrdinalIgnoreCase) || text.Equals("GameLogo", StringComparison.OrdinalIgnoreCase) || text.Equals("MainLogo", StringComparison.OrdinalIgnoreCase) || text.Equals("ValheimLogo", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			Image val = (Image)(object)((graphic is Image) ? graphic : null);
			if (val != null && (Object)(object)val.sprite != (Object)null && (((Object)val.sprite).name ?? "").IndexOf("logo", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("glow", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return true;
			}
			return false;
		}

		private static Transform FindTitleLogoRoot(Transform menu)
		{
			if ((Object)(object)menu == (Object)null)
			{
				return null;
			}
			Transform[] componentsInChildren = ((Component)menu).GetComponentsInChildren<Transform>(true);
			Transform val = null;
			Transform[] array = componentsInChildren;
			foreach (Transform val2 in array)
			{
				if ((Object)(object)val2 == (Object)null || !string.Equals(((Object)val2).name, "Logo", StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				if ((Object)(object)val == (Object)null)
				{
					val = val2;
				}
				Image[] componentsInChildren2 = ((Component)val2).GetComponentsInChildren<Image>(true);
				foreach (Image val3 in componentsInChildren2)
				{
					if ((Object)(object)val3 != (Object)null && LooksLikeLogoGraphic((Graphic)(object)val3))
					{
						return val2;
					}
				}
			}
			return val;
		}

		private void LogLogoCandidatesOnce(Transform root)
		{
			if (logoHierarchyLogged || !verboseDiagnostics.Value)
			{
				return;
			}
			logoHierarchyLogged = true;
			if ((Object)(object)root == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Branding: no Logo group found within FejdStartup.m_mainMenu; active title hierarchy differs from the expected prefab.");
				return;
			}
			int num = 0;
			Graphic[] componentsInChildren = ((Component)root).GetComponentsInChildren<Graphic>(true);
			foreach (Graphic val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && num++ < 24)
				{
					Image val2 = (Image)(object)((val is Image) ? val : null);
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: candidate " + ((Object)val).name + " (" + ((object)val).GetType().Name + ", active=" + ((Component)val).gameObject.activeInHierarchy + ", enabled=" + ((Behaviour)val).enabled + ", sprite=" + (((Object)(object)val2 != (Object)null && (Object)(object)val2.sprite != (Object)null) ? ((Object)val2.sprite).name : "n/a") + ")"));
					continue;
				}
				break;
			}
		}

		private void SuppressOtherTitleLogoGraphics(Transform root, Graphic target)
		{
			if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)null)
			{
				return;
			}
			Graphic[] componentsInChildren = ((Component)root).GetComponentsInChildren<Graphic>(true);
			foreach (Graphic val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)target) && LooksLikeLogoGraphic(val))
				{
					if (!suppressedLogoGraphics.ContainsKey(val))
					{
						suppressedLogoGraphics.Add(val, ((Behaviour)val).enabled);
					}
					if (((Behaviour)val).enabled)
					{
						((Behaviour)val).enabled = false;
					}
				}
			}
		}

		private void TryApplyNativeTitleLogo()
		{
			//IL_03e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0405: Unknown result type (might be due to invalid IL or missing references)
			//IL_0465: Unknown result type (might be due to invalid IL or missing references)
			//IL_046a: Unknown result type (might be due to invalid IL or missing references)
			//IL_047e: Unknown result type (might be due to invalid IL or missing references)
			if (!pluginEnabled.Value || !logoEnabled.Value || (Object)(object)logo == (Object)null || !OnMenu())
			{
				return;
			}
			FejdStartup instance = FejdStartup.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_mainMenu == (Object)null || !instance.m_mainMenu.activeInHierarchy || ((Object)(object)instance.m_characterSelectScreen != (Object)null && instance.m_characterSelectScreen.activeInHierarchy))
			{
				return;
			}
			try
			{
				Transform transform = instance.m_mainMenu.transform;
				if ((Object)(object)cachedTitleMenu != (Object)null && (Object)(object)cachedTitleMenu != (Object)(object)transform)
				{
					RestoreNativeTitleLogo();
					cachedTitleLogoRoot = null;
					logoHierarchyLogged = false;
				}
				if ((Object)(object)cachedTitleMenu != (Object)(object)transform || (Object)(object)cachedTitleLogoRoot == (Object)null || !((Component)cachedTitleLogoRoot).gameObject.activeInHierarchy)
				{
					Transform val = cachedTitleLogoRoot;
					Transform val2 = FindTitleLogoRoot(transform);
					if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)val2)
					{
						RestoreNativeTitleLogo();
						logoHierarchyLogged = false;
					}
					cachedTitleMenu = transform;
					cachedTitleLogoRoot = val2;
				}
				Transform val3 = cachedTitleLogoRoot;
				LogLogoCandidatesOnce(val3);
				if ((Object)(object)val3 == (Object)null)
				{
					return;
				}
				if (((Object)(object)nativeLogoImage != (Object)null && !((Component)nativeLogoImage).transform.IsChildOf(val3)) || ((Object)(object)nativeLogoRaw != (Object)null && !((Component)nativeLogoRaw).transform.IsChildOf(val3)))
				{
					RestoreNativeTitleLogo();
				}
				if ((Object)(object)nativeLogoImage != (Object)null && ((Component)nativeLogoImage).gameObject.activeInHierarchy)
				{
					if ((Object)(object)nativeLogoImage.sprite != (Object)(object)logoSprite)
					{
						nativeLogoImage.sprite = logoSprite;
					}
					((Behaviour)nativeLogoImage).enabled = true;
					SuppressOtherTitleLogoGraphics(val3, (Graphic)(object)nativeLogoImage);
					return;
				}
				if ((Object)(object)nativeLogoRaw != (Object)null && ((Component)nativeLogoRaw).gameObject.activeInHierarchy)
				{
					if ((Object)(object)nativeLogoRaw.texture != (Object)(object)logo)
					{
						nativeLogoRaw.texture = (Texture)(object)logo;
					}
					((Behaviour)nativeLogoRaw).enabled = true;
					SuppressOtherTitleLogoGraphics(val3, (Graphic)(object)nativeLogoRaw);
					return;
				}
				RestoreNativeTitleLogo();
				Image val4 = null;
				RawImage val5 = null;
				int num = -1;
				Image[] componentsInChildren = ((Component)val3).GetComponentsInChildren<Image>(true);
				foreach (Image val6 in componentsInChildren)
				{
					if (!((Object)(object)val6 == (Object)null) && ((Component)val6).gameObject.activeInHierarchy && ((Behaviour)val6).enabled && !((Object)(object)val6.sprite == (Object)null) && LooksLikeLogoGraphic((Graphic)(object)val6) && !((Object)(object)((Component)val6).GetComponentInParent<Button>() != (Object)null))
					{
						int num2 = 50;
						if (((Object)val6).name.Equals("LOGO", StringComparison.OrdinalIgnoreCase))
						{
							num2 += 40;
						}
						if (((Object)val6).name.Equals("DeepNorthLogo", StringComparison.OrdinalIgnoreCase))
						{
							num2 += 30;
						}
						if (((Object)val6).name.Equals("AshlandsLogo", StringComparison.OrdinalIgnoreCase))
						{
							num2 += 20;
						}
						if (num2 > num)
						{
							num = num2;
							val4 = val6;
							val5 = null;
						}
					}
				}
				if ((Object)(object)val4 == (Object)null)
				{
					RawImage[] componentsInChildren2 = ((Component)val3).GetComponentsInChildren<RawImage>(true);
					foreach (RawImage val7 in componentsInChildren2)
					{
						if (!((Object)(object)val7 == (Object)null) && ((Component)val7).gameObject.activeInHierarchy && ((Behaviour)val7).enabled && !((Object)(object)val7.texture == (Object)null) && LooksLikeLogoGraphic((Graphic)(object)val7) && !((Object)(object)((Component)val7).GetComponentInParent<Button>() != (Object)null))
						{
							val5 = val7;
							break;
						}
					}
				}
				if ((Object)(object)val4 != (Object)null)
				{
					nativeLogoImage = val4;
					savedTitleSprite = val4.sprite;
					savedTitlePreserveAspect = val4.preserveAspect;
					savedTitleImageColor = ((Graphic)val4).color;
					val4.sprite = logoSprite;
					val4.preserveAspect = true;
					((Graphic)val4).color = Color.white;
					SuppressOtherTitleLogoGraphics(val3, (Graphic)(object)val4);
					missingTitleLogoLogged = false;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: replaced Valheim title logo Image '" + ((Object)val4).name + "' and suppressed competing title-logo graphics."));
				}
				else if ((Object)(object)val5 != (Object)null)
				{
					nativeLogoRaw = val5;
					savedTitleTexture = val5.texture;
					savedTitleRawColor = ((Graphic)val5).color;
					val5.texture = (Texture)(object)logo;
					((Graphic)val5).color = Color.white;
					SuppressOtherTitleLogoGraphics(val3, (Graphic)(object)val5);
					missingTitleLogoLogged = false;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: replaced Valheim title RawImage '" + ((Object)val5).name + "'."));
				}
				else if (!missingTitleLogoLogged)
				{
					missingTitleLogoLogged = true;
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Branding: Logo group exists but has no active logo Image/RawImage; its visible artwork may be drawn elsewhere. Please send Branding lines from the log.");
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: title logo replacement failed: " + ex.Message));
			}
		}

		private void RestoreNativeTitleLogo()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)nativeLogoImage != (Object)null)
				{
					nativeLogoImage.sprite = savedTitleSprite;
					nativeLogoImage.preserveAspect = savedTitlePreserveAspect;
					((Graphic)nativeLogoImage).color = savedTitleImageColor;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: image restore skipped: " + ex.Message));
			}
			try
			{
				if ((Object)(object)nativeLogoRaw != (Object)null)
				{
					nativeLogoRaw.texture = savedTitleTexture;
					((Graphic)nativeLogoRaw).color = savedTitleRawColor;
				}
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: raw-image restore skipped: " + ex2.Message));
			}
			foreach (KeyValuePair<Graphic, bool> suppressedLogoGraphic in suppressedLogoGraphics)
			{
				try
				{
					if ((Object)(object)suppressedLogoGraphic.Key != (Object)null)
					{
						((Behaviour)suppressedLogoGraphic.Key).enabled = suppressedLogoGraphic.Value;
					}
				}
				catch (Exception ex3)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: overlay restore skipped: " + ex3.Message));
				}
			}
			suppressedLogoGraphics.Clear();
			nativeLogoImage = null;
			nativeLogoRaw = null;
			savedTitleSprite = null;
			savedTitleTexture = null;
		}

		private void PatchVisibleCharacterSlots(bool forceRescan = false)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			ApplyMappedCharacterLabels();
			if (stableScreenWidth != Screen.width || stableScreenHeight != Screen.height)
			{
				stableScreenWidth = Screen.width;
				stableScreenHeight = Screen.height;
				hasStableCharacterCenters = false;
				forceRescan = true;
			}
			if (!forceRescan && !characterRefreshPending && renderedSlotLabels.Count >= 6 && !(Time.unscaledTime >= nextSlotLabelProbe))
			{
				return;
			}
			nextSlotLabelProbe = Time.unscaledTime + (characterRefreshPending ? 0.015f : 1f);
			List<Tuple<Component, float, string>> list = new List<Tuple<Component, float, string>>();
			TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
			foreach (TMP_Text val in array)
			{
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && IsCharacterProbeText((val.text ?? "").Trim()))
				{
					list.Add(Tuple.Create<Component, float, string>((Component)(object)val, val.transform.position.y, (val.text ?? "").Trim()));
				}
			}
			Text[] array2 = Resources.FindObjectsOfTypeAll<Text>();
			foreach (Text val2 in array2)
			{
				if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy && IsCharacterProbeText((val2.text ?? "").Trim()))
				{
					list.Add(Tuple.Create<Component, float, string>((Component)(object)val2, ((Component)val2).transform.position.y, (val2.text ?? "").Trim()));
				}
			}
			list.Sort((Tuple<Component, float, string> a, Tuple<Component, float, string> b) => b.Item2.CompareTo(a.Item2));
			List<List<Tuple<Component, float, string>>> list2 = new List<List<Tuple<Component, float, string>>>();
			foreach (Tuple<Component, float, string> item2 in list)
			{
				if (list2.Count == 0 || Mathf.Abs(list2[list2.Count - 1][0].Item2 - item2.Item2) > 13f)
				{
					list2.Add(new List<Tuple<Component, float, string>>());
				}
				list2[list2.Count - 1].Add(item2);
			}
			List<int> list3 = new List<int>();
			if (hasStableCharacterCenters)
			{
				HashSet<int> hashSet = new HashSet<int>();
				for (int num = 0; num < 6; num++)
				{
					int num2 = -1;
					float num3 = 24f;
					for (int num4 = 0; num4 < list2.Count; num4++)
					{
						if (!hashSet.Contains(num4))
						{
							float num5 = Mathf.Abs(list2[num4][0].Item2 - stableCharacterCenters[num]);
							if (num5 < num3)
							{
								num2 = num4;
								num3 = num5;
							}
						}
					}
					if (num2 < 0)
					{
						list3.Clear();
						break;
					}
					hashSet.Add(num2);
					list3.Add(num2);
				}
			}
			if (list3.Count != 6)
			{
				list3.Clear();
				for (int num6 = 0; num6 <= list2.Count - 6; num6++)
				{
					bool flag = false;
					foreach (Tuple<Component, float, string> item3 in list2[num6])
					{
						string text = item3.Item3;
						int num7 = text.IndexOf("<size=", StringComparison.OrdinalIgnoreCase);
						if (num7 >= 0)
						{
							text = text.Substring(0, num7).TrimEnd(Array.Empty<char>());
						}
						if (text.Equals("START", StringComparison.OrdinalIgnoreCase) || text.Equals(buttonTitle.Value, StringComparison.OrdinalIgnoreCase))
						{
							flag = true;
							break;
						}
					}
					if (!flag)
					{
						continue;
					}
					bool flag2 = true;
					for (int num8 = 1; num8 < 6; num8++)
					{
						float num9 = list2[num6 + num8 - 1][0].Item2 - list2[num6 + num8][0].Item2;
						if (num9 < 18f || num9 > 120f)
						{
							flag2 = false;
							break;
						}
					}
					if (flag2)
					{
						for (int num10 = 0; num10 < 6; num10++)
						{
							list3.Add(num6 + num10);
						}
						break;
					}
				}
			}
			if (list3.Count == 6)
			{
				string[] array3 = new string[6] { buttonTitle.Value, "PLAY LOCAL", "NEW", "REMOVE", "MANAGE SAVES", "BACK" };
				Dictionary<Component, string> dictionary = new Dictionary<Component, string>();
				for (int num11 = 0; num11 < 6; num11++)
				{
					float item = list2[list3[num11]][0].Item2;
					stableCharacterCenters[num11] = item;
					foreach (Tuple<Component, float, string> item4 in list2[list3[num11]])
					{
						dictionary[item4.Item1] = array3[num11];
					}
				}
				hasStableCharacterCenters = true;
				renderedSlotLabels.Clear();
				foreach (KeyValuePair<Component, string> item5 in dictionary)
				{
					renderedSlotLabels[item5.Key] = item5.Value;
				}
				ApplyMappedCharacterLabels();
				characterRefreshPending = false;
				characterRefreshTries = 0;
			}
			else if (characterRefreshPending && ++characterRefreshTries >= 20)
			{
				characterRefreshPending = false;
				((BaseUnityPlugin)this).Logger.LogWarning((object)"CHAR refresh: couldn't confirm all six slots; last known labels retained.");
			}
			if (verboseDiagnostics.Value && slotProbeCount < 3)
			{
				slotProbeCount++;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("CHAR refresh #" + slotProbeCount + ": bands=" + list2.Count + ", slots=" + list3.Count + ", stable=" + hasStableCharacterCenters + ", map=" + renderedSlotLabels.Count));
			}
		}

		private void ApplyMappedCharacterLabels()
		{
			string value = buttonTitle.Value;
			string text = FormatEnterLabel();
			foreach (KeyValuePair<Component, string> renderedSlotLabel in renderedSlotLabels)
			{
				if ((Object)(object)renderedSlotLabel.Key == (Object)null || !renderedSlotLabel.Key.gameObject.activeInHierarchy)
				{
					continue;
				}
				Component key = renderedSlotLabel.Key;
				TMP_Text val = (TMP_Text)(object)((key is TMP_Text) ? key : null);
				if ((Object)(object)val != (Object)null)
				{
					string text2 = ((renderedSlotLabel.Value == value) ? text : renderedSlotLabel.Value);
					if (val.text != text2)
					{
						val.text = text2;
					}
					continue;
				}
				Component key2 = renderedSlotLabel.Key;
				Text val2 = (Text)(object)((key2 is Text) ? key2 : null);
				if ((Object)(object)val2 != (Object)null)
				{
					string text3 = ((renderedSlotLabel.Value == value) ? text : renderedSlotLabel.Value);
					if (val2.text != text3)
					{
						val2.text = text3;
					}
				}
			}
		}

		private void TrySubscribeBeforeCanvasRender()
		{
			try
			{
				Type type = Type.GetType("UnityEngine.Canvas, UnityEngine.UIModule", throwOnError: false);
				if (type == null)
				{
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					for (int i = 0; i < assemblies.Length; i++)
					{
						type = assemblies[i].GetType("UnityEngine.Canvas", throwOnError: false);
						if (type != null)
						{
							break;
						}
					}
				}
				if (type == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Canvas pre-render hook unavailable: UnityEngine.Canvas type not found. The normal character refresh remains available.");
					return;
				}
				EventInfo eventInfo = type.GetEvent("willRenderCanvases", BindingFlags.Static | BindingFlags.Public);
				MethodInfo method = ((object)this).GetType().GetMethod("BeforeCharacterCanvasRender", BindingFlags.Instance | BindingFlags.NonPublic);
				if (eventInfo == null || method == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Canvas pre-render hook unavailable: event or callback not found.");
					return;
				}
				Delegate obj = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, method, throwOnBindFailure: false);
				if ((object)obj == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Canvas pre-render hook unavailable: callback signature mismatch.");
					return;
				}
				eventInfo.AddEventHandler(null, obj);
				canvasRenderEvent = eventInfo;
				canvasRenderHandler = obj;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Canvas pre-render hook attached without an additional Unity DLL reference.");
			}
			catch (Exception ex)
			{
				canvasRenderEvent = null;
				canvasRenderHandler = null;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Canvas pre-render hook unavailable; fallback refresh remains active: " + ex.Message));
			}
		}

		private void BeforeCharacterCanvasRender()
		{
			if (!shuttingDown && ((Behaviour)this).isActiveAndEnabled && pluginEnabled.Value && nativeSlotActionsMapped && OnMenu())
			{
				FejdStartup instance = FejdStartup.instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.m_characterSelectScreen == (Object)null) && instance.m_characterSelectScreen.activeInHierarchy)
				{
					PatchVisibleCharacterSlots();
				}
			}
		}

		private void PatchVisibleCharacterStart()
		{
			if ((Object)(object)activeVisibleStart != (Object)null && ((Component)activeVisibleStart).gameObject.activeInHierarchy)
			{
				activeVisibleStart.text = FormatEnterLabel();
			}
			if ((Object)(object)activeLegacyStart != (Object)null && ((Component)activeLegacyStart).gameObject.activeInHierarchy)
			{
				activeLegacyStart.text = FormatEnterLabel();
			}
			if (Time.unscaledTime < nextVisibleLabelProbe)
			{
				return;
			}
			nextVisibleLabelProbe = Time.unscaledTime + 0.35f;
			bool flag = verboseDiagnostics.Value && visibleLabelProbeCount < 3;
			int num = 0;
			List<string> list = new List<string>();
			TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
			foreach (TMP_Text val in array)
			{
				if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
				{
					continue;
				}
				string text = (val.text ?? string.Empty).Trim();
				if (text.Equals("START", StringComparison.OrdinalIgnoreCase))
				{
					num++;
					if ((Object)(object)activeVisibleStart == (Object)null || !((Component)activeVisibleStart).gameObject.activeInHierarchy)
					{
						activeVisibleStart = val;
					}
				}
				if (flag && IsCharacterProbeText(text) && list.Count < 22)
				{
					list.Add(DescribeVisibleLabel(val.transform, text));
				}
			}
			Text[] array2 = Resources.FindObjectsOfTypeAll<Text>();
			foreach (Text val2 in array2)
			{
				if ((Object)(object)val2 == (Object)null || !((Component)val2).gameObject.activeInHierarchy)
				{
					continue;
				}
				string text2 = (val2.text ?? string.Empty).Trim();
				if (text2.Equals("START", StringComparison.OrdinalIgnoreCase))
				{
					num++;
					if ((Object)(object)activeLegacyStart == (Object)null || !((Component)activeLegacyStart).gameObject.activeInHierarchy)
					{
						activeLegacyStart = val2;
					}
				}
				if (flag && IsCharacterProbeText(text2) && list.Count < 22)
				{
					list.Add("LEGACY " + DescribeVisibleLabel(((Component)val2).transform, text2));
				}
			}
			if (flag)
			{
				visibleLabelProbeCount++;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("CHAR visible UI probe #" + visibleLabelProbeCount + " foundUnchangedStartLabels=" + num + "; primary=" + (((Object)(object)characterPrimaryButton == (Object)null) ? "none" : ((Object)characterPrimaryButton).name) + "; entries=[" + string.Join(" || ", list.ToArray()) + "]"));
			}
			if ((Object)(object)activeVisibleStart != (Object)null && ((Component)activeVisibleStart).gameObject.activeInHierarchy)
			{
				activeVisibleStart.text = FormatEnterLabel();
			}
			if ((Object)(object)activeLegacyStart != (Object)null && ((Component)activeLegacyStart).gameObject.activeInHierarchy)
			{
				activeLegacyStart.text = FormatEnterLabel();
			}
		}

		private static bool IsCharacterProbeText(string value)
		{
			int num = value.IndexOf("<size=", StringComparison.OrdinalIgnoreCase);
			if (num >= 0)
			{
				value = value.Substring(0, num).TrimEnd(Array.Empty<char>());
			}
			if (!value.Equals("START", StringComparison.OrdinalIgnoreCase) && !value.Equals("ENTER VALHALLA", StringComparison.OrdinalIgnoreCase) && !value.Equals("PLAY LOCAL", StringComparison.OrdinalIgnoreCase) && !value.Equals("NEW", StringComparison.OrdinalIgnoreCase) && !value.Equals("REMOVE", StringComparison.OrdinalIgnoreCase) && !value.Equals("MANAGE SAVES", StringComparison.OrdinalIgnoreCase))
			{
				return value.Equals("BACK", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static string DescribeVisibleLabel(Transform tf, string content)
		{
			//IL_006e: 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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)(object)((tf is RectTransform) ? tf : null);
			Button componentInParent = ((Component)tf).GetComponentInParent<Button>();
			string text = ((Object)tf).name;
			Transform parent = tf.parent;
			int num = 0;
			while ((Object)(object)parent != (Object)null && num++ < 5)
			{
				text = ((Object)parent).name + "/" + text;
				parent = parent.parent;
			}
			string text2 = (((Object)(object)val == (Object)null) ? ((object)tf.position/*cast due to .constrained prefix*/).ToString() : ((object)((Transform)val).position/*cast due to .constrained prefix*/).ToString());
			return "'" + content + "' path=" + text + " owner=" + (((Object)(object)componentInParent == (Object)null) ? "none" : ((Object)componentInParent).name) + " pos=" + text2;
		}

		private void LogLayoutSnapshot()
		{
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)localButton != (Object)null && ((Component)localButton).gameObject.activeInHierarchy)
			{
				Transform parent = ((Component)localButton).transform.parent;
				LayoutGroup val = (((Object)(object)parent != (Object)null) ? ((Component)parent).GetComponent<LayoutGroup>() : null);
				RectTransform val2 = (((Object)(object)characterPrimaryButton != (Object)null) ? ((Component)characterPrimaryButton).GetComponent<RectTransform>() : null);
				RectTransform component = ((Component)localButton).GetComponent<RectTransform>();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("CHAR rendered: parent=" + (((Object)(object)parent == (Object)null) ? "null" : ((Object)parent).name) + " layout=" + (((Object)(object)val == (Object)null) ? "none" : ((object)val).GetType().Name) + " enter=" + (((Object)(object)val2 == (Object)null) ? "null" : ((object)((Transform)val2).position/*cast due to .constrained prefix*/).ToString()) + " play=" + (((Object)(object)component == (Object)null) ? "null" : ((object)((Transform)component).position/*cast due to .constrained prefix*/).ToString()) + " order=" + (((Object)(object)characterPrimaryButton == (Object)null) ? (-1) : ((Component)characterPrimaryButton).transform.GetSiblingIndex()) + "/" + ((Component)localButton).transform.GetSiblingIndex() + " SeneaLRows=" + nativeCharacterRowsMatched));
			}
		}

		private static bool HasText(Button b, string expected)
		{
			if ((Object)(object)b == (Object)null || !((Component)b).gameObject.activeInHierarchy)
			{
				return false;
			}
			TMP_Text[] componentsInChildren = ((Component)b).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if (val.text != null && val.text.Trim().Equals(expected, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private void SetButtonLabel(Button b, string label)
		{
			if ((Object)(object)b == (Object)null)
			{
				return;
			}
			ButtonLabels value;
			bool flag = !buttonLabels.TryGetValue(b, out value);
			TMP_Text[] tmp;
			Text[] legacy;
			if (!flag)
			{
				tmp = value.Tmp;
				for (int i = 0; i < tmp.Length; i++)
				{
					if ((Object)(object)tmp[i] == (Object)null)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					legacy = value.Legacy;
					for (int i = 0; i < legacy.Length; i++)
					{
						if ((Object)(object)legacy[i] == (Object)null)
						{
							flag = true;
							break;
						}
					}
				}
			}
			if (flag)
			{
				value = new ButtonLabels
				{
					Tmp = ((Component)b).GetComponentsInChildren<TMP_Text>(true),
					Legacy = ((Component)b).GetComponentsInChildren<Text>(true)
				};
				buttonLabels[b] = value;
			}
			bool flag2 = true;
			tmp = value.Tmp;
			foreach (TMP_Text val in tmp)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = (flag2 ? label : string.Empty);
					flag2 = false;
					if (!string.Equals(val.text, text, StringComparison.Ordinal))
					{
						val.text = text;
					}
				}
			}
			bool flag3 = true;
			legacy = value.Legacy;
			foreach (Text val2 in legacy)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					string text2 = (flag3 ? label : string.Empty);
					flag3 = false;
					if (!string.Equals(val2.text, text2, StringComparison.Ordinal))
					{
						val2.text = text2;
					}
				}
			}
		}

		private void PatchSeneaLCharacterMenu()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			MethodInfo methodInfo = SeneaLCompatibility.CharacterMenuTick();
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Plugin), "AfterSeneaLCharacterTick", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"SeneaL character: MenuView.Tick missing; post-render integration unavailable.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SeneaL character: patched MenuView.Tick POSTFIX to update the actual rendered rows.");
			MethodInfo methodInfo3 = AccessTools.Method(typeof(FejdStartup), "ShowCharacterSelection", (Type[])null, (Type[])null);
			MethodInfo methodInfo4 = AccessTools.Method(typeof(Plugin), "AfterCharacterSelectionShown", (Type[])null, (Type[])null);
			if (methodInfo3 != null && methodInfo4 != null)
			{
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Character menu: patched ShowCharacterSelection for immediate Play Local binding.");
			}
		}

		private static void AfterCharacterSelectionShown()
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || instance.shuttingDown || !instance.pluginEnabled.Value)
			{
				return;
			}
			instance.activeVisibleStart = null;
			instance.activeLegacyStart = null;
			instance.buttonLabels.Clear();
			instance.characterRefreshPending = true;
			instance.characterRefreshTries = 0;
			instance.slotProbeCount = 0;
			instance.nextSlotLabelProbe = 0f;
			instance.ApplyMappedCharacterLabels();
			instance.visibleLabelProbeCount = 0;
			instance.nextVisibleLabelProbe = 0f;
			try
			{
				instance.AttachToCharacterMenu();
				if (instance.nativeSlotActionsMapped)
				{
					instance.PatchVisibleCharacterSlots(forceRescan: true);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)instance).Logger.LogWarning((object)("Character select immediate bind failed: " + ex.Message));
			}
		}

		private static void AfterSeneaLCharacterTick(object __instance)
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || instance.shuttingDown || !instance.pluginEnabled.Value || !instance.OnMenu() || instance.nativeSlotActionsMapped)
			{
				return;
			}
			try
			{
				instance.UpdateNativeCharacterRows(__instance);
			}
			catch (Exception ex)
			{
				if (Time.unscaledTime >= instance.nextCharacterLookupLog)
				{
					instance.nextCharacterLookupLog = Time.unscaledTime + 8f;
					((BaseUnityPlugin)instance).Logger.LogWarning((object)("SeneaL character post-render update failed: " + ex));
				}
			}
		}

		private void UpdateNativeCharacterRows(object menu)
		{
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_054a: Unknown result type (might be due to invalid IL or missing references)
			//IL_055f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0502: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_05da: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05fb: Unknown result type (might be due to invalid IL or missing references)
			FejdStartup instance = FejdStartup.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_characterSelectScreen == (Object)null || !instance.m_characterSelectScreen.activeInHierarchy)
			{
				return;
			}
			if (!nativeCharacterTickSeen)
			{
				nativeCharacterTickSeen = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"SeneaL character: post-render MenuView.Tick is active during character selection.");
			}
			if ((Object)(object)characterPrimaryButton == (Object)null || (Object)(object)localButton == (Object)null)
			{
				return;
			}
			FieldInfo fieldInfo = AccessTools.Field(menu.GetType(), "_entries");
			Array array = ((fieldInfo == null) ? null : (fieldInfo.GetValue(menu) as Array));
			if (array == null || array.Length == 0)
			{
				return;
			}
			Type? elementType = array.GetType().GetElementType();
			FieldInfo fieldInfo2 = AccessTools.Field(elementType, "Vanilla");
			FieldInfo fieldInfo3 = AccessTools.Field(elementType, "Label");
			FieldInfo fieldInfo4 = AccessTools.Field(elementType, "Root");
			FieldInfo fieldInfo5 = AccessTools.Field(elementType, "Click");
			if (fieldInfo2 == null || fieldInfo3 == null || fieldInfo4 == null || fieldInfo5 == null)
			{
				return;
			}
			Dictionary<string, NativeCharacterRow> dictionary = new Dictionary<string, NativeCharacterRow>(StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			foreach (object item in array)
			{
				if (item == null)
				{
					continue;
				}
				object? value = fieldInfo2.GetValue(item);
				Button val = (Button)((value is Button) ? value : null);
				object? value2 = fieldInfo3.GetValue(item);
				TMP_Text val2 = (TMP_Text)((value2 is TMP_Text) ? value2 : null);
				object? value3 = fieldInfo4.GetValue(item);
				object? obj = ((value3 is Transform) ? value3 : null);
				RectTransform val3 = (RectTransform)((obj is RectTransform) ? obj : null);
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val3 == (Object)null))
				{
					string text = (val2.text ?? "").Trim();
					string text2 = null;
					if ((Object)(object)val == (Object)(object)characterPrimaryButton)
					{
						text2 = "ENTER";
					}
					else if ((Object)(object)val == (Object)(object)localButton || ((Object)(object)val != (Object)null && ((Object)val).name == "Valhalla_PlayLocalButton"))
					{
						text2 = "LOCAL";
					}
					else if (text.Equals("NEW", StringComparison.OrdinalIgnoreCase))
					{
						text2 = "NEW";
					}
					else if (text.Equals("REMOVE", StringComparison.OrdinalIgnoreCase))
					{
						text2 = "REMOVE";
					}
					else if (text.Equals("MANAGE SAVES", StringComparison.OrdinalIgnoreCase))
					{
						text2 = "MANAGE";
					}
					else if (text.Equals("BACK", StringComparison.OrdinalIgnoreCase))
					{
						text2 = "BACK";
					}
					if (list.Count < 14)
					{
						list.Add(text + ":" + (((Object)(object)val == (Object)null) ? "no button" : ((Object)val).name) + "@" + ((object)val3.anchoredPosition/*cast due to .constrained prefix*/).ToString());
					}
					if (text2 != null && !dictionary.ContainsKey(text2))
					{
						dictionary[text2] = new NativeCharacterRow
						{
							Source = val,
							Rect = val3,
							Text = val2,
							Entry = item
						};
					}
				}
			}
			if (!dictionary.ContainsKey("ENTER") || !dictionary.ContainsKey("LOCAL") || !dictionary.ContainsKey("NEW") || !dictionary.ContainsKey("REMOVE") || !dictionary.ContainsKey("MANAGE") || !dictionary.ContainsKey("BACK"))
			{
				if (Time.unscaledTime >= nextCharacterLookupLog)
				{
					nextCharacterLookupLog = Time.unscaledTime + 8f;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("SeneaL character: post-render rows unmatched; have=" + string.Join(",", new List<string>(dictionary.Keys).ToArray()) + "; observed=" + string.Join(" | ", list.ToArray())));
				}
				return;
			}
			dictionary["ENTER"].Text.text = FormatEnterLabel();
			dictionary["LOCAL"].Text.text = "PLAY LOCAL";
			fieldInfo5.SetValue(dictionary["ENTER"].Entry, new Action(OnEnterValhalla));
			fieldInfo5.SetValue(dictionary["LOCAL"].Entry, new Action(OpenLocalWorldSelection));
			if ((Object)(object)nativeLastStartRoot != (Object)(object)dictionary["ENTER"].Rect || (Object)(object)nativeLastLocalRoot != (Object)(object)dictionary["LOCAL"].Rect || nativeOriginalSlots.Count != 6)
			{
				nativeLastStartRoot = dictionary["ENTER"].Rect;
				nativeLastLocalRoot = dictionary["LOCAL"].Rect;
				nativeOriginalSlots.Clear();
				foreach (KeyValuePair<string, NativeCharacterRow> item2 in dictionary)
				{
					nativeOriginalSlots[item2.Key] = item2.Value.Rect.anchoredPosition;
				}
				nativeCharacterRowsLogged = false;
			}
			List<Vector2> list2 = new List<Vector2>(nativeOriginalSlots.Values);
			bool startAboveNew = nativeOriginalSlots["ENTER"].y >= nativeOriginalSlots["NEW"].y;
			list2.Sort((Vector2 a, Vector2 b) => (!startAboveNew) ? a.y.CompareTo(b.y) : b.y.CompareTo(a.y));
			string[] array2 = new string[6] { "ENTER", "LOCAL", "NEW", "REMOVE", "MANAGE", "BACK" };
			for (int num = 0; num < array2.Length; num++)
			{
				NativeCharacterRow nativeCharacterRow = dictionary[array2[num]];
				Vector2 val4 = list2[num];
				nativeCharacterRow.Rect.anchoredPosition = new Vector2(nativeCharacterRow.Rect.anchoredPosition.x, val4.y);
			}
			nativeCharacterRowsMatched = true;
			if (!nativeCharacterRowsLogged)
			{
				nativeCharacterRowsLogged = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("SeneaL character: POST-RENDER native rows reordered ENTER > LOCAL > NEW > REMOVE > MANAGE > BACK. slots=" + string.Join(",", list2.ConvertAll((Vector2 p) => p.y.ToString("0.0")).ToArray())));
			}
		}

		private void OpenLocalWorldSelection()
		{
			if (savedStartClick != null)
			{
				((UnityEvent)savedStartClick).Invoke();
			}
		}

		private void AttachToCharacterMenu()
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0353: Unknown result type (might be due to invalid IL or missing references)
			//IL_035d: Expected O, but got Unknown
			//IL_036a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0374: Expected O, but got Unknown
			FejdStartup instance = FejdStartup.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_characterSelectScreen == (Object)null || !instance.m_characterSelectScreen.activeInHierarchy || ((Object)(object)characterPrimaryButton != (Object)null && ((Component)characterPrimaryButton).gameObject.activeInHierarchy && (Object)(object)localButton != (Object)null))
			{
				return;
			}
			buttonLabels.Clear();
			FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "m_csStartButton");
			Button val = (Button)((fieldInfo == null) ? null : /*isinst with value type is only supported in some contexts*/);
			if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
			{
				if (Time.unscaledTime > nextCharacterLookupLog)
				{
					nextCharacterLookupLog = Time.unscaledTime + 10f;
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Character menu: FejdStartup.m_csStartButton not active; no buttons changed.");
				}
				return;
			}
			characterPrimaryButton = val;
			savedStartClick = val.onClick;
			RectTransform component = ((Component)val).GetComponent<RectTransform>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			characterOriginalPositions.Clear();
			characterOriginalPositions[component] = component.anchoredPosition;
			Transform parent = ((Component)val).transform.parent;
			Button[] componentsInChildren = ((Component)parent).GetComponentsInChildren<Button>(true);
			foreach (Button val2 in componentsInChildren)
			{
				if (!((Object)(object)val2 == (Object)(object)val) && !((Object)(object)((Component)val2).transform.parent != (Object)(object)parent))
				{
					RectTransform component2 = ((Component)val2).GetComponent<RectTransform>();
					if (!((Object)(object)component2 == (Object)null) && (HasText(val2, "NEW") || HasText(val2, "REMOVE") || HasText(val2, "MANAGE SAVES") || HasText(val2, "BACK")))
					{
						characterOriginalPositions[component2] = component2.anchoredPosition;
					}
				}
			}
			object? obj = AccessTools.Field(typeof(FejdStartup), "m_csNewButton")?.GetValue(instance);
			Button val3 = (Button)((obj is Button) ? obj : null);
			object? obj2 = AccessTools.Field(typeof(FejdStartup), "m_csRemoveButton")?.GetValue(instance);
			Button val4 = (Button)((obj2 is Button) ? obj2 : null);
			if ((Object)(object)val3 == (Object)null || !((Component)val3).gameObject.activeInHierarchy || (Object)(object)val3 == (Object)(object)val)
			{
				componentsInChildren = ((Component)parent).GetComponentsInChildren<Button>(true);
				foreach (Button val5 in componentsInChildren)
				{
					if ((Object)(object)val5 != (Object)(object)val && ((Component)val5).gameObject.activeInHierarchy && HasText(val5, "NEW"))
					{
						val3 = val5;
						break;
					}
				}
			}
			if ((Object)(object)val4 == (Object)null || !((Component)val4).gameObject.activeInHierarchy || (Object)(object)val4 == (Object)(object)val)
			{
				componentsInChildren = ((Component)parent).GetComponentsInChildren<Button>(true);
				foreach (Button val6 in componentsInChildren)
				{
					if ((Object)(object)val6 != (Object)(object)val && ((Component)val6).gameObject.activeInHierarchy && HasText(val6, "REMOVE"))
					{
						val4 = val6;
						break;
					}
				}
			}
			localButton = Object.Instantiate<GameObject>(((Component)val).gameObject, parent).GetComponent<Button>();
			((Object)localButton).name = "Valhalla_ExtraCharacterSlot";
			RemoveLocalization(localButton);
			((Component)localButton).transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex() + 1);
			val.onClick = new ButtonClickedEvent();
			((UnityEvent)val.onClick).AddListener(new UnityAction(OnEnterValhalla));
			SetButtonLabel(val, buttonTitle.Value);
			nativeSlotActionsMapped = (Object)(object)val3 != (Object)null && (Object)(object)val4 != (Object)null && (Object)(object)val3 != (Object)(object)val4;
			if (nativeSlotActionsMapped)
			{
				characterNewButton = val3;
				characterRemoveButton = val4;
				savedNewClick = val3.onClick;
				savedRemoveClick = val4.onClick;
				val3.onClick = savedStartClick;
				val4.onClick = savedNewClick;
				localButton.onClick = savedRemoveClick;
				RemoveLocalization(val3);
				RemoveLocalization(val4);
				SetButtonLabel(val3, "PLAY LOCAL");
				SetButtonLabel(val4, "NEW");
				SetButtonLabel(localButton, "REMOVE");
				((BaseUnityPlugin)this).Logger.LogInfo((object)"CHAR slots: remapped Start->Enter; New->Play Local; Remove->New; Extra->Remove. Click handlers preserved.");
			}
			else
			{
				localButton.onClick = savedStartClick;
				SetButtonLabel(localButton, "PLAY LOCAL");
				((BaseUnityPlugin)this).Logger.LogWarning((object)"CHAR slots: vanilla New/Remove buttons not both found. Original v1.5 arrangement retained to protect their actions.");
			}
			nativeCharacterRowsMatched = false;
			nativeCharacterRowsLogged = false;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Character menu: bound exact m_csStartButton=" + ((Object)val).name + ", parent=" + ((Object)parent).name + ", cloned extra; actionsMapped=" + nativeSlotActionsMapped));
		}

		private static void RemoveLocalization(Button button)
		{
			MonoBehaviour[] componentsInChildren = ((Component)button).GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && ((object)val).GetType().Name == "Localize")
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private void PatchSeneaLMenu()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			MethodInfo methodInfo = SeneaLCompatibility.MainMenuScan();
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Plugin), "AfterSeneaLScan", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"SeneaL UI 1.2.8 MainMenuView.Scan unavailable; native menu integration disabled.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched SeneaL native MainMenuView.Scan for fixed Discord / Rules / Quit ordering.");
		}

		private static void AfterSeneaLScan(object __instance)
		{
			try
			{
				if ((Object)(object)Instance != (Object)null && !Instance.shuttingDown && Instance.pluginEnabled.Value)
				{
					Instance.RewriteSeneaLRows(__instance);
				}
			}
			catch (Exception ex)
			{
				Plugin instance = Instance;
				if (instance != null)
				{
					((BaseUnityPlugin)instance).Logger.LogWarning((object)("Native menu rows could not be updated: " + ex.Message));
				}
			}
		}

		private void RewriteSeneaLRows(object menu)
		{
			Type type = menu.GetType();
			FieldInfo fieldInfo = AccessTools.Field(type, "_count");
			FieldInfo fieldInfo2 = AccessTools.Field(type, "_cin");
			FieldInfo fieldInfo3 = AccessTools.Field(type, "_src");
			FieldInfo fieldInfo4 = AccessTools.Field(type, "_text");
			FieldInfo fieldInfo5 = AccessTools.Field(type, "_tag");
			FieldInfo fieldInfo6 = AccessTools.Field(type, "_act");
			FieldInfo fieldInfo7 = AccessTools.Field(type, "_off");
			MethodInfo methodInfo = AccessTools.Method(type, "Layout", (Type[])null, (Type[])null);
			if (fieldInfo == null || fieldInfo2 == null || fieldInfo3 == null || fieldInfo4 == null || fieldInfo5 == null || fieldInfo6 == null || fieldInfo7 == null || methodInfo == null || (bool)fieldInfo2.GetValue(menu))
			{
				return;
			}
			Button[] array = (Button[])fieldInfo3.GetValue(menu);
			string[] array2 = (string[])fieldInfo4.GetValue(menu);
			string[] array3 = (string[])fieldInfo5.GetValue(menu);
			Action[] array4 = (Action[])fieldInfo6.GetValue(menu);
			bool[] array5 = (bool[])fieldInfo7.GetValue(menu);
			int num = Math.Min((int)fieldInfo.GetValue(menu), array2.Length);
			List<MenuEntry> list = new List<MenuEntry>(num);
			MenuEntry item = default(MenuEntry);
			bool flag = false;
			for (int i = 0; i < num; i++)
			{
				string text = (array2[i] ?? "").Trim();
				if (hideMenuLook.Value && text.Equals("Menu Look", StringComparison.OrdinalIgnoreCase))
				{
					if (!menuLookHideLogged)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)"SeneaL main menu: Menu Look entry hidden by [MainMenu] HideMenuLook.");
						menuLookHideLogged = true;
					}
				}
				else if (text.Equals("Quit", StringComparison.OrdinalIgnoreCase) || text.Equals("Exit", StringComparison.OrdinalIgnoreCase) || ((Object)(object)array[i] != (Object)null && ((Object)array[i]).name.Equals("Exit", StringComparison.OrdinalIgnoreCase)))
				{
					item = new MenuEntry
					{
						Source = array[i],
						Text = array2[i],
						Tag = null,
						Action = array4[i],
						Disabled = array5[i]
					};
					flag = true;
				}
				else if (!text.Equals("Discord", StringComparison.OrdinalIgnoreCase) && !text.Equals("Server Rules", StringComparison.OrdinalIgnoreCase) && !text.Equals(supportTitle.Value.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					list.Add(new MenuEntry
					{
						Source = array[i],
						Text = array2[i],
						Tag = array3[i],
						Action = array4[i],
						Disabled = array5[i]
					});
				}
			}
			int num2 = 1 + (showRulesButton.Value ? 1 : 0) + (supportEnabled.Value ? 1 : 0);
			if (list.Count + (flag ? 1 : 0) + num2 > array2.Length)
			{
				if (!supportOverflowLogged)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"SeneaL main menu has no free row for the optional Support entry; leaving its existing menu unchanged.");
					supportOverflowLogged = true;
				}
				return;
			}
			list.Add(new MenuEntry
			{
				Text = "Discord",
				Action = OpenDiscord,
				Tag = null
			});
			if (showRulesButton.Value)
			{
				list.Add(new MenuEntry
				{
					Text = "Server Rules",
					Tag = null,
					Action = ShowRulesForReading
				});
			}
			if (supportEnabled.Value)
			{
				list.Add(new MenuEntry
				{
					Text = supportTitle.Value.Trim(),
					Tag = null,
					Action = ShowSupport
				});
			}
			if (flag)
			{
				list.Add(item);
			}
			for (int j = 0; j < array2.Length; j++)
			{
				if (j < list.Count)
				{
					array[j] = list[j].Source;
					array2[j] = list[j].Text;
					array3[j] = list[j].Tag;
					array4[j] = list[j].Action;
					array5[j] = list[j].Disabled;
				}
				else
				{
					array[j] = null;
					array2[j] = null;
					array3[j] = null;
					array4[j] = null;
					array5[j] = false;
				}
			}
			fieldInfo.SetValue(menu, list.Count);
			methodInfo.Invoke(menu, null);
			firstMenuModelInitialized = true;
			if (!menuBound)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"SeneaL native main-menu rows include the optional Support entry; Quit remains last.");
				menuBound = true;
			}
		}

		private void ShowSupport()
		{
			if (!TrySupportUrl(out var url))
			{
				ShowNativeNotice("SUPPORT VALHALLA", "The support link has not been configured yet.");
				return;
			}
			if (!supportAskBeforeOpen.Value)
			{
				Application.OpenURL(url.AbsoluteUri);
				return;
			}
			try
			{
				Dialogs.Confirm("SUPPORT VALHALLA", supportDescription.Value, (Action)delegate
				{
					Application.OpenURL(url.AbsoluteUri);
				}, (Action)null, "OPEN SUPPORT PAGE", "CLOSE");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Support dialog could not open: " + ex.Message));
			}
		}

		private bool TrySupportUrl(out Uri url)
		{
			url = null;
			if (!ReleaseConfigValidation.TryGetHttpsUrl(supportUrl.Value ?? "", out var url2) || url2.AbsoluteUri.IndexOf("YOURPAGE", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			url = url2;
			return true;
		}

		private void OpenDiscord()
		{
			if (ReleaseConfigValidation.TryGetHttpsUrl(discord.Value, out var url) && url.AbsoluteUri.IndexOf("YOURINVITE", StringComparison.OrdinalIgnoreCase) < 0)
			{
				Application.OpenURL(url.AbsoluteUri);
			}
			else
			{
				ShowNativeNotice("DISCORD LINK UNAVAILABLE", "The Discord invite URL is not configured correctly. Please contact the administrator.");
			}
		}

		private void OnEnterValhalla()
		{
			if (shuttingDown || rulesOpen)
			{
				return;
			}
			if (rulesEnabled.Value && !rulesAvailable)
			{
				ReloadAssets();
				if (!rulesAvailable)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Cannot present rules agreement: rules file is unavailable or empty.");
					ShowNativeNotice("SERVER RULES UNAVAILABLE", "The server rules are missing or unreadable. Please contact the administrator before joining.");
					return;
				}
			}
			if (rulesEnabled.Value && !Accepted())
			{
				ShowRulesForAgreement();
			}
			else
			{
				BeginJoin();
			}
		}

		private void BeginJoin()
		{
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			if (shuttingDown || !pluginEnabled.Value)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Quick Connect: companion disabled; join not attempted.");
				return;
			}
			if (!ReleaseConfigValidation.TryGetServerAddress(host.Value, out var _) || port.Value < 1 || port.Value > 65535)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Quick Connect: set a valid ServerAddress and ServerPort first.");
				ShowNativeNotice("CONNECTION NOT STARTED", "The server address or port is not configured.");
				return;
			}
			try
			{
				FejdStartup instance = FejdStartup.instance;
				if ((Object)(object)instance == (Object)null)
				{
					throw new InvalidOperationException("FejdStartup instance is not available.");
				}
				if ((Object)(object)instance.m_characterSelectScreen == (Object)null || !instance.m_characterSelectScreen.activeInHierarchy)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Quick Connect: character selection is no longer open; join cancelled.");
					return;
				}
				ServerJoinDataDedicated val = default(ServerJoinDataDedicated);
				((ServerJoinDataDedicated)(ref val))..ctor(host.Value.Trim(), (ushort)port.Value);
				ServerJoinData val2 = default(ServerJoinData);
				((ServerJoinData)(ref val2))..ctor(val);
				if (!((ServerJoinData)(ref val2)).IsValid)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)"Quick Connect: dedicated server join data is invalid.");
					ShowNativeNotice("CONNECTION NOT STARTED", "The configured server address is not valid.");
					return;
				}
				MethodInfo methodInfo = AccessTools.Method(typeof(FejdStartup), "ProceedJoinRequest", new Type[1] { typeof(ServerJoinData) }, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(FejdStartup), "OnCharacterStart", Type.EmptyTypes, (Type[])null);
				FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "m_queuedJoinServer");
				if (methodInfo == null || methodInfo2 == null || fieldInfo == null)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Quick Connect: missing native join members (proceed=" + (methodInfo != null) + ", characterStart=" + (methodInfo2 != null) + ", queuedServer=" + (fieldInfo != null) + ")."));
					ShowNativeNotice("CONNECTION NOT STARTED", "This Valheim version has an unsupported server join API. Please share the BepInEx log.");
					return;
				}
				PendingPassword = password.Value;
				PendingPasswordActive = !string.IsNullOrEmpty(PendingPassword);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Quick Connect: queueing server join; port=" + port.Value + ", configuredPassword=" + PendingPasswordActive + ". Password value is never logged."));
				methodInfo.Invoke(instance, new object[1] { val2 });
				if (!(fieldInfo.GetValue(instance) is ServerJoinData val3))
				{
					throw new InvalidOperationException("Valheim did not return queued ServerJoinData.");
				}
				if (((ServerJoinData)(ref val3)).IsValid && val3.m_type == val2.m_type && ((ServerJoinData)(ref val3)).Dedicated.m_port == (ushort)port.Value)
				{
					ServerJoinDataDedicated dedicated = ((ServerJoinData)(ref val3)).Dedicated;
					if (string.Equals(((ServerJoinDataDedicated)(ref dedicated)).m_host, host.Value.Trim(), StringComparison.OrdinalIgnoreCase))
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)"Quick Connect: queued endpoint verified; invoking native OnCharacterStart for selected character.");
						methodInfo2.Invoke(instance, null);
						((BaseUnityPlugin)this).Logger.LogInfo((object)"Quick Connect: selected-character join started. Watch Valheim networking messages for connection result.");
						return;
					}
				}
				PendingPassword = null;
				PendingPasswordActive = false;
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Quick Connect: Valheim did not queue the configured endpoint; selected-character start was not invoked.");
				ShowNativeNotice("CONNECTION NOT STARTED", "Valheim did not accept the server join request. Please retry or use Join IP.");
			}
			catch (Exception ex)
			{
				PendingPassword = null;
				PendingPasswordActive = false;
				Exception obj = ((ex is TargetInvocationException && ex.InnerException != null) ? ex.InnerException : ex);
				((BaseUnityPlugin)this).Logger.LogError((object)("Quick Connect: native join request failed: " + obj));
				ShowNativeNotice("CONNECTION NOT STARTED", "Valheim could not start the server connection. Check BepInEx/LogOutput.log for details.");
			}
		}

		private static List<string> WrapAndPaginateRules(string text, int linesPerPage, int lineWidth, bool balancePages)
		{
			int num = Mathf.Clamp(lineWidth, 45, 128);
			List<string> list = new List<string>();
			string[] array = (text ?? "").Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].TrimEnd(Array.Empty<char>());
				if (string.IsNullOrWhiteSpace(text2))
				{
					if (list.Count > 0 && list[list.Count - 1].Length != 0)
					{
						list.Add("");
					}
					continue;
				}
				while (text2.Length > num)
				{
					int num2 = text2.LastIndexOf(' ', num);
					if (num2 < num / 2)
					{
						num2 = num;
					}
					list.Add(text2.Substring(0, num2).TrimEnd(Array.Empty<char>()));
					text2 = text2.Substring(num2).TrimStart(Array.Empty<char>());
				}
				if (text2.Length > 0)
				{
					list.Add(text2);
				}
			}
			while (list.Count > 0 && list[list.Count - 1].Length == 0)
			{
				list.RemoveAt(list.Count - 1);
			}
			List<string> list2 = new List<string>();
			int num3 = Math.Max(6, Math.Min(36, linesPerPage));
			if (!balancePages)
			{
				for (int j = 0; j < list.Count; j += num3)
				{
					int count = Math.Min(num3, list.Count - j);
					List<string> range = list.GetRange(j, count);
					while (range.Count > 0 && range[range.Count - 1].Length == 0)
					{
						range.RemoveAt(range.Count - 1);
					}
					if (range.Count > 0)
					{
						list2.Add(string.Join("\n", range.ToArray()));
					}
				}
				return list2;
			}
			int num4 = Math.Max(1, (list.Count + num3 - 1) / num3);
			int k = 0;
			for (int l = 0; l < num4; l++)
			{
				if (k >= list.Count)
				{
					break;
				}
				for (; k < list.Count && list[k].Length == 0; k++)
				{
				}
				if (k >= list.Count)
				{
					break;
				}
				int num5 = list.Count - k;
				int num6 = num4 - l;
				int num7 = Math.Min(num3, (num5 + num6 - 1) / num6);
				if (num6 > 1)
				{
					int num8 = num7;
					int num9 = int.MaxValue;
					int num10 = Math.Max(1, num7 - 4);
					int num11 = Math.Min(num3, num7 + 4);
					for (int m = num10; m <= num11 && k + m < list.Count; m++)
					{
						if (num5 - m <= (num6 - 1) * num3 && num5 - m >= num6 - 1 && (list[k + m - 1].Length == 0 || list[k + m].Length == 0))
						{
							int num12 = Math.Abs(m - num7);
							if (num12 < num9)
							{
								num8 = m;
								num9 = num12;
							}
						}
					}
					num7 = num8;
				}
				List<string> range2 = list.GetRange(k, num7);
				while (range2.Count > 0 && range2[range2.Count - 1].Length == 0)
				{
					range2.RemoveAt(range2.Count - 1);
				}
				if (range2.Count > 0)
				{
					list2.Add(string.Join("\n", range2.ToArray()));
				}
				k += num7;
			}
			if (k < list.Count)
			{
				int num13;
				for (; k < list.Count; k += num13)
				{
					for (; k < list.Count && list[k].Length == 0; k++)
					{
					}
					if (k >= list.Count)
					{
						break;
					}
					num13 = Math.Min(num3, list.Count - k);
					List<string> range3 = list.GetRange(k, num13);
					while (range3.Count > 0 && range3[range3.Count - 1].Length == 0)
					{
						range3.RemoveAt(range3.Count - 1);
					}
					if (range3.Count > 0)
					{
						list2.Add(string.Join("\n", range3.ToArray()));
					}
				}
			}
			return list2;
		}

		private bool TryResizeCurrentNativeRulesDialog(Dialog expectedDialog, int expectedPage, bool remeasure = false)
		{
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0469: Unknown result type (might be due to invalid IL or missing references)
			//IL_0486: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0606: Unknown result type (might be due to invalid IL or missing references)
			//IL_060b: Unknown result type (might be due to invalid IL or missing references)
			//IL_06dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e6: Unknown result type (might be due to invalid IL or missing references)
			if (!rulesOpen || rulesPageTransitioning || shuttingDown || expectedDialog == null || activeRulesDialog != expectedDialog || expectedPage != rulesPageIndex || !expectedDialog.IsOpen)
			{
				return false;
			}
			try
			{
				Type type = SeneaLCompatibility.NativeDialogType();
				if (type == null)
				{
					return false;
				}
				PropertyInfo property = type.GetProperty("Top", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				FieldInfo field = type.GetField("_w", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				FieldInfo field2 = type.GetField("_h", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				FieldInfo field3 = type.GetField("Root", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				MethodInfo method = type.GetMethod("SetSize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
				{
					typeof(float),
					typeof(float)
				}, null);
				if (property == null || field == null || field2 == null || field3 == null || method == null)
				{
					return false;
				}
				object value = property.GetValue(null, null);
				if (value == null)
				{
					return false;
				}
				if (resizedNativeDialog == value)
				{
					return true;
				}
				object? value2 = field3.GetValue(value);
				RectTransform val = (RectTransform)((value2 is RectTransform) ? value2 : null);
				if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
				{
					return false;
				}
				if (rulesSizingDialog != value || rulesSizingPage != expectedPage)
				{
					rulesSizingDialog = value;
					rulesSizingPage = expectedPage;
					rulesSizingOriginalWidth = Convert.ToSingle(field.GetValue(value));
					rulesSizingOriginalHeight = Convert.ToSingle(field2.GetValue(value));
					rulesSizingAppliedHeight = 0f;
					rulesSizingLogComplete = false;
				}
				float num = rulesSizingOriginalWidth;
				float num2 = rulesSizingOriginalHeight;
				if (num <= 1f || num2 <= 1f)
				{
					return false;
				}
				float num3 = Mathf.Clamp(rulesNativeDialogScale.Value, 1f, 2f);
				if (num3 <= 1.001f)
				{
					resizedNativeDialog = value;
					return true;
				}
				float num4 = Screen.width;
				float num5 = Screen.height;
				Transform parent = ((Transform)val).parent;
				int num6 = 0;
				Rect rect;
				while ((Object)(object)parent != (Object)null && num6 < 8)
				{
					RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
					if ((Object)(object)val2 != (Object)null)
					{
						rect = val2.rect;
						if (((Rect)(ref rect)).width >= num)
						{
							rect = val2.rect;
							if (((Rect)(ref rect)).height >= num2)
							{
								rect = val2.rect;
								num4 = ((Rect)(ref rect)).width;
								rect = val2.rect;
								num5 = ((Rect)(ref rect)).height;
								break;
							}
						}
					}
					num6++;
					parent = parent.parent;
				}
				float num7 = Mathf.Max(num, Mathf.Min(num * num3, num4 * 0.92f));
				float num8 = Mathf.Max(num2, Mathf.Min(num2 * num3, num5 * 0.86f));
				float num9 = ((!remeasure || rulesSizingAppliedHeight <= 0f) ? num8 : rulesSizingAppliedHeight);
				method.Invoke(value, new object[2] { num7, num9 });
				rulesSizingAppliedHeight = num9;
				if (!TryExpandNativeRulesBody(val, rulesPages[expectedPage], out var textRect, out var preferredBodyHeight) || (Object)(object)textRect == (Object)null || preferredBodyHeight <= 1f)
				{
					return false;
				}
				rect = textRect.rect;
				if (((Rect)(ref rect)).height < preferredBodyHeight + 4f)
				{
					if (num9 < num8 - 1f)
					{
						rulesSizingAppliedHeight = num8;
						method.Invoke(value, new object[2] { num7, num8 });
						TryExpandNativeRulesBody(val, rulesPages[expectedPage], out var _, out var _);
					}
					ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
					string[] obj = new string[6]
					{
						"Rules visual-fit text-area safety fallback on page ",
						(expectedPage + 1).ToString(),
						": rect=",
						null,
						null,
						null
					};
					rect = textRect.rect;
					obj[3] = ((Rect)(ref rect)).height.ToString("0");
					obj[4] = ", required=";
					obj[5] = preferredBodyHeight.ToString("0");
					logger.LogWarning((object)string.Concat(obj));
					resizedNativeDialog = value;
					return true;
				}
				if (!TryFindRulesActionTop(val, out var topWorld))
				{
					return false;
				}
				Vector3[] array = (Vector3[])(object)new Vector3[4];
				textRect.GetWorldCorners(array);
				float num10 = Mathf.Max(0.0001f, Mathf.Abs(((Transform)textRect).lossyScale.y));
				float num11 = Mathf.Max(0.0001f, Mathf.Abs(((Transform)val).lossyScale.y));
				float num12 = (array[1].y - preferredBodyHeight * num10 - topWorld) / num11;
				if (float.IsNaN(num12) || float.IsInfinity(num12) || Mathf.Abs(num12) > num8 * 3f)
				{
					return false;
				}
				float num13 = Mathf.Min(num8, Mathf.Max(245f, Mathf.Max(num2 * 0.38f, preferredBodyHeight + 130f)));
				float num14 = Mathf.Clamp(num9 - (num12 - 34f), num13, num8);
				bool flag = Mathf.Abs(num14 - num9) < 2f;
				bool flag2 = Mathf.Abs(num12 - 34f) <= 12f;
				if (flag2 || flag)
				{
					resizedNativeDialog = value;
					if (!rulesSizingLogComplete)
					{
						rulesSizingLogComplete = true;
						ManualLogSource logger2 = ((BaseUnityPlugin)this).Logger;
						string[] obj2 = new string[18]
						{
							"Rules visual-fit page ",
							(expectedPage + 1).ToString(),
							": measured gap=",
							num12.ToString("0"),
							", target=",
							34f.ToString("0"),
							", content=",
							preferredBodyHeight.ToString("0"),
							", frame request=",
							num9.ToString("0"),
							", actual frame=",
							null,
							null,
							null,
							null,
							null,
							null,
							null
						};
						rect = val.rect;
						obj2[11] = ((Rect)(ref rect)).height.ToString("0");
						obj2[12] = ", original=";
						obj2[13] = num2.ToString("0");
						obj2[14] = ", max=";
						obj2[15] = num8.ToString("0");
						obj2[16] = ", limited=";
						obj2[17] = (!flag2).ToString();
						logger2.LogInfo((object)string.Concat(obj2));
					}
					return true;
				}
				method.Invoke(value, new object[2] { num7, num14 });
				rulesSizingAppliedHeight = num14;
				if (TryExpandNativeRulesBody(val, rulesPages[expectedPage], out var textRect3, out var preferredBodyHeight3) && !((Object)(object)textRect3 == (Object)null) && !(preferredBodyHeight3 <= 1f))
				{
					rect = textRect3.rect;
					if (!(((Rect)(ref rect)).height < preferredBodyHeight3 + 4f))
					{
						ManualLogSource logger3 = ((BaseUnityPlugin)this).Logger;
						string[] obj3 = new string[10]
						{
							"Rules visual-fit page ",
							(expectedPage + 1).ToString(),
							" pass: gap=",
							num12.ToString("0"),
							", request=",
							num9.ToString("0"),
							"->",
							num14.ToString("0"),
							", actual frame=",
							null
						};
						rect = val.rect;
						obj3[9] = ((Rect)(ref rect)).height.ToString("0");
						logger3.LogInfo((object)string.Concat(obj3));
						return false;
					}
				}
				method.Invoke(value, new object[2] { num7, num8 });
				rulesSizingAppliedHeight = num8;
				TryExpandNativeRulesBody(val, rulesPages[expectedPage], out var _, out var _);
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Rules visual-fit shrink rejected: insufficient text area, page " + (expectedPage + 1)));
				resizedNativeDialog = value;
				return true;
			}
			catch (Exception ex)
			{
				if (!nativeResizeMissingLogged)
				{
					nativeResizeMissingLogged = true;
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Rules native layout unavailable; leaving SeneaL window unchanged: " + ((ex.InnerException != null) ? ex.InnerException.Message : ex.Message)));
				}
				return false;
			}
		}

		private bool TryExpandNativeRulesBody(RectTransform dialogRoot, string expectedBody, out RectTransform textRect, out float preferredBodyHeight)
		{
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_030e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0315: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0323: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Unknown result type (might be due to invalid IL or missing references)
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			textRect = null;
			preferredBodyHeight = 0f;
			if ((Object)(object)dialogRoot == (Object)null || string.IsNullOrEmpty(expectedBody))
			{
				return false;
			}
			string text = expectedBody.TrimStart(Array.Empty<char>());
			if (text.Length > 35)
			{
				text = text.Substring(0, 35);
			}
			if (text.Length < 5)
			{
				return false;
			}
			TMP_Text val = null;
			Text val2 = null;
			TMP_Text[] componentsInChildren = ((Component)dialogRoot).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val3 in componentsInChildren)
			{
				if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.rectTransform == (Object)null) && (val3.text ?? "").IndexOf(text, StringComparison.Ordinal) >= 0)
				{
					val = val3;
					textRect = val3.rectTransform;
					break;
				}
			}
			if ((Object)(object)textRect == (Object)null)
			{
				Text[] componentsInChildren2 = ((Component)dialogRoot).GetComponentsInChildren<Text>(true);
				foreach (Text val4 in componentsInChildren2)
				{
					if (!((Object)(object)val4 == (Object)null) && (val4.text ?? "").IndexOf(text, StringComparison.Ordinal) >= 0)
					{
						val2 = val4;
						textRect = ((Graphic)val4).rectTransform;
						break;
					}
				}
			}
			if ((Object)(object)textRect == (Object)null)
			{
				return false;
			}
			Vector3[] array = (Vector3[])(object)new Vector3[4];
			Vector3[] array2 = (Vector3[])(object)new Vector3[4];
			dialogRoot.GetWorldCorners(array);
			textRect.GetWorldCorners(array2);
			Transform parent = ((Transform)textRect).parent;
			if ((Object)(object)parent == (Object)null)
			{
				return false;
			}
			float num = Mathf.Max(0.0001f, Mathf.Abs(parent.lossyScale.x));
			float num2 = Mathf.Max(0.0001f, Mathf.Abs(parent.lossyScale.y));
			float num3 = Mathf.Abs(array[2].x - array[0].x);
			float num4 = Mathf.Abs(array[2].y - array[0].y);
			float num5 = array[2].x - num3 * 0.045f;
			float topWorld;
			float num6 = (TryFindRulesActionTop(dialogRoot, out topWorld) ? (topWorld + num4 * 0.025f) : (array[0].y + num4 * 0.13f));
			float num7 = (num5 - array2[0].x) / num;
			float num8 = (array2[1].y - num6) / num2;
			Rect rect = textRect.rect;
			float width = ((Rect)(ref rect)).width;
			rect = textRect.rect;
			float height = ((Rect)(ref rect)).height;
			if (num7 < 1f || num8 < 1f)
			{
				return false;
			}
			ContentSizeFitter component = ((Component)textRect).GetComponent<ContentSizeFitter>();
			if ((Object)(object)component != (Object)null)
			{
				component.horizontalFit = (FitMode)0;
				component.verticalFit = (FitMode)0;
			}
			textRect.SetSizeWithCurrentAnchors((Axis)0, num7);
			textRect.SetSizeWithCurrentAnchors((Axis)1, num8);
			Vector3[] array3 = (Vector3[])(object)new Vector3[4];
			textRect.GetWorldCorners(array3);
			RectTransform obj = textRect;
			((Transform)obj).position = ((Transform)obj).position + (array2[1] - array3[1]);
			if ((Object)(object)val != (Object)null)
			{
				val.ForceMeshUpdate(false, false);
				TMP_Text obj2 = val;
				rect = textRect.rect;
				Vector2 preferredValues = obj2.GetPreferredValues(expectedBody, Mathf.Max(1f, ((Rect)(ref rect)).width), float.PositiveInfinity);
				if (preferredValues.y > 0f && !float.IsInfinity(preferredValues.y) && !float.IsNaN(preferredValues.y))
				{
					preferredBodyHeight = preferredValues.y;
				}
			}
			else if ((Object)(object)val2 != (Object)null)
			{
				((Graphic)val2).SetAllDirty();
			}
			ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
			string[] obj3 = new string[9]
			{
				"Rules native SeneaL message resized: ",
				width.ToString("0"),
				"x",
				height.ToString("0"),
				" -> ",
				null,
				null,
				null,
				null
			};
			rect = textRect.rect;
			obj3[5] = ((Rect)(ref rect)).width.ToString("0");
			obj3[6] = "x";
			rect = textRect.rect;
			obj3[7] = ((Rect)(ref rect)).height.ToString("0");
			obj3[8] = "; genuine provider text component.";
			logger.LogInfo((object)string.Concat(obj3));
			return true;
		}

		private static bool TryFindRulesActionTop(RectTransform root, out float topWorld)
		{
			topWorld = float.NegativeInfinity;
			if ((Object)(object)root == (Object)null)
			{
				return false;
			}
			TMP_Text[] componentsInChildren = ((Component)root).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && IsNativeRulesActionLabel(val.text))
				{
					Vector3[] array = (Vector3[])(object)new Vector3[4];
					val.rectTransform.GetWorldCorners(array);
					topWorld = Mathf.Max(topWorld, array[1].y);
				}
			}
			if (float.IsNegativeInfinity(topWorld))
			{
				Text[] componentsInChildren2 = ((Component)root).GetComponentsInChildren<Text>(true);
				foreach (Text val2 in componentsInChildren2)
				{
					if (!((Object)(object)val2 == (Object)null) && IsNativeRulesActionLabel(val2.text))
					{
						Vector3[] array2 = (Vector3[])(object)new Vector3[4];
						((Graphic)val2).rectTransform.GetWorldCorners(array2);
						topWorld = Mathf.Max(topWorld, array2[1].y);
					}
				}
			}
			return !float.IsNegativeInfinity(topWorld);
		}

		private static bool IsNativeRulesActionLabel(string label)
		{
			if (string.IsNullOrEmpty(label))
			{
				return false;
			}
			string a = label.Trim();
			if (!string.Equals(a, "NEXT PAGE", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "BACK", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "DECLINE", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "AGREE & JOIN", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(a, "CLOSE", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private IEnumerator ResizeRulesDialogNextFrame(Dialog expectedDialog, int page)
		{
			for (int pass = 0; pass < 8; pass++)
			{
				yield return null;
				if (shuttingDown || !rulesOpen || rulesPageTransitioning || expectedDialog == null || activeRulesDialog != expectedDialog || page != rulesPageIndex || !expectedDialog.IsOpen || TryResizeCurrentNativeRulesDialog(expectedDialog, page, remeasure: true))
				{
					yield break;
				}
			}
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Rules visual-fit did not converge within eight layout frames, page " + (page + 1) + ". Please provide BepInEx/LogOutput.log for diagnosis."));
		}

		private void BeginRulesViewer(bool forAgreement)
		{
			if (shuttingDown || rulesOpen)
			{
				return;
			}
			if (!Dialogs.HasProvider)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Server Rules: native SeneaL dialog provider unavailable; rules window and join cancelled.");
				return;
			}
			ReloadAssets();
			if (!rulesAvailable)
			{
				ShowNativeNotice("SERVER RULES", "The server rules file is missing or empty. Please contact the server administrator.");
				return;
			}
			float num = Mathf.Clamp(rulesNativeDialogScale.Value, 1f, 2f);
			int lineWidth = Mathf.Clamp(Mathf.RoundToInt(52f * num), 52, 112);
			int num2 = Mathf.Clamp(rulesLinesPerPage.Value, 6, 16);
			int linesPerPage = (rulesOptimizePageSpace.Value ? Mathf.Clamp(Mathf.RoundToInt((float)num2 * (1f + 2.2f * (num - 1f))), 6, 36) : Mathf.Clamp(Mathf.RoundToInt((float)num2 * num), 6, 24));
			rulesPages.Clear();
			rulesPages.AddRange(WrapAndPaginateRules(rulesText, linesPerPage, lineWidth, rulesOptimizePageSpace.Value));
			if (rulesPages.Count < 1 || rulesPages.Count > 128)
			{
				rulesPages.Clear();
				ShowNativeNotice("SERVER RULES", "The rules file cannot be displayed safely. Please contact an administrator.");
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Rules pagination produced an invalid page count. Connection not attempted.");
				return;
			}
			rulesOpen = true;
			pendingJoin = forAgreement;
			rulesPageIndex = 0;
			rulesPageTransitioning = false;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Opening SeneaL rules " + (forAgreement ? "agreement" : "viewer") + "; pages=" + rulesPages.Count + "; max wrapped lines/page=" + linesPerPage + "; balanced=" + rulesOptimizePageSpace.Value + "; native provider=" + Dialogs.HasProvider));
			OpenRulesPage();
		}

		private void ShowRulesForReading()
		{
			BeginRulesViewer(forAgreement: false);
		}

		private void ShowNativeNotice(string title, string message)
		{
			try
			{
				Dialogs.Message(title, message, (Action)null, "CLOSE");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("SeneaL notice dialog unavailable: " + ex));
			}
		}

		private void OpenRulesPage()
		{
			if (shuttingDown || !rulesOpen || rulesPageIndex < 0 || rulesPageIndex >= rulesPages.Count)
			{
				return;
			}
			try
			{
				activeRulesDialogWasOpen = false;
				bool flag = rulesPageIndex == 0;
				bool flag2 = rulesPageIndex == rulesPages.Count - 1;
				string text = "SERVER RULES (" + (rulesPageIndex + 1) + "/" + rulesPages.Count + ")";
				string text2 = rulesPages[rulesPageIndex];
				if (rulesPages.Count == 1)
				{
					activeRulesDialog = (Dialog)(pendingJoin ? ((object)Dialogs.Confirm(text, text2, (Action)OnRulesAccepted, (Action)OnRulesDialogClosed, "AGREE & JOIN", "DECLINE")) : ((object)Dialogs.Message(text, text2, (Action)OnRulesDialogClosed, "CLOSE")));
				}
				else if (flag2)
				{
					activeRulesDialog = (Dialog)(object)(pendingJoin ? Dialogs.Confirm(text, text2, (Action)OnRulesAccepted, (Action)PreviousRulesPage, "AGREE & JOIN", "BACK") : Dialogs.Confirm(text, text2, (Action)OnRulesDialogClosed, (Action)PreviousRulesPage, "CLOSE", "BACK"));
				}
				else if (flag)
				{
					activeRulesDialog = (Dialog)(object)Dialogs.Confirm(text, text2, (Action)NextRulesPage, (Action)OnRulesDialogClosed, "NEXT PAGE", pendingJoin ? "DECLINE" : "CLOSE");
				}
				else
				{
					activeRulesDialog = (Dialog)(object)Dialogs.Confirm(text, text2, (Action)NextRulesPage, (Action)PreviousRulesPage, "NEXT PAGE", "BACK");
				}
				if (activeRulesDialog == null)
				{
					throw new InvalidOperationException("SeneaL API did not create a rules dialog.");
				}
				TryResizeCurrentNativeRulesDialog(activeRulesDialog, rulesPageIndex);
				((MonoBehaviour)this).StartCoroutine(ResizeRulesDialogNextFrame(activeRulesDialog, rulesPageIndex));
				((BaseUnityPlugin)this).Logger.LogInfo((object)("SeneaL rules page " + (rulesPageIndex + 1) + "/" + rulesPages.Count + " opened."));
			}
			catch (Exception ex)
			{
				OnRulesDialogClosed();
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not open SeneaL rules page; connection cancelled: " + ex));
			}
		}

		private void NextRulesPage()
		{
			NavigateRulesPage(1);
		}

		private void PreviousRulesPage()
		{
			NavigateRulesPage(-1);
		}

		private void NavigateRulesPage(int direction)
		{
			if (!shuttingDown && rulesOpen && !rulesPageTransitioning)
			{
				int num = rulesPageIndex + direction;
				if (num >= 0 && num < rulesPages.Count)
				{
					rulesPageTransitioning = true;
					activeRulesDialog = null;
					resizedNativeDialog = null;
					rulesSizingDialog = null;
					rulesSizingPage = -1;
					rulesSizingAppliedHeight = 0f;
					activeRulesDialogWasOpen = false;
					((MonoBehaviour)this).StartCoroutine(OpenRulesPageAfterClose(num));
				}
			}
		}

		private IEnumerator OpenRulesPageAfterClose(int page)
		{
			yield return null;
			if (!shuttingDown && rulesOpen && !((Object)(object)this == (Object)null) && ((Behaviour)this).isActiveAndEnabled)
			{
				rulesPageIndex = page;
				rulesPageTransitioning = false;
				OpenRulesPage();
			}
		}

		private void OnRulesDialogClosed()
		{
			rulesOpen = false;
			pendingJoin = false;
			activeRulesDialog = null;
			resizedNativeDialog = null;
			rulesSizingDialog = null;
			rulesSizingPage = -1;
			rulesSizingAppliedHeight = 0f;
			activeRulesDialogWasOpen = false;
			rulesPageTransitioning = false;
			rulesPages.Clear();
			rulesPageIndex = 0;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Server Rules dialog closed without joining.");
		}

		private void OnRulesAccepted()
		{
			if (shuttingDown)
			{
				return;
			}
			if (!rulesOpen || !pendingJoin || rulesPages.Count == 0 || rulesPageIndex != rulesPages.Count - 1)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Ignoring rules acceptance outside the final agreement page.");
				OnRulesDialogClosed();
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Quick Connect: rules AGREE callback received on final page; scheduling join.");
			OnRulesDialogClosed();
			PlayerPrefs.SetInt(AcceptanceKey(), 1);
			PlayerPrefs.Save();
			if (!joinQueuedAfterRules)
			{
				joinQueuedAfterRules = true;
				((MonoBehaviour)this).StartCoroutine(JoinAfterRulesDialog());
			}
		}

		private IEnumerator JoinAfterRulesDialog()
		{
			yield return null;
			joinQueuedAfterRules = false;
			if (!shuttingDown && (Object)(object)this != (Object)null && ((Behaviour)this).isActiveAndEnabled)
			{
				BeginJoin();
			}
		}

		private void ShowRulesForAgreement()
		{
			BeginRulesViewer(forAgreement: true);
		}

		private void CheckRulesModalLifetime()
		{
			if (shuttingDown || !rulesOpen || rulesPageTransitioning || activeRulesDialog == null)
			{
				return;
			}
			try
			{
				if (activeRulesDialog.IsOpen)
				{
					activeRulesDialogWasOpen = true;
				}
				else if (activeRulesDialogWasOpen)
				{
					OnRulesDialogClosed();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Log