Decompiled source of PauseMyServer v1.5.0

BepInEx/plugins/PauseMyServer.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[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("PauseMyServer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.5.0.0")]
[assembly: AssemblyInformationalVersion("1.5.0+d245c77f708eee4de28e0a32def7470a073b2ab3")]
[assembly: AssemblyProduct("PauseMyServer")]
[assembly: AssemblyTitle("PauseMyServer")]
[assembly: AssemblyVersion("1.5.0.0")]
namespace PauseMyServer;

internal static class Commands
{
	[Serializable]
	[CompilerGenerated]
	private sealed class <>c
	{
		public static readonly <>c <>9 = new <>c();

		public static ConsoleEvent <>9__0_0;

		public static ConsoleEvent <>9__0_1;

		internal void <Register>b__0_0(ConsoleEventArgs args)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("Not in a game.");
				}
			}
			else if (instance.IsServer() && (Object)(object)Player.m_localPlayer == (Object)null)
			{
				PauseSync.SetAdminPause(!PauseSync.AdminPaused, "the server console");
				Terminal context2 = args.Context;
				if (context2 != null)
				{
					context2.AddString(PauseSync.AdminPaused ? "Server paused." : "Server resumed.");
				}
			}
			else
			{
				PauseSync.SendAdminToggle();
				Terminal context3 = args.Context;
				if (context3 != null)
				{
					context3.AddString("Pause toggle requested.");
				}
			}
		}

		internal void <Register>b__0_1(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			if (context != null)
			{
				context.AddString(PauseSync.Status());
			}
		}
	}

	internal static void Register()
	{
		//IL_0033: 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_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Expected O, but got Unknown
		//IL_006c: 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_0062: Expected O, but got Unknown
		object obj = <>c.<>9__0_0;
		if (obj == null)
		{
			ConsoleEvent val = delegate(ConsoleEventArgs args)
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Not in a game.");
					}
				}
				else if (instance.IsServer() && (Object)(object)Player.m_localPlayer == (Object)null)
				{
					PauseSync.SetAdminPause(!PauseSync.AdminPaused, "the server console");
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString(PauseSync.AdminPaused ? "Server paused." : "Server resumed.");
					}
				}
				else
				{
					PauseSync.SendAdminToggle();
					Terminal context3 = args.Context;
					if (context3 != null)
					{
						context3.AddString("Pause toggle requested.");
					}
				}
			};
			<>c.<>9__0_0 = val;
			obj = (object)val;
		}
		new ConsoleCommand("pms_pause", "Pause My Server: toggle the server-wide admin pause (same as the pause key). On the dedicated server's own console it toggles directly, which is the way out if no admin is online.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		object obj2 = <>c.<>9__0_1;
		if (obj2 == null)
		{
			ConsoleEvent val2 = delegate(ConsoleEventArgs args)
			{
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(PauseSync.Status());
				}
			};
			<>c.<>9__0_1 = val2;
			obj2 = (object)val2;
		}
		new ConsoleCommand("pms_status", "Pause My Server: current pause state", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}
}
[HarmonyPatch(typeof(Game), "Pause")]
internal static class Game_Pause_Patch
{
	[HarmonyPostfix]
	private static void Postfix()
	{
		PauseSync.WantPause = true;
	}
}
[HarmonyPatch(typeof(Game), "Unpause")]
internal static class Game_Unpause_Patch
{
	[HarmonyPostfix]
	private static void Postfix()
	{
		PauseSync.WantPause = false;
	}
}
[HarmonyPatch(typeof(Game), "IsPaused")]
internal static class Game_IsPaused_Patch
{
	[HarmonyPostfix]
	private static void Postfix(ref bool __result)
	{
		if (!__result && !((Object)(object)Player.m_localPlayer == (Object)null) && PauseSync.ClientPaused && (PauseSync.ClientForced || PauseSync.WantPause))
		{
			__result = true;
		}
	}
}
[HarmonyPatch(typeof(Player), "OnSpawned")]
internal static class Player_OnSpawned_Patch
{
	[HarmonyPostfix]
	private static void Postfix(Player __instance)
	{
		if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
		{
			PauseSync.RequestState();
		}
	}
}
[HarmonyPatch(typeof(ZNet), "UpdateNetTime")]
internal static class ZNet_UpdateNetTime_Patch
{
	[HarmonyPrefix]
	private static bool Prefix()
	{
		return !PauseSync.ServerPaused;
	}
}
[HarmonyPatch(typeof(RandEventSystem), "FixedUpdate")]
internal static class RandEventSystem_FixedUpdate_Patch
{
	[HarmonyPrefix]
	private static bool Prefix()
	{
		return !PauseSync.ServerPaused;
	}
}
[HarmonyPatch(typeof(EnvMan), "UpdateTimeSkip")]
internal static class EnvMan_UpdateTimeSkip_Patch
{
	[HarmonyPrefix]
	private static bool Prefix()
	{
		return !PauseSync.ServerPaused;
	}
}
[HarmonyPatch(typeof(Game), "Start")]
internal static class Game_Start_Patch
{
	[HarmonyPostfix]
	private static void Postfix()
	{
		PauseSync.Reset();
		PauseSync.Register();
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			PauseStateFile.Publish(paused: false, null, 0, 0);
		}
	}
}
[HarmonyPatch(typeof(ZNet), "Shutdown")]
internal static class ZNet_Shutdown_Patch
{
	[HarmonyPrefix]
	private static void Prefix()
	{
		PauseSync.Reset();
		PauseStateFile.Shutdown();
	}
}
[BepInPlugin("DeathMonger.PauseMyServer", "Pause My Server", "1.5.0")]
public class PauseMyServerMod : BaseUnityPlugin
{
	public const string ModGuid = "DeathMonger.PauseMyServer";

