Decompiled source of RunicSentinelServer v1.0.0

RunicSentinelServer.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunicSafety.Api;
using RunicSentinel.Admission;
using RunicSentinel.Api;
using RunicSentinel.Contracts;
using RunicSentinel.Core;
using RunicSentinel.Runtime;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Sentinel Server")]
[assembly: AssemblyDescription("Dedicated and listen-host Sentinel authority without client reporting or administrator GUI code")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Sentinel Server")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: InternalsVisibleTo("RunicSentinelServer.Tests")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.0.0.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 RunicSentinel
{
	[BepInPlugin("chazman.RunicSentinelServer", "Runic Sentinel Server", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInIncompatibility("chazman.RunicSentinel")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicSentinelServer";

		public const string Name = "Runic Sentinel Server";

		public const string Version = "1.0.0";

		public const string ModuleId = "runic.sentinel.server";

		private static Plugin _instance;

		private SentinelRuntime _runtime;

		private SentinelEnforcementRuntime _enforcement;

		private SentinelOperatorCommands _operatorCommands;

		private SentinelManagedPolicyService _managedPolicy;

		private SentinelAdminControl _adminControl;

		private SentinelFlightRecorder _flightRecorder;

		private Harmony _failClosedHarmony;

		private Harmony _roleHarmony;

		private Harmony _authorityHarmony;

		private ZNet _authorityNetwork;

		private ZNet _destroyedAuthorityNetwork;

		private int _refreshRequested;

		private bool _authorityStarted;

		private bool _activationFailed;

		private bool _configuredRoleKnown;

		private bool _serverRoleConfigured;

		private bool _unavailableGateLogged;

		private bool _clientNoticeLogged;

		private void Awake()
		{
			SentinelConfig.Bind(((BaseUnityPlugin)this).Config);
			ConfigEntry<bool> enabled = SentinelConfig.Enabled;
			if (enabled != null && !enabled.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Sentinel Server is disabled; no worker or network handlers were created.");
				return;
			}
			_instance = this;
			try
			{
				InstallPermanentFailClosedGate();
			}
			catch (Exception ex)
			{
				_activationFailed = true;
				Shutdown();
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic Sentinel Server could not install its Required-mode safety gate and is inactive: " + ex));
				return;
			}
			try
			{
				InstallRoleObservers();
				TryActivateAuthority(ZNet.instance);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Sentinel Server v1.0.0 installed. Authority services remain inert until Valheim selects a dedicated server or listen host role.");
			}
			catch (Exception ex2)
			{
				_activationFailed = true;
				ShutdownAuthority();
				try
				{
					Harmony roleHarmony = _roleHarmony;
					if (roleHarmony != null)
					{
						roleHarmony.UnpatchSelf();
					}
				}
				catch
				{
				}
				_roleHarmony = null;
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic Sentinel Server role bootstrap failed. Required mode remains fail closed through the permanent world-load and connection gates: " + ex2));
			}
		}

		private void InstallPermanentFailClosedGate()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			MethodInfo methodInfo = RequireInstanceVoid("RPC_ServerHandshake", typeof(ZRpc));
			MethodInfo methodInfo2 = RequireInstanceVoid("LoadWorld");
			_failClosedHarmony = new Harmony("chazman.RunicSentinelServer.fail-closed");
			try
			{
				_failClosedHarmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SentinelServerRoleBootstrap), "BeforeUnavailableServerHandshake", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_failClosedHarmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SentinelServerRoleBootstrap), "BeforeUnavailableWorldLoad", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				try
				{
					_failClosedHarmony.UnpatchSelf();
				}
				catch
				{
				}
				_failClosedHarmony = null;
				throw;
			}
		}

		private void InstallRoleObservers()
		{
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Expected O, but got Unknown
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Expected O, but got Unknown
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "SetServer", new Type[6]
			{
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(string),
				typeof(string),
				typeof(World)
			}, (Type[])null);
			if (methodInfo == null || !methodInfo.IsStatic || methodInfo.ReturnType != typeof(void))
			{
				throw new MissingMethodException(typeof(ZNet).FullName, "SetServer");
			}
			MethodInfo methodInfo2 = RequireInstanceVoid("Awake");
			MethodInfo methodInfo3 = RequireInstanceVoid("OnNewConnection", typeof(ZNetPeer));
			MethodInfo methodInfo4 = RequireInstanceVoid("OnDestroy");
			_roleHarmony = new Harmony("chazman.RunicSentinelServer.authority-role");
			_roleHarmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SentinelServerRoleBootstrap), "AfterSetServer", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_roleHarmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(SentinelServerRoleBootstrap), "AfterZNetAwake", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_roleHarmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(SentinelServerRoleBootstrap), "AfterNewConnection", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_roleHarmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(SentinelServerRoleBootstrap), "AfterZNetDestroy", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static MethodInfo RequireInstanceVoid(string name, params Type[] parameters)
		{
			MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), name, parameters ?? Type.EmptyTypes, (Type[])null);
			if (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(void))
			{
				throw new MissingMethodException(typeof(ZNet).FullName, name);
			}
			return methodInfo;
		}

		internal static void ObserveConfiguredRole(bool server)
		{
			Plugin instance = _instance;
			if (!((Object)(object)instance == (Object)null))
			{
				instance._configuredRoleKnown = true;
				instance._serverRoleConfigured = server;
				instance._destroyedAuthorityNetwork = null;
				if (server)
				{
					instance.TryActivateAuthority(null);
					return;
				}
				instance.ShutdownAuthority();
				instance.LogClientInertOnce();
			}
		}

		internal static void ObserveNetwork(ZNet network)
		{
			_instance?.TryActivateAuthority(network);
		}

		internal static void ObserveConnection(ZNet network, ZNetPeer peer)
		{
			Plugin instance = _instance;
			if (!((Object)(object)instance == (Object)null))
			{
				instance.TryActivateAuthority(network);
				if (instance._authorityStarted && (Object)(object)network != (Object)null && network.IsServer())
				{
					instance._authorityNetwork = network;
					SentinelNetworkCompatibility.ObserveConnection(network, peer);
				}
				else
				{
					instance.BlockUnavailableConnection(network, peer, null);
				}
			}
		}

		internal static void ObserveNetworkDestroyed(ZNet network)
		{
			Plugin instance = _instance;
			if ((Object)(object)instance != (Object)null && instance._authorityStarted && network == instance._authorityNetwork)
			{
				instance._destroyedAuthorityNetwork = network;
				instance.ShutdownAuthority();
			}
		}

		internal static bool AllowUnavailableServerHandshake(ZNet network, ZRpc rpc)
		{
			Plugin instance = _instance;
			if (!((Object)(object)instance == (Object)null))
			{
				return !instance.BlockUnavailableConnection(network, null, rpc);
			}
			return true;
		}

		internal static void GuardUnavailableWorldLoad(ZNet network)
		{
			Plugin instance = _instance;
			if ((Object)(object)instance == (Object)null || !instance.ShouldBlockUnavailable(network))
			{
				return;
			}
			instance.LogUnavailableGateOnce();
			throw new InvalidOperationException("Runic Sentinel Server Required authority is unavailable; world load was blocked.");
		}

		private void TryActivateAuthority(ZNet network)
		{
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Expected O, but got Unknown
			if (_authorityStarted)
			{
				if ((Object)(object)network != (Object)null && network.IsServer())
				{
					_authorityNetwork = network;
					_destroyedAuthorityNetwork = null;
				}
			}
			else
			{
				if (_activationFailed || network == _destroyedAuthorityNetwork)
				{
					return;
				}
				bool flag = _configuredRoleKnown && _serverRoleConfigured;
				if ((Object)(object)network != (Object)null)
				{
					try
					{
						flag |= network.IsServer();
					}
					catch
					{
					}
				}
				if (flag)
				{
					try
					{
						_runtime = new SentinelRuntime();
						_flightRecorder = new SentinelFlightRecorder(_runtime.Evidence, ((BaseUnityPlugin)this).Logger, Paths.ConfigPath);
						_runtime.AttachNetwork(SentinelConfig.RemoteAdmissionMode);
						_enforcement = new SentinelEnforcementRuntime(_runtime);
						SentinelIntegrationApi.Attach(_enforcement);
						SentinelTransitionBackup.Attach(_runtime, ((BaseUnityPlugin)this).Logger, Paths.ConfigPath);
						_managedPolicy = new SentinelManagedPolicyService(_runtime, ((BaseUnityPlugin)this).Logger, Paths.ConfigPath, (string reason) => SentinelTransitionBackup.CreateVerifiedBackupNow(reason));
						_operatorCommands = new SentinelOperatorCommands(_runtime, ((BaseUnityPlugin)this).Logger, Paths.ConfigPath, _managedPolicy);
						_adminControl = new SentinelAdminControl(_runtime, _managedPolicy, _operatorCommands);
						_authorityHarmony = new Harmony("chazman.RunicSentinelServer.authority");
						_authorityHarmony.PatchAll(typeof(Plugin).Assembly);
						SentinelConfig.Changed += Refresh;
						_runtime.Start(Paths.ConfigPath);
						_authorityNetwork = (((Object)(object)network != (Object)null && network.IsServer()) ? network : null);
						_authorityStarted = true;
						((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Sentinel Server authority services started. Required admission now gates the exact direct connection and the dedicated console/admin backend is available.");
						return;
					}
					catch (Exception ex)
					{
						_activationFailed = true;
						ShutdownAuthority();
						((BaseUnityPlugin)this).Logger.LogError((object)("Runic Sentinel Server authority startup failed closed: " + ex));
						return;
					}
				}
				if ((Object)(object)network != (Object)null || _configuredRoleKnown)
				{
					LogClientInertOnce();
				}
			}
		}

		internal static bool UnavailableAuthorityFailsClosed(SentinelRemoteAdmissionMode mode, bool authoritative, bool authorityStarted)
		{
			if (authoritative && !authorityStarted)
			{
				return mode == SentinelRemoteAdmissionMode.Required;
			}
			return false;
		}

		private bool ShouldBlockUnavailable(ZNet network)
		{
			bool authoritative = false;
			try
			{
				authoritative = (Object)(object)network != (Object)null && network.IsServer();
			}
			catch
			{
			}
			return UnavailableAuthorityFailsClosed(SentinelConfig.RemoteAdmissionMode, authoritative, _authorityStarted);
		}

		private bool BlockUnavailableConnection(ZNet network, ZNetPeer peer, ZRpc rpc)
		{
			if (!ShouldBlockUnavailable(network))
			{
				return false;
			}
			LogUnavailableGateOnce();
			try
			{
				ZNetPeer val = peer;
				if (val == null && rpc != null)
				{
					foreach (ZNetPeer peer2 in network.GetPeers())
					{
						if (peer2 != null && peer2.m_rpc == rpc)
						{
							val = peer2;
							break;
						}
					}
				}
				if (val != null)
				{
					network.Disconnect(val);
				}
			}
			catch
			{
			}
			return true;
		}

		private void LogUnavailableGateOnce()
		{
			if (!_unavailableGateLogged)
			{
				_unavailableGateLogged = true;
				((BaseUnityPlugin)this).Logger.LogError((object)"Runic Sentinel Server Required authority is unavailable. World loading and native client admission remain blocked until the server is restarted successfully.");
			}
		}

		private void LogClientInertOnce()
		{
			if (!_clientNoticeLogged)
			{
				_clientNoticeLogged = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Sentinel Server detected a non-authoritative client and remains fully inert. Install Runic Sentinel Client in player-only profiles.");
			}
		}

		private void Refresh()
		{
			Interlocked.Exchange(ref _refreshRequested, 1);
		}

		private void Update()
		{
			ZNet instance = ZNet.instance;
			if (_authorityStarted && (Object)(object)instance != (Object)null && !instance.IsServer())
			{
				ShutdownAuthority();
				LogClientInertOnce();
				return;
			}
			if (_authorityStarted && (Object)(object)instance != (Object)null && instance.IsServer())
			{
				_authorityNetwork = instance;
				_destroyedAuthorityNetwork = null;
			}
			if (!_authorityStarted)
			{
				if ((Object)(object)instance != (Object)null)
				{
					TryActivateAuthority(instance);
				}
				return;
			}
			try
			{
				_runtime?.TickNetwork();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel server admission tick stopped safely: " + ex.Message));
			}
			try
			{
				_runtime?.TickIntegrity();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel server integrity check failed closed: " + ex2.Message));
			}
			try
			{
				_adminControl?.Tick();
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel server administrator transport stopped safely: " + ex3.Message));
			}
			try
			{
				_operatorCommands?.TickDedicatedConsole();
			}
			catch (Exception ex4)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel server console input stopped safely: " + ex4.Message));
			}
			if (Interlocked.Exchange(ref _refreshRequested, 0) == 0 || _runtime == null)
			{
				return;
			}
			try
			{
				_runtime.Start(Paths.ConfigPath);
			}
			catch (Exception ex5)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Sentinel server policy refresh failed closed: " + ex5.Message));
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			if (_instance == this)
			{
				_instance = null;
			}
			ShutdownAuthority();
			try
			{
				Harmony roleHarmony = _roleHarmony;
				if (roleHarmony != null)
				{
					roleHarmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			_roleHarmony = null;
			try
			{
				Harmony failClosedHarmony = _failClosedHarmony;
				if (failClosedHarmony != null)
				{
					failClosedHarmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			_failClosedHarmony = null;
		}

		private void ShutdownAuthority()
		{
			SentinelConfig.Changed -= Refresh;
			Interlocked.Exchange(ref _refreshRequested, 0);
			_authorityStarted = false;
			_authorityNetwork = null;
			try
			{
				Harmony authorityHarmony = _authorityHarmony;
				if (authorityHarmony != null)
				{
					authorityHarmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			_authorityHarmony = null;
			try
			{
				_adminControl?.Dispose();
			}
			catch
			{
			}
			_adminControl = null;
			SentinelTransitionBackup.Detach();
			try
			{
				_operatorCommands?.Dispose();
			}
			catch
			{
			}
			_operatorCommands = null;
			_managedPolicy = null;
			try
			{
				_flightRecorder?.Dispose();
			}
			catch
			{
			}
			_flightRecorder = null;
			SentinelIntegrationApi.Detach(_enforcement);
			try
			{
				_enforcement?.Dispose();
			}
			catch
			{
			}
			_enforcement = null;
			try
			{
				_runtime?.Dispose();
			}
			catch
			{
			}
			_runtime = null;
		}
	}
	internal static class SentinelServerRoleBootstrap
	{
		internal static void AfterSetServer([HarmonyArgument(0)] bool server)
		{
			Plugin.ObserveConfiguredRole(server);
		}

		internal static void AfterZNetAwake(ZNet __instance)
		{
			Plugin.ObserveNetwork(__instance);
		}

		internal static void AfterNewConnection(ZNet __instance, [HarmonyArgument(0)] ZNetPeer peer)
		{
			Plugin.ObserveConnection(__instance, peer);
		}

		internal static void AfterZNetDestroy(ZNet __instance)
		{
			Plugin.ObserveNetworkDestroyed(__instance);
		}

		[HarmonyPriority(800)]
		internal static bool BeforeUnavailableServerHandshake(ZNet __instance, [HarmonyArgument(0)] ZRpc rpc)
		{
			return Plugin.AllowUnavailableServerHandshake(__instance, rpc);
		}

		[HarmonyPriority(800)]
		internal static void BeforeUnavailableWorldLoad(ZNet __instance)
		{
			Plugin.GuardUnavailableWorldLoad(__instance);
		}
	}
	internal static class SentinelConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> PolicyFile;

		internal static ConfigEntry<string> SignatureFile;

		internal static ConfigEntry<string> PublicKeyFile;

		internal static ConfigEntry<string> TrustedPublicKeySha256;

		internal static ConfigEntry<string> RemoteAdmissionPolicy;

		internal static ConfigEntry<int> IntegrityCheckSeconds;

		internal static ConfigEntry<bool> BackupBeforeTransitions;

		internal static ConfigEntry<int> VeryHighDisconnectCount;

		internal static ConfigEntry<int> HighDisconnectCount;

		internal static ConfigEntry<int> EnforcementWindowSeconds;

		internal static SentinelRemoteAdmissionMode RemoteAdmissionMode
		{
			get
			{
				if (!string.Equals(RemoteAdmissionPolicy?.Value?.Trim(), "Disabled", StringComparison.OrdinalIgnoreCase))
				{
					if (!string.Equals(RemoteAdmissionPolicy?.Value?.Trim(), "Required", StringComparison.OrdinalIgnoreCase))
					{
						return SentinelRemoteAdmissionMode.Optional;
					}
					return SentinelRemoteAdmissionMode.Required;
				}
				return SentinelRemoteAdmissionMode.Disabled;
			}
		}

		internal static event Action Changed;

		internal static void Bind(ConfigFile config)
		{
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Expected O, but got Unknown
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Expected O, but got Unknown
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable bounded Sentinel local-snapshot, RSA policy, and evidence services. Sampled at startup; false registers nothing and starts no worker.");
			PolicyFile = config.Bind<string>("Policy", "ManifestFile", "RunicSentinel.policy", "Canonical RUNIC-SENTINEL/3 policy path, relative to BepInEx/config unless absolute.");
			SignatureFile = config.Bind<string>("Policy", "SignatureFile", "RunicSentinel.policy.sig", "Canonical Base64 detached RSA-3072/SHA-256 PKCS#1 v1.5 signature path.");
			PublicKeyFile = config.Bind<string>("Policy", "PublicKeyFile", "RunicSentinel.policy.pub", "Canonical RUNIC-RSA-PUBLIC/1 verification public key path. The optional F3 workflow keeps its private key in a separate server-only directory.");
			TrustedPublicKeySha256 = config.Bind<string>("Policy", "TrustedPublicKeySha256", string.Empty, "Required lowercase SHA-256 of the exact canonical public-key file. Empty or mismatched pins keep Sentinel monitor-only.");
			RemoteAdmissionPolicy = config.Bind<string>("Remote Admission", "Policy", "Optional", "Sampled at startup; changing it requires a restart. Required withholds native admission and denies a missing, stale, malformed, or signed-policy-incompatible client report after a bounded grace period. Optional records evidence without delaying or disconnecting. Disabled does not register the direct Sentinel admission protocol.");
			IntegrityCheckSeconds = config.Bind<int>("Runtime Integrity", "CheckIntervalSeconds", 15, "Metadata-check loaded plugin DLLs and active signed-passport files at this interval. A detected runtime change denies new strict admissions until restart. Range 5-300 seconds.");
			BackupBeforeTransitions = config.Bind<bool>("Transition Safety", "BackupWorldBeforeProfileChange", true, "Before a server loads an existing world with a different signed policy or plugin snapshot, require a verified Runic Safety backup of the world database and metadata.");
			VeryHighDisconnectCount = config.Bind<int>("Automatic Enforcement", "VeryHighFindingsBeforeDisconnect", 2, new ConfigDescription("Disconnect after this many very-high-confidence violations in the enforcement window.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			HighDisconnectCount = config.Bind<int>("Automatic Enforcement", "HighFindingsBeforeDisconnect", 3, new ConfigDescription("Disconnect after this many high-confidence violations in the enforcement window.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			EnforcementWindowSeconds = config.Bind<int>("Automatic Enforcement", "FindingWindowSeconds", 60, new ConfigDescription("Rolling violation window used by graduated automatic enforcement.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 600), Array.Empty<object>()));
			PolicyFile.SettingChanged += Notify;
			SignatureFile.SettingChanged += Notify;
			PublicKeyFile.SettingChanged += Notify;
			TrustedPublicKeySha256.SettingChanged += Notify;
		}

		private static void Notify(object sender, EventArgs args)
		{
			SentinelConfig.Changed?.Invoke();
		}
	}
}
namespace RunicSentinel.Admission
{
	internal sealed class AdmissionPluginEvidence
	{
		internal string Id { get; }

		internal string Version { get; }

		internal string Sha256 { get; }

		internal AdmissionPluginEvidence(string id, string version, string sha256)
		{
			Id = AdmissionValidation.RequireAtom(id, 1, 128, "id");
			Version = AdmissionValidation.RequireAtom(version, 1, 64, "version");
			Sha256 = AdmissionValidation.RequireLowerHex(sha256, 64, "sha256");
		}
	}
	internal sealed class AdmissionClientProfile
	{
		internal long CapturedUnixSeconds { get; }

		internal string Digest { get; }

		internal IReadOnlyList<AdmissionPluginEvidence> Plugins { get; }

		internal AdmissionClientProfile(long capturedUnixSeconds, string digest, IList<AdmissionPluginEvidence> plugins)
		{
			if (capturedUnixSeconds < 0)
			{
				throw new ArgumentOutOfRangeException("capturedUnixSeconds");
			}
			CapturedUnixSeconds = capturedUnixSeconds;
			Digest = AdmissionValidation.RequireLowerHex(digest, 64, "digest");
			if (plugins == null)
			{
				throw new ArgumentNullException("plugins");
			}
			Plugins = new ReadOnlyCollection<AdmissionPluginEvidence>(new List<AdmissionPluginEvidence>(plugins));
		}
	}
	internal sealed class AdmissionChallenge
	{
		private readonly byte[] _nonce;

		internal string RequestId { get; }

		internal byte[] Nonce => (byte[])_nonce.Clone();

		internal long IssuedUnixSeconds { get; }

		internal long DeadlineUnixSeconds { get; }

		internal AdmissionChallenge(string requestId, byte[] nonce, long issuedUnixSeconds, long deadlineUnixSeconds)
		{
			RequestId = AdmissionValidation.RequireLowerHex(requestId, 32, "requestId");
			if (nonce == null || nonce.Length != 32)
			{
				throw new ArgumentOutOfRangeException("nonce");
			}
			if (issuedUnixSeconds < 0 || deadlineUnixSeconds < issuedUnixSeconds)
			{
				throw new ArgumentOutOfRangeException("issuedUnixSeconds");
			}
			_nonce = (byte[])nonce.Clone();
			IssuedUnixSeconds = issuedUnixSeconds;
			DeadlineUnixSeconds = deadlineUnixSeconds;
		}
	}
	internal sealed class AdmissionReport
	{
		internal string RequestId { get; }

		internal string ClientVersion { get; }

		internal long CapturedUnixSeconds { get; }

		internal long IssuedUnixSeconds { get; }

		internal string ProfileDigest { get; }

		internal string NonceBinding { get; }

		internal IReadOnlyList<AdmissionPluginEvidence> Plugins { get; }

		internal AdmissionReport(string requestId, string clientVersion, long capturedUnixSeconds, long issuedUnixSeconds, string profileDigest, string nonceBinding, IList<AdmissionPluginEvidence> plugins)
		{
			RequestId = AdmissionValidation.RequireLowerHex(requestId, 32, "requestId");
			ClientVersion = AdmissionValidation.RequireAtom(clientVersion, 1, 32, "clientVersion");
			if (capturedUnixSeconds < 0 || issuedUnixSeconds < 0)
			{
				throw new ArgumentOutOfRangeException("capturedUnixSeconds");
			}
			ProfileDigest = AdmissionValidation.RequireLowerHex(profileDigest, 64, "profileDigest");
			NonceBinding = AdmissionValidation.RequireLowerHex(nonceBinding, 64, "nonceBinding");
			if (plugins == null || plugins.Count > 512)
			{
				throw new ArgumentOutOfRangeException("plugins");
			}
			CapturedUnixSeconds = capturedUnixSeconds;
			IssuedUnixSeconds = issuedUnixSeconds;
			Plugins = new ReadOnlyCollection<AdmissionPluginEvidence>(new List<AdmissionPluginEvidence>(plugins));
		}
	}
	internal sealed class AdmissionDecisionMessage
	{
		internal string RequestId { get; }

		internal bool Accepted { get; }

		internal bool ResumeHandshake { get; }

		internal string ReasonCode { get; }

		internal long PolicySequence { get; }

		internal string PolicyProfile { get; }

		internal long IssuedUnixSeconds { get; }

		internal AdmissionDecisionMessage(string requestId, bool accepted, bool resumeHandshake, string reasonCode, long policySequence, string policyProfile, long issuedUnixSeconds)
		{
			RequestId = AdmissionValidation.RequireLowerHex(requestId, 32, "requestId");
			ReasonCode = AdmissionValidation.RequireReason(reasonCode, "reasonCode");
			if (policySequence < 0 || issuedUnixSeconds < 0)
			{
				throw new ArgumentOutOfRangeException("policySequence");
			}
			if (!string.IsNullOrEmpty(policyProfile))
			{
				AdmissionValidation.RequireAtom(policyProfile, 1, 64, "policyProfile");
			}
			if (resumeHandshake && !accepted)
			{
				throw new ArgumentException("Only an accepted decision may resume the native handshake.", "resumeHandshake");
			}
			Accepted = accepted;
			ResumeHandshake = resumeHandshake;
			PolicySequence = policySequence;
			PolicyProfile = policyProfile ?? string.Empty;
			IssuedUnixSeconds = issuedUnixSeconds;
		}
	}
	internal static class AdmissionValidation
	{
		internal static string RequireAtom(string value, int minimum, int maximum, string parameterName)
		{
			if (!IsAtom(value, minimum, maximum))
			{
				throw new ArgumentException("Value is not a canonical ASCII atom.", parameterName);
			}
			return value;
		}

		internal static bool IsAtom(string value, int minimum, int maximum)
		{
			if (value == null || value.Length < minimum || value.Length > maximum)
			{
				return false;
			}
			foreach (char c in value)
			{
				if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '.' && c != '-' && c != '_')
				{
					return false;
				}
			}
			return true;
		}

		internal static string RequireLowerHex(string value, int length, string parameterName)
		{
			if (!IsLowerHex(value, length))
			{
				throw new ArgumentException("Value is not canonical lowercase hexadecimal.", parameterName);
			}
			return value;
		}

		internal static bool IsLowerHex(string value, int length)
		{
			if (value == null || value.Length != length)
			{
				return false;
			}
			foreach (char c in value)
			{
				if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
				{
					return false;
				}
			}
			return true;
		}

		internal static string RequireReason(string value, string parameterName)
		{
			if (!IsReason(value))
			{
				throw new ArgumentException("Reason code is not canonical.", parameterName);
			}
			return value;
		}

		internal static bool IsReason(string value)
		{
			if (value == null || value.Length < 1 || value.Length > 96)
			{
				return false;
			}
			foreach (char c in value)
			{
				if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '-' && c != '.')
				{
					return false;
				}
			}
			return true;
		}
	}
	internal static class AdmissionProfileCanonicalizer
	{
		internal const int MaximumCanonicalBytes = 262144;

		private const string Header = "RUNIC-SENTINEL-CLIENT-PROFILE/1\n";

		internal static bool TryCreate(IEnumerable<AdmissionPluginEvidence> source, long capturedUnixSeconds, out AdmissionClientProfile profile, out string failure)
		{
			profile = null;
			failure = string.Empty;
			if (source == null || capturedUnixSeconds < 0)
			{
				failure = "profile-missing";
				return false;
			}
			List<AdmissionPluginEvidence> list = new List<AdmissionPluginEvidence>();
			foreach (AdmissionPluginEvidence item in source)
			{
				if (item == null)
				{
					failure = "profile-entry-null";
					return false;
				}
				if (list.Count >= 512)
				{
					failure = "profile-plugin-cap";
					return false;
				}
				list.Add(item);
			}
			list.Sort((AdmissionPluginEvidence left, AdmissionPluginEvidence right) => string.CompareOrdinal(left.Id, right.Id));
			string a = null;
			StringBuilder stringBuilder = new StringBuilder("RUNIC-SENTINEL-CLIENT-PROFILE/1\n", Math.Min(32768, 64 + list.Count * 160));
			foreach (AdmissionPluginEvidence item2 in list)
			{
				if (string.Equals(a, item2.Id, StringComparison.Ordinal))
				{
					failure = "profile-plugin-duplicate";
					return false;
				}
				a = item2.Id;
				stringBuilder.Append(item2.Id).Append('|').Append(item2.Version)
					.Append('|')
					.Append(item2.Sha256)
					.Append('\n');
				if (stringBuilder.Length > 262144)
				{
					failure = "profile-size";
					return false;
				}
			}
			byte[] bytes = Encoding.UTF8.GetBytes(stringBuilder.ToString());
			if (bytes.Length > 262144)
			{
				failure = "profile-size";
				return false;
			}
			string digest;
			using (SHA256 sHA = SHA256.Create())
			{
				digest = Hex(sHA.ComputeHash(bytes));
			}
			profile = new AdmissionClientProfile(capturedUnixSeconds, digest, list);
			return true;
		}

		internal static string Hex(byte[] bytes)
		{
			if (bytes == null)
			{
				throw new ArgumentNullException("bytes");
			}
			StringBuilder stringBuilder = new StringBuilder(bytes.Length * 2);
			for (int i = 0; i < bytes.Length; i++)
			{
				stringBuilder.Append(bytes[i].ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		internal static bool FixedTimeEquals(string left, string right)
		{
			if (left == null || right == null)
			{
				return false;
			}
			int num = left.Length ^ right.Length;
			int num2 = Math.Max(left.Length, right.Length);
			for (int i = 0; i < num2; i++)
			{
				char c = ((i < left.Length) ? left[i] : '\0');
				char c2 = ((i < right.Length) ? right[i] : '\0');
				num |= c ^ c2;
			}
			return num == 0;
		}
	}
	internal enum AdmissionMessageKind : byte
	{
		Challenge = 1,
		Report,
		Decision
	}
	internal static class AdmissionProtocolV2
	{
		internal const string DirectRpcName = "chazman.RunicSentinel.Admission.v2";

		internal const int WireSchema = 2;

		internal const int MaximumPlugins = 512;

		internal const int MaximumFrameBytes = 262144;

		internal const int NonceBytes = 32;

		internal const int RequestIdHexLength = 32;

		internal const long MaximumClockSkewSeconds = 60L;

		internal const long MaximumMessageAgeSeconds = 300L;

		internal const long MaximumChallengeLifetimeSeconds = 120L;

		private const int Magic = 843141970;

		private const int Terminal = 843337285;

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		internal static AdmissionChallenge CreateChallenge(long nowUnixSeconds, long lifetimeSeconds)
		{
			if (nowUnixSeconds < 0 || lifetimeSeconds < 1 || lifetimeSeconds > 120 || nowUnixSeconds > long.MaxValue - lifetimeSeconds)
			{
				throw new ArgumentOutOfRangeException("lifetimeSeconds");
			}
			byte[] array = new byte[32];
			byte[] array2 = new byte[16];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
				randomNumberGenerator.GetBytes(array2);
			}
			return new AdmissionChallenge(AdmissionProfileCanonicalizer.Hex(array2), array, nowUnixSeconds, nowUnixSeconds + lifetimeSeconds);
		}

		internal static bool IsChallengeCurrent(AdmissionChallenge challenge, long nowUnixSeconds, out string failure)
		{
			failure = string.Empty;
			if (challenge == null || nowUnixSeconds < 0)
			{
				failure = "challenge-missing";
				return false;
			}
			if (challenge.DeadlineUnixSeconds < challenge.IssuedUnixSeconds || challenge.DeadlineUnixSeconds - challenge.IssuedUnixSeconds > 120)
			{
				failure = "challenge-lifetime";
				return false;
			}
			if (challenge.IssuedUnixSeconds < nowUnixSeconds - 300 || challenge.IssuedUnixSeconds > nowUnixSeconds + 60 || nowUnixSeconds > challenge.DeadlineUnixSeconds)
			{
				failure = "challenge-stale";
				return false;
			}
			return true;
		}

		internal static bool TryValidateReport(AdmissionChallenge challenge, AdmissionReport report, long receivedUnixSeconds, out AdmissionClientProfile profile, out string failure)
		{
			profile = null;
			failure = string.Empty;
			if (!IsChallengeCurrent(challenge, receivedUnixSeconds, out failure))
			{
				return false;
			}
			if (report == null || !AdmissionProfileCanonicalizer.FixedTimeEquals(challenge.RequestId, report.RequestId))
			{
				failure = "report-request-mismatch";
				return false;
			}
			if (report.IssuedUnixSeconds < receivedUnixSeconds - 300 || report.IssuedUnixSeconds > receivedUnixSeconds + 60 || report.CapturedUnixSeconds > receivedUnixSeconds + 60)
			{
				failure = "report-stale";
				return false;
			}
			if (!AdmissionProfileCanonicalizer.TryCreate(report.Plugins, report.CapturedUnixSeconds, out profile, out failure))
			{
				return false;
			}
			if (!AdmissionProfileCanonicalizer.FixedTimeEquals(profile.Digest, report.ProfileDigest))
			{
				profile = null;
				failure = "report-digest-mismatch";
				return false;
			}
			if (!TryComputeNonceBinding(challenge.RequestId, challenge.Nonce, profile.Digest, out var binding) || !AdmissionProfileCanonicalizer.FixedTimeEquals(binding, report.NonceBinding))
			{
				profile = null;
				failure = "report-binding-mismatch";
				return false;
			}
			return true;
		}

		internal static bool TryComputeNonceBinding(string requestId, byte[] nonce, string profileDigest, out string binding)
		{
			binding = string.Empty;
			if (!AdmissionValidation.IsLowerHex(requestId, 32) || nonce == null || nonce.Length != 32 || !AdmissionValidation.IsLowerHex(profileDigest, 64))
			{
				return false;
			}
			string s = "RUNIC-SENTINEL-ADMISSION-BINDING/2\n" + requestId + "\n" + AdmissionProfileCanonicalizer.Hex(nonce) + "\n" + profileDigest + "\n";
			using (SHA256 sHA = SHA256.Create())
			{
				binding = AdmissionProfileCanonicalizer.Hex(sHA.ComputeHash(Encoding.ASCII.GetBytes(s)));
			}
			return true;
		}

		internal static byte[] EncodeChallenge(AdmissionChallenge value)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}
			return Encode(AdmissionMessageKind.Challenge, delegate(BinaryWriter writer)
			{
				WriteString(writer, value.RequestId, 32);
				byte[] nonce = value.Nonce;
				writer.Write(nonce.Length);
				writer.Write(nonce);
				writer.Write(value.IssuedUnixSeconds);
				writer.Write(value.DeadlineUnixSeconds);
			});
		}

		internal static byte[] EncodeDecision(AdmissionDecisionMessage value)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}
			return Encode(AdmissionMessageKind.Decision, delegate(BinaryWriter writer)
			{
				WriteString(writer, value.RequestId, 32);
				writer.Write(value.Accepted ? ((byte)1) : ((byte)0));
				writer.Write(value.ResumeHandshake ? ((byte)1) : ((byte)0));
				WriteString(writer, value.ReasonCode, 96);
				writer.Write(value.PolicySequence);
				WriteString(writer, value.PolicyProfile, 64);
				writer.Write(value.IssuedUnixSeconds);
			});
		}

		internal static bool TryGetKind(byte[] bytes, out AdmissionMessageKind kind, out string failure)
		{
			kind = (AdmissionMessageKind)0;
			failure = string.Empty;
			try
			{
				using MemoryStream input = NewReadStream(bytes);
				using BinaryReader reader = new BinaryReader(input, StrictUtf8);
				if (!ReadHeader(reader, out kind, out failure))
				{
					return false;
				}
				return true;
			}
			catch
			{
				kind = (AdmissionMessageKind)0;
				failure = "frame-malformed";
				return false;
			}
		}

		internal static bool TryDecodeReport(byte[] bytes, out AdmissionReport value, out string failure)
		{
			value = null;
			failure = string.Empty;
			try
			{
				using MemoryStream memoryStream = NewReadStream(bytes);
				using BinaryReader binaryReader = new BinaryReader(memoryStream, StrictUtf8);
				if (!ReadExpectedHeader(binaryReader, AdmissionMessageKind.Report, out failure) || !TryReadString(binaryReader, memoryStream, 32, out var value2) || !TryReadString(binaryReader, memoryStream, 32, out var value3))
				{
					failure = "report-shape";
					return false;
				}
				long num = binaryReader.ReadInt64();
				long num2 = binaryReader.ReadInt64();
				if (!TryReadString(binaryReader, memoryStream, 64, out var value4) || !TryReadString(binaryReader, memoryStream, 64, out var value5))
				{
					failure = "report-shape";
					return false;
				}
				int num3 = binaryReader.ReadInt32();
				if (num3 < 0 || num3 > 512)
				{
					failure = "report-plugin-cap";
					return false;
				}
				List<AdmissionPluginEvidence> list = new List<AdmissionPluginEvidence>(num3);
				string text = null;
				for (int i = 0; i < num3; i++)
				{
					if (!TryReadString(binaryReader, memoryStream, 128, out var value6) || !TryReadString(binaryReader, memoryStream, 64, out var value7) || !TryReadString(binaryReader, memoryStream, 64, out var value8) || !AdmissionValidation.IsAtom(value6, 1, 128) || !AdmissionValidation.IsAtom(value7, 1, 64) || !AdmissionValidation.IsLowerHex(value8, 64) || (text != null && string.CompareOrdinal(text, value6) >= 0))
					{
						failure = "report-plugin-shape";
						return false;
					}
					text = value6;
					list.Add(new AdmissionPluginEvidence(value6, value7, value8));
				}
				if (!AdmissionValidation.IsLowerHex(value2, 32) || !AdmissionValidation.IsAtom(value3, 1, 32) || num < 0 || num2 < 0 || !AdmissionValidation.IsLowerHex(value4, 64) || !AdmissionValidation.IsLowerHex(value5, 64) || !ReadTerminal(binaryReader, memoryStream))
				{
					failure = "report-shape";
					return false;
				}
				value = new AdmissionReport(value2, value3, num, num2, value4, value5, list);
				return true;
			}
			catch
			{
				value = null;
				failure = "report-malformed";
				return false;
			}
		}

		private static byte[] Encode(AdmissionMessageKind kind, Action<BinaryWriter> body)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, StrictUtf8);
			binaryWriter.Write(843141970);
			binaryWriter.Write(2);
			binaryWriter.Write((byte)kind);
			body(binaryWriter);
			binaryWriter.Write(843337285);
			binaryWriter.Flush();
			if (memoryStream.Length <= 0 || memoryStream.Length > 262144)
			{
				throw new InvalidDataException("Admission frame exceeds its wire bound.");
			}
			return memoryStream.ToArray();
		}

		private static MemoryStream NewReadStream(byte[] bytes)
		{
			if (bytes == null || bytes.Length == 0 || bytes.Length > 262144)
			{
				throw new InvalidDataException("Admission frame size is invalid.");
			}
			return new MemoryStream(bytes, writable: false);
		}

		private static bool ReadHeader(BinaryReader reader, out AdmissionMessageKind kind, out string failure)
		{
			kind = (AdmissionMessageKind)0;
			failure = string.Empty;
			if (reader.ReadInt32() != 843141970 || reader.ReadInt32() != 2)
			{
				failure = "frame-header";
				return false;
			}
			kind = (AdmissionMessageKind)reader.ReadByte();
			if ((int)kind < 1 || (int)kind > 3)
			{
				kind = (AdmissionMessageKind)0;
				failure = "frame-kind";
				return false;
			}
			return true;
		}

		private static bool ReadExpectedHeader(BinaryReader reader, AdmissionMessageKind expected, out string failure)
		{
			if (!ReadHeader(reader, out var kind, out failure))
			{
				return false;
			}
			if (kind == expected)
			{
				return true;
			}
			failure = "frame-kind";
			return false;
		}

		private static void WriteString(BinaryWriter writer, string value, int maximumCharacters)
		{
			if (value == null || value.Length > maximumCharacters)
			{
				throw new InvalidDataException("Admission string exceeds its bound.");
			}
			byte[] bytes = StrictUtf8.GetBytes(value);
			if (bytes.Length > maximumCharacters)
			{
				throw new InvalidDataException("Admission string exceeds its byte bound.");
			}
			writer.Write(bytes.Length);
			writer.Write(bytes);
		}

		private static bool TryReadString(BinaryReader reader, MemoryStream stream, int maximumBytes, out string value)
		{
			value = string.Empty;
			int num = reader.ReadInt32();
			if (num < 0 || num > maximumBytes || Remaining(stream) < num)
			{
				return false;
			}
			byte[] array = reader.ReadBytes(num);
			if (array.Length != num)
			{
				return false;
			}
			value = StrictUtf8.GetString(array);
			return value.Length <= maximumBytes;
		}

		private static bool ReadTerminal(BinaryReader reader, MemoryStream stream)
		{
			if (Remaining(stream) == 4 && reader.ReadInt32() == 843337285)
			{
				return Remaining(stream) == 0;
			}
			return false;
		}

		private static long Remaining(MemoryStream stream)
		{
			return stream.Length - stream.Position;
		}
	}
}
namespace RunicSentinel.Runtime
{
	internal sealed class SentinelAdminControl : IDisposable
	{
		private sealed class ServerConnection
		{
			internal ZNetPeer Peer { get; }

			internal ZRpc Rpc { get; }

			internal long Ordinal { get; }

			internal ServerConnection(ZNetPeer peer, ZRpc rpc, long ordinal)
			{
				Peer = peer;
				Rpc = rpc;
				Ordinal = ordinal;
			}
		}

		private sealed class CachedResponse
		{
			internal byte[] RequestDigest;

			internal bool Accepted;

			internal string Reason;

			internal byte[] Payload;

			internal long Expires;
		}

		private const string RequestRpc = "runic.sentinel.admin.request.v1";

		private const string ResponseRpc = "runic.sentinel.admin.response.v1";

		private const int WireSchema = 1;

		private const int TerminalMarker = 1369914905;

		private const int MaximumEnvelopeBytes = 184320;

		private const int MaximumReplayEntries = 256;

		private const int MaximumTrackedPeers = 64;

		private static readonly long ReplayLifetimeTicks = TimeSpan.FromMinutes(1.0).Ticks;

		private readonly SentinelRuntime _runtime;

		private readonly SentinelManagedPolicyService _managed;

		private readonly SentinelOperatorCommands _commands;

		private readonly Dictionary<string, CachedResponse> _cache = new Dictionary<string, CachedResponse>(StringComparer.Ordinal);

		private readonly Queue<string> _cacheOrder = new Queue<string>();

		private readonly Dictionary<ZRpc, ServerConnection> _serverConnections = new Dictionary<ZRpc, ServerConnection>();

		private ZNet _network;

		private long _nextConnectionOrdinal;

		private bool _disposed;

		internal SentinelAdminControl(SentinelRuntime runtime, SentinelManagedPolicyService managed, SentinelOperatorCommands commands)
		{
			_runtime = runtime ?? throw new ArgumentNullException("runtime");
			_managed = managed ?? throw new ArgumentNullException("managed");
			_commands = commands ?? throw new ArgumentNullException("commands");
		}

		internal void Tick()
		{
			if (_disposed)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				if ((Object)(object)_network != (Object)null)
				{
					ClearConnections("The authoritative server administrator channel disconnected.");
					_network = null;
				}
			}
			else
			{
				EnsureNetwork(instance);
				if (instance.IsServer())
				{
					TickServer(instance);
				}
				else
				{
					ClearConnections("Runic Sentinel Server is inert on a non-authoritative client.");
					_network = null;
				}
			}
			long ticks = DateTime.UtcNow.Ticks;
			ExpireCache(ticks);
		}

		private void EnsureNetwork(ZNet network)
		{
			if (network != _network)
			{
				ClearConnections("The authoritative server administrator channel changed.");
				_network = network;
			}
		}

		private void TickServer(ZNet network)
		{
			List<ZNetPeer> list;
			try
			{
				list = network.GetPeers();
			}
			catch
			{
				list = null;
			}
			Dictionary<ZRpc, ZNetPeer> dictionary = new Dictionary<ZRpc, ZNetPeer>();
			int num = 0;
			if (list != null)
			{
				foreach (ZNetPeer item in list)
				{
					if (num++ >= 64)
					{
						break;
					}
					ZRpc val = item?.m_rpc;
					if (val != null && IsReady(item) && !dictionary.ContainsKey(val))
					{
						dictionary.Add(val, item);
					}
				}
			}
			foreach (ZRpc item2 in new List<ZRpc>(_serverConnections.Keys))
			{
				if (!dictionary.TryGetValue(item2, out var value) || !_serverConnections.TryGetValue(item2, out var value2) || value2.Peer != value || value.m_rpc != item2)
				{
					RemoveServerConnection(item2);
				}
			}
			foreach (KeyValuePair<ZRpc, ZNetPeer> item3 in dictionary)
			{
				if (!_serverConnections.ContainsKey(item3.Key) && _serverConnections.Count < 64)
				{
					try
					{
						item3.Key.Register<ZPackage>("runic.sentinel.admin.request.v1", (Action<ZRpc, ZPackage>)ReceiveRequest);
					}
					catch
					{
						continue;
					}
					long ordinal = ((_nextConnectionOrdinal == long.MaxValue) ? long.MaxValue : (++_nextConnectionOrdinal));
					_serverConnections.Add(item3.Key, new ServerConnection(item3.Value, item3.Key, ordinal));
				}
			}
		}

		private void ReceiveRequest(ZRpc rpc, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			if (_disposed || (Object)(object)instance == (Object)null || instance != _network || !instance.IsServer() || rpc == null || !_serverConnections.TryGetValue(rpc, out var value))
			{
				return;
			}
			ZNetPeer val = FindExactReadyPeer(instance, rpc);
			if (val == null || val != value.Peer || val.m_rpc != value.Rpc)
			{
				return;
			}
			if (!TryReadRequest(package, out var id, out var action, out var payload, out var issued))
			{
				SendResponse(value, string.Empty, accepted: false, "Malformed administrator request.", Array.Empty<byte>());
				return;
			}
			long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			if (issued < num - 30 || issued > num + 30)
			{
				SendResponse(value, id, accepted: false, "Administrator request expired.", Array.Empty<byte>());
				return;
			}
			string key = value.Ordinal.ToString(CultureInfo.InvariantCulture) + ":" + id;
			byte[] array = Digest(action, payload, issued);
			ExpireCache(DateTime.UtcNow.Ticks);
			string authority;
			string subject;
			if (_cache.TryGetValue(key, out var value2))
			{
				if (!Fixed(value2.RequestDigest, array))
				{
					SendResponse(value, id, accepted: false, "Administrator request identity was reused.", Array.Empty<byte>());
				}
				else
				{
					SendResponse(value, id, value2.Accepted, value2.Reason, value2.Payload);
				}
			}
			else if (!SentinelTransportIdentity.TryResolvePeer(val, out authority, out subject))
			{
				SendResponse(value, id, accepted: false, "Authenticated backend identity is unavailable.", Array.Empty<byte>());
			}
			else
			{
				Execute(authority, subject, action, payload, out var accepted, out var response, out var reason);
				Cache(key, array, accepted, reason, response);
				SendResponse(value, id, accepted, reason, response);
			}
		}

		private void Execute(string authority, string subject, string action, byte[] payload, out bool accepted, out byte[] response, out string reason)
		{
			accepted = false;
			response = Array.Empty<byte>();
			reason = "Administrator access denied.";
			if (_runtime.IsBanned(authority, subject))
			{
				reason = "This account is banned.";
			}
			else
			{
				if (!_runtime.IsAdministrator(authority, subject))
				{
					return;
				}
				try
				{
					SentinelAdminDocument value;
					if (action == "status" && payload.Length == 0)
					{
						response = SentinelAdminProtocol.Encode(_managed.CreateDocument("Authenticated by " + authority + " backend identity."));
					}
					else if (action == "apply" && SentinelAdminProtocol.TryDecode(payload, out value))
					{
						response = SentinelAdminProtocol.EncodeMessage(_managed.Apply(value, authority, subject));
					}
					else
					{
						if (!(action == "tool") || !SentinelAdminProtocol.TryDecodeTool(payload, out var tool))
						{
							reason = "Administrator operation is invalid.";
							return;
						}
						response = SentinelAdminProtocol.EncodeMessage(RunToolCore(tool));
					}
					accepted = true;
					reason = "ok";
				}
				catch (Exception ex)
				{
					reason = Bounded(ex.Message);
				}
			}
		}

		private string RunToolCore(string tool)
		{
			return tool switch
			{
				"report" => "Support report created: " + _commands.WriteReport(), 
				"networks" => "Network map created: " + _commands.WriteReport(includeNetworks: true), 
				"backup" => "Verified backup created: " + SentinelTransitionBackup.CreateVerifiedBackupNow("runic-sentinel-admin-tool"), 
				_ => throw new InvalidOperationException("Unknown administrator tool."), 
			};
		}

		private static bool TryReadRequest(ZPackage package, out string id, out string action, out byte[] payload, out long issued)
		{
			id = (action = string.Empty);
			payload = null;
			issued = 0L;
			try
			{
				if (package == null || package.Size() < 1 || package.Size() > 184320 || package.ReadInt() != 1)
				{
					return false;
				}
				id = package.ReadString();
				action = package.ReadString();
				issued = package.ReadLong();
				payload = package.ReadByteArray();
				return CanonicalId(id) && (action == "status" || action == "apply" || action == "tool") && payload != null && payload.Length <= 122880 && package.ReadInt() == 1369914905 && package.GetPos() == package.Size();
			}
			catch
			{
				return false;
			}
		}

		private void SendResponse(ServerConnection connection, string id, bool accepted, string reason, byte[] payload)
		{
			ZNet network = _network;
			if (_disposed || (Object)(object)network == (Object)null || !network.IsServer() || connection == null || FindExactReadyPeer(network, connection.Rpc) != connection.Peer)
			{
				return;
			}
			ZPackage val = WriteResponse(id, accepted, reason, payload);
			if (val.Size() > 184320)
			{
				return;
			}
			try
			{
				connection.Rpc.Invoke("runic.sentinel.admin.response.v1", new object[1] { val });
			}
			catch
			{
			}
		}

		private static ZPackage WriteResponse(string id, bool accepted, string reason, byte[] payload)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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)
			//IL_0023: 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_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(id ?? string.Empty);
			val.Write(accepted);
			val.Write(Bounded(reason));
			val.Write(payload ?? Array.Empty<byte>());
			val.Write(1369914905);
			return val;
		}

		private static ZNetPeer FindExactReadyPeer(ZNet network, ZRpc rpc)
		{
			if ((Object)(object)network == (Object)null || rpc == null || !network.IsServer())
			{
				return null;
			}
			List<ZNetPeer> peers;
			try
			{
				peers = network.GetPeers();
			}
			catch
			{
				return null;
			}
			if (peers == null)
			{
				return null;
			}
			int num = 0;
			foreach (ZNetPeer item in peers)
			{
				if (num++ >= 64)
				{
					break;
				}
				if (item != null && item.m_rpc == rpc && IsReady(item))
				{
					return item;
				}
			}
			return null;
		}

		private static bool IsReady(ZNetPeer peer)
		{
			try
			{
				return peer != null && peer.IsReady();
			}
			catch
			{
				return false;
			}
		}

		private void RemoveServerConnection(ZRpc rpc)
		{
			if (rpc == null || !_serverConnections.Remove(rpc))
			{
				return;
			}
			try
			{
				rpc.Unregister("runic.sentinel.admin.request.v1");
			}
			catch
			{
			}
		}

		private void ClearServerConnections()
		{
			foreach (ZRpc item in new List<ZRpc>(_serverConnections.Keys))
			{
				try
				{
					if (item != null)
					{
						item.Unregister("runic.sentinel.admin.request.v1");
					}
				}
				catch
				{
				}
			}
			_serverConnections.Clear();
		}

		private void ClearConnections(string reason)
		{
			ClearServerConnections();
		}

		private void Cache(string key, byte[] digest, bool accepted, string reason, byte[] payload)
		{
			while (_cache.Count >= 256 && _cacheOrder.Count != 0)
			{
				_cache.Remove(_cacheOrder.Dequeue());
			}
			_cache[key] = new CachedResponse
			{
				RequestDigest = digest,
				Accepted = accepted,
				Reason = Bounded(reason),
				Payload = (byte[])(payload ?? Array.Empty<byte>()).Clone(),
				Expires = DateTime.UtcNow.Ticks + ReplayLifetimeTicks
			};
			_cacheOrder.Enqueue(key);
		}

		private void ExpireCache(long now)
		{
			while (_cacheOrder.Count != 0)
			{
				string key = _cacheOrder.Peek();
				if (!_cache.TryGetValue(key, out var value) || value.Expires <= now)
				{
					_cacheOrder.Dequeue();
					_cache.Remove(key);
					continue;
				}
				break;
			}
		}

		private static byte[] Digest(string action, byte[] payload, long issued)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(action + "\n" + issued.ToString(CultureInfo.InvariantCulture) + "\n");
			byte[] array = new byte[bytes.Length + payload.Length];
			Buffer.BlockCopy(bytes, 0, array, 0, bytes.Length);
			Buffer.BlockCopy(payload, 0, array, bytes.Length, payload.Length);
			using SHA256 sHA = SHA256.Create();
			return sHA.ComputeHash(array);
		}

		private static bool Fixed(byte[] left, byte[] right)
		{
			if (left != null && right != null && left.Length == right.Length)
			{
				return CryptographicOperations.FixedTimeEquals(left, right);
			}
			return false;
		}

		private static bool CanonicalId(string id)
		{
			Guid result;
			if (id != null && id.Length == 32)
			{
				return Guid.TryParseExact(id, "N", out result);
			}
			return false;
		}

		private static string Bounded(string reason)
		{
			if (!string.IsNullOrWhiteSpace(reason))
			{
				if (reason.Length > 512)
				{
					return reason.Substring(0, 512);
				}
				return reason;
			}
			return "Administrator operation failed.";
		}

		public void Dispose()
		{
			_disposed = true;
			ClearConnections("Administrator control stopped.");
			_cache.Clear();
			_cacheOrder.Clear();
			_network = null;
		}
	}
	internal sealed class SentinelAdminDocument
	{
		internal long Sequence;

		internal string Profile = "runic-suite";

		internal string ExpiresUnixSeconds = "0";

		internal string UnknownMods = "Forbidden";

		internal string RequiredMods = string.Empty;

		internal string OptionalMods = string.Empty;

		internal string GrayListMods = string.Empty;

		internal string ForbiddenMods = string.Empty;

		internal string Administrators = string.Empty;

		internal string BannedUsers = string.Empty;

		internal string Modules = string.Empty;

		internal string DetectedProfile = string.Empty;

		internal string Integrity = string.Empty;

		internal string LastDenial = string.Empty;

		internal string AdmissionMode = "Optional";

		internal string IntegritySeconds = "15";

		internal string VeryHighThreshold = "2";

		internal string HighThreshold = "3";

		internal string EnforcementWindowSeconds = "60";

		internal bool BackupTransitions = true;

		internal bool ManagedSigningKey;

		internal string SigningKeyPin = string.Empty;

		internal string Status = string.Empty;
	}
	internal static class SentinelAdminProtocol
	{
		internal const int MaximumWireBytes = 122880;

		private const string Header = "RUNIC-SENTINEL-ADMIN/1\n";

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		internal static byte[] Encode(SentinelAdminDocument value)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}
			Dictionary<string, string> obj = new Dictionary<string, string>(StringComparer.Ordinal)
			{
				["sequence"] = value.Sequence.ToString(CultureInfo.InvariantCulture),
				["profile"] = value.Profile,
				["expires"] = value.ExpiresUnixSeconds,
				["unknown"] = value.UnknownMods,
				["required"] = value.RequiredMods,
				["optional"] = value.OptionalMods,
				["gray"] = value.GrayListMods,
				["forbidden"] = value.ForbiddenMods,
				["admins"] = value.Administrators,
				["bans"] = value.BannedUsers,
				["modules"] = value.Modules,
				["detected"] = value.DetectedProfile,
				["integrity"] = value.Integrity,
				["last-denial"] = value.LastDenial,
				["admission"] = value.AdmissionMode,
				["integrity-seconds"] = value.IntegritySeconds,
				["very-high"] = value.VeryHighThreshold,
				["high"] = value.HighThreshold,
				["window"] = value.EnforcementWindowSeconds,
				["backup"] = (value.BackupTransitions ? "1" : "0"),
				["managed-key"] = (value.ManagedSigningKey ? "1" : "0"),
				["key-pin"] = value.SigningKeyPin,
				["status"] = value.Status
			};
			StringBuilder stringBuilder = new StringBuilder("RUNIC-SENTINEL-ADMIN/1\n");
			foreach (KeyValuePair<string, string> item in obj)
			{
				stringBuilder.Append(item.Key).Append('=').Append(Convert.ToBase64String(StrictUtf8.GetBytes(item.Value ?? string.Empty)))
					.Append('\n');
			}
			byte[] bytes = StrictUtf8.GetBytes(stringBuilder.ToString());
			if (bytes.Length > 122880)
			{
				throw new InvalidDataException("admin-document-too-large");
			}
			return bytes;
		}

		internal static bool TryDecode(byte[] bytes, out SentinelAdminDocument value)
		{
			value = null;
			if (bytes == null || bytes.Length == 0 || bytes.Length > 122880)
			{
				return false;
			}
			string text;
			try
			{
				text = StrictUtf8.GetString(bytes);
			}
			catch
			{
				return false;
			}
			if (!text.StartsWith("RUNIC-SENTINEL-ADMIN/1\n", StringComparison.Ordinal) || text.IndexOf('\r') >= 0 || !text.EndsWith("\n", StringComparison.Ordinal))
			{
				return false;
			}
			string[] array = text.Split('\n');
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			for (int i = 1; i < array.Length - 1; i++)
			{
				int num = array[i].IndexOf('=');
				if (num <= 0 || !dictionary.TryAdd(array[i].Substring(0, num), Decode(array[i].Substring(num + 1))))
				{
					return false;
				}
			}
			if (!TryLong(dictionary, "sequence", out var result))
			{
				return false;
			}
			value = new SentinelAdminDocument
			{
				Sequence = result,
				Profile = Get(dictionary, "profile"),
				ExpiresUnixSeconds = Get(dictionary, "expires"),
				UnknownMods = Get(dictionary, "unknown"),
				RequiredMods = Get(dictionary, "required"),
				OptionalMods = Get(dictionary, "optional"),
				GrayListMods = Get(dictionary, "gray"),
				ForbiddenMods = Get(dictionary, "forbidden"),
				Administrators = Get(dictionary, "admins"),
				BannedUsers = Get(dictionary, "bans"),
				Modules = Get(dictionary, "modules"),
				DetectedProfile = Get(dictionary, "detected"),
				Integrity = Get(dictionary, "integrity"),
				LastDenial = Get(dictionary, "last-denial"),
				AdmissionMode = Get(dictionary, "admission"),
				IntegritySeconds = Get(dictionary, "integrity-seconds"),
				VeryHighThreshold = Get(dictionary, "very-high"),
				HighThreshold = Get(dictionary, "high"),
				EnforcementWindowSeconds = Get(dictionary, "window"),
				BackupTransitions = (Get(dictionary, "backup") == "1"),
				ManagedSigningKey = (Get(dictionary, "managed-key") == "1"),
				SigningKeyPin = Get(dictionary, "key-pin"),
				Status = Get(dictionary, "status")
			};
			return true;
		}

		internal static byte[] EncodeTool(string tool)
		{
			string text = tool ?? string.Empty;
			if (text != "report" && text != "networks" && text != "backup")
			{
				throw new ArgumentException("Unknown admin tool.", "tool");
			}
			return StrictUtf8.GetBytes("RUNIC-SENTINEL-ADMIN-TOOL/1\n" + text + "\n");
		}

		internal static bool TryDecodeTool(byte[] bytes, out string tool)
		{
			tool = string.Empty;
			if (bytes == null || bytes.Length > 128)
			{
				return false;
			}
			string text;
			try
			{
				text = StrictUtf8.GetString(bytes);
			}
			catch
			{
				return false;
			}
			if (!text.StartsWith("RUNIC-SENTINEL-ADMIN-TOOL/1\n", StringComparison.Ordinal) || !text.EndsWith("\n", StringComparison.Ordinal))
			{
				return false;
			}
			tool = text.Substring("RUNIC-SENTINEL-ADMIN-TOOL/1\n".Length, text.Length - "RUNIC-SENTINEL-ADMIN-TOOL/1\n".Length - 1);
			if (!(tool == "report") && !(tool == "networks"))
			{
				return tool == "backup";
			}
			return true;
		}

		internal static byte[] EncodeMessage(string value)
		{
			byte[] bytes = StrictUtf8.GetBytes(value ?? string.Empty);
			if (bytes.Length > 4096)
			{
				throw new InvalidDataException("admin-message-too-large");
			}
			return bytes;
		}

		internal static string DecodeMessage(byte[] bytes)
		{
			if (bytes == null || bytes.Length > 4096)
			{
				return "invalid-response";
			}
			try
			{
				return StrictUtf8.GetString(bytes);
			}
			catch
			{
				return "invalid-response";
			}
		}

		private static string Decode(string value)
		{
			try
			{
				return StrictUtf8.GetString(Convert.FromBase64String(value));
			}
			catch
			{
				return null;
			}
		}

		private static string Get(IDictionary<string, string> values, string key)
		{
			if (!values.TryGetValue(key, out var value) || value == null)
			{
				return string.Empty;
			}
			return value;
		}

		private static bool TryLong(IDictionary<string, string> values, string key, out long result)
		{
			if (long.TryParse(Get(values, key), NumberStyles.None, CultureInfo.InvariantCulture, out result))
			{
				return result >= 0;
			}
			return false;
		}
	}
	internal static class SentinelDraftExporter
	{
		internal const string FileName = "RunicSentinel.current-profile.json";

		internal static void TryWrite(string configRoot, AttestationSnapshot snapshot)
		{
			try
			{
				if (snapshot != null && !string.IsNullOrEmpty(configRoot))
				{
					string text = Path.Combine(Path.GetFullPath(configRoot), "RunicSentinel.current-profile.json");
					string text2 = text + ".tmp";
					byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(Build(snapshot));
					using (FileStream fileStream = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.None, 65536, FileOptions.WriteThrough))
					{
						fileStream.Write(bytes, 0, bytes.Length);
						fileStream.Flush(flushToDisk: true);
					}
					if (File.Exists(text))
					{
						File.Replace(text2, text, null);
					}
					else
					{
						File.Move(text2, text);
					}
				}
			}
			catch
			{
			}
		}

		private static string Build(AttestationSnapshot snapshot)
		{
			StringBuilder stringBuilder = new StringBuilder(4096 + snapshot.Plugins.Count * 160);
			stringBuilder.Append("{\n  \"profile\": \"runic-suite\",\n  \"sequence\": 1,\n  \"issued\": ").Append(DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture)).Append(",\n  \"expires\": 0,\n  \"unknownMods\": \"Forbidden\",\n")
				.Append("  \"requiredMods\": [\n");
			for (int i = 0; i < snapshot.Plugins.Count; i++)
			{
				AttestedPlugin attestedPlugin = snapshot.Plugins[i];
				stringBuilder.Append("    { \"id\": \"").Append(Json(attestedPlugin.Id)).Append("\", \"version\": \"")
					.Append(Json(attestedPlugin.Version))
					.Append("\", \"sha256\": \"")
					.Append(attestedPlugin.Sha256)
					.Append("\" }")
					.Append((i + 1 == snapshot.Plugins.Count) ? "\n" : ",\n");
			}
			stringBuilder.Append("  ],\n  \"optionalMods\": [],\n  \"grayListMods\": [],\n").Append("  \"forbiddenMods\": [],\n  \"modules\": [],\n").Append("  \"administrators\": [],\n  \"bannedUsers\": []\n}\n");
			return stringBuilder.ToString();
		}

		private static string Json(string value)
		{
			StringBuilder stringBuilder = new StringBuilder(value?.Length ?? 0);
			string text = value ?? string.Empty;
			foreach (char c in text)
			{
				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"));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class SentinelEnforcementRuntime : IDisposable
	{
		private sealed class EscalationState
		{
			internal long Started { get; }

			internal int High { get; set; }

			internal int VeryHigh { get; set; }

			internal EscalationState(long started)
			{
				Started = started;
			}
		}

		private const int MaximumTrackedPeers = 256;

		private readonly object _gate = new object();

		private readonly SentinelRuntime _runtime;

		private readonly ISentinelEvidenceProviderLease _evidence;

		private readonly Dictionary<long, EscalationState> _states = new Dictionary<long, EscalationState>();

		private bool _disposed;

		internal SentinelEnforcementRuntime(SentinelRuntime runtime)
		{
			_runtime = runtime ?? throw new ArgumentNullException("runtime");
			_evidence = runtime.Evidence.RegisterProvider("runic.sentinel.enforcement");
		}

		internal bool ReportRejectedServerRequest(string sourceModuleId, long peerId, string actor, string rule, string correlationId, FindingConfidence confidence, string detail)
		{
			if (sourceModuleId != "runic.portals" || peerId == 0L || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return false;
			}
			bool flag = false;
			long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			lock (_gate)
			{
				if (_disposed)
				{
					return false;
				}
				Prune(num);
				long num2 = Math.Max(10, Math.Min(600, SentinelConfig.EnforcementWindowSeconds?.Value ?? 60));
				int num3 = Math.Max(1, Math.Min(10, SentinelConfig.VeryHighDisconnectCount?.Value ?? 2));
				int num4 = Math.Max(1, Math.Min(20, SentinelConfig.HighDisconnectCount?.Value ?? 3));
				if (!_states.TryGetValue(peerId, out var value) || num - value.Started > num2)
				{
					value = new EscalationState(num);
				}
				if (confidence >= FindingConfidence.High)
				{
					value.High++;
				}
				if (confidence >= FindingConfidence.VeryHigh)
				{
					value.VeryHigh++;
				}
				flag = confidence == FindingConfidence.Conclusive || value.VeryHigh >= num3 || value.High >= num4;
				_states[peerId] = value;
				_evidence.Sink.TryAppend(Safe(actor, "peer:" + peerId), Safe(rule, "security-violation"), Safe(correlationId, Guid.NewGuid().ToString("N")), confidence, flag ? EnforcementAction.Disconnect : EnforcementAction.Cancel, Safe(detail, "request-denied"), out var _);
			}
			if (!flag)
			{
				return true;
			}
			try
			{
				ZNetPeer peer = ZNet.instance.GetPeer(peerId);
				if (peer != null && peer.m_uid == peerId && peer.IsReady())
				{
					ZNet.instance.Disconnect(peer);
				}
			}
			catch
			{
			}
			return true;
		}

		private void Prune(long now)
		{
			long num = Math.Max(10, Math.Min(600, SentinelConfig.EnforcementWindowSeconds?.Value ?? 60));
			List<long> list = new List<long>();
			foreach (KeyValuePair<long, EscalationState> state in _states)
			{
				if (now - state.Value.Started > num)
				{
					list.Add(state.Key);
				}
			}
			foreach (long item in list)
			{
				_states.Remove(item);
			}
			if (_states.Count < 256)
			{
				return;
			}
			long key = 0L;
			long num2 = long.MaxValue;
			foreach (KeyValuePair<long, EscalationState> state2 in _states)
			{
				if (state2.Value.Started < num2)
				{
					num2 = state2.Value.Started;
					key = state2.Key;
				}
			}
			_states.Remove(key);
		}

		private static string Safe(string value, string fallback)
		{
			string text = (string.IsNullOrEmpty(value) ? fallback : value);
			if (text.Length > 128)
			{
				text = text.Substring(0, 128);
			}
			return text;
		}

		public void Dispose()
		{
			lock (_gate)
			{
				if (_disposed)
				{
					return;
				}
				_disposed = true;
				_states.Clear();
			}
			try
			{
				_evidence.Dispose();
			}
			catch
			{
			}
		}
	}
	internal sealed class SentinelFlightRecorder : IDisposable
	{
		internal const long MaximumFileBytes = 524288L;

		private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

		private static readonly byte[] Header = Utf8.GetBytes("RUNIC-SENTINEL-FLIGHT/1\n");

		private readonly object _gate = new object();

		private readonly EvidenceLedger _ledger;

		private readonly ManualLogSource _log;

		private readonly string _activePath;

		private readonly string _previousPath;

		private bool _disposed;

		private bool _faultLogged;

		internal string ActivePath => _activePath;

		internal SentinelFlightRecorder(EvidenceLedger ledger, ManualLogSource log, string configRoot)
		{
			_ledger = ledger ?? throw new ArgumentNullException("ledger");
			_log = log;
			string path = Path.Combine(Path.GetFullPath(configRoot), "RunicSentinel", "flight-recorder");
			_activePath = Path.Combine(path, "security-current.log");
			_previousPath = Path.Combine(path, "security-previous.log");
			_ledger.Accepted += OnAccepted;
		}

		private void OnAccepted(SecurityEvidence evidence)
		{
			if (evidence == null)
			{
				return;
			}
			byte[] bytes = Utf8.GetBytes(Encode(evidence));
			lock (_gate)
			{
				if (_disposed)
				{
					return;
				}
				try
				{
					Directory.CreateDirectory(Path.GetDirectoryName(_activePath));
					long num = (File.Exists(_activePath) ? new FileInfo(_activePath).Length : 0);
					long num2 = (long)bytes.Length + (long)((num == 0L) ? Header.Length : 0);
					if (num + num2 > 524288)
					{
						if (File.Exists(_previousPath))
						{
							File.Delete(_previousPath);
						}
						if (File.Exists(_activePath))
						{
							File.Move(_activePath, _previousPath);
						}
						num = 0L;
					}
					using FileStream fileStream = new FileStream(_activePath, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.WriteThrough);
					if (num == 0L)
					{
						fileStream.Write(Header, 0, Header.Length);
					}
					fileStream.Write(bytes, 0, bytes.Length);
					fileStream.Flush(flushToDisk: true);
				}
				catch (Exception ex)
				{
					if (!_faultLogged)
					{
						_faultLogged = true;
						ManualLogSource log = _log;
						if (log != null)
						{
							log.LogWarning((object)("Sentinel flight-recorder write failed; enforcement remains active: " + ex.GetType().Name + "."));
						}
					}
				}
			}
		}

		private static string Encode(SecurityEvidence value)
		{
			return value.Sequence.ToString(CultureInfo.InvariantCulture) + "|" + value.UnixSeconds.ToString(CultureInfo.InvariantCulture) + "|" + Base64(value.ProviderModuleId) + "|" + Base64(value.Actor) + "|" + Base64(value.Rule) + "|" + Base64(value.CorrelationId) + "|" + ((int)value.Confidence).ToString(CultureInfo.InvariantCulture) + "|" + ((int)value.RequestedAction).ToString(CultureInfo.InvariantCulture) + "|" + ((int)value.EffectiveAction).ToString(CultureInfo.InvariantCulture) + "|" + value.PolicySequence.ToString(CultureInfo.InvariantCulture) + "|" + Base64(value.Detail) + "\n";
		}

		private static string Base64(string value)
		{
			return Convert.ToBase64String(Utf8.GetBytes(value ?? string.Empty));
		}

		public void Dispose()
		{
			lock (_gate)
			{
				if (_disposed)
				{
					return;
				}
				_disposed = true;
			}
			_ledger.Accepted -= OnAccepted;
		}
	}
	internal enum SentinelIntegrityState
	{
		Unavailable,
		MonitorOnly,
		Ready,
		Compromised
	}
	internal sealed class SentinelIntegritySnapshot
	{
		internal SentinelIntegrityState State { get; }

		internal long CheckedUnixSeconds { get; }

		internal string ReasonCode { get; }

		internal string PolicyDigest { get; }

		internal SentinelIntegritySnapshot(SentinelIntegrityState state, long checkedUnixSeconds, string reasonCode, string policyDigest)
		{
			if (!Enum.IsDefined(typeof(SentinelIntegrityState), state) || checkedUnixSeconds < 0)
			{
				throw new ArgumentOutOfRangeException("state");
			}
			State = state;
			CheckedUnixSeconds = checkedUnixSeconds;
			ReasonCode = (string.IsNullOrEmpty(reasonCode) ? "unavailable" : reasonCode);
			PolicyDigest = policyDigest ?? string.Empty;
		}
	}
	internal sealed class SentinelManagedPolicyService
	{
		private sealed class Rule
		{
			internal string Classification { get; }

			internal string Id { get; }

			internal string Version { get; }

			internal string Hash { get; }

			internal Rule(string classification, string id, string version, string hash)
			{
				Classification = classification;
				Id = id;
				Version = version;
				Hash = hash;
			}
		}

		private const string PrivateHeader = "RUNIC-RSA-PRIVATE/1";

		private readonly object _gate = new object();

		private readonly SentinelRuntime _runtime;

		private readonly ManualLogSource _log;

		private readonly string _configRoot;

		private readonly string _privatePath;

		private readonly Func<string, string> _backup;

		internal bool HasManagedKey => File.Exists(_privatePath);

		internal SentinelManagedPolicyService(SentinelRuntime runtime, ManualLogSource log, string configRoot, Func<string, string> backup)
		{
			_runtime = runtime ?? throw new ArgumentNullException("runtime");
			_log = log;
			_configRoot = Path.GetFullPath(configRoot);
			_privatePath = Path.Combine(_configRoot, "RunicSentinel", "server-private", "RunicSentinel.private.key");
			_backup = backup;
		}

		internal SentinelAdminDocument CreateDocument(string status = "Ready")
		{
			SentinelAdminDocument sentinelAdminDocument = new SentinelAdminDocument
			{
				Status = status,
				AdmissionMode = _runtime.EffectiveRemoteAdmissionMode.ToString(),
				IntegritySeconds = (SentinelConfig.IntegrityCheckSeconds?.Value ?? 15).ToString(CultureInfo.InvariantCulture),
				VeryHighThreshold = (SentinelConfig.VeryHighDisconnectCount?.Value ?? 2).ToString(CultureInfo.InvariantCulture),
				HighThreshold = (SentinelConfig.HighDisconnectCount?.Value ?? 3).ToString(CultureInfo.InvariantCulture),
				EnforcementWindowSeconds = (SentinelConfig.EnforcementWindowSeconds?.Value ?? 60).ToString(CultureInfo.InvariantCulture),
				BackupTransitions = (SentinelConfig.BackupBeforeTransitions?.Value ?? true),
				ManagedSigningKey = HasManagedKey,
				Integrity = _runtime.GetIntegritySnapshot().State.ToString() + ":" + _runtime.GetIntegritySnapshot().ReasonCode,
				LastDenial = _runtime.LastAdmissionFailure
			};
			if (_runtime.TryGetVerifiedPolicy(out var policy))
			{
				sentinelAdminDocument.Sequence = policy.Sequence;
				sentinelAdminDocument.Profile = policy.Profile;
				sentinelAdminDocument.ExpiresUnixSeconds = policy.ExpiresUnixSeconds.ToString(CultureInfo.InvariantCulture);
				sentinelAdminDocument.UnknownMods = policy.Unknown.ToString();
				sentinelAdminDocument.RequiredMods = PluginLines(policy, PluginClassification.Required);
				sentinelAdminDocument.OptionalMods = PluginLines(policy, PluginClassification.ApprovedOptional);
				sentinelAdminDocument.GrayListMods = PluginLines(policy, PluginClassification.Unmanaged);
				sentinelAdminDocument.ForbiddenMods = PluginLines(policy, PluginClassification.Forbidden);
				sentinelAdminDocument.Administrators = IdentityLines(policy.Administrators);
				sentinelAdminDocument.BannedUsers = IdentityLines(policy.BannedUsers);
				sentinelAdminDocument.Modules = "Standalone Sentinel transport; no Runic Core or Runic Persistence dependency.";
				sentinelAdminDocument.SigningKeyPin = SentinelConfig.TrustedPublicKeySha256?.Value ?? string.Empty;
			}
			AttestationSnapshot snapshot;
			string status2;
			string text = ((!_runtime.TryGetCurrent(out snapshot, out status2)) ? ("Snapshot unavailable: " + status2) : string.Join("\n", snapshot.Plugins.Select((AttestedPlugin plugin) => plugin.Id + "|" + plugin.Version + "|" + plugin.Sha256)));
			AdmissionClientProfile profile;
			string text2 = (_runtime.TryGetLastRemoteAdmissionProfile(out profile) ? string.Join("\n", profile.Plugins.Select((AdmissionPluginEvidence plugin) => plugin.Id + "|" + plugin.Version + "|" + plugin.Sha256)) : "No client report observed in this process lifetime.");
			sentinelAdminDocument.DetectedProfile = "SERVER PROFILE (not a client allowlist)\n" + text + "\n\nMOST RECENT CLIENT REPORT\n" + text2;
			return sentinelAdminDocument;
		}

		internal string Bootstrap(string authority, string subject)
		{
			lock (_gate)
			{
				if (!CanonicalAuthority(authority) || !CanonicalSubject(subject))
				{
					throw new InvalidDataException("bootstrap-identity-invalid");
				}
				if (File.Exists(_privatePath))
				{
					throw new InvalidOperationException("managed-signing-key-already-exists");
				}
				if (!_runtime.TryGetCurrent(out var snapshot, out var _))
				{
					throw new InvalidOperationException("sentinel-snapshot-not-ready");
				}
				RSAParameters rSAParameters;
				using (RSA rSA = CreateManagedRsa3072())
				{
					rSAParameters = rSA.ExportParameters(includePrivateParameters: true);
				}
				SentinelPolicy policy;
				SentinelAdminDocument sentinelAdminDocument = (_runtime.TryGetVerifiedPolicy(out policy) ? CreateDocument("Bootstrap") : DefaultDocument(snapshot));
				SortedDictionary<string, SentinelAdministratorRole> sortedDictionary = ParseIdentities(sentinelAdminDocument.Administrators, "administrators");
				sortedDictionary[authority + ":" + Uri.EscapeDataString(subject)] = new SentinelAdministratorRole(authority, subject);
				sentinelAdminDocument.Administrators = IdentityLines(sortedDictionary.Values);
				string text = ApplyCore(sentinelAdminDocument, null, rSAParameters, changingTrustRoot: true);
				Directory.CreateDirectory(Path.GetDirectoryName(_privatePath));
				try
				{
					WriteExclusive(_privatePath, EncodePrivate(rSAParameters));
				}
				catch
				{
					throw new IOException("managed-key-persistence-failed-after-policy-signing");
				}
				return text + " Initial administrator: " + authority + ":" + subject + ".";
			}
		}

		internal string Apply(SentinelAdminDocument draft, string callerAuthority, string callerSubject)
		{
			if (draft == null)
			{
				throw new ArgumentNullException("draft");
			}
			lock (_gate)
			{
				if (!File.Exists(_privatePath))
				{
					throw new InvalidOperationException("server-managed-signing-key-required");
				}
				if (!_runtime.TryGetVerifiedPolicy(out var policy))
				{
					throw new InvalidOperationException("verified-policy-required");
				}
				if (draft.Sequence != policy.Sequence)
				{
					throw new InvalidOperationException("policy-sequence-stale");
				}
				RSAParameters privateParameters = DecodePrivate(File.ReadAllBytes(_privatePath));
				return ApplyCore(draft, callerAuthority + ":" + Uri.EscapeDataString(callerSubject), privateParameters, changingTrustRoot: false);
			}
		}

		private string ApplyCore(SentinelAdminDocument draft, string callerKey, RSAParameters privateParameters, bool changingTrustRoot)
		{
			ValidateSettings(draft);
			if (!string.Equals(draft.AdmissionMode, _runtime.EffectiveRemoteAdmissionMode.ToString(), StringComparison.Ordinal))
			{
				throw new InvalidDataException("admission-mode-restart-required");
			}
			SentinelPolicy policy;
			long num = (_runtime.TryGetVerifiedPolicy(out policy) ? checked(policy.Sequence + 1) : 1);
			long issued = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			byte[] array = BuildPolicy(draft, num, issued, callerKey);
			byte[] array2;
			byte[] array3;
			string text;
			using (RSA rSA = ImportManagedRsa3072(privateParameters))
			{
				array2 = rSA.SignData(array, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
				array3 = EncodePublic(rSA.ExportParameters(includePrivateParameters: false));
				text = Sha256(array3);
			}
			if (!PinnedRsaPublicKey.TryParse(array3, text, out var key, out var failure) || !SentinelPolicy.TryParseAndVerify(array, array2, key, out var policy2, out failure))
			{
				throw new InvalidDataException("generated-policy-invalid-" + failure);
			}
			if (policy2.Administrators.Count == 0)
			{
				throw new InvalidDataException("at-least-one-administrator-required");
			}
			string left = SentinelConfig.TrustedPublicKeySha256?.Value ?? string.Empty;
			if (!changingTrustRoot && !SentinelPolicy.FixedTimeHexEquals(left, text))
			{
				throw new InvalidOperationException("managed-key-does-not-match-active-trust-root");
			}
			string text2 = _backup?.Invoke("runic-sentinel-admin-policy-apply") ?? "no-world-loaded";
			ArchiveCurrent(num);
			AtomicWrite(Resolve(SentinelConfig.PolicyFile?.Value), array);
			AtomicWrite(Resolve(SentinelConfig.SignatureFile?.Value), Encoding.ASCII.GetBytes(Convert.ToBase64String(array2) + "\n"));
			AtomicWrite(Resolve(SentinelConfig.PublicKeyFile?.Value), array3);
			if (SentinelConfig.TrustedPublicKeySha256 != null && !string.Equals(SentinelConfig.TrustedPublicKeySha256.Value, text, StringComparison.Ordinal))
			{
				SentinelConfig.TrustedPublicKeySha256.Value = text;
			}
			ApplySettings(draft);
			_runtime.Start(_configRoot);
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)("Raven's Gate administrator applied signed policy sequence " + num + "; backup=" + text2 + ". Connected clients must receive the public passport before their next strict admission."));
			}
			return "Applied signed policy sequence " + num + ". Backup: " + text2 + ". Public-key pin: " + text + ". Admission mode remains " + _runtime.EffectiveRemoteAdmissionMode.ToString() + ".";
		}

		internal static RSA CreateManagedRsa3072()
		{
			RSA rSA = null;
			try
			{
				rSA = RSA.Create();
				rSA.KeySize = 3072;
				if (IsExactRsa3072(rSA, requirePrivate: true))
				{
					return rSA;
				}
			}
			catch
			{
			}
			rSA?.Dispose();
			try
			{
				rSA = new RSACryptoServiceProvider(3072)
				{
					PersistKeyInCsp = false
				};
				if (IsExactRsa3072(rSA, requirePrivate: true))
				{
					return rSA;
				}
			}
			catch
			{
			}
			rSA?.Dispose();
			throw new CryptographicException("rsa-3072-unavailable");
		}

		internal static RSA ImportManagedRsa3072(RSAParameters parameters)
		{
			RSA rSA = null;
			try
			{
				rSA = RSA.Create();
				rSA.ImportParameters(parameters);
				if (IsExactRsa3072(rSA, requirePrivate: true))
				{
					return rSA;
				}
			}
			catch
			{
			}
			rSA?.Dispose();
			try
			{
				rSA = new RSACryptoServiceProvider
				{
					PersistKeyInCsp = false
				};
				rSA.ImportParameters(parameters);
				if (IsExactRsa3072(rSA, requirePrivate: true))
				{
					return rSA;
				}
			}
			catch
			{
			}
			rSA?.Dispose();
			throw new CryptographicException("managed-key-not-rsa-3072");
		}

		private static bool IsExactRsa3072(RSA rsa, bool requirePrivate)
		{
			if (rsa == null || rsa.KeySize != 3072)
			{
				return false;
			}
			try
			{
				RSAParameters rSAParameters = rsa.ExportParameters(requirePrivate);
				return rSAParameters.Modulus != null && rSAParameters.Modulus.Length == 384 && rSAParameters.Exponent != null && rSAParameters.Exponent.Length == 3 && rSAParameters.Exponent[0] == 1 && rSAParameters.Exponent[1] == 0 && rSAParameters.Exponent[2] == 1 && (!requirePrivate || (rSAParameters.D != null && rSAParameters.D.Length != 0));
			}
			catch
			{
				return false;
			}
		}

		private byte[] BuildPolicy(SentinelAdminDocument draft, long sequence, long issued, string callerKey)
		{
			if (!SentinelPolicy.CanonicalAtom(draft.Profile, 1, 64))
			{
				throw new InvalidDataException("profile-invalid");
			}
			if (!long.TryParse(draft.ExpiresUnixSeconds, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 0 || (result != 0L && result <= issued))
			{
				throw new InvalidDataException("expiration-must-be-zero-or-future-unix-time");
			}
			if (draft.UnknownMods != "Forbidden" && draft.UnknownMods != "Quarantined" && draft.UnknownMods != "Unmanaged")
			{
				throw new InvalidDataException("unknown-mod-policy-invalid");
			}
			SortedDictionary<string, Rule> sortedDictionary = new SortedDictionary<string, Rule>(StringComparer.Ordinal);
			AddRules(draft.RequiredMods, "Required", sortedDictionary);
			AddRules(draft.OptionalMods, "ApprovedOptional", sortedDictionary);
			AddRules(draft.GrayListMods, "Unmanaged", sortedDictionary);
			AddRules(draft.ForbiddenMods, "Forbidden", sortedDictionary);
			SortedDictionary<string, SentinelAdministratorRole> sortedDictionary2 = ParseIdentities(draft.Administrators, "administrators");
			SortedDictionary<string, SentinelAdministratorRole> sortedDictionary3 = ParseIdentities(draft.BannedUsers, "banned-users");
			if (sortedDictionary2.Keys.Any(sortedDictionary3.ContainsKey))
			{
				throw new InvalidDataException("identity-cannot-be-admin-and-banned");
			}
			if (sortedDictionary2.Count == 0)
			{
				throw new InvalidDataException("at-least-one-administrator-required");
			}
			if (callerKey != null && !sortedDictionary2.ContainsKey(callerKey) && sortedDictionary2.Count < 1)
			{
				throw new InvalidDataException("last-administrator-cannot-be-removed");
			}
			IReadOnlyList<SentinelModuleRule> source = Array.Empty<SentinelModuleRule>();
			StringBuilder stringBuilder = new StringBuilder(4096);
			stringBuilder.Append("RUNIC-SENTINEL/3\nprofile=").Append(draft.Profile).Append("\nsequence=")
				.Append(sequence.ToString(CultureInfo.InvariantCulture))
				.Append("\nissued=")
				.Append(issued.ToString(CultureInfo.InvariantCulture))
				.Append("\nexpires=")
				.Append(result.ToString(CultureInfo.InvariantCulture))
				.Append("\nunknown=")
				.Append(draft.UnknownMods)
				.Append("\nunknown-capability=Forbidden\n");
			foreach (Rule value in sortedDictionary.Values)
			{
				stringBuilder.Append("rule=").Append(value.Classification).Append('|')
					.Append(value.Id)
					.Append('|')
					.Append(value.Version)
					.Append('|')
					.Append(value.Hash)
					.Append('\n');
			}
			foreach (SentinelModuleRule item in source.OrderBy<SentinelModuleRule, string>((SentinelModuleRule value) => value.Id, StringComparer.Ordinal))
			{
				stringBuilder.Append("module=").Append(item.Scope).Append('|')
					.Append(item.Id)
					.Append('|')
					.Append(item.Version)
					.Append('|')
					.Append(item.Protocol.ToString(CultureInfo.InvariantCulture))
					.Append('|')
					.Append(string.Join(",", item.Capabilities))
					.Append('\n');
			}
			foreach (SentinelAdministratorRole value2 in sortedDictionary2.Values)
			{
				stringBuilder.Append("role=").Append(value2.Authority).Append('|')
					.Append(Uri.EscapeDataString(value2.Subject))
					.Append('\n');
			}
			foreach (SentinelAdministratorRole value3 in sortedDictionary3.Values)
			{
				stringBuilder.Append("ban=").Append(value3.Authority).Append('|')
					.Append(Uri.EscapeDataString(value3.Subject))
					.Append('\n');
			}
			byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes(stringBuilder.ToString());
			if (bytes.Length > 1048576 || bytes.Length > 122880)
			{
				throw new InvalidDataException("policy-exceeds-admin-panel-bound");
			}
			return bytes;
		}

		private SentinelAdminDocument DefaultDocument(AttestationSnapshot snapshot)
		{
			return new SentinelAdminDocument
			{
				Profile = "runic-suite",
				Sequence = 0L,
				ExpiresUnixSeconds = "0",
				UnknownMods = "Unmanaged",
				RequiredMods = string.Empty,
				Modules = string.Empty,
				AdmissionMode = (SentinelConfig.RemoteAdmissionPolicy?.Value ?? "Optional"),
				IntegritySeconds = "15",
				VeryHighThreshold = "2",
				HighThreshold = "3",
				EnforcementWindowSeconds = "60",
				BackupTransitions = true
			};
		}

		private static void AddRules(string text, string classification, IDictionary<string, Rule> target)
		{
			foreach (string item in Lines(text))
			{
				string[] array = item.Split('|');
				if (array.Length != 3 || !SentinelPolicy.CanonicalPluginId(array[0]) || !SentinelPolicy.CanonicalVersionOrWildcard(array[1]) || !SentinelPolicy.CanonicalHashOrWildcard(array[2]))
				{
					throw new InvalidDataException("plugin-rule-invalid-" + item);
				}
				if (target.ContainsKey(array[0]))
				{
					throw new InvalidDataException("plugin-listed-more-than-once-" + array[0]);
				}
				target.Add(array[0], new Rule(classification, array[0], array[1], array[2]));
			}
		}

		private static SortedDictionary<string, SentinelAdministratorRole> ParseIdentities(string text, string label)
		{
			SortedDictionary<string, SentinelAdministratorRole> sortedDictionary = new SortedDictionary<string, SentinelAdministratorRole>(StringComparer.Ordinal);
			foreach (string item in Lines(text))
			{
				string[] array = item.Split('|');
				if (array.Length != 2 || !CanonicalAuthority(array[0]) || !CanonicalSubject(array[1]))
				{
					throw new InvalidDataException(label + "-identity-invalid-" + item);
				}
				string text2 = array[0] + ":" + Uri.EscapeDataString(array[1]);
				if (sortedDictionary.ContainsKey(text2))
				{
					throw new InvalidDataException(label + "-duplicate-" + text2);
				}
				sortedDictionary.Add(text2, new SentinelAdministratorRole(array[0], array[1]));
			}
			return sortedDictionary;
		}

		private void ApplySettings(SentinelAdminDocument value)
		{
			int value2 = BoundedInt(value.IntegritySeconds, 5, 300, "integrity-seconds");
			int value3 = BoundedInt(value.VeryHighThreshold, 1, 10, "very-high-threshold");
			int value4 = BoundedInt(value.HighThreshold, 1, 20, "high-threshold");
			int value5 = BoundedInt(value.EnforcementWindowSeconds, 10, 600, "enforcement-window");
			if (value.AdmissionMode != "Disabled" && value.AdmissionMode != "Optional" && value.AdmissionMode != "Required")
			{
				throw new InvalidDataException("admission-mode-invalid");
			}
			Set(SentinelConfig.IntegrityCheckSeconds, value2);
			Set(SentinelConfig.VeryHighDisconnectCount, value3);
			Set(SentinelConfig.HighDisconnectCount, value4);
			Set(SentinelConfig.EnforcementWindowSeconds, value5);
			Set(SentinelConfig.BackupBeforeTransitions, value.BackupTransitions);
		}

		private static void ValidateSettings(SentinelAdminDocument value)
		{
			BoundedInt(value.IntegritySeconds, 5, 300, "integrity-seconds");
			BoundedInt(value.VeryHighThreshold, 1, 10, "very-high-threshold");
			BoundedInt(value.HighThreshold, 1, 20, "high-threshold");
			BoundedInt(value.EnforcementWindowSeconds, 10, 600, "enforcement-window");
			if (value.AdmissionMode != "Disabled" && value.AdmissionMode != "Optional" && value.AdmissionMode != "Required")
			{
				throw new InvalidDataException("admission-mode-invalid");
			}
		}

		private void ArchiveCurrent(long nextSequence)
		{
			string text = Path.Combine(_configRoot, "RunicSentinel", "policy-history", "before-sequence-" + nextSequence.ToString(CultureInfo.InvariantCulture));
			Directory.CreateDirectory(text);
			CopyIfPresent(Resolve(SentinelConfig.PolicyFile?.Value), Path.Combine(text, "RunicSentinel.policy"));
			CopyIfPresent(Resolve(SentinelConfig.SignatureFile?.Value), Path.Combine(text, "RunicSentinel.policy.sig"));
			CopyIfPresent(Resolve(SentinelConfig.PublicKeyFile?.Value), Path.Combine(text, "RunicSentinel.policy.pub"));
		}

		private string Resolve(string configured)
		{
			if (!Path.IsPathRooted(configured ?? string.Empty))
			{
				return Path.GetFullPath(Path.Combine(_configRoot, configured ?? string.Empty));
			}
			return Path.GetFullPath(configured);
		}

		private static void AtomicWrite(string path, byte[] bytes)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			string text = path + ".admin.tmp";
			using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None, 65536, FileOptions.WriteThrough))
			{
				fileStream.Write(bytes, 0, bytes.Length);
				fileStream.Flush(flushToDisk: true);
			}
			if (File.Exists(path))
			{
				File.Replace(text, path, null);
			}
			else
			{
				File.Move(text, path);
			}
		}

		private static void WriteExclusive(string path, byte[] bytes)
		{
			using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough);
			fileStream.Write(bytes, 0, bytes.Length);
			fileStream.Flush(flushToDisk: true);
		}

		private static byte[] EncodePrivate(RSAParameters value)
		{
			return Encoding.ASCII.GetBytes("RUNIC-RSA-PRIVATE/1\n" + Text("modulus", value.Modulus) + Text("exponent", value.Exponent) + Text("d", value.D) + Text("p", value.P) + Text("q", value.Q) + Text("dp", value.DP) + Text("dq", value.DQ) + Text("inverseq", value.InverseQ));
			static string Text(string name, byte[] bytes)
			{
				return name + "=" + Convert.ToBase64String(bytes) + "\n";
			}
		}

		private static RSAParameters DecodePrivate(byte[] bytes)
		{
			if (bytes == null || bytes.Length == 0 || bytes.Length > 65536)
			{
				throw new InvalidDataException("managed-key-size-invalid");
			}
			string[] array = Encoding.ASCII.GetString(bytes).Split('\n');
			if (array.Length != 10 || array[0] != "RUNIC-RSA-PRIVATE/1" || array[9].Length != 0)
			{
				throw new InvalidDataException("managed-key-format-invalid");
			}
			Dictionary<string, byte[]> values = new Dictionary<string, byte[]>(StringComparer.Ordinal);
			for (int i = 1; i < 9; i++)
			{
				int num = array[i].IndexOf('=');
				if (num <= 0 || values.ContainsKey(array[i].Substring(0, num)))
				{
					throw new InvalidDataException("managed-key-field-invalid");
				}
				try
				{
					values.Add(array[i].Substring(0, num), Convert.FromBase64String(array[i].Substring(num + 1)));
				}
				catch
				{
					throw new InvalidDataException("managed-key-base64-invalid");
				}
			}
			return new RSAParameters
			{
				Modulus = Get("modulus"),
				Exponent = Get("exponent"),
				D = Get("d"),
				P = Get("p"),
				Q = Get("q"),
				DP = Get("dp"),
				DQ = Get("dq"),
				InverseQ = Get("inverseq")
			};
			byte[] Get(string key)
			{
				if (!values.TryGetValue(key, out var value) || value.Length == 0)
				{
					throw new InvalidDataException("managed-key-field-missing-" + key);
				}
				return value;
			}
		}

		private static byte[] EncodePublic(RSAParameters value)
		{
			return Encoding.ASCII.GetBytes("RUNIC-RSA-PUBLIC/1\nmodulus=" + Convert.ToBase64String(value.Modulus) + "\nexponent=" + Convert.ToBase64String(value.Exponent) + "\n");
		}

		private static string Sha256(byte[] bytes)
		{
			using SHA256 sHA = SHA256.Create();
			return SentinelPolicy.Hex(sHA.ComputeHash(bytes));
		}

		private static string PluginLines(SentinelPolicy policy, PluginClassification kind)
		{
			return string.Join("\n", from value in policy.Rules
				where value.Classification == kind
				select value.Id + "|" + value.Version + "|" + value.Sha256);
		}

		private static string IdentityLines(IEnumerable<SentinelAdministratorRole> values)
		{
			return string.Join("\n", from value in values.OrderBy<SentinelAdministratorRole, string>((SentinelAdministratorRole value) => value.CanonicalKey, StringComparer.Ordinal)
				select value.Authority + "|" + value.Subject);
		}

		private static string ModuleLines(IEnumerable<SentinelModuleRule> values)
		{
			return string.Join("\n", from value in values.OrderBy<SentinelModuleRule, string>((SentinelModuleRule value) => value.Id, StringComparer.Ordinal)
				select value.Scope.ToString() + "|" + value.Id + "|" + value.Version + "|" + value.Protocol + "|" + string.Join(",", value.Capabilities));
		}

		private static IEnumerable<string> Lines(string value)
		{
			return from line in (value ?? string.Empty).Replace("\r", string.Empty).Split('\n')
				select line.Trim() into line
				where line.Length > 0
				select line;
		}

		private static bool CanonicalAuthority(string value)
		{
			if (SentinelPolicy.CanonicalAtom(value, 1, 64))
			{
				return value.All((char character) => character < 'A' || character > 'Z');
			}
			return false;
		}

		private static bool CanonicalSubject(string value)
		{
			if (value != null && value.Length > 0 && value.Length <= 256 && !value.Any(char.IsControl) && !char.IsWhiteSpace(value[0]))
			{
				return !char.IsWhiteSpace(value[value.Length - 1]);
			}
			return false;
		}

		private static int BoundedInt(string value, int minimum, int maximum, string label)
		{
			if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < minimum || result > maximum)
			{
				throw new InvalidDataException(label + "-invalid");
			}
			return result;
		}

		private static void Set<T>(ConfigEntry<T> entry, T value)
		{
			if (entry != null && !EqualityComparer<T>.Default.Equals(entry.Value, value))
			{
				entry.Value = value;
			}
		}

		private static void CopyIfPresent(string source, string destination)
		{
			if (File.Exists(source) && !File.Exists(destination))
			{
				File.Copy(source, destination, overwrite: false);
			}
		}
	}
	internal static class SentinelNetworkMapWriter
	{
		private const int MaximumZdos = 16384;

		private const int MaximumEdges = 2048;

		private static readonly FieldInfo ObjectsField = AccessTools.Field(typeof(ZDOMan), "m_objectsByID");

		private static readonly string[] ProductionRoles = new string[4] { "input", "fuel", "output", "replenishment" };

		internal static bool TryAppend(StringBuilder builder, out string failure)
		{
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			failure = string.Empty;
			if (builder == null)
			{
				failure = "builder-missing";
				return false;
			}
			ZNet instance = ZNet.instance;
			ZDOMan instance2 = ZDOMan.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || instance2 == null)
			{
				failure = "server-console-required";
				return false;
			}
			if (!(ObjectsField?.GetValue(instance2) is Dictionary<ZDOID, ZDO> dictionary))
			{
				failure = "world-index-unavailable";
				return false;
			}
			int val = 0;
			int num = 0;
			int num2 = 0;
			builder.Append("network-map=server-local-snapshot\n");
			foreach (KeyValuePair<ZDOID, ZDO> item in dictionary)
			{
				if (val++ >= 16384)
				{
					break;
				}
				ZDO value = item.Value;
				if (value == null || !value.IsValid())
				{
					continue;
				}
				string value2 = value.GetString("runic.portals.record", string.Empty);
				string value3 = Safe(value.GetString("runic.portals.network", string.Empty), 64);
				if (!string.IsNullOrEmpty(value2) || !string.IsNullOrEmpty(value3))
				{
					num++;
					builder.Append("portal=").Append(Id(item.Key)).Append('|')
						.Append(value3)