using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("CustomMainMenu")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Custom main menu logo, music, loading art, and Discord link via BepInEx config folder.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CustomMainMenu")] [assembly: AssemblyTitle("CustomMainMenu")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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 CustomMainMenu { internal static class Hu { internal const string StartGameLower = "játék indítása"; internal const string SettingsLower = "beállitások"; internal const string PatchNotesTitleLower = "frissítési napló"; internal const string ModdedMarkerLower = "módosított"; } [BepInPlugin("rdmods.custommainmenu", "Custom Main Menu", "1.0.0")] public sealed class CustomMainMenuPlugin : BaseUnityPlugin { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__72_0; internal void b__72_0() { if (!DiscordPanelVisibility.TryOpenInviteUrl()) { LogWarn("Discord invite URL is missing or invalid in config."); } } } public const string ModGuid = "rdmods.custommainmenu"; public const string ModName = "Custom Main Menu"; public const string AssetFolderName = "CustomMainMenu"; public const string LoadingArtFolderName = "LoadingArt"; public const int LoadingArtMaxSlot = 12; public const string DiscordTitleText = "Need help? Join Discord!"; public const string DiscordButtonText = "Click here to join Discord!"; internal static bool MenuMusicFadeCommittedForSession; internal static volatile bool MainMenuActive; internal static readonly bool EnableCustomLoadingScreens = true; private ConfigEntry? _enableLogging; private Harmony? _harmony; private GameObject? _menuMusicGo; private AudioSource? _menuMusicSource; private Coroutine? _menuMusicFadeCoroutine; private Coroutine? _menuMusicStartCoroutine; private Coroutine? _menuAtmosphereCoroutine; private Coroutine? _menuAtmosphereApplyCoroutine; private static GameObject? _discordPanel; private Coroutine? _discordLogoCoroutine; private static Image? _discordLogoImage; private static readonly HashSet NeverHide = new HashSet(StringComparer.Ordinal) { "startgui", "worldpanel", "startgame", "topleft", "topright", "menu", "joinpanel", "panel", "tooltips" }; internal static CustomMainMenuPlugin? Instance { get; private set; } internal static ConfigEntry? ModEnabled { get; private set; } internal static ManualLogSource? ModLog { get; private set; } internal static GameObject? DiscordPanelRoot => _discordPanel; internal static void SetDiscordPanelRoot(GameObject? panel) { _discordPanel = panel; } private void Awake() { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown Instance = this; ModLog = ((BaseUnityPlugin)this).Logger; MenuMusicFadeCommittedForSession = false; ModEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Turn this mod on or off."); _enableLogging = ((BaseUnityPlugin)this).Config.Bind("General", "EnableLogging", false, "Write informational logs (menu lifecycle, loading watcher, atmosphere, Discord visibility). Delete cfg after changing."); DiscordPanelVisibility.BindConfig(((BaseUnityPlugin)this).Config); MenuScenery.BindConfig(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.Save(); if (!ModEnabled.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom Main Menu is disabled in config."); return; } EnsureAssetDirectories(); _harmony = new Harmony("rdmods.custommainmenu"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Custom Main Menu loaded. Asset root: " + GetAssetRootPath())); } private void OnDestroy() { if (_discordLogoCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_discordLogoCoroutine); _discordLogoCoroutine = null; } if (_menuMusicFadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_menuMusicFadeCoroutine); _menuMusicFadeCoroutine = null; } if (_menuMusicStartCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_menuMusicStartCoroutine); _menuMusicStartCoroutine = null; } if (_menuAtmosphereCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_menuAtmosphereCoroutine); _menuAtmosphereCoroutine = null; } if (_menuAtmosphereApplyCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_menuAtmosphereApplyCoroutine); _menuAtmosphereApplyCoroutine = null; } MenuAtmosphere.Clear(); MenuScenery.Clear(); MainMenuActive = false; StopMenuMusic(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; Instance = null; } internal static bool TryGetLocalPlayerPresentPlain() { try { return (Object)(object)Player.m_localPlayer != (Object)null; } catch { return false; } } internal static void CommitMenuMusicFadeForWorldEntry(string trigger) { if (ModEnabled != null && ModEnabled.Value && !MenuMusicFadeCommittedForSession) { MenuMusicFadeCommittedForSession = true; LogInfo("Stopping menu music (" + trigger + ")."); FadeOutAndStopMenuMusic(); } } internal static bool IsWorldFullyLoaded() { bool num = TryGetLocalPlayerPresentPlain(); if (num && !MenuMusicFadeCommittedForSession) { CommitMenuMusicFadeForWorldEntry("local player in world"); } return num; } internal static void LogInfo(string message) { if (Instance?._enableLogging != null && Instance._enableLogging.Value) { ManualLogSource? modLog = ModLog; if (modLog != null) { modLog.LogInfo((object)message); } } } internal static void LogWarn(string message) { ManualLogSource? modLog = ModLog; if (modLog != null) { modLog.LogWarning((object)message); } } internal static string GetAssetRootPath() { return Path.Combine(Paths.ConfigPath, "CustomMainMenu"); } internal static string GetLoadingArtFolder() { return Path.Combine(GetAssetRootPath(), "LoadingArt"); } internal static string GetLogoPath() { return Path.Combine(GetAssetRootPath(), "logo.png"); } internal static string GetDiscordLogoPath() { return Path.Combine(GetAssetRootPath(), "discordlogo.png"); } internal static string GetMusicPath() { return Path.Combine(GetAssetRootPath(), "mainmenu.ogg"); } internal static string? GetRandomLoadingArtPath() { string loadingArtFolder = GetLoadingArtFolder(); if (!Directory.Exists(loadingArtFolder)) { return null; } string result = null; int num = 0; for (int i = 1; i <= 12; i++) { string text = Path.Combine(loadingArtFolder, $"art{i}.png"); if (File.Exists(text)) { num++; if (num == 1 || Random.Range(0, num) == 0) { result = text; } } } return result; } private void EnsureAssetDirectories() { try { Directory.CreateDirectory(GetAssetRootPath()); Directory.CreateDirectory(GetLoadingArtFolder()); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not create asset folders: " + ex.Message)); } } internal static void OnMainMenuOpened(FejdStartup fejd) { MainMenuActive = true; DiscordPanelVisibility.ResetSessionState(); MenuAtmosphere.ResetForMenuSession(); MenuScenery.ResetForMenuSession(); MenuScenery.ScheduleApplyStaging(fejd); StopMusicManWorldMusicForMainMenu(); if ((Object)(object)Instance == (Object)null) { MenuAtmosphere.Apply(); return; } if (Instance._menuAtmosphereApplyCoroutine != null) { ((MonoBehaviour)Instance).StopCoroutine(Instance._menuAtmosphereApplyCoroutine); Instance._menuAtmosphereApplyCoroutine = null; } Instance._menuAtmosphereApplyCoroutine = ((MonoBehaviour)fejd).StartCoroutine(MenuAtmosphere.ApplyWhenReady()); if (Instance._menuAtmosphereCoroutine != null) { ((MonoBehaviour)Instance).StopCoroutine(Instance._menuAtmosphereCoroutine); Instance._menuAtmosphereCoroutine = null; } Instance._menuAtmosphereCoroutine = ((MonoBehaviour)Instance).StartCoroutine(MaintainMenuAtmosphereWhileOnMainMenu()); } internal static void LogSceneryClear(string reason) { LogInfo("Menu scenery cleared (" + reason + ")."); } internal static void OnWorldLoadCommitted() { LoadingScreens.StopAllWatchers("world load committed"); DiscordPanelVisibility.Hide("world load committed"); MenuAtmosphere.Clear(); MenuScenery.Clear(); LogSceneryClear("world load"); if (Instance?._menuAtmosphereCoroutine != null) { ((MonoBehaviour)Instance).StopCoroutine(Instance._menuAtmosphereCoroutine); Instance._menuAtmosphereCoroutine = null; } if (Instance?._menuAtmosphereApplyCoroutine != null) { ((MonoBehaviour)Instance).StopCoroutine(Instance._menuAtmosphereApplyCoroutine); Instance._menuAtmosphereApplyCoroutine = null; } MainMenuActive = false; try { MusicMan instance = MusicMan.instance; if ((Object)(object)instance != (Object)null) { MusicManAccess.ReleaseMainMenuMute(instance); } } catch (Exception ex) { ManualLogSource? modLog = ModLog; if (modLog != null) { modLog.LogError((object)ex); } } } internal static void SilenceMusicManForMainMenu() { try { MusicMan instance = MusicMan.instance; if (!((Object)(object)instance == (Object)null)) { MusicManAccess.SilenceForMainMenu(instance); } } catch (Exception ex) { ManualLogSource? modLog = ModLog; if (modLog != null) { modLog.LogError((object)ex); } } } private static void StopMusicManWorldMusicForMainMenu() { SilenceMusicManForMainMenu(); LogInfo("MusicMan world music cleared for main menu."); } internal static IEnumerator MaintainMenuAtmosphereWhileOnMainMenu() { WaitForSecondsRealtime wait = new WaitForSecondsRealtime(0.25f); while (MainMenuActive && (Object)(object)Instance != (Object)null) { MenuAtmosphere.MaintainAppliedState(); yield return wait; } if ((Object)(object)Instance != (Object)null) { Instance._menuAtmosphereCoroutine = null; } } internal static IEnumerator MuteVanillaMenuMusicOnceDelayed() { yield return (object)new WaitForSecondsRealtime(0.5f); if ((Object)(object)Instance != (Object)null && MainMenuActive) { MuteVanillaMenuMusicNow(); } } private static void MuteVanillaMenuMusicNow() { try { if (!(Resources.FindObjectsOfTypeAll(typeof(AudioSource)) is AudioSource[] array)) { return; } AudioSource[] array2 = array; foreach (AudioSource val in array2) { if (!((Object)(object)((val != null) ? ((Component)val).gameObject : null) == (Object)null) && !((Object)(object)val == (Object)(object)Instance?._menuMusicSource)) { string text = ((Object)((Component)val).gameObject).name.ToLowerInvariant(); string text2 = (((Object)(object)val.clip != (Object)null) ? ((Object)val.clip).name.ToLowerInvariant() : string.Empty); string text3 = LoadingScreens.GetHierarchyPath(((Component)val).transform).ToLowerInvariant(); if ((text3.Contains("fejd") || text3.Contains("menu") || text3.Contains("main") || text.Contains("music") || text.Contains("audio") || text2.Contains("menu") || text2.Contains("theme") || text2.Contains("music")) && (val.isPlaying || !(val.volume <= 0.001f))) { val.volume = 0f; val.Stop(); } } } } catch (Exception ex) { ManualLogSource? modLog = ModLog; if (modLog != null) { modLog.LogError((object)ex); } } } internal static IEnumerator HideUnwantedMainMenuUiDelayed() { yield return (object)new WaitForSecondsRealtime(0.5f); HideMenuClutterOnce(); } private static void HideMenuClutterOnce() { FejdStartup val = Object.FindFirstObjectByType(); if ((Object)(object)val == (Object)null) { return; } Transform[] componentsInChildren = ((Component)val).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { GameObject gameObject = ((Component)componentsInChildren[i]).gameObject; string text = (((Object)gameObject).name ?? string.Empty).ToLowerInvariant(); if (!NeverHide.Contains(text)) { string lowerOwn = GetOwnText(gameObject).ToLowerInvariant(); if (ShouldHideUi(text, lowerOwn)) { gameObject.SetActive(false); } } } } private static bool ShouldHideUi(string lowerName, string lowerOwn) { bool flag; switch (lowerName) { case "canvas changelog": case "changelog": case "topic": case "patchlogscroll": flag = true; break; default: flag = false; break; } if (flag || lowerOwn == "frissítési napló") { return true; } if (lowerName.Contains("merch") || lowerName == "valheim.shop" || lowerName == "url" || lowerOwn == "valheim.shop") { return true; } if (lowerName == "modded_text" || lowerOwn.Contains("currently playing a modded version") || lowerOwn.Contains("modded version") || lowerOwn.Contains("módosított")) { return true; } return false; } private static string GetOwnText(GameObject go) { TextMeshProUGUI component = go.GetComponent(); if ((Object)(object)component != (Object)null && !string.IsNullOrWhiteSpace(((TMP_Text)component).text)) { return ((TMP_Text)component).text.Trim(); } Text component2 = go.GetComponent(); if (!((Object)(object)component2 != (Object)null) || string.IsNullOrWhiteSpace(component2.text)) { return string.Empty; } return component2.text.Trim(); } internal static IEnumerator ReplaceLogoDelayed() { yield return (object)new WaitForSecondsRealtime(0.75f); FejdStartup val = Object.FindFirstObjectByType(); if ((Object)(object)val == (Object)null || !File.Exists(GetLogoPath())) { yield break; } Image val2 = null; int num = int.MinValue; Image[] componentsInChildren = ((Component)val).GetComponentsInChildren(true); foreach (Image val3 in componentsInChildren) { if (!((Object)(object)val3 == (Object)null)) { int num2 = ScoreLogo(val3); if (num2 > num) { num = num2; val2 = val3; } } } if ((Object)(object)val2 != (Object)null && num >= 50) { Sprite val4 = LoadSpriteFromDisk(GetLogoPath()); if ((Object)(object)val4 != (Object)null) { ApplyLogo(val2, val4); LogInfo($"Logo applied (score {num})."); } } } private static int ScoreLogo(Image img) { //IL_00a6: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) string text = (((Object)((Component)img).gameObject).name ?? "").ToLowerInvariant(); string text2 = (((Object)(object)img.sprite != (Object)null) ? ((Object)img.sprite).name.ToLowerInvariant() : ""); int num = 0; if (text.Contains("logo")) { num += 100; } if (text.Contains("valheim")) { num += 80; } if (text2.Contains("logo")) { num += 50; } if (((Component)img).gameObject.activeInHierarchy) { num += 15; } if (((Behaviour)img).enabled) { num += 10; } RectTransform rectTransform = ((Graphic)img).rectTransform; if ((Object)(object)rectTransform != (Object)null) { Rect rect = rectTransform.rect; float width = ((Rect)(ref rect)).width; rect = rectTransform.rect; if (Mathf.Abs(width * ((Rect)(ref rect)).height) > 10000f) { num += 10; } if (((Transform)rectTransform).position.y > (float)Screen.height * 0.45f) { num += 10; } } return num; } private static Sprite? LoadSpriteFromDisk(string path) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) try { byte[] data = File.ReadAllBytes(path); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!TextureDecode.LoadImage(val, data, markNonReadable: false)) { Object.Destroy((Object)(object)val); return null; } val.Apply(true, false); return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } catch (Exception ex) { LogWarn("logo load: " + ex.Message); return null; } } private static void ApplyLogo(Image target, Sprite sp) { //IL_002b: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) target.sprite = sp; target.overrideSprite = sp; ((Behaviour)target).enabled = true; target.preserveAspect = true; target.type = (Type)0; ((Graphic)target).material = null; ((Graphic)target).color = Color.white; if ((Object)(object)((Graphic)target).rectTransform != (Object)null) { RectTransform rectTransform = ((Graphic)target).rectTransform; Rect rect = sp.rect; float width = ((Rect)(ref rect)).width; rect = sp.rect; rectTransform.sizeDelta = new Vector2(width, ((Rect)(ref rect)).height); ((Transform)((Graphic)target).rectTransform).localScale = Vector3.one; } } internal static TextMeshProUGUI? FindBestMenuTmpText(FejdStartup startup) { if ((Object)(object)startup == (Object)null) { return null; } TextMeshProUGUI result = null; int num = int.MinValue; TextMeshProUGUI[] componentsInChildren = ((Component)startup).GetComponentsInChildren(true); foreach (TextMeshProUGUI val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { string text = (((TMP_Text)val).text ?? "").ToLowerInvariant(); string text2 = (((Object)((Component)val).gameObject).name ?? "").ToLowerInvariant(); int num2 = 0; if (text.Contains("start game") || text.Contains("játék indítása")) { num2 += 300; } if (text.Contains("beállitások") || text.Contains("settings")) { num2 += 220; } if (text2.Contains("text")) { num2 += 20; } if (((TMP_Text)val).fontSize >= 20f) { num2 += 15; } if (num2 > num) { num = num2; result = val; } } } return result; } internal static Button? FindBestVisibleMainMenuButton(FejdStartup startup) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)startup == (Object)null) { return null; } Button result = null; int num = int.MinValue; Button[] componentsInChildren = ((Component)startup).GetComponentsInChildren