	public const string ModName = "Pause My Server";

	public const string ModVersion = "1.5.0";

	internal static ConfigEntry<bool> ModEnabled;

	internal static ConfigEntry<bool> ShowMessages;

	internal static ConfigEntry<KeyboardShortcut> PauseKey;

	internal static ConfigEntry<bool> PublishStateFile;

	internal static ConfigEntry<bool> ShowPauseMessage;

	internal static ConfigEntry<string> PauseMessage;

	internal static ConfigEntry<string> AdminText;

	internal static ConfigEntry<PauseOverlay.Position> PauseMessagePosition;

	internal static ConfigEntry<int> PauseMessageSize;

	internal static ConfigEntry<bool> ShowUnpausedWarning;

	internal static ConfigEntry<string> UnpausedText;

	internal static ConfigEntry<string> UnpausedCountText;

	private readonly Harmony _harmony = new Harmony("DeathMonger.PauseMyServer");

	internal static ManualLogSource Log { get; private set; }

	private void Awake()
	{
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		//IL_016e: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Mod Enabled", true, "Master toggle for the entire mod. Set to false to disable every patch without removing the DLL. Requires a game restart to take effect.");
		ShowMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Show Messages", true, "Show a small top-left HUD message when the game resumes for a reason other than you closing your menu: an admin lifted the pause, or another player came online while your menu was still open. Client-side only.");
		PauseKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Admin", "Pause Key", new KeyboardShortcut((KeyCode)19, Array.Empty<KeyCode>()), "Admins only: toggle the server-wide pause for everyone. Ignored while typing in chat, the console or a text box. Non-admins get a notice. The console command pms_pause does the same.");
		PublishStateFile = ((BaseUnityPlugin)this).Config.Bind<bool>("Server", "Publish State File", true, "Servers only: keep BepInEx/config/PauseMyServer.state.json up to date with the pause state, so a monitor or dashboard can read it instead of scraping the log. Rewritten on every change and every few seconds as a heartbeat, and deleted when the world shuts down.");
		PauseStateFile.Version = "1.5.0";
		PauseStateFile.Enabled = PublishStateFile.Value;
		ShowPauseMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("Pause Message", "Show Pause Message", true, "Show a persistent on-screen label while the game is paused. Also shown when pausing a solo game. Client-side only.");
		PauseMessage = BindText("Text", "Paused", "The label text.", "Game paused");
		AdminText = BindText("Admin Text", "Paused by {0}", "The label text during an admin pause; {0} is replaced by the admin's name.", "Game paused by {0}");
		PauseMessagePosition = ((BaseUnityPlugin)this).Config.Bind<PauseOverlay.Position>("Pause Message", "Position", PauseOverlay.Position.Bottom, "Where the label sits, centered at the Top or the Bottom of the screen.");
		PauseMessageSize = ((BaseUnityPlugin)this).Config.Bind<int>("Pause Message", "Font Size", 40, new ConfigDescription("Label font size (at a 1920x1080 reference; scales with the screen).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(12, 120), Array.Empty<object>()));
		ShowUnpausedWarning = ((BaseUnityPlugin)this).Config.Bind<bool>("Pause Message", "Show Unpaused Warning", true, "Show the label in bright red while something has asked for a pause and the game is still running: the ESC menu, or any mod that pauses for an open map or inventory panel.");
		UnpausedText = BindText("Unpaused Text", "Unpaused", "The first line of that warning.", "Game Unpaused");
		UnpausedCountText = BindText("Unpaused Count Text", "{0}/{1} want to pause", "The second line of that warning, in a smaller font, shown once the server reports the counts: {0} players have asked for a pause, {1} are online. Leave it empty for no second line.", "Game Unpaused ({0} of {1} players paused)");
		if (!ModEnabled.Value)
		{
			Log.LogInfo((object)"[PauseMyServer] Mod Enabled = false in config; skipping patches.");
			return;
		}
		Commands.Register();
		_harmony.PatchAll();
		Log.LogInfo((object)"[PauseMyServer] 1.5.0 loaded.");
	}

	private void Update()
	{
		if (ModEnabled.Value)
		{
			PauseSync.Update();
			PauseSync.UpdateInput();
			PauseOverlay.Update();
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				PauseStateFile.Heartbeat();
			}
		}
	}

	private void OnDestroy()
	{
		PauseStateFile.Shutdown();
		_harmony.UnpatchSelf();
	}

	private ConfigEntry<string> BindText(string key, string defaultValue, string description, params string[] supersededDefaults)
	{
		ConfigEntry<string> val = ((BaseUnityPlugin)this).Config.Bind<string>("Pause Message", key, defaultValue, description);
		foreach (string text in supersededDefaults)
		{
			if (!(val.Value != text))
			{
				val.Value = defaultValue;
				Log.LogInfo((object)("[PauseMyServer] Config '" + key + "' still held the old default; updated it to \"" + defaultValue + "\"."));
				break;
			}
		}
		return val;
	}

	internal static void Message(string text, bool always = false)
	{
		if ((always || (ShowMessages != null && ShowMessages.Value)) && !((Object)(object)MessageHud.instance == (Object)null))
		{
			MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
		}
	}
}
internal static class PauseOverlay
{
	internal enum Position
	{
		Top,
		Bottom
	}

