Decompiled source of RavenEye v0.2.2

plugins/RavenEye.dll

Decompiled 17 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RavenIron.RavenEye.Config;
using RavenIron.RavenEye.Core;
using RavenIron.RavenEye.Net;
using RavenIron.RavenEye.Server;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Raven Iron")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.2.0")]
[assembly: AssemblyInformationalVersion("0.2.2+7ac39e573be1dfa612d5705f056b8a29dcfcfae0")]
[assembly: AssemblyProduct("RavenEye")]
[assembly: AssemblyTitle("RavenEye")]
[assembly: AssemblyVersion("0.2.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RavenIron.RavenEye
{
	[BepInPlugin("com.raveniron.raveneye", "RavenEye", "0.2.2")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class RavenEye : BaseUnityPlugin
	{
		public const string PluginId = "com.raveniron.raveneye";

		public const string PluginName = "RavenEye";

		public const string PluginVersion = "0.2.2";

		private Harmony _harmony;

		public static RavenEye Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static bool HasRenderer => (int)SystemInfo.graphicsDeviceType != 4;

		private void Awake()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			((Component)this).gameObject.AddComponent<RavenEyeTick>();
			_harmony = new Harmony("com.raveniron.raveneye");
			List<string> list = PatchInstall.Each(AccessTools.GetTypesFromAssembly(typeof(RavenEye).Assembly), delegate(Type t)
			{
				if (t.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0)
				{
					_harmony.CreateClassProcessor(t).Patch();
				}
			}, delegate(Type t, Exception ex)
			{
				Log.LogError((object)("patch " + t.Name + " could not be installed (" + ex.GetType().Name + ": " + ex.Message + "); that part of the mod is off, the rest carries on. A Valheim update probably moved its target."));
			});
			Log.LogInfo((object)(string.Format("{0} v{1} loaded — renderer={2}, ", "RavenEye", "0.2.2", HasRenderer) + $"patches={_harmony.GetPatchedMethods().Count()}" + ((list.Count > 0) ? string.Format(" ({0} FAILED: {1})", list.Count, string.Join(", ", list)) : "") + ", role is decided when a world loads."));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			Instance = null;
		}
	}
	internal static class BuildVersion
	{
		public const string Value = "0.2.2";
	}
}
namespace RavenIron.RavenEye.Server
{
	public static class AdminGate
	{
		private static bool _failureLogged;

		public static bool IsAdmin(ZNet znet, ZNetPeer peer)
		{
			if ((Object)(object)znet == (Object)null || peer == null)
			{
				return false;
			}
			string text;
			try
			{
				ISocket socket = peer.m_socket;
				text = ((socket != null) ? socket.GetHostName() : null);
			}
			catch
			{
				return false;
			}
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			try
			{
				return Check(znet, text);
			}
			catch (Exception ex)
			{
				if (!_failureLogged)
				{
					_failureLogged = true;
					RavenEye.Log.LogError((object)("AdminGate: ZNet.IsAdmin(string) threw " + ex.GetType().Name + ": " + ex.Message + ". Nobody will be granted the map. If this is a MissingMethodException, Valheim's API moved."));
				}
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool Check(ZNet znet, string host)
		{
			return znet.IsAdmin(host);
		}

		public static int AdminListCount(ZNet znet)
		{
			try
			{
				return ((znet == null) ? ((int?)null) : znet.GetAdminList()?.Count) ?? (-1);
			}
			catch
			{
				return -1;
			}
		}
	}
}
namespace RavenIron.RavenEye.Patches
{
	[HarmonyPatch(typeof(Achievements), "IsCleanNoMap")]
	public static class Patch_Achievements_IsCleanNoMap
	{
		private static int _warned;

		private static void Postfix(ref bool __result)
		{
			try
			{
				if (__result)
				{
					__result = Grant.CleanNoMap(__result, RavenEyeTick.EvaluateGrant(ZNet.instance));
				}
			}
			catch (Exception ex)
			{
				__result = false;
				if (_warned++ < 3)
				{
					RavenEye.Log.LogWarning((object)("IsCleanNoMap postfix threw (" + ex.GetType().Name + ": " + ex.Message + "); no-map credit withheld."));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Game), "UpdateNoMap")]
	public static class Patch_Game_UpdateNoMap
	{
		private static int _warned;

		private static void Postfix()
		{
			try
			{
				if (!RavenEyeTick.EvaluateGrant(ZNet.instance))
				{
					return;
				}
				Minimap instance = Minimap.instance;
				if (!((Object)(object)instance == (Object)null))
				{
					bool flag = false;
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer != (Object)null)
					{
						flag = PlatformPrefs.GetFloat("mapenabled_" + localPlayer.GetPlayerName(), 1f) == 0f;
					}
					Game.m_noMap = flag;
					instance.SetMapMode((MapMode)(!flag));
				}
			}
			catch (Exception ex)
			{
				if (_warned++ < 3)
				{
					RavenEye.Log.LogWarning((object)("UpdateNoMap postfix threw (" + ex.GetType().Name + ": " + ex.Message + "); vanilla's decision stands."));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	public static class Patch_Terminal_RavenEye
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;
		}

		private static void Postfix()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_0025: Expected O, but got Unknown
			try
			{
				object obj = <>O.<0>__Run;
				if (obj == null)
				{
					ConsoleEvent val = Run;
					<>O.<0>__Run = val;
					obj = (object)val;
				}
				new ConsoleCommand("raveneye", "RavenEye: status | roster | map on|off", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
			catch (Exception ex)
			{
				RavenEye.Log.LogWarning((object)("raveneye console: registration failed: " + ex.Message));
			}
		}

		private static void Run(ConsoleEventArgs args)
		{
			try
			{
				switch ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status")
				{
				case "status":
					Status(args);
					break;
				case "roster":
					Roster(args);
					break;
				case "map":
					Map(args);
					break;
				default:
					Help(args);
					break;
				}
			}
			catch (Exception ex)
			{
				Say(args, "raveneye: " + ex.Message);
				RavenEye.Log.LogWarning((object)$"raveneye console threw: {ex}");
			}
		}

		private static void Help(ConsoleEventArgs args)
		{
			Say(args, "raveneye status      — role, grant, and in words why the map is or is not showing");
			Say(args, "raveneye roster      — every player the roster carries (admins and hosts)");
			Say(args, "raveneye map on|off  — use, or refuse, the map the server grants this client");
		}

		private static void Status(ConsoleEventArgs args)
		{
			ZNet instance = ZNet.instance;
			Say(args, "RavenEye v0.2.2 — role=" + RavenEyeTick.Role() + ", renderer=" + (RavenEye.HasRenderer ? "yes" : "no"));
			if ((Object)(object)instance == (Object)null)
			{
				Say(args, "  no world loaded.");
				return;
			}
			bool flag = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)31);
			Say(args, "  world NoMap key: " + (flag ? "set (no-map world)" : "not set (this world has a map)"));
			if (instance.IsServer())
			{
				Say(args, string.Format("  server: Enabled={0}, interval {1}s, ", ModConfig.ServerEnabled.Value, F(ModConfig.IntervalSeconds.Value, "0.##")) + "grace " + F(ModConfig.GraceSeconds.Value, "0") + "s, deathGrace " + F(ModConfig.DeathGraceSeconds.Value, "0") + "s, " + $"RevealWhenMapEnabled={ModConfig.RevealWhenMapEnabled.Value}");
				Say(args, (RosterSync.ClosedGate.Length > 0) ? ("  gate: CLOSED — " + RosterSync.ClosedGate) : "  gate: open");
				int num = AdminGate.AdminListCount(instance);
				Say(args, $"  adminlist.txt entries: {num}" + (RosterSync.AdminListUnreadable ? "   <- reads as EMPTY with players online: treated as a failed read, nobody is demoted; re-save the file if it is not actually empty" : ""));
				if (RosterSync.LastDropped > 0)
				{
					Say(args, string.Format("  wire cap: {0} entr{1} over the {2}-entry cap were not sent (deaths first)", RosterSync.LastDropped, (RosterSync.LastDropped == 1) ? "y" : "ies", 64));
				}
				if (RosterSync.LastSendTime < 0f)
				{
					Say(args, "  roster: never built yet (first cadence pending)");
				}
				else if (RosterSync.ClosedGate.Length > 0)
				{
					Say(args, "  roster: none — gate closed, last cadence " + Age(RosterSync.LastSendTime) + " ago");
				}
				else
				{
					Say(args, string.Format("  roster: {0} entr{1} ", RosterSync.LastRosterSize, (RosterSync.LastRosterSize == 1) ? "y" : "ies") + string.Format("({0} dead snapshot{1}), ", RosterSync.DeadSnapshots, (RosterSync.DeadSnapshots == 1) ? "" : "s") + $"sent to {RosterSync.LastSendAdmins} admin(s), {RosterSync.GrantedPeerCount} granted, {Age(RosterSync.LastSendTime)} ago");
				}
			}
			if (!RavenEye.HasRenderer)
			{
				return;
			}
			Grant.Reason r = RavenEyeTick.ExplainGrant(instance);
			bool flag2 = Grant.IsGranted(r);
			Say(args, "  grant: " + (flag2 ? "YES" : "no") + " — " + RavenEyeTick.Words(r));
			if (!instance.IsServer())
			{
				Say(args, "  receiver registered on this connection: " + (RosterSync.IsRegisteredOnCurrentConnection(instance) ? "yes" : "NO"));
				if (RosterSync.LastReceipt < 0f && RosterSync.LastRevoke < 0f)
				{
					Say(args, "  roster: never received since this join");
				}
				else
				{
					Say(args, string.Format("  roster: {0} entr{1}, ", RosterSync.Current(instance).Count, (RosterSync.Current(instance).Count == 1) ? "y" : "ies") + $"{RosterSync.LastAppended} appended to vanilla's pins last frame; " + $"{RosterSync.PacketsReceived} received, {RosterSync.RevokesReceived} revoke(s), {RosterSync.PacketsRejected} rejected");
				}
				Say(args, string.Format("  format: this build v{0}; server RavenEye {1}", 1, (RosterSync.ServerVersion.Length > 0) ? ("v" + RosterSync.ServerVersion) : "unknown — no packet yet") + ((RosterSync.PacketsRejected > 0) ? ("; last rejection: " + RosterSync.LastRejectReason) : ""));
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				bool flag3 = RavenEyeTick.PersonalOptOut(localPlayer);
				Say(args, flag3 ? ("  character opt-out: THIS CHARACTER'S OWN MAP IS OFF (vanilla `nomap`, pref " + RavenEyeTick.PersonalKey(localPlayer) + " = 0) — `raveneye map on` clears it") : "  character opt-out: none");
			}
			Minimap instance2 = Minimap.instance;
			Say(args, string.Format("  Game.m_noMap={0}   Minimap mode={1}   corrections this session: {2} (zero is normal)", Game.m_noMap, ((Object)(object)instance2 != (Object)null) ? ((object)Unsafe.As<MapMode, MapMode>(ref instance2.m_mode)/*cast due to .constrained prefix*/).ToString() : "no minimap", RavenEyeTick.Reapplies));
		}

		private static void Roster(ConsoleEventArgs args)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				Say(args, "raveneye: no world loaded.");
				return;
			}
			if (!instance.IsServer() && !RavenEyeTick.EvaluateGrant(instance))
			{
				Say(args, "raveneye: no roster — this client is not granted. See `raveneye status`.");
				return;
			}
			IList<RosterEntry> list = RosterSync.Current(instance);
			Say(args, string.Format("raveneye: {0} entr{1}", list.Count, (list.Count == 1) ? "y" : "ies"));
			for (int i = 0; i < list.Count; i++)
			{
				RosterEntry rosterEntry = list[i];
				Vector3 position = rosterEntry.Position;
				Say(args, "  " + rosterEntry.Name + (rosterEntry.Dead ? " (dead — last known)" : "") + "  at (" + F(position.x, "0") + ", " + F(position.y, "0") + ", " + F(position.z, "0") + ")");
			}
		}

		private static void Map(ConsoleEventArgs args)
		{
			string text = ((args.Args.Length > 2) ? args.Args[2].ToLowerInvariant() : "");
			if (!(text == "on"))
			{
				if (!(text == "off"))
				{
					Say(args, "raveneye map is " + (ModConfig.ShowMap.Value ? "on" : "off") + ". Use `raveneye map on` or `raveneye map off`.");
					return;
				}
				ModConfig.ShowMap.Value = false;
			}
			else
			{
				ModConfig.ShowMap.Value = true;
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null && RavenEyeTick.PersonalOptOut(localPlayer))
				{
					PlatformPrefs.SetFloat(RavenEyeTick.PersonalKey(localPlayer), 1f);
					Say(args, "raveneye: cleared this character's own `nomap` opt-out as well.");
				}
			}
			Say(args, "raveneye: map " + (ModConfig.ShowMap.Value ? "on" : "off") + " for this client" + (ModConfig.ShowMap.Value ? " — it shows while the server's grant is current." : "."));
		}

		private static string F(float v, string fmt)
		{
			return v.ToString(fmt, CultureInfo.InvariantCulture);
		}

		private static string Age(float since)
		{
			return F(Time.realtimeSinceStartup - since, "0.#") + "s";
		}

		private static void Say(ConsoleEventArgs args, string text)
		{
			if ((Object)(object)args?.Context != (Object)null)
			{
				args.Context.AddString(text);
			}
			else
			{
				RavenEye.Log.LogInfo((object)text);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "GetOtherPublicPlayers")]
	public static class Patch_ZNet_GetOtherPublicPlayers
	{
		private static int _warned;

		private static void Postfix(ZNet __instance, List<PlayerInfo> playerList)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (playerList != null && !((Object)(object)__instance == (Object)null))
				{
					if (!RavenEyeTick.EvaluateGrant(__instance))
					{
						RosterSync.LastAppended = 0;
					}
					else
					{
						RosterSync.LastAppended = RosterMerge.Append(playerList, RosterSync.Current(__instance), __instance.LocalPlayerCharacterID);
					}
				}
			}
			catch (Exception ex)
			{
				if (_warned++ < 3)
				{
					RavenEye.Log.LogWarning((object)("GetOtherPublicPlayers postfix threw (" + ex.GetType().Name + ": " + ex.Message + "); vanilla's list stands."));
				}
			}
		}
	}
}
namespace RavenIron.RavenEye.Net
{
	public struct RosterEntry
	{
		public string Name;

		public ZDOID Id;

		public Vector3 Position;

		public bool Dead;
	}
	public struct RosterHeader
	{
		public string ServerVersion;

		public bool Granted;

		public float GraceSeconds;
	}
	public static class RosterPacket
	{
		public const int FormatVersion = 1;

		public const int MaxEntries = 64;

		public static void Write(ZPackage pkg, RosterHeader header, IList<RosterEntry> entries)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			if (pkg == null)
			{
				throw new ArgumentNullException("pkg");
			}
			int num = (header.Granted ? (entries?.Count ?? 0) : 0);
			if (num > 64)
			{
				throw new ArgumentException($"roster has {num} entries; the wire format allows {64}");
			}
			pkg.Write(1);
			pkg.Write(header.ServerVersion ?? "");
			pkg.Write(header.Granted);
			pkg.Write(header.GraceSeconds);
			pkg.Write(num);
			for (int i = 0; i < num; i++)
			{
				RosterEntry rosterEntry = entries[i];
				pkg.Write(rosterEntry.Name ?? "");
				pkg.Write(rosterEntry.Id);
				pkg.Write(rosterEntry.Position);
				pkg.Write(rosterEntry.Dead);
			}
		}

		public static RosterHeader Read(ZPackage pkg, List<RosterEntry> into)
		{
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			if (pkg == null)
			{
				throw new ArgumentNullException("pkg");
			}
			if (into == null)
			{
				throw new ArgumentNullException("into");
			}
			int num = pkg.ReadInt();
			if (num != 1)
			{
				string arg = ((num > 1) ? "this client" : "the server");
				throw new FormatException($"roster format v{num}; this build reads v{1} — update RavenEye on {arg}");
			}
			RosterHeader result = new RosterHeader
			{
				ServerVersion = pkg.ReadString(),
				Granted = pkg.ReadBool(),
				GraceSeconds = pkg.ReadSingle()
			};
			if (!IsFinite(result.GraceSeconds))
			{
				throw new FormatException("roster grace is not a finite number");
			}
			int num2 = pkg.ReadInt();
			if (num2 < 0 || num2 > 64)
			{
				throw new FormatException($"roster count {num2} is outside 0..{64}");
			}
			if (!result.Granted && num2 != 0)
			{
				throw new FormatException("a revoke carries no entries");
			}
			List<RosterEntry> list = new List<RosterEntry>(num2);
			for (int i = 0; i < num2; i++)
			{
				RosterEntry item = new RosterEntry
				{
					Name = pkg.ReadString(),
					Id = pkg.ReadZDOID(),
					Position = pkg.ReadVector3(),
					Dead = pkg.ReadBool()
				};
				if (!IsFinite(item.Position.x) || !IsFinite(item.Position.y) || !IsFinite(item.Position.z))
				{
					throw new FormatException($"roster entry {i} has a non-finite position");
				}
				list.Add(item);
			}
			into.Clear();
			into.AddRange(list);
			return result;
		}

		public static bool IsFinite(float f)
		{
			if (!float.IsNaN(f))
			{
				return !float.IsInfinity(f);
			}
			return false;
		}
	}
	public static class RosterSync
	{
		public const string RpcName = "com.raveniron.raveneye.roster";

		private static readonly List<RosterEntry> _cache = new List<RosterEntry>(16);

		private static readonly List<RosterEntry> _scratch = new List<RosterEntry>(16);

		private static ZRpc _registeredOn;

		private const long HostKey = long.MinValue;

		private static readonly List<RosterEntry> _roster = new List<RosterEntry>(16);

		private static readonly List<RosterEntry> _wire = new List<RosterEntry>(16);

		private static readonly List<ZNetPeer> _peerScratch = new List<ZNetPeer>(16);

		private static readonly List<ZNetPeer> _demoteScratch = new List<ZNetPeer>(4);

		private static readonly HashSet<long> _grantedPeers = new HashSet<long>();

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

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

		private static readonly DeathLedger _ledger = new DeathLedger();

		private static bool _emptyAdminListWarned;

		private static bool _overflowLogged;

		private static int _packetFailuresLogged;

		public static float LastReceipt { get; private set; } = -1f;

		public static float LastRevoke { get; private set; } = -1f;

		public static float LastGrace { get; private set; }

		public static string ServerVersion { get; private set; } = "";

		public static int PacketsReceived { get; private set; }

		public static int RevokesReceived { get; private set; }

		public static int PacketsRejected { get; private set; }

		public static string LastRejectReason { get; private set; } = "";

		public static int LastAppended { get; set; }

		public static float LastSendTime { get; private set; } = -1f;

		public static int LastSendAdmins { get; private set; }

		public static int LastRosterSize { get; private set; }

		public static int DeadSnapshots { get; private set; }

		public static int LastDropped { get; private set; }

		public static int GrantedPeerCount => _grantedPeers.Count;

		public static string ClosedGate { get; private set; } = "";

		public static bool AdminListUnreadable { get; private set; }

		public static void ResetSession()
		{
			_cache.Clear();
			_scratch.Clear();
			_registeredOn = null;
			_roster.Clear();
			_wire.Clear();
			_peerScratch.Clear();
			_demoteScratch.Clear();
			_grantedPeers.Clear();
			_uidScratch.Clear();
			_presentScratch.Clear();
			_ledger.Clear();
			_emptyAdminListWarned = false;
			_overflowLogged = false;
			_packetFailuresLogged = 0;
			LastReceipt = -1f;
			LastRevoke = -1f;
			LastGrace = 0f;
			ServerVersion = "";
			PacketsReceived = 0;
			RevokesReceived = 0;
			PacketsRejected = 0;
			LastRejectReason = "";
			LastAppended = 0;
			LastSendTime = -1f;
			LastSendAdmins = 0;
			LastRosterSize = 0;
			DeadSnapshots = 0;
			LastDropped = 0;
			ClosedGate = "";
			AdminListUnreadable = false;
		}

		public static IList<RosterEntry> Current(ZNet znet)
		{
			if ((Object)(object)znet != (Object)null && znet.IsServer())
			{
				return _roster;
			}
			return _cache;
		}

		public static void EnsureRegistered(ZNet znet)
		{
			if ((Object)(object)znet == (Object)null || znet.IsServer())
			{
				return;
			}
			ZRpc serverRPC;
			try
			{
				serverRPC = znet.GetServerRPC();
			}
			catch
			{
				return;
			}
			if (serverRPC == null || serverRPC == _registeredOn)
			{
				return;
			}
			try
			{
				serverRPC.Register<ZPackage>("com.raveniron.raveneye.roster", (Action<ZRpc, ZPackage>)RPC_Roster);
				_registeredOn = serverRPC;
			}
			catch (Exception ex)
			{
				RavenEye.Log.LogWarning((object)("RosterSync: register on the server socket failed: " + ex.Message));
				_registeredOn = serverRPC;
			}
		}

		public static bool IsRegisteredOnCurrentConnection(ZNet znet)
		{
			if ((Object)(object)znet == (Object)null || znet.IsServer())
			{
				return false;
			}
			try
			{
				return _registeredOn != null && _registeredOn == znet.GetServerRPC();
			}
			catch
			{
				return false;
			}
		}

		private static void RPC_Roster(ZRpc rpc, ZPackage pkg)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer())
			{
				return;
			}
			try
			{
				RosterHeader rosterHeader = RosterPacket.Read(pkg, _scratch);
				ServerVersion = rosterHeader.ServerVersion ?? "";
				if (rosterHeader.Granted)
				{
					_cache.Clear();
					_cache.AddRange(_scratch);
					LastGrace = rosterHeader.GraceSeconds;
					LastReceipt = Time.realtimeSinceStartup;
					PacketsReceived++;
				}
				else
				{
					_cache.Clear();
					LastReceipt = -1f;
					LastRevoke = Time.realtimeSinceStartup;
					RevokesReceived++;
				}
			}
			catch (Exception ex)
			{
				PacketsRejected++;
				string text = ex.GetType().Name + ": " + ex.Message;
				if (PacketsRejected <= 3 || text != LastRejectReason)
				{
					RavenEye.Log.LogWarning((object)("RosterSync: rejected a roster packet (" + text + ")."));
				}
				LastRejectReason = text;
			}
		}

		public static bool RevealActive(bool revealWhenMapEnabled)
		{
			if (revealWhenMapEnabled)
			{
				return true;
			}
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.GetGlobalKey((GlobalKeys)31);
			}
			return false;
		}

		public static void BuildRoster(ZNet znet, List<RosterEntry> into, float now, float deathGraceSeconds)
		{
			//IL_0019: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			into.Clear();
			_presentScratch.Clear();
			if (!znet.IsDedicated())
			{
				ZDOID localPlayerCharacterID = znet.LocalPlayerCharacterID;
				Observe(long.MinValue, localPlayerCharacterID, HostPlayerName(), znet.GetReferencePosition(), into, now);
			}
			List<ZNetPeer> peers = znet.GetPeers();
			if (peers != null)
			{
				for (int i = 0; i < peers.Count; i++)
				{
					ZNetPeer val = peers[i];
					if (val != null && val.IsReady())
					{
						Observe(val.m_uid, val.m_characterID, val.m_playerName ?? "", val.m_refPos, into, now);
					}
				}
			}
			_ledger.Retain(_presentScratch);
			DeadSnapshots = _ledger.Emit(into, now, deathGraceSeconds);
			LastRosterSize = into.Count;
		}

		private static void Observe(long key, ZDOID id, string name, Vector3 refPos, List<RosterEntry> into, float now)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			_presentScratch.Add(key);
			if (((ZDOID)(ref id)).IsNone())
			{
				_ledger.Observe(key, hasCharacter: false, default(RosterEntry), now);
				return;
			}
			RosterEntry rosterEntry = new RosterEntry
			{
				Name = name,
				Id = id,
				Position = PositionOf(id, refPos),
				Dead = false
			};
			into.Add(rosterEntry);
			_ledger.Observe(key, hasCharacter: true, rosterEntry, now);
		}

		private static Vector3 PositionOf(ZDOID id, Vector3 fallback)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ZDOMan instance = ZDOMan.instance;
				if (instance != null)
				{
					ZDO zDO = instance.GetZDO(id);
					if (zDO != null)
					{
						return zDO.GetPosition();
					}
				}
			}
			catch
			{
			}
			return fallback;
		}

		private static string HostPlayerName()
		{
			try
			{
				Game instance = Game.instance;
				object obj;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					PlayerProfile playerProfile = instance.GetPlayerProfile();
					obj = ((playerProfile != null) ? playerProfile.GetName() : null);
				}
				if (obj == null)
				{
					obj = "";
				}
				return (string)obj;
			}
			catch
			{
				return "";
			}
		}

		public static int SendToAdmins(ZNet znet, bool enabled, float graceSeconds, float deathGraceSeconds, bool revealWhenMapEnabled, bool verbose)
		{
			float now = (LastSendTime = Time.realtimeSinceStartup);
			List<ZNetPeer> peers = znet.GetPeers();
			_peerScratch.Clear();
			if (peers != null)
			{
				_peerScratch.AddRange(peers);
			}
			string text = ((!enabled) ? "Server.Enabled is off" : ((!RevealActive(revealWhenMapEnabled)) ? "the world has a map and RevealWhenMapEnabled is off" : ""));
			if (text.Length > 0)
			{
				if (ClosedGate != text)
				{
					RavenEye.Log.LogInfo((object)("roster: gate closed — " + text + "."));
				}
				ClosedGate = text;
				_roster.Clear();
				_ledger.Clear();
				LastRosterSize = 0;
				DeadSnapshots = 0;
				RevokeEveryone(graceSeconds, "gate closed");
				LastSendAdmins = 0;
				return 0;
			}
			if (ClosedGate.Length > 0)
			{
				RavenEye.Log.LogInfo((object)"roster: gate open again.");
			}
			ClosedGate = "";
			BuildRoster(znet, _roster, now, deathGraceSeconds);
			ZPackage val = null;
			bool flag = false;
			int num = 0;
			_uidScratch.Clear();
			_demoteScratch.Clear();
			for (int i = 0; i < _peerScratch.Count; i++)
			{
				ZNetPeer val2 = _peerScratch[i];
				if (val2 == null || !val2.IsReady())
				{
					continue;
				}
				_uidScratch.Add(val2.m_uid);
				if (!AdminGate.IsAdmin(znet, val2))
				{
					if (_grantedPeers.Contains(val2.m_uid))
					{
						_demoteScratch.Add(val2);
					}
					continue;
				}
				if (val == null && !flag)
				{
					try
					{
						val = BuildGrantPacket(graceSeconds);
					}
					catch (Exception ex)
					{
						flag = true;
						if (_packetFailuresLogged++ < 3)
						{
							RavenEye.Log.LogWarning((object)("roster: could not build the packet (" + ex.GetType().Name + ": " + ex.Message + "); no grant this cadence."));
						}
					}
				}
				if (val != null)
				{
					try
					{
						val2.m_rpc.Invoke("com.raveniron.raveneye.roster", new object[1] { val });
						num++;
					}
					catch (Exception ex2)
					{
						RavenEye.Log.LogWarning((object)("roster: send to " + Describe(val2) + " failed: " + ex2.Message));
						continue;
					}
					if (_grantedPeers.Add(val2.m_uid))
					{
						RavenEye.Log.LogInfo((object)("roster: " + Describe(val2) + " is an admin — map granted."));
					}
				}
			}
			int num2 = AdminGate.AdminListCount(znet);
			AdminListUnreadable = num2 <= 0 && _uidScratch.Count > 0;
			WarnIfAdminListEmpty(num2, _uidScratch.Count);
			if (!AdminListUnreadable)
			{
				for (int j = 0; j < _demoteScratch.Count; j++)
				{
					ZNetPeer val3 = _demoteScratch[j];
					_grantedPeers.Remove(val3.m_uid);
					SendRevoke(val3, graceSeconds);
					RavenEye.Log.LogInfo((object)("roster: " + Describe(val3) + " is no longer an admin — map revoked."));
				}
			}
			if (_grantedPeers.Count > 0)
			{
				_grantedPeers.RemoveWhere((long uid) => !_uidScratch.Contains(uid));
			}
			LastSendAdmins = num;
			if (verbose)
			{
				RavenEye.Log.LogInfo((object)string.Format("roster: {0} entr{1} ({2} dead) sent to {3} admin(s).", _roster.Count, (_roster.Count == 1) ? "y" : "ies", DeadSnapshots, num));
			}
			return num;
		}

		private static ZPackage BuildGrantPacket(float graceSeconds)
		{
			//IL_0093: 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)
			//IL_00c8: Expected O, but got Unknown
			//IL_00c9: Expected O, but got Unknown
			_wire.Clear();
			int num = Math.Min(_roster.Count, 64);
			for (int i = 0; i < num; i++)
			{
				_wire.Add(_roster[i]);
			}
			LastDropped = _roster.Count - num;
			if (LastDropped > 0 && !_overflowLogged)
			{
				_overflowLogged = true;
				RavenEye.Log.LogWarning((object)$"roster: {_roster.Count} entries exceed the wire cap of {64}; the first {num} are sent (deaths dropped first). Logged once.");
			}
			ZPackage val = new ZPackage();
			RosterPacket.Write(val, new RosterHeader
			{
				ServerVersion = "0.2.2",
				Granted = true,
				GraceSeconds = graceSeconds
			}, _wire);
			return val;
		}

		private static void RevokeEveryone(float graceSeconds, string why)
		{
			if (_grantedPeers.Count == 0)
			{
				return;
			}
			for (int i = 0; i < _peerScratch.Count; i++)
			{
				ZNetPeer val = _peerScratch[i];
				if (val != null && val.IsReady() && _grantedPeers.Contains(val.m_uid))
				{
					SendRevoke(val, graceSeconds);
					RavenEye.Log.LogInfo((object)("roster: " + Describe(val) + " — map revoked (" + why + ")."));
				}
			}
			_grantedPeers.Clear();
		}

		private static void SendRevoke(ZNetPeer peer, float graceSeconds)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			try
			{
				ZPackage val = new ZPackage();
				RosterPacket.Write(val, new RosterHeader
				{
					ServerVersion = "0.2.2",
					Granted = false,
					GraceSeconds = graceSeconds
				}, null);
				peer.m_rpc.Invoke("com.raveniron.raveneye.roster", new object[1] { val });
			}
			catch (Exception ex)
			{
				RavenEye.Log.LogWarning((object)("roster: revoke to " + Describe(peer) + " failed: " + ex.Message));
			}
		}

		private static void WarnIfAdminListEmpty(int admins, int peerCount)
		{
			if (admins <= 0 && peerCount > 0)
			{
				if (!_emptyAdminListWarned)
				{
					_emptyAdminListWarned = true;
					RavenEye.Log.LogWarning((object)($"roster: adminlist.txt reads as EMPTY while {peerCount} player(s) are online — no grants, and no " + "revokes either (this is what a failed read looks like, not a de-admin). If the file is not actually empty, the game failed to read it (open in an editor?) and will only retry when its timestamp changes: re-save the file. Granted admins keep their map for the grace window."));
				}
			}
			else if (admins > 0 && _emptyAdminListWarned)
			{
				_emptyAdminListWarned = false;
				RavenEye.Log.LogInfo((object)string.Format("roster: adminlist.txt reads {0} entr{1} again.", admins, (admins == 1) ? "y" : "ies"));
			}
		}

		private static string Describe(ZNetPeer peer)
		{
			string text;
			try
			{
				ISocket socket = peer.m_socket;
				text = ((socket != null) ? socket.GetHostName() : null) ?? "?";
			}
			catch
			{
				text = "?";
			}
			return peer.m_playerName + " (" + text + ")";
		}
	}
}
namespace RavenIron.RavenEye.Core
{
	public sealed class DeathLedger
	{
		private struct Death
		{
			public RosterEntry Entry;

