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 v3.0.0
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 BepInEx; using BepInEx.Configuration; 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("NGT.AutoBroadcaster")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0+6e668b66873743bc9f443b065e2b60cd36723f9e")] [assembly: AssemblyProduct("NGT.AutoBroadcaster")] [assembly: AssemblyTitle("NGT.AutoBroadcaster")] [assembly: AssemblyVersion("2.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 NGT.AutoBroadcaster { internal sealed class BroadcastDispatcher { private const string ChatSenderName = "Server"; private const string ChatSenderId = "NGTAutoBroadcaster_0"; private readonly MonoBehaviour _coroutineHost; private readonly Action<string> _logInfo; private readonly Action<string> _logWarning; private readonly Func<bool> _debugLogging; private readonly MethodInfo _sendPlayerListMethod; private bool _chatWarningLogged; internal BroadcastDispatcher(MonoBehaviour coroutineHost, Action<string> logInfo, Action<string> logWarning, Func<bool> debugLogging) { _coroutineHost = coroutineHost; _logInfo = logInfo; _logWarning = logWarning; _debugLogging = debugLogging; try { _sendPlayerListMethod = typeof(ZNet).GetMethod("SendPlayerList", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); } catch { _sendPlayerListMethod = null; } } internal void Dispatch(ScheduledMessage message) { if (message != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { switch (message.Method) { case DeliveryMethod.Alert: SendAlert(message); break; case DeliveryMethod.Chat: SendChat(message.Text); break; case DeliveryMethod.Both: SendAlert(message); SendChat(message.Text); break; default: _logWarning("Unknown delivery method for " + message.Id + "."); break; } } } private void SendAlert(ScheduledMessage message) { try { BroadcastCenterAlert(message.Text); if (message.AlertDurationSeconds > 2) { _coroutineHost.StartCoroutine(RepeatAlert(message.Text, message.AlertDurationSeconds)); } _logInfo("AutoBroadcaster alert: " + message.Text); } catch (Exception exception) { _logWarning("AutoBroadcaster alert failed: " + Describe(exception)); } } private IEnumerator RepeatAlert(string text, int seconds) { int refreshes = Math.Max(0, Mathf.CeilToInt((float)seconds / 2f) - 1); for (int i = 0; i < refreshes; i++) { yield return (object)new WaitForSecondsRealtime(2f); BroadcastCenterAlert(text); } } private static void BroadcastCenterAlert(string text) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text }); } } private void SendChat(string text) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || ZRoutedRpc.instance == null) { return; } List<ZNetPeer> list = new List<ZNetPeer>(); List<ZNetPeer> peers = instance.GetPeers(); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val != null && val.IsReady() && val.m_rpc != null && val.m_rpc.IsConnected()) { list.Add(val); } } if (list.Count == 0) { return; } ZPackage val2 = BuildPlayerList(instance.GetPlayerList(), includeSyntheticServer: true); UserInfo val3 = new UserInfo(); val3.Name = "Server"; val3.UserId = new PlatformUserID("NGTAutoBroadcaster_0"); try { for (int j = 0; j < list.Count; j++) { ZNetPeer val4 = list[j]; if (val4.m_rpc != null && val4.m_rpc.IsConnected()) { val4.m_rpc.Invoke("PlayerList", new object[1] { val2 }); ZRoutedRpc.instance.InvokeRoutedRPC(val4.m_uid, "ChatMessage", new object[4] { Vector3.zero, 1, val3, text }); } } } finally { RestorePlayerList(instance, list); } _logInfo("AutoBroadcaster chat: " + text); } catch (Exception exception) { if (!_chatWarningLogged || _debugLogging()) { _chatWarningLogged = true; _logWarning("AutoBroadcaster chat failed. Alert delivery is unaffected: " + Describe(exception)); } } } private unsafe ZPackage BuildPlayerList(List<PlayerInfo> players, bool includeSyntheticServer) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); val.Write(players.Count + (includeSyntheticServer ? 1 : 0)); for (int i = 0; i < players.Count; i++) { PlayerInfo val2 = players[i]; WritePlayerEntry(val, val2.m_name, val2.m_characterID, ((object)(*(PlatformUserID*)(&val2.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString(), val2.m_userInfo.m_displayName, val2.m_userInfo.m_serverAssignedDisplayName, val2.m_userInfo.m_playfabId, val2.m_publicPosition, val2.m_position); } if (includeSyntheticServer) { WritePlayerEntry(val, "Server", ZDOID.None, "NGTAutoBroadcaster_0", "Server", string.Empty, string.Empty, publicPosition: false, Vector3.zero); } return val; } private void RestorePlayerList(ZNet znet, List<ZNetPeer> recipients) { if (_sendPlayerListMethod != null) { try { _sendPlayerListMethod.Invoke(znet, null); return; } catch (Exception exception) { if (_debugLogging()) { _logWarning("Vanilla SendPlayerList restore failed; using fallback: " + Describe(exception)); } } } try { ZPackage val = BuildPlayerList(znet.GetPlayerList(), includeSyntheticServer: false); for (int i = 0; i < recipients.Count; i++) { ZNetPeer val2 = recipients[i]; if (val2.m_rpc != null && val2.m_rpc.IsConnected()) { val2.m_rpc.Invoke("PlayerList", new object[1] { val }); } } } catch (Exception exception2) { if (_debugLogging()) { _logWarning("Player-list fallback restore failed: " + Describe(exception2)); } } } private static void WritePlayerEntry(ZPackage package, string name, ZDOID characterId, string platformId, string displayName, string serverAssignedDisplayName, string playfabId, bool publicPosition, Vector3 position) { //IL_0011: 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) package.Write(name ?? string.Empty); package.Write(characterId); package.Write(platformId ?? string.Empty); package.Write(displayName ?? string.Empty); package.Write(serverAssignedDisplayName ?? string.Empty); package.Write(playfabId ?? string.Empty); package.Write(publicPosition); if (publicPosition) { package.Write(position); } } private static string Describe(Exception exception) { Exception ex = exception; while (ex is TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } return ex.GetType().Name + ": " + ex.Message; } } internal sealed class MessageConfigService : IDisposable { private readonly ConfigFile _configFile; private readonly Action<string> _logInfo; private readonly Action<string> _logWarning; private readonly object _reloadLock = new object(); private ConfigEntry<bool> _enabledEntry; private ConfigEntry<int> _countEntry; private ConfigEntry<bool> _debugEntry; private FileSystemWatcher _watcher; private bool _reloadRequested; private DateTime _reloadAfterUtc; private ScheduledMessage[] _messages = Array.Empty<ScheduledMessage>(); internal bool IsEnabled { get; private set; } internal bool DebugLogging { get; private set; } internal IReadOnlyList<ScheduledMessage> Messages => _messages; internal MessageConfigService(ConfigFile configFile, Action<string> logInfo, Action<string> logWarning) { _configFile = configFile; _logInfo = logInfo; _logWarning = logWarning; } internal void Initialize() { _enabledEntry = _configFile.Bind<bool>("Default Settings", "EnableBroadcasting", true, "Enable or disable all scheduled automatic broadcasts."); _countEntry = _configFile.Bind<int>("Default Settings", "AutoBroadcastCount", 3, "Number of [Message XX] sections to load."); _debugEntry = _configFile.Bind<bool>("Default Settings", "DebugLogging", false, "Write extra scheduler and delivery information to the BepInEx log."); LoadMessages(); StartWatcher(); } internal void ReloadIfRequested() { bool flag = false; lock (_reloadLock) { if (_reloadRequested && DateTime.UtcNow >= _reloadAfterUtc) { _reloadRequested = false; flag = true; } } if (!flag) { return; } try { _configFile.Reload(); LoadMessages(); _logInfo("AutoBroadcaster config reloaded. Loaded " + _messages.Length + " valid message section(s)."); } catch (Exception ex) { _logWarning("AutoBroadcaster config reload failed: " + ex.Message); } } private void StartWatcher() { string configFilePath = _configFile.ConfigFilePath; string directoryName = Path.GetDirectoryName(configFilePath); string fileName = Path.GetFileName(configFilePath); if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(fileName)) { _logWarning("Config path could not be resolved. Live reload is disabled."); return; } _watcher = new FileSystemWatcher(directoryName, fileName); _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime; _watcher.Changed += OnConfigChanged; _watcher.Created += OnConfigChanged; _watcher.Renamed += OnConfigChanged; _watcher.EnableRaisingEvents = true; } private void OnConfigChanged(object sender, FileSystemEventArgs args) { lock (_reloadLock) { _reloadRequested = true; _reloadAfterUtc = DateTime.UtcNow.AddMilliseconds(500.0); } } private void LoadMessages() { IsEnabled = _enabledEntry != null && _enabledEntry.Value; DebugLogging = _debugEntry != null && _debugEntry.Value; int num = ((_countEntry != null) ? Math.Max(0, _countEntry.Value) : 0); List<ScheduledMessage> list = new List<ScheduledMessage>(num); for (int i = 1; i <= num; i++) { string text = "Message " + i.ToString("00"); if (TryReadMessage(text, out var message, out var error)) { list.Add(message); } else { _logWarning("Skipping [" + text + "]: " + error); } } _messages = list.OrderBy((ScheduledMessage scheduledMessage) => scheduledMessage.MinuteOfHour).ThenBy<ScheduledMessage, string>((ScheduledMessage scheduledMessage) => scheduledMessage.Id, StringComparer.Ordinal).ToArray(); } private bool TryReadMessage(string section, out ScheduledMessage message, out string error) { message = null; error = string.Empty; ConfigEntry<bool> val = _configFile.Bind<bool>(section, "Enabled", true, "Enable or disable this individual broadcast."); ConfigEntry<string> val2 = _configFile.Bind<string>(section, "MessageText", DefaultText(section), "Message displayed to players. Valheim rich-text tags may be used where the target UI supports them."); ConfigEntry<string> val3 = _configFile.Bind<string>(section, "MessageType", DefaultMethod(section), "Alert, Chat, or Both."); ConfigEntry<int> val4 = _configFile.Bind<int>(section, "ScheduledMinuteOfHour", DefaultMinute(section), "Minute of every real-world server hour when this message is sent (0-59)."); ConfigEntry<int> val5 = _configFile.Bind<int>(section, "MessageTime", 5, "Requested center-alert duration in seconds. Values above 2 are maintained by refreshing the vanilla alert."); if (val4.Value < 0 || val4.Value > 59) { error = "ScheduledMinuteOfHour must be between 0 and 59."; return false; } if (!Enum.TryParse<DeliveryMethod>(val3.Value.Trim(), ignoreCase: true, out var result)) { error = "MessageType must be Alert, Chat, or Both."; return false; } if (string.IsNullOrWhiteSpace(val2.Value)) { error = "MessageText cannot be empty."; return false; } if (val5.Value < 1) { error = "MessageTime must be at least 1 second."; return false; } message = new ScheduledMessage(section, val.Value, val4.Value, result, val2.Value, val5.Value); return true; } private static string DefaultText(string section) { return section 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 playing!", _ => "Edit this message.", }; } private static string DefaultMethod(string section) { return section switch { "Message 01" => "Alert", "Message 02" => "Chat", "Message 03" => "Both", _ => "Alert", }; } private static int DefaultMinute(string section) { return section switch { "Message 01" => 15, "Message 02" => 30, "Message 03" => 45, _ => 0, }; } public void Dispose() { if (_watcher != null) { _watcher.EnableRaisingEvents = false; _watcher.Changed -= OnConfigChanged; _watcher.Created -= OnConfigChanged; _watcher.Renamed -= OnConfigChanged; _watcher.Dispose(); _watcher = null; } } } internal sealed class MessageScheduler { private readonly Func<IReadOnlyList<ScheduledMessage>> _getMessages; private readonly Action<ScheduledMessage> _dispatch; private readonly Action<string> _logDebug; private readonly HashSet<string> _fired = new HashSet<string>(StringComparer.Ordinal); private string _activeHourKey = string.Empty; internal MessageScheduler(Func<IReadOnlyList<ScheduledMessage>> getMessages, Action<ScheduledMessage> dispatch, Action<string> logDebug) { _getMessages = getMessages; _dispatch = dispatch; _logDebug = logDebug; } internal void Tick(DateTime localNow) { string text = localNow.ToString("yyyyMMddHH"); if (!string.Equals(_activeHourKey, text, StringComparison.Ordinal)) { _activeHourKey = text; _fired.Clear(); _logDebug("Scheduler entered hour " + text + "."); } IReadOnlyList<ScheduledMessage> readOnlyList = _getMessages(); for (int i = 0; i < readOnlyList.Count; i++) { ScheduledMessage scheduledMessage = readOnlyList[i]; if (scheduledMessage.Enabled && scheduledMessage.MinuteOfHour == localNow.Minute) { string item = text + "|" + scheduledMessage.Id; if (_fired.Add(item)) { _dispatch(scheduledMessage); } } } } } internal enum DeliveryMethod { Alert, Chat, Both } internal sealed class ScheduledMessage { internal string Id { get; private set; } internal bool Enabled { get; private set; } internal int MinuteOfHour { get; private set; } internal DeliveryMethod Method { get; private set; } internal string Text { get; private set; } internal int AlertDurationSeconds { get; private set; } internal ScheduledMessage(string id, bool enabled, int minuteOfHour, DeliveryMethod method, string text, int alertDurationSeconds) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentException("Message id is required.", "id"); } if (minuteOfHour < 0 || minuteOfHour > 59) { throw new ArgumentOutOfRangeException("minuteOfHour"); } if (string.IsNullOrWhiteSpace(text)) { throw new ArgumentException("Message text is required.", "text"); } if (alertDurationSeconds < 1) { throw new ArgumentOutOfRangeException("alertDurationSeconds"); } Id = id.Trim(); Enabled = enabled; MinuteOfHour = minuteOfHour; Method = method; Text = text.Trim(); AlertDurationSeconds = alertDurationSeconds; } } [BepInPlugin("NGT_Autobroadcaster", "Nerdy Gamer Tools AutoBroadcaster", "2.0.0")] public sealed class AutoBroadcasterPlugin : BaseUnityPlugin { public const string PluginGuid = "NGT_Autobroadcaster"; public const string PluginName = "Nerdy Gamer Tools AutoBroadcaster"; public const string PluginVersion = "2.0.0"; private MessageConfigService _configService; private MessageScheduler _scheduler; private BroadcastDispatcher _dispatcher; private float _nextSchedulerTick; private void Awake() { _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(); _dispatcher = new BroadcastDispatcher((MonoBehaviour)(object)this, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }, () => _configService.DebugLogging); _scheduler = new MessageScheduler(() => (!_configService.IsEnabled) ? Array.Empty<ScheduledMessage>() : _configService.Messages, delegate(ScheduledMessage message) { _dispatcher.Dispatch(message); }, delegate(string message) { if (_configService.DebugLogging) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } }); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Nerdy Gamer Tools AutoBroadcaster 2.0.0 loaded. Waiting for the dedicated server network to become ready."); } private void Update() { if (_configService == null || _scheduler == null) { return; } _configService.ReloadIfRequested(); if (Application.isBatchMode && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextSchedulerTick)) { _nextSchedulerTick = realtimeSinceStartup + 0.5f; _scheduler.Tick(DateTime.Now); } } } private void OnDestroy() { if (_configService != null) { _configService.Dispose(); _configService = null; } } } }