	private enum Mode
	{
		Hidden,
		Paused,
		Unpaused
	}

	private const float DefaultTopOffset = 60f;

	private const float DefaultBottomOffset = 90f;

	private const float MinEdgeOffset = 4f;

	private const float DetailScale = 0.55f;

	private const float RefusedGraceSeconds = 0.5f;

	private static GameObject _root;

	private static Canvas _canvas;

	private static TMP_Text _label;

	private static Color _baseColor = Color.white;

	private static Mode _mode = Mode.Hidden;

	private static float _lastY = float.NaN;

	private static float _wantSince = -1f;

	private static readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4];

	internal static void Update()
	{
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		Mode mode = Mode.Hidden;
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			_wantSince = -1f;
		}
		else if (Game.IsPaused())
		{
			_wantSince = -1f;
			if (PauseMyServerMod.ShowPauseMessage.Value)
			{
				mode = Mode.Paused;
			}
		}
		else if (PauseSync.WantPause && !InCutscene())
		{
			if (_wantSince < 0f)
			{
				_wantSince = Time.unscaledTime;
			}
			if (PauseMyServerMod.ShowUnpausedWarning.Value && Time.unscaledTime - _wantSince >= 0.5f)
			{
				mode = Mode.Unpaused;
			}
		}
		else
		{
			_wantSince = -1f;
		}
		if (mode == Mode.Hidden)
		{
			if (_mode != Mode.Hidden)
			{
				if ((Object)(object)_root != (Object)null)
				{
					_root.SetActive(false);
				}
				_mode = Mode.Hidden;
			}
			return;
		}
		if ((Object)(object)_root == (Object)null)
		{
			_mode = Mode.Hidden;
			if (!Create())
			{
				return;
			}
		}
		string text = ((mode == Mode.Paused) ? PausedText() : UnpausedText());
		if (_mode == Mode.Hidden)
		{
			ApplyLayout();
			_root.SetActive(true);
		}
		if (_mode != mode || _label.text != text)
		{
			SetText(text);
			((Graphic)_label).color = ((mode == Mode.Paused) ? _baseColor : Color.red);
		}
		_mode = mode;
		UpdatePosition();
	}

	private static bool InCutscene()
	{
		if (CinematicsManager.IsPlaying())
		{
			return true;
		}
		Game instance = Game.instance;
		if ((Object)(object)instance != (Object)null)
		{
			return instance.InIntro(true);
		}
		return false;
	}

	private static string PausedText()
	{
		if (PauseSync.ClientForced && !string.IsNullOrEmpty(PauseSync.PausedBy))
		{
			try
			{
				return string.Format(PauseMyServerMod.AdminText.Value, PauseSync.PausedBy);
			}
			catch (FormatException)
			{
				return PauseMyServerMod.AdminText.Value;
			}
		}
		return PauseMyServerMod.PauseMessage.Value;
	}

	private static string UnpausedText()
	{
		string value = PauseMyServerMod.UnpausedText.Value;
		if (PauseSync.PlayerCount <= 0)
		{
			return value;
		}
		string text;
		try
		{
			text = string.Format(PauseMyServerMod.UnpausedCountText.Value, PauseSync.WantCount, PauseSync.PlayerCount);
		}
		catch (FormatException)
		{
			return value;
		}
		if (string.IsNullOrEmpty(text))
		{
			return value;
		}
		return value + "\n<size=" + Mathf.RoundToInt(55f) + "%>" + text + "</size>";
	}

	private static void SetText(string text)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		_label.text = text;
		float num = PauseMyServerMod.PauseMessageSize.Value;
		float num2 = num * 1.25f;
		if (text.IndexOf('\n') >= 0)
		{
			num2 += num * 0.55f * 1.25f;
		}
		_label.rectTransform.sizeDelta = new Vector2(1600f, num2);
		_lastY = float.NaN;
	}

	private static void ApplyLayout()
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		_label.fontSize = PauseMyServerMod.PauseMessageSize.Value;
		bool flag = PauseMyServerMod.PauseMessagePosition.Value == Position.Top;
		RectTransform rectTransform = _label.rectTransform;
		Vector2 val = default(Vector2);
		((Vector2)(ref val))..ctor(0.5f, flag ? 1f : 0f);
		rectTransform.anchorMax = val;
		rectTransform.anchorMin = val;
		rectTransform.pivot = new Vector2(0.5f, flag ? 1f : 0f);
		_lastY = float.NaN;
	}

	private static void UpdatePosition()
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		bool num = PauseMyServerMod.PauseMessagePosition.Value == Position.Top;
		float num2 = (num ? 60f : 90f);
		float num3 = num2;
		float num4 = MapStrip(num);
		if (num4 >= 0f)
		{
			num3 = Mathf.Clamp(Mathf.Max(0f, num4 - _label.rectTransform.sizeDelta.y) / 2f, 4f, num2);
		}
		float num5 = (num ? (0f - num3) : num3);
		if (float.IsNaN(_lastY) || !(Mathf.Abs(num5 - _lastY) < 0.5f))
		{
			_label.rectTransform.anchoredPosition = new Vector2(0f, num5);
			_lastY = num5;
		}
	}

	private static float MapStrip(bool top)
	{
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_canvas == (Object)null || !Minimap.IsOpen())
		{
			return -1f;
		}
		Minimap instance = Minimap.instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_mapImageLarge == (Object)null)
		{
			return -1f;
		}
		RectTransform rectTransform = ((Graphic)instance.m_mapImageLarge).rectTransform;
		Canvas val = ((Component)rectTransform).GetComponentInParent<Canvas>();
		if ((Object)(object)val != (Object)null)
		{
			val = val.rootCanvas;
		}
		Camera val2 = (((Object)(object)val != (Object)null && (int)val.renderMode != 0) ? val.worldCamera : null);
		rectTransform.GetWorldCorners(_corners);
		float num = ((_canvas.scaleFactor > 0f) ? _canvas.scaleFactor : 1f);
		if (top)
		{
			float y = RectTransformUtility.WorldToScreenPoint(val2, _corners[1]).y;
			return Mathf.Max(0f, (float)Screen.height - y) / num;
		}
		float y2 = RectTransformUtility.WorldToScreenPoint(val2, _corners[0]).y;
		return Mathf.Max(0f, y2) / num;
	}

	private static bool Create()
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected O, but got Unknown
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		MessageHud instance = MessageHud.instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_messageCenterText == (Object)null)
		{
			return false;
		}
		TMP_Text messageCenterText = instance.m_messageCenterText;
		if ((Object)(object)messageCenterText.font == (Object)null)
		{
			return false;
		}
		_root = new GameObject("PauseMyServer_Overlay");
		_root.SetActive(false);
		_canvas = _root.AddComponent<Canvas>();
		_canvas.renderMode = (RenderMode)0;
		_canvas.sortingOrder = 1000;
		CanvasScaler obj = _root.AddComponent<CanvasScaler>();
		obj.uiScaleMode = (ScaleMode)1;
		obj.referenceResolution = new Vector2(1920f, 1080f);
		obj.matchWidthOrHeight = 0.5f;
		GameObject val = new GameObject("PauseMyServer_Label", new Type[2]
		{
			typeof(RectTransform),
			typeof(CanvasRenderer)
		});
		val.transform.SetParent(_root.transform, false);
		TextMeshProUGUI obj2 = val.AddComponent<TextMeshProUGUI>();
		((TMP_Text)obj2).font = messageCenterText.font;
		((TMP_Text)obj2).fontSharedMaterial = messageCenterText.fontSharedMaterial;
		_baseColor = ((Graphic)messageCenterText).color;
		((Graphic)obj2).color = _baseColor;
		((TMP_Text)obj2).alignment = (TextAlignmentOptions)514;
		((TMP_Text)obj2).richText = true;
		((Graphic)obj2).raycastTarget = false;
		((TMP_Text)obj2).rectTransform.sizeDelta = new Vector2(1600f, 50f);
		_label = (TMP_Text)(object)obj2;
		return true;
	}
}
internal static class PauseStateFile
{
	internal const int HeartbeatSeconds = 5;