			public float DiedAt;
		}

		private readonly Dictionary<long, RosterEntry> _live = new Dictionary<long, RosterEntry>(16);

		private readonly Dictionary<long, Death> _dead = new Dictionary<long, Death>(16);

		private readonly List<long> _scratch = new List<long>(16);

		public int LiveCount => _live.Count;

		public int DeadCount => _dead.Count;

		public void Observe(long key, bool hasCharacter, RosterEntry live, float now)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			RosterEntry value;
			bool flag = _live.TryGetValue(key, out value);
			if (hasCharacter)
			{
				if (flag && value.Id != live.Id)
				{
					_dead[key] = new Death
					{
						Entry = value,
						DiedAt = now
					};
				}
				live.Dead = false;
				_live[key] = live;
			}
			else if (flag)
			{
				_dead[key] = new Death
				{
					Entry = value,
					DiedAt = now
				};
				_live.Remove(key);
			}
		}

		public int Emit(List<RosterEntry> into, float now, float graceSeconds)
		{
			if (graceSeconds <= 0f)
			{
				_dead.Clear();
				return 0;
			}
			int num = 0;
			_scratch.Clear();
			foreach (KeyValuePair<long, Death> item in _dead)
			{
				if (now - item.Value.DiedAt > graceSeconds)
				{
					_scratch.Add(item.Key);
					continue;
				}
				RosterEntry entry = item.Value.Entry;
				entry.Dead = true;
				into?.Add(entry);
				num++;
			}
			for (int i = 0; i < _scratch.Count; i++)
			{
				_dead.Remove(_scratch[i]);
			}
			return num;
		}

