Decompiled source of ValhallaMenuCompanion v2.0.0

plugins/ValhallaMenuCompanion.dll

Decompiled 17 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
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: AssemblyVersion("0.0.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.11.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class ButtonTextCache
		{
			public TMP_Text[] Tmp;

			public Text[] Legacy;

			public float RefreshAfter;
		}

		public enum TooltipLinesMode
		{
			All,
			Random
		}

		public enum CaptionCorner
		{
			TopLeft,
			TopCenter,
			TopRight,
			BottomLeft,
			BottomCenter,
			BottomRight
		}

		public enum ImageOrderMode
		{
			Random,
			Sequential
		}

		public enum TipsModeSetting
		{
			Replace,
			Append,
			Off
		}

		public enum ImageFitMode
		{
			Cover,
			Fit,
			Stretch
		}

		public enum LoadingLogoMode
		{
			Keep,
			Custom,
			Hide
		}

		private struct MenuEntry
		{
			public Button Source;

			public string Text;

			public string Tag;

			public Action Action;

			public bool Disabled;
		}

		[HarmonyPatch]
		private static class SeneaLMainMenuScanPatch
		{
			private static MethodBase target;

			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				target = SeneaLCompatibility.MainMenuScan();
				if (target == null)
				{
					Warn("SeneaL UI 1.2.8 MainMenuView.Scan unavailable; native menu integration disabled.");
				}
				else
				{
					Log("Patched SeneaL native MainMenuView.Scan for fixed Discord / Rules / Quit ordering.");
				}
				return target != null;
			}

			private static MethodBase TargetMethod()
			{
				return target;
			}

			[HarmonyPostfix]
			private static void Postfix(object __instance)
			{
				AfterSeneaLScan(__instance);
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "TryPlayIntroCinematic")]
		private static class StartupIntroPatch
		{
			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				MethodInfo methodInfo = AccessTools.Method(typeof(FejdStartup), "TryPlayIntroCinematic", (Type[])null, (Type[])null);
				int num;
				if (methodInfo != null)
				{
					num = (typeof(IEnumerator).IsAssignableFrom(methodInfo.ReturnType) ? 1 : 0);
					if (num != 0)
					{
						Log("Startup cinematic: hooked FejdStartup.TryPlayIntroCinematic; skip=" + Instance?.skipStartupCinematic.Value);
						return (byte)num != 0;
					}
				}
				else
				{
					num = 0;
				}
				Warn("Startup cinematic: TryPlayIntroCinematic hook unavailable.");
				return (byte)num != 0;
			}

			[HarmonyPrefix]
			private static bool Prefix(ref IEnumerator __result)
			{
				return BeforeStartupCinematic(ref __result);
			}
		}

		[HarmonyPatch]
		private static class CinematicsPlayPatch
		{
			private static readonly List<MethodBase> targets = new List<MethodBase>();

			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				targets.Clear();
				Type type = AccessTools.TypeByName("CinematicsManager");
				if (type != null)
				{
					MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (MethodInfo methodInfo in methods)
					{
						if (!(methodInfo.Name != "Play") && !(methodInfo.ReturnType != typeof(bool)))
						{
							ParameterInfo[] parameters = methodInfo.GetParameters();
							if (parameters.Length == 2 && parameters[0].ParameterType.IsEnum)
							{
								targets.Add(methodInfo);
							}
						}
					}
				}
				Log("Startup cinematic: intro-flag playback guard count=" + targets.Count);
				return targets.Count > 0;
			}

			private static IEnumerable<MethodBase> TargetMethods()
			{
				return targets;
			}

			[HarmonyPrefix]
			private static bool Prefix(object[] __args, ref bool __result)
			{
				return BeforeCinematicsPlay(__args, ref __result);
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "ShowCharacterSelection")]
		private static class CharacterSelectionShownPatch
		{
			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				bool num = AccessTools.Method(typeof(FejdStartup), "ShowCharacterSelection", (Type[])null, (Type[])null) != null;
				if (num)
				{
					Log("Character menu: patched ShowCharacterSelection for immediate Play Local binding.");
				}
				return num;
			}

			[HarmonyPostfix]
			private static void Postfix()
			{
				AfterCharacterSelectionShown();
			}
		}

		[HarmonyPatch(typeof(Hud), "ShuffleTips")]
		private static class LoadingScreenSetupPatch
		{
			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				if (!(AccessTools.Method(typeof(Hud), "ShuffleTips", (Type[])null, (Type[])null) != null) || !(AccessTools.Field(typeof(Hud), "m_loadingImage") != null) || !(AccessTools.Field(typeof(Hud), "m_loadingTips") != null))
				{
					Warn("Loading screen: Hud.ShuffleTips / m_loadingImage / m_loadingTips not found in this Valheim build; custom loading screens disabled.");
					return false;
				}
				firstSpawnField = AccessTools.Field(typeof(Game), "m_firstSpawn");
				if (firstSpawnField == null)
				{
					Warn("Loading screen: Game.m_firstSpawn not found; every loading screen counts as 'join'.");
				}
				Plugin instance = Instance;
				Log("Loading screen: hooked Hud.ShuffleTips; images=" + ((!((Object)(object)instance == (Object)null)) ? instance.loadingImageFiles.Count : 0) + ", tips=" + ((!((Object)(object)instance == (Object)null)) ? instance.customLoadingTips.Count : 0) + ".");
				return true;
			}

			[HarmonyPrefix]
			private static void Prefix(Hud __instance)
			{
				BeforeLoadingScreenSetup(__instance);
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "ResetStartupMusic")]
		private static class MenuMusicPatch
		{
			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				int num;
				if (AccessTools.Method(typeof(FejdStartup), "ResetStartupMusic", (Type[])null, (Type[])null) != null && AccessTools.Field(typeof(MusicMan), "m_music") != null)
				{
					num = ((AccessTools.Field(typeof(MusicMan), "m_musicSource") != null) ? 1 : 0);
					if (num != 0)
					{
						Log("Menu music: hooked FejdStartup.ResetStartupMusic.");
						return (byte)num != 0;
					}
				}
				else
				{
					num = 0;
				}
				Warn("Menu music: FejdStartup.ResetStartupMusic / MusicMan fields not found in this Valheim build; custom menu music disabled.");
				return (byte)num != 0;
			}

			[HarmonyPrefix]
			private static void Prefix()
			{
				BeforeStartupMusic();
			}
		}

		[HarmonyPatch]
		private static class ItemTooltipPatch
		{
			private static MethodBase target;

			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				MethodInfo methodInfo = null;
				MethodInfo[] methods = typeof(ItemData).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods)
				{
					if (!(methodInfo2.Name != "GetTooltip") && !(methodInfo2.ReturnType != typeof(string)))
					{
						ParameterInfo[] parameters = methodInfo2.GetParameters();
						if (parameters.Length >= 2 && !(parameters[0].ParameterType != typeof(ItemData)) && (methodInfo == null || parameters.Length > methodInfo.GetParameters().Length))
						{
							methodInfo = methodInfo2;
						}
					}
				}
				target = methodInfo;
				if (methodInfo == null)
				{
					Warn("Tooltips: ItemDrop.ItemData.GetTooltip(ItemData, ...) not found in this Valheim build; custom item tooltips disabled.");
					return false;
				}
				tooltipAppendingArg = -1;
				ParameterInfo[] parameters2 = methodInfo.GetParameters();
				for (int j = 0; j < parameters2.Length; j++)
				{
					if (parameters2[j].ParameterType == typeof(bool) && parameters2[j].Name == "appending")
					{
						tooltipAppendingArg = j;
					}
				}
				Log("Tooltips: hooked ItemData.GetTooltip (" + parameters2.Length + " args); entries=" + itemTooltipText.Count + ".");
				return true;
			}

			private static MethodBase TargetMethod()
			{
				return target;
			}

			[HarmonyPostfix]
			private static void Postfix(object[] __args, ref string __result)
			{
				AfterItemTooltip(__args, ref __result);
			}
		}

		[HarmonyPatch]
		private static class DeathlinkWindowPatch
		{
			private static readonly List<MethodBase> targets = new List<MethodBase>();

			private static bool Prepare(MethodBase original)
			{
				if (original != null)
				{
					return true;
				}
				targets.Clear();
				Plugin instance = Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				DeathlinkRestyle deathlinkRestyle = DeathlinkRestyle.TryCreate(((BaseUnityPlugin)instance).Logger);
				if (deathlinkRestyle == null)
				{
					return false;
				}
				MethodInfo methodInfo = AccessTools.Method(deathlinkRestyle.UiType, "CreateStaticUIObjects", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(deathlinkRestyle.UiType, "SetChoiceList", (Type[])null, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(deathlinkRestyle.UiType, "Show", (Type[])null, (Type[])null);
				MethodInfo methodInfo4 = AccessTools.Method(deathlinkRestyle.UiType, "ShowForChange", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null || methodInfo3 == null)
				{
					Warn("Deathlink window: DeathChoiceUI.CreateStaticUIObjects/SetChoiceList/Show not found in this Deathlink version; its window is left as it is.");
					return false;
				}
				targets.Add(methodInfo);
				targets.Add(methodInfo2);
				targets.Add(methodInfo3);
				if (methodInfo4 != null)
				{
					targets.Add(methodInfo4);
				}
				instance.deathlinkRestyle = deathlinkRestyle;
				Log("Deathlink window: hooked DeathChoiceUI (SeneaL layout " + (instance.deathlinkWindowEnabled.Value ? "on" : "off") + ").");
				return true;
			}

			private static IEnumerable<MethodBase> TargetMethods()
			{
				return targets;
			}

			[HarmonyPostfix]
			private static void Postfix()
			{
				AfterDeathlinkWindowChange();
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
		private static class PasswordPatch
		{
			private sealed class Applied
			{
				public string Previous;
			}

			[HarmonyPrefix]
			private static void Prefix(out Applied __state)
			{
				__state = null;
				if (!PendingPasswordActive)
				{
					return;
				}
				if ((Object)(object)Instance == (Object)null || Instance.pluginEnabled == null || !Instance.pluginEnabled.Value || DateTime.UtcNow >= pendingPasswordExpiryUtc || !Instance.PendingPasswordMatchesConfiguration())
				{
					ClearPendingPassword();
					return;
				}
				try
				{
					if (!TryGetServerPassword(out var value) || !TrySetServerPassword(PendingPassword))
					{
						Plugin instance = Instance;
						if (instance != null)
						{
							((BaseUnityPlugin)instance).Logger.LogWarning((object)"Quick Connect: Valheim ServerPassword property unavailable; manual entry may be required.");
						}
						return;
					}
					__state = new Applied
					{
						Previous = value
					};
					Plugin instance2 = Instance;
					if (instance2 != null)
					{
						((BaseUnityPlugin)instance2).Logger.LogInfo((object)"Quick Connect: password handed to Valheim's native handshake (value not logged).");
					}
				}
				catch (Exception ex)
				{
					Plugin instance3 = Instance;
					if (instance3 != null)
					{
						((BaseUnityPlugin)instance3).Logger.LogWarning((object)("Automatic password entry failed: " + ex.Message));
					}
				}
				finally
				{
					ClearPendingPassword();
				}
			}

			[HarmonyFinalizer]
			private static Exception Finalizer(Exception __exception, Applied __state)
			{
				if (__state != null)
				{
					try
					{
						TrySetServerPassword(__state.Previous);
						Plugin instance = Instance;
						if (instance != null)
						{
							((BaseUnityPlugin)instance).Logger.LogInfo((object)"Quick Connect: handed-off password cleared after the handshake.");
						}
					}
					catch (Exception ex)
					{
						Plugin instance2 = Instance;
						if (instance2 != null)
						{
							((BaseUnityPlugin)instance2).Logger.LogWarning((object)("Quick Connect: could not clear handed-off password: " + ex.Message));
						}
					}
				}
				return __exception;
			}
		}

		private sealed class QueryCompletion
		{
			public readonly int Generation;

			public readonly int RequestId;

			public readonly string Target;

			public readonly QuerySnapshot Snapshot;

			public QueryCompletion(int generation, int requestId, string target, QuerySnapshot snapshot)
			{
				Generation = generation;
				RequestId = requestId;
				Target = target;
				Snapshot = snapshot;
			}
		}

		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 Button localButton;

		private Button characterPrimaryButton;

		private Button characterNewButton;

		private Button characterRemoveButton;

		private ButtonClickedEvent savedStartClick;

		private ButtonClickedEvent savedNewClick;

		private ButtonClickedEvent savedRemoveClick;

		private ButtonClickedEvent installedStartClick;

		private ButtonClickedEvent installedNewClick;

		private ButtonClickedEvent installedRemoveClick;

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

		private readonly Dictionary<Button, ButtonTextCache> buttonTextCache = new Dictionary<Button, ButtonTextCache>();

		private bool nativeSlotActionsMapped;

		private const string EnterRowIdentity = "\u0001ValhallaEnterRow";

		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 uiNextSearch;

		private float nextLayoutDiagnostic;

		private int lastCharacterCanvasFrame = -1;

		private QuerySnapshot cachedEnterSnapshot;

		private string cachedEnterTitle;

		private string cachedEnterLabel;

		private bool cachedEnterShowsPlayers;

		private bool cachedEnterBelowTitle;

		private float nextVisibleLabelProbe;

		private int visibleLabelProbeCount;

		private TMP_Text activeVisibleStart;

		private Text activeLegacyStart;

		private float nextCharacterLookupLog;

		private readonly Dictionary<MonoBehaviour, bool> suspendedLocalizations = new Dictionary<MonoBehaviour, bool>();

		private ConfigEntry<bool> deathlinkWindowEnabled;

		private ConfigEntry<float> deathlinkTextScale;

		private DeathlinkRestyle deathlinkRestyle;

		private ConfigEntry<bool> tooltipsEnabled;

		private ConfigEntry<TooltipLinesMode> tooltipsMultipleLines;

		private static Dictionary<string, List<string>> itemTooltipLines = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		private static readonly TooltipRotation tooltipRotation = new TooltipRotation();

		private static readonly Random tooltipRandom = new Random();

		private ConfigEntry<string> tooltipsFile;

		private ConfigEntry<string> tooltipsColor;

		private static Dictionary<string, string> itemTooltipText = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static int tooltipAppendingArg = -1;

		private string loadedTooltipPath;

		private DateTime loadedTooltipUtc = DateTime.MinValue;

		private long loadedTooltipLength = -1L;

		private string tooltipColorValue = "#E8C97A";

		private ConfigEntry<bool> captionsEnabled;

		private ConfigEntry<string> captionsFile;

		private ConfigEntry<string> captionColor;

		private ConfigEntry<CaptionCorner> captionPosition;

		private ConfigEntry<int> captionFontSize;

		private Dictionary<string, string> loadingCaptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private string loadedCaptionsPath;

		private DateTime loadedCaptionsUtc = DateTime.MinValue;

		private long loadedCaptionsLength = -1L;

		private TextMeshProUGUI captionText;

		private bool captionErrorLogged;

		private bool captionOutlineApplied;

		private Material captionMaterial;

		private ConfigEntry<bool> loadingEnabled;

		private ConfigEntry<bool> loadingHideSpinner;

		private ConfigEntry<bool> loadingHideShading;

		private ConfigEntry<string> loadingImageFolder;

		private ConfigEntry<string> loadingTipsFile;

		private ConfigEntry<string> loadingLogoFile;

		private ConfigEntry<ImageOrderMode> loadingImageOrder;

		private ConfigEntry<TipsModeSetting> loadingTipsMode;

		private ConfigEntry<ImageFitMode> loadingImageFit;

		private ConfigEntry<LoadingLogoMode> loadingLogoMode;

		private ConfigEntry<float> loadingSlideshowSeconds;

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

		private readonly Dictionary<LoadingContext, List<string>> loadingContextImages = new Dictionary<LoadingContext, List<string>>
		{
			{
				LoadingContext.Join,
				new List<string>()
			},
			{
				LoadingContext.Respawn,
				new List<string>()
			},
			{
				LoadingContext.Teleport,
				new List<string>()
			}
		};

		private readonly Dictionary<LoadingContext, int> loadingSequentialIndex = new Dictionary<LoadingContext, int>();

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

		private List<string> customLoadingTips = new List<string>();

		private string loadedTipsPath;

		private DateTime loadedTipsUtc = DateTime.MinValue;

		private long loadedTipsLength = -1L;

		private string lastLoadingFolderReport;

		private readonly Random loadingRandom = new Random();

		private Texture2D loadingTexture;

		private Sprite loadingSprite;

		private string loadingImagePath;

		private Image loadingDisplayTarget;

		private IList<string> loadingCurrentList;

		private LoadingContext loadingCurrentContext;

		private float loadingShownAt;

		private Hud boundHud;

		private Image boundLoadingImage;

		private Sprite savedLoadingSprite;

		private bool savedLoadingPreserveAspect;

		private Color savedLoadingImageColor = Color.white;

		private bool loadingImageColorChanged;

		private List<string> savedLoadingTips;

		private AspectRatioFitter loadingFitter;

		private bool loadingFitterAdded;

		private AspectMode savedFitterMode;

		private float savedFitterRatio;

		private bool loadingLayoutLogged;

		private bool loadingHierarchyLogged;

		private Image teleportImage;

		private AspectRatioFitter teleportFitter;

		private bool teleportScreenActive;

		private Image loadingLogoImage;

		private Sprite savedLoadingLogoSprite;

		private bool savedLoadingLogoAspect;

		private bool savedLoadingLogoEnabled;

		private bool loadingLogoChanged;

		private bool loadingLogoSearched;

		private Texture2D loadingLogoTexture;

		private Sprite loadingLogoSprite;

		private string loadedLoadingLogoPath;

		private DateTime loadedLoadingLogoUtc = DateTime.MinValue;

		private CanvasGroup spinnerGroup;

		private bool spinnerGroupAdded;

		private readonly List<Graphic> hiddenShading = new List<Graphic>();

		private bool shadingLogged;

		private static FieldInfo firstSpawnField;

		private Dictionary<string, LoadingSets.ShuffleDeck> loadingDecks;

		private const string DeckFileName = "loading_shuffle.txt";

		private bool menuBound;

		private bool menuLookHideLogged;

		private bool supportOverflowLogged;

		private bool menuArrayMismatchLogged;

		private string lastHiddenRowsLogged;

		private bool discordDuplicateLogged;

		private bool firstMenuModelInitialized;

		private bool startupIntroBlocked;

		private string hiddenRowsText;

		private List<MenuRowFilter.Rule> hiddenRowsParsed;

		private ConfigEntry<bool> menuAudioEnabled;

		private ConfigEntry<int> menuMasterVolume;

		private ConfigEntry<int> menuMusicLevel;

		private ConfigEntry<int> menuSfxVolume;

		private bool menuAudioApplied;

		private bool menuAudioLogged;

		private int menuAudioFrames;

		private float nextMenuAudioCheck;

		private ConfigEntry<bool> menuMusicEnabled;

		private ConfigEntry<string> menuMusicFolder;

		private ConfigEntry<ImageOrderMode> menuMusicOrder;

		private ConfigEntry<float> menuMusicStartSeconds;

		private ConfigEntry<int> menuMusicVolume;

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

		private Dictionary<string, float> menuMusicStarts = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private string loadedMusicTimesPath;

		private DateTime loadedMusicTimesUtc = DateTime.MinValue;

		private long loadedMusicTimesLength = -1L;

		private string lastMusicFolderReport;

		private int menuMusicSequential = -1;

		private AudioClip nextMusicClip;

		private AudioClip playingMusicClip;

		private AudioClip previousMusicClip;

		private string nextMusicPath;

		private string playingMusicPath;

		private bool musicLoading;

		private bool musicApplyPending;

		private bool musicStartApplied;

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

		private NamedMusic menuMusicEntry;

		private AudioClip[] savedMenuClips;

		private bool savedMenuLoop;

		private bool savedMenuResume;

		private float savedMenuVolume;

		private static MethodInfo getAudioClipMethod;

		private static MethodInfo getContentMethod;

		private static PropertyInfo downloadHandlerProp;

		private static PropertyInfo errorProp;

		private static MethodInfo sendRequestMethod;

		private static FieldRef<MusicMan, AudioSource> musicSourceRef;

		private static bool musicSourceResolved;

		private static readonly Type[] PatchClasses = new Type[9]
		{
			typeof(PasswordPatch),
			typeof(SeneaLMainMenuScanPatch),
			typeof(StartupIntroPatch),
			typeof(CinematicsPlayPatch),
			typeof(CharacterSelectionShownPatch),
			typeof(LoadingScreenSetupPatch),
			typeof(ItemTooltipPatch),
			typeof(DeathlinkWindowPatch),
			typeof(MenuMusicPatch)
		};

		public const string PluginGuid = "valhalla.menu.companion";

		public const string PluginName = "Valhalla Menu Companion";

		public const string PluginVersion = "1.11.0";

		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> discordEnabled;

		private ConfigEntry<bool> replaceOtherDiscordRows;

		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> hiddenMenuRows;

		private ConfigEntry<bool> quickConnectEnabled;

		private ConfigEntry<bool> rulesFeatureEnabled;

		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 ConfigEntry<float> rulesNativeDialogScale;

		private string dataFolder;

		private bool previousEnabledSetting;

		private float nextReload;

		private Harmony harmony;

		private EventInfo canvasRenderEvent;

		private Delegate canvasRenderHandler;

		internal static Plugin Instance;

		private bool startSceneActive;

		internal static string PendingPassword;

		internal static bool PendingPasswordActive;

		private static DateTime pendingPasswordExpiryUtc = DateTime.MinValue;

		private static string pendingPasswordHost;

		private static int pendingPasswordPort;

		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 Dialog activeRulesDialog;

		private bool activeRulesDialogWasOpen;

		private bool rulesOpen;

		private bool pendingJoin;

		private bool readOnlyRules;

		private string displayedAgreementKey;

		private string displayedRulesDigest;

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

		private int rulesPageIndex;

		private bool rulesPageTransitioning;

		private string rulesText = string.Empty;

		private string loadedRulesPath;

		private DateTime loadedRulesUtc = DateTime.MinValue;

		private long loadedRulesLength = -1L;

		private bool rulesAvailable;

		private bool missingRulesLogged;

		private bool joinQueuedAfterRules;

		private string queuedAgreementKey;

		private readonly ConcurrentQueue<QueryCompletion> completedQueryResults = new ConcurrentQueue<QueryCompletion>();

		private int statusRequestSequence;

		private int lastStartedStatusRequestId;

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

		private volatile string lastQueryTarget = "";

		private bool statusEndpointCacheReady;

		private string cachedStatusHostInput;

		private string cachedStatusAddress;

		private string cachedStatusTarget;

		private int cachedStatusGamePort;

		private int cachedStatusQueryPortOverride;

		private int cachedStatusQueryPort;

		private bool cachedStatusValid;

		private int queryWorkersActive;

		private int queryGeneration;

		private volatile bool shuttingDown;

		private float nextQueryTime;

		private float checkingSinceUnscaled = -1f;

		private float lastOnlineResponseUnscaled = -1f;

		private bool DeathlinkWindowOn
		{
			get
			{
				if (pluginEnabled != null && pluginEnabled.Value && deathlinkWindowEnabled != null)
				{
					return deathlinkWindowEnabled.Value;
				}
				return false;
			}
		}

		private bool MenuMusicOn
		{
			get
			{
				if (pluginEnabled.Value)
				{
					return menuMusicEnabled.Value;
				}
				return false;
			}
		}

		private bool RulesFeatureOn
		{
			get
			{
				if (rulesFeatureEnabled != null)
				{
					return rulesFeatureEnabled.Value;
				}
				return false;
			}
		}

		private bool RulesRequired
		{
			get
			{
				if (RulesFeatureOn)
				{
					return rulesEnabled.Value;
				}
				return false;
			}
		}

		private bool RulesRowShown
		{
			get
			{
				if (RulesFeatureOn)
				{
					return showRulesButton.Value;
				}
				return false;
			}
		}

		private bool QuickConnectOn
		{
			get
			{
				if (quickConnectEnabled != null)
				{
					return quickConnectEnabled.Value;
				}
				return false;
			}
		}

		private void ReloadLogo()
		{
			if (!logoEnabled.Value)
			{
				RestoreNativeTitleLogo();
				return;
			}
			try
			{
				string text = Path.Combine(dataFolder, Path.GetFileName(logoFilename.Value ?? "logo.png"));
				if (!File.Exists(text))
				{
					if (!missingLogoFileLogged)
					{
						missingLogoFileLogged = true;
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: no logo at " + text + "; Valheim's title logo is kept."));
					}
					if ((Object)(object)logo != (Object)null)
					{
						RestoreNativeTitleLogo();
						if ((Object)(object)logoSprite != (Object)null)
						{
							Object.Destroy((Object)(object)logoSprite);
						}
						Object.Destroy((Object)(object)logo);
						logoSprite = null;
						logo = null;
						loadedLogoPath = null;
					}
					return;
				}
				missingLogoFileLogged = false;
				FileInfo fileInfo = new FileInfo(text);
				if (loadedLogoPath == text && loadedLogoUtc == fileInfo.LastWriteTimeUtc && loadedLogoLength == fileInfo.Length)
				{
					return;
				}
				loadedLogoPath = text;
				loadedLogoUtc = fileInfo.LastWriteTimeUtc;
				loadedLogoLength = fileInfo.Length;
				string error;
				Texture2D val = ImageLoader.TryLoad(text, out error);
				if ((Object)(object)val == (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Branding: logo skipped (" + error + "); use a transparent RGBA PNG."));
					return;
				}
				Sprite val2 = ImageLoader.MakeSprite(val, "ValhallaCompanion_TitleLogo");
				if ((Object)(object)val2 == (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Branding: could not create the logo sprite.");
					Object.Destroy((Object)(object)val);
					return;
				}
				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 (" + ((Texture)val).width + "x" + ((Texture)val).height + ")."));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Logo file read failed: " + ex.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_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0419: Unknown result type (might be due to invalid IL or missing references)
			//IL_041e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0432: 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)(object)transform || (Object)(object)cachedTitleLogoRoot == (Object)null || !((Component)cachedTitleLogoRoot).gameObject.activeInHierarchy)
				{
					cachedTitleMenu = transform;
					cachedTitleLogoRoot = FindTitleLogoRoot(transform);
				}
				Transform val = cachedTitleLogoRoot;
				if (((Object)(object)nativeLogoImage != (Object)null && ((Object)(object)val == (Object)null || !((Component)nativeLogoImage).transform.IsChildOf(val))) || ((Object)(object)nativeLogoRaw != (Object)null && ((Object)(object)val == (Object)null || !((Component)nativeLogoRaw).transform.IsChildOf(val))))
				{
					RestoreNativeTitleLogo();
				}
				LogLogoCandidatesOnce(val);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				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(val, (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(val, (Graphic)(object)nativeLogoRaw);
					return;
				}
				RestoreNativeTitleLogo();
				Image val2 = null;
				RawImage val3 = null;
				int num = -1;
				Image[] componentsInChildren = ((Component)val).GetComponentsInChildren<Image>(true);
				foreach (Image val4 in componentsInChildren)
				{
					if (!((Object)(object)val4 == (Object)null) && ((Component)val4).gameObject.activeInHierarchy && ((Behaviour)val4).enabled && !((Object)(object)val4.sprite == (Object)null) && LooksLikeLogoGraphic((Graphic)(object)val4) && !((Object)(object)((Component)val4).GetComponentInParent<Button>() != (Object)null))
					{
						int num2 = 50;
						if (((Object)val4).name.Equals("LOGO", StringComparison.OrdinalIgnoreCase))
						{
							num2 += 40;
						}
						if (((Object)val4).name.Equals("DeepNorthLogo", StringComparison.OrdinalIgnoreCase))
						{
							num2 += 30;
						}
						if (((Object)val4).name.Equals("AshlandsLogo", StringComparison.OrdinalIgnoreCase))
						{
							num2 += 20;
						}
						if (num2 > num)
						{
							num = num2;
							val2 = val4;
							val3 = null;
						}
					}
				}
				if ((Object)(object)val2 == (Object)null)
				{
					RawImage[] componentsInChildren2 = ((Component)val).GetComponentsInChildren<RawImage>(true);
					foreach (RawImage val5 in componentsInChildren2)
					{
						if (!((Object)(object)val5 == (Object)null) && ((Component)val5).gameObject.activeInHierarchy && ((Behaviour)val5).enabled && !((Object)(object)val5.texture == (Object)null) && LooksLikeLogoGraphic((Graphic)(object)val5) && !((Object)(object)((Component)val5).GetComponentInParent<Button>() != (Object)null))
						{
							val3 = val5;
							break;
						}
					}
				}
				if ((Object)(object)val2 != (Object)null)
				{
					nativeLogoImage = val2;
					savedTitleSprite = val2.sprite;
					savedTitlePreserveAspect = val2.preserveAspect;
					savedTitleImageColor = ((Graphic)val2).color;
					val2.sprite = logoSprite;
					val2.preserveAspect = true;
					((Graphic)val2).color = Color.white;
					SuppressOtherTitleLogoGraphics(val, (Graphic)(object)val2);
					missingTitleLogoLogged = false;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: replaced Valheim title logo Image '" + ((Object)val2).name + "' and suppressed competing title-logo graphics."));
				}
				else if ((Object)(object)val3 != (Object)null)
				{
					nativeLogoRaw = val3;
					savedTitleTexture = val3.texture;
					savedTitleRawColor = ((Graphic)val3).color;
					val3.texture = (Texture)(object)logo;
					((Graphic)val3).color = Color.white;
					SuppressOtherTitleLogoGraphics(val, (Graphic)(object)val3);
					missingTitleLogoLogged = false;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Branding: replaced Valheim title RawImage '" + ((Object)val3).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_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: 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)
			if ((Object)(object)nativeLogoImage != (Object)null && (Object)(object)nativeLogoImage.sprite == (Object)(object)logoSprite)
			{
				nativeLogoImage.sprite = savedTitleSprite;
				if (nativeLogoImage.preserveAspect)
				{
					nativeLogoImage.preserveAspect = savedTitlePreserveAspect;
				}
				if (((Graphic)nativeLogoImage).color == Color.white)
				{
					((Graphic)nativeLogoImage).color = savedTitleImageColor;
				}
			}
			if ((Object)(object)nativeLogoRaw != (Object)null && (Object)(object)nativeLogoRaw.texture == (Object)(object)logo)
			{
				nativeLogoRaw.texture = savedTitleTexture;
				if (((Graphic)nativeLogoRaw).color == Color.white)
				{
					((Graphic)nativeLogoRaw).color = savedTitleRawColor;
				}
			}
			foreach (KeyValuePair<Graphic, bool> suppressedLogoGraphic in suppressedLogoGraphics)
			{
				if ((Object)(object)suppressedLogoGraphic.Key != (Object)null && suppressedLogoGraphic.Value && !((Behaviour)suppressedLogoGraphic.Key).enabled)
				{
					((Behaviour)suppressedLogoGraphic.Key).enabled = true;
				}
			}
			suppressedLogoGraphics.Clear();
			nativeLogoImage = null;
			nativeLogoRaw = null;
			savedTitleSprite = null;
			savedTitleTexture = null;
		}

		private void LateUpdate()
		{
			if (!pluginEnabled.Value || !OnMenu())
			{
				return;
			}
			try
			{
				if ((Object)(object)localButton != (Object)null && ((Component)localButton).gameObject.activeInHierarchy && (Object)(object)characterPrimaryButton != (Object)null && ((Component)characterPrimaryButton).gameObject.activeInHierarchy)
				{
					SetButtonLabel(characterPrimaryButton, buttonTitle.Value);
					if (nativeSlotActionsMapped)
					{
						SetButtonLabel(characterNewButton, "PLAY LOCAL");
						SetButtonLabel(characterRemoveButton, "NEW");
						SetButtonLabel(localButton, "REMOVE");
						PatchVisibleCharacterSlots();
					}
					else
					{
						SetButtonLabel(localButton, "PLAY LOCAL");
						PatchVisibleCharacterStart();
					}
				}
				if (verboseDiagnostics.Value && Time.unscaledTime > nextLayoutDiagnostic)
				{
					nextLayoutDiagnostic = Time.unscaledTime + 8f;
					LogLayoutSnapshot();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Late menu sync failed: " + ex.Message));
			}
		}

		private void PatchVisibleCharacterSlots(bool forceRescan = false)
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: 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 = Object.FindObjectsByType<TMP_Text>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (TMP_Text val in array)
			{
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && IsCharacterProbeText(val.text))
				{
					list.Add(Tuple.Create<Component, float, string>((Component)(object)val, val.transform.position.y, (val.text ?? "").Trim()));
				}
			}
			Text[] array2 = Object.FindObjectsByType<Text>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (Text val2 in array2)
			{
				if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy && IsCharacterProbeText(val2.text))
				{
					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 = NormalizeCharacterProbeCaption(item3.Item3);
						if (text.Equals("START", StringComparison.OrdinalIgnoreCase) || text.Equals((buttonTitle.Value ?? string.Empty).Trim(), StringComparison.OrdinalIgnoreCase))
						{
							flag = true;
							break;
						}
					}
					if (!flag)
					{
						continue;
					}
					bool flag2 = true;
					for (int num7 = 1; num7 < 6; num7++)
					{
						float num8 = list2[num6 + num7 - 1][0].Item2 - list2[num6 + num7][0].Item2;
						if (num8 < 18f || num8 > 120f)
						{
							flag2 = false;
							break;
						}
					}
					if (flag2)
					{
						for (int num9 = 0; num9 < 6; num9++)
						{
							list3.Add(num6 + num9);
						}
						break;
					}
				}
			}
			if (list3.Count == 6)
			{
				string[] array3 = new string[6] { "\u0001ValhallaEnterRow", "PLAY LOCAL", "NEW", "REMOVE", "MANAGE SAVES", "BACK" };
				Dictionary<Component, string> dictionary = new Dictionary<Component, string>();
				for (int num10 = 0; num10 < 6; num10++)
				{
					float item = list2[list3[num10]][0].Item2;
					stableCharacterCenters[num10] = item;
					foreach (Tuple<Component, float, string> item4 in list2[list3[num10]])
					{
						dictionary[item4.Item1] = array3[num10];
					}
				}
				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()
		{
			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 text = ((renderedSlotLabel.Value == "\u0001ValhallaEnterRow") ? FormatEnterLabel() : renderedSlotLabel.Value);
					if (val.text != text)
					{
						val.text = text;
					}
					continue;
				}
				Component key2 = renderedSlotLabel.Key;
				Text val2 = (Text)(object)((key2 is Text) ? key2 : null);
				if ((Object)(object)val2 != (Object)null)
				{
					string text2 = ((renderedSlotLabel.Value == "\u0001ValhallaEnterRow") ? FormatEnterLabel() : renderedSlotLabel.Value);
					if (val2.text != text2)
					{
						val2.text = text2;
					}
				}
			}
		}

		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 (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 && lastCharacterCanvasFrame != Time.frameCount)
				{
					lastCharacterCanvasFrame = Time.frameCount;
					PatchVisibleCharacterSlots();
				}
			}
		}

		private void PatchVisibleCharacterStart()
		{
			string text = FormatEnterLabel();
			if ((Object)(object)activeVisibleStart != (Object)null && ((Component)activeVisibleStart).gameObject.activeInHierarchy && activeVisibleStart.text != text)
			{
				activeVisibleStart.text = text;
			}
			if ((Object)(object)activeLegacyStart != (Object)null && ((Component)activeLegacyStart).gameObject.activeInHierarchy && activeLegacyStart.text != text)
			{
				activeLegacyStart.text = text;
			}
			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 = Object.FindObjectsByType<TMP_Text>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (TMP_Text val in array)
			{
				if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
				{
					continue;
				}
				string text2 = (val.text ?? string.Empty).Trim();
				if (text2.Equals("START", StringComparison.OrdinalIgnoreCase))
				{
					num++;
					if ((Object)(object)activeVisibleStart == (Object)null || !((Component)activeVisibleStart).gameObject.activeInHierarchy)
					{
						activeVisibleStart = val;
					}
				}
				if (flag && IsCharacterProbeText(text2) && list.Count < 22)
				{
					list.Add(DescribeVisibleLabel(val.transform, text2));
				}
			}
			Text[] array2 = Object.FindObjectsByType<Text>((FindObjectsInactive)0, (FindObjectsSortMode)0);
			foreach (Text val2 in array2)
			{
				if ((Object)(object)val2 == (Object)null || !((Component)val2).gameObject.activeInHierarchy)
				{
					continue;
				}
				string text3 = (val2.text ?? string.Empty).Trim();
				if (text3.Equals("START", StringComparison.OrdinalIgnoreCase))
				{
					num++;
					if ((Object)(object)activeLegacyStart == (Object)null || !((Component)activeLegacyStart).gameObject.activeInHierarchy)
					{
						activeLegacyStart = val2;
					}
				}
				if (flag && IsCharacterProbeText(text3) && list.Count < 22)
				{
					list.Add("LEGACY " + DescribeVisibleLabel(((Component)val2).transform, text3));
				}
			}
			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 != text)
			{
				activeVisibleStart.text = text;
			}
			if ((Object)(object)activeLegacyStart != (Object)null && ((Component)activeLegacyStart).gameObject.activeInHierarchy && activeLegacyStart.text != text)
			{
				activeLegacyStart.text = text;
			}
		}

		private static string NormalizeCharacterProbeCaption(string raw)
		{
			string text = (raw ?? string.Empty).Trim();
			int num = text.IndexOf('<');
			if (num >= 0)
			{
				text = text.Substring(0, num);
			}
			int num2 = text.IndexOf('\n');
			if (num2 >= 0)
			{
				text = text.Substring(0, num2);
			}
			return text.Trim();
		}

		private bool IsCharacterProbeText(string raw)
		{
			string text = NormalizeCharacterProbeCaption(raw);
			if (!text.Equals("START", StringComparison.OrdinalIgnoreCase) && (string.IsNullOrWhiteSpace(buttonTitle.Value) || !text.Equals(buttonTitle.Value.Trim(), StringComparison.OrdinalIgnoreCase)) && !text.Equals("ENTER VALHALLA", StringComparison.OrdinalIgnoreCase) && !text.Equals("PLAY LOCAL", StringComparison.OrdinalIgnoreCase) && !text.Equals("NEW", StringComparison.OrdinalIgnoreCase) && !text.Equals("REMOVE", StringComparison.OrdinalIgnoreCase) && !text.Equals("MANAGE SAVES", StringComparison.OrdinalIgnoreCase))
			{
				return text.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()));
			}
		}

		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 CaptureOriginalCharacterLabels(Button button)
		{
			if ((Object)(object)button == (Object)null)
			{
				return;
			}
			TMP_Text[] componentsInChildren = ((Component)button).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && !originalCharacterLabels.ContainsKey((Component)(object)val))
				{
					originalCharacterLabels.Add((Component)(object)val, val.text);
				}
			}
			Text[] componentsInChildren2 = ((Component)button).GetComponentsInChildren<Text>(true);
			foreach (Text val2 in componentsInChildren2)
			{
				if ((Object)(object)val2 != (Object)null && !originalCharacterLabels.ContainsKey((Component)(object)val2))
				{
					originalCharacterLabels.Add((Component)(object)val2, val2.text);
				}
			}
		}

		private void RestoreOriginalCharacterLabels(Button button, string fallback)
		{
			if ((Object)(object)button == (Object)null)
			{
				return;
			}
			bool flag = false;
			foreach (KeyValuePair<Component, string> originalCharacterLabel in originalCharacterLabels)
			{
				if ((Object)(object)originalCharacterLabel.Key == (Object)null || !originalCharacterLabel.Key.transform.IsChildOf(((Component)button).transform))
				{
					continue;
				}
				Component key = originalCharacterLabel.Key;
				TMP_Text val = (TMP_Text)(object)((key is TMP_Text) ? key : null);
				if ((Object)(object)val != (Object)null)
				{
					if (val.text != originalCharacterLabel.Value)
					{
						val.text = originalCharacterLabel.Value;
					}
					flag = true;
					continue;
				}
				Component key2 = originalCharacterLabel.Key;
				Text val2 = (Text)(object)((key2 is Text) ? key2 : null);
				if ((Object)(object)val2 != (Object)null)
				{
					if (val2.text != originalCharacterLabel.Value)
					{
						val2.text = originalCharacterLabel.Value;
					}
					flag = true;
				}
			}
			if (!flag)
			{
				SetButtonLabel(button, fallback);
			}
		}

		private void SetButtonLabel(Button b, string label)
		{
			if ((Object)(object)b == (Object)null)
			{
				return;
			}
			if (!buttonTextCache.TryGetValue(b, out var value) || Time.unscaledTime >= value.RefreshAfter || (value.Tmp.Length != 0 && ((Object)(object)value.Tmp[0] == (Object)null || !value.Tmp[0].transform.IsChildOf(((Component)b).transform))) || (value.Legacy.Length != 0 && ((Object)(object)value.Legacy[0] == (Object)null || !((Component)value.Legacy[0]).transform.IsChildOf(((Component)b).transform))))
			{
				value = new ButtonTextCache
				{
					Tmp = ((Component)b).GetComponentsInChildren<TMP_Text>(true),
					Legacy = ((Component)b).GetComponentsInChildren<Text>(true),
					RefreshAfter = Time.unscaledTime + 2f
				};
				buttonTextCache[b] = value;
			}
			for (int i = 0; i < value.Tmp.Length; i++)
			{
				TMP_Text val = value.Tmp[i];
				if (!((Object)(object)val == (Object)null) && val.transform.IsChildOf(((Component)b).transform))
				{
					string text = ((i == 0) ? label : string.Empty);
					if (val.text != text)
					{
						val.text = text;
					}
				}
			}
			for (int j = 0; j < value.Legacy.Length; j++)
			{
				Text val2 = value.Legacy[j];
				if (!((Object)(object)val2 == (Object)null) && ((Component)val2).transform.IsChildOf(((Component)b).transform))
				{
					string text2 = ((j == 0) ? label : string.Empty);
					if (val2.text != text2)
					{
						val2.text = text2;
					}
				}
			}
		}

		private static void AfterCharacterSelectionShown()
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.pluginEnabled.Value || !instance.QuickConnectOn)
			{
				return;
			}
			instance.activeVisibleStart = null;
			instance.activeLegacyStart = null;
			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 void OpenLocalWorldSelection()
		{
			if (!shuttingDown && pluginEnabled != null && pluginEnabled.Value)
			{
				ClearPendingPassword();
				if (savedStartClick != null)
				{
					((UnityEvent)savedStartClick).Invoke();
				}
			}
		}

		private void AttachToCharacterMenu()
		{
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Expected O, but got Unknown
			//IL_034a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Expected O, but got Unknown
			//IL_047a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0484: Expected O, but got Unknown
			//IL_0496: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a0: Expected O, but got Unknown
			//IL_03c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cd: Expected O, but got Unknown
			//IL_03db: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e5: 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;
			}
			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;
			}
			if ((Object)(object)characterPrimaryButton != (Object)null || (Object)(object)localButton != (Object)null)
			{
				RestoreCharacterBindings();
			}
			if ((Object)(object)((Component)val).GetComponent<RectTransform>() == (Object)null)
			{
				return;
			}
			characterPrimaryButton = val;
			savedStartClick = val.onClick;
			CaptureOriginalCharacterLabels(val);
			Transform parent = ((Component)val).transform.parent;
			object? obj = AccessTools.Field(typeof(FejdStartup), "m_csNewButton")?.GetValue(instance);
			Button val2 = (Button)((obj is Button) ? obj : null);
			object? obj2 = AccessTools.Field(typeof(FejdStartup), "m_csRemoveButton")?.GetValue(instance);
			Button val3 = (Button)((obj2 is Button) ? obj2 : null);
			if ((Object)(object)val2 == (Object)null || !((Component)val2).gameObject.activeInHierarchy || (Object)(object)val2 == (Object)(object)val)
			{
				Button[] componentsInChildren = ((Component)parent).GetComponentsInChildren<Button>(true);
				foreach (Button val4 in componentsInChildren)
				{
					if ((Object)(object)val4 != (Object)(object)val && ((Component)val4).gameObject.activeInHierarchy && HasText(val4, "NEW"))
					{
						val2 = val4;
						break;
					}
				}
			}
			if ((Object)(object)val3 == (Object)null || !((Component)val3).gameObject.activeInHierarchy || (Object)(object)val3 == (Object)(object)val)
			{
				Button[] componentsInChildren = ((Component)parent).GetComponentsInChildren<Button>(true);
				foreach (Button val5 in componentsInChildren)
				{
					if ((Object)(object)val5 != (Object)(object)val && ((Component)val5).gameObject.activeInHierarchy && HasText(val5, "REMOVE"))
					{
						val3 = val5;
						break;
					}
				}
			}
			if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)val)
			{
				CaptureOriginalCharacterLabels(val2);
			}
			if ((Object)(object)val3 != (Object)null && (Object)(object)val3 != (Object)(object)val)
			{
				CaptureOriginalCharacterLabels(val3);
			}
			GameObject val6 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent);
			localButton = (((Object)(object)val6 == (Object)null) ? null : val6.GetComponent<Button>());
			if ((Object)(object)localButton == (Object)null)
			{
				if ((Object)(object)val6 != (Object)null)
				{
					Object.Destroy((Object)(object)val6);
				}
				RestoreCharacterBindings();
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Character menu: could not clone the Play Local button; original controls retained.");
				return;
			}
			try
			{
				((Object)localButton).name = "Valhalla_ExtraCharacterSlot";
				RemoveLocalization(localButton);
				((Component)localButton).transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex() + 1);
				val.onClick = new ButtonClickedEvent();
				installedStartClick = val.onClick;
				((UnityEvent)val.onClick).AddListener(new UnityAction(OnEnterValhalla));
				SetButtonLabel(val, buttonTitle.Value);
				nativeSlotActionsMapped = (Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null && (Object)(object)val2 != (Object)(object)val3;
				if (nativeSlotActionsMapped)
				{
					characterNewButton = val2;
					characterRemoveButton = val3;
					savedNewClick = val2.onClick;
					savedRemoveClick = val3.onClick;
					val2.onClick = new ButtonClickedEvent();
					((UnityEvent)val2.onClick).AddListener(new UnityAction(OpenLocalWorldSelection));
					installedNewClick = val2.onClick;
					val3.onClick = MakeForwardingClick(savedNewClick);
					installedRemoveClick = val3.onClick;
					localButton.onClick = MakeForwardingClick(savedRemoveClick);
					RemoveLocalization(val2);
					RemoveLocalization(val3);
					SetButtonLabel(val2, "PLAY LOCAL");
					SetButtonLabel(val3, "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 = new ButtonClickedEvent();
					((UnityEvent)localButton.onClick).AddListener(new UnityAction(OpenLocalWorldSelection));
					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.");
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Character menu: bound exact m_csStartButton=" + ((Object)val).name + ", parent=" + ((Object)parent).name + ", cloned extra; actionsMapped=" + nativeSlotActionsMapped));
			}
			catch (Exception ex)
			{
				if (Time.unscaledTime >= nextCharacterLookupLog)
				{
					nextCharacterLookupLog = Time.unscaledTime + 8f;
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Character menu: mapping failed; original buttons will be restored: " + ex.Message));
				}
				try
				{
					RestoreCharacterBindings();
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Character menu: rollback failed: " + ex2.Message));
				}
			}
		}

		private static ButtonClickedEvent MakeForwardingClick(ButtonClickedEvent original)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			ButtonClickedEvent val = new ButtonClickedEvent();
			if (original != null)
			{
				((UnityEvent)val).AddListener((UnityAction)delegate
				{
					((UnityEvent)original).Invoke();
				});
			}
			return val;
		}

		private void RestoreCharacterBindings()
		{
			if ((Object)(object)characterPrimaryButton != (Object)null && savedStartClick != null && installedStartClick != null && characterPrimaryButton.onClick == installedStartClick)
			{
				characterPrimaryButton.onClick = savedStartClick;
				RestoreOriginalCharacterLabels(characterPrimaryButton, "START");
			}
			if ((Object)(object)characterNewButton != (Object)null && savedNewClick != null && installedNewClick != null && characterNewButton.onClick == installedNewClick)
			{
				characterNewButton.onClick = savedNewClick;
				RestoreOriginalCharacterLabels(characterNewButton, "NEW");
			}
			if ((Object)(object)characterRemoveButton != (Object)null && savedRemoveClick != null && installedRemoveClick != null && characterRemoveButton.onClick == installedRemoveClick)
			{
				characterRemoveButton.onClick = savedRemoveClick;
				RestoreOriginalCharacterLabels(characterRemoveButton, "REMOVE");
			}
			foreach (KeyValuePair<MonoBehaviour, bool> suspendedLocalization in suspendedLocalizations)
			{
				if ((Object)(object)suspendedLocalization.Key != (Object)null)
				{
					((Behaviour)suspendedLocalization.Key).enabled = suspendedLocalization.Value;
				}
			}
			suspendedLocalizations.Clear();
			originalCharacterLabels.Clear();
			buttonTextCache.Clear();
			if ((Object)(object)localButton != (Object)null)
			{
				((Component)localButton).gameObject.SetActive(false);
				Object.Destroy((Object)(object)((Component)localButton).gameObject);
			}
			localButton = null;
			characterPrimaryButton = null;
			characterNewButton = null;
			characterRemoveButton = null;
			savedStartClick = (savedNewClick = (savedRemoveClick = null));
			installedStartClick = (installedNewClick = (installedRemoveClick = null));
			nativeSlotActionsMapped = false;
			renderedSlotLabels.Clear();
			activeVisibleStart = null;
			activeLegacyStart = null;
			hasStableCharacterCenters = false;
			characterRefreshPending = true;
			characterRefreshTries = 0;
			lastCharacterCanvasFrame = -1;
		}

		private void RemoveLocalization(Button button)
		{
			if ((Object)(object)button == (Object)null)
			{
				return;
			}
			MonoBehaviour[] componentsInChildren = ((Component)button).GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !(((object)val).GetType().Name != "Localize"))
				{
					if (!suspendedLocalizations.ContainsKey(val))
					{
						suspendedLocalizations.Add(val, ((Behaviour)val).enabled);
					}
					((Behaviour)val).enabled = false;
				}
			}
		}

		private void BindDeathlinkWindowConfig()
		{
			deathlinkWindowEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("DeathlinkWindow", "Enabled", true, "When Deathlink is installed: give its profile selection window the SeneaL UI look and a layout where every section fits (long lists scroll). Off = Deathlink's own window. Without Deathlink this does nothing.");
			deathlinkTextScale = ((BaseUnityPlugin)this).Config.Bind<float>("DeathlinkWindow", "TextScale", 1.4f, "Size of the SeneaL text styles inside the Deathlink window (0.8-2.2). SeneaL's sizes are made for its own HUD canvas; Deathlink's window sits on a different canvas, so 1.4 matches the rest of the game at 1080p-1440p.");
		}

		private static void AfterDeathlinkWindowChange()
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || instance.deathlinkRestyle == null)
			{
				return;
			}
			try
			{
				if (instance.DeathlinkWindowOn)
				{
					instance.deathlinkRestyle.Apply(DeathlinkText.ClampScale(instance.deathlinkTextScale.Value));
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)instance).Logger.LogWarning((object)("Deathlink window: restyle failed, restoring Deathlink's layout: " + ex.Message));
				instance.RestoreDeathlinkWindow();
			}
		}

		internal bool DeathlinkWindowStillWanted()
		{
			return DeathlinkWindowOn;
		}

		private void RestoreDeathlinkWindow()
		{
			if (deathlinkRestyle != null)
			{
				deathlinkRestyle.Restore();
			}
		}

		private void BindTooltipConfig()
		{
			tooltipsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Tooltips", "Enabled", true, "Add extra text from BepInEx/config/ValhallaMenu/<File> to item tooltips. With an empty file nothing changes.");
			tooltipsFile = ((BaseUnityPlugin)this).Config.Bind<string>("Tooltips", "File", "item_tooltips.txt", "Tooltip file inside BepInEx/config/ValhallaMenu. Format: ItemPrefabName = text");
			tooltipsColor = ((BaseUnityPlugin)this).Config.Bind<string>("Tooltips", "TextColor", "#E8C97A", "Hex color (#RRGGBB) for the added text. Rich text inside an entry can override it.");
			tooltipsMultipleLines = ((BaseUnityPlugin)this).Config.Bind<TooltipLinesMode>("Tooltips", "MultipleLines", TooltipLinesMode.All, "When an item has several lines in the file. All: show every line. Random: show one line, a different one each time the item is hovered (every line once before any repeats).");
		}

		private static void AfterItemTooltip(object[] __args, ref string __result)
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || __result == null || itemTooltipText.Count == 0 || !instance.pluginEnabled.Value || !instance.tooltipsEnabled.Value)
			{
				return;
			}
			try
			{
				bool flag = default(bool);
				int num;
				if (tooltipAppendingArg >= 0 && tooltipAppendingArg < __args.Length)
				{
					object obj = __args[tooltipAppendingArg];
					if (obj is bool)
					{
						flag = (bool)obj;
						num = 1;
					}
					else
					{
						num = 0;
					}
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					return;
				}
				object obj2 = __args[0];
				ItemData val = (ItemData)((obj2 is ItemData) ? obj2 : null);
				if (val == null)
				{
					return;
				}
				string text = null;
				if ((Object)(object)val.m_dropPrefab != (Object)null && itemTooltipText.ContainsKey(((Object)val.m_dropPrefab).name))
				{
					text = ((Object)val.m_dropPrefab).name;
				}
				else if (val.m_shared != null && !string.IsNullOrEmpty(val.m_shared.m_name) && itemTooltipText.ContainsKey(val.m_shared.m_name))
				{
					text = val.m_shared.m_name;
				}
				if (text != null)
				{
					string value2;
					if (instance.tooltipsMultipleLines.Value == TooltipLinesMode.Random && itemTooltipLines.TryGetValue(text, out var value))
					{
						value2 = tooltipRotation.Pick(text, value, Time.unscaledTime, tooltipRandom);
					}
					else
					{
						itemTooltipText.TryGetValue(text, out value2);
					}
					if (!string.IsNullOrEmpty(value2))
					{
						__result = __result.TrimEnd(Array.Empty<char>()) + "\n\n<color=" + instance.tooltipColorValue + ">" + value2 + "</color>";
					}
				}
			}
			catch
			{
			}
		}

		private void ReloadTooltipFile()
		{
			tooltipColorValue = HexColor.Normalize(tooltipsColor.Value, "#E8C97A");
			if (!tooltipsEnabled.Value)
			{
				return;
			}
			try
			{
				string text = Path.Combine(dataFolder, Path.GetFileName(tooltipsFile.Value ?? "item_tooltips.txt"));
				if (!File.Exists(text))
				{
					File.WriteAllText(text, "# Extra item tooltip text for Valhalla Menu Companion.\n# Format:   ItemPrefabName = text\n# The key is the item's prefab name (SwordIron, ArmorBronzeChest, ...) or its name token\n# ($item_sword_iron). Keys are not case-sensitive. Use \\n for a line break.\n# Repeating a key adds another line; with [Tooltips] MultipleLines = Random one of them is shown\n# per hover instead (each once before any repeats). Valheim rich text works: <b>, <i>, <color=#hex>.\n# The text is added at the bottom of the tooltip in [Tooltips] TextColor.\n#\n# SwordIron = Forged in the fires of the north.\n# SwordIron = <i>Favoured by the jarls of the north.</i>\n");
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Tooltips: created a commented example file at " + text + "."));
				}
				FileInfo fileInfo = new FileInfo(text);
				if (loadedTooltipPath == text && loadedTooltipUtc == fileInfo.LastWriteTimeUtc && loadedTooltipLength == fileInfo.Length)
				{
					return;
				}
				loadedTooltipPath = text;
				loadedTooltipUtc = fileInfo.LastWriteTimeUtc;
				loadedTooltipLength = fileInfo.Length;
				if (fileInfo.Length > 2097152)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Tooltips: file exceeds 2 MiB; ignored.");
					itemTooltipText = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
					itemTooltipLines = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
					return;
				}
				List<string> list = new List<string>();
				itemTooltipLines = TooltipFile.ParseEntries(File.ReadAllText(text), list);
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, List<string>> itemTooltipLine in itemTooltipLines)
				{
					dictionary[itemTooltipLine.Key] = string.Join("\n", itemTooltipLine.Value.ToArray());
				}
				itemTooltipText = dictionary;
				tooltipRotation.Clear();
				foreach (string item in list)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Tooltips: " + Path.GetFileName(text) + " " + item));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Tooltips: " + itemTooltipText.Count + " item(s) with custom text."));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Tooltips: file read failed: " + ex.Message));
			}
		}

		private void BindCaptionConfig()
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			captionsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("LoadingScreen", "ShowCaptions", true, "Show a caption on your loading-screen images, from CaptionsFile. No captions in the file = nothing is shown.");
			captionsFile = ((BaseUnityPlugin)this).Config.Bind<string>("LoadingScreen", "CaptionsFile", "captions.txt", "Captions file inside the ImageFolder. Format: image.png = caption text   (* = caption for all other images).");
			captionPosition = ((BaseUnityPlugin)this).Config.Bind<CaptionCorner>("LoadingScreen", "CaptionPosition", CaptionCorner.BottomLeft, "Where the caption sits on the screen. The loading tip is at the bottom centre, so a corner usually works best.");
			captionFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("LoadingScreen", "CaptionFontSize", 30, new ConfigDescription("Caption text size.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(12, 96), Array.Empty<object>()));
			captionColor = ((BaseUnityPlugin)this).Config.Bind<string>("LoadingScreen", "CaptionColor", "#FFFFFF", "Caption colour as #RRGGBB or #RRGGBBAA. A dark outline is added so it stays readable on bright images.");
		}

		private string CaptionsPath()
		{
			return Path.Combine(Path.Combine(dataFolder, Path.GetFileName(loadingImageFolder.Value ?? "loading_screens")), Path.GetFileName(captionsFile.Value ?? "captions.txt"));
		}

		private void ReloadCaptionsFile()
		{
			try
			{
				string text = CaptionsPath();
				if (!File.Exists(text))
				{
					Directory.CreateDirectory(Path.GetDirectoryName(text));
					File.WriteAllText(text, "# Captions for your loading-screen images.\n# Format:   image file name = caption text\n# The name may be written with or without its extension (sunset.png or sunset) and may contain spaces.\n# A line starting with * gives the caption for every image that has no line of its own.\n# Use \\n for a line break. Valheim rich text works: <b>, <i>, <size=80%>, <color=#hex>.\n# Lines starting with # are comments. Position, size and colour are in the [LoadingScreen] settings.\n#\n# sunset.png = <b>Sunset over the Meadows</b>\\n<size=75%>Screenshot by Ragnar</size>\n# * = My Valheim Server\n");
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Loading screen: created a commented example captions file at " + text + "."));
				}
				FileInfo fileInfo = new FileInfo(text);
				if (loadedCaptionsPath == text && loadedCaptionsUtc == fileInfo.LastWriteTimeUtc && loadedCaptionsLength == fileInfo.Length)
				{
					return;
				}
				loadedCaptionsPath = text;
				loadedCaptionsUtc = fileInfo.LastWriteTimeUtc;
				loadedCaptionsLength = fileInfo.Length;
				if (fileInfo.Length > 524288)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Loading screen: captions file exceeds 512 KiB; ignored.");
					loadingCaptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
					return;
				}
				List<string> list = new List<string>();
				loadingCaptions = CaptionFile.Parse(File.ReadAllText(text), list);
				foreach (string item in list)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Loading screen captions: " + item));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Loading screen: " + loadingCaptions.Count + " caption(s) loaded."));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Loading screen: captions file read failed: " + ex.Message));
			}
		}

		private void ApplyLoadingCaption(string imagePath)
		{
			string text = ((captionsEnabled.Value && imagePath != null) ? CaptionFile.Lookup(loadingCaptions, Path.GetFileName(imagePath)) : null);
			if (string.IsNullOrEmpty(text))
			{
				HideLoadingCaption();
				return;
			}
			try
			{
				ShowCaption(text);
			}
			catch (Exception ex)
			{
				if (!captionErrorLogged)
				{
					captionErrorLogged = true;
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Loading screen: caption could not be shown: " + ex));
				}
				DestroyLoadingCaption();
			}
		}

		private void ShowCaption(string caption)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = EnsureCaptionText();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			((Component)val).gameObject.SetActive(true);
			((TMP_Text)val).text = caption;
			((TMP_Text)val).fontSize = Mathf.Clamp(captionFontSize.Value, 12, 96);
			Color white = default(Color);
			if (!ColorUtility.TryParseHtmlString(HexColor.Normalize(captionColor.Value, "#FFFFFF"), ref white))
			{
				white = Color.white;
			}
			((Graphic)val).color = white;
			PlaceCaption(val);
			((TMP_Text)val).transform.SetAsLastSibling();
			if (captionOutlineApplied)
			{
				return;
			}
			captionOutlineApplied = true;
			try
			{
				ApplyCaptionOutline(val);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Loading screen: caption outline unavailable (" + ex.Message + ")."));
			}
		}

		private TextMeshProUGUI EnsureCaptionText()
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			Image val = (((Object)(object)loadingDisplayTarget != (Object)null) ? loadingDisplayTarget : boundLoadingImage);
			Transform val2 = (((Object)(object)val != (Object)null) ? ((Component)val).transform.parent : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			if ((Object)(object)captionText != (Object)null)
			{
				if ((Object)(object)((TMP_Text)captionText).transform.parent != (Object)(object)val2)
				{
					((TMP_Text)captionText).transform.SetParent(val2, false);
				}
				return captionText;
			}
			GameObject val3 = new GameObject("ValhallaCompanion_LoadingCaption", new Type[1] { typeof(RectTransform) });
			val3.transform.SetParent(val2, false);
			TextMeshProUGUI val4 = val3.AddComponent<TextMeshProUGUI>();
			TMP_Text val5 = (((Object)(object)boundHud != (Object)null) ? boundHud.m_loadingTip : null);
			if ((Object)(object)val5 != (Object)null && (Object)(object)val5.font != (Object)null)
			{
				((TMP_Text)val4).font = val5.font;
				if ((Object)(object)val5.fontSharedMaterial != (Object)null)
				{
					((TMP_Text)val4).fontSharedMaterial = val5.fontSharedMaterial;
				}
			}
			((Graphic)val4).raycastTarget = false;
			((TMP_Text)val4).richText = true;
			((TMP_Text)val4).overflowMode = (TextOverflowModes)0;
			captionOutlineApplied = false;
			captionText = val4;
			return val4;
		}

		private void ApplyCaptionOutline(TextMeshProUGUI text)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			Material val = ((TMP_Text)text).fontSharedMaterial;
			if ((Object)(object)val == (Object)null && (Object)(object)((TMP_Text)text).font != (Object)null)
			{
				val = ((TMP_Asset)((TMP_Text)text).font).material;
			}
			if ((Object)(object)val == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Loading screen: caption has no font material; outline skipped.");
				return;
			}
			Material val2 = new Material(val)
			{
				name = "ValhallaCompanion_CaptionMaterial"
			};
			bool flag = val2.HasProperty("_OutlineWidth") && val2.HasProperty("_OutlineColor");
			bool flag2 = val2.HasProperty("_UnderlayColor") && val2.HasProperty("_UnderlayOffsetX");
			if (!flag && !flag2)
			{
				Object.Destroy((Object)(object)val2);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Loading screen: caption font shader '" + ((Object)val.shader).name + "' has no outline support."));
				return;
			}
			if (flag)
			{
				val2.EnableKeyword("OUTLINE_ON");
				val2.SetFloat("_OutlineWidth", 0.2f);
				val2.SetColor("_OutlineColor", new Color(0f, 0f, 0f, 0.9f));
			}
			if (flag2)
			{
				val2.EnableKeyword("UNDERLAY_ON");
				val2.SetColor("_UnderlayColor", new Color(0f, 0f, 0f, 0.75f));
				val2.SetFloat("_UnderlayOffsetX", 0.6f);
				val2.SetFloat("_UnderlayOffsetY", -0.6f);
				if (val2.HasProperty("_UnderlayDilate"))
				{
					val2.SetFloat("_UnderlayDilate", 0.2f);
				}
				if (val2.HasProperty("_UnderlaySoftness"))
				{
					val2.SetFloat("_UnderlaySoftness", 0.4f);
				}
			}
			if ((Object)(object)captionMaterial != (Object)null)
			{
				Object.Destroy((Object)(object)captionMaterial);
			}
			captionMaterial = val2;
			((TMP_Text)text).fontSharedMaterial = val2;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Loading screen: caption outline=" + flag + ", shadow=" + flag2 + " (shader " + ((Object)val.shader).name + ")."));
		}

		private void PlaceCaption(TextMeshProUGUI text)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			RectTransform rectTransform = ((TMP_Text)text).rectTransform;
			CaptionCorner value = captionPosition.Value;
			bool flag = value == CaptionCorner.TopLeft || value == CaptionCorner.TopCenter || value == CaptionCorner.TopRight;
			float num;
			switch (value)
			{
			default:
				num = 0.5f;
				break;
			case CaptionCorner.TopRight:
			case CaptionCorner.BottomRight:
				num = 1f;
				break;
			case CaptionCorner.TopLeft:
			case CaptionCorner.BottomLeft:
				num = 0f;
				break;
			}
			float num2 = num;
			float num3 = (flag ? 1f : 0f);
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(num2, num3);
			rectTransform.pivot = val;
			Vector2 anchorMin = (rectTransform.anchorMax = val);
			rectTransform.anchorMin = anchorMin;
			rectTransform.anchoredPosition = new Vector2((num2 == 0f) ? 48f : ((num2 == 1f) ? (-48f) : 0f), flag ? (-48f) : 48f);
			rectTransform.sizeDelta = new Vector2(900f, 300f);
			TextAlignmentOptions alignment = ((!flag) ? ((TextAlignmentOptions)((num2 == 0f) ? 1025 : ((num2 == 1f) ? 1028 : 1026))) : ((TextAlignmentOptions)((num2 == 0f) ? 257 : ((num2 == 1f) ? 260 : 258))));
			((TMP_Text)text).alignment = alignment;
		}

		private void HideLoadingCaption()
		{
			if ((Object)(object)captionText != (Object)null)
			{
				((Component)captionText).gameObject.SetActive(false);
			}
		}

		private void DestroyLoadingCaption()
		{
			if ((Object)(object)captionText != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)captionText).gameObject);
			}
			captionText = null;
			if ((Object)(object)captionMaterial != (Object)null)
			{
				Object.Destroy((Object)(object)captionMaterial);
			}
			captionMaterial = null;
		}

		private void BindLoadingScreenConfig()
		{
			loadingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("LoadingScreen", "Enabled", true, "Use custom loading-screen images and tips from BepInEx/config/ValhallaMenu. With no images and no tips nothing changes.");
			loadingImageFolder = ((BaseUnityPlugin)this).Config.Bind<string>("LoadingScreen", "ImageFolder", "loading_screens", "Folder inside BepInEx/config/ValhallaMenu holding .png/.jpg loading images (up to 16 MiB / 16 megapixels each; 1920x1080 recommended). Optional sub-folders join, respawn and teleport hold images for just that moment.");
			loadingImageOrder = ((BaseUnityPlugin)this).Config.Bind<ImageOrderMode>("LoadingScreen", "ImageOrder", ImageOrderMode.Random, "Random: shuffled, every image is shown once before any repeats. Sequential: in file-name order.");
			loadingImageFit = ((BaseUnityPlugin)this).Config.Bind<ImageFitMode>("LoadingScreen", "ImageFit", ImageFitMode.Cover, "Cover: fill the screen, cropping the edges if the aspect ratio differs. Fit: show the whole image (letterbox). Stretch: fill and distort.");
			loadingSlideshowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("LoadingScreen", "SlideshowSeconds", 20f, "While a loading screen stays up this long, switch to the next image (minimum 3). 0 = keep one image per loading screen.");
			loadingTipsFile = ((BaseUnityPlugin)this).Config.Bind<string>("LoadingScreen", "TipsFile", "loading_tips.txt", "Tips file inside BepInEx/config/ValhallaMenu, one tip per line.");
			loadingTipsMode = ((BaseUnityPlugin)this).Config.Bind<TipsModeSetting>("LoadingScreen", "TipsMode", TipsModeSetting.Replace, "Replace: show only your tips. Append: vanilla tips plus yours. Off: vanilla tips only. An empty tips file always shows vanilla tips.");
			loadingLogoMode = ((BaseUnityPlugin)this).Config.Bind<LoadingLogoMode>("LoadingScreen", "Logo", LoadingLogoMode.Keep, "Logo at the top of the loading screen. Keep: as it is. Custom: LogoFile (or, if that file is missing, the [Branding] title logo). Hide: no logo.");
			loadingLogoFile = ((BaseUnityPlugin)this).Config.Bind<string>("LoadingScreen", "LogoFile", "loading_logo.png", "PNG in BepInEx/config/ValhallaMenu used when Logo = Custom. Missing = the [Branding] LogoFile is used.");
			loadingHideSpinner = ((BaseUnityPlugin)this).Config.Bind<bool>("LoadingScreen", "HideSpinner", false, "Hide the spinning loading icon in the corner.");
			loadingHideShading = ((BaseUnityPlugin)this).Config.Bind<bool>("LoadingScreen", "HideShading", false, "Show your images at full brightness: removes Valheim's darkening of the background image and its shading layers.");
			BindCaptionConfig();
		}

		private static void BeforeLoadingScreenSetup(Hud __instance)
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.ApplyLoadingScreen(__instance);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)instance).Logger.LogWarning((object)("Loading screen: could not apply custom screen: " + ex));
			}
		}

		private LoadingContext CurrentSpawnContext()
		{
			bool firstSpawn = true;
			try
			{
				if (firstSpawnField != null && (Object)(object)Game.instance != (Object)null)
				{
					firstSpawn = (bool)firstSpawnField.GetValue(Game.instance);
				}
			}
			catch
			{
				firstSpawn = true;
			}
			return LoadingSets.ContextForSpawn(firstSpawn);
		}

		private void ApplyLoadingScreen(Hud hud)
		{
			if (!((Object)(object)hud == (Object)null))
			{
				if (!pluginEnabled.Value || !loadingEnabled.Value)
				{
					RestoreLoadingScreen();
					return;
				}
				BindLoadingHud(hud);
				ReloadLoadingScreenFiles();
				ApplyLoadingTips(hud);
				ApplyLoadingExtras(hud);
				ShowContextImage(CurrentSpawnContext(), boundLoadingImage);
			}
		}

		private void BindLoadingHud(Hud hud)
		{
			if (boundHud != hud || !((Object)(object)boundHud != (Object)null))
			{
				RestoreLoadingScreen();
				boundHud = hud;
				boundLoadingImage = hud.m_loadingImage;
				savedLoadingSprite = (((Object)(object)boundLoadingImage != (Object)null) ? boundLoadingImage.sprite : null);
				savedLoadingPreserveAspect = (Object)(object)boundLoadingImage != (Object)null && boundLoadingImage.preserveAspect;
				savedLoadingTips = ((hud.m_loadingTips != null) ? new List<string>(hud.m_loadingTips) : null);
				loadingFitter = null;
				loadingFitterAdded = false;
				loadingLogoSearched = false;
				LogLoadingHierarchyOnce(hud);
			}
		}

		private void ApplyLoadingTips(Hud hud)
		{
			if (hud.m_loadingTips != null && savedLoadingTips != null)
			{
				List<string> list = LoadingTipsFile.Compose(savedLoadingTips, customLoadingTips, loadingTipsMode.Value.ToString());
				if (list.Count != 0)
				{
					hud.m_loadingTips.Clear();
					hud.m_loadingTips.AddRange(list);
				}
			}
		}

		private bool ShowContextImage(LoadingContext context, Image target)
		{
			if ((Object)(object)target == (Object)null)
			{
				return false;
			}
			IList<string> list = LoadingSets.Choose(context, loadingContextImages[context], loadingImageFiles);
			loadingCurrentContext = context;
			if (list == null)
			{
				loadingCurrentList = null;
				if ((Object)(object)target == (Object)(object)boundLoadingImage)
				{
					RestoreLoadingSprite();
				}
				ApplyLoadingCaption(null);
				return false;
			}
			return ShowImageAt(context, target, list, PickNextImage(context, list));
		}

		private int PickNextImage(LoadingContext context, IList<string> list)
		{
			if (loadingImageOrder.Value == ImageOrderMode.Sequential)
			{
				if (!loadingSequentialIndex.TryGetValue(context, out var value))
				{
					value = -1;
				}
				int num = LoadingSets.NextIndex(list.Count, value, sequential: true, loadingRandom);
				loadingSequentialIndex[context] = num;
				return num;
			}
			LoadDecksOnce();
			string key = ((list == loadingImageFiles) ? "default" : LoadingSets.FolderFor(context));
			if (!loadingDecks.TryGetValue(key, out var value2))
			{
				value2 = (loadingDecks[key] = new LoadingSets.ShuffleDeck());
			}
			List<string> list2 = new List<string>(list.Count);
			foreach (string item in list)
			{
				list2.Add(Path.GetFileName(item));
			}
			int result = value2.Next(list2, loadingRandom);
			SaveDecks();
			return result;
		}

		private void LoadDecksOnce()
		{
			if (loadingDecks != null)
			{
				return;
			}
			try
			{
				string text = Path.Combine(dataFolder, "loading_shuffle.txt");
				loadingDecks = ((File.Exists(text) && new FileInfo(text).Length < 1048576) ? LoadingSets.ParseDecks(File.ReadAllText(text)) : new Dictionary<string, LoadingSets.ShuffleDeck>(StringComparer.OrdinalIgnoreCase));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Loading screen: could not read loading_shuffle.txt (" + ex.Message + "); starting a new shuffle."));
				loadingDecks = new Dictionary<string, LoadingSets.ShuffleDeck>(StringComparer.OrdinalIgnoreCase);
			}
		}

		private void SaveDecks()
		{
			try
			{
				File.WriteAllText(Path.Combine(dataFolder, "loading_shuffle.txt"), LoadingSets.FormatDecks(loadingDecks));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Loading screen: could not save loading_shuffle.txt: " + ex.Message));
			}
		}

		private bool ShowImageAt(LoadingContext context, Image target, IList<string> list, int index)
		{
			for (int i = 0; i < 8; i++)
			{
				if (list.Count <= 0)
				{
					break;
				}
				if (index < 0 || index >= list.Count)
				{
					index = 0;
				}
				string path = list[index];
				if (LoadImageFile(path))
				{
					target.sprite = loadingSprite;
					ApplyLoadingFit(target, ((Texture)loadingTexture).width, ((Texture)loadingTexture).height);
					loadingDisplayTarget = target;
					loadingCurrentList = list;
					loadingShownAt = Time.unscaledTime;
					ApplyLoadingCaption(loadingImagePath);
					return true;
				}
				list.RemoveAt(index);
			}
			loadingCurrentList = null;
			if ((Object)(object)target == (Object)(object)boundLoadingImage)
			{
				RestoreLoadingSprite();
			}
			ApplyLoadingCaption(null);
			return false;
		}

		private bool LoadImageFile(string path)
		{
			if (string.Equals(path, loadingImagePath, StringComparison.OrdinalIgnoreCase) && (Object)(object)loadingSprite != (Object)null)
			{
				return true;
			}
			string error;
			Texture2D val = ImageLoader.TryLoad(path, out error);
			if ((Object)(object)val == (Object)null