	internal static bool Enabled = true;

	internal static string Version = "";

	private static readonly string FilePath = Path.Combine(Paths.ConfigPath, "PauseMyServer.state.json");

	private static bool _paused;

	private static string _by = "";

	private static int _wanting;

	private static int _players;

	private static DateTime _sinceUtc = DateTime.UtcNow;

	private static float _nextBeat;

	internal static void Publish(bool paused, string by, int wanting, int players)
	{
		if (Enabled)
		{
			if (paused != _paused)
			{
				_sinceUtc = DateTime.UtcNow;
			}
			_paused = paused;
			_by = (paused ? (by ?? "") : "");
			_wanting = wanting;
			_players = players;
			Write();
		}
	}

	internal static void Heartbeat()
	{
		if (Enabled && !(Time.realtimeSinceStartup < _nextBeat))
		{
			Write();
		}
	}

	internal static void Shutdown()
	{
		try
		{
			if (File.Exists(FilePath))
			{
				File.Delete(FilePath);
			}
		}
		catch
		{
		}
	}

	private static void Write()
	{
		_nextBeat = Time.realtimeSinceStartup + 5f;
		StringBuilder stringBuilder = new StringBuilder(220);
		stringBuilder.Append('{');
		stringBuilder.Append("\"paused\":").Append(_paused ? "true" : "false");
		stringBuilder.Append(",\"by\":\"").Append(Escape(_by)).Append('"');
		stringBuilder.Append(",\"wanting\":").Append(_wanting.ToString(CultureInfo.InvariantCulture));
		stringBuilder.Append(",\"players\":").Append(_players.ToString(CultureInfo.InvariantCulture));
		stringBuilder.Append(",\"since\":\"").Append(Iso(_sinceUtc)).Append('"');
		stringBuilder.Append(",\"written\":\"").Append(Iso(DateTime.UtcNow)).Append('"');
		stringBuilder.Append(",\"heartbeat\":").Append(5.ToString(CultureInfo.InvariantCulture));
		stringBuilder.Append(",\"version\":\"").Append(Escape(Version)).Append('"');
		stringBuilder.Append('}');
		try
		{
			string text = FilePath + ".tmp";
			File.WriteAllText(text, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(FilePath))
			{
				File.Replace(text, FilePath, null);
			}
			else
			{
				File.Move(text, FilePath);
			}
		}
		catch
		{
		}
	}

