Decompiled source of RunicSentinel v1.2.0

RunicSentinel.dll

Decompiled 32 minutes ago
using System;
using System.Collections.Generic;
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.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")]
[assembly: AssemblyDescription("Pinned-RSA policy verification, direct optional/required admission claims, and bounded transport-identity evidence")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Sentinel")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0")]
[assembly: InternalsVisibleTo("RunicSentinel.Tests")]
[assembly: InternalsVisibleTo("RunicSentinel.Forge")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.2.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
{
	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 ConfigEntry<KeyboardShortcut> AdminPanelKey;

		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
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			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. Required denies missing, stale, malformed, or signed-policy-mismatched self-reported claims by disconnecting the exact authenticated peer. Optional records bounded evidence without disconnecting. Disabled does not register Sentinel handshake claims or evaluators.");
			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>()));
			AdminPanelKey = config.Bind<KeyboardShortcut>("Administrator Panel", "OpenPanel", new KeyboardShortcut((KeyCode)284, Array.Empty<KeyCode>()), "Open the server-authorized Runic Sentinel administrator panel. Non-administrators are denied by the server.");
			PolicyFile.SettingChanged += Notify;
			SignatureFile.SettingChanged += Notify;
			PublicKeyFile.SettingChanged += Notify;
			TrustedPublicKeySha256.SettingChanged += Notify;
			AdminPanelKey.SettingChanged += Notify;
		}

		private static void Notify(object sender, EventArgs args)
		{
			SentinelConfig.Changed?.Invoke();
		}
	}
	[BepInPlugin("chazman.RunicSentinel", "Runic Sentinel", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicSentinel";

		public const string Name = "Runic Sentinel";

		public const string Version = "1.2.0";

		public const string ModuleId = "runic.sentinel";

		private SentinelRuntime _runtime;

		private SentinelEnforcementRuntime _enforcement;

		private SentinelOperatorCommands _operatorCommands;

		private SentinelManagedPolicyService _managedPolicy;

		private SentinelAdminControl _adminControl;

		private SentinelAdminPanel _adminPanel;

		private SentinelFlightRecorder _flightRecorder;

		private Harmony _harmony;

		private int _refreshRequested;

		private void Awake()
		{
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Expected O, but got Unknown
			SentinelConfig.Bind(((BaseUnityPlugin)this).Config);
			ConfigEntry<bool> enabled = SentinelConfig.Enabled;
			if (enabled != null && !enabled.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Sentinel is disabled; no worker or network handlers were created.");
				return;
			}
			try
			{
				_runtime = new SentinelRuntime();
				_runtime.Start(Paths.ConfigPath);
				_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);
				_adminPanel = new SentinelAdminPanel(_adminControl);
				_harmony = new Harmony("chazman.RunicSentinel");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				SentinelConfig.Changed += Refresh;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Sentinel v1.2.0 initialized as a standalone plugin. Signed passports enforce exact plugin profiles, administrators, and banned accounts; F3 administration is authenticated by the current Valheim transport peer. Client file claims remain self-reported compatibility evidence.");
			}
			catch (Exception ex)
			{
				Shutdown();
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic Sentinel failed closed: " + ex));
			}
		}

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

		private void Update()
		{
			try
			{
				_runtime?.TickNetwork();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel network request stopped: " + ex.Message));
			}
			try
			{
				_runtime?.TickIntegrity();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel runtime-integrity check failed closed: " + ex2.Message));
			}
			try
			{
				_adminControl?.Tick();
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel admin transport stopped safely: " + ex3.Message));
			}
			try
			{
				_adminPanel?.Tick();
			}
			catch (Exception ex4)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel administrator panel 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 policy refresh failed closed: " + ex5.Message));
			}
		}

		private void OnGUI()
		{
			try
			{
				_adminPanel?.Draw();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Sentinel administrator panel draw failed safely: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			SentinelConfig.Changed -= Refresh;
			Interlocked.Exchange(ref _refreshRequested, 0);
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			_harmony = null;
			try
			{
				_adminPanel?.Dispose();
			}
			catch
			{
			}
			_adminPanel = 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;
		}
	}
}
namespace RunicSentinel.Runtime
{
	internal sealed class SentinelAdminControl : IDisposable
	{
		private sealed class Pending
		{
			internal string Id;

			internal string Action;

			internal long Expires;

			internal Action<bool, SentinelAdminDocument, string> Status;

			internal Action<bool, string> Result;
		}

		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 MaximumPending = 16;

		private const int MaximumReplayEntries = 256;

		private static readonly long RequestLifetimeTicks = TimeSpan.FromSeconds(30.0).Ticks;

		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, Pending> _pending = new Dictionary<string, Pending>(StringComparer.Ordinal);

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

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