		public void Retain(List<long> present)
		{
			Prune(_live, present);
			Prune(_dead, present);
		}

		public void Clear()
		{
			_live.Clear();
			_dead.Clear();
		}

		private void Prune<T>(Dictionary<long, T> store, List<long> present)
		{
			if (store.Count == 0)
			{
				return;
			}
			_scratch.Clear();
			foreach (long key in store.Keys)
			{
				if (present == null || !present.Contains(key))
				{
					_scratch.Add(key);
				}
			}
			for (int i = 0; i < _scratch.Count; i++)
			{
				store.Remove(_scratch[i]);
			}
		}
	}
	public static class Grant
	{
		public enum Reason
		{
			Granted,
			GrantedAsAuthority,
			ClientOff,
			NoRenderer,
			NotConnected,
			Revoked,
			NeverReceived,
			Stale
		}

		public const float MinGraceSeconds = 10f;

		public const float MaxGraceSeconds = 120f;

		public const float Never = -1f;

		public static float EffectiveGrace(float graceSeconds)
		{
			if (float.IsNaN(graceSeconds) || graceSeconds <= 0f)
			{
				return 10f;
			}
			if (graceSeconds < 10f)
			{
				return 10f;
			}
			if (graceSeconds > 120f)
			{
				return 120f;
			}
			return graceSeconds;
		}

		public static bool IsFresh(float now, float lastReceipt, float graceSeconds)
		{
			if (lastReceipt < 0f)
			{
				return false;
			}
			float num = now - lastReceipt;
			if (num < 0f)
			{
				return false;
			}
			return num <= EffectiveGrace(graceSeconds);
		}

		public static Reason Explain(bool showMap, bool hasRenderer, bool isAuthority, bool connected, float now, float lastReceipt, float lastRevoke, float graceSeconds)
		{
			if (!showMap)
			{
				return Reason.ClientOff;
			}
			if (!hasRenderer)
			{
				return Reason.NoRenderer;
			}
			if (isAuthority)
			{
				return Reason.GrantedAsAuthority;
			}
			if (!connected)
			{
				return Reason.NotConnected;
			}
			if (IsFresh(now, lastReceipt, graceSeconds))
			{
				return Reason.Granted;
			}
			if (lastReceipt < 0f)
			{
				if (!(lastRevoke >= 0f))
				{
					return Reason.NeverReceived;
				}
				return Reason.Revoked;
			}
			return Reason.Stale;
		}

		public static bool IsGranted(Reason r)
		{
			if (r != Reason.Granted)
			{
				return r == Reason.GrantedAsAuthority;
			}
			return true;
		}

		public static bool Evaluate(bool showMap, bool hasRenderer, bool isAuthority, bool connected, float now, float lastReceipt, float graceSeconds)
		{
			return IsGranted(Explain(showMap, hasRenderer, isAuthority, connected, now, lastReceipt, -1f, graceSeconds));
		}

		public static bool DesiredNoMap(bool granted, bool worldNoMap, bool personalOptOut)
		{
			if (!granted)
			{
				return worldNoMap || personalOptOut;
			}
			return personalOptOut;
		}

		public static bool CleanNoMap(bool vanillaClean, bool granted)
		{
			if (vanillaClean)
			{
				return !granted;
			}
			return false;
		}
	}
	public sealed class GrantTracker
	{
		public bool Granted { get; private set; }

		public bool Update(bool granted)
		{
			if (granted == Granted)
			{
				return false;
			}
			Granted = granted;
			return true;
		}

		public void Reset()
		{
			Granted = false;
		}
	}
	public static class PatchInstall
	{
		public static List<string> Each(IEnumerable<Type> classes, Action<Type> install, Action<Type, Exception> onFailure)
		{
			List<string> list = new List<string>();
			foreach (Type @class in classes)
			{
				try
				{
					install(@class);
				}
				catch (Exception arg)
				{
					list.Add(@class.Name);
					try
					{
						onFailure?.Invoke(@class, arg);
					}
					catch
					{
					}
				}
			}
			return list;
		}
	}
	public class RavenEyeTick : MonoBehaviour
	{
		private const float ConvergeEvery = 0.5f;

		private readonly GrantTracker _grant = new GrantTracker();

		private float _sendAccumulator;

		private float _convergeAccumulator;

		private bool _hadWorld;

		private bool _roleLogged;

		private int _reapplies;

		private int _sendFailuresLogged;

		private int _convergeFailuresLogged;

		public static bool GrantedNow { get; private set; }

		public static int Reapplies { get; private set; }

		public static string Role()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "no world";
			}
			if (instance.IsDedicated())
			{
				return "dedicated server";
			}
			if (instance.IsServer())
			{
				return "listen host";
			}
			return "client";
		}

		public static bool IsAuthorityGranting(ZNet znet)
		{
			if ((Object)(object)znet != (Object)null && znet.IsServer() && ModConfig.ServerEnabled.Value)
			{
				return RosterSync.RevealActive(ModConfig.RevealWhenMapEnabled.Value);
			}
			return false;
		}

		public static Grant.Reason ExplainGrant(ZNet znet)
		{
			if ((Object)(object)znet == (Object)null)
			{
				return Grant.Reason.NotConnected;
			}
			bool isAuthority = IsAuthorityGranting(znet);
			bool connected = znet.IsServer() || znet.GetServerPeer() != null;
			return Grant.Explain(ModConfig.ShowMap.Value, RavenEye.HasRenderer, isAuthority, connected, Time.realtimeSinceStartup, RosterSync.LastReceipt, RosterSync.LastRevoke, RosterSync.LastGrace);
		}

		public static bool EvaluateGrant(ZNet znet)
		{
			return Grant.IsGranted(ExplainGrant(znet));
		}

		public static bool PersonalOptOut(Player local)
		{
			if ((Object)(object)local == (Object)null)
			{
				return false;
			}
			return PlatformPrefs.GetFloat(PersonalKey(local), 1f) == 0f;
		}

		public static string PersonalKey(Player local)
		{
			return "mapenabled_" + local.GetPlayerName();
		}

		private void Update()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				if (_hadWorld)
				{
					_hadWorld = false;
					_roleLogged = false;
					_sendAccumulator = 0f;
					_convergeAccumulator = 0f;
					_grant.Reset();
					GrantedNow = false;
					RosterSync.ResetSession();
				}
				return;
			}
			_hadWorld = true;
			if (!_roleLogged)
			{
				_roleLogged = true;
				string arg = (instance.IsServer() ? $"admin list entries={AdminGate.AdminListCount(instance)}" : "admin list is the server's job");
				RavenEye.Log.LogInfo((object)($"RavenEye online — role={Role()}, renderer={RavenEye.HasRenderer}, " + "server feature=" + (ModConfig.ServerEnabled.Value ? "on" : "off") + ", " + $"interval={ModConfig.IntervalSeconds.Value:0.##}s, grace={ModConfig.GraceSeconds.Value:0}s, " + $"deathGrace={ModConfig.DeathGraceSeconds.Value:0}s, " + $"revealWhenMapEnabled={ModConfig.RevealWhenMapEnabled.Value}, " + $"client showMap={ModConfig.ShowMap.Value}, {arg}."));
			}
			if (instance.IsServer())
			{
				ServerTick(instance, Time.deltaTime);
			}
			else
			{
				RosterSync.EnsureRegistered(instance);
			}
			if (RavenEye.HasRenderer)
			{
				ClientTick(instance, Time.deltaTime);
			}
		}

		private void ServerTick(ZNet znet, float dt)
		{
			_sendAccumulator += dt;
			float num = Mathf.Max(0.5f, ModConfig.IntervalSeconds.Value);
			if (_sendAccumulator < num)
			{
				return;
			}
			_sendAccumulator = 0f;
			try
			{
				RosterSync.SendToAdmins(znet, ModConfig.ServerEnabled.Value, ModConfig.GraceSeconds.Value, ModConfig.DeathGraceSeconds.Value, ModConfig.RevealWhenMapEnabled.Value, ModConfig.VerboseLogging.Value);
			}
			catch (Exception ex)
			{
				if (_sendFailuresLogged++ < 3)
				{
					RavenEye.Log.LogWarning((object)("roster send failed (" + ex.GetType().Name + ": " + ex.Message + "); silence revokes within the grace window."));
				}
			}
		}

		private void ClientTick(ZNet znet, float dt)
		{
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Invalid comparison between Unknown and I4
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			Grant.Reason r = ExplainGrant(znet);
			bool flag = (GrantedNow = Grant.IsGranted(r));
			bool num = _grant.Update(flag);
			if (num)
			{
				RavenEye.Log.LogInfo((object)(flag ? ("admin map GRANTED — " + Words(r) + ".") : ("admin map not granted — " + Words(r) + ".")));
			}
			_convergeAccumulator += dt;
			if (!num && _convergeAccumulator < 0.5f)
			{
				return;
			}
			_convergeAccumulator = 0f;
			Player localPlayer = Player.m_localPlayer;
			Minimap instance = Minimap.instance;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				bool worldNoMap = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)31);
				bool flag3 = Grant.DesiredNoMap(flag, worldNoMap, PersonalOptOut(localPlayer));
				if (Game.m_noMap == flag3 && (!flag3 || (int)instance.m_mode == 0))
				{
					return;
				}
				Game.m_noMap = flag3;
				if (!flag3)
				{
					if ((int)instance.m_mode == 0)
					{
						instance.SetMapMode((MapMode)1);
					}
				}
				else
				{
					instance.SetMapMode((MapMode)0);
				}
				Reapplies = ++_reapplies;
				if (_reapplies <= 5 || _reapplies % 100 == 0)
				{
					RavenEye.Log.LogInfo((object)$"map flag corrected (noMap {!flag3} → {flag3}, {Words(r)}); corrections so far: {_reapplies}.");
				}
			}
			catch (Exception ex)
			{
				if (_convergeFailuresLogged++ < 3)
				{
					RavenEye.Log.LogWarning((object)("map convergence threw (" + ex.GetType().Name + ": " + ex.Message + "); will retry."));
				}
			}
		}

		public static string Words(Grant.Reason r)
		{
			return r switch
			{
				Grant.Reason.Granted => $"server roster {Time.realtimeSinceStartup - RosterSync.LastReceipt:0.#}s ago, grace {Grant.EffectiveGrace(RosterSync.LastGrace):0}s", 
				Grant.Reason.GrantedAsAuthority => "this is the host and the server gates are open", 
				Grant.Reason.ClientOff => "Client.ShowMap is off (`raveneye map on`)", 
				Grant.Reason.NoRenderer => "no renderer (headless)", 
				Grant.Reason.NotConnected => "not connected to a server", 
				Grant.Reason.Revoked => $"the server revoked it {Time.realtimeSinceStartup - RosterSync.LastRevoke:0}s ago (removed from adminlist.txt, or the server's gate closed)", 
				Grant.Reason.NeverReceived => "no roster has arrived since this join (not on the server's adminlist.txt, or the server is not running RavenEye)", 
				Grant.Reason.Stale => $"stale — last roster {Time.realtimeSinceStartup - RosterSync.LastReceipt:0}s ago, past the {Grant.EffectiveGrace(RosterSync.LastGrace):0}s grace (server stalled or stopped)", 
				_ => r.ToString(), 
			};
		}
	}
	public static class RosterMerge
	{
		public const string DeadSuffix = " (dead)";

		public static int Append(List<PlayerInfo> target, IList<RosterEntry> roster, ZDOID self)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if (target == null || roster == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < roster.Count; i++)
			{
				RosterEntry rosterEntry = roster[i];
				if (!((ZDOID)(ref rosterEntry.Id)).IsNone() && !(rosterEntry.Id == self) && !ContainsCharacter(target, rosterEntry.Id))
				{
					target.Add(new PlayerInfo
					{
						m_name = rosterEntry.Name + (rosterEntry.Dead ? " (dead)" : ""),
						m_characterID = rosterEntry.Id,
						m_position = rosterEntry.Position,
						m_publicPosition = true
					});
					num++;
				}
			}
			return num;
		}

		private static bool ContainsCharacter(List<PlayerInfo> list, ZDOID id)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i].m_characterID == id)
				{
					return true;
				}
			}
			return false;
		}
	}
}
namespace RavenIron.RavenEye.Config
{
	public static class ModConfig
	{
		public static ConfigEntry<bool> ServerEnabled;