	private static string Iso(DateTime utc)
	{
		return utc.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
	}

	private static string Escape(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return "";
		}
		StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
		foreach (char c in s)
		{
			switch (c)
			{
			case '"':
				stringBuilder.Append("\\\"");
				continue;
			case '\\':
				stringBuilder.Append("\\\\");
				continue;
			case '\b':
				stringBuilder.Append("\\b");
				continue;
			case '\f':
				stringBuilder.Append("\\f");
				continue;
			case '\n':
				stringBuilder.Append("\\n");
				continue;
			case '\r':
				stringBuilder.Append("\\r");
				continue;
			case '\t':
				stringBuilder.Append("\\t");
				continue;
			}
			if (c < ' ')
			{
				StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
				int num = c;
				stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
			}
			else
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.ToString();
	}
}
internal static class PauseSync
{
	private const string RpcWant = "PMS_WantPause";

	private const string RpcAdminToggle = "PMS_AdminToggle";

	private const string RpcRequestState = "PMS_RequestState";

	private const string RpcState = "PMS_PauseState";

	private const string RpcCounts = "PMS_Counts";

	private const string RpcNotice = "PMS_Notice";

	private static readonly Dictionary<long, bool> _wants = new Dictionary<long, bool>();

	private static readonly List<long> _gone = new List<long>();

