Decompiled source of PortalRules v1.0.0

PortalRules.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Jotunn.Configs;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using ServerSync;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("PortalRules")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("PortalRules")]
[assembly: AssemblyCopyright("Copyright © 2026 sighsorry")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace PortalRules
{
	internal static class AdminPortalBiomeDefaults
	{
		private sealed class BiomeDefaultsYaml
		{
			[YamlMember(Alias = "format_version")]
			public int FormatVersion { get; set; }

			[YamlMember(Alias = "biomes")]
			public Dictionary<string, string?>? Biomes { get; set; }
		}

		[HarmonyPatch(typeof(ZoneSystem), "Start")]
		[HarmonyAfter(new string[] { "expand_world_data" })]
		[HarmonyPriority(0)]
		private static class BiomeRegistryReadyPatch
		{
			private static void Postfix()
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					CompleteBiomeRegistryInitialization();
				}
			}
		}

		private const int FormatVersion = 1;

		private const int MaximumBiomeCount = 4096;

		private const int MaximumBiomeNameLength = 128;

		private const int MaximumReloadAttempts = 5;

		private const long MaximumFileBytes = 524288L;

		private const string DirectoryName = "PortalRules";

		private const string FileName = "admin-portal-biome-global-keys.yml";

		private const float RegistryReadyFallbackSeconds = 5f;

		private static readonly KeyValuePair<string, string>[] VanillaTemplate = new KeyValuePair<string, string>[9]
		{
			new KeyValuePair<string, string>("Meadows", ""),
			new KeyValuePair<string, string>("BlackForest", "defeated_eikthyr"),
			new KeyValuePair<string, string>("Swamp", "defeated_gdking"),
			new KeyValuePair<string, string>("Ocean", "defeated_bonemass"),
			new KeyValuePair<string, string>("Mountain", "defeated_bonemass"),
			new KeyValuePair<string, string>("Plains", "defeated_dragon"),
			new KeyValuePair<string, string>("Mistlands", "defeated_goblinking"),
			new KeyValuePair<string, string>("AshLands", "defeated_queen"),
			new KeyValuePair<string, string>("DeepNorth", "defeated_fader")
		};

		private static readonly HashSet<string> AmbiguousPlainYamlScalars = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "null", "true", "false", "y", "n", "yes", "no", "on", "off" };

		private static readonly TimeSpan ReloadDebounce = TimeSpan.FromMilliseconds(500.0);

		private static readonly TimeSpan ReloadRetryDelay = TimeSpan.FromSeconds(1.0);

		private static readonly TimeSpan PollingInterval = TimeSpan.FromSeconds(30.0);

		private static readonly TimeSpan WatcherRestartRetryDelay = TimeSpan.FromSeconds(10.0);

		private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithDuplicateKeyChecking().Build();

		private static Dictionary<string, string> _defaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static FileSystemWatcher? _watcher;

		private static string _configurationDirectory = "";

		private static string _filePath = "";

		private static bool _active;

		private static bool _hasValidSnapshot;

		private static bool _templatePending;

		private static bool _biomeRegistryReady;

		private static bool _registryFallbackLogged;

		private static float _registryFallbackNotBefore;

		private static DateTime _templateRetryNotBeforeUtc;

		private static DateTime _nextPollUtc;

		private static int _reloadRequested;

		private static int _reloadAttempts;

		private static int _watcherRestartRequested;

		private static long _reloadNotBeforeUtcTicks;

		private static long _watcherRestartNotBeforeUtcTicks;

		internal static bool IsActive => _active;

		internal static bool IsBiomeRegistryReady => _biomeRegistryReady;

		internal static bool HasValidSnapshot
		{
			get
			{
				if (_active)
				{
					return _hasValidSnapshot;
				}
				return false;
			}
		}

		internal static void BeginServerSession()
		{
			if (_active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			bool biomeRegistryReady = _biomeRegistryReady;
			EndServerSession();
			_active = true;
			_defaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			_hasValidSnapshot = false;
			_templatePending = false;
			_biomeRegistryReady = biomeRegistryReady;
			_registryFallbackLogged = false;
			_registryFallbackNotBefore = Time.realtimeSinceStartup + 5f;
			_templateRetryNotBeforeUtc = DateTime.MinValue;
			_nextPollUtc = DateTime.UtcNow + PollingInterval;
			Interlocked.Exchange(ref _reloadRequested, 0);
			Interlocked.Exchange(ref _reloadAttempts, 0);
			Interlocked.Exchange(ref _watcherRestartRequested, 0);
			Interlocked.Exchange(ref _reloadNotBeforeUtcTicks, 0L);
			Interlocked.Exchange(ref _watcherRestartNotBeforeUtcTicks, 0L);
			_configurationDirectory = Path.Combine(Paths.ConfigPath, "PortalRules");
			_filePath = Path.Combine(_configurationDirectory, "admin-portal-biome-global-keys.yml");
			try
			{
				Directory.CreateDirectory(_configurationDirectory);
				_templatePending = !File.Exists(_filePath);
				try
				{
					StartWatcher();
				}
				catch (Exception ex)
				{
					StopWatcher();
					Interlocked.Exchange(ref _watcherRestartRequested, 1);
					ScheduleWatcherRestart(WatcherRestartRetryDelay);
					PortalRulesPlugin.PortalRulesLogger.LogError((object)("Failed to watch admin-portal-biome-global-keys.yml; periodic reload remains active: " + ex.Message));
				}
				bool changed;
				if (_templatePending)
				{
					PortalRulesPlugin.PortalRulesLogger.LogInfo((object)"admin-portal-biome-global-keys.yml will be generated after the biome registry is ready.");
				}
				else if (!TryReload(logOnlyWhenChanged: false, out changed))
				{
					ScheduleReload(ReloadRetryDelay);
				}
			}
			catch (Exception ex2)
			{
				StopWatcher();
				_active = false;
				_templatePending = false;
				PortalRulesPlugin.PortalRulesLogger.LogError((object)("Failed to initialize admin-portal-biome-global-keys.yml: " + ex2.Message));
			}
		}

		internal static bool EnsureTemplateReady()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return false;
			}
			BeginServerSession();
			if (!_active)
			{
				return false;
			}
			if (!_templatePending)
			{
				if (_hasValidSnapshot || !File.Exists(_filePath))
				{
					return _hasValidSnapshot;
				}
				return TryReloadImmediately();
			}
			if (!File.Exists(_filePath) && DateTime.UtcNow < _templateRetryNotBeforeUtc)
			{
				return false;
			}
			string filePath = _filePath;
			try
			{
				bool flag = false;
				if (!File.Exists(filePath))
				{
					string content = BuildRegistryTemplate();
					flag = TryCreateFileWithoutOverwrite(filePath, content);
				}
				if (!_active || !string.Equals(_filePath, filePath, StringComparison.Ordinal) || !File.Exists(filePath))
				{
					return false;
				}
				_templatePending = false;
				_templateRetryNotBeforeUtc = DateTime.MinValue;
				if (flag)
				{
					PortalRulesPlugin.PortalRulesLogger.LogInfo((object)"Generated admin-portal-biome-global-keys.yml from the registered biome names.");
				}
				return TryReloadImmediately();
			}
			catch (Exception ex)
			{
				_templateRetryNotBeforeUtc = DateTime.UtcNow + ReloadRetryDelay;
				PortalRulesPlugin.PortalRulesLogger.LogError((object)("Failed to generate admin-portal-biome-global-keys.yml; it will remain pending until the next retry: " + ex.Message));
				return false;
			}
		}

		internal static void EndServerSession()
		{
			StopWatcher();
			_active = false;
			_defaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			_hasValidSnapshot = false;
			_templatePending = false;
			_biomeRegistryReady = false;
			_registryFallbackLogged = false;
			_registryFallbackNotBefore = 0f;
			_templateRetryNotBeforeUtc = DateTime.MinValue;
			_nextPollUtc = DateTime.MinValue;
			_configurationDirectory = "";
			_filePath = "";
			Interlocked.Exchange(ref _reloadRequested, 0);
			Interlocked.Exchange(ref _reloadAttempts, 0);
			Interlocked.Exchange(ref _watcherRestartRequested, 0);
			Interlocked.Exchange(ref _reloadNotBeforeUtcTicks, 0L);
			Interlocked.Exchange(ref _watcherRestartNotBeforeUtcTicks, 0L);
		}

		internal static bool Tick()
		{
			TryCompleteBiomeRegistryFallback();
			if (!_active)
			{
				return false;
			}
			DateTime utcNow = DateTime.UtcNow;
			if (Volatile.Read(in _watcherRestartRequested) != 0 && utcNow.Ticks >= Interlocked.Read(in _watcherRestartNotBeforeUtcTicks))
			{
				Interlocked.Exchange(ref _watcherRestartRequested, 0);
				try
				{
					StartWatcher();
					RequestReload();
				}
				catch (Exception ex)
				{
					PortalRulesPlugin.PortalRulesLogger.LogError((object)("Failed to restart the admin-portal-biome-global-keys.yml watcher: " + ex.Message));
					Interlocked.Exchange(ref _watcherRestartRequested, 1);
					ScheduleWatcherRestart(WatcherRestartRetryDelay);
				}
			}
			if (_templatePending)
			{
				if (!File.Exists(_filePath))
				{
					if (_biomeRegistryReady && utcNow >= _templateRetryNotBeforeUtc)
					{
						bool hasValidSnapshot = _hasValidSnapshot;
						if (EnsureTemplateReady())
						{
							return !hasValidSnapshot;
						}
						return false;
					}
					return false;
				}
				_templatePending = false;
				ScheduleReload(TimeSpan.Zero);
			}
			if (utcNow >= _nextPollUtc)
			{
				_nextPollUtc = utcNow + PollingInterval;
				if (Volatile.Read(in _reloadRequested) == 0)
				{
					ScheduleReload(TimeSpan.Zero);
				}
			}
			if (Volatile.Read(in _reloadRequested) == 0 || utcNow.Ticks < Interlocked.Read(in _reloadNotBeforeUtcTicks))
			{
				return false;
			}
			Interlocked.Exchange(ref _reloadRequested, 0);
			if (TryReload(logOnlyWhenChanged: true, out var changed))
			{
				Interlocked.Exchange(ref _reloadAttempts, 0);
				return changed;
			}
			if (Interlocked.Increment(ref _reloadAttempts) <= 5)
			{
				ScheduleReload(ReloadRetryDelay);
			}
			return false;
		}

		private static void TryCompleteBiomeRegistryFallback()
		{
			if (!_biomeRegistryReady && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && !((Object)(object)ZoneSystem.instance == (Object)null) && !(Time.realtimeSinceStartup < _registryFallbackNotBefore))
			{
				if (!_registryFallbackLogged)
				{
					_registryFallbackLogged = true;
					PortalRulesPlugin.PortalRulesLogger.LogWarning((object)"The ZoneSystem biome-registry completion hook was not observed; using the currently registered biome names as a fallback.");
				}
				CompleteBiomeRegistryInitialization();
			}
		}

		private static void CompleteBiomeRegistryInitialization()
		{
			BeginServerSession();
			if (!_biomeRegistryReady)
			{
				_biomeRegistryReady = true;
				if (_active)
				{
					EnsureTemplateReady();
				}
				PublicPortalCatalog.RefreshAndBroadcast();
			}
		}

		internal static bool TryResolve(Vector3 position, out string requiredGlobalKey)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			requiredGlobalKey = "";
			if (!_active || !_hasValidSnapshot || WorldGenerator.instance == null)
			{
				return false;
			}
			Biome biome = WorldGenerator.instance.GetBiome(position);
			string name = Enum.GetName(typeof(Biome), biome);
			if (!string.IsNullOrWhiteSpace(name))
			{
				return _defaults.TryGetValue(name.Trim(), out requiredGlobalKey);
			}
			return false;
		}

		private static string BuildRegistryTemplate()
		{
			return BuildRegistryTemplate(Enum.GetNames(typeof(Biome)));
		}

		private static string BuildRegistryTemplate(string[] registeredNames)
		{
			if (registeredNames.Length > 4096)
			{
				throw new InvalidDataException($"biome registry exceeds {4096} entries");
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			for (int i = 0; i < registeredNames.Length; i++)
			{
				string text = (registeredNames[i] ?? "").Trim();
				if (!string.Equals(text, "None", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "All", StringComparison.OrdinalIgnoreCase))
				{
					try
					{
						ValidateBiomeName(text);
					}
					catch (InvalidDataException)
					{
						string value = ((text.Length <= 64) ? text : (text.Substring(0, 64) + "..."));
						PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Skipped invalid registered biome name " + FormatYamlKey(value) + " while generating admin-portal-biome-global-keys.yml."));
						continue;
					}
					if (!dictionary.TryGetValue(text, out var value2) || string.CompareOrdinal(text, value2) < 0)
					{
						dictionary[text] = text;
					}
				}
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("# PortalRules default GlobalKey requirements for newly initialized Admin portals.");
			stringBuilder.AppendLine("# This file is generated once from the server's registered biome names and is");
			stringBuilder.AppendLine("# never automatically edited. Delete it before the next server session to rebuild it.");
			stringBuilder.AppendLine("# Biome names are case-insensitive. Expand World Data custom biomes use the stable");
			stringBuilder.AppendLine("# `biome:` name from its biome YAML, not a translated name or numeric biome value.");
			stringBuilder.AppendLine("# The biome is resolved from each portal's actual world position.");
			stringBuilder.AppendLine("# A RequiredGlobalKey already stored in the portal ZDO wins over this default.");
			stringBuilder.AppendLine("# This includes InfinityHammer blueprint data and EWD objectData/locationObjectData.");
			stringBuilder.AppendLine("# An explicitly stored empty RequiredGlobalKey suppresses the biome default.");
			stringBuilder.AppendLine("# With YouAreNotWorthy, the selected key is checked per character; otherwise it");
			stringBuilder.AppendLine("# is checked against the world's shared GlobalKeys.");
			stringBuilder.AppendLine("# An empty value means that biome has no default required GlobalKey.");
			stringBuilder.AppendLine("# Reloads affect only Admin portals whose initial authority is resolved afterwards.");
			stringBuilder.Append("format_version: ").AppendLine(1.ToString(CultureInfo.InvariantCulture));
			stringBuilder.AppendLine("biomes:");
			KeyValuePair<string, string>[] vanillaTemplate = VanillaTemplate;
			for (int i = 0; i < vanillaTemplate.Length; i++)
			{
				KeyValuePair<string, string> keyValuePair = vanillaTemplate[i];
				dictionary.Remove(keyValuePair.Key);
				AppendBiome(stringBuilder, keyValuePair.Key, keyValuePair.Value);
			}
			List<string> list = new List<string>(dictionary.Values);
			list.Sort(CompareBiomeNames);
			if (VanillaTemplate.Length + list.Count > 4096)
			{
				throw new InvalidDataException($"generated biome list exceeds {4096} entries");
			}
			foreach (string item in list)
			{
				AppendBiome(stringBuilder, item, "");
			}
			string text2 = stringBuilder.ToString();
			if ((long)Encoding.UTF8.GetByteCount(text2) > 524288L)
			{
				throw new InvalidDataException($"generated template exceeds {524288L} bytes");
			}
			return text2;
		}

		private static int CompareBiomeNames(string left, string right)
		{
			int num = StringComparer.OrdinalIgnoreCase.Compare(left, right);
			if (num == 0)
			{
				return StringComparer.Ordinal.Compare(left, right);
			}
			return num;
		}

		private static void AppendBiome(StringBuilder yaml, string biomeName, string requiredGlobalKey)
		{
			yaml.Append("  ").Append(FormatYamlKey(biomeName)).Append(':');
			if (requiredGlobalKey.Length != 0)
			{
				yaml.Append(' ').Append(requiredGlobalKey);
			}
			yaml.AppendLine();
		}

		private static string FormatYamlKey(string value)
		{
			if (IsSafePlainYamlKey(value))
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length + 2);
			stringBuilder.Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\u2028':
				case '\u2029':
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("X4", CultureInfo.InvariantCulture));
					continue;
				}
				}
				if (char.IsControl(c))
				{
					StringBuilder stringBuilder3 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder3.Append(num.ToString("X4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}

		private static bool IsSafePlainYamlKey(string value)
		{
			if (value.Length == 0 || AmbiguousPlainYamlScalars.Contains(value) || (!char.IsLetter(value[0]) && value[0] != '_'))
			{
				return false;
			}
			foreach (char c in value)
			{
				if (!char.IsLetterOrDigit(c) && c != '_' && c != '-')
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryCreateFileWithoutOverwrite(string path, string content)
		{
			string text = Path.Combine(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Configuration directory is unavailable."), "." + Path.GetFileName(path) + "." + Guid.NewGuid().ToString("N") + ".pending");
			try
			{
				PortalAccountStore.WriteAtomicFile(text, content, null);
				try
				{
					File.Move(text, path);
					return true;
				}
				catch (IOException) when (File.Exists(path))
				{
					return false;
				}
			}
			finally
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
			}
		}

		private static bool TryReloadImmediately()
		{
			Interlocked.Exchange(ref _reloadRequested, 0);
			Interlocked.Exchange(ref _reloadAttempts, 0);
			if (TryReload(logOnlyWhenChanged: false, out var _))
			{
				_nextPollUtc = DateTime.UtcNow + PollingInterval;
				return true;
			}
			ScheduleReload(ReloadRetryDelay);
			return false;
		}

		private static bool TryReload(bool logOnlyWhenChanged, out bool changed)
		{
			changed = false;
			if (!File.Exists(_filePath))
			{
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)"admin-portal-biome-global-keys.yml is missing; retaining the last valid biome defaults. Use 'biomes: {}' to clear them.");
				return false;
			}
			try
			{
				if (new FileInfo(_filePath).Length > 524288)
				{
					throw new InvalidDataException($"file exceeds {524288L} bytes");
				}
				BiomeDefaultsYaml biomeDefaultsYaml = Deserializer.Deserialize<BiomeDefaultsYaml>(File.ReadAllText(_filePath));
				if (biomeDefaultsYaml == null || biomeDefaultsYaml.FormatVersion != 1 || biomeDefaultsYaml.Biomes == null)
				{
					throw new InvalidDataException($"format_version must be {1} and biomes must be present");
				}
				if (biomeDefaultsYaml.Biomes.Count > 4096)
				{
					throw new InvalidDataException($"biomes exceeds {4096} entries");
				}
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, string> biome in biomeDefaultsYaml.Biomes)
				{
					string text = (biome.Key ?? "").Trim();
					ValidateBiomeName(text);
					if (!PublicPortalData.TryNormalizeRequiredGlobalKey(biome.Value, out string normalized, out RequiredGlobalKeyValidationFailure failure))
					{
						throw new InvalidDataException($"GlobalKey for biome '{text}' is invalid ({failure})");
					}
					if (dictionary.ContainsKey(text))
					{
						throw new InvalidDataException("biome '" + text + "' is duplicated after case and whitespace normalization");
					}
					dictionary.Add(text, normalized);
				}
				changed = !_hasValidSnapshot || !DictionariesEqual(_defaults, dictionary);
				_defaults = dictionary;
				_hasValidSnapshot = true;
				if (!logOnlyWhenChanged | changed)
				{
					PortalRulesPlugin.PortalRulesLogger.LogInfo((object)$"Loaded {_defaults.Count} Admin portal biome GlobalKey default(s).");
				}
				return true;
			}
			catch (Exception ex)
			{
				PortalRulesPlugin.PortalRulesLogger.LogError((object)("Failed to reload admin-portal-biome-global-keys.yml; retaining the last valid biome defaults: " + ex.Message));
				return false;
			}
		}

		private static void ValidateBiomeName(string biomeName)
		{
			if (biomeName.Length == 0)
			{
				throw new InvalidDataException("biome names must not be empty");
			}
			if (biomeName.Length > 128)
			{
				throw new InvalidDataException($"biome name '{biomeName}' exceeds {128} characters");
			}
			for (int i = 0; i < biomeName.Length; i++)
			{
				if (char.IsControl(biomeName[i]))
				{
					throw new InvalidDataException("biome name '" + biomeName + "' contains control characters");
				}
			}
			if (long.TryParse(biomeName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var _))
			{
				throw new InvalidDataException("biome '" + biomeName + "' is numeric; use its stable biome name instead");
			}
		}

		private static bool DictionariesEqual(Dictionary<string, string> left, Dictionary<string, string> right)
		{
			if (left.Count != right.Count)
			{
				return false;
			}
			foreach (KeyValuePair<string, string> item in left)
			{
				if (!right.TryGetValue(item.Key, out string value) || !string.Equals(value, item.Value, StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		private static void StartWatcher()
		{
			StopWatcher();
			_watcher = new FileSystemWatcher(_configurationDirectory, "admin-portal-biome-global-keys.yml")
			{
				IncludeSubdirectories = false,
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite)
			};
			_watcher.Changed += OnFileChanged;
			_watcher.Created += OnFileChanged;
			_watcher.Deleted += OnFileChanged;
			_watcher.Renamed += OnFileRenamed;
			_watcher.Error += OnWatcherError;
			_watcher.EnableRaisingEvents = true;
		}

		private static void StopWatcher()
		{
			if (_watcher != null)
			{
				_watcher.EnableRaisingEvents = false;
				_watcher.Changed -= OnFileChanged;
				_watcher.Created -= OnFileChanged;
				_watcher.Deleted -= OnFileChanged;
				_watcher.Renamed -= OnFileRenamed;
				_watcher.Error -= OnWatcherError;
				_watcher.Dispose();
				_watcher = null;
			}
		}

		private static void OnFileChanged(object sender, FileSystemEventArgs args)
		{
			RequestReload();
		}

		private static void OnFileRenamed(object sender, RenamedEventArgs args)
		{
			RequestReload();
		}

		private static void OnWatcherError(object sender, ErrorEventArgs args)
		{
			PortalRulesPlugin.PortalRulesLogger.LogError((object)("The admin-portal-biome-global-keys.yml watcher failed and will be restarted: " + args.GetException().Message));
			Interlocked.Exchange(ref _watcherRestartRequested, 1);
			ScheduleWatcherRestart(WatcherRestartRetryDelay);
			RequestReload();
		}

		private static void RequestReload()
		{
			Interlocked.Exchange(ref _reloadAttempts, 0);
			ScheduleReload(ReloadDebounce);
		}

		private static void ScheduleReload(TimeSpan delay)
		{
			Interlocked.Exchange(ref _reloadNotBeforeUtcTicks, (DateTime.UtcNow + delay).Ticks);
			Interlocked.Exchange(ref _reloadRequested, 1);
		}

		private static void ScheduleWatcherRestart(TimeSpan delay)
		{
			Interlocked.Exchange(ref _watcherRestartNotBeforeUtcTicks, (DateTime.UtcNow + delay).Ticks);
		}
	}
	[HarmonyPatch]
	internal static class AdminPortalPrefabManager
	{
		[HarmonyPatch(typeof(Game), "Awake")]
		private static class GameAwakePatch
		{
			private static void Postfix(Game __instance)
			{
				RegisterPortalHashes(__instance);
			}
		}

		[HarmonyPatch(typeof(TeleportWorld), "UpdatePortal")]
		private static class ShowAdminPortalWhirlingEffectNearbyPatch
		{
			[HarmonyPriority(0)]
			private static void Postfix(TeleportWorld __instance)
			{
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				ZDO portalZdo = PublicPortalKinds.GetPortalZdo(__instance);
				if (portalZdo != null && PublicPortalKinds.IsAdminPortalPrefab(portalZdo.GetPrefab()))
				{
					EffectFade target_found = __instance.m_target_found;
					if ((Object)(object)target_found != (Object)null)
					{
						Player localPlayer = Player.m_localPlayer;
						Transform val = (((Object)(object)__instance.m_proximityRoot != (Object)null) ? __instance.m_proximityRoot : ((Component)__instance).transform);
						float num = Mathf.Max(0f, __instance.m_activationRange);
						bool active = (Object)(object)localPlayer != (Object)null && (Object)(object)Player.GetClosestPlayer(val.position, num) != (Object)null;
						target_found.SetActive(active);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Piece), "Awake")]
		private static class ClearAdminPortalPieceCreatorPatch
		{
			private static void Postfix(Piece __instance, ref long ___m_creator)
			{
				if (IsAdminPortalPiece(__instance))
				{
					___m_creator = 0L;
					if (!ZNetView.m_forceDisableInit)
					{
						DisablePlacedPortalSolidColliders(((Component)__instance).gameObject);
						HidePlacedPortalMeshes(((Component)__instance).gameObject);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Piece), "SetCreator")]
		private static class BlockAdminPortalPieceCreatorPatch
		{
			private static bool Prefix(Piece __instance)
			{
				return !IsAdminPortalPiece(__instance);
			}
		}

		[HarmonyPatch(typeof(PieceTable), "UpdateAvailable")]
		private static class AdminPortalBuildMenuVisibilityPatch
		{
			[HarmonyPriority(0)]
			private static void Postfix(PieceTable __instance, Player player, List<List<Piece>> ___m_availablePieces)
			{
				foreach (List<Piece> ___m_availablePiece in ___m_availablePieces)
				{
					___m_availablePiece.RemoveAll(IsAdminPortalPiece);
				}
				if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !PortalRulesPlugin.HasAdminDebugAccess)
				{
					return;
				}
				int num = 0;
				if (num < 0 || num >= ___m_availablePieces.Count)
				{
					return;
				}
				List<Piece> list = ___m_availablePieces[num];
				foreach (GameObject piece in __instance.m_pieces)
				{
					Piece val = (((Object)(object)piece != (Object)null) ? piece.GetComponent<Piece>() : null);
					if ((Object)(object)val != (Object)null && IsAdminPortalPiece(val) && !list.Contains(val))
					{
						list.Add(val);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Player), "TryPlacePiece")]
		private static class AdminPortalPlacementAccessPatch
		{
			private static bool Prefix(Player __instance, Piece piece, ref bool __result)
			{
				if (!IsAdminPortalPiece(piece))
				{
					return true;
				}
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && PortalRulesPlugin.HasAdminDebugAccess)
				{
					return true;
				}
				if (__instance != null)
				{
					((Character)__instance).Message((MessageType)2, new PortalRulesMessage("$sighsorry_portalrules_admin_portal_placement_requires_admin_debug").Localize(), 0, (Sprite)null);
				}
				__result = false;
				return false;
			}
		}

		private readonly struct PortalCloneSpec
		{
			internal readonly string SourcePrefabName;

			internal readonly string TargetPrefabName;

			internal readonly string DisplayName;

			internal PortalCloneSpec(string sourcePrefabName, string targetPrefabName, string displayName)
			{
				SourcePrefabName = sourcePrefabName;
				TargetPrefabName = targetPrefabName;
				DisplayName = displayName;
			}
		}

		private static readonly PortalCloneSpec[] PortalCloneSpecs = new PortalCloneSpec[2]
		{
			new PortalCloneSpec("portal_wood", "admin_portal_wood", "$sighsorry_portalrules_admin_wood_portal_name"),
			new PortalCloneSpec("portal_stone", "admin_portal_stone", "$sighsorry_portalrules_admin_stone_portal_name")
		};

		private static bool _registered;

		private static Player? _lastBuildMenuPlayer;

		private static bool _lastBuildMenuAccess;

		private static bool _buildMenuAccessInitialized;

		private static readonly MethodInfo? UpdateAvailablePiecesMethod = AccessTools.DeclaredMethod(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null);

		internal static void Initialize()
		{
			PrefabManager.OnVanillaPrefabsAvailable += RegisterAdminPortals;
			PieceManager.OnPiecesRegistered += RegisterAdminPortalPieces;
		}

		internal static void Shutdown()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= RegisterAdminPortals;
			PieceManager.OnPiecesRegistered -= RegisterAdminPortalPieces;
			_lastBuildMenuPlayer = null;
			_lastBuildMenuAccess = false;
			_buildMenuAccessInitialized = false;
		}

		internal static void Tick()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!_registered || (Object)(object)localPlayer == (Object)null)
			{
				_lastBuildMenuPlayer = null;
				_buildMenuAccessInitialized = false;
				return;
			}
			bool hasAdminDebugAccess = PortalRulesPlugin.HasAdminDebugAccess;
			if (_buildMenuAccessInitialized && _lastBuildMenuPlayer == localPlayer && _lastBuildMenuAccess == hasAdminDebugAccess)
			{
				return;
			}
			_lastBuildMenuPlayer = localPlayer;
			_lastBuildMenuAccess = hasAdminDebugAccess;
			_buildMenuAccessInitialized = true;
			if (!((Character)localPlayer).InPlaceMode())
			{
				return;
			}
			if (UpdateAvailablePiecesMethod == null)
			{
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)"Could not refresh the Hammer piece list after admin build access changed.");
				return;
			}
			try
			{
				UpdateAvailablePiecesMethod.Invoke(localPlayer, Array.Empty<object>());
			}
			catch (Exception ex)
			{
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Could not refresh the Hammer piece list after admin build access changed: " + ex.GetBaseException().Message));
			}
		}

		private static void RegisterAdminPortals()
		{
			if (_registered)
			{
				PrefabManager.OnVanillaPrefabsAvailable -= RegisterAdminPortals;
				return;
			}
			int num = 0;
			PortalCloneSpec[] portalCloneSpecs = PortalCloneSpecs;
			for (int i = 0; i < portalCloneSpecs.Length; i++)
			{
				PortalCloneSpec portalCloneSpec = portalCloneSpecs[i];
				GameObject prefab = PrefabManager.Instance.GetPrefab(portalCloneSpec.TargetPrefabName);
				if ((Object)(object)prefab != (Object)null)
				{
					num++;
					continue;
				}
				GameObject prefab2 = PrefabManager.Instance.GetPrefab(portalCloneSpec.SourcePrefabName);
				Piece sourcePiece = ((prefab2 != null) ? prefab2.GetComponent<Piece>() : null);
				prefab = PrefabManager.Instance.CreateClonedPrefab(portalCloneSpec.TargetPrefabName, portalCloneSpec.SourcePrefabName);
				if ((Object)(object)prefab == (Object)null)
				{
					PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Could not clone '" + portalCloneSpec.SourcePrefabName + "' into '" + portalCloneSpec.TargetPrefabName + "'."));
				}
				else
				{
					PrepareAdminPortal(prefab, sourcePiece, portalCloneSpec.DisplayName);
					PrefabManager.Instance.AddPrefab(prefab);
					num++;
				}
			}
			if (num == PortalCloneSpecs.Length)
			{
				_registered = true;
				PrefabManager.OnVanillaPrefabsAvailable -= RegisterAdminPortals;
				PortalRulesPlugin.PortalRulesLogger.LogInfo((object)"Registered admin portal prefabs: admin_portal_wood, admin_portal_stone.");
			}
		}

		private static void RegisterAdminPortalPieces()
		{
			int num = 0;
			PortalCloneSpec[] portalCloneSpecs = PortalCloneSpecs;
			for (int i = 0; i < portalCloneSpecs.Length; i++)
			{
				PortalCloneSpec portalCloneSpec = portalCloneSpecs[i];
				GameObject prefab = PrefabManager.Instance.GetPrefab(portalCloneSpec.TargetPrefabName);
				if ((Object)(object)prefab == (Object)null)
				{
					PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Could not find admin portal '" + portalCloneSpec.TargetPrefabName + "' for Hammer registration."));
					continue;
				}
				try
				{
					PieceManager.Instance.RegisterPieceInPieceTable(prefab, PieceTables.Hammer, PieceCategories.Misc);
					num++;
				}
				catch (Exception ex)
				{
					PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Could not add admin portal '" + portalCloneSpec.TargetPrefabName + "' to the Hammer piece table: " + ex.Message));
				}
			}
			if (num == PortalCloneSpecs.Length)
			{
				PortalRulesPlugin.PortalRulesLogger.LogInfo((object)"Registered admin portal pieces in the Hammer Misc category.");
			}
		}

		private static void PrepareAdminPortal(GameObject prefab, Piece? sourcePiece, string displayName)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			PrepareAdminPiece(prefab, sourcePiece, displayName);
			RemoveRootComponent<WearNTear>(prefab);
			TeleportWorld component = prefab.GetComponent<TeleportWorld>();
			if ((Object)(object)component != (Object)null)
			{
				((Behaviour)component).enabled = true;
				component.m_allowAllItems = true;
				component.m_proximityRoot = (((Object)(object)component.m_proximityRoot != (Object)null) ? component.m_proximityRoot : prefab.transform);
				EnsureTargetFoundEffect(prefab, component);
				EnsureModelRenderer(prefab, component);
			}
			ZNetView component2 = prefab.GetComponent<ZNetView>();
			if ((Object)(object)component2 != (Object)null)
			{
				component2.m_persistent = true;
				component2.m_distant = false;
				component2.m_type = (ObjectType)2;
			}
			EnsureInteractionTrigger(prefab);
		}

		private static void PrepareAdminPiece(GameObject prefab, Piece? sourcePiece, string displayName)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			Piece component = prefab.GetComponent<Piece>();
			if ((Object)(object)component == (Object)null)
			{
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Admin portal '" + ((Object)prefab).name + "' has no Piece metadata; its icon cannot be displayed."));
				return;
			}
			if ((Object)(object)sourcePiece?.m_icon != (Object)null)
			{
				component.m_icon = sourcePiece.m_icon;
			}
			else
			{
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Admin portal '" + ((Object)prefab).name + "' could not reuse its source portal icon."));
			}
			component.m_name = displayName;
			component.m_description = "$sighsorry_portalrules_admin_portal_description";
			component.m_category = (PieceCategory)0;
			component.m_enabled = false;
			component.m_canBeRemoved = false;
			component.m_craftingStation = null;
			component.m_resources = Array.Empty<Requirement>();
			component.m_destroyedLootPrefab = null;
			component.m_comfort = 0;
			component.m_comfortGroup = (ComfortGroup)0;
			component.m_comfortObject = null;
			component.m_harvest = false;
			((StaticTarget)component).m_primaryTarget = false;
			((StaticTarget)component).m_randomTarget = false;
			component.m_targetNonPlayerBuilt = false;
		}

		private static bool IsAdminPortalPiece(Piece piece)
		{
			if ((Object)(object)piece == (Object)null)
			{
				return false;
			}
			string prefabName = Utils.GetPrefabName(((Component)piece).gameObject);
			if (!(prefabName == "admin_portal_wood"))
			{
				return prefabName == "admin_portal_stone";
			}
			return true;
		}

		private static void EnsureInteractionTrigger(GameObject prefab)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)prefab.transform.Find("ADMIN_INTERACT") != (Object)null))
			{
				Collider val = FindTeleportCollider(prefab);
				Transform val2 = (((Object)(object)val != (Object)null) ? ((Component)val).transform : prefab.transform);
				GameObject val3 = new GameObject("ADMIN_INTERACT")
				{
					layer = LayerOrFallback("piece_nonsolid", prefab.layer)
				};
				val3.transform.SetParent(prefab.transform, false);
				val3.transform.localPosition = val2.localPosition;
				val3.transform.localRotation = val2.localRotation;
				val3.transform.localScale = val2.localScale;
				BoxCollider val4 = val3.AddComponent<BoxCollider>();
				((Collider)val4).isTrigger = true;
				BoxCollider val5 = (BoxCollider)(object)((val is BoxCollider) ? val : null);
				if (val5 != null)
				{
					val4.center = val5.center;
					val4.size = val5.size;
				}
				else
				{
					val4.center = new Vector3(0f, 1.5f, 0f);
					val4.size = new Vector3(2f, 3f, 0.5f);
				}
			}
		}

		private static Collider FindTeleportCollider(GameObject prefab)
		{
			Collider[] componentsInChildren = prefab.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if ((Object)(object)((Component)val).GetComponent<TeleportWorldTrigger>() != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		private static void DisablePlacedPortalSolidColliders(GameObject portal)
		{
			Collider[] componentsInChildren = portal.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if (!val.isTrigger)
				{
					val.enabled = false;
				}
			}
		}

		private static void HidePlacedPortalMeshes(GameObject portal)
		{
			TeleportWorld component = portal.GetComponent<TeleportWorld>();
			object obj;
			if (component == null)
			{
				obj = null;
			}
			else
			{
				EffectFade target_found = component.m_target_found;
				obj = ((target_found != null) ? ((Component)target_found).transform : null);
			}
			Transform val = (Transform)obj;
			MeshRenderer[] componentsInChildren = portal.GetComponentsInChildren<MeshRenderer>(true);
			foreach (MeshRenderer val2 in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null || !((Component)val2).transform.IsChildOf(val))
				{
					((Renderer)val2).enabled = false;
				}
			}
			SkinnedMeshRenderer[] componentsInChildren2 = portal.GetComponentsInChildren<SkinnedMeshRenderer>(true);
			foreach (SkinnedMeshRenderer val3 in componentsInChildren2)
			{
				if ((Object)(object)val == (Object)null || !((Component)val3).transform.IsChildOf(val))
				{
					((Renderer)val3).enabled = false;
				}
			}
		}

		private static void EnsureTargetFoundEffect(GameObject prefab, TeleportWorld teleportWorld)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			if (!((Object)(object)teleportWorld.m_target_found != (Object)null))
			{
				GameObject val = new GameObject("ADMIN_TARGET_FOUND_DUMMY");
				val.transform.SetParent(prefab.transform, false);
				teleportWorld.m_target_found = val.AddComponent<EffectFade>();
			}
		}

		private static void EnsureModelRenderer(GameObject prefab, TeleportWorld teleportWorld)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)teleportWorld.m_model != (Object)null))
			{
				MeshRenderer val = prefab.GetComponentInChildren<MeshRenderer>(true);
				if ((Object)(object)val == (Object)null)
				{
					GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3);
					((Object)obj).name = "ADMIN_MODEL_DUMMY";
					obj.transform.SetParent(prefab.transform, false);
					obj.transform.localScale = Vector3.one * 0.1f;
					RemoveRootComponent<Collider>(obj);
					val = obj.GetComponent<MeshRenderer>();
				}
				teleportWorld.m_model = val;
			}
		}

		private static int LayerOrFallback(string layerName, int fallback)
		{
			int num = LayerMask.NameToLayer(layerName);
			if (num < 0)
			{
				return fallback;
			}
			return num;
		}

		private static void RemoveRootComponent<T>(GameObject gameObject) where T : Component
		{
			T component = gameObject.GetComponent<T>();
			if ((Object)(object)component != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component);
			}
		}

		internal static void RegisterPortalHashes(Game game)
		{
			PortalCloneSpec[] portalCloneSpecs = PortalCloneSpecs;
			for (int i = 0; i < portalCloneSpecs.Length; i++)
			{
				int stableHashCode = StringExtensionMethods.GetStableHashCode(portalCloneSpecs[i].TargetPrefabName);
				if (!game.PortalPrefabHash.Contains(stableHashCode))
				{
					game.PortalPrefabHash.Add(stableHashCode);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class AdminPortalOperations
	{
		private sealed class RequiredGlobalKeyTextReceiver : TextReceiver
		{
			private readonly ZDOID _portalId;

			private readonly string _currentValue;

			internal RequiredGlobalKeyTextReceiver(ZDOID portalId, string currentValue)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				_portalId = portalId;
				_currentValue = currentValue ?? "";
			}

			public string GetText()
			{
				return _currentValue;
			}

			public void SetText(string text)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				RequestRequiredGlobalKeyChange(_portalId, text);
			}
		}

		[HarmonyPatch(typeof(TeleportWorld), "SetText")]
		private static class AdminPortalSetTextPatch
		{
			private static bool Prefix(TeleportWorld __instance, string text)
			{
				ZDO portalZdo = PublicPortalKinds.GetPortalZdo(__instance);
				if (portalZdo == null || !PublicPortalInteraction.IsAdminPortal(portalZdo))
				{
					return true;
				}
				RequestTagChange(__instance, text);
				return false;
			}
		}

		[HarmonyPatch(typeof(TeleportWorld), "RPC_SetTag", new Type[]
		{
			typeof(long),
			typeof(string),
			typeof(string)
		})]
		private static class BlockVanillaAdminPortalTagRpcPatch
		{
			private static bool Prefix(TeleportWorld __instance)
			{
				ZDO portalZdo = PublicPortalKinds.GetPortalZdo(__instance);
				if (portalZdo != null)
				{
					return !PublicPortalInteraction.IsAdminPortal(portalZdo);
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(Player), "RemovePiece")]
		private static class AdminPortalRemovalPatch
		{
			private static bool Prefix(Player __instance, ref bool __result)
			{
				if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || !TryGetAimedAdminPortal(__instance, out ZDO zdo))
				{
					return true;
				}
				if (!PortalRulesPlugin.HasAdminDebugAccess)
				{
					ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_admin_portal_dismantling_requires_admin_debug"));
					__result = false;
					return false;
				}
				RequestRemoval(zdo);
				__result = false;
				return false;
			}

			private static bool TryGetAimedAdminPortal(Player player, out ZDO zdo)
			{
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				zdo = null;
				RaycastHit val = default(RaycastHit);
				if ((Object)(object)GameCamera.instance == (Object)null || !Physics.Raycast(((Component)GameCamera.instance).transform.position, ((Component)GameCamera.instance).transform.forward, ref val, 50f, AdminRemoveRayMask, (QueryTriggerInteraction)2) || Vector3.Distance(((RaycastHit)(ref val)).point, ((Character)player).GetEyePoint()) >= player.m_maxPlaceDistance)
				{
					return false;
				}
				TeleportWorld componentInParent = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<TeleportWorld>();
				ZDO val2 = (((Object)(object)componentInParent != (Object)null) ? PublicPortalKinds.GetPortalZdo(componentInParent) : null);
				if (val2 == null || !PublicPortalInteraction.IsAdminPortal(val2))
				{
					return false;
				}
				zdo = val2;
				return true;
			}
		}

		private const float AdminOperationCooldownSeconds = 0.25f;

		private const int MaximumPortalTagLength = 10;

		private static readonly Dictionary<ZRpc, float> LastAdminOperationAt = new Dictionary<ZRpc, float>();

		private static readonly int AdminRemoveRayMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid", "terrain", "vehicle" });

		internal static void Shutdown()
		{
			LastAdminOperationAt.Clear();
		}

		internal static void RegisterPeer(ZNet znet, ZNetPeer peer)
		{
			if (!((Object)(object)znet == (Object)null) && peer?.m_rpc != null && znet.IsServer())
			{
				peer.m_rpc.Register<ZDOID, string, bool>("sighsorry.PortalRules.ChangeAdminPortalTag.v1", (Action<ZRpc, ZDOID, string, bool>)OnRemoteTagChange);
				peer.m_rpc.Register<ZDOID, string, bool>("sighsorry.PortalRules.ChangeAdminPortalRequiredGlobalKey.v1", (Action<ZRpc, ZDOID, string, bool>)OnRemoteRequiredGlobalKeyChange);
				peer.m_rpc.Register<ZDOID, bool>("sighsorry.PortalRules.RemoveAdminPortal.v1", (Action<ZRpc, ZDOID, bool>)OnRemoteRemoval);
			}
		}

		internal static void ForgetPeer(ZRpc rpc)
		{
			if (rpc != null)
			{
				LastAdminOperationAt.Remove(rpc);
			}
		}

		private static void RequestTagChange(TeleportWorld portal, string tag)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			ZDO portalZdo = PublicPortalKinds.GetPortalZdo(portal);
			if (portalZdo == null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_admin_portal_unavailable"));
				return;
			}
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_server_unavailable"));
				return;
			}
			if (ZNet.instance.IsServer())
			{
				TryApplyTagChange(portalZdo.m_uid, tag, null, PortalRulesPlugin.IsAdmin, Player.m_debugMode, out var message);
				ShowOperationMessage(message);
				return;
			}
			ZRpc serverRPC = ZNet.instance.GetServerRPC();
			if (serverRPC == null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_server_connection_unavailable"));
				return;
			}
			serverRPC.Invoke("sighsorry.PortalRules.ChangeAdminPortalTag.v1", new object[3]
			{
				portalZdo.m_uid,
				tag ?? "",
				Player.m_debugMode
			});
		}

		private static void RequestRemoval(ZDO zdo)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_server_unavailable"));
				return;
			}
			if (ZNet.instance.IsServer())
			{
				TryApplyRemoval(zdo.m_uid, null, PortalRulesPlugin.IsAdmin, Player.m_debugMode, out var message);
				ShowOperationMessage(message);
				return;
			}
			ZRpc serverRPC = ZNet.instance.GetServerRPC();
			if (serverRPC == null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_server_connection_unavailable"));
				return;
			}
			serverRPC.Invoke("sighsorry.PortalRules.RemoveAdminPortal.v1", new object[2]
			{
				zdo.m_uid,
				Player.m_debugMode
			});
		}

		internal static void OpenRequiredGlobalKeyInput(TeleportWorld portal)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			ZDO portalZdo = PublicPortalKinds.GetPortalZdo(portal);
			if (portalZdo == null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_admin_portal_unavailable"));
				return;
			}
			TextInput instance = TextInput.instance;
			if ((Object)(object)instance == (Object)null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_text_input_unavailable"));
			}
			else
			{
				instance.RequestText((TextReceiver)(object)new RequiredGlobalKeyTextReceiver(portalZdo.m_uid, PublicPortalCatalog.GetEffectiveRequiredGlobalKey(portalZdo)), new PortalRulesMessage("$sighsorry_portalrules_required_global_key_input_title").Localize(), 128);
			}
		}

		private static void RequestRequiredGlobalKeyChange(ZDOID portalId, string requiredGlobalKey)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_server_unavailable"));
				return;
			}
			if (ZNet.instance.IsServer())
			{
				TryApplyRequiredGlobalKeyChange(portalId, requiredGlobalKey, null, PortalRulesPlugin.IsAdmin, Player.m_debugMode, out var message);
				ShowOperationMessage(message);
				return;
			}
			ZRpc serverRPC = ZNet.instance.GetServerRPC();
			if (serverRPC == null)
			{
				ShowOperationMessage(new PortalRulesMessage("$sighsorry_portalrules_server_connection_unavailable"));
				return;
			}
			serverRPC.Invoke("sighsorry.PortalRules.ChangeAdminPortalRequiredGlobalKey.v1", new object[3]
			{
				portalId,
				requiredGlobalKey ?? "",
				Player.m_debugMode
			});
		}

		private static void OnRemoteTagChange(ZRpc rpc, ZDOID portalId, string tag, bool debugEnabled)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (!TryResolveRemoteAdminOperation(rpc, out ZNetPeer peer, out bool requesterIsAdmin, out PortalRulesMessage rejection))
			{
				if (peer != null)
				{
					PublicPortalServerPolicy.SendPolicyMessage(peer, rejection);
				}
			}
			else
			{
				LogRejectedOperation(TryApplyTagChange(portalId, tag, peer, requesterIsAdmin, debugEnabled, out var message), peer, portalId, "tag change", message);
				PublicPortalServerPolicy.SendPolicyMessage(peer, message);
			}
		}

		private static void OnRemoteRemoval(ZRpc rpc, ZDOID portalId, bool debugEnabled)
		{
			//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)
			if (!TryResolveRemoteAdminOperation(rpc, out ZNetPeer peer, out bool requesterIsAdmin, out PortalRulesMessage rejection))
			{
				if (peer != null)
				{
					PublicPortalServerPolicy.SendPolicyMessage(peer, rejection);
				}
			}
			else
			{
				LogRejectedOperation(TryApplyRemoval(portalId, peer, requesterIsAdmin, debugEnabled, out var message), peer, portalId, "removal", message);
				PublicPortalServerPolicy.SendPolicyMessage(peer, message);
			}
		}

		private static void OnRemoteRequiredGlobalKeyChange(ZRpc rpc, ZDOID portalId, string requiredGlobalKey, bool debugEnabled)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (!TryResolveRemoteAdminOperation(rpc, out ZNetPeer peer, out bool requesterIsAdmin, out PortalRulesMessage rejection))
			{
				if (peer != null)
				{
					PublicPortalServerPolicy.SendPolicyMessage(peer, rejection);
				}
			}
			else
			{
				LogRejectedOperation(TryApplyRequiredGlobalKeyChange(portalId, requiredGlobalKey, peer, requesterIsAdmin, debugEnabled, out var message), peer, portalId, "Required GlobalKey change", message);
				PublicPortalServerPolicy.SendPolicyMessage(peer, message);
			}
		}

		private static bool TryResolveRemoteAdminOperation(ZRpc rpc, out ZNetPeer? peer, out bool requesterIsAdmin, out PortalRulesMessage rejection)
		{
			peer = null;
			requesterIsAdmin = false;
			rejection = new PortalRulesMessage("$sighsorry_portalrules_admin_request_unauthenticated");
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return false;
			}
			peer = PublicPortalData.FindPeer(instance, rpc);
			if (peer == null || !peer.IsReady())
			{
				return false;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (LastAdminOperationAt.TryGetValue(rpc, out var value) && realtimeSinceStartup - value < 0.25f)
			{
				rejection = new PortalRulesMessage("$sighsorry_portalrules_admin_change_rate_limited");
				return false;
			}
			LastAdminOperationAt[rpc] = realtimeSinceStartup;
			requesterIsAdmin = PublicPortalData.IsPeerAdmin(instance, peer);
			return true;
		}

		private static bool TryApplyTagChange(ZDOID portalId, string tag, ZNetPeer? requesterPeer, bool requesterIsAdmin, bool debugEnabled, out PortalRulesMessage message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!TryGetAuthorizedAdminPortal(portalId, requesterPeer, requesterIsAdmin, debugEnabled, out ZDO zdo, out message))
			{
				return false;
			}
			if (tag == null)
			{
				tag = "";
			}
			if (tag.Length > 10)
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_admin_tag_length_limit", 10.ToString(CultureInfo.InvariantCulture));
				return false;
			}
			if (!PublicPortalCatalog.SetAuthoritativeAdminPortalTag(zdo, tag))
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_admin_tag_update_failed");
				return false;
			}
			message = new PortalRulesMessage("$sighsorry_portalrules_admin_tag_updated");
			return true;
		}

		private static bool TryApplyRemoval(ZDOID portalId, ZNetPeer? requesterPeer, bool requesterIsAdmin, bool debugEnabled, out PortalRulesMessage message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!TryGetAuthorizedAdminPortal(portalId, requesterPeer, requesterIsAdmin, debugEnabled, out ZDO zdo, out message))
			{
				return false;
			}
			if (!PublicPortalServerPolicy.QueueAdminPortalRemoval(zdo))
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_admin_portal_removal_unavailable");
				return false;
			}
			message = new PortalRulesMessage("$sighsorry_portalrules_admin_portal_dismantling_queued");
			return true;
		}

		private static bool TryApplyRequiredGlobalKeyChange(ZDOID portalId, string requiredGlobalKey, ZNetPeer? requesterPeer, bool requesterIsAdmin, bool debugEnabled, out PortalRulesMessage message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!TryGetAuthorizedAdminPortal(portalId, requesterPeer, requesterIsAdmin, debugEnabled, out ZDO zdo, out message))
			{
				return false;
			}
			if (!TryNormalizeRequiredGlobalKeyMessage(requiredGlobalKey, out string normalizedRequiredGlobalKey, out message))
			{
				return false;
			}
			if (normalizedRequiredGlobalKey.Length != 0 && RequiredGlobalKeyAccess.YouAreNotWorthyInstalled)
			{
				switch ((requesterPeer != null) ? RequiredGlobalKeyAccess.QueryPeer(requesterPeer, normalizedRequiredGlobalKey) : RequiredGlobalKeyAccess.QueryLocal(normalizedRequiredGlobalKey))
				{
				case RequiredGlobalKeyQueryResult.Invalid:
					message = new PortalRulesMessage("$sighsorry_portalrules_ynw_required_global_key_rejected");
					return false;
				case RequiredGlobalKeyQueryResult.Unavailable:
					message = new PortalRulesMessage("$sighsorry_portalrules_ynw_key_api_unavailable");
					return false;
				}
			}
			if (!PublicPortalCatalog.SetAuthoritativeAdminPortalRequiredGlobalKey(zdo, normalizedRequiredGlobalKey))
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_admin_required_global_key_update_failed");
				return false;
			}
			message = ((normalizedRequiredGlobalKey.Length == 0) ? new PortalRulesMessage("$sighsorry_portalrules_admin_required_global_key_cleared") : new PortalRulesMessage("$sighsorry_portalrules_admin_required_global_key_updated"));
			return true;
		}

		private static bool TryNormalizeRequiredGlobalKeyMessage(string? requiredGlobalKey, out string normalizedRequiredGlobalKey, out PortalRulesMessage message)
		{
			if (PublicPortalData.TryNormalizeRequiredGlobalKey(requiredGlobalKey, out normalizedRequiredGlobalKey, out var failure))
			{
				message = PortalRulesMessage.Empty;
				return true;
			}
			switch (failure)
			{
			case RequiredGlobalKeyValidationFailure.TooLong:
				message = new PortalRulesMessage("$sighsorry_portalrules_required_global_key_length_limit", 128.ToString(CultureInfo.InvariantCulture));
				break;
			case RequiredGlobalKeyValidationFailure.ControlCharacters:
				message = new PortalRulesMessage("$sighsorry_portalrules_required_global_key_control_characters");
				break;
			case RequiredGlobalKeyValidationFailure.NumericKey:
				message = new PortalRulesMessage("$sighsorry_portalrules_required_global_key_numeric_invalid");
				break;
			case RequiredGlobalKeyValidationFailure.ReservedKey:
				message = new PortalRulesMessage("$sighsorry_portalrules_required_global_key_not_allowed");
				break;
			default:
				message = new PortalRulesMessage("$sighsorry_portalrules_admin_required_global_key_update_failed");
				break;
			}
			return false;
		}

		private static bool TryGetAuthorizedAdminPortal(ZDOID portalId, ZNetPeer? requesterPeer, bool requesterIsAdmin, bool debugEnabled, out ZDO zdo, out PortalRulesMessage message)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			zdo = null;
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null)
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_server_not_ready");
				return false;
			}
			if (!requesterIsAdmin || !debugEnabled)
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_admin_portal_controls_require_admin_debug");
				return false;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(portalId);
			if (zDO == null || !zDO.IsValid() || !PublicPortalInteraction.IsAdminPortal(zDO))
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_requested_admin_portal_unavailable");
				return false;
			}
			if (!PublicPortalInteraction.TryValidatePortalInteraction(zDO, requesterPeer, out message))
			{
				return false;
			}
			if (!((requesterPeer != null) ? PublicPortalData.TryGetPeerOwner(requesterPeer, out var _) : PublicPortalData.LocalOwner().IsValid))
			{
				message = new PortalRulesMessage("$sighsorry_portalrules_platform_identity_unverified");
				return false;
			}
			zdo = zDO;
			return true;
		}

		private static void LogRejectedOperation(bool success, ZNetPeer peer, ZDOID portalId, string operation, PortalRulesMessage message)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (!success && PublicPortalData.TryGetPeerOwner(peer, out var owner))
			{
				string text = (message.IsEmpty ? "<empty>" : ((message.Arguments.Length == 0) ? message.Token : (message.Token + " [" + string.Join(", ", message.Arguments) + "]")));
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)$"Rejected admin portal {operation} from {owner.Id} for {portalId}: {text}");
			}
		}

		private static void ShowOperationMessage(PortalRulesMessage message)
		{
			string text = message.Localize();
			if (!string.IsNullOrWhiteSpace(text))
			{
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null);
				}
			}
		}
	}
	internal readonly struct PortalClanMembership
	{
		private readonly string? _primaryClanId;

		private readonly string? _primaryClanName;

		private readonly string? _guestClanId;

		public string PrimaryClanId => _primaryClanId ?? string.Empty;

		public string PrimaryClanName => _primaryClanName ?? string.Empty;

		public string GuestClanId => _guestClanId ?? string.Empty;

		public bool HasPrimaryClan => PrimaryClanId.Length != 0;

		public PortalClanMembership(string primaryClanId, string primaryClanName, string guestClanId)
		{
			_primaryClanId = Normalize(primaryClanId);
			_primaryClanName = ((_primaryClanId.Length == 0) ? string.Empty : Normalize(primaryClanName));
			_guestClanId = Normalize(guestClanId);
		}

		public bool ContainsClan(string clanId)
		{
			string text = Normalize(clanId);
			if (text.Length != 0)
			{
				if (!string.Equals(text, PrimaryClanId, StringComparison.Ordinal))
				{
					return string.Equals(text, GuestClanId, StringComparison.Ordinal);
				}
				return true;
			}
			return false;
		}

		private static string Normalize(string? value)
		{
			return (value ?? string.Empty).Trim();
		}
	}
	internal static class ClanPortalAccess
	{
		private const string ClanApiTypeName = "Clan.ClanApi";

		private const int MinimumApiVersion = 4;

		private const string ResolvedResultName = "Resolved";

		private static readonly object Sync = new object();

		private static MemberInfo? _apiVersionMember;

		private static MethodInfo? _resolveMembershipsMethod;

		private static System.Reflection.EventInfo? _registryChangedEventInfo;

		private static Delegate? _registryChangedHandler;

		private static bool _initialized;

		private static bool _apiAvailable;

		private static bool _registryChangedEventBound;

		private static bool _bindingFailureLogged;

		private static bool _invocationFailureLogged;

		internal static bool IsServerRegistryAvailable
		{
			get
			{
				lock (Sync)
				{
					return IsApiAvailableLocked();
				}
			}
		}

		internal static event Action? RegistryChanged;

		internal static void Initialize()
		{
			lock (Sync)
			{
				if (_initialized)
				{
					return;
				}
				_initialized = true;
				Assembly installedClanAssembly = GetInstalledClanAssembly();
				if (!(installedClanAssembly == null))
				{
					BindAssemblyLocked(installedClanAssembly);
					if (_resolveMembershipsMethod == null || _registryChangedEventInfo == null || !TryReadApiVersion(out var apiVersion) || apiVersion < 4)
					{
						LogBindingFailureOnce($"Clan integration requires API version {4} " + "with ResolveMemberships and RegistryChanged; integration is disabled.");
						ResetBindingLocked();
					}
					else if (!TryBindRegistryChangedEventLocked())
					{
						LogBindingFailureOnce("Could not bind Clan.RegistryChanged; Clan integration is disabled.");
						ResetBindingLocked();
					}
					else
					{
						_apiAvailable = true;
					}
				}
			}
		}

		internal static void Shutdown()
		{
			lock (Sync)
			{
				_initialized = false;
				ResetBindingLocked();
			}
		}

		internal static bool TryResolvePeerMembership(ZNetPeer? peer, out PortalClanMembership membership)
		{
			membership = default(PortalClanMembership);
			if (TryGetPeerIdentity(peer, out string platformId, out long playerId))
			{
				return TryResolveMembership(platformId, playerId, out membership);
			}
			return false;
		}

		internal static bool TryResolveLocalMembership(out PortalClanMembership membership)
		{
			membership = default(PortalClanMembership);
			if (!TryGetLocalIdentity(out string platformId, out long playerId))
			{
				return false;
			}
			return TryResolveMembership(platformId, playerId, out membership);
		}

		internal static bool TryResolveBuilderMembership(PortalBuilder builder, out PortalClanMembership membership)
		{
			membership = default(PortalClanMembership);
			if (builder.IsValid && builder.CharacterPlayerId != 0L)
			{
				return TryResolveMembership(builder.PlatformId, builder.CharacterPlayerId, out membership);
			}
			return false;
		}

		internal static bool IsRequesterInBuilderPrimaryClan(PortalBuilder builder, ZNetPeer? requesterPeer)
		{
			if (!TryResolveBuilderMembership(builder, out var membership) || !membership.HasPrimaryClan)
			{
				return false;
			}
			if ((requesterPeer != null) ? TryResolvePeerMembership(requesterPeer, out var membership2) : TryResolveLocalMembership(out membership2))
			{
				return membership2.ContainsClan(membership.PrimaryClanId);
			}
			return false;
		}

		private static bool TryResolveMembership(string platformId, long playerId, out PortalClanMembership membership)
		{
			membership = default(PortalClanMembership);
			if (string.IsNullOrWhiteSpace(platformId) || playerId == 0L)
			{
				return false;
			}
			MethodInfo resolveMembershipsMethod;
			lock (Sync)
			{
				if (!IsApiAvailableLocked())
				{
					return false;
				}
				resolveMembershipsMethod = _resolveMembershipsMethod;
			}
			if (resolveMembershipsMethod == null)
			{
				return false;
			}
			object[] array = new object[6]
			{
				platformId.Trim(),
				playerId,
				null,
				null,
				null,
				null
			};
			try
			{
				if (!string.Equals(resolveMembershipsMethod.Invoke(null, array)?.ToString(), "Resolved", StringComparison.Ordinal))
				{
					return false;
				}
				membership = new PortalClanMembership((array[2] as string) ?? string.Empty, (array[3] as string) ?? string.Empty, (array[4] as string) ?? string.Empty);
				return true;
			}
			catch (Exception exception)
			{
				LogInvocationFailureOnce(exception);
				membership = default(PortalClanMembership);
				return false;
			}
		}

		private static bool TryGetPeerIdentity(ZNetPeer? peer, out string platformId, out long playerId)
		{
			platformId = string.Empty;
			playerId = 0L;
			if (PublicPortalData.TryGetPeerSteamId64(peer, out platformId))
			{
				return PublicPortalData.TryGetAuthenticatedPeerPlayerId(peer, out playerId);
			}
			return false;
		}

		private static bool TryGetLocalIdentity(out string platformId, out long playerId)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			platformId = string.Empty;
			playerId = 0L;
			try
			{
				IDistributionPlatform distributionPlatform = PlatformManager.DistributionPlatform;
				Player localPlayer = Player.m_localPlayer;
				if (((distributionPlatform != null) ? distributionPlatform.LocalUser : null) == null || (Object)(object)localPlayer == (Object)null)
				{
					return false;
				}
				platformId = ((object)((IUser)distributionPlatform.LocalUser).PlatformUserID/*cast due to .constrained prefix*/).ToString().Trim();
				playerId = localPlayer.GetPlayerID();
				return platformId.Length != 0 && playerId != 0;
			}
			catch (Exception)
			{
				platformId = string.Empty;
				playerId = 0L;
				return false;
			}
		}

		private static bool IsApiAvailableLocked()
		{
			if (_initialized && _apiAvailable && _resolveMembershipsMethod != null)
			{
				return _registryChangedEventBound;
			}
			return false;
		}

		private static void BindAssemblyLocked(Assembly assembly)
		{
			Type type = assembly.GetType("Clan.ClanApi", throwOnError: false);
			if (!(type == null))
			{
				_apiVersionMember = (MemberInfo?)(((object)type.GetProperty("ApiVersion", BindingFlags.Static | BindingFlags.Public)) ?? ((object)type.GetField("ApiVersion", BindingFlags.Static | BindingFlags.Public)));
				_resolveMembershipsMethod = type.GetMethod("ResolveMemberships", BindingFlags.Static | BindingFlags.Public, null, new Type[6]
				{
					typeof(string),
					typeof(long),
					typeof(string).MakeByRefType(),
					typeof(string).MakeByRefType(),
					typeof(string).MakeByRefType(),
					typeof(string).MakeByRefType()
				}, null);
				_registryChangedEventInfo = type.GetEvent("RegistryChanged", BindingFlags.Static | BindingFlags.Public);
			}
		}

		private static Assembly? GetInstalledClanAssembly()
		{
			try
			{
				PluginInfo value;
				return (!Chainloader.PluginInfos.TryGetValue("sighsorry.Clan", out value)) ? null : ((object)value.Instance)?.GetType().Assembly;
			}
			catch (Exception ex)
			{
				LogBindingFailureOnce("Could not inspect the installed Clan plugin; integration is disabled: " + ex.Message);
				return null;
			}
		}

		private static bool TryReadApiVersion(out int apiVersion)
		{
			apiVersion = 0;
			try
			{
				MemberInfo apiVersionMember = _apiVersionMember;
				object obj = ((apiVersionMember is PropertyInfo propertyInfo) ? propertyInfo.GetValue(null, null) : ((!(apiVersionMember is FieldInfo fieldInfo)) ? null : fieldInfo.GetValue(null)));
				object obj2 = obj;
				if (obj2 == null)
				{
					return false;
				}
				apiVersion = Convert.ToInt32(obj2);
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static bool TryBindRegistryChangedEventLocked()
		{
			if (_registryChangedEventInfo?.EventHandlerType != typeof(Action))
			{
				return false;
			}
			try
			{
				_registryChangedHandler = new Action(HandleRegistryChanged);
				_registryChangedEventInfo.AddEventHandler(null, _registryChangedHandler);
				_registryChangedEventBound = true;
				return true;
			}
			catch (Exception)
			{
				_registryChangedHandler = null;
				_registryChangedEventBound = false;
				return false;
			}
		}

		private static void HandleRegistryChanged()
		{
			Delegate[] invocationList;
			lock (Sync)
			{
				if (!_initialized || ClanPortalAccess.RegistryChanged == null)
				{
					return;
				}
				invocationList = ClanPortalAccess.RegistryChanged.GetInvocationList();
			}
			Delegate[] array = invocationList;
			foreach (Delegate obj in array)
			{
				try
				{
					((Action)obj)();
				}
				catch (Exception)
				{
				}
			}
		}

		private static void ResetBindingLocked()
		{
			if (_registryChangedEventBound && _registryChangedEventInfo != null && (object)_registryChangedHandler != null)
			{
				try
				{
					_registryChangedEventInfo.RemoveEventHandler(null, _registryChangedHandler);
				}
				catch (Exception)
				{
				}
			}
			_apiAvailable = false;
			_registryChangedEventBound = false;
			_registryChangedHandler = null;
			_registryChangedEventInfo = null;
			_resolveMembershipsMethod = null;
			_apiVersionMember = null;
		}

		private static void LogBindingFailureOnce(string message)
		{
			if (!_bindingFailureLogged)
			{
				_bindingFailureLogged = true;
				PortalRulesPlugin.PortalRulesLogger.LogWarning((object)message);
			}
		}

		private static void LogInvocationFailureOnce(Exception exception)
		{
			lock (Sync)
			{
				if (_invocationFailureLogged)
				{
					return;
				}
				_invocationFailureLogged = true;
			}
			PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Clan.ResolveMemberships failed; the request was denied: " + exception.Message));
		}
	}
	internal enum InviteTravelCooldownFailure : byte
	{
		None,
		DataUnavailable,
		StorageUnavailable,
		Active,
		AccountCapacityReached,
		PortalCapacityReached,
		FileCapacityReached,
		SaveFailed,
		ReservationConflict
	}
	internal readonly struct InviteTravelCooldownReservation
	{
		internal readonly string ReservationId;

		internal readonly string AccountId;

		internal readonly string SourceFavoriteId;

		internal readonly string DestinationFavoriteId;

		internal readonly bool RecordsDeparture;

		internal readonly bool RecordsArrival;

		internal bool IsActive
		{
			get
			{
				if (!RecordsDeparture)
				{
					return RecordsArrival;
				}
				return true;
			}
		}

		internal InviteTravelCooldownReservation(string reservationId, string accountId, string sourceFavoriteId, string destinationFavoriteId, bool recordsDeparture, bool recordsArrival)
		{
			ReservationId = reservationId;
			AccountId = accountId;
			SourceFavoriteId = sourceFavoriteId;
			DestinationFavoriteId = destinationFavoriteId;
			RecordsDeparture = recordsDeparture;
			RecordsArrival = recordsArrival;
		}
	}
	internal static class InviteTravelCooldownStore
	{
		private sealed class PendingReservation
		{
			internal readonly InviteTravelCooldownReservation Value;

			internal readonly long ExpiresAtUtc;

			internal readonly long SerializedByteEstimate;

			internal PendingReservation(InviteTravelCooldownReservation value, long expiresAtUtc, long serializedByteEstimate)
			{
				Value = value;
				ExpiresAtUtc = expiresAtUtc;
				SerializedByteEstimate = serializedByteEstimate;
			}
		}

		private sealed class CooldownFile
		{
			[YamlMember(Alias = "format_version")]
			public int FormatVersion { get; set; }

			[YamlMember(Alias = "worlds")]
			public Dictionary<string, Dictionary<string, Dictionary<string, CooldownEntryYaml>>>? Worlds { get; set; }
		}

		private sealed class CooldownEntryYaml
		{
			[YamlMember(Alias = "departure_last_utc")]
			public long DepartureLastUtc { get; set; }

			[YamlMember(Alias = "arrival_last_utc")]
			public long ArrivalLastUtc { get; set; }
		}

		private readonly struct CooldownEntry
		{
			public readonly long DepartureLastUtc;

			public readonly long ArrivalLastUtc;

			public CooldownEntry(long departureLastUtc, long arrivalLastUtc)
			{
				DepartureLastUtc = Math.Max(0L, departureLastUtc);
				ArrivalLastUtc = Math.Max(0L, arrivalLastUtc);
			}

			public CooldownEntry WithDeparture(long value)
			{
				return new CooldownEntry(value, ArrivalLastUtc);
			}

			public CooldownEntry WithArrival(long value)
			{
				return new CooldownEntry(DepartureLastUtc, value);
			}
		}

		private const int FormatVersion = 1;

		private const int MaximumWorldCount = 1024;

		private const int MaximumAccountsPerWorld = 32768;

		private const int MaximumPortalsPerAccount = 100000;

		private const long MaximumFileBytes = 16777216L;

		private const int MaximumSerializedPortalMutationBytes = 256;

		private const int MaximumSerializedAccountHeaderBytes = 64;

		private const int MaximumPendingReservations = 4096;

		private const int MaximumReservationIdLength = 128;

		private const long PendingReservationLifetimeSeconds = 180L;

		private const long MaximumUtcSeconds = 253402300799L;

		private const string DirectoryName = "PortalRules";

		private const string FileName = "invite-travel-cooldowns.yml";

		private static readonly TimeSpan SaveDebounce = TimeSpan.FromSeconds(1.0);

		private static readonly TimeSpan SaveRetryDelay = TimeSpan.FromSeconds(10.0);

		private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithDuplicateKeyChecking().Build();

		private static readonly Dictionary<string, Dictionary<string, Dictionary<string, CooldownEntry>>> Worlds = new Dictionary<string, Dictionary<string, Dictionary<string, CooldownEntry>>>(StringComparer.Ordinal);

		private static readonly Dictionary<string, PendingReservation> PendingReservations = new Dictionary<string, PendingReservation>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> PendingDirectionOwners = new Dictionary<string, string>(StringComparer.Ordinal);

		private static string _filePath = "";

		private static string _worldId = "";

		private static bool _serverActive;

		private static bool _saveDirty;

		private static bool _saveFaulted;

		private static DateTime _nextSaveAttemptUtc;

		private static long _serializedByteUpperBound;

		internal static void BeginServerSession()
		{
			ClearPendingReservations();
			if (!EndServerSession())
			{
				PortalRulesPlugin.PortalRulesLogger.LogError((object)"Invite cooldown storage retained unsaved data from the previous session; the new session will remain unavailable until that data can be saved.");
			}
			else
			{
				if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZNet.World == null)
				{
					return;
				}
				long uid = ZNet.World.m_uid;
				if (uid != 0L)
				{
					_worldId = uid.ToString(CultureInfo.InvariantCulture);
					string text = Path.Combine(Paths.ConfigPath, "PortalRules");
					_filePath = Path.Combine(text, "invite-travel-cooldowns.yml");
					try
					{
						Directory.CreateDirectory(text);
						Load();
						bool flag = Prune(UtcNowSeconds());
						if (!Worlds.ContainsKey(_worldId))
						{
							if (Worlds.Count >= 1024)
							{
								throw new InvalidDataException(string.Format("{0} cannot track more than {1} worlds", "invite-travel-cooldowns.yml", 1024));
							}
							Worlds.Add(_worldId, new Dictionary<string, Dictionary<string, CooldownEntry>>(StringComparer.Ordinal));
							flag = true;
						}
						if (flag)
						{
							Save();
						}
						_saveDirty = false;
						_saveFaulted = false;
						_nextSaveAttemptUtc = DateTime.UtcNow + SaveDebounce;
						_serverActive = true;
						return;
					}
					catch (Exception ex)
					{
						Worlds.Clear();
						_worldId = "";
						_filePath = "";
						_serverActive = false;
						_saveDirty = false;
						_saveFaulted = false;
						_nextSaveAttemptUtc = DateTime.MinValue;
						_serializedByteUpperBound = 0L;
						PortalRulesPlugin.PortalRulesLogger.LogError((object)("Failed to initialize Invite cooldown storage: " + ex.Message));
						return;
					}
				}
				PortalRulesPlugin.PortalRulesLogger.LogError((object)"Invite cooldown storage is unavailable because the world UID is invalid.");
			}
		}

		internal static bool EndServerSession()
		{
			ClearPendingReservations();
			bool num = Flush();
			_serverActive = false;
			if (!num && _saveDirty)
			{
				PortalRulesPlugin.PortalRulesLogger.LogError((object)"Invite cooldown storage could not complete its final save; unsaved data is being retained in memory.");
				return false;
			}
			_saveDirty = false;
			_saveFaulted = false;
			_nextSaveAttemptUtc = DateTime.MinValue;
			_serializedByteUpperBound = 0L;
			_worldId = "";
			_filePath = "";
			Worlds.Clear();
			return true;
		}

		internal static void Tick()
		{
			PrunePendingReservations(UtcNowSeconds());
			if (_saveDirty && !(DateTime.UtcNow < _nextSaveAttemptUtc))
			{
				bool flag = !_serverActive && !string.IsNullOrEmpty(_filePath);
				if (TryFlushPending(out string _) && flag && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZNet.World != null)
				{
					BeginServerSession();
				}
			}
		}

		internal static bool Flush()
		{
			if (!_saveDirty)
			{
				return true;
			}
			if (string.IsNullOrEmpty(_filePath))
			{
				return false;
			}
			string error;
			return TryFlushPending(out error);
		}

		internal static void GetServerDeadlines(string steamId, string favoriteId, out long departureUntilUtc, out long arrivalUntilUtc)
		{
			departureUntilUtc = 0L;
			arrivalUntilUtc = 0L;
			if (_serverActive && TryNormalizeKeys(steamId, favoriteId, out string accountId, out string portalId) && TryGetEntry(accountId, portalId, out var entry))
			{
				departureUntilUtc = CalculateDeadline(entry.DepartureLastUtc, PublicPortalConfig.InviteDepartureCooldownHours.Value);
				arrivalUntilUtc = CalculateDeadline(entry.ArrivalLastUtc, PublicPortalConfig.InviteArrivalCooldownHours.Value);
			}
		}

		internal static void RemovePortal(string favoriteId)
		{
			if (!_serverActive || !PublicPortalData.TryNormalizeFavoriteId(favoriteId, out string portalId) || !TryGetCurrentWorldAccounts(out Dictionary<string, Dictionary<string, CooldownEntry>> accounts))
			{
				return;
			}
			PendingReservation[] array = PendingReservations.Values.Where((PendingReservation candidate) => (candidate.Value.RecordsDeparture && string.Equals(candidate.Value.SourceFavoriteId, portalId, StringComparison.Ordinal)) || (candidate.Value.RecordsArrival && string.Equals(candidate.Value.DestinationFavoriteId, portalId, StringComparison.Ordinal))).ToArray();
			for (int num = 0; num < array.Length; num++)
			{
				RemovePendingReservation(array[num]);
			}
			bool flag = false;
			foreach (Dictionary<string, CooldownEntry> value in accounts.Values)
			{
				flag |= value.Remove(portalId);
			}
			if (flag)
			{
				string[] array2 = (from pair in accounts
					where pair.Value.Count == 0
					select pair.Key).ToArray();
				foreach (string key in array2)
				{
					accounts.Remove(key);
				}
				_saveDirty = true;
			}
		}

		internal static bool TryReserve(string reservationId, string steamId, bool usesInviteAsSource, string sourceFavoriteId, bool usesInviteAsDestination, string destinationFavoriteId, out InviteTravelCooldownReservation reservation, out InviteTravelCooldownFailure failure, out long departureRemaining, out long arrivalRemaining)
		{
			reservation = default(InviteTravelCooldownReservation);
			failure = InviteTravelCooldownFailure.None;
			departureRemaining = 0L;
			arrivalRemaining = 0L;
			double num = PublicPortalConfig.InviteDepartureCooldownHours.Value;
			double num2 = PublicPortalConfig.InviteArrivalCooldownHours.Value;
			bool flag = usesInviteAsSource && num > 0.0;
			bool flag2 = usesInviteAsDestination && num2 > 0.0;
			if (!flag && !flag2)
			{
				return true;
			}
			string normalizedFavoriteId = "";
			string normalizedFavoriteId2 = "";
			if (string.IsNullOrWhiteSpace(reservationId) || reservationId.Length > 128 || !_serverActive || !PublicPortalData.TryNormalizeSteamId64(steamId, out string steamId2) || (flag && !PublicPortalData.TryNormalizeFavoriteId(sourceFavoriteId, out normalizedFavoriteId)) || (flag2 && !PublicPortalData.TryNormalizeFavoriteId(destinationFavoriteId, out normalizedFavoriteId2)))
			{
				failure = InviteTravelCooldownFailure.DataUnavailable;
				return false;
			}
			if (_saveFaulted && (DateTime.UtcNow < _nextSaveAttemptUtc || !TryFlushPending(out string error)))
			{
				failure = InviteTravelCooldownFailure.StorageUnavailable;
				return false;
			}
			long num3 = UtcNowSeconds();
			PrunePendingReservations(num3);
			if (Prune(num3))
			{
				_saveDirty = true;
				if (!TryFlushPending(out error))
				{
					failure = InviteTravelCooldownFailure.StorageUnavailable;
					return false;
				}
			}
			CooldownEntry entry = default(CooldownEntry);
			CooldownEntry entry2 = default(CooldownEntry);
			if (flag)
			{
				TryGetEntry(steamId2, normalizedFavoriteId, out entry);
			}
			if (flag2)
			{
				TryGetEntry(steamId2, normalizedFavoriteId2, out entry2);
			}
			departureRemaining = (flag ? GetRemainingSeconds(CalculateDeadline(entry.DepartureLastUtc, num), num3) : 0);
			arrivalRemaining = (flag2 ? GetRemainingSeconds(CalculateDeadline(entry2.ArrivalLastUtc, num2), num3) : 0);
			if (departureRemaining > 0 || arrivalRemaining > 0)
			{
				failure = InviteTravelCooldownFailure.Active;
				return false;
			}
			if (PendingReservations.Count >= 4096)
			{
				failure = InviteTravelCooldownFailure.StorageUnavailable;
				return false;
			}
			string text = (flag ? CreateDirectionKey(steamId2, normalizedFavoriteId, departure: true) : "");
			string text2 = (flag2 ? CreateDirectionKey(steamId2, normalizedFavoriteId2, departure: false) : "");
			if ((text.Length > 0 && PendingDirectionOwners.ContainsKey(text)) || (text2.Length > 0 && PendingDirectionOwners.ContainsKey(text2)))
			{
				failure = InviteTravelCooldownFailure.ReservationConflict;
				return false;
			}
			if (!TryGetCurrentWorldAccounts(out Dictionary<string, Dictionary<string, CooldownEntry>> accounts))
			{
				failure = InviteTravelCooldownFailure.DataUnavailable;
				return false;
			}
			Dictionary<string, CooldownEntry> portalEntries;
			bool flag3 = accounts.TryGetValue(steamId2, out portalEntries);
			if (!flag3)
			{
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				foreach (PendingReservation value2 in PendingReservations.Values)
				{
					if (!accounts.ContainsKey(value2.Value.AccountId))
					{
						hashSet.Add(value2.Value.AccountId);
					}
				}
				if (!hashSet.Contains(steamId2) && accounts.Count > 32768 - hashSet.Count - 1)
				{
					failure = InviteTravelCooldownFailure.AccountCapacityReached;
					return false;
				}
				portalEntries = new Dictionary<string, CooldownEntry>(StringComparer.Ordinal);
			}
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			foreach (PendingReservation value3 in PendingReservations.Values)
			{
				InviteTravelCooldownReservation value = value3.Value;
				if (string.Equals(value.AccountId, steamId2, StringComparison.Ordinal))
				{
					if (value.RecordsDeparture)
					{
						hashSet2.Add(value.SourceFavoriteId);
					}
					if (value.RecordsArrival)
					{
						hashSet2.Add(value.DestinationFavoriteId);
					}
				}
			}
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet4 = new HashSet<string>(StringComparer.Ordinal);
			if (flag && !portalEntries.ContainsKey(normalizedFavoriteId) && !hashSet2.Contains(normalizedFavoriteId))
			{
				hashSet3.Add(normalizedFavoriteId);
			}
			if (flag)
			{
				hashSet4.Add(normalizedFavoriteId);
			}
			if (flag2 && !portalEntries.ContainsKey(normalizedFavoriteId2) && !hashSet2.Contains(normalizedFavoriteId2))
			{
				hashSet3.Add(normalizedFavoriteId2);
			}
			if (flag2)
			{
				hashSet4.Add(normalizedFavoriteId2);
			}
			int num4 = hashSet2.Count((string favoriteId) => !portalEntries.ContainsKey(favoriteId));
			if (portalEntries.Count > 100000 - num4 - hashSet3.Count)
			{
				failure = InviteTravelCooldownFailure.PortalCapacityReached;
				return false;
			}
			long num5 = (long)hashSet4.Count * 256L + (flag3 ? 0 : 64);
			long num6 = PendingReservations.Values.Sum((PendingReservation pending) => pending.SerializedByteEstimate);
			if (_serializedByteUpperBound > 16777216 - num6 - num5)
			{
				if (!_saveDirty || !TryFlushPending(out error))
				{
					failure = InviteTravelCooldownFailure.FileCapacityReached;
					return false;
				}
				if (_serializedByteUpperBound > 16777216 - num6 - num5)
				{
					failure = InviteTravelCooldownFailure.FileCapacityReached;
					return false;
				}
			}
			reservation = new InviteTravelCooldownReservation(reservationId, steamId2, normalizedFavoriteId, normalizedFavoriteId2, flag, flag2);
			PendingReservations.Add(reservationId, new PendingReservation(reservation, Math.Min(253402300799L, num3 + 180), num5));
			if (text.Length > 0)
			{
				PendingDirectionOwners.Add(text, reservationId);
			}
			if (text2.Length > 0)
			{
				PendingDirectionOwners.Add(text2, reservationId);
			}
			return true;
		}

		internal static bool TryCommitReservation(InviteTravelCooldownReservation reservation, out InviteTravelCooldownFailure failure)
		{
			failure = InviteTravelCooldownFailure.None;
			if (!reservation.IsActive)
			{
				return true;
			}
			long num = UtcNowSeconds();
			if (!PendingReservations.TryGetValue(reservation.ReservationId, out PendingReservation value) || value.ExpiresAtUtc < num || !ReservationMatches(reservation, value.Value))
			{
				CancelReservation(reservation.ReservationId);
				failure = InviteTravelCooldownFailure.DataUnavailable;
				return false;
			}
			if (!_serverActive)
			{
				failure = InviteTravelCooldownFailure.DataUnavailable;
				return false;
			}
			if (_saveFaulted && (DateTime.UtcNow < _nextSaveAttemptUtc || !TryFlushPending(out string error)))
			{
				failure = InviteTravelCooldownFailure.StorageUnavailable;
				return false;
			}
			if (!TryGetCurrentWorldAccounts(out Dictionary<string, Dictionary<string, CooldownEntry>> accounts))
			{
				failure = InviteTravelCooldownFailure.DataUnavailable;
				return false;
			}
			Dictionary<string, CooldownEntry> value2;
			bool flag = accounts.TryGetValue(reservation.AccountId, out value2);
			if (!flag)
			{
				if (accounts.Count >= 32768)
				{
					failure = InviteTravelCooldownFailure.AccountCapacityReached;
					return false;
				}
				value2 = new Dictionary<string, CooldownEntry>(StringComparer.Ordinal);
			}
			CooldownEntry value3 = default(CooldownEntry);
			CooldownEntry value4 = default(CooldownEntry);
			bool flag2 = reservation.RecordsDeparture && value2.TryGetValue(reservation.SourceFavoriteId, out value3);
			bool flag3 = reservation.RecordsArrival && value2.TryGetValue(reservation.DestinationFavoriteId, out value4);
			int num2 = 0;
			if (reservation.RecordsDeparture && !flag2)
			{
				num2++;
			}
			if (reservation.RecordsArrival && !flag3 && (!reservation.RecordsDeparture || !string.Equals(reservation.SourceFavoriteId, reservation.DestinationFavoriteId, StringComparison.Ordinal)))
			{
				num2++;
			}
			if (value2.Count > 100000 - num2)
			{
				failure = InviteTravelCooldownFailure.PortalCapacityReached;
				return false;
			}
			if (_serializedByteUpperBound > 16777216 - value.SerializedByteEstimate)
			{
				failure = InviteTravelCooldownFailure.FileCapacityReached;
				return false;
			}
			if (!flag)
			{
				accounts.Add(reservation.AccountId, value2);
			}
			bool saveDirty = _saveDirty;
			long serializedByteUpperBound = _serializedByteUpperBound;
			if (reservation.RecordsDeparture)
			{
				value2[reservation.SourceFavoriteId] = value3.WithDeparture(num);
			}
			if (reservation.RecordsArrival)
			{
				CooldownEntry value5;
				CooldownEntry cooldownEntry = ((reservation.RecordsDeparture && string.Equals(reservation.SourceFavoriteId, reservation.DestinationFavoriteId, StringComparison.Ordinal) && value2.TryGetValue(reservation.DestinationFavoriteId, out value5)) ? value5 : value4);
				value2[reservation.DestinationFavoriteId] = cooldownEntry.WithArrival(num);
			}
			_serializedByteUpperBound += value.SerializedByteEstimate;
			_saveDirty = true;
			if (TryFlushPending(out error))
			{
				RemovePendingReservation(value);
				return true;
			}
			RestoreEntry(value2, reservation.SourceFavoriteId, flag2, value3);
			RestoreEntry(value2, reservation.DestinationFavoriteId, flag3, value4);
			if (value2.Count == 0)
			{
				accounts.Remove(reservation.AccountId);
			}
			_saveDirty = saveDirty || _saveFaulted;
			_serializedByteUpperBound = serializedByteUpperBound;
			failure = InviteTravelCooldownFailure.SaveFailed;
			return false;
		}

		internal static void CancelReservation(string reservationId)
		{
			if (!string.IsNullOrEmpty(reservationId) && PendingReservations.TryGetValue(reservationId, out PendingReservation value))
			{
				RemovePendingReservation(value);
			}
		}

		internal static string FormatRemaining(long seconds)
		{
			seconds = Math.Max(1L, seconds);
			long num = Math.Max(1L, (seconds + 59) / 60);
			if (num < 60)
			{
				return PortalRulesLocalization.Translate("$sighsorry_portalrules_duration_minutes", num.ToString(CultureInfo.InvariantCulture));
			}
			long num2 = num / 60;
			long num3 = num % 60;
			if (num3 != 0L)
			{
				return PortalRulesLocalization.Translate("$sighsorry_portalrules_duration_hours_minutes", num2.ToString(CultureInfo.InvariantCulture), num3.ToString(CultureInfo.InvariantCulture));
			}
			return PortalRulesLocalization.Translate("$sighsorry_portalrules_duration_hours", num2.ToString(CultureInfo.InvariantCulture));
		}

		internal static long GetRemainingSeconds(long deadlineUtc)
		{
			return GetRemainingSeconds(deadlineUtc, PublicPortalCatalog.GetEstimatedServerUtcNowSeconds());
		}

		internal static bool TryGetInviteArrivalCooldownRemaining(PublicPortalCatalogEntry portal, out long remainingSeconds)
		{
			remainingSeconds = 0L;
			if (portal.AccessMode != PublicPortalAccessMode.Invite || portal.InviteArrivalCooldownUntilUtc <= 0)
			{
				return false;
			}
			remainingSeconds = GetRemainingSeconds(portal.InviteArrivalCooldownUntilUtc);
			return remainingSeconds > 0;
		}

		private static bool TryNormalizeKeys(string steamId, string favoriteId, out string accountId, out string portalId)
		{
			accountId = "";
			portalId = "";
			if (PublicPortalData.TryNormalizeSteamId64(steamId, out accountId))
			{
				return PublicPortalData.TryNormalizeFavoriteId(favoriteId, out portalId);
			}
			return false;
		}

		private static string CreateDirectionKey(string accountId, string favoriteId, bool departure)
		{
			return accountId + (departure ? ":D:" : ":A:") + favoriteId;
		}

		private static bool ReservationMatches(InviteTravelCooldownReservation first, InviteTravelCooldownReservation second)
		{
			if (string.Equals(first.ReservationId, second.ReservationId, StringComparison.Ordinal) && string.Equals(first.AccountId, second.AccountId, StringComparison.Ordinal) && string.Equals(first.SourceFavoriteId, second.SourceFavoriteId, StringComparison.Ordinal) && string.Equals(first.DestinationFavoriteId, second.DestinationFavoriteId, StringComparison.Ordinal) && first.RecordsDeparture == second.RecordsDeparture)
			{
				return first.RecordsArrival == second.RecordsArrival;
			}
			return false;
		}

		private static void PrunePendingReservations(long nowUtc)
		{
			PendingReservation[] array = PendingReservations.Values.Where((PendingReservation candidate) => candidate.ExpiresAtUtc < nowUtc).ToArray();
			for (int num = 0; num < array.Length; num++)
			{
				RemovePendingReservation(array[num]);
			}
		}

		private static void RemovePendingReservation(PendingReservation pending)
		{
			InviteTravelCooldownReservation value = pending.Value;
			PendingReservations.Remove(value.ReservationId);
			if (value.RecordsDeparture)
			{
				PendingDirectionOwners.Remove(CreateDirectionKey(value.AccountId, value.SourceFavoriteId, departure: true));
			}
			if (value.RecordsArrival)
			{
				PendingDirectionOwners.Remove(CreateDirectionKey(value.AccountId, value.DestinationFavoriteId, departure: false));
			}
		}

		private static void ClearPendingReservations()
		{
			PendingReservations.Clear();
			PendingDirectionOwners.Clear();
		}

		private static bool TryGetEntry(string accountId, string portalId, out CooldownEntry entry)
		{
			entry = default(CooldownEntry);
			if (TryGetCurrentWorldAccounts(out Dictionary<string, Dictionary<string, CooldownEntry>> accounts) && accounts.TryGetValue(accountId, out var value))
			{
				return value.TryGetValue(portalId, out entry);
			}
			return false;
		}

		private static bool TryGetCurrentWorldAccounts(out Dictionary<string, Dictionary<string, CooldownEntry>> accounts)
		{
			accounts = null;
			if (!string.IsNullOrEmpty(_worldId))
			{
				return Worlds.TryGetValue(_worldId, out accounts);
			}
			return false;
		}

		private static void RestoreEntry(Dictionary<string, CooldownEntry> entries, string portalId, bool existed, CooldownEntry previous)
		{
			if (!string.IsNullOrEmpty(portalId))
			{
				if (existed)
				{
					entries[portalId] = previous;
				}
				else
				{
					entries.Remove(portalId);
				}
			}
		}

		private static long CalculateDeadline(long lastUtc, double hours)
		{
			if (lastUtc <= 0 || hours <= 0.0 || double.IsNaN(hours) || double.IsInfinity(hours))
			{
				return 0L;
			}
			long num = (long)Math.Ceiling(Math.Min(hours, 8760.0) * 3600.0);
			if (lastUtc <= 253402300799L - num)
			{
				return lastUtc + num;
			}
			return 253402300799L;
		}

		private static long GetRemainingSeconds(long deadlineUtc, long nowUtc)
		{
			if (deadlineUtc <= nowUtc)
			{
				return 0L;
			}
			return deadlineUtc - nowUtc;
		}

		private static long UtcNowSeconds()
		{
			return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
		}

		private static long NormalizeUtcSeconds(long value)
		{
			if (value < 0 || value > 253402300799L)
			{
				return 0L;
			}
			return value;
		}

		private static void Load()
		{
			Worlds.Clear();
			string path = _filePath + ".bak";
			if (!File.Exists(_filePath))
			{
				if (File.Exists(path))
				{
					LoadFromPath(path);
					Save();
					PortalRulesPlugin.PortalRulesLogger.LogWarning((object)"Recovered invite-travel-cooldowns.yml from its backup because the primary file was missing.");
				}
				return;
			}
			try
			{
				LoadFromPath(_filePath);
			}
			catch (Exception ex)
			{
				Worlds.Clear();
				if (!File.Exists(path))
				{
					throw;
				}
				try
				{
					LoadFromPath(path);
					string text = _filePath + ".invalid-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N");
					File.Move(_filePath, text);
					Save();
					PortalRulesPlugin.PortalRulesLogger.LogWarning((object)("Recovered invite-travel-cooldowns.yml from its backup. The invalid primary file was preserved as '" + Path.GetFileName(text) + "'."));
				}
				catch (Exception ex2)
				{
					Worlds.Clear();
					throw new InvalidDataException("Failed to load invite-travel-cooldowns.yml and its backup. Primary error: " + ex.Message + ". Recovery error: " + ex2.Message, ex2);
				}
			}
		}

		private static void LoadFromPath(string path)
		{
			Worlds.Clear();
			_serializedByteUpperBound = 0L;
			if (new FileInfo(path).Length > 16777216)
			{
				throw new InvalidDataException($"'{Path.GetFileName(path)}' exceeds {16777216L} bytes");
			}
			string text = File.ReadAllText(path);
			CooldownFile cooldownFile = Deserializer.Deserialize<CooldownFile>(text);
			if (cooldownFile == null || cooldownFile.FormatVersion != 1 || cooldownFile.Worlds == null)
			{
				throw new InvalidDataException(string.Format("{0} must contain format_version {1} and worlds", "invite-travel-cooldowns.yml", 1));
			}
			if (cooldownFile.Worlds.Count > 1024)
			{
				throw new InvalidDataException(string.Format("{0} contains more than {1} worlds", "invite-travel-cooldowns.yml", 1024));
			}
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, CooldownEntryYaml>>> world in cooldownFile.Worlds)
			{
				if (!long.TryParse(world.Key, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var result) || result == 0L || !string.Equals(world.Key, result.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal) || world.Value == null || world.Value.Count > 32768)
				{
					throw new InvalidDataException("invite-travel-cooldowns.yml contains an invalid world entry '" + world.Key + "'");
				}
				Dictionary<string, Dictionary<string, CooldownEntry>> dictionary = new Dictionary<string, Dictionary<string, CooldownEntry>>(StringComparer.Ordinal);
				foreach (KeyValuePair<string, Dictionary<string, CooldownEntryYaml>> item in world.Value)
				{
					if (!PublicPortalData.TryNormalizeSteamId64(item.Key, out string steamId) || !string.Equals(item.Key, steamId, StringComparison.Ordinal) || item.Value == null || item.Value.Count > 100000)
					{
						throw new InvalidDataException("invite-travel-cooldowns.yml contains an invalid account entry '" + item.Key + "'");
					}
					Dictionary<string, CooldownEntry> dictionary2 = new Dictionary<string, C