Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValhallaMenuCompanion v2.0.0
plugins/ValhallaMenuCompanion.dll
Decompiled 17 hours ago
The result has been truncated due to the large size, download it to view full contents!
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