using 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();
}
}
}