Please disclose if your mod was created primarily 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 ServerRestart v1.1.5
ServerRestart.dll
Decompiled 11 months agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DiscordTools; using HarmonyLib; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Server Restart")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Server Restart")] [assembly: AssemblyCopyright("Copyright © 2022")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("d50b60f0-8c9a-42f8-84a4-7d602fe5283b")] [assembly: AssemblyFileVersion("1.1.5")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.5.0")] [module: UnverifiableCode] internal class Helper { public static bool IsDebugBuild => false; public static void WatchConfigFileChanges(ConfigFile config, Action onChanged = null) { WatchFileChanges(config.ConfigFilePath, (Action)config.Reload); config.SettingChanged += delegate { onChanged?.Invoke(); }; } public static void WatchFileChanges(string path, Action onChanged) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); string directoryName = Path.GetDirectoryName(path); string fileName = Path.GetFileName(path); fileSystemWatcher.Path = directoryName; fileSystemWatcher.Filter = fileName; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite; fileSystemWatcher.Changed += delegate { onChanged?.Invoke(); }; fileSystemWatcher.Deleted += delegate { onChanged?.Invoke(); }; fileSystemWatcher.Created += delegate { onChanged?.Invoke(); }; fileSystemWatcher.Renamed += delegate { onChanged?.Invoke(); }; fileSystemWatcher.EnableRaisingEvents = true; } public static void WatchFolderChanges(string path, Action onChanged) { WatchFileChanges(Path.Combine(path, "*.*"), onChanged); } } internal class Log { private static Log _instance; private ManualLogSource _source; public static Log CreateInstance(ManualLogSource source) { _instance = new Log { _source = source }; return _instance; } private Log() { } public static void Info(object msg) { _instance._source.LogInfo((object)FormatMessage(msg)); } public static void Message(object msg) { _instance._source.LogMessage((object)FormatMessage(msg)); } public static void Debug(object msg) { _instance._source.LogDebug((object)FormatMessage(msg)); } public static void Warning(object msg) { _instance._source.LogWarning((object)FormatMessage(msg)); } public static void Error(object msg) { _instance._source.LogError((object)FormatMessage(msg)); } public static void Fatal(object msg) { _instance._source.LogFatal((object)FormatMessage(msg)); } private static string FormatMessage(object msg) { return $"[{DateTime.UtcNow}] {msg}"; } } internal static class ThreadingUtil { private class DisposableThread : IDisposable { private Thread _thread; internal DisposableThread(Thread thread) { _thread = thread; } public void Dispose() { _thread.Abort(); } } private class MainThreadDispatcher : MonoBehaviour { private static MainThreadDispatcher _instance; private ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>(); private ConcurrentQueue<IEnumerator> _coroutinesQueue = new ConcurrentQueue<IEnumerator>(); public static MainThreadDispatcher GetInstante() { //IL_0025: 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_0030: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("MainThreadDispatcher", new Type[1] { typeof(MainThreadDispatcher) }); Object.DontDestroyOnLoad((Object)val); _instance = val.GetComponent<MainThreadDispatcher>(); } return _instance; } public void AddAction(Action action) { _queue.Enqueue(action); } public void AddCoroutine(IEnumerator coroutine) { _coroutinesQueue.Enqueue(coroutine); } private void Update() { Action result; while (_queue.Count > 0 && _queue.TryDequeue(out result)) { result?.Invoke(); } IEnumerator result2; while (_coroutinesQueue.Count > 0 && _coroutinesQueue.TryDequeue(out result2)) { ((MonoBehaviour)this).StartCoroutine(result2); } } } internal static IDisposable RunPeriodical(Action action, int periodMilliseconds) { return new Timer(delegate { action?.Invoke(); }, null, 0, periodMilliseconds); } internal static IDisposable RunPeriodicalInSingleThread(Action action, int periodMilliseconds) { Thread thread = new Thread((ParameterizedThreadStart)delegate { while (true) { action?.Invoke(); Thread.Sleep(periodMilliseconds); } }); thread.Start(); return new DisposableThread(thread); } internal static void RunInMainThread(Action action) { MainThreadDispatcher.GetInstante().AddAction(action); } internal static void RunCoroutine(IEnumerator coroutine) { MainThreadDispatcher.GetInstante().AddCoroutine(coroutine); } internal static void RunDelayed(float delay, Action action) { MainThreadDispatcher.GetInstante().AddCoroutine(DelayedActionCoroutine(delay, action)); } internal static IDisposable RunThread(Action action) { Thread thread = new Thread(action.Invoke); thread.Start(); return new DisposableThread(thread); } internal static IEnumerator DelayedActionCoroutine(float delay, Action action) { yield return (object)new WaitForSeconds(delay); action?.Invoke(); } } namespace ServerRestart { internal class MaintenanceService : MonoBehaviour { private string _maintenanceFilePath; private void Awake() { PluginInfo val = ((IEnumerable<PluginInfo>)Chainloader.PluginInfos.Values).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo p) => p.Metadata.GUID == "org.bepinex.plugins.servercharacters")); if (val != null) { string directoryName = Path.GetDirectoryName(val.Location); Log.Debug("ServerCharacters mod found at " + directoryName); _maintenanceFilePath = Path.Combine(directoryName, "maintenance"); } } private void OnEnable() { RestartService.OnScheduledRestartChanged += OnRestartDateChanged; } private void OnDisable() { RestartService.OnScheduledRestartChanged -= OnRestartDateChanged; } private void OnRestartDateChanged(DateTime date) { if (!Plugin.EnableMaintenance.Value) { return; } if (string.IsNullOrEmpty(_maintenanceFilePath)) { Log.Error("Cannot enable maintenance. ServerCharacters mod not found!"); return; } ((MonoBehaviour)this).StopAllCoroutines(); RemoveMaintenance(); if (!(date == default(DateTime))) { ((MonoBehaviour)this).StartCoroutine(ScheduleMaintenance(date.Subtract(TimeSpan.FromMinutes(Plugin.MaintenanceMinutes.Value)))); } } private void RemoveMaintenance() { if (File.Exists(_maintenanceFilePath)) { File.Delete(_maintenanceFilePath); Log.Info("Maintenance disabled"); } } private IEnumerator ScheduleMaintenance(DateTime date) { yield return (object)new WaitUntil((Func<bool>)(() => DateTime.UtcNow >= date)); using (File.Create(_maintenanceFilePath)) { } Log.Info("Maintenance started"); } } [BepInProcess("valheim_server.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("org.tristan.serverrestart", "Server Restart", "1.1.5")] internal class Plugin : BaseUnityPlugin { [HarmonyPatch] private class Patch { [HarmonyPostfix] [HarmonyPatch(typeof(Game), "Start")] private static void Game_Start(Game __instance) { _service = ((Component)__instance).gameObject.AddComponent<RestartService>(); ((Component)__instance).gameObject.AddComponent<MaintenanceService>(); ((Component)__instance).gameObject.AddComponent<RestartMessages>(); ((Component)__instance).gameObject.AddComponent<RestartScheduleLogService>(); } [HarmonyPrefix] [HarmonyPatch(typeof(ZNet), "CheckForIncommingServerConnections")] private static bool ZNet_CheckForIncommingServerConnections() { if (_service.RestartStarted || ((Object)(object)Game.instance != (Object)null && Game.instance.IsShuttingDown())) { return false; } return true; } [HarmonyPriority(0)] [HarmonyFinalizer] [HarmonyPatch(typeof(Game), "OnApplicationQuit")] private static void Game_OnApplicationQuit() { Thread.Sleep(5000); Process.GetCurrentProcess().Kill(); } [HarmonyPostfix] [HarmonyPatch(typeof(ZNet), "UpdatePlayerList")] private static void ZNet_UpdatePlayerList(ZNet __instance) { //IL_0000: 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) //IL_0039: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) PlayerInfo val = default(PlayerInfo); if (!ZNet.TryGetPlayerByPlatformUserID(RestartMessages.ServerUserId, ref val) && __instance.m_players.Count != 0) { val = default(PlayerInfo); val.m_name = ChatName.Value; val.m_userInfo = new CrossNetworkUserInfo { m_displayName = ChatName.Value, m_id = RestartMessages.ServerUserId }; val.m_serverAssignedDisplayName = ChatName.Value; PlayerInfo item = val; __instance.m_players.Add(item); } } } public const string Guid = "org.tristan.serverrestart"; public const string Name = "Server Restart"; public const string Version = "1.1.5"; public static ConfigEntry<string> RestartTimes; public static ConfigEntry<string> Message1Hour; public static ConfigEntry<string> Message30Mins; public static ConfigEntry<string> Message10Mins; public static ConfigEntry<string> Message5Min; public static ConfigEntry<string> Message4Min; public static ConfigEntry<string> Message3Min; public static ConfigEntry<string> Message2Min; public static ConfigEntry<string> Message1Min; public static ConfigEntry<string> AnounceFormat; public static ConfigEntry<string> ChatName; public static ConfigEntry<bool> SendMessagesToChat; public static ConfigEntry<string> ChatFormat; public static ConfigEntry<string> DiscordUrl; public static ConfigEntry<string> DiscordName; public static ConfigEntry<string> DiscordFormat; public static ConfigEntry<bool> ShutDownServer; public static ConfigEntry<bool> EnableMaintenance; public static ConfigEntry<int> MaintenanceMinutes; public static ConfigEntry<bool> PrintLogs; public static ConfigEntry<int> PrintLogsPeriod; private static RestartService _service; private void Awake() { Log.CreateInstance(((BaseUnityPlugin)this).Logger); RestartTimes = ((BaseUnityPlugin)this).Config.Bind<string>("1. Restart", "Schedule (utc)", "23:00:00,11:00:00", "Restart times divied by ,"); ShutDownServer = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Restart", "Shut down", true, "Should plugin shut down server process. Disable if you use hosting restart schedule or another plugin"); Message1Hour = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "1 hour", "Server restart in 1 hour", (ConfigDescription)null); Message30Mins = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "30 minutes", "Server restart in 30 minutes", (ConfigDescription)null); Message10Mins = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "10 minutes", "Server restart in 10 minutes", (ConfigDescription)null); Message5Min = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "5 minutes", "Server restart in 5 minutes", (ConfigDescription)null); Message4Min = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "4 minutes", "Server restart in 4 minutes", (ConfigDescription)null); Message3Min = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "3 minutes", "Server restart in 3 minutes", (ConfigDescription)null); Message2Min = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "2 minutes", "Server restart in 2 minutes", (ConfigDescription)null); Message1Min = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "1 minute", "Server restart in 1 minute", (ConfigDescription)null); AnounceFormat = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "Anounce format", "{0}", "Format of center screen message"); ChatName = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "Chat name", "Restart", (ConfigDescription)null); SendMessagesToChat = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Messages", "Send to chat", true, (ConfigDescription)null); ChatFormat = ((BaseUnityPlugin)this).Config.Bind<string>("2. Messages", "Chat format", "{0}", "Format of chat message"); EnableMaintenance = ((BaseUnityPlugin)this).Config.Bind<bool>("3. ServerCharacters", "Maintenance mode", false, "Should enable maintenance mode for ServerCharacters"); MaintenanceMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("3. ServerCharacters", "Maintenance time", 7, "Enable maintenance before scheduled server restart time"); DiscordUrl = ((BaseUnityPlugin)this).Config.Bind<string>("4. Discord", "Webhook", "", (ConfigDescription)null); DiscordName = ((BaseUnityPlugin)this).Config.Bind<string>("4. Discord", "Display name", "Restart", (ConfigDescription)null); DiscordFormat = ((BaseUnityPlugin)this).Config.Bind<string>("4. Discord", "Discord format", "{0}", "Format of discord message"); PrintLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("5. Logs", "Print logs", false, (ConfigDescription)null); PrintLogsPeriod = ((BaseUnityPlugin)this).Config.Bind<int>("5. Logs", "Period", 600, "Period for displaying date of next restart and remaining time"); Helper.WatchConfigFileChanges(((BaseUnityPlugin)this).Config, OnConfigChanged); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "org.tristan.serverrestart"); } private void OnConfigChanged() { Log.Message("Config reloaded"); ((BaseUnityPlugin)this).Config.Reload(); _service?.ScheduleNextRestart(); } } public class RestartMessages : MonoBehaviour { public static readonly PlatformUserID ServerUserId = new PlatformUserID("Bot", 0uL, false); private static UserInfo User = new UserInfo { UserId = ServerUserId, Name = string.Empty }; public static event Action<string> OnMessageSent; private void OnEnable() { RestartService.OnScheduledRestartChanged += ScheduleRestartMessages; } private void OnDisable() { RestartService.OnScheduledRestartChanged -= ScheduleRestartMessages; } private void ScheduleRestartMessages(DateTime date) { ((MonoBehaviour)this).StopAllCoroutines(); if (!(date == default(DateTime))) { ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromHours(1.0)), Plugin.Message1Hour.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(30.0)), Plugin.Message30Mins.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(10.0)), Plugin.Message10Mins.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(5.0)), Plugin.Message5Min.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(4.0)), Plugin.Message4Min.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(3.0)), Plugin.Message3Min.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(2.0)), Plugin.Message2Min.Value)); ((MonoBehaviour)this).StartCoroutine(ScheduleMessage(date.Subtract(TimeSpan.FromMinutes(1.0)), Plugin.Message1Min.Value)); } } private IEnumerator ScheduleMessage(DateTime date, string message) { if (!(DateTime.UtcNow > date) && !string.IsNullOrEmpty(message)) { Log.Debug($"Schedule message '{message}' at {date}"); yield return (object)new WaitUntil((Func<bool>)(() => DateTime.UtcNow >= date)); Log.Debug("Sending message '" + message + "'"); SendMessageToAll(message); SendMessageToDiscord(message); SendMessageToChat(message); RestartMessages.OnMessageSent?.Invoke(message); } } private void SendMessageToAll(string message) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ShowMessage", new object[2] { 2, string.Format(Plugin.AnounceFormat.Value, message) }); } private void SendMessageToDiscord(string message) { string webhookUrl = Plugin.DiscordUrl.Value; if (!string.IsNullOrEmpty(Plugin.DiscordUrl.Value)) { string displayName = Plugin.DiscordName.Value; ThreadingUtil.RunThread(delegate { DiscordTool.SendMessageToDiscord(webhookUrl, displayName, string.Format(Plugin.DiscordFormat.Value, message)); }); } } private void SendMessageToChat(string message) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (Plugin.SendMessagesToChat.Value) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4] { (object)new Vector3(0f, 200f, 0f), 2, User, string.Format(Plugin.ChatFormat.Value, message) }); } } } internal class RestartScheduleLogService : MonoBehaviour { private DateTime _nextRestartDate; private void OnEnable() { RestartService.OnScheduledRestartChanged += ScheduleLogMessages; } private void OnDisable() { RestartService.OnScheduledRestartChanged -= ScheduleLogMessages; } private void ScheduleLogMessages(DateTime time) { _nextRestartDate = time; PrintLogMessage(); ((MonoBehaviour)this).CancelInvoke(); if (Plugin.PrintLogs.Value) { int value = Plugin.PrintLogsPeriod.Value; ((MonoBehaviour)this).InvokeRepeating("PrintLogMessage", (float)value, (float)value); } } private void PrintLogMessage() { DateTime utcNow = DateTime.UtcNow; TimeSpan timeSpan = _nextRestartDate - utcNow; if (_nextRestartDate != default(DateTime)) { Log.Message($"Next restart {_nextRestartDate}. Time left: {timeSpan}"); } else { Log.Message("No scheduled restarts"); } } } public class RestartService : MonoBehaviour { public DateTime NextRestartDate { get; private set; } public bool RestartStarted { get; private set; } public static event Action<DateTime> OnScheduledRestartChanged; private void Start() { ScheduleNextRestart(); } public void ScheduleNextRestart() { ((MonoBehaviour)this).StopAllCoroutines(); RestartStarted = false; string[] array = Plugin.RestartTimes.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { NextRestartDate = default(DateTime); RestartService.OnScheduledRestartChanged?.Invoke(NextRestartDate); return; } NextRestartDate = GetNextRestartDate(array); if (Plugin.ShutDownServer.Value) { ((MonoBehaviour)this).StartCoroutine(ScheduleRestart(NextRestartDate)); } RestartService.OnScheduledRestartChanged?.Invoke(NextRestartDate); } private IEnumerator ScheduleRestart(DateTime date) { yield return (object)new WaitUntil((Func<bool>)(() => DateTime.UtcNow >= date)); Log.Message("Starting restart. Disconnecting players"); ZNet.instance.SendDisconnect(); RestartStarted = true; yield return (object)new WaitWhile((Func<bool>)(() => ZNet.instance.GetPeers().Count > 0)); yield return (object)new WaitForSeconds(5f); Log.Message("Shutting down server"); Game.instance.Shutdown(true); yield return (object)new WaitForSeconds(10f); Log.Message("Stopping server"); Application.Quit(); } private DateTime GetNextRestartDate(IEnumerable<string> schedule) { DateTime nowDate = DateTime.UtcNow; return (from date in schedule.Select(delegate(string timeText) { TimeSpan value = TimeSpan.Parse(timeText); DateTime dateTime = new DateTime(nowDate.Year, nowDate.Month, nowDate.Day).Add(value); if (dateTime < nowDate) { dateTime = dateTime.AddDays(1.0); } return dateTime; }) orderby date - nowDate select date).FirstOrDefault(); } } } namespace DiscordTools { internal static class DiscordTool { public static void SendMessageToDiscord(string url, string name, string message) { HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url); httpWebRequest.ContentType = "application/json"; httpWebRequest.Method = "POST"; using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream())) { string value = "{\"username\":\"" + name + "\",\"content\":\"" + message + "\"}"; streamWriter.Write(value); } httpWebRequest.GetResponse(); } } }