		public static ConfigEntry<float> IntervalSeconds;

		public static ConfigEntry<float> GraceSeconds;

		public static ConfigEntry<float> DeathGraceSeconds;

		public static ConfigEntry<bool> RevealWhenMapEnabled;

		public static ConfigEntry<bool> VerboseLogging;

		public static ConfigEntry<bool> ShowMap;

		public static void Bind(ConfigFile cfg)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Expected O, but got Unknown
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Expected O, but got Unknown
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Expected O, but got Unknown
			ServerEnabled = cfg.Bind<bool>("Server", "Enabled", true, new ConfigDescription("Send admins the position of every online player. Read on the SERVER (dedicated or listen host); a client's copy of this value does nothing.", (AcceptableValueBase)null, Array.Empty<object>()));
			IntervalSeconds = cfg.Bind<float>("Server", "IntervalSeconds", 2f, new ConfigDescription("Seconds between roster sends. Vanilla refreshes player positions every 2 s, so a smaller value only costs bandwidth; the ceiling stays under the shortest grace window so a live server can never be mistaken for a silent one. Read on the SERVER.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 5f), Array.Empty<object>()));
			GraceSeconds = cfg.Bind<float>("Server", "GraceSeconds", 60f, new ConfigDescription("If the server goes SILENT (crash, stall, network), an admin's map survives this many seconds after the last roster before it relocks. A demotion does not wait for this: it is sent explicitly and relocks within one interval. Clients clamp what they receive to 10..120. Read on the SERVER.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 120f), Array.Empty<object>()));
			DeathGraceSeconds = cfg.Bind<float>("Server", "DeathGraceSeconds", 180f, new ConfigDescription("After a player dies, keep their last known position on the admins' map, marked (dead), for this many seconds — so an admin can answer 'where did I die?' on a world with no map. 0 disables. Read on the SERVER.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), Array.Empty<object>()));
			RevealWhenMapEnabled = cfg.Bind<bool>("Server", "RevealWhenMapEnabled", false, new ConfigDescription("On a world WITH a map, also show admins the players who turned position sharing off. Off by default: out of the box RavenEye is a no-map tool and does nothing on a world that has a map, where players' share-position choice stands. Read on the SERVER.", (AcceptableValueBase)null, Array.Empty<object>()));
			VerboseLogging = cfg.Bind<bool>("Server", "VerboseLogging", false, new ConfigDescription("Log every roster send. Grants and revocations are logged regardless. Read on the SERVER.", (AcceptableValueBase)null, Array.Empty<object>()));
			ShowMap = cfg.Bind<bool>("Client", "ShowMap", true, new ConfigDescription("Use the map when the server grants it to this client. Set false to keep the vanilla no-map experience anyway (an admin playing honestly). `raveneye map on|off` toggles this at runtime. Read on the CLIENT.", (AcceptableValueBase)null, Array.Empty<object>()));
		}
	}
}