	private static string _resumedBy = "";

	private static bool _sentForced;

	private static string _sentBy = "";

	private static int _sentWants = -1;

	private static int _sentTotal = -1;

	private static int _lastReady;

	internal static bool WantPause;

	private static bool _sentWant;

	internal static bool ServerPaused { get; private set; }

	internal static bool AdminPaused { get; private set; }

	internal static string AdminPausedBy { get; private set; } = "";

	internal static bool ClientPaused { get; private set; }

	internal static bool ClientForced { get; private set; }

	internal static string PausedBy { get; private set; } = "";

	internal static int WantCount { get; private set; }

	internal static int PlayerCount { get; private set; }

	internal static void Register()
	{
		ZRoutedRpc instance = ZRoutedRpc.instance;
		if (instance != null)
		{
			instance.Register<bool>("PMS_WantPause", (Action<long, bool>)RPC_WantPause);
			instance.Register("PMS_AdminToggle", (Action<long>)RPC_AdminToggle);
			instance.Register("PMS_RequestState", (Action<long>)RPC_RequestState);
			instance.Register<bool, bool, string>("PMS_PauseState", (Action<long, bool, bool, string>)RPC_PauseState);
			instance.Register<int, int>("PMS_Counts", (Action<long, int, int>)RPC_Counts);
			instance.Register<string>("PMS_Notice", (Action<long, string>)RPC_Notice);
		}
	}

	internal static void Reset()
	{
		ServerPaused = false;
		AdminPaused = false;
		AdminPausedBy = "";
		_wants.Clear();
		_resumedBy = "";
		_sentForced = false;
		_sentBy = "";
		_sentWants = -1;
		_sentTotal = -1;
		_lastReady = 0;
		ClientPaused = false;
		ClientForced = false;
		PausedBy = "";
		WantCount = 0;
		PlayerCount = 0;
		WantPause = false;
		_sentWant = false;
	}

	internal static void Update()
	{
		ZNet instance = ZNet.instance;
		if (!((Object)(object)instance == (Object)null) && ZRoutedRpc.instance != null)
		{
			if (instance.IsServer())
			{
				UpdateServer(instance);
			}
			else
			{
				UpdateClient();
			}
		}
	}

