Decompiled source of PLL Library v3.1.0

PLL_Library.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Ashley.MeshSplitter;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Peak;
using Peak.Island;
using Peak.Network;
using Photon.Pun;
using Photon.Realtime;
using Steamworks;
using TMPro;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.Rendering.UnifiedRayTracing;
using UnityEngine.Rendering.Universal;
using UnityEngine.SceneManagement;
using UnityEngine.Sprites;
using UnityEngine.UI;
using Zorro.ControllerSupport;
using Zorro.Core;
using Zorro.Core.Serizalization;
using Zorro.Settings;
using Zorro.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Aeralis Foundation")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("[DirectX 11] Map layout mod and custom segment library.")]
[assembly: AssemblyFileVersion("3.1.0.0")]
[assembly: AssemblyInformationalVersion("3.1.0+63489a713810c57c94a2190a21039d838f870c9a")]
[assembly: AssemblyProduct("AF.PLL.Library")]
[assembly: AssemblyTitle("AF.PLL.Library")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly's creation was supported by AI: Code Suggestions, Log Lines (print statements) and Research.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")]
[assembly: AssemblyVersion("3.1.0.0")]
[module: RefSafetyRules(11)]
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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PLL.Library
{
	internal static class DeveloperNoteLoader
	{
		private sealed class BoundedTextDownloadHandler : DownloadHandlerScript
		{
			private const int ReceiveBufferSize = 4096;

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

			private readonly byte[] Content;

			private int Length;

			internal bool LimitExceeded { get; private set; }

			internal BoundedTextDownloadHandler(int ByteLimit)
				: base(new byte[4096])
			{
				Content = new byte[ByteLimit];
			}

			protected override void ReceiveContentLengthHeader(ulong contentLength)
			{
				LimitExceeded = contentLength > (ulong)Content.Length;
			}

			protected override bool ReceiveData(byte[] data, int dataLength)
			{
				if (LimitExceeded || dataLength > Content.Length - Length)
				{
					LimitExceeded = true;
					return false;
				}
				if (dataLength == 0)
				{
					return true;
				}
				Buffer.BlockCopy(data, 0, Content, Length, dataLength);
				Length += dataLength;
				return true;
			}

			internal bool TryGetText(out string Text)
			{
				try
				{
					Text = Utf8.GetString(Content, 0, Length);
					return true;
				}
				catch (DecoderFallbackException)
				{
					Text = string.Empty;
					return false;
				}
			}
		}

		private const string Url = "https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md";

		private const string FailureText = "No networking or connection failure? Well the same for me... v.v";

		private const int ByteLimit = 32768;

		private const int TimeoutSeconds = 10;

		private static UnityWebRequestAsyncOperation? Load;

		internal static string? Result { get; private set; }

		internal static event Action<string>? ResultAvailable;

		internal static void Start()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_003e: Expected O, but got Unknown
			Result = null;
			BoundedTextDownloadHandler boundedTextDownloadHandler = new BoundedTextDownloadHandler(32768);
			UnityWebRequest val = new UnityWebRequest("https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md", "GET", (DownloadHandler)(object)boundedTextDownloadHandler, (UploadHandler)null)
			{
				redirectLimit = 2,
				timeout = 10
			};
			UnityWebRequestAsyncOperation val2 = null;
			try
			{
				val2 = val.SendWebRequest();
			}
			catch (UnityException ex)
			{
				UnityException ex2 = ex;
				Publish("No networking or connection failure? Well the same for me... v.v");
				Plugin.PLLLog.LogError((object)("PLL Library could not start the Aeralis Foundation developer-note request because " + ((Exception)(object)ex2).Message + ". Check that https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md is reachable."));
			}
			finally
			{
				if (val2 == null)
				{
					val.Dispose();
				}
			}
			if (val2 != null)
			{
				Load = val2;
				((AsyncOperation)val2).completed += Complete;
			}
		}

		internal static void Stop()
		{
			UnityWebRequestAsyncOperation load = Load;
			Load = null;
			if (load == null)
			{
				return;
			}
			((AsyncOperation)load).completed -= Complete;
			UnityWebRequest webRequest = load.webRequest;
			try
			{
				webRequest.Abort();
			}
			finally
			{
				webRequest.Dispose();
			}
		}

		private static void Complete(AsyncOperation Operation)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Invalid comparison between Unknown and I4
			UnityWebRequestAsyncOperation load = Load;
			if (load == null || (object)load != Operation)
			{
				return;
			}
			((AsyncOperation)load).completed -= Complete;
			Load = null;
			UnityWebRequest webRequest = load.webRequest;
			try
			{
				BoundedTextDownloadHandler boundedTextDownloadHandler = (BoundedTextDownloadHandler)(object)webRequest.downloadHandler;
				string text;
				string Text;
				if (boundedTextDownloadHandler.LimitExceeded)
				{
					text = $"the response exceeded {32768} bytes";
				}
				else if ((int)webRequest.result != 1)
				{
					text = ((webRequest.responseCode > 0) ? $"GitHub returned HTTP {webRequest.responseCode}" : "raw.githubusercontent.com could not be reached");
				}
				else if (!boundedTextDownloadHandler.TryGetText(out Text))
				{
					text = "the response was not valid UTF-8 text";
				}
				else
				{
					if (!string.IsNullOrWhiteSpace(Text))
					{
						Publish(Text);
						return;
					}
					text = "the response was empty";
				}
				Publish("No networking or connection failure? Well the same for me... v.v");
				Plugin.PLLLog.LogError((object)("PLL Library could not load the Aeralis Foundation developer note because " + text + ". " + string.Format("Check that {0} is reachable and no larger than {1} bytes.", "https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md", 32768)));
			}
			finally
			{
				webRequest.Dispose();
			}
		}

		private static void Publish(string Text)
		{
			Result = Text;
			DeveloperNoteLoader.ResultAvailable?.Invoke(Text);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "af.pll.library";

		public const string PLUGIN_NAME = "AF.PLL.Library";

		public const string PLUGIN_VERSION = "3.1.0";
	}
	public static class PLLLibrary
	{
		public const string PluginGuid = "af.pll.library";

		public const string PluginName = "AF.PLL.Library";

		public const string PluginVersion = "3.1.0";

		public const string StartConnector = "af.pll.library/connector/start/v1";

		public const string LayoutConnector = "af.pll.library/connector/layout/v1";

		public static string? CurrentSegmentId => Map.CurrentSource?.Id;

		public static string? CurrentSegmentTypeId => Map.CurrentSource?.SegmentTypeId;

		public static string? CurrentBiomeId => Map.CurrentSource?.BiomeId;

		public static IReadOnlyList<PLLHookedProp> HookedProps => HookedPropRuntime.HookedProps;

		public static event Action<PLLHookedProp>? HookedPropAdded
		{
			add
			{
				HookedPropRuntime.Added += value;
			}
			remove
			{
				HookedPropRuntime.Added -= value;
			}
		}

		public static event Action<PLLHookedProp>? HookedPropRemoving
		{
			add
			{
				HookedPropRuntime.Removing += value;
			}
			remove
			{
				HookedPropRuntime.Removing -= value;
			}
		}

		public static int EmitEffect(string hook)
		{
			return AuthoringRuntime.Emit(hook);
		}

		public static int StopEffects(string hook)
		{
			return AuthoringRuntime.Stop(hook);
		}

		public static int EmitStinger(string hook)
		{
			return AuthoringRuntime.EmitStinger(hook);
		}

		public static int StopStingers(string hook)
		{
			return AuthoringRuntime.StopStingers(hook);
		}

		public static void DeployLayout(LayoutDefinition definition)
		{
			LayoutRegistry.Register(definition);
		}

		public static void ModifyLayout(LayoutModification modification)
		{
			LayoutRegistry.Register(modification);
		}

		public static IReadOnlyList<PLLHookedProp> GetHookedProps(string hook)
		{
			return HookedPropRuntime.Get(hook);
		}

		internal static void NotifyHookedPropDestroyed(PLLHookedProp value)
		{
			HookedPropRuntime.NotifyDestroyed(value);
		}
	}
	[BepInPlugin("af.pll.library", "AF.PLL.Library", "3.1.0")]
	public sealed class Plugin : BaseUnityPlugin, IInRoomCallbacks, IMatchmakingCallbacks
	{
		private Harmony? harmony;

		private readonly HashSet<IEnumerator> mapRoutines = new HashSet<IEnumerator>();

		private bool teardownStarted;

		private static Plugin instance;

		public static ManualLogSource PLLLog { get; private set; }

		internal static Coroutine StartRoutine(IEnumerator routine)
		{
			return instance.StartMapRoutine(routine);
		}

		private void Awake()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			PLLLog = ((BaseUnityPlugin)this).Logger;
			try
			{
				FileManager.Initialize(((BaseUnityPlugin)this).Info.Metadata);
				BiomeRegistration.DiscoverStartupBundles();
				AuthoringRuntime.Initialize(((Component)this).gameObject);
				instance = this;
				SceneManager.sceneUnloaded += OnSceneUnloaded;
				harmony = new Harmony("af.pll.library");
				harmony.PatchAll(typeof(Plugin).Assembly);
				PhotonNetwork.AddCallbackTarget((object)this);
				PhotonNetwork.NetworkingClient.StateChanged += OnNetworkStateChanged;
				DeveloperNoteLoader.Start();
			}
			catch (Exception ex)
			{
				((Behaviour)this).enabled = false;
				try
				{
					Teardown();
				}
				catch (Exception ex2)
				{
					throw new AggregateException("PLL Library initialization and rollback both failed.", ex, ex2);
				}
				throw;
			}
		}

		private void Start()
		{
			try
			{
				LayoutRegistry.Seal();
			}
			catch (Exception ex)
			{
				((Behaviour)this).enabled = false;
				try
				{
					Teardown();
				}
				catch (Exception ex2)
				{
					throw new AggregateException("PLL Library layout registration and rollback both failed.", ex, ex2);
				}
				throw;
			}
		}

		private void Teardown()
		{
			if (teardownStarted)
			{
				return;
			}
			teardownStarted = true;
			List<Exception> failures = new List<Exception>();
			Release(DeveloperNoteLoader.Stop);
			Release(delegate
			{
				ReleaseMapOwnership(Map.ReleaseCurrentRun);
			});
			Release(Map.ClearTransientPreflight);
			Release(delegate
			{
				SceneManager.sceneUnloaded -= OnSceneUnloaded;
			});
			Release(delegate
			{
				PhotonNetwork.NetworkingClient.StateChanged -= OnNetworkStateChanged;
			});
			Release(delegate
			{
				PhotonNetwork.RemoveCallbackTarget((object)this);
			});
			Release(PllSettingsSurface.Shutdown);
			Release(MenuVersionMark.Shutdown);
			Release(MainMenuDeveloperBar.Shutdown);
			Release(TerminalLayoutSelector.Shutdown);
			Release(TerminalLayoutSelection.Shutdown);
			Release(delegate
			{
				Harmony? obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
			});
			harmony = null;
			Release(BiomeRegistration.ClearLiveCatalogs);
			Release(AuthoringRuntime.Shutdown);
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
			if (failures.Count == 1)
			{
				throw failures[0];
			}
			if (failures.Count == 0)
			{
				return;
			}
			throw new AggregateException("PLL Library teardown failed.", failures);
			void Release(Action action)
			{
				try
				{
					action();
				}
				catch (Exception item)
				{
					failures.Add(item);
				}
			}
		}

		private Coroutine StartMapRoutine(IEnumerator routine)
		{
			if (routine == null)
			{
				throw new ArgumentNullException("routine");
			}
			if (!mapRoutines.Add(routine))
			{
				throw new InvalidOperationException("A map coroutine is already owned by the plugin.");
			}
			try
			{
				return ((MonoBehaviour)this).StartCoroutine(ObserveMapRoutine(routine));
			}
			catch
			{
				mapRoutines.Remove(routine);
				throw;
			}
		}

		private IEnumerator ObserveMapRoutine(IEnumerator routine)
		{
			try
			{
				while (routine.MoveNext())
				{
					yield return routine.Current;
				}
			}
			finally
			{
				Plugin plugin = this;
				try
				{
					(routine as IDisposable)?.Dispose();
				}
				finally
				{
					plugin.mapRoutines.Remove(routine);
				}
			}
		}

		private void StopMapRoutines()
		{
			((MonoBehaviour)this).StopAllCoroutines();
			if (mapRoutines.Count == 0)
			{
				return;
			}
			List<IEnumerator> list = new List<IEnumerator>(mapRoutines);
			mapRoutines.Clear();
			Exception ex = null;
			foreach (IEnumerator item in list)
			{
				try
				{
					(item as IDisposable)?.Dispose();
				}
				catch (Exception ex2)
				{
					ex = ((ex == null) ? ex2 : new AggregateException("Multiple map coroutine releases failed.", ex, ex2));
				}
			}
			if (ex == null)
			{
				return;
			}
			throw ex;
		}

		private void ReleaseMapOwnership(Action release)
		{
			List<Exception> failures = new List<Exception>();
			Try(release);
			Try(StopMapRoutines);
			if (failures.Count == 1)
			{
				throw failures[0];
			}
			if (failures.Count != 0)
			{
				throw new AggregateException("Map ownership release failed.", failures);
			}
			void Try(Action action)
			{
				try
				{
					action();
				}
				catch (Exception item)
				{
					failures.Add(item);
				}
			}
		}

		private void ReleaseScene(Scene scene)
		{
			//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)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (Map.OwnsScene(SceneHandle.op_Implicit(((Scene)(ref scene)).handle)))
			{
				ReleaseMapOwnership(delegate
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					Map.ReleaseScene(SceneHandle.op_Implicit(((Scene)(ref scene)).handle));
				});
			}
			else
			{
				Map.ReleaseScene(SceneHandle.op_Implicit(((Scene)(ref scene)).handle));
			}
		}

		private void OnSceneUnloaded(Scene scene)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (!teardownStarted)
			{
				ReleaseScene(scene);
			}
		}

		private void OnNetworkStateChanged(ClientState previous, ClientState current)
		{
			//IL_001e: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (!teardownStarted)
			{
				Map.CurrentRun?.Network.GenerationBarrier.ConnectionStateChanged(current);
				QuicksaveRoomGate.ConnectionStateChanged(current);
				IslandLoadPublication.ConnectionStateChanged(current);
				FinalManifestConsensus.ConnectionStateChanged(current);
			}
		}

		private void OnDestroy()
		{
			Teardown();
		}

		private void Update()
		{
			IslandLoadPublication.Tick();
			Map.CurrentRun?.Network.DeferredOperations.Tick();
		}

		void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
		{
			if (!teardownStarted)
			{
				IslandLoadPublication.RoomPropertiesChanged(propertiesThatChanged);
				FinalManifestConsensus.MaintainCurrent();
			}
		}

		void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient)
		{
			if (!teardownStarted)
			{
				Map.CurrentRun?.Network.GenerationBarrier.MasterClientChanged();
				IslandLoadPublication.MasterClientChanged();
				FinalManifestConsensus.MaintainCurrent();
				if (newMasterClient.IsLocal)
				{
					TerminalLayoutSelection.Reset();
				}
				else
				{
					TerminalLayoutSelection.RefreshAuthority();
				}
			}
		}

		void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer)
		{
			if (!teardownStarted)
			{
				FinalManifestConsensus.MaintainCurrent();
			}
		}

		void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer)
		{
			if (!teardownStarted)
			{
				Map.CurrentRun?.Network.ViewBatches.PlayerLeft(otherPlayer);
				Map.CurrentRun?.Network.GenerationBarrier.PlayerLeft(otherPlayer);
				FinalManifestConsensus.MaintainCurrent();
			}
		}

		void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
		{
		}

		void IMatchmakingCallbacks.OnFriendListUpdate(List<FriendInfo> friendList)
		{
		}

		void IMatchmakingCallbacks.OnCreatedRoom()
		{
			if (!teardownStarted)
			{
				TerminalLayoutSelection.Reset();
			}
		}

		void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
		{
			if (!teardownStarted)
			{
				QuicksaveRoomGate.MatchmakingFailed("room creation", returnCode, message);
			}
		}

		void IMatchmakingCallbacks.OnJoinedRoom()
		{
			if (!teardownStarted && PhotonNetwork.IsMasterClient)
			{
				TerminalLayoutSelection.Reset();
			}
		}

		void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message)
		{
			if (!teardownStarted)
			{
				QuicksaveRoomGate.MatchmakingFailed("room join", returnCode, message);
			}
		}

		void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
		{
			if (!teardownStarted)
			{
				QuicksaveRoomGate.MatchmakingFailed("random room join", returnCode, message);
			}
		}

		void IMatchmakingCallbacks.OnLeftRoom()
		{
			if (!teardownStarted)
			{
				Map.ClearTransientPreflight();
				FinalManifestConsensus.Reset();
				AscentUIStartPatch.ResetResolved();
				QuicksaveRoomGate.RoomLeft();
				TerminalLayoutSelection.Reset();
			}
		}
	}
	internal sealed class ConfigDocument
	{
		private readonly struct RawSetting
		{
			internal string? SettingType { get; }

			internal string Value { get; }

			internal RawSetting(string? SettingType, string Value)
			{
				this.SettingType = SettingType;
				this.Value = Value;
			}
		}

		private const string LegacyPublicSchemaRevision = "17";

		private const string PreviousPublicSchemaRevision = "20";

		private const string CurrentSchemaRevision = "21";

		private readonly string Path;

		private readonly bool Exists;

		private readonly Dictionary<string, Dictionary<string, RawSetting>> RawSettings = new Dictionary<string, Dictionary<string, RawSetting>>(StringComparer.Ordinal);

		private string SchemaRevision { get; }

		private ConfigDocument(string Path, bool Exists, IEnumerable<string> Lines)
		{
			this.Path = Path;
			this.Exists = Exists;
			Parse(Lines);
			SchemaRevision = ((!Exists) ? "21" : (TryGet("Version", "SchemaRevision", out var Setting) ? Setting.Value : string.Empty));
		}

		internal static ConfigBindings Open(string Path, BepInPlugin OwnerMetadata)
		{
			ConfigDocument configDocument = Read(Path);
			bool exists = configDocument.Exists;
			bool flag = exists;
			if (flag)
			{
				bool flag2;
				switch (configDocument.SchemaRevision)
				{
				case "17":
				case "20":
				case "21":
					flag2 = true;
					break;
				default:
					flag2 = false;
					break;
				}
				flag = !flag2;
			}
			if (flag)
			{
				throw new InvalidDataException("Configuration schema '" + configDocument.SchemaRevision + "' is unsupported; expected '21'.");
			}
			ConfigBindings configBindings = ConfigBindings.Bind(Path, OwnerMetadata, "21");
			List<BoundConfigSetting> list = configDocument.FindMismatchedSettings(configBindings);
			bool flag3 = configDocument.SchemaRevision != "21" || list.Count != 0 || !configDocument.SchemaMatches(configBindings);
			foreach (BoundConfigSetting item in list)
			{
				if (item.Migratable)
				{
					item.Entry.BoxedValue = item.Entry.DefaultValue;
				}
			}
			if (flag3)
			{
				configBindings = configDocument.Replace(configBindings, OwnerMetadata);
			}
			else if (!configDocument.Exists)
			{
				configBindings.Config.Save();
			}
			configBindings.Config.SaveOnConfigSet = true;
			return configBindings;
		}

		private static ConfigDocument Read(string Path)
		{
			bool flag = File.Exists(Path);
			IEnumerable<string> lines;
			if (!flag)
			{
				IEnumerable<string> enumerable = Array.Empty<string>();
				lines = enumerable;
			}
			else
			{
				lines = File.ReadLines(Path);
			}
			return new ConfigDocument(Path, flag, lines);
		}

		private void Parse(IEnumerable<string> Lines)
		{
			string key = string.Empty;
			string settingType = null;
			foreach (string Line in Lines)
			{
				string text = Line.Trim();
				if (text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal))
				{
					key = text.Substring(1, text.Length - 2);
					settingType = null;
					continue;
				}
				if (text.StartsWith("# Setting type:", StringComparison.Ordinal))
				{
					settingType = text.Substring("# Setting type:".Length).Trim();
					continue;
				}
				int num = text.IndexOf('=');
				if (num >= 0 && !text.StartsWith("#", StringComparison.Ordinal))
				{
					string key2 = text.Substring(0, num).Trim();
					if (!RawSettings.TryGetValue(key, out Dictionary<string, RawSetting> value))
					{
						value = new Dictionary<string, RawSetting>(StringComparer.Ordinal);
						RawSettings.Add(key, value);
					}
					value[key2] = new RawSetting(settingType, text.Substring(num + 1).Trim());
					settingType = null;
				}
			}
		}

		private List<BoundConfigSetting> FindMismatchedSettings(ConfigBindings Bindings)
		{
			List<BoundConfigSetting> list = new List<BoundConfigSetting>();
			foreach (BoundConfigSetting setting in Bindings.Settings)
			{
				if (TryGet(setting.Entry.Definition.Section, setting.Entry.Definition.Key, out var Setting) && ((Setting.SettingType != null && !(Setting.SettingType == setting.Kind.TypeName())) || !setting.Kind.ValueMatches(Setting.Value)))
				{
					list.Add(setting);
				}
			}
			return list;
		}

		private bool SchemaMatches(ConfigBindings Bindings)
		{
			foreach (KeyValuePair<string, Dictionary<string, RawSetting>> rawSetting in RawSettings)
			{
				foreach (string key in rawSetting.Value.Keys)
				{
					if (!Bindings.IsKnown(rawSetting.Key, key))
					{
						return false;
					}
				}
			}
			return true;
		}

		private bool TryGet(string Section, string Key, out RawSetting Setting)
		{
			if (RawSettings.TryGetValue(Section, out Dictionary<string, RawSetting> value) && value.TryGetValue(Key, out Setting))
			{
				return true;
			}
			Setting = default(RawSetting);
			return false;
		}

		private ConfigBindings Replace(ConfigBindings Source, BepInPlugin OwnerMetadata)
		{
			Dictionary<ConfigDefinition, object> dictionary = new Dictionary<ConfigDefinition, object>();
			foreach (BoundConfigSetting setting in Source.Settings)
			{
				if (setting.Migratable)
				{
					dictionary.Add(setting.Entry.Definition, setting.Entry.BoxedValue);
				}
			}
			string text = Path + ".new";
			if (File.Exists(text))
			{
				File.Delete(text);
			}
			try
			{
				ConfigBindings configBindings = ConfigBindings.Bind(text, OwnerMetadata, "21");
				foreach (BoundConfigSetting setting2 in configBindings.Settings)
				{
					if (setting2.Migratable)
					{
						setting2.Entry.BoxedValue = dictionary[setting2.Entry.Definition];
					}
				}
				configBindings.Config.Save();
				File.Replace(text, Path, null);
			}
			finally
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
			}
			return ConfigBindings.Bind(Path, OwnerMetadata, "21");
		}
	}
	internal sealed class ConfigBindings
	{
		private const string DefaultSeed = "";

		private const int DefaultScale = 8;

		private readonly List<BoundConfigSetting> BoundSettings = new List<BoundConfigSetting>();

		private readonly Dictionary<string, HashSet<string>> KnownSettings = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);

		internal ConfigFile Config { get; }

		internal IReadOnlyList<BoundConfigSetting> Settings => BoundSettings;

		internal ConfigEntry<string> LayoutOrder { get; }

		internal ConfigEntry<string> Seed { get; }

		internal ConfigEntry<bool> ReplaceGenerated { get; }

		internal ConfigEntry<bool> AdvertiseLayout { get; }

		internal ConfigEntry<bool> DoOrder { get; }

		internal ConfigEntry<bool> GenerativeMap { get; }

		internal ConfigEntry<bool> InfiniteMap { get; }

		internal ConfigEntry<string> InfiniteMapRepeatable { get; }

		internal ConfigEntry<string> InfiniteMapChances { get; }

		internal ConfigEntry<int> InfiniteMapScale { get; }

		internal ConfigEntry<int> InfiniteMapBeginIndex { get; }

		internal ConfigEntry<bool> InfiniteMapRemoveOld { get; }

		internal ConfigEntry<bool> Winnable { get; }

		internal ConfigEntry<bool> PeakAtEnd { get; }

		internal ConfigEntry<bool> GemlessNadir { get; }

		internal ConfigEntry<bool> WaterFollowsBeach { get; }

		internal ConfigEntry<bool> VoidWaterFollowsNadir { get; }

		internal ConfigEntry<bool> RelocatePlane { get; }

		internal ConfigEntry<bool> RelocateStart { get; }

		internal ConfigEntry<bool> ValidNadir { get; }

		internal ConfigEntry<bool> LazyGeneration { get; }

		internal ConfigEntry<bool> Fogless { get; }

		internal ConfigEntry<bool> NoRisingGhosts { get; }

		internal ConfigEntry<bool> NoSleepFog { get; }

		internal ConfigEntry<bool> NoWater { get; }

		internal ConfigEntry<bool> NoVoidWater { get; }

		internal ConfigEntry<bool> NoPlane { get; }

		internal ConfigEntry<bool> NoSkyProps { get; }

		internal ConfigEntry<bool> RemoveOld { get; }

		private ConfigBindings(string Path, BepInPlugin OwnerMetadata, string SchemaRevision)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Expected O, but got Unknown
			Config = new ConfigFile(Path, false, OwnerMetadata)
			{
				SaveOnConfigSet = false
			};
			Bind("Version", "SchemaRevision", SchemaRevision, "Tracks the configuration schema.", Migratable: false);
			Seed = Bind("Map", "Seed", "", "Empty chooses a new random world seed; any text produces a deterministic seed.");
			ReplaceGenerated = Bind("Map", "Replace Generated", DefaultValue: true, "Regenerates PEAK's already generated native segment props with the selected seed.");
			AdvertiseLayout = Bind("PLL", "Advertise Layout", DefaultValue: false, "Shows one configured PLL layout label beneath PEAK's ascent name.");
			LayoutOrder = Bind("Map", "LayoutOrder", "Beach > Tropics > Roots > Alpine > Mesa > Volcano > Citadel > Nadir", "Defines an ordered list of public native or custom ordinary layout segments. Volcano and Citadel each include their native lower half.");
			DoOrder = Bind("Map", "DoOrder", DefaultValue: true, "Enables LayoutOrder for the map.");
			GenerativeMap = Bind("Map", "Generative Map", DefaultValue: false, "Builds one deterministic layout from registered native and custom biome candidates when Layout Order is disabled.");
			InfiniteMap = Bind("Map", "InfiniteMap", DefaultValue: false, "Enables infinite map generation.");
			InfiniteMapRepeatable = Bind("Fields", "InfiniteMap: Repeatable", "", "Up to 255 public segment names eligible for repetition, separated by >.");
			InfiniteMapChances = Bind("Fields", "InfiniteMap: Chances", "", "Up to 255 non-negative weights mapped by position to InfiniteMap: Repeatable, separated by >.");
			InfiniteMapScale = Bind("Fields", "InfiniteMap: Scale", 8, new ConfigDescription("Requested total public segment count; configuration accepts 1 through 255, current PEAK navigation resolves at most 254 ordinary internal slots, and an online shared Photon room may separately reject a content-dependent generated object batch when it lacks free room ViewIDs.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 255), Array.Empty<object>()));
			InfiniteMapBeginIndex = Bind("Fields", "InfiniteMap: Begin Index", -1, "-1 inserts at the final eligible public boundary (before a terminal Nadir); other values are clamped zero-based public insertion boundaries.");
			InfiniteMapRemoveOld = Bind("Fields", "InfiniteMap: Remove Old", DefaultValue: false, "Removes the default layout so only InfiniteMap: Repeatable segments are used.");
			Winnable = Bind("Behaviors", "Winnable", DefaultValue: true, "Enables the configured final-zone win behavior.");
			PeakAtEnd = Bind("Behaviors", "Peak at end", DefaultValue: false, "Attaches full Peak after the actual final public ordinary segment; terminal Volcano and Citadel retain contextual Peak.");
			GemlessNadir = Bind("Behaviors", "Gemless Nadir", DefaultValue: false, "Allows every Nadir occurrence to load without Scout's Honor.");
			WaterFollowsBeach = Bind("Behaviors", "Water follows Beach", DefaultValue: true, "Activates the water floor when Beach is reached; otherwise enables it beneath the first segment from the start.");
			VoidWaterFollowsNadir = Bind("Behaviors", "Void Water follows Nadir", DefaultValue: true, "Activates PEAK's Void Water when its Nadir occurrence is reached; otherwise enables it from the start.");
			RelocatePlane = Bind("Prerequisites", "Relocate Plane", DefaultValue: true, "Moves PEAK's original crashed plane to the first segment's explicit Start port.");
			RelocateStart = Bind("Prerequisites", "Relocate Start", DefaultValue: true, "Moves PEAK's active four-player spawn group to the first segment's explicit Start port.");
			ValidNadir = Bind("Prerequisites", "Valid Nadir", DefaultValue: true, "Requires Shore; Tropics or Roots; Alpine or Mesa; and Volcano or Citadel to precede Nadir before it may require Scout's Honor; otherwise Nadir is gemless.");
			LazyGeneration = Bind("Optimizations", "Lazy Generation", DefaultValue: true, "Defers procedural prop generation until its prerequisite zone is reached.");
			Fogless = Bind("Optimizations", "Fogless", DefaultValue: false, "Disables the fog sphere and its relocation behavior.");
			NoRisingGhosts = Bind("Optimizations", "No Rising Ghosts", DefaultValue: false, "Disables Nadir's rising lost-soul fog and Citadel's rising Gloom.");
			NoSleepFog = Bind("Optimizations", "No Sleep Fog", DefaultValue: false, "Disables Gloom's stationary sleep fog field.");
			NoWater = Bind("Optimizations", "No Water", DefaultValue: false, "Disables Beach water and all water relocation behavior.");
			NoVoidWater = Bind("Optimizations", "No Void Water", DefaultValue: false, "Disables PEAK's Nadir-local Void Water surface and WaterZone.");
			NoPlane = Bind("Optimizations", "No Plane", DefaultValue: false, "Removes the crashed plane and its affiliates except spawn points.");
			NoSkyProps = Bind("Optimizations", "No Sky Props", DefaultValue: true, "Restricts native raycast-position prop generation to the active map occurrence.");
			RemoveOld = Bind("Optimizations", "Remove Old", DefaultValue: true, "Destroys map zones older than the immediately previous zone after progression.");
		}

		internal static ConfigBindings Bind(string Path, BepInPlugin OwnerMetadata, string SchemaRevision)
		{
			return new ConfigBindings(Path, OwnerMetadata, SchemaRevision);
		}

		internal bool IsKnown(string Section, string Key)
		{
			if (KnownSettings.TryGetValue(Section, out HashSet<string> value))
			{
				return value.Contains(Key);
			}
			return false;
		}

		private ConfigEntry<string> Bind(string Section, string Key, string DefaultValue, string Description, bool Migratable = true)
		{
			return Add<string>(Config.Bind<string>(Section, Key, DefaultValue, Description), ConfigValueKind.String, Migratable);
		}

		private ConfigEntry<bool> Bind(string Section, string Key, bool DefaultValue, string Description)
		{
			return Add<bool>(Config.Bind<bool>(Section, Key, DefaultValue, Description), ConfigValueKind.Boolean, Migratable: true);
		}

		private ConfigEntry<int> Bind(string Section, string Key, int DefaultValue, string Description)
		{
			return Add<int>(Config.Bind<int>(Section, Key, DefaultValue, Description), ConfigValueKind.Int32, Migratable: true);
		}

		private ConfigEntry<int> Bind(string Section, string Key, int DefaultValue, ConfigDescription Description)
		{
			return Add<int>(Config.Bind<int>(Section, Key, DefaultValue, Description), ConfigValueKind.Int32, Migratable: true);
		}

		private ConfigEntry<T> Add<T>(ConfigEntry<T> Entry, ConfigValueKind Kind, bool Migratable)
		{
			BoundSettings.Add(new BoundConfigSetting((ConfigEntryBase)(object)Entry, Kind, Migratable));
			if (!KnownSettings.TryGetValue(((ConfigEntryBase)Entry).Definition.Section, out HashSet<string> value))
			{
				value = new HashSet<string>(StringComparer.Ordinal);
				KnownSettings.Add(((ConfigEntryBase)Entry).Definition.Section, value);
			}
			value.Add(((ConfigEntryBase)Entry).Definition.Key);
			return Entry;
		}
	}
	internal readonly struct BoundConfigSetting
	{
		internal ConfigEntryBase Entry { get; }

		internal ConfigValueKind Kind { get; }

		internal bool Migratable { get; }

		internal BoundConfigSetting(ConfigEntryBase Entry, ConfigValueKind Kind, bool Migratable)
		{
			this.Entry = Entry;
			this.Kind = Kind;
			this.Migratable = Migratable;
		}
	}
	internal enum ConfigValueKind
	{
		String,
		Boolean,
		Int32
	}
	internal static class ConfigValueKindOperations
	{
		internal static string TypeName(this ConfigValueKind Kind)
		{
			return Kind switch
			{
				ConfigValueKind.String => typeof(string).Name, 
				ConfigValueKind.Boolean => typeof(bool).Name, 
				ConfigValueKind.Int32 => typeof(int).Name, 
				_ => throw new ArgumentOutOfRangeException("Kind", Kind, null), 
			};
		}

		internal static bool ValueMatches(this ConfigValueKind Kind, string Value)
		{
			bool result;
			int result2;
			return Kind switch
			{
				ConfigValueKind.String => true, 
				ConfigValueKind.Boolean => bool.TryParse(Value, out result), 
				ConfigValueKind.Int32 => int.TryParse(Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2), 
				_ => throw new ArgumentOutOfRangeException("Kind", Kind, null), 
			};
		}
	}
	internal sealed class CustomBiomeConfig
	{
		internal BiomeId BiomeId { get; }

		internal string ConfigPath { get; }

		internal ConfigEntry<string> Favorites { get; }

		internal ConfigEntry<string> Overwrites { get; }

		internal ConfigEntry<string> Before { get; }

		internal ConfigEntry<string> After { get; }

		internal ConfigEntry<int> SpawnChance { get; }

		internal ConfigFile File { get; }

		internal CustomBiomeConfig(BiomeId biomeId, string configPath, ConfigFile file, ConfigEntry<string> favorites, ConfigEntry<string> overwrites, ConfigEntry<string> before, ConfigEntry<string> after, ConfigEntry<int> spawnChance)
		{
			BiomeId = biomeId;
			ConfigPath = configPath;
			File = file;
			Favorites = favorites;
			Overwrites = overwrites;
			Before = before;
			After = after;
			SpawnChance = spawnChance;
		}
	}
	internal readonly struct CustomBiomeConfigDefaults
	{
		internal BiomeId Id { get; }

		internal BiomeId[] Favorites { get; }

		internal BiomeId[] Overwrites { get; }

		internal BiomeId[] Before { get; }

		internal BiomeId[] After { get; }

		internal int SpawnChance { get; }

		private CustomBiomeConfigDefaults(BiomeId id, BiomeId[] favorites, BiomeId[] overwrites, BiomeId[] before, BiomeId[] after, int spawnChance)
		{
			Id = id;
			Favorites = favorites;
			Overwrites = overwrites;
			Before = before;
			After = after;
			SpawnChance = spawnChance;
		}

		internal static CustomBiomeConfigDefaults Create(PllBiomeDefinition definition)
		{
			if (!Object.op_Implicit((Object)(object)definition))
			{
				throw new ArgumentNullException("definition");
			}
			BiomeId biomeId = definition.BiomeId;
			int spawnChance = definition.SpawnChance;
			if (spawnChance < 0 || spawnChance > 100)
			{
				throw new ArgumentOutOfRangeException("definition", spawnChance, $"Biome '{biomeId}' SpawnChance must be between 0 and 100.");
			}
			return new CustomBiomeConfigDefaults(biomeId, CopyIds(definition.Favorites, biomeId, "Favorites"), CopyIds(definition.Overwrites, biomeId, "Overwrites", rejectReservedNonLayout: true), CopyIds(definition.Before, biomeId, "Before"), CopyIds(definition.After, biomeId, "After"), spawnChance);
		}

		private static BiomeId[] CopyIds(IReadOnlyList<BiomeId> source, BiomeId owner, string name, bool rejectReservedNonLayout = false)
		{
			BiomeId[] array = new BiomeId[source.Count];
			for (int i = 0; i < array.Length; i++)
			{
				BiomeId biomeId = source[i];
				if (!biomeId.IsValid || biomeId == owner)
				{
					throw new ArgumentException($"Biome '{owner}' contains an invalid {name} entry.", "source");
				}
				if (rejectReservedNonLayout && BiomeLayoutConfigSnapshot.IsReservedNonLayoutBiome(biomeId))
				{
					throw new ArgumentException($"Biome '{owner}' cannot overwrite reserved non-layout biome '{biomeId}'.", "source");
				}
				array[i] = biomeId;
			}
			Array.Sort(array, CompareIds);
			for (int j = 1; j < array.Length; j++)
			{
				if (array[j - 1] == array[j])
				{
					throw new ArgumentException($"Biome '{owner}' contains duplicate {name} entry '{array[j]}'.", "source");
				}
			}
			return array;
		}

		private static int CompareIds(BiomeId left, BiomeId right)
		{
			return StringComparer.Ordinal.Compare(left.Value, right.Value);
		}
	}
	internal sealed class BiomeLayoutConfigValues
	{
		internal BiomeId Id { get; }

		internal BiomeId[] Favorites { get; }

		internal BiomeId[] Overwrites { get; }

		internal BiomeId[] Before { get; }

		internal BiomeId[] After { get; }

		internal int SpawnChance { get; }

		internal BiomeLayoutConfigValues(BiomeId id, BiomeId[] favorites, BiomeId[] overwrites, BiomeId[] before, BiomeId[] after, int spawnChance)
		{
			Id = id;
			Favorites = favorites;
			Overwrites = overwrites;
			Before = before;
			After = after;
			SpawnChance = spawnChance;
		}

		internal bool Favors(BiomeId id)
		{
			return Contains(Favorites, id);
		}

		internal bool OverwritesBiome(BiomeId id)
		{
			return Contains(Overwrites, id);
		}

		internal bool PrefersBefore(BiomeId id)
		{
			return Contains(Before, id);
		}

		internal bool PrefersAfter(BiomeId id)
		{
			return Contains(After, id);
		}

		private static bool Contains(IReadOnlyList<BiomeId> values, BiomeId id)
		{
			for (int i = 0; i < values.Count; i++)
			{
				if (values[i] == id)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal sealed class BiomeLayoutConfigSnapshot
	{
		internal static readonly BiomeLayoutConfigSnapshot Empty = new BiomeLayoutConfigSnapshot(Array.Empty<BiomeLayoutConfigValues>());

		private readonly Dictionary<BiomeId, BiomeLayoutConfigValues> byId;

		internal IReadOnlyList<BiomeLayoutConfigValues> Values { get; }

		internal string Serialized { get; }

		internal BiomeLayoutConfigSnapshot(BiomeLayoutConfigValues[] values)
		{
			Values = values;
			byId = new Dictionary<BiomeId, BiomeLayoutConfigValues>(values.Length);
			foreach (BiomeLayoutConfigValues biomeLayoutConfigValues in values)
			{
				byId.Add(biomeLayoutConfigValues.Id, biomeLayoutConfigValues);
			}
			Serialized = Serialize(values);
		}

		internal bool TryGet(BiomeId id, out BiomeLayoutConfigValues values)
		{
			return byId.TryGetValue(id, out values);
		}

		private static string Serialize(IReadOnlyList<BiomeLayoutConfigValues> values)
		{
			StringBuilder stringBuilder = new StringBuilder();
			LengthPrefixedText.Append(stringBuilder, values.Count.ToString(CultureInfo.InvariantCulture));
			foreach (BiomeLayoutConfigValues value in values)
			{
				LengthPrefixedText.Append(stringBuilder, value.Id.Value);
				LengthPrefixedText.Append(stringBuilder, FormatIds(value.Favorites));
				LengthPrefixedText.Append(stringBuilder, FormatIds(value.Overwrites));
				LengthPrefixedText.Append(stringBuilder, FormatIds(value.Before));
				LengthPrefixedText.Append(stringBuilder, FormatIds(value.After));
				LengthPrefixedText.Append(stringBuilder, value.SpawnChance.ToString(CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		internal static bool TryDeserialize(string data, IReadOnlyList<CustomBiomeConfig> expected, out BiomeLayoutConfigSnapshot snapshot)
		{
			snapshot = Empty;
			int Offset = 0;
			if (!LengthPrefixedText.TryRead(data, ref Offset, out string Value) || !int.TryParse(Value, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result != expected.Count)
			{
				return false;
			}
			BiomeLayoutConfigValues[] array = new BiomeLayoutConfigValues[result];
			for (int i = 0; i < array.Length; i++)
			{
				BiomeId biomeId = expected[i].BiomeId;
				if (!LengthPrefixedText.TryRead(data, ref Offset, out string Value2) || !string.Equals(Value2, biomeId.Value, StringComparison.Ordinal) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value3) || !TryParseCanonicalIds(Value3, biomeId, out BiomeId[] result2) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value4) || !TryParseCanonicalIds(Value4, biomeId, out BiomeId[] result3, rejectReservedNonLayout: true) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value5) || !TryParseCanonicalIds(Value5, biomeId, out BiomeId[] result4) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value6) || !TryParseCanonicalIds(Value6, biomeId, out BiomeId[] result5) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value7) || !int.TryParse(Value7, NumberStyles.None, CultureInfo.InvariantCulture, out var result6) || result6 < 0 || result6 > 100)
				{
					return false;
				}
				array[i] = new BiomeLayoutConfigValues(biomeId, result2, result3, result4, result5, result6);
			}
			if (Offset != data.Length)
			{
				return false;
			}
			snapshot = new BiomeLayoutConfigSnapshot(array);
			return string.Equals(snapshot.Serialized, data, StringComparison.Ordinal);
		}

		internal static string FormatIds(IReadOnlyList<BiomeId> values)
		{
			if (values.Count == 0)
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(values.Count * 16);
			for (int i = 0; i < values.Count; i++)
			{
				if (i != 0)
				{
					stringBuilder.Append('>');
				}
				stringBuilder.Append(values[i].Value);
			}
			return stringBuilder.ToString();
		}

		internal static BiomeId[] ParseConfiguredIds(string? value, BiomeId owner, bool rejectReservedNonLayout = false)
		{
			if (string.IsNullOrEmpty(value))
			{
				return Array.Empty<BiomeId>();
			}
			List<BiomeId> list = new List<BiomeId>();
			HashSet<BiomeId> hashSet = new HashSet<BiomeId>();
			string[] array = value.Split(new char[1] { '>' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				if (BiomeId.TryParse(text.Trim(), out var id) && id != owner)
				{
					if (rejectReservedNonLayout && IsReservedNonLayoutBiome(id))
					{
						throw new InvalidOperationException($"Biome '{owner}' cannot overwrite reserved non-layout biome '{id}'.");
					}
					if (hashSet.Add(id))
					{
						list.Add(id);
					}
				}
			}
			list.Sort(CompareIds);
			return list.ToArray();
		}

		internal static bool IsReservedNonLayoutBiome(BiomeId id)
		{
			if (!(id == NativeBiomeIds.Peak) && !(id == NativeBiomeIds.Caldera))
			{
				return id == NativeBiomeIds.Gloom;
			}
			return true;
		}

		private static bool TryParseCanonicalIds(string value, BiomeId owner, out BiomeId[] result, bool rejectReservedNonLayout = false)
		{
			result = Array.Empty<BiomeId>();
			if (value.Length == 0)
			{
				return true;
			}
			string[] array = value.Split('>');
			result = new BiomeId[array.Length];
			for (int i = 0; i < array.Length; i++)
			{
				if (!BiomeId.TryParse(array[i], out var id) || id == owner || (rejectReservedNonLayout && IsReservedNonLayoutBiome(id)) || (i != 0 && StringComparer.Ordinal.Compare(result[i - 1].Value, id.Value) >= 0))
				{
					result = Array.Empty<BiomeId>();
					return false;
				}
				result[i] = id;
			}
			return true;
		}

		private static int CompareIds(BiomeId left, BiomeId right)
		{
			return StringComparer.Ordinal.Compare(left.Value, right.Value);
		}
	}
	internal sealed class CustomBiomeConfigBatch
	{
		internal BiomeId[] Ids { get; }

		internal bool Registered { get; private set; } = true;

		internal CustomBiomeConfigBatch(BiomeId[] ids)
		{
			Ids = ids;
		}

		internal void CompleteUnregister()
		{
			Registered = false;
		}
	}
	internal static class CustomBiomeConfigRegistry
	{
		private const string Section = "Generative Map";

		private const int ReadableStemLength = 48;

		private static readonly Dictionary<BiomeId, CustomBiomeConfigDefaults> Pending = new Dictionary<BiomeId, CustomBiomeConfigDefaults>();

		private static readonly Dictionary<BiomeId, CustomBiomeConfig> Configurations = new Dictionary<BiomeId, CustomBiomeConfig>();

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

		private static BepInPlugin? ownerMetadata;

		private static string? directory;

		private static CustomBiomeConfig[]? startupConfigurations;

		private static bool registrySealed;

		internal static void Initialize(BepInPlugin metadata, string configDirectory)
		{
			if (metadata == null)
			{
				throw new ArgumentNullException("metadata");
			}
			if (configDirectory == null)
			{
				throw new ArgumentNullException("configDirectory");
			}
			string fullPath = Path.GetFullPath(configDirectory);
			if (ownerMetadata != null)
			{
				if (ownerMetadata != metadata || !string.Equals(directory, fullPath, StringComparison.Ordinal))
				{
					throw new InvalidOperationException("The custom biome configuration registry is already initialized.");
				}
			}
			else
			{
				ownerMetadata = metadata;
				directory = fullPath;
			}
		}

		internal static CustomBiomeConfigBatch RegisterDefaults(IReadOnlyList<CustomBiomeConfigDefaults> defaults)
		{
			if (defaults == null)
			{
				throw new ArgumentNullException("defaults");
			}
			if (startupConfigurations != null)
			{
				throw new InvalidOperationException("The startup custom biome configuration registry is sealed.");
			}
			HashSet<BiomeId> hashSet = new HashSet<BiomeId>();
			BiomeId[] array = new BiomeId[defaults.Count];
			for (int i = 0; i < defaults.Count; i++)
			{
				BiomeId id = defaults[i].Id;
				if (!id.IsValid || !hashSet.Add(id) || Pending.ContainsKey(id) || Configurations.ContainsKey(id))
				{
					throw new InvalidOperationException($"Biome '{id}' already has a custom configuration.");
				}
				array[i] = id;
			}
			foreach (CustomBiomeConfigDefaults @default in defaults)
			{
				Pending.Add(@default.Id, @default);
			}
			return new CustomBiomeConfigBatch(array);
		}

		internal static void Unregister(CustomBiomeConfigBatch batch)
		{
			if (batch == null)
			{
				throw new ArgumentNullException("batch");
			}
			if (registrySealed)
			{
				throw new InvalidOperationException("The custom biome configuration registry is sealed.");
			}
			if (!batch.Registered)
			{
				throw new InvalidOperationException("The custom biome configuration batch is already unregistered.");
			}
			BiomeId[] ids = batch.Ids;
			foreach (BiomeId biomeId in ids)
			{
				if (!Pending.ContainsKey(biomeId) && !Configurations.ContainsKey(biomeId))
				{
					throw new InvalidOperationException($"Biome '{biomeId}' has no custom configuration owned by the batch.");
				}
			}
			BiomeId[] ids2 = batch.Ids;
			foreach (BiomeId key in ids2)
			{
				Pending.Remove(key);
				Configurations.Remove(key);
			}
			batch.CompleteUnregister();
		}

		internal static void MaterializePending()
		{
			if (Pending.Count == 0)
			{
				return;
			}
			List<CustomBiomeConfigDefaults> list = new List<CustomBiomeConfigDefaults>(Pending.Values);
			list.Sort((CustomBiomeConfigDefaults left, CustomBiomeConfigDefaults right) => StringComparer.Ordinal.Compare(left.Id.Value, right.Id.Value));
			List<string> createdFiles;
			CustomBiomeConfig[] array = Prepare(list, out createdFiles);
			int num = 0;
			try
			{
				for (; num < array.Length; num++)
				{
					Configurations.Add(array[num].BiomeId, array[num]);
				}
				CustomBiomeConfig[] array2 = array;
				foreach (CustomBiomeConfig customBiomeConfig in array2)
				{
					customBiomeConfig.File.SaveOnConfigSet = true;
				}
				foreach (CustomBiomeConfigDefaults item in list)
				{
					Pending.Remove(item.Id);
				}
			}
			catch (Exception failure)
			{
				for (int num3 = 0; num3 < num; num3++)
				{
					Configurations.Remove(array[num3].BiomeId);
				}
				DeleteFiles(createdFiles, failure);
				throw;
			}
		}

		internal static void SealStartup()
		{
			if (startupConfigurations == null)
			{
				MaterializePending();
				startupConfigurations = SortedConfigurations().ToArray();
			}
		}

		internal static void SealRegistry()
		{
			registrySealed = true;
		}

		internal static BiomeLayoutConfigSnapshot Capture()
		{
			MaterializePending();
			CustomBiomeConfig[] array = startupConfigurations;
			List<CustomBiomeConfig> list = ((array != null) ? new List<CustomBiomeConfig>(array) : SortedConfigurations());
			BiomeLayoutConfigValues[] array2 = new BiomeLayoutConfigValues[list.Count];
			for (int i = 0; i < array2.Length; i++)
			{
				CustomBiomeConfig customBiomeConfig = list[i];
				BiomeId biomeId = customBiomeConfig.BiomeId;
				array2[i] = new BiomeLayoutConfigValues(biomeId, BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.Favorites.Value, biomeId), BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.Overwrites.Value, biomeId, rejectReservedNonLayout: true), BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.Before.Value, biomeId), BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.After.Value, biomeId), Math.Max(0, Math.Min(100, customBiomeConfig.SpawnChance.Value)));
			}
			return new BiomeLayoutConfigSnapshot(array2);
		}

		internal static BiomeLayoutConfigSnapshot Active(BiomeLayoutConfigSnapshot snapshot)
		{
			if (snapshot == null)
			{
				throw new ArgumentNullException("snapshot");
			}
			List<BiomeLayoutConfigValues> list = new List<BiomeLayoutConfigValues>();
			foreach (BiomeLayoutConfigValues value in snapshot.Values)
			{
				if (Configurations.ContainsKey(value.Id))
				{
					list.Add(value);
				}
			}
			if (list.Count != snapshot.Values.Count)
			{
				return new BiomeLayoutConfigSnapshot(list.ToArray());
			}
			return snapshot;
		}

		internal static bool TryParseSnapshot(string data, out BiomeLayoutConfigSnapshot snapshot)
		{
			if (data == null)
			{
				snapshot = BiomeLayoutConfigSnapshot.Empty;
				return false;
			}
			MaterializePending();
			CustomBiomeConfig[] array = startupConfigurations;
			IReadOnlyList<CustomBiomeConfig> readOnlyList2;
			if (array == null)
			{
				IReadOnlyList<CustomBiomeConfig> readOnlyList = SortedConfigurations();
				readOnlyList2 = readOnlyList;
			}
			else
			{
				IReadOnlyList<CustomBiomeConfig> readOnlyList = array;
				readOnlyList2 = readOnlyList;
			}
			IReadOnlyList<CustomBiomeConfig> expected = readOnlyList2;
			return BiomeLayoutConfigSnapshot.TryDeserialize(data, expected, out snapshot);
		}

		private static List<CustomBiomeConfig> SortedConfigurations()
		{
			List<CustomBiomeConfig> list = new List<CustomBiomeConfig>(Configurations.Values);
			list.Sort((CustomBiomeConfig left, CustomBiomeConfig right) => StringComparer.Ordinal.Compare(left.BiomeId.Value, right.BiomeId.Value));
			return list;
		}

		private static CustomBiomeConfig[] Prepare(IReadOnlyList<CustomBiomeConfigDefaults> defaults, out List<string> createdFiles)
		{
			BepInPlugin metadata = ownerMetadata ?? throw new InvalidOperationException("The custom biome configuration registry is not initialized.");
			string text = directory;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (CustomBiomeConfig value in Configurations.Values)
			{
				hashSet.Add(value.ConfigPath);
			}
			string[] array = new string[defaults.Count];
			for (int i = 0; i < defaults.Count; i++)
			{
				BiomeId id = defaults[i].Id;
				string text2 = Path.Combine(text, FileName(id));
				if (!hashSet.Add(text2))
				{
					throw new InvalidOperationException($"Biome '{id}' collides with another custom configuration path.");
				}
				array[i] = text2;
			}
			Directory.CreateDirectory(text);
			createdFiles = new List<string>();
			CustomBiomeConfig[] array2 = new CustomBiomeConfig[defaults.Count];
			try
			{
				for (int j = 0; j < array2.Length; j++)
				{
					bool flag = File.Exists(array[j]);
					array2[j] = Bind(array[j], metadata, defaults[j]);
					if (!flag)
					{
						createdFiles.Add(array[j]);
						array2[j].File.Save();
					}
				}
				return array2;
			}
			catch (Exception failure)
			{
				DeleteFiles(createdFiles, failure);
				throw;
			}
		}

		private static void DeleteFiles(IReadOnlyList<string> paths, Exception failure)
		{
			List<Exception> list = null;
			foreach (string path in paths)
			{
				try
				{
					File.Delete(path);
				}
				catch (Exception item)
				{
					(list ?? (list = new List<Exception>())).Add(item);
				}
			}
			if (list == null)
			{
				return;
			}
			list.Insert(0, failure);
			throw new AggregateException("Custom biome configuration transaction and its rollback both failed.", list);
		}

		private static CustomBiomeConfig Bind(string path, BepInPlugin metadata, CustomBiomeConfigDefaults defaults)
		{
			//IL_0003: 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)
			//IL_0010: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Expected O, but got Unknown
			ConfigFile val = new ConfigFile(path, false, metadata)
			{
				SaveOnConfigSet = false
			};
			ConfigEntry<string> favorites = val.Bind<string>("Generative Map", "Favorites", FormatDefaults(defaults.Favorites), "Biome IDs preferred next to this biome, separated by >.");
			ConfigEntry<string> overwrites = val.Bind<string>("Generative Map", "Overwrites", FormatDefaults(defaults.Overwrites), "Biome IDs this biome may replace, separated by >.");
			ConfigEntry<string> before = val.Bind<string>("Generative Map", "Before", FormatDefaults(defaults.Before), "Biome IDs this biome prefers to precede, separated by >.");
			ConfigEntry<string> after = val.Bind<string>("Generative Map", "After", FormatDefaults(defaults.After), "Biome IDs this biome prefers to follow, separated by >.");
			ConfigEntry<int> spawnChance = val.Bind<int>("Generative Map", "Spawn Chance", defaults.SpawnChance, new ConfigDescription("Chance from 0 to 100 that Generative Map includes this biome.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			return new CustomBiomeConfig(defaults.Id, path, val, favorites, overwrites, before, after, spawnChance);
		}

		private static string FormatDefaults(IReadOnlyList<BiomeId> values)
		{
			if (values.Count == 0)
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(values.Count * 18);
			for (int i = 0; i < values.Count; i++)
			{
				if (i != 0)
				{
					stringBuilder.Append(" > ");
				}
				stringBuilder.Append(values[i].Value);
			}
			return stringBuilder.ToString();
		}

		private static string FileName(BiomeId id)
		{
			StringBuilder stringBuilder = new StringBuilder(Math.Min(id.Value.Length, 48));
			bool flag = false;
			string value = id.Value;
			foreach (char c in value)
			{
				bool flag2;
				switch (c)
				{
				case '-':
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '_':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
				case 'g':
				case 'h':
				case 'i':
				case 'j':
				case 'k':
				case 'l':
				case 'm':
				case 'n':
				case 'o':
				case 'p':
				case 'q':
				case 'r':
				case 's':
				case 't':
				case 'u':
				case 'v':
				case 'w':
				case 'x':
				case 'y':
				case 'z':
					flag2 = true;
					break;
				default:
					flag2 = false;
					break;
				}
				if (flag2)
				{
					if (stringBuilder.Length >= 48)
					{
						break;
					}
					stringBuilder.Append(c);
					flag = false;
				}
				else if (!flag && stringBuilder.Length != 0 && stringBuilder.Length < 48)
				{
					stringBuilder.Append('-');
					flag = true;
				}
			}
			while (stringBuilder.Length != 0 && stringBuilder[stringBuilder.Length - 1] == '-')
			{
				stringBuilder.Length--;
			}
			if (stringBuilder.Length == 0)
			{
				stringBuilder.Append("biome");
			}
			using SHA256 sHA = SHA256.Create();
			string arg = BitConverter.ToString(sHA.ComputeHash(StrictUtf8.GetBytes(id.Value))).Replace("-", string.Empty);
			return $"{stringBuilder}-{arg}.cfg";
		}
	}
	public static class FileManager
	{
		internal const string DefaultLayoutOrder = "Beach > Tropics > Roots > Alpine > Mesa > Volcano > Citadel > Nadir";

		internal const string DefaultRepeatable = "";

		internal const string DefaultChances = "";

		public static ConfigEntry<string> LayoutOrder { get; private set; }

		public static ConfigEntry<string> Seed { get; private set; }

		public static ConfigEntry<bool> ReplaceGenerated { get; private set; }

		public static ConfigEntry<bool> AdvertiseLayout { get; private set; }

		public static ConfigEntry<bool> DoOrder { get; private set; }

		public static ConfigEntry<bool> GenerativeMap { get; private set; }

		public static ConfigEntry<bool> InfiniteMap { get; private set; }

		public static ConfigEntry<string> InfiniteMapRepeatable { get; private set; }

		public static ConfigEntry<string> InfiniteMapChances { get; private set; }

		public static ConfigEntry<int> InfiniteMapScale { get; private set; }

		public static ConfigEntry<int> InfiniteMapBeginIndex { get; private set; }

		public static ConfigEntry<bool> InfiniteMapRemoveOld { get; private set; }

		public static ConfigEntry<bool> Winnable { get; private set; }

		public static ConfigEntry<bool> PeakAtEnd { get; private set; }

		public static ConfigEntry<bool> GemlessNadir { get; private set; }

		public static ConfigEntry<bool> WaterFollowsBeach { get; private set; }

		public static ConfigEntry<bool> VoidWaterFollowsNadir { get; private set; }

		public static ConfigEntry<bool> RelocatePlane { get; private set; }

		public static ConfigEntry<bool> RelocateStart { get; private set; }

		public static ConfigEntry<bool> ValidNadir { get; private set; }

		public static ConfigEntry<bool> LazyGeneration { get; private set; }

		public static ConfigEntry<bool> Fogless { get; private set; }

		public static ConfigEntry<bool> NoRisingGhosts { get; private set; }

		public static ConfigEntry<bool> NoSleepFog { get; private set; }

		public static ConfigEntry<bool> NoWater { get; private set; }

		public static ConfigEntry<bool> NoVoidWater { get; private set; }

		public static ConfigEntry<bool> NoPlane { get; private set; }

		public static ConfigEntry<bool> NoSkyProps { get; private set; }

		public static ConfigEntry<bool> RemoveOld { get; private set; }

		internal static void Initialize(BepInPlugin OwnerMetadata)
		{
			string text = Path.Combine(Paths.ConfigPath, "AF", "PLL_Library");
			Directory.CreateDirectory(text);
			CustomBiomeConfigRegistry.Initialize(OwnerMetadata, Path.Combine(text, "Custom Segments"));
			ConfigBindings configBindings = ConfigDocument.Open(Path.Combine(text, "config.cfg"), OwnerMetadata);
			LayoutOrder = configBindings.LayoutOrder;
			Seed = configBindings.Seed;
			ReplaceGenerated = configBindings.ReplaceGenerated;
			AdvertiseLayout = configBindings.AdvertiseLayout;
			DoOrder = configBindings.DoOrder;
			GenerativeMap = configBindings.GenerativeMap;
			InfiniteMap = configBindings.InfiniteMap;
			InfiniteMapRepeatable = configBindings.InfiniteMapRepeatable;
			InfiniteMapChances = configBindings.InfiniteMapChances;
			InfiniteMapScale = configBindings.InfiniteMapScale;
			InfiniteMapBeginIndex = configBindings.InfiniteMapBeginIndex;
			InfiniteMapRemoveOld = configBindings.InfiniteMapRemoveOld;
			Winnable = configBindings.Winnable;
			PeakAtEnd = configBindings.PeakAtEnd;
			GemlessNadir = configBindings.GemlessNadir;
			WaterFollowsBeach = configBindings.WaterFollowsBeach;
			VoidWaterFollowsNadir = configBindings.VoidWaterFollowsNadir;
			RelocatePlane = configBindings.RelocatePlane;
			RelocateStart = configBindings.RelocateStart;
			ValidNadir = configBindings.ValidNadir;
			LazyGeneration = configBindings.LazyGeneration;
			Fogless = configBindings.Fogless;
			NoRisingGhosts = configBindings.NoRisingGhosts;
			NoSleepFog = configBindings.NoSleepFog;
			NoWater = configBindings.NoWater;
			NoVoidWater = configBindings.NoVoidWater;
			NoPlane = configBindings.NoPlane;
			NoSkyProps = configBindings.NoSkyProps;
			RemoveOld = configBindings.RemoveOld;
		}
	}
	internal static class AirportConnectionTransport
	{
		[HarmonyPatch]
		private static class AirportLoadPatch
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return HandleMessage;
				yield return OnLobbyCreated;
			}

			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> Instructions, MethodBase __originalMethod)
			{
				List<CodeInstruction> list = new List<CodeInstruction>(Instructions);
				int num = 0;
				int num2 = 0;
				foreach (CodeInstruction item in list)
				{
					if (CodeInstructionExtensions.Calls(item, LoadSceneProcess))
					{
						item.opcode = OpCodes.Call;
						item.operand = WrapLoadSceneProcess;
						num++;
					}
					else if (CodeInstructionExtensions.Calls(item, Load))
					{
						num2++;
					}
				}
				if (num != 1 || num2 != 1)
				{
					throw new MissingMethodException(__originalMethod.DeclaringType?.FullName + "." + __originalMethod.Name + " contained " + $"{num} Airport scene process calls and {num2} loading calls.");
				}
				return list;
			}
		}

		private static readonly MethodInfo HandleMessage = AccessTools.DeclaredMethod(typeof(SteamLobbyHandler), "HandleMessage", new Type[3]
		{
			typeof(MessageType),
			typeof(BinaryDeserializer),
			typeof(CSteamID)
		}, (Type[])null) ?? throw new MissingMethodException(typeof(SteamLobbyHandler).FullName, "HandleMessage");

		private static readonly MethodInfo OnLobbyCreated = AccessTools.DeclaredMethod(typeof(SteamLobbyHandler), "OnLobbyCreated", new Type[1] { typeof(LobbyCreated_t) }, (Type[])null) ?? throw new MissingMethodException(typeof(SteamLobbyHandler).FullName, "OnLobbyCreated");

		private static readonly MethodInfo LoadSceneProcess = AccessTools.DeclaredMethod(typeof(LoadingScreenHandler), "LoadSceneProcess", new Type[4]
		{
			typeof(string),
			typeof(bool),
			typeof(bool),
			typeof(float)
		}, (Type[])null) ?? throw new MissingMethodException(typeof(LoadingScreenHandler).FullName, "LoadSceneProcess");

		private static readonly MethodInfo Load = AccessTools.DeclaredMethod(typeof(LoadingScreenHandler), "Load", new Type[3]
		{
			typeof(LoadingScreenType),
			typeof(Action),
			typeof(IEnumerator[])
		}, (Type[])null) ?? throw new MissingMethodException(typeof(LoadingScreenHandler).FullName, "Load");

		private static readonly MethodInfo WrapLoadSceneProcess = new Func<LoadingScreenHandler, string, bool, bool, float, IEnumerator>(Wrap).Method;

		private static IEnumerator Wrap(LoadingScreenHandler Handler, string SceneName, bool Networked, bool YieldForCharacterSpawn, float ExtraYieldTimeOnEnd)
		{
			return SuspendedPhotonTransport.SendOutgoingAtYields(Handler.LoadSceneProcess(SceneName, Networked, YieldForCharacterSpawn, ExtraYieldTimeOnEnd), null);
		}
	}
	internal sealed class FinalManifestConfirmation
	{
		private readonly int[] Actors;

		internal Room Room { get; }

		internal ResolvedRunConfiguration Configuration { get; }

		internal string Revision { get; }

		internal int LocalActor { get; }

		internal FinalManifestConfirmation(Room Room, ResolvedRunConfiguration Configuration, string Revision, int LocalActor, int[] Actors)
		{
			this.Room = Room;
			this.Configuration = Configuration;
			this.Revision = Revision;
			this.LocalActor = LocalActor;
			this.Actors = (int[])Actors.Clone();
		}

		internal int[] SnapshotActors()
		{
			return (int[])Actors.Clone();
		}
	}
	internal static class FinalManifestConsensus
	{
		private abstract class Round
		{
			internal ResolvedRunConfiguration Configuration { get; }

			internal string Revision { get; }

			internal string Epoch => Configuration.Protocol.GenerationEpoch;

			protected Round(ResolvedRunConfiguration Configuration, string Revision)
			{
				this.Configuration = Configuration;
				this.Revision = Revision;
			}
		}

		private sealed class OfflineRound : Round
		{
			internal Room? Room { get; }

			internal OfflineRound(Room? Room, ResolvedRunConfiguration Configuration, string Revision)
				: base(Configuration, Revision)
			{
				this.Room = Room;
			}
		}

		private sealed class OnlineRound : Round
		{
			internal Room Room { get; }

			internal int LocalActor { get; }

			internal string LocalReadyKey { get; }

			internal object[] ReadyValue { get; }

			internal RoomPropertyPublication ReadyPublication { get; } = new RoomPropertyPublication();

			internal RoomPropertyPublication CommitPublication { get; } = new RoomPropertyPublication();

			internal int MasterActor { get; private set; }

			internal string? MasterReadyKey { get; private set; }

			internal int[] Actors { get; private set; }

			internal string[] ActorReadyKeys { get; private set; }

			internal object[]? DesiredCommit { get; set; }

			internal OnlineRound(Room Room, ResolvedRunConfiguration Configuration, string Revision, int LocalActor, int MasterActor, int[] Actors)
				: base(Configuration, Revision)
			{
				this.Room = Room;
				this.LocalActor = LocalActor;
				LocalReadyKey = ReadyKey(LocalActor);
				this.Actors = Array.Empty<int>();
				ActorReadyKeys = Array.Empty<string>();
				ReplaceActors(Actors);
				ReplaceMaster(MasterActor);
				ReadyValue = CreateReadyValue(base.Epoch, Revision);
			}

			internal void ReplaceActors(int[] Actors)
			{
				this.Actors = Actors;
				ActorReadyKeys = new string[Actors.Length];
				for (int i = 0; i < Actors.Length; i++)
				{
					ActorReadyKeys[i] = ((Actors[i] == LocalActor) ? LocalReadyKey : ReadyKey(Actors[i]));
				}
				RefreshMasterReadyKey();
				ResetCommit();
			}

			internal void ReplaceMaster(int Actor)
			{
				MasterActor = Actor;
				RefreshMasterReadyKey();
				ResetCommit();
			}

			internal void ResetCommit()
			{
				DesiredCommit = null;
				CommitPublication.Reset();
			}

			private void RefreshMasterReadyKey()
			{
				MasterReadyKey = null;
				for (int i = 0; i < Actors.Length; i++)
				{
					if (Actors[i] == MasterActor)
					{
						MasterReadyKey = ActorReadyKeys[i];
						break;
					}
				}
			}
		}

		private const string Protocol = "1";

		private const string ReadyPrefix = "af.pll.library/state/final-ready/";

		private const string CommitKey = "af.pll.library/state/final-commit";

		private static Round? CurrentRound;

		internal static void Reset()
		{
			CurrentRound = null;
		}

		internal static void ConfirmOffline(Room? Room, ResolvedRunConfiguration Configuration, string Revision)
		{
			CurrentRound = new OfflineRound(Room, Configuration, Revision);
		}

		internal static IEnumerator Wait(ResolvedRunConfiguration Configuration)
		{
			Room currentRoom = PhotonNetwork.CurrentRoom;
			string manifestRevision = SegmentRegistry.ManifestRevision;
			if (currentRoom == null || currentRoom.IsOffline)
			{
				ConfirmOffline(currentRoom, Configuration, manifestRevision);
				yield break;
			}
			if (!currentRoom.BroadcastPropertiesChangeToAll)
			{
				throw PhotonRoomRejection.Disconnect("PLL Library cannot verify the final custom-content manifest without server property echoes.");
			}
			if ((int)PhotonNetwork.NetworkClientState != 9)
			{
				throw PhotonRoomRejection.Disconnect("PLL Library lost its Photon room before final custom-content validation.");
			}
			Player masterClient = PhotonNetwork.MasterClient;
			if (masterClient == null || masterClient.ActorNumber <= 0 || !currentRoom.Players.ContainsKey(masterClient.ActorNumber))
			{
				throw PhotonRoomRejection.Disconnect("PLL Library could not identify the authoritative player for final custom-content validation.");
			}
			int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber;
			if (actorNumber <= 0 || !currentRoom.Players.ContainsKey(actorNumber))
			{
				throw PhotonRoomRejection.Disconnect("PLL Library could not identify the local player for final custom-content validation.");
			}
			if (!TryCurrentActors(currentRoom, out int[] Actors))
			{
				throw PhotonRoomRejection.Disconnect("PLL Library found an invalid player set during final custom-content validation.");
			}
			OnlineRound Consensus = (OnlineRound)(CurrentRound = ((CurrentRound is OnlineRound onlineRound && onlineRound.Room == currentRoom && onlineRound.LocalActor == actorNumber && SameIdentity(onlineRound, Configuration, manifestRevision)) ? onlineRound : new OnlineRound(currentRoom, Configuration, manifestRevision, actorNumber, masterClient.ActorNumber, Actors)));
			while (true)
			{
				if (!IsCurrent(Consensus))
				{
					if (CurrentRound == Consensus)
					{
						CurrentRound = null;
					}
					throw PhotonRoomRejection.Disconnect("PLL Library lost its Photon room before final custom-content validation.");
				}
				if (!Consensus.Room.BroadcastPropertiesChangeToAll)
				{
					throw PhotonRoomRejection.Disconnect("PLL Library cannot verify the final custom-content manifest without server property echoes.");
				}
				if (TryMaintainAndConfirm(Consensus))
				{
					break;
				}
				yield return null;
			}
		}

		internal static void RequireConfirmed(ResolvedRunConfiguration Configuration)
		{
			Room currentRoom = PhotonNetwork.CurrentRoom;
			string manifestRevision = SegmentRegistry.ManifestRevision;
			if (currentRoom == null || currentRoom.IsOffline)
			{
				if (!(CurrentRound is OfflineRound offlineRound) || offlineRound.Room != currentRoom || !SameIdentity(offlineRound, Configuration, manifestRevision))
				{
					CurrentRound = null;
					throw new InvalidOperationException("PLL Library's final custom-content manifest is not confirmed.");
				}
				return;
			}
			if (!(CurrentRound is OnlineRound onlineRound) || onlineRound.Room != currentRoom || !SameIdentity(onlineRound, Configuration, manifestRevision) || !IsCurrent(onlineRound))
			{
				CurrentRound = null;
				throw PhotonRoomRejection.Disconnect("PLL Library's final custom-content manifest is not confirmed.");
			}
			if (!TryMaintainAndConfirm(onlineRound))
			{
				throw PhotonRoomRejection.Disconnect("PLL Library's final custom-content manifest confirmation is no longer valid.");
			}
		}

		internal static FinalManifestConfirmation? CaptureConfirmation(ResolvedRunConfiguration Configuration)
		{
			RequireConfirmed(Configuration);
			if (!(CurrentRound is OnlineRound onlineRound))
			{
				return null;
			}
			return new FinalManifestConfirmation(onlineRound.Room, Configuration, onlineRound.Revision, onlineRound.LocalActor, onlineRound.Actors);
		}

		internal static bool Owns(ResolvedRunConfiguration Configuration, Room Room)
		{
			if (CurrentRound is OnlineRound onlineRound && onlineRound.Room == Room && SameIdentity(onlineRound, Configuration, SegmentRegistry.ManifestRevision))
			{
				return IsCurrent(onlineRound);
			}
			return false;
		}

		internal static bool TryMaintainConfirmation(ResolvedRunConfiguration Configuration)
		{
			if (!(CurrentRound is OnlineRound onlineRound) || onlineRound.Configuration != Configuration || !IsCurrent(onlineRound))
			{
				throw PhotonRoomRejection.Disconnect("PLL Library's final custom-content manifest is not confirmed for initial generation.");
			}
			return TryMaintainAndConfirm(onlineRound);
		}

		internal static void ConnectionStateChanged(ClientState Current)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Invalid comparison between Unknown and I4
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			if (Current - 13 <= 1)
			{
				CurrentRound = null;
			}
			else if ((int)Current == 9)
			{
				MaintainCurrent();
			}
		}

		private static string ReadyKey(int Actor)
		{
			return "af.pll.library/state/final-ready/" + Actor.ToString(CultureInfo.InvariantCulture);
		}

		private static object[] CreateReadyValue(string Epoch, string Revision)
		{
			return new object[3] { "1", Epoch, Revision };
		}

		private static object[] CreateCommitValue(string Epoch, string Revision, int MasterActor, int[] Actors)
		{
			return new object[5] { "1", Epoch, Revision, MasterActor, Actors };
		}

		private static bool SameIdentity(Round Consensus, ResolvedRunConfiguration Configuration, string Revision)
		{
			if (Consensus.Configuration == Configuration)
			{
				return string.Equals(Consensus.Revision, Revision, StringComparison.Ordinal);
			}
			return false;
		}

		private static bool IsCurrent(OnlineRound Consensus)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			if (CurrentRound == Consensus && PhotonNetwork.CurrentRoom == Consensus.Room && (int)PhotonNetwork.NetworkClientState == 9)
			{
				return string.Equals(Consensus.Revision, SegmentRegistry.ManifestRevision, StringComparison.Ordinal);
			}
			return false;
		}

		private static bool TryCurrentActors(Room Room, out int[] Actors)
		{
			Actors = new int[Room.Players.Count];
			int num = 0;
			foreach (int key in Room.Players.Keys)
			{
				if (key <= 0 || num == Actors.Length)
				{
					return false;
				}
				Actors[num++] = key;
			}
			if (num != Actors.Length || num == 0)
			{
				return false;
			}
			Array.Sort(Actors);
			return true;
		}

		private static bool CurrentActorsMatch(OnlineRound Consensus)
		{
			if (Consensus.Actors.Length != Consensus.Room.Players.Count)
			{
				return false;
			}
			int[] actors = Consensus.Actors;
			foreach (int key in actors)
			{
				if (!Consensus.Room.Players.ContainsKey(key))
				{
					return false;
				}
			}
			return true;
		}

		private static bool RefreshTopology(OnlineRound Consensus)
		{
			if (!CurrentActorsMatch(Consensus))
			{
				if (!TryCurrentActors(Consensus.Room, out int[] Actors))
				{
					return false;
				}
				Consensus.ReplaceActors(Actors);
			}
			Player masterClient = PhotonNetwork.MasterClient;
			int num = ((masterClient != null) ? masterClient.ActorNumber : 0);
			if (num <= 0 || !Consensus.Room.Players.ContainsKey(num))
			{
				if (Consensus.MasterActor != 0)
				{
					Consensus.ReplaceMaster(0);
				}
				return false;
			}
			if (Consensus.MasterActor != num)
			{
				Consensus.ReplaceMaster(num);
			}
			return Consensus.Room.Players.ContainsKey(Consensus.LocalActor);
		}

		private static bool TryMaintainAndConfirm(OnlineRound Consensus)
		{
			bool LocalReady;
			bool flag = MaintainLocal(Consensus, out LocalReady);
			string text = PublicationFailure(Consensus);
			if (text != null)
			{
				throw PhotonRoomRejection.Disconnect(text);
			}
			if (!flag || !LocalReady)
			{
				return false;
			}
			if (Consensus.MasterActor == Consensus.LocalActor)
			{
				bool CommitEchoed;
				bool flag2 = MaintainMaster(Consensus, ValidateIdentity: true, out CommitEchoed);
				string text2 = PublicationFailure(Consensus);
				if (text2 != null)
				{
					throw PhotonRoomRejection.Disconnect(text2);
				}
				if (flag2)
				{
					if (!CommitEchoed)
					{
						return CommitMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)"af.pll.library/state/final-commit"], Consensus);
					}
					return true;
				}
				return false;
			}
			if (Consensus.MasterReadyKey == null || !ReadyMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)Consensus.MasterReadyKey], Consensus, Consensus.MasterActor, ValidateIdentity: true))
			{
				return false;
			}
			if (!CommitMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)"af.pll.library/state/final-commit"], Consensus))
			{
				return false;
			}
			return CurrentReadySetMatches(Consensus, ValidateIdentity: true, Consensus.MasterActor);
		}

		private static bool MaintainLocal(OnlineRound Consensus, out bool LocalReady)
		{
			LocalReady = false;
			if (PublicationFailure(Consensus) != null || !IsCurrent(Consensus) || !RefreshTopology(Consensus))
			{
				return false;
			}
			LocalReady = Consensus.ReadyPublication.AwaitEcho(Consensus.Room, Consensus.LocalReadyKey, Consensus.ReadyValue, "PLL Library could not publish its final custom-content manifest.");
			return true;
		}

		private static bool MaintainMaster(OnlineRound Consensus, bool ValidateIdentity, out bool CommitEchoed)
		{
			bool flag = CurrentReadySetMatches(Consensus, ValidateIdentity, 0);
			CommitEchoed = MaintainCommit(Consensus, flag);
			return flag;
		}

		private static bool MaintainCommit(OnlineRound Consensus, bool AllReady)
		{
			if (!AllReady)
			{
				Consensus.ResetCommit();
				return false;
			}
			if (Consensus.DesiredCommit == null)
			{
				object[] array = (Consensus.DesiredCommit = CreateCommitValue(Consensus.Epoch, Consensus.Revision, Consensus.MasterActor, Consensus.Actors));
			}
			return Consensus.CommitPublication.AwaitEcho(Consensus.Room, "af.pll.library/state/final-commit", Consensus.DesiredCommit, "PLL Library could not publish the final custom-content manifest release.");
		}

		private static string? PublicationFailure(OnlineRound Consensus)
		{
			return Consensus.ReadyPublication.Failure ?? Consensus.CommitPublication.Failure;
		}

		internal static void MaintainCurrent()
		{
			Room currentRoom = PhotonNetwork.CurrentRoom;
			if (CurrentRound is OfflineRound offlineRound)
			{
				if (offlineRound.Room != currentRoom || (currentRoom != null && !currentRoom.IsOffline) || !string.Equals(offlineRound.Revision, SegmentRegistry.ManifestRevision, StringComparison.Ordinal))
				{
					CurrentRound = null;
				}
			}
			else
			{
				if (!(CurrentRound is OnlineRound onlineRound))
				{
					return;
				}
				bool LocalReady;
				if (!IsCurrent(onlineRound))
				{
					if (CurrentRound == onlineRound)
					{
						CurrentRound = null;
					}
				}
				else if (MaintainLocal(onlineRound, out LocalReady) && LocalReady && PublicationFailure(onlineRound) == null && onlineRound.MasterActor == onlineRound.LocalActor)
				{
					MaintainMaster(onlineRound, ValidateIdentity: false, out var _);
				}
			}
		}

		private static bool CurrentReadySetMatches(OnlineRound Consensus, bool ValidateIdentity, int ExcludedActor)
		{
			for (int i = 0; i < Consensus.Actors.Length; i++)
			{
				int num = Consensus.Actors[i];
				if (num != Consensus.LocalActor && num != ExcludedActor && !ReadyMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)Consensus.ActorReadyKeys[i]], Consensus, num, ValidateIdentity))
				{
					return false;
				}
			}
			return true;
		}

		private static bool ReadyMatches(object? Value, OnlineRound Consensus, int Actor, bool ValidateIdentity)
		{
			if (!(Value is object[] array) || array.Length < 2 || !(array[1] is string a) || !string.Equals(a, Consensus.Epoch, StringComparison.Ordinal))
			{
				return false;
			}
			if (array.Length != 3 || !(array[0] is string a2) || !(array[2] is string a3))
			{
				if (ValidateIdentity)
				{
					throw PhotonRoomRejection.Disconnect($"Player {Actor} published a malformed final custom-content manifest.");
				}
				return false;
			}
			if (!string.Equals(a2, "1", StringComparison.Ordinal))
			{
				if (ValidateIdentity)
				{
					throw PhotonRoomRejection.Disconnect($"Player {Actor} uses an incompatible final custom-content protocol.");
				}
				return false;
			}
			if (string.Equals(a3, Consensus.Revision, StringComparison.Ordinal))
			{
				return true;
			}
			if (ValidateIdentity)
			{
				throw PhotonRoomRejection.Disconnect($"Player {Actor} has different surviving PLL content.");
			}
			return false;
		}

		private static bool CommitMatches(object? Value, OnlineRound Consensus)
		{
			if (!(Value is object[] array) || array.Length < 2 || !(array[1] is string a) || !string.Equals(a, Consensus.Epoch, StringComparison.Ordinal))
			{
				return false;
			}
			if (array.Length != 5 || !(array[0] is string a2) || !(array[2] is string a3) || !(array[3] is int num) || !(array[4] is int[] array2))
			{
				throw PhotonRoomRejection.Disconnect("The host published a malformed final custom-content manifest release.");
			}
			if (num != Consensus.MasterActor)
			{
				return false;
			}
			if (!string.Equals(a2, "1", StringComparison.Ordinal))
			{
				throw PhotonRoomRejection.Disconnect("The host uses an incompatible final custom-content protocol.");
			}
			if (!string.Equals(a3, Consensus.Revision, StringComparison.Ordinal))
			{
				throw PhotonRoomRejection.Disconnect("The host has different surviving PLL content.");
			}
			if (array2.Length != Consensus.Actors.Length)
			{
				return false;
			}
			for (int i = 0; i < array2.Length; i++)
			{
				if (array2[i] != Consensus.Actors[i])
				{
					return false;
				}
			}
			return true;
		}
	}
	internal static class InitialGenerationCohortBarrier
	{
		private enum PropertyState
		{
			Absent,
			Valid,
			Invalid
		}

		private const string Protocol = "1";

		private const string CohortKey = "af.pll.library/state/generation/cohort";

		private const string ReadyPrefix = "af.pll.library/state/generation/cohort-ready/";

		internal static IEnumerator Wait(ResolvedRunConfiguration Configuration, Action<MapRun.GenerationCohort?> Capture)
		{
			if (Configuration == null)
			{
				throw new ArgumentNullException("Configuration");
			}
			if (Capture == null)
			{
				throw new ArgumentNullException("Capture");
			}
			FinalManifestConfirmation Confirmation = FinalManifestConsensus.CaptureConfirmation(Configuration);
			if (Confirmation == null)
			{
				Capture(null);
				yield break;
			}
			Room Room = Confirmation.Room;
			MapRun.GenerationCohort ProposedCohort = new MapRun.GenerationCohort(Configuration.Protocol.GenerationEpoch, Confirmation.Revision, Confirmation.SnapshotActors());
			object[] ProposedValue = Encode(ProposedCohort);
			RoomPropertyPublication CohortPublication = new RoomPropertyPublication();
			RoomPropertyPublication ReadyPublication = new RoomPropertyPublication();
			MapRun.GenerationCohort Cohort = null;
			object[] CohortValue = null;
			string[] ReadyKeys = null;
			string LocalReadyKey = null;
			object CachedProperty = null;
			MapRun.GenerationCohort CachedCohort = null;
			string CachedFailure = null;
			PropertyState CachedState = PropertyState.Absent;
			bool PropertyCached = false;
			while (true)
			{
				if (PhotonNetwork.CurrentRoom != Room || (int)PhotonNetwork.NetworkClientState != 9 || !FinalManifestConsensus.Owns(Configuration, Room))
				{
					throw PhotonRoomRejection.Disconnect("PLL Library lost its Photon room before initial generation was sealed.");
				}
				if (!Room.BroadcastPropertiesChangeToAll)
				{
					break;
				}
				bool flag = FinalManifestConsensus.TryMaintainConfirmation(Configuration);
				object obj = ((RoomInfo)Room).CustomProperties[(object)"af.pll.library/state/generation/cohort"];
				PropertyState propertyState;
				MapRun.GenerationCohort Cohort2;
				string Failure;
				if (PropertyCached && obj == CachedProperty)
				{
					propertyState = CachedState;
					Cohort2 = CachedCohort;
					Failure = CachedFailure;
				}
				else
				{
					if (CohortValue != null && PhotonPropertyProtocol.SameValue(obj, CohortValue))
					{
						propertyState = PropertyState.Valid;
						Cohort2 = Cohort;
						Failure = null;
					}
					else
					{
						propertyState = Read(obj, Configuration.Protocol.GenerationEpoch, Confirmation.Revision, out Cohort2, out Failure);
					}
					CachedProperty = obj;
					CachedState = propertyState;
					CachedCohort = Cohort2;
					CachedFailure = Failure;
					PropertyCached = true;
				}
				switch (propertyState)
				{
				case PropertyState.Invalid:
					throw PhotonRoomRejection.Disconnect(Failure);
				case PropertyState.Absent:
					if (Cohort != null)
					{
						throw PhotonRoomRejection.Disconnect("The initial generation cohort disappeared before release.");
					}
					RequireActorsPresent(Room, ProposedCohort);
					if (PhotonNetwork.IsMasterClient)
					{
						CohortPublication.AwaitEcho(Room, "af.pll.library/state/generation/cohort", ProposedValue, "PLL Library could not publish the initial generation cohort.");
						string failure = CohortPublication.Failure;
						if (failure != null)
						{
							throw PhotonRoomRejection.Disconnect(failure);
						}
					}
					yield return null;
					continue;
				}
				if (Cohort == null)
				{
					Cohort = Cohort2;
					CohortValue = Encode(Cohort);
					ReadyKeys = new string[Cohort.Count];
					for (int i = 0; i < ReadyKeys.Length; i++)
					{
						int num = Cohort.ActorAt(i);
						string text = (ReadyKeys[i] = ReadyKey(num));
						if (num == Confirmation.LocalActor)
						{
							LocalReadyKey = text;
						}
					}
				}
				else if (!Cohort.Matches(Cohort2))
				{
					throw PhotonRoomRejection.Disconnect("The initial generation cohort changed before release.");
				}
				if (LocalReadyKey == null)
				{
					if (!flag)
					{
						yield return null;
						continue;
					}
					Capture(Cohort);
					yield break;
				}
				RequireActorsPresent(Room, Cohort);
				bool flag2 = ReadyPublication.AwaitEcho(Room, LocalReadyKey, CohortValue, "PLL Library could not confirm its initial generation cohort.");
				string failure2 = ReadyPublication.Failure;
				if (failure2 != null)
				{
					throw PhotonRoomRejection.Disconnect(failure2);
				}
				if (flag2 && flag && AllActorsReady(Room, Cohort, CohortValue, ReadyKeys))
				{
					Capture(Cohort);
					yield break;
				}
				yield return null;
			}
			throw PhotonRoomRejection.Disconnect("PLL Library cannot seal initial generation without server property echoes.");
		}

		internal static void RequireCurrent(MapRun.GenerationCohort Cohort, object[] Expected, Room Room)
		{
			if (PhotonNetwork.CurrentRoom != Room)
			{
				throw new InvalidOperationException("Initial generation synchronization lost its Photon room.");
			}
			object obj = ((RoomInfo)Room).CustomProperties[(object)"af.pll.library/state/generation/cohort"];
			if (!PhotonPropertyProtocol.SameValue(obj, Expected))
			{
				MapRun.GenerationCohort Cohort2;
				string Failure;
				PropertyState propertyState = Read(obj, Cohort.GenerationEpoch, Cohort.FinalManifestRevision, out Cohort2, out Failure);
				if (propertyState != PropertyState.Valid)
				{
					throw new InvalidOperationException(Failure ?? "The initial generation cohort is unavailable.");
				}
				if (!Cohort.Matches(Cohort2))
				{
					throw new InvalidOperationException("The initial generation cohort changed after map preparation.");
				}
			}
		}

		internal static bool TryFindMissingActor(MapRun.GenerationCohort Cohort, Room Room, out int Actor)
		{
			for (int i = 0; i < Cohort.Count; i++)
			{
				Actor = Cohort.ActorAt(i);
				if (!Room.Players.ContainsKey(Actor))
				{
					return true;
				}
			}
			Actor = 0;
			return false;
		}

		private static string ReadyKey(int Actor)
		{
			return "af.pll.library/state/generation/cohort-ready/" + Actor.ToString(CultureInfo.InvariantCulture);
		}

		internal static object[] Encode(MapRun.GenerationCohort Cohort)
		{
			return new object[4]
			{
				"1",
				Cohort.GenerationEpoch,
				Cohort.FinalManifestRevision,
				Cohort.SnapshotActors()
			};
		}

		private static PropertyState Read(object? Value, string Epoch, string Revision, out MapRun.GenerationCohort Cohort, out string? Failure)
		{
			Cohort = null;
			Failure = null;
			if (Value == null || (Value is object[] array && array.Length == 0))
			{
				return PropertyState.Absent;
			}
			if (Value is object[] array2 && array2.Length > 1 && array2[1] is string a && !string.Equals(a, Epoch, StringComparison.Ordinal))
			{
				return PropertyState.Absent;
			}
			if (!(Value is object[] array3) || array3.Length != 4 || !(array3[0] is string a2) || !(array3[1] is string text) || !(array3[2] is string text2) || !(array3[3] is int[] array4))
			{
				Failure = "The initial generation cohort property is malformed.";
				return PropertyState.Invalid;
			}
			if (!string.Equals(a2, "1", StringComparison.Ordinal))
			{
				Failure = "The initial generation cohort protocol is incompatible.";
				return PropertyState.Invalid;
			}
			if (!string.Equals(text, Epoch, StringComparison.Ordinal))
			{
				return PropertyState.Absent;
			}
			if (!string.Equals(text2, Revision, StringComparison.Ordinal))
			{
				Failure = "The initial generation cohort has a different final manifest revision.";
				return PropertyState.Invalid;
			}
			try
			{
				Cohort = new MapRun.GenerationCohort(text, text2, array4);
				return PropertyState.Valid;
			}
			catch (ArgumentException)
			{
				Failure = ((array4.Length == 0) ? "The initial generation cohort has no actors." : "The initial generation cohort actor list is invalid.");
				return PropertyState.Invalid;
			}
		}

		private static void RequireActorsPresent(Room Room, MapRun.GenerationCohort Cohort)
		{
			if (TryFindMissingActor(Cohort, Room, out var Actor))
			{
				throw PhotonRoomRejection.Disconnect($"Player {Actor} left before the initial generation cohort was released.");
			}
		}

		private static bool AllActorsReady(Room Room, MapRun.GenerationCohort Cohort, object[] Expected, string[] ReadyKeys)
		{
			for (int i = 0; i < Cohort.Count; i++)
			{
				int num = Cohort.ActorAt(i);
				object obj = ((RoomInfo)Room).CustomProperties[(object)ReadyKeys[i]];
				if (PhotonPropertyProtocol.SameValue(obj, Expected))
				{
					continue;
				}
				MapRun.GenerationCohort Cohort2;
				string Failure;
				switch (Read(obj, Cohort.GenerationEpoch, Cohort.FinalManifestRevision, out Cohort2, out Failure))
				{
				case PropertyState.Absent:
					return false;
				case PropertyState.Invalid:
					throw PhotonRoomRejection.Disconnect($"Player {num} published an invalid initial generation cohort echo.");
				}
				if (!Cohort.Matches(Cohort2))
				{
					throw PhotonRoomRejection.Disconnect($"Player {num} published a different initial generation cohort echo.");
				}
			}
			return true;
		}
	}
	internal static class IslandLoadPublication
	{
		internal sealed class Publication
		{
			internal Room Room { get; }

			internal AirportCheckInKiosk? Kiosk { get; }

			internal int Ascent { get; }

			internal byte[]? SerializedRunSettings { get; }

			internal string Payload { get; }

			internal Hashtable Properties { get; }

			internal bool Confirmed { get; set; }

			internal string? Failure { get; set; }

			internal Publication(Room Room, AirportCheckInKiosk? Kiosk, int Ascent, byte[]? SerializedRunSettings, string Payload, Hashtable Properties)
			{
				this.Room = Room;
				this.Kiosk = Kiosk;
				this.Ascent = Ascent;
				this.SerializedRunSettings = SerializedRunSettings;
				this.Payload = Payload;
				this.Properties = Properties;
			}
		}

		[HarmonyPatch(typeof(LoadingScreenHandler), "LoadSceneProcess", new Type[]
		{
			typeof(string),
			typeof(bool),
			typeof(bool),
			typeof(float)
		})]
		private static class ProcessingSceneLoadPatch
		{
			[HarmonyPostfix]
			private static void Postfix(string sceneName, bool networked, ref IEnumerator __result)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Invalid comparison between Unknown and I4
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (networked && !PhotonNetwork.OfflineMode && currentRoom != null && (int)PhotonNetwork.NetworkClientState == 9 && string.Equals(sceneName, "WilIsland", StringComparison.Ordinal))
				{
					__result = HoldProcessingSceneLoad(__result, currentRoom);
				}
			}
		}

		[HarmonyPatch(typeof(AirportCheckInKiosk), "LoadIslandMaster", new Type[]
		{
			typeof(int),
			typeof(byte[])
		})]
		private static class AirportLoadPatch
		{
			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> Instructions)
			{
				MethodInfo GetLevel = AccessTools.DeclaredMethod(typeof(MapBaker), "GetLevel", new Type[1] { typeof(int) }, (Type[])null) ?? throw new MissingMethodException("MapBaker.GetLevel(int) was not found.");
				MethodInfo Selector = new Func<string, string>(SelectMapBakerScene).Method;
				int Replaced = 0;
				foreach (CodeInstruction Instruction in Instructions)
				{
					yield return Instruction;
					if (CodeInstructionExtensions.Calls(Instruction, GetLevel))
					{
						yield return new CodeInstruction(OpCodes.Call, (object)Selector);
						Replaced++;
					}
				}
				if (Replaced != 1)
				{
					throw new MissingMethodException($"AirportCheckInKiosk.LoadIslandMaster's MapBaker.GetLevel call was found {Replaced} times.");
				}
			}

			[HarmonyPrefix]
			private static bool Prefix(AirportCheckInKiosk __instance, int ascent, byte[] serializedRunSettings)
			{
				Publication releasing = Releasing;
				if (releasing != null)
				{
					if (releasing.Kiosk == __instance && releasing.Ascent == ascent)
					{
						return releasing.SerializedRunSettings == serializedRunSettings;
					}
					return false;
				}
				if (LoadingScreenHandler.loading || serializedRunSettings == null)
				{
					return false;
				}
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom == null)
				{
					return false;
				}
				Publication pending = Pending;
				if (pending != null)
				{
					Cancel(pending);
				}
				if (!currentRoom.IsOffline && !currentRoom.BroadcastPropertiesChangeToAll)
				{
					Plugin.PLLLog.LogError((object)"PLL Library cannot verify room configuration publication without server property echoes.");
					return false;
				}
				return Publish(__instance, ascent, serializedRunSettings) == null;
			}
		}

		private static Publication? Pending;

		private static Publication? Releasing;

		internal static Publication PublishQuicksave(CapturedLayoutSelection Selection)
		{
			return Publish(null, 0, null, Selection) ?? throw new InvalidOperationException("PLL Library cannot publish a room configuration without host authority.");
		}

		internal static bool Owns(Publication Request)
		{
			return Pending == Request;
		}

		internal static bool Consume(Publication Request)
		{
			if (Pending != Request)
			{
				return false;
			}
			Pending = null;
			return true;
		}

		internal static void Cancel(Publication Request)
		{
			if (Pending == Request)
			{
				Pending = null;
			}
		}

		internal static void Fail(Publication Request, string Message)
		{
			if (Pending == Request)
			{
				Request.Failure = Message;
				Pending = null;
				Plugin.PLLLog.LogError((object)Message);
			}
		}

		internal static bool HasServerEcho(Publication Request)
		{
			foreach (object key in ((Dictionary<object, object>)(object)Request.Properties).Keys)
			{
				if (!((Dictionary<object, object>)(object)((RoomInfo)Request.Room).CustomProperties).TryGetValue(key, out object value) || !PhotonPropertyProtocol.SameValue(value, Request.Properties[key]))
				{
					return false;
				}
			}
			foreach (object key2 in ((Dictionary<object, object>)(object)((RoomInfo)Request.Room).CustomProperties).Keys)
			{
				if (key2 is string text && text.StartsWith("af.pll.library/state/", StringComparison.Ordinal) && (!(((RoomInfo)Request.Room).CustomProperties[key2] is object[] array) || array.Length != 0))
				{
					return false;
				}
			}
			return true;
		}

		internal static void Tick()
		{
			Publication pending = Pending;
			if (pending != null)
			{
				if (PhotonNetwork.CurrentRoom != pending.Room || !PhotonNetwork.IsMasterClient)
				{
					Fail(pending, "PLL Library cancelled the pending room configuration because room authority changed.");
				}
				else
				{
					TryConfirm(pending);
				}
			}
		}

		internal static void RoomPropertiesChanged(Hashtable PropertiesThatChanged)
		{
			Publication pending = Pending;
			if (pending != null)
			{
				object value;
				if (PhotonNetwork.CurrentRoom != pending.Room || !PhotonNetwork.IsMasterClient)
				{
					Fail(pending, "PLL Library cancelled the pending room configuration because room authority changed.");
				}
				else if (((Dictionary<object, object>)(object)PropertiesThatChanged).TryGetValue((object)"af.pll.library/c", out value) && value is string a && string.Equals(a, pending.Payload, StringComparison.Ordinal))
				{
					TryConfirm(pending);
				}
			}
		}

		internal static void ConnectionStateChanged(ClientState Current)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			Publication pending = Pending;
			if (pending != null && !pending.Room.IsOffline && (int)Current != 9)
			{
				Fail(pending, "PLL Library cancelled the pending room configuration because the room connection changed.");
			}
		}

		internal static void MasterClientChanged()
		{
			Publication pending = Pending;
			if (pending != null)
			{
				Fail(pending, "PLL Library cancelled the pending room configuration because the master client changed.");
			}
		}

		private static Publication? Publish(AirportCheckInKiosk? Kiosk, int Ascent, byte[]? SerializedRunSettings, CapturedLayoutSelection? RestoredSelection = null)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			Room currentRoom = PhotonNetwork.CurrentRoom;
			if (currentRoom == null || !PhotonNetwork.IsMasterClient)
			{
				return null;
			}
			if (Pending != null)
			{
				throw new InvalidOperationException("PLL Library already has a pending room configuration.");
			}
			SegmentRegistry.SealStartup();
			ResolvedRunConfiguration configuration = RunConfigurationCodec.CaptureLocal(RestoredSelection);
			if ((Object)(object)Kiosk != (Object)null && SerializedRunSettings != null && MapQuicksave.RuntimeLayoutPublicationRequired(configuration) && !MapQuicksave.ClearBufferedCampfireRpcs())
			{
				throw new InvalidOperationException("PLL Library could not clear stale PEAK campfire state before loading the island.");
			}
			RunConfigurationResolution.AdoptForPublication(configuration);
			string text = RunConfigurationCodec.Serialize(configuration);
			Hashtable val = new Hashtable { [(object)"af.pll.library/c"] = text };
			foreach (object key in ((Dictionary<object, object>)(object)((RoomInfo)currentRoom).CustomProperties).Keys)
			{
				if (key is string text2 && text2.StartsWith("af.pll.library/state/", StringComparison.Ordinal))
				{
					val[key] = Array.Empty<object>();
				}
			}
			Publication publication = (Pending = new Publication(currentRoom, Kiosk, Ascent, SerializedRunSettings, text, val));