		private ZRoutedRpc _registeredRpc;

		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;
			}
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != _registeredRpc)
			{
				instance.Register<ZPackage>("runic.sentinel.admin.request.v1", (Action<long, ZPackage>)ReceiveRequest);
				instance.Register<ZPackage>("runic.sentinel.admin.response.v1", (Action<long, ZPackage>)ReceiveResponse);
				_registeredRpc = instance;
				_pending.Clear();
			}
			long ticks = DateTime.UtcNow.Ticks;
			foreach (string item in new List<string>(_pending.Keys))
			{
				if (_pending.TryGetValue(item, out var value) && value.Expires <= ticks)
				{
					_pending.Remove(item);
					Fail(value, "The server did not answer the administrator request in time.");
				}
			}
			ExpireCache(ticks);
		}

		internal void RequestStatus(Action<bool, SentinelAdminDocument, string> callback)
		{
			if (TryExecuteLocal("status", Array.Empty<byte>(), out var accepted, out var response, out var reason))
			{
				callback?.Invoke(accepted, DecodeDocument(response), reason);
				return;
			}
			Submit("status", Array.Empty<byte>(), new Pending
			{
				Status = callback
			});
		}

		internal void Apply(SentinelAdminDocument document, Action<bool, string> callback)
		{
			byte[] payload;
			try
			{
				payload = SentinelAdminProtocol.Encode(document);
			}
			catch (Exception ex)
			{
				callback?.Invoke(arg1: false, ex.Message);
				return;
			}
			if (TryExecuteLocal("apply", payload, out var accepted, out var response, out var reason))
			{
				callback?.Invoke(accepted, accepted ? SentinelAdminProtocol.DecodeMessage(response) : reason);
				return;
			}
			Submit("apply", payload, new Pending
			{
				Result = callback
			});
		}

		internal void RunTool(string tool, Action<bool, string> callback)
		{
			byte[] payload;
			try
			{
				payload = SentinelAdminProtocol.EncodeTool(tool);
			}
			catch (Exception ex)
			{
				callback?.Invoke(arg1: false, ex.Message);
				return;
			}
			if (TryExecuteLocal("tool", payload, out var accepted, out var response, out var reason))
			{
				callback?.Invoke(accepted, accepted ? SentinelAdminProtocol.DecodeMessage(response) : reason);
				return;
			}
			Submit("tool", payload, new Pending
			{
				Result = callback
			});
		}

		private bool TryExecuteLocal(string action, byte[] payload, out bool accepted, out byte[] response, out string reason)
		{
			accepted = false;
			response = Array.Empty<byte>();
			reason = string.Empty;
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return false;
			}
			if (!SentinelTransportIdentity.TryResolveLocal(out var authority, out var subject))
			{
				reason = "The host backend identity is unavailable.";
				return true;
			}
			Execute(authority, subject, action, payload, out accepted, out response, out reason);
			return true;
		}

		private void Submit(string action, byte[] payload, Pending pending)
		{
			if (_disposed || pending == null)
			{
				Fail(pending, "Administrator control is unavailable.");
				return;
			}
			ZNet instance = ZNet.instance;
			ZRoutedRpc registeredRpc = _registeredRpc;
			ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null);
			if ((Object)(object)instance == (Object)null || instance.IsServer() || registeredRpc == null || val == null || !val.IsReady() || _pending.Count >= 16)
			{
				Fail(pending, "The authoritative server administrator channel is unavailable.");
				return;
			}
			string text = (pending.Id = Guid.NewGuid().ToString("N"));
			pending.Action = action;
			pending.Expires = DateTime.UtcNow.Ticks + RequestLifetimeTicks;
			_pending.Add(text, pending);
			ZPackage val2 = WriteRequest(text, action, payload, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
			if (val2.Size() > 184320)
			{
				_pending.Remove(text);
				Fail(pending, "Administrator request exceeded its bounded size.");
			}
			else
			{
				registeredRpc.InvokeRoutedRPC(val.m_uid, "runic.sentinel.admin.request.v1", new object[1] { val2 });
			}
		}

		private void ReceiveRequest(long sender, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			if (_disposed || (Object)(object)instance == (Object)null || !instance.IsServer() || ZRoutedRpc.instance == null)
			{
				return;
			}
			ZNetPeer peer = instance.GetPeer(sender);
			if (peer == null || peer.m_uid != sender || !peer.IsReady())
			{
				return;
			}
			if (!TryReadRequest(package, out var id, out var action, out var payload, out var issued))
			{
				SendResponse(sender, string.Empty, accepted: false, "Malformed administrator request.", Array.Empty<byte>());
				return;
			}
			long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			if (issued < num - 30 || issued > num + 30)
			{
				SendResponse(sender, id, accepted: false, "Administrator request expired.", Array.Empty<byte>());
				return;
			}
			string key = sender.ToString(CultureInfo.InvariantCulture) + ":" + id;
			byte[] array = Digest(action, payload, issued);
			ExpireCache(DateTime.UtcNow.Ticks);
			string authority;
			string subject;
			if (_cache.TryGetValue(key, out var value))
			{
				if (!Fixed(value.RequestDigest, array))
				{
					SendResponse(sender, id, accepted: false, "Administrator request identity was reused.", Array.Empty<byte>());
				}
				else
				{
					SendResponse(sender, id, value.Accepted, value.Reason, value.Payload);
				}
			}
			else if (!SentinelTransportIdentity.TryResolvePeer(peer, out authority, out subject))
			{
				SendResponse(sender, 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(sender, 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 void ReceiveResponse(long sender, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			if (_disposed || (Object)(object)instance == (Object)null || instance.IsServer())
			{
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer != null && serverPeer.IsReady() && serverPeer.m_uid == sender && TryReadResponse(package, out var id, out var accepted, out var reason, out var payload) && _pending.TryGetValue(id, out var value))
			{
				_pending.Remove(id);
				if (value.Status != null)
				{
					SentinelAdminDocument sentinelAdminDocument = (accepted ? DecodeDocument(payload) : null);
					value.Status(accepted && sentinelAdminDocument != null, sentinelAdminDocument, (accepted && sentinelAdminDocument == null) ? "Server returned an invalid document." : reason);
				}
				else
				{
					value.Result?.Invoke(accepted, accepted ? SentinelAdminProtocol.DecodeMessage(payload) : reason);
				}
			}
		}

		private static SentinelAdminDocument DecodeDocument(byte[] payload)
		{
			if (!SentinelAdminProtocol.TryDecode(payload, out var value))
			{
				return null;
			}
			return value;
		}

		private static ZPackage WriteRequest(string id, string action, byte[] payload, long issued)
		{
			//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_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(id);
			val.Write(action);
			val.Write(issued);
			val.Write(payload ?? Array.Empty<byte>());
			val.Write(1369914905);
			return val;
		}

		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 static void SendResponse(long peer, string id, bool accepted, string reason, byte[] payload)
		{
			ZPackage val = WriteResponse(id, accepted, reason, payload);
			if (val.Size() <= 184320)
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(peer, "runic.sentinel.admin.response.v1", new object[1] { val });
				}
			}
		}

		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 bool TryReadResponse(ZPackage package, out string id, out bool accepted, out string reason, out byte[] payload)
		{
			id = (reason = string.Empty);
			accepted = false;
			payload = null;
			try
			{
				if (package == null || package.Size() < 1 || package.Size() > 184320 || package.ReadInt() != 1)
				{
					return false;
				}
				id = package.ReadString();
				accepted = package.ReadBool();
				reason = package.ReadString();
				payload = package.ReadByteArray();
				return CanonicalId(id) && reason.Length <= 512 && payload != null && payload.Length <= 122880 && package.ReadInt() == 1369914905 && package.GetPos() == package.Size();
			}
			catch
			{
				return false;
			}
		}

		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.";
		}

		private static void Fail(Pending pending, string reason)
		{
			pending?.Status?.Invoke(arg1: false, null, reason);
			pending?.Result?.Invoke(arg1: false, reason);
		}

		public void Dispose()
		{
			_disposed = true;
			foreach (Pending value in _pending.Values)
			{
				Fail(value, "Administrator control stopped.");
			}
			_pending.Clear();
			_cache.Clear();
			_cacheOrder.Clear();
			_registeredRpc = null;
		}
	}
	internal sealed class SentinelAdminPanel : IDisposable
	{
		private static SentinelAdminPanel _active;

		private readonly object _callbackGate = new object();

		private readonly Queue<Action> _callbacks = new Queue<Action>();

		private readonly SentinelAdminControl _control;

		private Rect _window = new Rect(0f, 0f, 1040f, 740f);

		private Vector2 _scroll;

		private SentinelAdminDocument _document;

		private bool _open;

		private bool _requesting;

		private bool _cursorVisible;

		private CursorLockMode _cursorLock;

		private int _tab;

		private string _status = "Press F3 to authenticate with the server.";

		private GUIStyle _heading;

		private GUIStyle _section;

		private GUIStyle _statusStyle;

		private GUIStyle _textArea;

		internal static bool IsOpen
		{
			get
			{
				if (_active != null)
				{
					return _active._open;
				}
				return false;
			}
		}

		internal SentinelAdminPanel(SentinelAdminControl control)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			_control = control ?? throw new ArgumentNullException("control");
			_active = this;
		}

		internal void Tick()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			DrainCallbacks();
			KeyboardShortcut val = (KeyboardShortcut)(((??)SentinelConfig.AdminPanelKey?.Value) ?? new KeyboardShortcut((KeyCode)284, Array.Empty<KeyCode>()));
			if (((KeyboardShortcut)(ref val)).IsDown())
			{
				if (_open)
				{
					Close();
				}
				else
				{
					RequestOpen();
				}
			}
			RenewCursorLease();
		}

		internal void Draw()
		{
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			if (_open && _document != null)
			{
				if (Event.current != null && (int)Event.current.type == 4 && (int)Event.current.keyCode == 27)
				{
					Event.current.Use();
					Close();
					return;
				}
				RenewCursorLease();
				EnsureStyles();
				float num = Mathf.Min(1100f, (float)Screen.width - 24f);
				float num2 = Mathf.Min(780f, (float)Screen.height - 24f);
				((Rect)(ref _window)).width = num;
				((Rect)(ref _window)).height = num2;
				((Rect)(ref _window)).x = Mathf.Clamp(((Rect)(ref _window)).x, 12f, Math.Max(12f, (float)Screen.width - num - 12f));
				((Rect)(ref _window)).y = Mathf.Clamp(((Rect)(ref _window)).y, 12f, Math.Max(12f, (float)Screen.height - num2 - 12f));
				_window = GUI.Window(730311, _window, new WindowFunction(DrawWindow), "Runic Sentinel Forge — Server Administrator");
			}
		}

		internal static void RenewCursorLease()
		{
			if (IsOpen)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
			}
		}

		private void RequestOpen()
		{
			if (_requesting)
			{
				return;
			}
			_requesting = true;
			_status = "Authenticating administrator with the authoritative server…";
			_control.RequestStatus(delegate(bool ok, SentinelAdminDocument document, string reason)
			{
				Enqueue(delegate
				{
					//IL_0076: Unknown result type (might be due to invalid IL or missing references)
					//IL_007b: Unknown result type (might be due to invalid IL or missing references)
					_requesting = false;
					if (!ok || document == null)
					{
						_status = "Access denied: " + reason;
						Player localPlayer = Player.m_localPlayer;
						if (localPlayer != null)
						{
							((Character)localPlayer).Message((MessageType)2, "Runic Sentinel administrator access denied.", 0, (Sprite)null);
						}
					}
					else
					{
						_document = document;
						_cursorVisible = Cursor.visible;
						_cursorLock = Cursor.lockState;
						((Rect)(ref _window)).x = ((float)Screen.width - ((Rect)(ref _window)).width) * 0.5f;
						((Rect)(ref _window)).y = ((float)Screen.height - ((Rect)(ref _window)).height) * 0.5f;
						_status = document.Status;
						_open = true;
						RenewCursorLease();
					}
				});
			});
		}

		private void DrawWindow(int id)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			string[] array = new string[5] { "Status", "Mod Policy", "People", "Enforcement", "Admin Tools" };
			for (int i = 0; i < array.Length; i++)
			{
				if (GUILayout.Toggle(_tab == i, array[i], GUIStyle.op_Implicit("Button"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }))
				{
					_tab = i;
				}
			}
			if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(72f),
				GUILayout.Height(30f)
			}))
			{
				Close();
			}
			GUILayout.EndHorizontal();
			GUILayout.Space(6f);
			_scroll = GUILayout.BeginScrollView(_scroll, Array.Empty<GUILayoutOption>());
			if (_tab == 0)
			{
				DrawStatus();
			}
			else if (_tab == 1)
			{
				DrawMods();
			}
			else if (_tab == 2)
			{
				DrawPeople();
			}
			else if (_tab == 3)
			{
				DrawEnforcement();
			}
			else
			{
				DrawTools();
			}
			GUILayout.EndScrollView();
			GUILayout.FlexibleSpace();
			GUILayout.Label(_status ?? string.Empty, _statusStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) });
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Every operation is re-authorized by backend account on the server.", Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUI.enabled = !_requesting && _document.ManagedSigningKey;
			if (GUILayout.Button("Apply & Sign Policy", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(175f),
				GUILayout.Height(34f)
			}))
			{
				Apply();
			}
			GUI.enabled = true;
			GUILayout.EndHorizontal();
			GUILayout.EndVertical();
			GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _window)).width - 80f, 24f));
		}

		private void DrawStatus()
		{
			Header("Raven's Gate status");
			Row("Signed profile", _document.Profile);
			Row("Policy sequence", _document.Sequence.ToString());
			Row("Runtime integrity", _document.Integrity);
			Row("Admission transport", _document.Status);
			Row("Last admission denial", Empty(_document.LastDenial));
			Row("Server-managed signing key", _document.ManagedSigningKey ? "Present" : "Not initialized");
			Row("Public-key pin", Empty(_document.SigningKeyPin));
			GUILayout.Space(10f);
			GUILayout.Label("The exact DLL list is admission evidence reported by each client. It does not turn a client into a trusted machine. Gameplay security comes from server-owned authorization and validation in every Runic endpoint.", _section, Array.Empty<GUILayoutOption>());
			if (!_document.ManagedSigningKey)
			{
				GUILayout.Label("Initialize once from the authoritative server console: runic_sentinel bootstrap steam <your SteamID64>", _statusStyle, Array.Empty<GUILayoutOption>());
			}
		}

		private void DrawMods()
		{
			Header("Signed mod passport");
			LabeledField("Profile name", ref _document.Profile);
			LabeledField("Expires (Unix seconds; 0 = never)", ref _document.ExpiresUnixSeconds);
			GUILayout.Label("Unknown mods", _section, Array.Empty<GUILayoutOption>());
			Choice(ref _document.UnknownMods, "Forbidden", "Quarantined", "Unmanaged");
			PolicyArea("Required / whitelist — id|version|sha256", ref _document.RequiredMods);
			PolicyArea("Approved optional — id|version|sha256", ref _document.OptionalMods);
			PolicyArea("Gray list / unmanaged — id|version|sha256", ref _document.GrayListMods);
			PolicyArea("Forbidden — id|version|sha256", ref _document.ForbiddenMods);
			GUILayout.Label("Detected server profile (read-only)", _section, Array.Empty<GUILayoutOption>());
			GUILayout.TextArea(_document.DetectedProfile, _textArea, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(150f) });
			GUILayout.Label("Standalone transport (server-owned, read-only)", _section, Array.Empty<GUILayoutOption>());
			GUILayout.TextArea(_document.Modules, _textArea, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(120f) });
		}

		private void DrawPeople()
		{
			Header("Signed identities");
			GUILayout.Label("Administrators — authority|subject", _section, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Only these authenticated backend accounts may open or use this panel.", Array.Empty<GUILayoutOption>());
			_document.Administrators = GUILayout.TextArea(_document.Administrators, _textArea, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(220f) });
			GUILayout.Space(12f);
			GUILayout.Label("Banned users — authority|subject", _section, Array.Empty<GUILayoutOption>());
			_document.BannedUsers = GUILayout.TextArea(_document.BannedUsers, _textArea, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(220f) });
		}

		private void DrawEnforcement()
		{
			Header("Automatic enforcement");
			GUILayout.Label("Admission mode", _section, Array.Empty<GUILayoutOption>());
			Choice(ref _document.AdmissionMode, "Required", "Optional", "Disabled");
			LabeledField("Runtime DLL/policy check interval (5–300 seconds)", ref _document.IntegritySeconds);
			LabeledField("Very-high-confidence findings before disconnect (1–10)", ref _document.VeryHighThreshold);
			LabeledField("High-confidence findings before disconnect (1–20)", ref _document.HighThreshold);
			LabeledField("Graduated-enforcement window (10–600 seconds)", ref _document.EnforcementWindowSeconds);
			_document.BackupTransitions = GUILayout.Toggle(_document.BackupTransitions, " Require a verified world backup before policy/modpack transitions", Array.Empty<GUILayoutOption>());
			GUILayout.Space(14f);
			GUILayout.Label("Conclusive violations disconnect immediately. Lesser findings are blocked first and disconnect only after the configured threshold. All decisions are recorded in the bounded security flight recorder.", _section, Array.Empty<GUILayoutOption>());
		}

		private void DrawTools()
		{
			Header("Server-owned administrator tools");
			Tool("Create Support Report", "report", "Writes bounded policy, profile, integrity, and enforcement evidence.");
			Tool("Create Production/Portal Network Map", "networks", "Writes the administrator-only live network topology snapshot on the server.");
			Tool("Create Verified World Backup", "backup", "Creates and validates a Runic Safety backup of the currently loaded world.");
		}

		private void Tool(string label, string tool, string explanation)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.enabled = !_requesting;
			if (GUILayout.Button(label, (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(280f),
				GUILayout.Height(38f)
			}))
			{
				RunTool(tool);
			}
			GUI.enabled = true;
			GUILayout.Label(explanation, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			GUILayout.EndHorizontal();
			GUILayout.Space(8f);
		}

		private void Apply()
		{
			_requesting = true;
			_status = "Validating, backing up, signing, and applying on the server…";
			_control.Apply(_document, delegate(bool ok, string message)
			{
				Enqueue(delegate
				{
					_requesting = false;
					_status = (ok ? "Success: " : "Rejected: ") + message;
					if (ok)
					{
						Refresh();
					}
				});
			});
		}

		private void RunTool(string tool)
		{
			_requesting = true;
			_status = "Running server tool…";
			_control.RunTool(tool, delegate(bool ok, string message)
			{
				Enqueue(delegate
				{
					_requesting = false;
					_status = (ok ? "Success: " : "Failed: ") + message;
				});
			});
		}

		private void Refresh()
		{
			_requesting = true;
			_control.RequestStatus(delegate(bool ok, SentinelAdminDocument document, string reason)
			{
				Enqueue(delegate
				{
					_requesting = false;
					if (ok && document != null)
					{
						_document = document;
					}
					else
					{
						_status = "Refresh failed: " + reason;
					}
				});
			});
		}

		private void Close()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if (_open)
			{
				_open = false;
				Cursor.visible = _cursorVisible;
				Cursor.lockState = _cursorLock;
			}
		}

		private void EnsureStyles()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Expected O, but got Unknown
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			if (_heading == null)
			{
				_heading = new GUIStyle(GUI.skin.label)
				{
					fontSize = 21,
					fontStyle = (FontStyle)1
				};
				_heading.normal.textColor = new Color(1f, 0.63f, 0.08f);
				_section = new GUIStyle(GUI.skin.label)
				{
					fontSize = 15,
					fontStyle = (FontStyle)1,
					wordWrap = true
				};
				_section.normal.textColor = new Color(1f, 0.72f, 0.25f);
				_statusStyle = new GUIStyle(GUI.skin.box)
				{
					alignment = (TextAnchor)3,
					wordWrap = true
				};
				_textArea = new GUIStyle(GUI.skin.textArea)
				{
					wordWrap = false,
					fontSize = 13
				};
			}
		}

		private void Header(string text)
		{
			GUILayout.Label(text, _heading, Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
		}

		private static void Row(string name, string value)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) });
			GUILayout.Label(value ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
			GUILayout.EndHorizontal();
		}

		private void LabeledField(string label, ref string value)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(label, _section, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(430f) });
			value = GUILayout.TextField(value ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			GUILayout.EndHorizontal();
		}

		private void PolicyArea(string label, ref string value)
		{
			GUILayout.Label(label, _section, Array.Empty<GUILayoutOption>());
			value = GUILayout.TextArea(value ?? string.Empty, _textArea, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(145f) });
		}

		private static void Choice(ref string value, params string[] choices)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			foreach (string text in choices)
			{
				if (GUILayout.Toggle(value == text, text, GUIStyle.op_Implicit("Button"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }))
				{
					value = text;
				}
			}
			GUILayout.EndHorizontal();
		}

		private static string Empty(string value)
		{
			if (!string.IsNullOrEmpty(value))
			{
				return value;
			}
			return "None";
		}

		private void Enqueue(Action action)
		{
			lock (_callbackGate)
			{
				_callbacks.Enqueue(action);
			}
		}

		private void DrainCallbacks()
		{
			while (true)
			{
				Action action;
				lock (_callbackGate)
				{
					if (_callbacks.Count == 0)
					{
						break;
					}
					action = _callbacks.Dequeue();
				}
				try
				{
					action();
				}
				catch
				{
				}
			}
		}

		public void Dispose()
		{
			Close();
			if (_active == this)
			{
				_active = null;
			}
			lock (_callbackGate)
			{
				_callbacks.Clear();
			}
		}
	}
	[HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")]
	internal static class SentinelAdminCursorPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			SentinelAdminPanel.RenewCursorLease();
		}
	}
	[HarmonyPatch(typeof(Player), "TakeInput")]
	internal static class SentinelAdminInputPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(Player __instance, ref bool __result)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && SentinelAdminPanel.IsOpen)
			{
				__result = false;
			}
		}
	}
	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 = (SentinelConfig.RemoteAdmissionPolicy?.Value ?? "Optional"),
				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;
			}
			if (_runtime.TryGetCurrent(out var snapshot, out var status2))
			{
				sentinelAdminDocument.DetectedProfile = string.Join("\n", snapshot.Plugins.Select((AttestedPlugin plugin) => plugin.Id + "|" + plugin.Version + "|" + plugin.Sha256));
			}
			else
			{
				sentinelAdminDocument.DetectedProfile = "Snapshot unavailable: " + status2;
			}
			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 = RSA.Create())
				{
					rSA.KeySize = 3072;
					if (rSA.KeySize != 3072)
					{
						throw new CryptographicException("rsa-3072-unavailable");
					}
					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);
			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 = RSA.Create())
			{
				rSA.ImportParameters(privateParameters);
				if (rSA.KeySize != 3072)
				{
					throw new CryptographicException("managed-key-not-rsa-3072");
				}
				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 changes take effect after restart.";
		}

		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 = "Forbidden",
				RequiredMods = string.Join("\n", snapshot.Plugins.Select((AttestedPlugin plugin) => plugin.Id + "|" + plugin.Version + "|" + plugin.Sha256)),
				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);
			Set(SentinelConfig.RemoteAdmissionPolicy, value.AdmissionMode);
		}

		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)
						.Append('|')
						.Append(Safe(value.GetString("runic.portals.name", string.Empty), 64))
						.Append('|')
						.Append(value.GetInt("runic.portals.networkKind", 0).ToString(CultureInfo.InvariantCulture))
						.Append('|')
						.Append(Safe(value.GetString("runic.portals.group", string.Empty), 64))
						.Append('|')
						.Append(Position(value.GetPosition()))
						.Append('\n');
				}
				for (int i = 0; i < ProductionRoles.Length; i++)
				{
					if (num2 >= 2048)
					{
						break;
					}
					string text = ProductionRoles[i];
					string text2 = value.GetString("runic.production." + text + ".record", string.Empty);
					if (!string.IsNullOrEmpty(text2) && !(text2 == "!"))
					{
						num2++;
						if (!TryReadProductionTarget(text2, i, out var linkId, out var target))
						{
							target = "record-invalid";
						}
						builder.Append("production-edge=").Append(Id(item.Key)).Append('|')
							.Append(value.GetPrefab().ToString(CultureInfo.InvariantCulture))
							.Append('|')
							.Append(text)
							.Append('|')
							.Append(Safe(linkId, 128))
							.Append('|')
							.Append(Safe(target, 160))
							.Append('|')
							.Append(Position(value.GetPosition()))
							.Append('\n');
					}
				}
			}
			builder.Append("network-map-summary=zdos:").Append(Math.Min(val, 16384).ToString(CultureInfo.InvariantCulture)).Append(",portals:")
				.Append(num.ToString(CultureInfo.InvariantCulture))
				.Append(",production-edges:")
				.Append(num2.ToString(CultureInfo.InvariantCulture))
				.Append(",truncated:")
				.Append((dictionary.Count > 16384 || num2 >= 2048) ? "true" : "false")
				.Append('\n');
			return true;
		}

		private static bool TryReadProductionTarget(string encoded, int expectedRole, out string linkId, out string target)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Expected O, but got Unknown
			linkId = string.Empty;
			target = string.Empty;
			try
			{
				if (encoded.Length > 2048)
				{
					return false;
				}
				byte[] array = Convert.FromBase64String(encoded);
				if (array.Length == 0 || array.Length > 1536)
				{
					return false;
				}
				ZPackage val = new ZPackage(array);
				byte[] array2 = val.ReadByteArray();
				byte[] array3 = val.ReadByteArray();
				if (val.GetPos() != val.Size() || array2 == null || array2.Length == 0 || array2.Length > 1536 || array3 == null || array3.Length != 32)
				{
					return false;
				}
				using (SHA256 sHA = SHA256.Create())
				{
					byte[] array4 = sHA.ComputeHash(array2);
					int num = 0;
					for (int i = 0; i < array4.Length; i++)
					{
						num |= array4[i] ^ array3[i];
					}
					if (num != 0)
					{
						return false;
					}
				}
				ZPackage val2 = new ZPackage(array2);
				if (val2.ReadInt() != 1 || val2.ReadInt() != expectedRole)
				{
					return false;
				}
				linkId = val2.ReadString();
				target = val2.ReadString();
				return val2.GetPos() <= val2.Size() && linkId.Length <= 128 && target.Length <= 160;
			}
			catch
			{
				linkId = string.Empty;
				target = string.Empty;
				return false;
			}
		}

		private unsafe static string Id(ZDOID id)
		{
			return Safe(((object)(*(ZDOID*)(&id))/*cast due to .constrained prefix*/).ToString(), 96);
		}

		private static string Position(Vector3 value)
		{
			return value.x.ToString("F1", CultureInfo.InvariantCulture) + "," + value.y.ToString("F1", CultureInfo.InvariantCulture) + "," + value.z.ToString("F1", CultureInfo.InvariantCulture);
		}

		private static string Safe(string value, int maximum)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			if (value.Length > maximum)
			{
				value = value.Substring(0, maximum);
			}
			for (int i = 0; i < value.Length; i++)
			{
				if (char.IsControl(value[i]) || value[i] == '|')
				{
					return "invalid-text";
				}
			}
			return value;
		}
	}
	internal sealed class SentinelOperatorCommands : IDisposable
	{
		private const int MaximumReportBytes = 524288;

		private readonly SentinelRuntime _runtime;

		private readonly ManualLogSource _log;

		private readonly string _reportRoot;

		private readonly SentinelManagedPolicyService _managed;

		private readonly ConsoleCommand _command;

		private bool _disposed;

		internal SentinelOperatorCommands(SentinelRuntime runtime, ManualLogSource log, string configRoot, SentinelManagedPolicyService managed)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			_runtime = runtime ?? throw new ArgumentNullException("runtime");
			_log = log;
			_managed = managed ?? throw new ArgumentNullException("managed");
			_reportRoot = Path.Combine(Path.GetFullPath(configRoot), "RunicSentinel", "reports");
			_command = new ConsoleCommand("runic_sentinel", "Raven's Gate: status | report | networks | bootstrap <authority> <subject>", new ConsoleEvent(OnCommand), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private void OnCommand(ConsoleEventArgs args)
		{
			if (_disposed || (Object)(object)args?.Context == (Object)null)
			{
				return;
			}
			switch ((args.Args.Length > 1) ? args.Args[1].Trim().ToLowerInvariant() : "status")
			{
			case "status":
			{
				SentinelIntegritySnapshot integritySnapshot = _runtime.GetIntegritySnapshot();
				args.Context.AddString("Raven's Gate: " + integritySnapshot.State.ToString() + "; profile=" + ((_runtime.PolicyProfile.Length == 0) ? "none" : _runtime.PolicyProfile) + "; sequence=" + _runtime.PolicySequence.ToString(CultureInfo.InvariantCulture) + "; admission=" + (_runtime.AuthoritativeTransportReady ? "standalone-routed" : "unavailable") + "; last-denial=" + ((_runtime.LastAdmissionFailure.Length == 0) ? "none" : _runtime.LastAdmissionFailure) + ".");
				break;
			}
			case "report":
				try
				{
					string text = WriteReport();
					args.Context.AddString("Runic Sentinel support report created: " + text);
					break;
				}
				catch (Exception ex2)
				{
					args.Context.AddString("Runic Sentinel report failed closed: " + ex2.GetType().Name + ".");
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogWarning((object)("Sentinel report failed: " + ex2.GetType().Name + "."));
					}
					break;
				}
			case "networks":
				try
				{
					string text2 = WriteReport(includeNetworks: true);
					args.Context.AddString("Runic Sentinel administrator network snapshot created: " + text2);
					break;
				}
				catch (Exception ex3)
				{
					args.Context.AddString((ex3.Message == "server-console-required") ? "Runic Sentinel network maps are available only on the authoritative server." : ("Runic Sentinel network snapshot failed closed: " + ex3.GetType().Name + "."));
					break;
				}
			case "bootstrap":
				if (args.Args.Length != 4)
				{
					args.Context.AddString("Usage: runic_sentinel bootstrap <authority> <subject>");
					break;
				}
				try
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
					{
						throw new InvalidOperationException("authoritative-server-console-required");
					}
					args.Context.AddString(_managed.Bootstrap(args.Args[2], args.Args[3]));
					break;
				}
				catch (Exception ex)
				{
					args.Context.AddString("Runic Sentinel bootstrap failed closed: " + ex.Message);
					break;
				}
			default:
				args.Context.AddString("Usage: runic_sentinel status | report | networks | bootstrap <authority> <subject>");
				break;
			}
		}

		internal string WriteReport(bool includeNetworks = false)
		{
			StringBuilder stringBuilder = new StringBuilder(16384);
			SentinelIntegritySnapshot integritySnapshot = _runtime.GetIntegritySnapshot();
			stringBuilder.Append("RUNIC-SENTINEL-SUPPORT/1\n").Append("created-utc=").Append(DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture))
				.Append('\n')
				.Append("integrity=")
				.Append(integritySnapshot.State)
				.Append('\n')
				.Append("integrity-reason=")
				.Append(integritySnapshot.ReasonCode)
				.Append('\n')
				.Append("policy-profile=")
				.Append(_runtime.PolicyProfile)
				.Append('\n')
				.Append("policy-sequence=")
				.Append(_runtime.PolicySequence.ToString(CultureInfo.InvariantCulture))
				.Append('\n')
				.Append("policy-digest=")
				.Append(integritySnapshot.PolicyDigest)
				.Append('\n')
				.Append("admission-transport=")
				.Append(_runtime.AuthoritativeTransportReady ? "ready" : "unavailable")
				.Append('\n')
				.Append("last-admission-denial=")
				.Append(_runtime.LastAdmissionFailure)
				.Append('\n');
			if (_runtime.TryGetCurrent(out var snapshot, out var status))
			{
				stringBuilder.Append("snapshot-status=").Append(status).Append('\n')
					.Append("snapshot-digest=")
					.Append(snapshot.Digest)
					.Append('\n')
					.Append("plugins=")
					.Append(snapshot.Plugins.Count.ToString(CultureInfo.InvariantCulture))
					.Append('\n');
				foreach (AttestedPlugin plugin in snapshot.Plugins)
				{
					stringBuilder.Append("plugin=").Append(plugin.Id).Append('|')
						.Append(plugin.Version)
						.Append('|')
						.Append(plugin.Sha256)
						.Append('\n');
				}
			}
			else
			{
				stringBuilder.Append("snapshot-status=").Append(status).Append('\n');
			}
			stringBuilder.Append("transport=standalone-valheim\n");
			EvidenceReadSnapshot evidenceReadSnapshot = _runtime.Evidence.ReadAfter(0L, 256);
			stringBuilder.Append("evidence-newest=").Append(evidenceReadSnapshot.NewestSequence.ToString(CultureInfo.InvariantCulture)).Append('\n');
			foreach (SecurityEvidence entry in evidenceReadSnapshot.Entries)
			{
				stringBuilder.Append("evidence=").Append(entry.Sequence.ToString(CultureInfo.InvariantCulture)).Append('|')
					.Append(entry.UnixSeconds.ToString(CultureInfo.InvariantCulture))
					.Append('|')
					.Append(entry.ProviderModuleId)
					.Append('|')
					.Append(entry.Rule)
					.Append('|')
					.Append(entry.Confidence)
					.Append('|')
					.Append(entry.EffectiveAction)
					.Append('|')
					.Append(entry.Detail)
					.Append('\n');
			}
			if (includeNetworks && !SentinelNetworkMapWriter.TryAppend(stringBuilder, out var failure))
			{
				throw new InvalidOperationException(failure);
			}
			byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(stringBuilder.ToString());
			if (bytes.Length > 524288)
			{
				throw new InvalidDataException("The bounded support report exceeded 512 KiB.");
			}
			Directory.CreateDirectory(_reportRoot);
			string text = Path.Combine(_reportRoot, "sentinel-report-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + ".txt");
			using FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 65536, FileOptions.WriteThrough);
			fileStream.Write(bytes, 0, bytes.Length);
			fileStream.Flush(flushToDisk: true);
			return text;
		}

		public void Dispose()
		{
			_disposed = true;
		}
	}
	internal sealed class SentinelRuntime : ISentinelAttestationService, ISentinelAdmissionService, ISentinelNetworkProfileSource, IDisposable
	{
		private sealed class FileEvidence
		{
			internal string Sha256 { get; }

			internal long Length { get; }

			internal long LastWriteUtcTicks { get; }

			internal FileEvidence(string sha256, long length, long lastWriteUtcTicks)
			{
				Sha256 = sha256;
				Length = length;
				LastWriteUtcTicks = lastWriteUtcTicks;
			}
		}

		private sealed class IntegrityFileStamp
		{
			internal string Path { get; }

			internal long Length { get; }

			internal long LastWriteUtcTicks { get; }

			internal bool PolicyAsset { get; }

			internal IntegrityFileStamp(string path, long length, long lastWriteUtcTicks, bool policyAsset)
			{
				Path = path;
				Length = length;
				LastWri