	internal static string Status()
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return "Not in a game.";
		}
		string text = (((Object)(object)Player.m_localPlayer == (Object)null) ? "" : $" | client: paused={ClientPaused} admin={ClientForced} by='{PausedBy}' menu={WantPause} wanting={WantCount}/{PlayerCount}");
		return (((!instance.IsServer()) ? "" : $"server: paused={ServerPaused} admin={AdminPaused} by='{AdminPausedBy}' wanting={_sentWants}/{_sentTotal} players={_lastReady}") + text).TrimStart(' ', '|');
	}

	private static void UpdateClient()
	{
		if (WantPause != _sentWant)
		{
			_sentWant = WantPause;
			ZRoutedRpc.instance.InvokeRoutedRPC("PMS_WantPause", new object[1] { WantPause });
		}
	}

	internal static void UpdateInput()
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)Player.m_localPlayer == (Object)null) && ZRoutedRpc.instance != null)
		{
			KeyboardShortcut value = PauseMyServerMod.PauseKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && !Console.IsVisible() && !TextInput.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()))
			{
				SendAdminToggle();
			}
		}
	}

	internal static void SendAdminToggle()
	{
		if (ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("PMS_AdminToggle", Array.Empty<object>());
		}
	}

	internal static void RequestState()
	{
		if (ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC("PMS_RequestState", Array.Empty<object>());
		}
	}

	private static bool FromServer(long sender)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return false;
		}
		if (instance.IsServer() && (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return false;
		}
		ZNetPeer serverPeer = instance.GetServerPeer();
		if (serverPeer != null)
		{
			return sender == serverPeer.m_uid;
		}
		return true;
	}

	private static void RPC_PauseState(long sender, bool paused, bool forced, string by)
	{
		if (!FromServer(sender))
		{
			return;
		}
		bool clientPaused = ClientPaused;
		bool clientForced = ClientForced;
		ClientPaused = paused;
		ClientForced = paused && forced;
		PausedBy = by ?? "";
		if (paused == clientPaused && ClientForced == clientForced)
		{
			return;
		}
		PauseMyServerMod.Log.LogInfo((object)(paused ? ("[PauseMyServer] Game paused" + (forced ? (" by admin " + PausedBy) : "") + ".") : "[PauseMyServer] Game resumed."));
		if (!paused)
		{
			if (clientForced)
			{
				PauseMyServerMod.Message(string.IsNullOrEmpty(PausedBy) ? "Game resumed" : ("Game resumed by " + PausedBy));
			}
			else if (WantPause)
			{
				PauseMyServerMod.Message("Game resumed: not everyone is paused");
			}
		}
	}

	private static void RPC_Counts(long sender, int wanting, int online)
	{
		if (FromServer(sender))
		{
			WantCount = wanting;
			PlayerCount = online;
		}
	}

	private static void RPC_Notice(long sender, string text)
	{
		if (!((Object)(object)Player.m_localPlayer == (Object)null))
		{
			PauseMyServerMod.Message(text, always: true);
		}
	}

	private static void RPC_WantPause(long sender, bool want)
	{
		ZNet instance = ZNet.instance;
		if (!((Object)(object)instance == (Object)null) && instance.IsServer() && instance.GetPeer(sender) != null)
		{
			_wants[sender] = want;
		}
	}

	private static void RPC_AdminToggle(long sender)
	{
		ZNet instance = ZNet.instance;
		if (!((Object)(object)instance == (Object)null) && instance.IsServer())
		{
			string text = PeerName(instance, sender);
			if (!IsAdmin(instance, sender))
			{
				PauseMyServerMod.Log.LogInfo((object)("[PauseMyServer] " + text + " pressed the pause key but is not an admin."));
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PMS_Notice", new object[1] { "Only an admin can pause the server" });
			}
			else
			{
				SetAdminPause(!AdminPaused, text);
			}
		}
	}

	private static void RPC_RequestState(long sender)
	{
		ZNet instance = ZNet.instance;
		if (!((Object)(object)instance == (Object)null) && instance.IsServer())
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PMS_Counts", new object[2]
			{
				(_sentWants >= 0) ? _sentWants : 0,
				(_sentTotal >= 0) ? _sentTotal : 0
			});
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PMS_PauseState", new object[3] { ServerPaused, _sentForced, _sentBy });
		}
	}

	internal static void SetAdminPause(bool paused, string by)
	{
		if (paused != AdminPaused)
		{
			AdminPaused = paused;
			if (paused)
			{
				AdminPausedBy = by;
				_resumedBy = "";
			}
			else
			{
				_resumedBy = by;
				AdminPausedBy = "";
			}
			PauseMyServerMod.Log.LogInfo((object)("[PauseMyServer] Admin pause " + (paused ? "set" : "lifted") + " by " + by + "."));
		}
	}

	private static bool IsAdmin(ZNet znet, long sender)
	{
		if (sender == ZDOMan.GetSessionID())
		{
			return true;
		}
		ZNetPeer peer = znet.GetPeer(sender);
		if (peer == null || peer.m_socket == null)
		{
			return false;
		}
		string hostName = peer.m_socket.GetHostName();
		if (!string.IsNullOrEmpty(hostName))
		{
			return znet.IsAdmin(hostName);
		}
		return false;
	}

	private static string PeerName(ZNet znet, long sender)
	{
		if (sender == ZDOMan.GetSessionID())
		{
			if (!((Object)(object)Player.m_localPlayer != (Object)null))
			{
				return "the server";
			}
			return Player.m_localPlayer.GetPlayerName();
		}
		ZNetPeer peer = znet.GetPeer(sender);
		if (peer == null || string.IsNullOrEmpty(peer.m_playerName))
		{
			return "an admin";
		}
		return peer.m_playerName;
	}

	private static void UpdateServer(ZNet znet)
	{
		int num = 0;
		int num2 = 0;
		string text = "";
		List<ZNetPeer> peers = znet.GetPeers();
		for (int i = 0; i < peers.Count; i++)
		{
			ZNetPeer val = peers[i];
			if (val.IsReady())
			{
				num++;
				text = val.m_playerName;
				if (_wants.TryGetValue(val.m_uid, out var value) && value)
				{
					num2++;
				}
			}
		}
		if (_wants.Count > 0)
		{
			_gone.Clear();
			foreach (long key in _wants.Keys)
			{
				if (znet.GetPeer(key) == null)
				{
					_gone.Add(key);
				}
			}
			for (int j = 0; j < _gone.Count; j++)
			{
				_wants.Remove(_gone[j]);
			}
		}
		if (AdminPaused && num == 0 && _lastReady > 0 && (Object)(object)Player.m_localPlayer == (Object)null)
		{
			PauseMyServerMod.Log.LogInfo((object)"[PauseMyServer] Server is empty; admin pause dropped.");
			AdminPaused = false;
			AdminPausedBy = "";
			_resumedBy = "";
		}
		_lastReady = num;
		bool flag = (Object)(object)Player.m_localPlayer != (Object)null;
		int num3 = num + (flag ? 1 : 0);
		int num4 = num2 + ((flag && WantPause) ? 1 : 0);
		bool flag2 = num3 > 0 && num4 == num3 && (!flag || num != 0);
		bool flag3 = num4 != _sentWants || num3 != _sentTotal;
		if (flag3)
		{
			_sentWants = num4;
			_sentTotal = num3;
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "PMS_Counts", new object[2] { num4, num3 });
		}
		bool flag4 = flag2 || AdminPaused;
		bool adminPaused = AdminPaused;
		string text2 = (AdminPaused ? AdminPausedBy : ((!flag2) ? _resumedBy : ((num3 == 1) ? text : "everyone")));
		if (flag4 == ServerPaused && adminPaused == _sentForced && text2 == _sentBy)
		{
			if (flag3)
			{
				PauseStateFile.Publish(ServerPaused, _sentBy, num4, num3);
			}
			return;
		}
		ServerPaused = flag4;
		_sentForced = adminPaused;
		_sentBy = text2;
		if (flag4)
		{
			_resumedBy = "";
		}
		PauseMyServerMod.Log.LogInfo((object)(flag4 ? string.Format("[PauseMyServer] World paused ({0}{1}; {2} player(s) online).", adminPaused ? "admin: " : "wanted by ", text2, num3) : string.Format("[PauseMyServer] World resumed ({0} of {1} player(s) want a pause{2}).", num4, num3, string.IsNullOrEmpty(text2) ? "" : ("; lifted by " + text2))));
		ZRoutedRpc.instance.InvokeRoutedRPC(0L, "PMS_PauseState", new object[3] { flag4, adminPaused, text2 });
		PauseStateFile.Publish(flag4, text2, num4, num3);
	}
}