Decompiled source of NerdyGamerTools AutoBroadcaster v3.0.0

NGT.AutoBroadcaster.dll

Decompiled a month ago
using 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;
			}
		}
	}
}