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 NerdyGamerTools AutoBroadcaster v1.0.1
NGT.AutoBroadcaster.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using AutoBroadcast.Config; using AutoBroadcast.Messaging; using AutoBroadcast.Models; using AutoBroadcast.Patches; using AutoBroadcast.Scheduling; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Nerdy Gamer Tools")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Server-side scheduled broadcast mod for Valheim.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Nerdy Gamer Tools AutoBroadcaster")] [assembly: AssemblyTitle("NGT.AutoBroadcaster")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 AutoBroadcast { [BepInPlugin("NGT_Autobroadcaster", "Nerdy Gamer Tools AutoBroadcaster", "1.0.0")] public sealed class AutoBroadcastPlugin : BaseUnityPlugin { public const string PluginGuid = "NGT_Autobroadcaster"; public const string PluginName = "Nerdy Gamer Tools AutoBroadcaster"; public const string PluginVersion = "1.0.0"; private MessageConfigService? _configService; private HourlyMessageScheduler? _scheduler; private Harmony? _harmony; private void Awake() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _harmony = new Harmony("NGT_Autobroadcaster"); _harmony.PatchAll(typeof(ZLogFilterPatch).Assembly); _configService = new MessageConfigService(((BaseUnityPlugin)this).Config, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }); _configService.Initialize(); BroadcastDispatcher @object = new BroadcastDispatcher(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }, (MonoBehaviour)(object)this); _scheduler = new HourlyMessageScheduler(() => (!_configService.IsEnabled) ? Array.Empty<ScheduledMessage>() : _configService.Messages, @object.Dispatch, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); ((MonoBehaviour)this).StartCoroutine(_scheduler.Run()); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Nerdy Gamer Tools AutoBroadcaster initialized. Server-side scheduler is running."); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _configService?.Dispose(); _configService = null; } } } namespace AutoBroadcast.Scheduling { internal sealed class HourlyMessageScheduler { [CompilerGenerated] private sealed class <Run>d__6 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public HourlyMessageScheduler <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <Run>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown int num = <>1__state; HourlyMessageScheduler hourlyMessageScheduler = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; hourlyMessageScheduler.Tick(DateTime.Now); } else { <>1__state = -1; hourlyMessageScheduler.Tick(DateTime.Now); } float secondsUntilNextMinute = GetSecondsUntilNextMinute(DateTime.Now); <>2__current = (object)new WaitForSecondsRealtime(secondsUntilNextMinute); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly Func<IReadOnlyList<ScheduledMessage>> _getMessages; private readonly Action<ScheduledMessage> _dispatch; private readonly Action<string> _logInfo; private readonly HashSet<string> _firedKeys = new HashSet<string>(StringComparer.Ordinal); private DateTime _lastPruneUtc = DateTime.MinValue; public HourlyMessageScheduler(Func<IReadOnlyList<ScheduledMessage>> getMessages, Action<ScheduledMessage> dispatch, Action<string> logInfo) { _getMessages = getMessages; _dispatch = dispatch; _logInfo = logInfo; } [IteratorStateMachine(typeof(<Run>d__6))] public IEnumerator Run() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <Run>d__6(0) { <>4__this = this }; } private void Tick(DateTime localNow) { string text = localNow.ToString("yyyyMMddHH"); IReadOnlyList<ScheduledMessage> readOnlyList = _getMessages(); for (int i = 0; i < readOnlyList.Count; i++) { ScheduledMessage scheduledMessage = readOnlyList[i]; if (scheduledMessage.Minute == localNow.Minute) { string item = $"{text}:{scheduledMessage.MessageId}:{scheduledMessage.Method}:{scheduledMessage.Minute}"; if (!_firedKeys.Contains(item)) { _firedKeys.Add(item); _dispatch(scheduledMessage); } } } PruneOldState(localNow); } private static float GetSecondsUntilNextMinute(DateTime now) { float num = (float)(new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0).AddMinutes(1.0) - now).TotalSeconds; return Mathf.Max(0.25f, num + 0.05f); } private void PruneOldState(DateTime localNow) { DateTime utcNow = DateTime.UtcNow; if (!(utcNow - _lastPruneUtc < TimeSpan.FromHours(2.0))) { _lastPruneUtc = utcNow; string keepPrefix = localNow.ToString("yyyyMMddHH"); _firedKeys.RemoveWhere((string key) => !key.StartsWith(keepPrefix, StringComparison.Ordinal)); _logInfo($"Scheduler state pruned. Active execution keys: {_firedKeys.Count}"); } } } } namespace AutoBroadcast.Patches { [HarmonyPatch(typeof(ZLog), "Log")] internal static class ZLogFilterPatch { private const string SuppressedSnippet = "Failed to get player info for player Steam_0"; private static bool Prefix(object o) { return !ShouldSuppress(o); } private static bool ShouldSuppress(object o) { string text = o?.ToString(); if (!string.IsNullOrEmpty(text)) { return text.IndexOf("Failed to get player info for player Steam_0", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } } namespace AutoBroadcast.Models { internal enum DeliveryMethod { Alert, Chat, Both } internal sealed class ScheduledMessage { public string MessageId { get; } public int Minute { get; } public DeliveryMethod Method { get; } public string Text { get; } public int AlertDurationSeconds { get; } public ScheduledMessage(string messageId, int minute, DeliveryMethod method, string text, int alertDurationSeconds) { if (string.IsNullOrWhiteSpace(messageId)) { throw new ArgumentException("Message ID must not be empty.", "messageId"); } if ((minute < 0 || minute > 59) ? true : false) { throw new ArgumentOutOfRangeException("minute", "Minute must be between 0 and 59."); } if (string.IsNullOrWhiteSpace(text)) { throw new ArgumentException("Message text must not be empty.", "text"); } if (alertDurationSeconds < 1) { throw new ArgumentOutOfRangeException("alertDurationSeconds", "Alert duration must be at least 1 second."); } MessageId = messageId.Trim(); Minute = minute; Method = method; Text = text.Trim(); AlertDurationSeconds = alertDurationSeconds; } } } namespace AutoBroadcast.Messaging { internal sealed class BroadcastDispatcher { [CompilerGenerated] private sealed class <RepeatAlertForDuration>d__11 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public int seconds; public string text; private int <repeats>5__2; private int <i>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <RepeatAlertForDuration>d__11(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <repeats>5__2 = Mathf.Max(0, Mathf.CeilToInt((float)seconds / 2f) - 1); <i>5__3 = 0; break; case 1: <>1__state = -1; BroadcastCenterAlert(text); <i>5__3++; break; } if (<i>5__3 < <repeats>5__2) { <>2__current = (object)new WaitForSecondsRealtime(2f); <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly Action<string> _logInfo; private readonly Action<string> _logWarning; private readonly MonoBehaviour _coroutineHost; public BroadcastDispatcher(Action<string> logInfo, Action<string> logWarning, MonoBehaviour coroutineHost) { _logInfo = logInfo; _logWarning = logWarning; _coroutineHost = coroutineHost; } public void Dispatch(ScheduledMessage message) { if (message != null && ZNet.instance != null && ZNet.instance.IsServer()) { switch (message.Method) { case DeliveryMethod.Alert: DispatchAlert(message); break; case DeliveryMethod.Chat: DispatchChat(message); break; case DeliveryMethod.Both: DispatchAlert(message); DispatchChat(message); break; default: _logWarning($"Unknown delivery method: {message.Method}"); break; } } } private void DispatchAlert(ScheduledMessage message) { try { BroadcastCenterAlert(message.Text); if (message.AlertDurationSeconds > 2) { _coroutineHost.StartCoroutine(RepeatAlertForDuration(message.Text, message.AlertDurationSeconds)); } _logInfo("Broadcasted alert: " + message.Text); } catch (Exception ex) { _logWarning("Failed to broadcast alert message: " + ex.Message); } } private void DispatchChat(ScheduledMessage message) { try { BroadcastConsoleStyledChatMessage(message.Text, message.Text); _logInfo("Broadcasted chat: " + message.Text); } catch (Exception arg) { _logWarning($"Failed to broadcast chat message: {arg}"); } } private static void BroadcastCenterAlert(string text) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ShowMessage", new object[2] { 2, text }); } } private static void BroadcastConsoleStyledChatMessage(string formattedText, string fallbackText) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance == null) { Debug.Log((object)("[AutoBroadcaster] " + fallbackText)); return; } UserInfo val = CreateServerConsoleUserInfo(); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4] { Vector3.zero, 1, val, formattedText }); } private static UserInfo CreateServerConsoleUserInfo() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown return new UserInfo { Name = "Server", UserId = CreateServerSenderId() }; } private static PlatformUserID CreateServerSenderId() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) PlatformUserID result = default(PlatformUserID); if (PlatformUserID.TryParse("Steam_0", ref result)) { return result; } PlatformUserID result2 = default(PlatformUserID); if (PlatformUserID.TryParse("PlayFab_0", ref result2)) { return result2; } return PlatformUserID.None; } [IteratorStateMachine(typeof(<RepeatAlertForDuration>d__11))] private IEnumerator RepeatAlertForDuration(string text, int seconds) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <RepeatAlertForDuration>d__11(0) { text = text, seconds = seconds }; } } } namespace AutoBroadcast.Config { internal sealed class MessageConfigService : IDisposable { private readonly ConfigFile _configFile; private readonly Action<string> _logInfo; private readonly Action<string> _logWarning; private readonly object _sync = new object(); private FileSystemWatcher? _watcher; private Timer? _reloadDebounceTimer; private ConfigEntry<bool>? _enableBroadcastingEntry; private ConfigEntry<int>? _broadcastCountEntry; private volatile ScheduledMessage[] _messages = Array.Empty<ScheduledMessage>(); private volatile bool _isEnabled = true; public IReadOnlyList<ScheduledMessage> Messages => _messages; public bool IsEnabled => _isEnabled; public MessageConfigService(ConfigFile configFile, Action<string> logInfo, Action<string> logWarning) { _configFile = configFile; _logInfo = logInfo; _logWarning = logWarning; } public void Initialize() { _enableBroadcastingEntry = _configFile.Bind<bool>("Default Settings", "EnableBroadcasting", true, "Enable or disable scheduled automatic broadcasts."); _broadcastCountEntry = _configFile.Bind<int>("Default Settings", "AutoBroadcastCount", 3, "The number of message sections to be loaded from config."); LoadMessagesFromConfig(); ConfigureWatcher(); } public void Dispose() { lock (_sync) { _reloadDebounceTimer?.Dispose(); _reloadDebounceTimer = null; if (_watcher != null) { _watcher.Changed -= OnConfigFileChanged; _watcher.Created -= OnConfigFileChanged; _watcher.Renamed -= OnConfigFileChanged; _watcher.Dispose(); _watcher = null; } } } private void ConfigureWatcher() { string configFilePath = _configFile.ConfigFilePath; string directoryName = Path.GetDirectoryName(configFilePath); string fileName = Path.GetFileName(configFilePath); if (string.IsNullOrWhiteSpace(directoryName) || string.IsNullOrWhiteSpace(fileName)) { _logWarning("Could not configure config file watcher. Live reload is disabled."); return; } lock (_sync) { _watcher = new FileSystemWatcher(directoryName, fileName) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime), IncludeSubdirectories = false, EnableRaisingEvents = true }; _watcher.Changed += OnConfigFileChanged; _watcher.Created += OnConfigFileChanged; _watcher.Renamed += OnConfigFileChanged; _reloadDebounceTimer = new Timer(delegate { ReloadFromDisk(); }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); } } private void OnConfigFileChanged(object sender, FileSystemEventArgs e) { lock (_sync) { _reloadDebounceTimer?.Change(TimeSpan.FromMilliseconds(300.0), Timeout.InfiniteTimeSpan); } } private void ReloadFromDisk() { try { _configFile.Reload(); LoadMessagesFromConfig(); _logInfo($"Config reloaded. Parsed {_messages.Length} scheduled messages."); } catch (Exception ex) { _logWarning("Failed to reload config from disk: " + ex.Message); } } private void LoadMessagesFromConfig() { if (_broadcastCountEntry == null || _enableBroadcastingEntry == null) { _messages = Array.Empty<ScheduledMessage>(); return; } _isEnabled = _enableBroadcastingEntry.Value; int num = Math.Max(0, _broadcastCountEntry.Value); List<ScheduledMessage> list = new List<ScheduledMessage>(num); for (int i = 1; i <= num; i++) { string text = $"Message {i:00}"; if (!TryLoadMessageSection(text, out ScheduledMessage message2, out string error)) { _logWarning("Skipped invalid section '" + text + "': " + error); } else { list.Add(message2); } } _messages = list.OrderBy((ScheduledMessage message) => message.Minute).ThenBy<ScheduledMessage, string>((ScheduledMessage message) => message.MessageId, StringComparer.Ordinal).ThenBy((ScheduledMessage message) => message.Method) .ToArray(); } private bool TryLoadMessageSection(string sectionName, out ScheduledMessage? message, out string error) { message = null; error = string.Empty; ConfigEntry<string> val = _configFile.Bind<string>(sectionName, "MessageText", GetDefaultMessageText(sectionName), "The message to be displayed."); ConfigEntry<string> val2 = _configFile.Bind<string>(sectionName, "MessageType", GetDefaultMessageType(sectionName), "The message type (Chat, Alert, or Both)."); ConfigEntry<int> obj = _configFile.Bind<int>(sectionName, "ScheduledMinuteOfHour", GetDefaultMessageMinute(sectionName), "Minute of each hour to broadcast (0-59)."); ConfigEntry<int> val3 = _configFile.Bind<int>(sectionName, "MessageTime", 5, "Amount of time the Alert stays on screen (in seconds)."); int value = obj.Value; if ((value < 0 || value > 59) ? true : false) { error = "Minute must be a number between 0 and 59."; return false; } if (!Enum.TryParse<DeliveryMethod>(val2.Value.Trim(), ignoreCase: true, out var result)) { error = "MessageType must be Alert, Chat, or Both."; return false; } string text = val.Value.Trim(); if (string.IsNullOrWhiteSpace(text)) { error = "MessageText is empty."; return false; } if (val3.Value < 1) { error = "MessageTime must be at least 1."; return false; } message = new ScheduledMessage(sectionName, value, result, text, val3.Value); return true; } private static string GetDefaultMessageText(string sectionName) { return sectionName switch { "Message 01" => "Welcome to the server! Be respectful and have fun.", "Message 02" => "Join Our Discord To Be Part of The Community.", "Message 03" => "Thank-You For Downloading. Please Edit Your Configs.", _ => "Edit this message.", }; } private static string GetDefaultMessageType(string sectionName) { return sectionName switch { "Message 01" => "Alert", "Message 02" => "Chat", "Message 03" => "Both", _ => "Chat", }; } private static int GetDefaultMessageMinute(string sectionName) { return sectionName switch { "Message 01" => 15, "Message 02" => 30, "Message 03" => 45, _ => 0, }; } } }