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 Kvedja v1.0.0
plugins/Kvedja/Kvedja.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Ezomic.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("Thijssen Software")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")] [assembly: AssemblyDescription("A message of the day in the chat window when you log in.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+c666481772c83c0634604856dacb8c56a0e1441f")] [assembly: AssemblyProduct("Kvedja")] [assembly: AssemblyTitle("Kvedja")] [assembly: AssemblyVersion("1.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 Kvedja { internal static class KvedjaConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<string> Url; internal static ConfigEntry<string> Title; internal static ConfigEntry<Type> Voice; internal static ConfigEntry<float> Delay; internal static ConfigEntry<int> Timeout; internal static ConfigEntry<bool> Repeat; internal static ConfigEntry<bool> Verbose; internal static void Bind(ConfigFile cfg) { Enabled = cfg.Bind<bool>("Kvedja", "Enabled", true, "Off leaves the plugin loaded and says nothing. Nothing is fetched either, so this also stops the mod asking the site anything at all."); Url = cfg.Bind<string>("Kvedja", "Url", "https://longhouse.thijssensoftware.nl/api/motd", "Where the message is read from. Plain text, one line of chat per line. Empty turns the mod off as surely as Enabled does. Any server can host its own: the only requirement is that the answer is text."); Title = cfg.Bind<string>("Kvedja", "Title", "Longhouse", "The name in front of the message, where a player's name normally goes. It is drawn orange by the game, which is what makes the line read as somebody speaking rather than as an error."); Voice = cfg.Bind<Type>("Kvedja", "Voice", (Type)1, "How the line is styled. Shout is drawn yellow AND IN CAPITALS, whisper is faded and forced to lowercase; both are done by the game, not here. Normal is the only one that prints what was written."); Delay = cfg.Bind<float>("Kvedja", "Delay", 4f, "Seconds after you appear in the world before the message is said. Too early and it lands under the loading fade, which looks exactly like it never arrived."); Timeout = cfg.Bind<int>("Kvedja", "Timeout", 8, "Seconds to wait for the site before giving up. Giving up is silent: a message of the day that reports its own failure to a player is worse than no message, because the player can do nothing about it."); Repeat = cfg.Bind<bool>("Kvedja", "Repeat", false, "On says the message again every time you respawn, not only when you log in. Off is once per world session, which is what login means."); Verbose = cfg.Bind<bool>("Kvedja", "Verbose", false, "Write what was asked for and what came back to BepInEx/LogOutput.log. Off unless the message is not appearing; it is three or four lines per login."); } } internal static class KvedjaPatches { [HarmonyPostfix] [HarmonyPatch(typeof(Chat), "Awake")] private static void ChatAwake() { Motd.NewWorld(); } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "OnSpawned")] private static void OnSpawned(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { Motd.Wanted(); } } [HarmonyPostfix] [HarmonyPatch(typeof(Chat), "Update")] private static void ChatUpdate() { Motd.Tick(); } } [BepInPlugin("ezomic.valheim.kvedja", "Kvedja", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class KvedjaPlugin : BaseUnityPlugin { public const string PluginGuid = "ezomic.valheim.kvedja"; public const string PluginName = "Kvedja"; public const string PluginVersion = "1.0.0"; public const string PluginAuthor = "Robbin Thijssen"; private const string CoreGuid = "ezomic.valheim.core"; internal static ManualLogSource Log; internal static bool CorePresent; private Harmony _harmony; private void Awake() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; KvedjaConfig.Bind(((BaseUnityPlugin)this).Config); TryRegisterWithCore(); _harmony = new Harmony("ezomic.valheim.kvedja"); _harmony.PatchAll(typeof(KvedjaPatches)); _harmony.PatchAll(typeof(Notices)); Log.LogInfo((object)"Kvedja 1.0.0 by Robbin Thijssen - ready."); } private void TryRegisterWithCore() { CorePresent = Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core"); if (!CorePresent) { Log.LogInfo((object)"Core not installed - running standalone, without the version gate."); } else { RegisterWithCore(); } } [MethodImpl(MethodImplOptions.NoInlining)] private void RegisterWithCore() { Suite.Register("ezomic.valheim.kvedja", "Kvedja", "1.0.0", ((BaseUnityPlugin)this).Config, (Requirement)1, (Assembly)null); Suite.Local((ConfigEntryBase[])(object)new ConfigEntryBase[8] { (ConfigEntryBase)KvedjaConfig.Enabled, (ConfigEntryBase)KvedjaConfig.Url, (ConfigEntryBase)KvedjaConfig.Title, (ConfigEntryBase)KvedjaConfig.Voice, (ConfigEntryBase)KvedjaConfig.Delay, (ConfigEntryBase)KvedjaConfig.Timeout, (ConfigEntryBase)KvedjaConfig.Repeat, (ConfigEntryBase)KvedjaConfig.Verbose }); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } } internal static class Motd { private static readonly HttpClient Client = new HttpClient(); private static readonly object Gate = new object(); private static readonly List<string> Pending = new List<string>(); private static bool _asking; private static bool _saidThisSession; private static float _sayAt; private static FieldRef<Chat, float> _hideTimer; private static bool _triedHideTimer; internal static void NewWorld() { lock (Gate) { Pending.Clear(); } _saidThisSession = false; _sayAt = 0f; } internal static void Wanted() { if (KvedjaConfig.Enabled.Value && (!_saidThisSession || KvedjaConfig.Repeat.Value)) { string value = KvedjaConfig.Url.Value; if (!string.IsNullOrEmpty(value)) { _sayAt = Time.realtimeSinceStartup + Mathf.Max(0f, KvedjaConfig.Delay.Value); Fetch(value); } } } private static void Fetch(string url) { lock (Gate) { if (_asking) { return; } _asking = true; } Thread thread = new Thread((ThreadStart)delegate { Ask(url); }); thread.IsBackground = true; thread.Name = "Kvedja"; thread.Start(); } private static void Ask(string url) { try { try { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; } catch { } string result2; using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(Mathf.Clamp(KvedjaConfig.Timeout.Value, 1, 60)))) { HttpResponseMessage result = Client.GetAsync(url, cancellationTokenSource.Token).GetAwaiter().GetResult(); try { result.EnsureSuccessStatusCode(); result2 = result.Content.ReadAsStringAsync().GetAwaiter().GetResult(); } finally { ((IDisposable)result)?.Dispose(); } } List<string> list = new List<string>(); string[] array = result2.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim('\r', ' ', '\t'); if (text.Length != 0) { list.Add(text); } } lock (Gate) { Pending.Clear(); Pending.AddRange(list); } if (KvedjaConfig.Verbose.Value) { KvedjaPlugin.Log.LogInfo((object)("Read " + list.Count + " line(s) from " + url)); } } catch (Exception ex) { if (KvedjaConfig.Verbose.Value) { KvedjaPlugin.Log.LogInfo((object)("No message of the day: " + ex.Message)); } } finally { lock (Gate) { _asking = false; } } } internal static void Tick() { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) if (_sayAt <= 0f || Time.realtimeSinceStartup < _sayAt) { return; } Chat instance = Chat.instance; if ((Object)(object)instance == (Object)null) { return; } string[] array; lock (Gate) { if (Pending.Count == 0) { return; } array = Pending.ToArray(); Pending.Clear(); } _sayAt = 0f; _saidThisSession = true; string value = KvedjaConfig.Title.Value; Type value2 = KvedjaConfig.Voice.Value; string[] array2 = array; foreach (string text in array2) { ((Terminal)instance).AddString(value, text, value2, false); } Show(instance); if (KvedjaConfig.Verbose.Value) { KvedjaPlugin.Log.LogInfo((object)("Said " + array.Length + " line(s) as " + value + ".")); } } internal static void SayNow(string line) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) Chat instance = Chat.instance; if (!((Object)(object)instance == (Object)null)) { ((Terminal)instance).AddString(KvedjaConfig.Title.Value, line, KvedjaConfig.Voice.Value, false); Show(instance); } } private static void Show(Chat chat) { FieldRef<Chat, float> val = HideTimer(); if (val == null) { return; } try { val.Invoke(chat) = 0f; } catch (Exception) { } } private static FieldRef<Chat, float> HideTimer() { if (_triedHideTimer) { return _hideTimer; } _triedHideTimer = true; try { _hideTimer = AccessTools.FieldRefAccess<Chat, float>("m_hideTimer"); } catch (Exception ex) { KvedjaPlugin.Log.LogWarning((object)("Cannot reach the chat window's hide timer (" + ex.GetType().Name + "). The message is still added to the chat log; what is lost is the window opening by itself to show it.")); } return _hideTimer; } } internal static class Notices { internal const string NoticeRpc = "Kvedja_Notice"; [HarmonyPostfix] [HarmonyPatch(typeof(ZNet), "Awake")] private static void Register() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<string>("Kvedja_Notice", (Action<long, string>)OnNotice); } } private static void OnNotice(long sender, string text) { try { if (KvedjaConfig.Enabled.Value && !string.IsNullOrEmpty(text) && !((Object)(object)ZNet.instance == (Object)null)) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid == sender) { Motd.SayNow(text); } } } catch (Exception ex) { KvedjaPlugin.Log.LogWarning((object)("Showing a server notice failed: " + ex)); } } } }