using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using AtlyssDedicatedServer.HarmonyPatches; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Steamworks; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("BepInEx")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("FleesDedicatedServer")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.1.2.0")] [assembly: AssemblyInformationalVersion("0.1.2+a8aa761b1b3601ec81c1673c93c03a0e3050390b")] [assembly: AssemblyProduct("DedicatedServer")] [assembly: AssemblyTitle("FleesDedicatedServer")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 AtlyssDedicatedServer { public class ConsoleListenerWithInput(ConsoleLogListener original) : ILogListener, IDisposable { public readonly StringBuilder Input = new StringBuilder(); private static readonly object _consoleLock = new object(); private static string WindowWidthString = ""; public void Dispose() { original.Dispose(); } public void LogEvent(object sender, LogEventArgs eventArgs) { ClearInputLine(); original.LogEvent(sender, eventArgs); DrawInputPrompt(); } private void ClearInputLine() { int num = Math.Max(Console.WindowWidth - 1, 0); if (WindowWidthString.Length != num) { WindowWidthString = new string(' ', num); } ConsoleManager.StandardOutStream.Write('\r'); ConsoleManager.StandardOutStream.Write(WindowWidthString); ConsoleManager.StandardOutStream.Write('\r'); } private void DrawInputPrompt() { lock (_consoleLock) { ConsoleManager.StandardOutStream.Write("> "); ConsoleManager.StandardOutStream.Write(Input.ToString()); ConsoleManager.StandardOutStream.Flush(); } } public void ProcessInput() { if (!Console.KeyAvailable) { return; } ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(intercept: true); lock (_consoleLock) { switch (consoleKeyInfo.Key) { case ConsoleKey.Enter: { string text = Input.ToString(); Input.Clear(); ConsoleManager.StandardOutStream.WriteLine(); HostConsole._current.Send_ServerMessage(text); ClearInputLine(); DrawInputPrompt(); break; } case ConsoleKey.Backspace: if (Input.Length > 0) { Input.Length--; ClearInputLine(); DrawInputPrompt(); } break; default: if (!char.IsControl(consoleKeyInfo.KeyChar)) { Input.Append(consoleKeyInfo.KeyChar); ConsoleManager.StandardOutStream.Write(consoleKeyInfo.KeyChar); } break; } } } } [BepInPlugin("FleesDedicatedServer", "DedicatedServer", "0.1.2")] [BepInProcess("ATLYSS.exe")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger; internal static ServerHandler Handler; internal static ConsoleListenerWithInput ConsoleListener; private Plugin() { Logger = ((BaseUnityPlugin)this).Logger; } private void GenerateLaunchScripts() { string directoryName = Path.GetDirectoryName(Paths.ExecutablePath); File.WriteAllText(Path.Combine(directoryName, "steam_appid.txt"), "2768430"); if (WineDetect.IsRunningInWine) { string environmentVariable = Environment.GetEnvironmentVariable("STEAM_COMPAT_DATA_PATH"); string environmentVariable2 = Environment.GetEnvironmentVariable("STEAM_COMPAT_CLIENT_INSTALL_PATH"); string text = Environment.GetEnvironmentVariable("STEAM_COMPAT_TOOL_PATHS")?.Split(":").FirstOrDefault((string x) => x.Contains("/Proton")); string text2 = Paths.ExecutablePath.Replace('\\', '/'); if (text2.IndexOf(':') != -1) { text2 = text2.Substring(text2.IndexOf(':') + 1); } if (string.IsNullOrWhiteSpace(environmentVariable) || string.IsNullOrWhiteSpace(environmentVariable2) || string.IsNullOrWhiteSpace(text)) { Logger.LogWarning((object)"Couldn't generate a dedicated server launch script for Linux w/ Proton!"); return; } File.WriteAllText(Path.Combine(directoryName, "start_dedicated_server.sh"), "export STEAM_COMPAT_DATA_PATH=" + environmentVariable + "\nexport STEAM_COMPAT_CLIENT_INSTALL_PATH=" + environmentVariable2 + "\n" + string.Join(" ", "\"" + text + "/proton\"", "run", "\"" + text2 + "\"", "-batchmode", "-nographics", "-server", "--doorstop-enabled", "true", "--doorstop-target-assembly", "\"" + Path.Combine(Paths.BepInExAssemblyDirectory, "BepInEx.Preloader.dll") + "\"", "&")); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { File.WriteAllText(Path.Combine(directoryName, "start_dedicated_server.cmd"), string.Join(" ", "start", "\"Launch Dedicated Server\"", "\"" + Paths.ExecutablePath + "\"", "-batchmode", "-nographics", "-server", "--doorstop-enabled", "true", "--doorstop-target-assembly", "\"" + Path.Combine(Paths.BepInExAssemblyDirectory, "BepInEx.Preloader.dll") + "\"")); } else { Logger.LogWarning((object)"Couldn't generate a dedicated server launch script for current platform!"); } } private void Awake() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown PluginConfig.Configure(((BaseUnityPlugin)this).Config, Environment.GetCommandLineArgs()); GenerateLaunchScripts(); if (!PluginConfig.IsActive) { Logger.LogInfo((object)"Argument \"-server\" not found; starting in normal mode."); return; } Logger.LogInfo((object)"Starting in dedicated server mode."); Logger.LogInfo((object)("Headless mode is " + (PluginConfig.IsHeadless ? "on" : "off") + ".")); GameObject val = new GameObject("DedicatedServer"); Object.DontDestroyOnLoad((Object)(object)val); Handler = val.AddComponent(); Console.CursorVisible = true; try { ICollection listeners = Logger.Listeners; ILogListener? obj = ((IEnumerable)listeners).FirstOrDefault((Func)((ILogListener l) => ((object)l).GetType() == typeof(ConsoleLogListener))); ConsoleLogListener val2 = (ConsoleLogListener)(object)((obj is ConsoleLogListener) ? obj : null); if (val2 != null) { listeners.Remove((ILogListener)(object)val2); Logger.Listeners.Add((ILogListener)(object)(ConsoleListener = new ConsoleListenerWithInput(val2))); Logger.LogInfo((object)"Successfully detached default BepInEx console listener."); } } catch (Exception arg) { Logger.LogError((object)$"An error occurred while accessing the console stream: {arg}"); } if (PluginConfig.IsHeadless) { AudioListener.volume = 0f; PreventAudioChanges.LockAudio = true; } Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); } } internal static class PluginConfig { internal static string ServerName { get; private set; } internal static SteamLobbyType ServerType { get; private set; } internal static AtlyssSteamLobbyTag ServerTag { get; private set; } internal static string ServerPassword { get; private set; } internal static string ServerMOTD { get; private set; } internal static int ServerMaxPlayers { get; private set; } internal static int CharacterSlotToUse { get; private set; } internal static bool PeriodicRestartEnabled { get; private set; } internal static string PeriodicRestartInterval { get; private set; } internal static int PeriodicRestartPlayerThresholdPercent { get; private set; } internal static bool IsActive { get; private set; } internal static bool IsHeadless { get; private set; } internal static int PeriodicRestartIntervalSeconds { get; private set; } internal static int PeriodicRestartPlayerThreshold { get; private set; } public static void Configure(ConfigFile file, string[] args) { //IL_017c: Unknown result type (might be due to invalid IL or missing references) IsActive = args.Contains("-server"); IsHeadless = IsActive && Application.isBatchMode && args.Contains("-nographics"); ServerName = file.Bind("ServerData", "ServerName", "ATLYSS Server", "Name to set when launching the server.").Value; if (TryGetStringArgment(args, "-name", out string value)) { ServerName = value; } if (ServerName.Length > 20) { Plugin.Logger.LogWarning((object)$"Server name \"{ServerName}\" is too long ({ServerName.Length}/20). Defaulting to \"ATLYSS Server\"."); ServerName = "ATLYSS Server"; } if (string.IsNullOrWhiteSpace(ServerName)) { Plugin.Logger.LogWarning((object)("Server name \"" + ServerName + "\" is invalid. Defaulting to \"ATLYSS Server\".")); ServerName = "ATLYSS Server"; } ServerType = file.Bind("ServerData", "ServerType", SteamLobbyType.PUBLIC, "Type to set when launching the server.").Value; if (args.Contains("-public")) { ServerType = SteamLobbyType.PUBLIC; } else if (args.Contains("-friends")) { ServerType = SteamLobbyType.FRIENDS; } else if (args.Contains("-private")) { ServerType = SteamLobbyType.PRIVATE; } ServerTag = file.Bind("ServerData", "ServerTag", (AtlyssSteamLobbyTag)0, "Lobby tag to set when launching the server.").Value; if (args.Contains("-pve")) { ServerTag = (AtlyssSteamLobbyTag)0; } else if (args.Contains("-pvp")) { ServerTag = (AtlyssSteamLobbyTag)1; } else if (args.Contains("-social")) { ServerTag = (AtlyssSteamLobbyTag)2; } else if (args.Contains("-rp")) { ServerTag = (AtlyssSteamLobbyTag)3; } ServerPassword = file.Bind("ServerData", "ServerPassword", "", "Password to use for connecting to the server. Leave empty for a passwordless server.").Value; if (TryGetStringArgment(args, "-password", out string value2)) { ServerPassword = value2; } ServerMOTD = file.Bind("ServerData", "ServerMOTD", "Welcome to ATLYSS Server!", "MOTD to display for clients that connect to the server.").Value; if (TryGetStringArgment(args, "-motd", out string value3)) { ServerMOTD = value3; } ServerMaxPlayers = file.Bind("ServerData", "ServerMaxPlayers", 16, "Maximum number of players that can connect to the server (this includes the dummy host player).").Value; if (TryGetIntegerArgment(args, "-maxplayers", out var value4)) { ServerMaxPlayers = value4; } if (ServerMaxPlayers < 2 || ServerMaxPlayers > 250) { ServerMaxPlayers = 16; Plugin.Logger.LogWarning((object)"MaxPlayers must be between 2 and 250. Defaulting to 16."); } CharacterSlotToUse = file.Bind("ServerData", "CharacterSlotToUse", 0, "Character slot to use for the (idle) host character. If the slot is empty, the first non-empty save slot will be used instead. If all slots are empty, a new randomized character will be created in the first slot.").Value; if (TryGetIntegerArgment(args, "-hostsave", out var value5)) { CharacterSlotToUse = value5; } PeriodicRestartEnabled = file.Bind("ServerHealth", "PeriodicRestartEnabled", false, "Enable restarting the server automatically after the given time interval and number of players left.").Value; if (args.Contains("-autorestart")) { PeriodicRestartEnabled = true; } PeriodicRestartInterval = file.Bind("ServerHealth", "PeriodicRestartInterval", "6h", "How much time to wait before restarting. Must be at least 30 minutes. Format is specified in days, hours, minutes and/or seconds like 1d12h30m30s, 4h45m, 4h, 30m, 1d, etc.").Value; if (TryGetStringArgment(args, "-autorestartin", out string value6)) { PeriodicRestartInterval = value6; } if (Utils.TryParseInterval(PeriodicRestartInterval, out var seconds)) { if (seconds < 1800) { seconds = 1800; Plugin.Logger.LogWarning((object)"Specified restart time is too low (<30m). Increasing it to 30 minutes."); } PeriodicRestartIntervalSeconds = seconds; } else { PeriodicRestartIntervalSeconds = 14400; Plugin.Logger.LogWarning((object)"Specified restart time is invalid. Defaulting to 4 hours."); } PeriodicRestartPlayerThresholdPercent = file.Bind("ServerHealth", "PeriodicRestartPlayerThresholdPercent", 100, "Theshold of players (% of MaxPlayers) with which the server will proceed with a periodic restart. If there are more players than that, the restart timer will be stalled at 1 minute left until enough players leave. Set to 100 to ignore player counts and always restart the server.").Value; if (TryGetIntegerArgment(args, "-autorestartthreshold", out var value7)) { PeriodicRestartPlayerThresholdPercent = value7; } if (0 > PeriodicRestartPlayerThresholdPercent || PeriodicRestartPlayerThresholdPercent > 100) { PeriodicRestartPlayerThresholdPercent = 100; Plugin.Logger.LogWarning((object)"Specified restart player threshold is invalid. Defaulting to 100 (threshold disabled)."); } PeriodicRestartPlayerThreshold = Math.Clamp((int)Math.Ceiling((float)(ServerMaxPlayers * PeriodicRestartPlayerThresholdPercent) / 100f), 1, ServerMaxPlayers); file.Save(); } private static bool TryGetStringArgment(string[] args, string key, [NotNullWhen(true)] out string? value) { int num = Array.IndexOf(args, key); value = ((num >= 0 && num + 1 < args.Length) ? args[num + 1] : null); return value != null; } private static bool TryGetIntegerArgment(string[] args, string key, out int value) { int num = Array.IndexOf(args, key); string text = ((num >= 0 && num + 1 < args.Length) ? args[num + 1] : null); if (text == null) { value = 0; return false; } if (int.TryParse((num >= 0 && num + 1 < args.Length) ? args[num + 1] : null, out value)) { return true; } Plugin.Logger.LogWarning((object)("Value \"" + text + "\" supplied to argument \"" + key + "\" is not a valid integer.")); return false; } } public class ServerHandler : MonoBehaviour { private int ShutdownSecondsLeft; private bool PlayerThresholdNotified; private bool PlayerThresholdReached; private bool ShouldRestart; private bool IsAutomaticRestart; private void Awake() { ((MonoBehaviour)this).StartCoroutine(ServerHeartbeat()); } public void ScheduleShutdown(int seconds) { ShutdownSecondsLeft = seconds; PlayerThresholdNotified = false; PlayerThresholdReached = false; IsAutomaticRestart = false; ShouldRestart = false; HostConsole._current.Init_ServerMessage($"Server will shut down in {ShutdownSecondsLeft} seconds."); } public void ScheduleRestart(int seconds) { ShutdownSecondsLeft = seconds; PlayerThresholdNotified = false; PlayerThresholdReached = false; IsAutomaticRestart = false; ShouldRestart = true; HostConsole._current.Init_ServerMessage($"Server will restart in {ShutdownSecondsLeft} seconds."); } public void ScheduleAutomaticRestart() { ShutdownSecondsLeft = PluginConfig.PeriodicRestartIntervalSeconds; PlayerThresholdNotified = false; PlayerThresholdReached = false; IsAutomaticRestart = true; ShouldRestart = true; Plugin.Logger.LogInfo((object)("Scheduled automatic restart in " + Utils.FormatInterval(PluginConfig.PeriodicRestartIntervalSeconds) + ".")); } public void CancelShutdownOrRestart() { if (ShutdownSecondsLeft == 0) { Plugin.Logger.LogInfo((object)"No pending shutdown or restart to cancel."); return; } HostConsole._current.Init_ServerMessage((ShouldRestart ? "Restart" : "Shutdown") + " cancelled."); ShutdownSecondsLeft = 0; ShouldRestart = false; } private IEnumerator ServerHeartbeat() { WaitForSeconds oneSecond = new WaitForSeconds(1f); while (true) { yield return oneSecond; if (Object.op_Implicit((Object)(object)Player._mainPlayer) && !Player._mainPlayer._pVisual._forceHidden) { Player._mainPlayer._pVisual.Network_forceHidden = true; } if (Object.op_Implicit((Object)(object)MainMenuManager._current) && !NetworkServer.active && !NetworkClient.active) { StartServer(); yield return oneSecond; } else if (NetworkServer.active && PluginConfig.PeriodicRestartEnabled && ShutdownSecondsLeft == 0) { ScheduleAutomaticRestart(); } else { if (!NetworkServer.active || ShutdownSecondsLeft <= 0) { continue; } bool supressTimer = false; if (!IsAutomaticRestart || ShutdownSecondsLeft > 60 || PlayerThresholdReached) { ShutdownSecondsLeft--; } if (IsAutomaticRestart && ShutdownSecondsLeft <= 60 && !PlayerThresholdReached) { if (NetworkServer.connections.Count <= PluginConfig.PeriodicRestartPlayerThreshold) { PlayerThresholdReached = true; } else { supressTimer = true; if (!PlayerThresholdNotified) { HostConsole._current.Init_ServerMessage($"Server will restart once there are {PluginConfig.PeriodicRestartPlayerThreshold} or less players remaining."); PlayerThresholdNotified = true; } } } if (supressTimer) { continue; } if (ShutdownSecondsLeft == 0) { StopServer(); if (!ShouldRestart) { break; } } else if (ShutdownSecondsLeft <= 5 || (ShutdownSecondsLeft <= 60 && ShutdownSecondsLeft % 15 == 0) || (ShutdownSecondsLeft <= 300 && ShutdownSecondsLeft % 60 == 0) || (ShutdownSecondsLeft <= 900 && ShutdownSecondsLeft % 300 == 0) || (ShutdownSecondsLeft <= 3600 && ShutdownSecondsLeft % 900 == 0)) { HostConsole._current.Init_ServerMessage("Server will " + (ShouldRestart ? "restart" : "shut down") + " in " + Utils.FormatInterval(ShutdownSecondsLeft) + "."); } } } yield return oneSecond; Application.Quit(); } internal void StopServer() { if (NetworkServer.active) { Plugin.Logger.LogInfo((object)"Stopping Server"); HostConsole._current.Init_ServerDisconnect(); } } internal void StartServer() { //IL_00a4: 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) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"Starting Server"); LogServerConfig(); ServerHostSettings_Profile hostSettingsProfile = ProfileDataManager._current._hostSettingsProfile; AtlyssNetworkManager current = AtlyssNetworkManager._current; ProfileDataManager current2 = ProfileDataManager._current; LobbyListManager current3 = LobbyListManager._current; current._steamworksMode = true; current._soloMode = false; current._serverMode = false; current._serverName = PluginConfig.ServerName; current._serverPassword = PluginConfig.ServerPassword; current._sentPassword = PluginConfig.ServerPassword; current._serverMotd = PluginConfig.ServerMOTD; ((NetworkManager)current).maxConnections = PluginConfig.ServerMaxPlayers; current3._lobbyPasswordInput.text = PluginConfig.ServerPassword; current3._lobbyTypeDropdown.value = (int)PluginConfig.ServerType; current3._hostLobbyRealm = PluginConfig.ServerTag; current._bannedClientList.Clear(); current._mutedClientList.Clear(); if (hostSettingsProfile._banList != null) { current._bannedClientList.AddRange(hostSettingsProfile._banList); } if (hostSettingsProfile._mutedList != null) { current._mutedClientList.AddRange(hostSettingsProfile._mutedList); } SteamLobbyType serverType = PluginConfig.ServerType; if (1 == 0) { } ELobbyType val = (ELobbyType)(serverType switch { SteamLobbyType.PUBLIC => 2, SteamLobbyType.FRIENDS => 1, SteamLobbyType.PRIVATE => 0, _ => 2, }); if (1 == 0) { } ELobbyType val2 = val; int num = PluginConfig.CharacterSlotToUse; if (0 > num || num > current2._characterFiles.Length) { Plugin.Logger.LogWarning((object)$"Save slot {num} is invalid! Valid slots are between 0 and {current2._characterFiles.Length}. Defaulting to 0."); num = 0; } if (current2._characterFiles[num]._isEmptySlot) { Plugin.Logger.LogWarning((object)$"Slot {num} is empty! Creating a new character in it..."); current2._characterFile = current2._characterFiles[num]; CharacterCreationManager characterCreationManager = ProfileDataManager._current._charSelectManager._characterCreationManager; characterCreationManager.SelectRace(0); characterCreationManager.RandomizeAll(); characterCreationManager._characterNameInputField.text = "Hostman"; characterCreationManager.Create_Character(); } current2._characterFile = current2._characterFiles[num]; SteamLobby._current.HostLobby(val2); MainMenuManager._current._characterSelectManager.Send_CharacterFile(); } private void LogServerConfig() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) bool periodicRestartEnabled = PluginConfig.PeriodicRestartEnabled; int periodicRestartIntervalSeconds = PluginConfig.PeriodicRestartIntervalSeconds; Action action = Plugin.Logger.LogInfo; int periodicRestartPlayerThresholdPercent = PluginConfig.PeriodicRestartPlayerThresholdPercent; int periodicRestartPlayerThreshold = PluginConfig.PeriodicRestartPlayerThreshold; action("=== Server Configuration ==="); action("Server Name : " + PluginConfig.ServerName); action($"Server Type : {PluginConfig.ServerType}"); action($"Server Tag : {PluginConfig.ServerTag}"); action("Server Password : " + (string.IsNullOrEmpty(PluginConfig.ServerPassword) ? "[None]" : PluginConfig.ServerPassword)); action("Server MOTD : " + (string.IsNullOrEmpty(PluginConfig.ServerMOTD) ? "[None]" : PluginConfig.ServerMOTD)); action($"Max Players : {PluginConfig.ServerMaxPlayers}"); action("Auto Restart : " + ((!periodicRestartEnabled) ? "disabled" : (Utils.FormatInterval(periodicRestartIntervalSeconds) + ((periodicRestartPlayerThresholdPercent == 100) ? "" : $" at {periodicRestartPlayerThreshold} players left ({periodicRestartPlayerThresholdPercent}%)")))); action("============================"); } } public enum SteamLobbyType : byte { PUBLIC, FRIENDS, PRIVATE } public static class Utils { public static bool TryParseInterval(string text, out int seconds) { if (string.IsNullOrWhiteSpace(text)) { seconds = 0; return false; } Match match = Regex.Match(text, "^\\s*(?:([0-9]+)d)?\\s*(?:([0-9]+)h)?\\s*(?:([0-9]+)m)?\\s*(?:([0-9]+)s)?\\s*$"); bool success = match.Groups[1].Success; bool success2 = match.Groups[2].Success; bool success3 = match.Groups[3].Success; bool success4 = match.Groups[4].Success; seconds = 0; if (!match.Success || !(success || success2 || success3 || success4)) { return false; } seconds += (success ? (int.Parse(match.Groups[1].Value) * 86400) : 0); seconds += (success2 ? (int.Parse(match.Groups[2].Value) * 3600) : 0); seconds += (success3 ? (int.Parse(match.Groups[3].Value) * 60) : 0); seconds += (success4 ? int.Parse(match.Groups[4].Value) : 0); return true; } public static string FormatInterval(int seconds, bool pretty = false) { if (seconds == 0) { return pretty ? "0 second(s)" : "0s"; } int num = seconds / 86400; seconds %= 86400; int num2 = seconds / 3600; seconds %= 3600; int num3 = seconds / 60; seconds %= 60; string text = ""; if (num != 0) { text += (pretty ? $"{num} day(s)" : $"{num}d"); } if (pretty && num != 0 && num2 != 0) { text += ", "; } if (num2 != 0) { text += (pretty ? $"{num2} hour(s)" : $"{num2}h"); } if (pretty && num2 != 0 && num3 != 0) { text += ", "; } if (num3 != 0) { text += (pretty ? $"{num3} minute(s)" : $"{num3}m"); } if (pretty && num3 != 0 && seconds != 0) { text += ", "; } if (seconds != 0) { text += (pretty ? $"{seconds} second(s)" : $"{seconds}s"); } return text; } } public static class WineDetect { [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private delegate IntPtr WineGetVersion(); [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private delegate void WineGetHostVersion(out string sysName, out string version); private static bool _checkDone = false; private static bool _isRunningInWine = false; private static string _systemName = ""; private static string _systemVersion = ""; private static string _wineVersion = ""; public static bool IsRunningInWine { get { Check(); return _isRunningInWine; } } public static string SystemName { get { Check(); return _systemName; } } public static string SystemVersion { get { Check(); return _systemVersion; } } public static string WineVersion { get { Check(); return _wineVersion; } } [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] public static extern IntPtr GetModuleHandleA(string moduleName); [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] public static extern IntPtr GetProcAddress(IntPtr module, string procName); public static void Check() { if (_checkDone) { return; } _checkDone = true; IntPtr moduleHandleA = GetModuleHandleA("ntdll.dll"); if (!(moduleHandleA == IntPtr.Zero)) { IntPtr procAddress = GetProcAddress(moduleHandleA, "wine_get_version"); IntPtr procAddress2 = GetProcAddress(moduleHandleA, "wine_get_host_version"); if (!(procAddress == IntPtr.Zero) && !(procAddress2 == IntPtr.Zero)) { IntPtr ptr = Marshal.GetDelegateForFunctionPointer(procAddress)(); string wineVersion = Marshal.PtrToStringAnsi(ptr); Marshal.GetDelegateForFunctionPointer(procAddress2)(out var sysName, out var version); _isRunningInWine = true; _systemName = sysName; _systemVersion = version; _wineVersion = wineVersion; } } } } internal static class ModInfo { public const string GUID = "FleesDedicatedServer"; public const string NAME = "DedicatedServer"; public const string VERSION = "0.1.2"; } } namespace AtlyssDedicatedServer.HarmonyPatches { [HarmonyPatch(typeof(HostConsole), "Send_ServerMessage")] [HarmonyPriority(800)] internal static class ChatManagerPatch { private static bool Prefix(ref string _message) { if (string.IsNullOrWhiteSpace(_message)) { return false; } string[] array = _message.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (_message.Contains('<') || _message.Contains('>')) { Plugin.Logger.LogInfo((object)"'<' and '>' are not allowed in server messages."); return false; } if (_message[0] != '/') { HostConsole._current.Init_ServerMessage(_message); return false; } if (array[0].Trim() == "/restart") { if (array.Length < 2 || !Utils.TryParseInterval(array[1], out var seconds) || seconds < 1) { seconds = 20; } Plugin.Handler.ScheduleRestart(seconds); return false; } if (array[0].Trim() == "/shutdown") { if (array.Length < 2 || !Utils.TryParseInterval(array[1], out var seconds2) || seconds2 < 1) { seconds2 = 20; } Plugin.Handler.ScheduleShutdown(seconds2); return false; } if (_message.Trim() == "/cancelsd" || _message.Trim() == "/cancelrt") { Plugin.Handler.CancelShutdownOrRestart(); return false; } HostConsole._current._cmdManager.Init_ConsoleCommand(array[0].TrimStart('/'), (array.Length > 1) ? array[1] : string.Empty); return false; } } [HarmonyPatch(typeof(ChatBehaviour), "New_ChatMessage")] internal static class ChatMirrorPatch { private static void Postfix(ChatBehaviour __instance, string _message) { if (!((Object)(object)__instance == (Object)null)) { string text = StripUnityRichText(_message); if (!(text == HostConsoleFixPatch.LastServerMessage) || !((DateTime.UtcNow - HostConsoleFixPatch.LastServerMessageTime).TotalMilliseconds < 100.0)) { Logger.LogInfo((object)("[Chat] " + text)); } } } private static string StripUnityRichText(string input) { return Regex.Replace(input, "<.*?>", string.Empty); } } [HarmonyPatch(typeof(AtlyssNetworkManager), "Clear_ConsoleInit")] internal static class ClearPatch { private static bool Prefix() { return false; } } [HarmonyPatch(typeof(AtlyssNetworkManager), "Console_GetInput")] internal static class ConsolePatch { private static bool Prefix() { Plugin.ConsoleListener.ProcessInput(); return false; } } [HarmonyPatch(typeof(HostConsole), "New_LogMessage")] internal class HostConsoleFixPatch { internal static string LastServerMessage { get; private set; } = ""; internal static DateTime LastServerMessageTime { get; private set; } private static bool Prefix(string _message) { string value = $"[{DateTime.Now.Hour}:{DateTime.Now.Minute}] " + _message; LastServerMessage = _message; LastServerMessageTime = DateTime.UtcNow; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine(value); Console.ResetColor(); return false; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class PreventAudioChanges { internal static bool LockAudio { get; set; } private static bool Prefix() { return !LockAudio; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }