Decompiled source of SuperhotArchipelago v0.2.2

Mods/SuperhotArchipelago.dll

Decompiled a day ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Archipelago.MultiClient.Net;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.Models;
using HarmonyLib;
using InputSystem;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SuperhotArchipelago.Core;
using SuperhotArchipelago.Patches;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Mod), "SuperhotArchipelago", "0.2.2", "My-Key-Morris", null)]
[assembly: MelonGame("SUPERHOT_Team", "SUPERHOT")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("SuperhotArchipelago")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+ac2738bb885f50d160aa9e05b060e49b13323e18")]
[assembly: AssemblyProduct("SuperhotArchipelago")]
[assembly: AssemblyTitle("SuperhotArchipelago")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.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 SuperhotArchipelago.Patches
{
	[HarmonyPatch(typeof(piOsMenu), "CreateViewFromNode", new Type[]
	{
		typeof(XElement),
		typeof(List<int>),
		typeof(List<int>),
		typeof(float),
		typeof(bool)
	})]
	public static class ArchipelagoFolderButtonPatch
	{
		private const string ButtonLabel = "ARCHIPELAGO";

		private const string SettingsButtonPrefix = "SETTINGS";

		public static void Postfix(SHGUIcommanderview ___createdView)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (___createdView != null && ___createdView.isRoot)
			{
				SHGUIcommanderbutton val = new SHGUIcommanderbutton("ARCHIPELAGO".PadRight(12, ' ') + "│>FOLDER<", 'w', (Action<SHGUIcommanderbutton>)delegate
				{
					OpenFolder();
				}).SetListLink(___createdView).SetData("Connect to a server, toggle Archipelago mode, view your check/item history, or configure notification settings.");
				___createdView.AddButtonView(val);
				InsertAboveSettings(___createdView);
			}
		}

		private static void InsertAboveSettings(SHGUIcommanderview view)
		{
			List<SHGUIcommanderbutton> buttons = view.buttons;
			int num = buttons.Count - 1;
			int num2 = buttons.FindIndex((SHGUIcommanderbutton b) => b.ButtonText.StartsWith("SETTINGS", StringComparison.Ordinal));
			if (num2 >= 0 && num2 < num)
			{
				SHGUIcommanderbutton item = buttons[num];
				buttons.RemoveAt(num);
				buttons.Insert(num2, item);
				for (int num3 = 0; num3 < buttons.Count; num3++)
				{
					((SHGUIview)buttons[num3]).y = num3 + 1;
				}
			}
		}

		private static void OpenFolder()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderview val = new SHGUIcommanderview
			{
				isRoot = false,
				path = "C:\\ARCHIPELAGO\\"
			};
			SHGUIcommanderbutton val2 = new SHGUIcommanderbutton("/..         │<UP-FOL>", 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				SHGUI.current.PopView();
			}).SetListLink(val).SetData("Go back.");
			val.AddButtonView(val2);
			ConnectionButtonPatch.AddTo(val);
			ArchipelagoModeTogglePatch.AddTo(val);
			ArchipelagoLogButtonPatch.AddTo(val);
			NotificationSettingsButtonPatch.AddTo(val);
			SHGUI.current.AddViewOnTop((SHGUIview)(object)val);
		}
	}
	public static class ArchipelagoLogButtonPatch
	{
		private const string ButtonLabel = "AP LOG";

		internal static void AddTo(SHGUIcommanderview view)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderbutton val = new SHGUIcommanderbutton("AP LOG".PadRight(12, ' ') + "│VIEW", 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				SHGUI.current.AddViewOnTop((SHGUIview)(object)new ArchipelagoLogApp());
			}).SetListLink(view).SetData("See a history of items you've received and checks you've sent this run.");
			view.AddButtonView(val);
		}
	}
	public static class ArchipelagoModeTogglePatch
	{
		private const string ButtonLabel = "AP MODE";

		private static bool? _lastEnabled;

		internal static SHGUIcommanderbutton? Button { get; private set; }

		internal static void AddTo(SHGUIcommanderview view)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderbutton val = new SHGUIcommanderbutton(BuildLabel(), 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				Mod.SetEnabled(!Config.Enabled.Value);
			}).SetListLink(view).SetData("Turn Archipelago mode on or off. Off plays SUPERHOT exactly like vanilla -- no level gating, no hub overlay -- and drops any active connection. Turning back on reconnects and picks up right where you left off.");
			view.AddButtonView(val);
			Button = val;
			_lastEnabled = null;
			RefreshLabel();
		}

		internal static void RefreshLabel()
		{
			if (Button != null)
			{
				bool value = Config.Enabled.Value;
				if (value != _lastEnabled)
				{
					_lastEnabled = value;
					Button.ButtonText = BuildLabel();
					Button.RefreshText();
				}
			}
		}

		private static string BuildLabel()
		{
			string text = (Config.Enabled.Value ? "ON" : "OFF");
			return "AP MODE".PadRight(12, ' ') + "│" + text;
		}
	}
	internal static class AutoTransitionCheck
	{
		public static void ReportCompletionIfFirstCall(bool alreadyStarted)
		{
			if (!alreadyStarted && Mod.IsEnabled)
			{
				LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo;
				if (currentLevelInfo != null)
				{
					Mod.Locations?.CheckLocation(currentLevelInfo.ID);
				}
			}
		}

		public static bool TryBlockAndRedirect()
		{
			LevelInfo nextLevelInfo = LevelSetup.GetNextLevelInfo();
			if (!LevelAccessGuard.ShouldBlock(nextLevelInfo, out string blockMessage))
			{
				return false;
			}
			Instance? log = Mod.Log;
			if (log != null)
			{
				log.Msg("Blocked auto-transition to '" + nextLevelInfo.SceneFileName + "' before any static/camera transition started -- " + blockMessage + ". Returning to hub.");
			}
			if ((Object)(object)SHGUI.current != (Object)null)
			{
				SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevel")]
	public static class AutoTransitionCheckPatch_LoadNextLevel
	{
		public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result)
		{
			AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel);
			if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect())
			{
				return true;
			}
			___loadedNextLevel = true;
			__result = (Status)1;
			return false;
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelWithTunnels")]
	public static class AutoTransitionCheckPatch_LoadNextLevelWithTunnels
	{
		public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result)
		{
			AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel);
			if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect())
			{
				return true;
			}
			___loadedNextLevel = true;
			__result = (Status)1;
			return false;
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelWithTunnelsWithoutScramble")]
	public static class AutoTransitionCheckPatch_LoadNextLevelWithTunnelsWithoutScramble
	{
		public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result)
		{
			AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel);
			if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect())
			{
				return true;
			}
			___loadedNextLevel = true;
			__result = (Status)1;
			return false;
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelWithTunnelsWithScrambleOnEnd")]
	public static class AutoTransitionCheckPatch_LoadNextLevelWithTunnelsWithScrambleOnEnd
	{
		public static bool Prefix(ref bool ___loadedNextLevel, ref Status __result)
		{
			AutoTransitionCheck.ReportCompletionIfFirstCall(___loadedNextLevel);
			if (___loadedNextLevel || !AutoTransitionCheck.TryBlockAndRedirect())
			{
				return true;
			}
			___loadedNextLevel = true;
			__result = (Status)1;
			return false;
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "LoadNextLevelInstant")]
	public static class AutoTransitionCheckPatch_LoadNextLevelInstant
	{
		public static bool Prefix()
		{
			AutoTransitionCheck.ReportCompletionIfFirstCall(alreadyStarted: false);
			return !AutoTransitionCheck.TryBlockAndRedirect();
		}
	}
	public static class ConnectionButtonPatch
	{
		private const string ButtonLabel = "CONNECT";

		private static string? _lastStatus;

		internal static SHGUIcommanderbutton? Button { get; private set; }

		internal static void AddTo(SHGUIcommanderview view)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderbutton val = new SHGUIcommanderbutton("CONNECT".PadRight(12, ' ') + "│OFFLINE", 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				SHGUI.current.AddViewOnTop((SHGUIview)(object)new ArchipelagoConnectApp());
			}).SetListLink(view).SetData("Set up or check your Archipelago server connection.");
			view.AddButtonView(val);
			Button = val;
			_lastStatus = null;
			RefreshLabel();
		}

		internal static void RefreshLabel()
		{
			if (Button != null)
			{
				ArchipelagoConnection? connection = Mod.Connection;
				string text = ((connection != null && connection.IsConnected) ? "ONLINE" : "OFFLINE");
				if (!(text == _lastStatus))
				{
					_lastStatus = text;
					Button.ButtonText = "CONNECT".PadRight(12, ' ') + "│" + text;
					Button.RefreshText();
				}
			}
		}
	}
	[HarmonyPatch(typeof(LevelSetup), "LoadNextLevel")]
	public static class DirectLevelSkipPatch
	{
		public static bool Prefix()
		{
			LevelInfo nextLevelInfo = LevelSetup.GetNextLevelInfo();
			if (!LevelAccessGuard.ShouldBlock(nextLevelInfo, out string blockMessage))
			{
				return true;
			}
			Instance? log = Mod.Log;
			if (log != null)
			{
				log.Msg("Blocked direct-continue to '" + nextLevelInfo.SceneFileName + "' -- " + blockMessage + ". Returning to hub instead of leaving the player stuck.");
			}
			if ((Object)(object)SHGUI.current != (Object)null)
			{
				SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f);
			}
			return false;
		}
	}
	internal static class EndingClickBuffer
	{
		public static void SuppressIfWithinBuffer(ref InputData inputData, bool endingStarted, float endingStartedTime)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (endingStarted && Time.realtimeSinceStartup - endingStartedTime < Config.EndingClickBufferSeconds.Value)
			{
				inputData.skipButton = (ButtonState)1;
			}
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEnding")]
	public static class EndingClickBufferPatch_Ending
	{
		public static void Prefix(ref InputData ___inputData, bool ___SuperHotEndingStarted, float ___SuperHotEndingStartedTime)
		{
			EndingClickBuffer.SuppressIfWithinBuffer(ref ___inputData, ___SuperHotEndingStarted, ___SuperHotEndingStartedTime);
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEndingClickThrough")]
	public static class EndingClickBufferPatch_EndingClickThrough
	{
		public static void Prefix(ref InputData ___inputData, bool ___SuperHotEndingStarted, float ___SuperHotEndingStartedTime)
		{
			EndingClickBuffer.SuppressIfWithinBuffer(ref ___inputData, ___SuperHotEndingStarted, ___SuperHotEndingStartedTime);
		}
	}
	[HarmonyPatch(typeof(piOsMenu), "LockUnfinishedLevels")]
	public static class HubUnlockPatch
	{
		private static readonly FieldInfo RecursiveColorSetField = AccessTools.Field(typeof(SHGUIcommanderbutton), "recursiveColorSet");

		public static void Postfix()
		{
			try
			{
				RunPostfixBody();
			}
			catch (Exception arg)
			{
				Instance? log = Mod.Log;
				if (log != null)
				{
					log.Error($"HubUnlockPatch.Postfix threw: {arg}");
				}
			}
		}

		private static void RunPostfixBody()
		{
			if (!Mod.IsEnabled || (Object)(object)SHGUI.current == (Object)null)
			{
				return;
			}
			SHGUIview interactableView = SHGUI.current.GetInteractableView();
			SHGUIcommanderview val = (SHGUIcommanderview)(object)((interactableView is SHGUIcommanderview) ? interactableView : null);
			if (val == null || val.buttons == null)
			{
				return;
			}
			foreach (SHGUIcommanderbutton button in val.buttons)
			{
				if (button == null || button.LevelToBeLoaded == "")
				{
					continue;
				}
				LevelInfo levelInfoByUniqueSHName = LevelSetup.GetLevelInfoByUniqueSHName(button.LevelToBeLoadedName);
				if (string.IsNullOrEmpty(levelInfoByUniqueSHName.SceneFileName) || !LevelCatalog.LevelIdToLevel.TryGetValue(levelInfoByUniqueSHName.ID, out LevelEntry value))
				{
					continue;
				}
				bool flag = Mod.Connection != null && Mod.Connection.IsLevelExcluded(value.Order);
				if (value.HasSecret)
				{
					if (flag)
					{
						button.AddScrollingNotification("INACTIVE".PadRight(8), false);
						string text = LocalizationAccessHelperExtensions.T("MENU_SECRETNOTCRACKED", Array.Empty<string>());
						string text2 = LocalizationAccessHelperExtensions.T("MENU_SECRETCRACKED", Array.Empty<string>());
						if (button.data.Contains(text2))
						{
							button.data = button.data.Replace(text2, "INACTIVE");
						}
						else if (button.data.Contains(text))
						{
							button.data = button.data.Replace(text, "INACTIVE");
						}
					}
					else
					{
						bool flag2 = Mod.Locations?.IsSecretCompleted(levelInfoByUniqueSHName.ID) ?? false;
						if (flag2)
						{
							button.AddScrollingNotification(LocalizationAccessHelperExtensions.T("MENU_CRACKED8CHARS", Array.Empty<string>()).PadRight(8), false);
						}
						else
						{
							button.RemoveScrollingNotification();
						}
						string text3 = LocalizationAccessHelperExtensions.T("MENU_SECRETNOTCRACKED", Array.Empty<string>());
						string text4 = LocalizationAccessHelperExtensions.T("MENU_SECRETCRACKED", Array.Empty<string>());
						if (flag2 && button.data.Contains(text3))
						{
							button.data = button.data.Replace(text3, text4);
						}
						else if (!flag2 && button.data.Contains(text4))
						{
							button.data = button.data.Replace(text4, text3);
						}
					}
				}
				bool flag3 = value.Order == LevelCatalog.Levels.Count;
				int num = 0;
				int num2 = 0;
				bool flag4 = false;
				if (flag3)
				{
					num = Mod.Locations?.GetLevelsRequiredForFree() ?? 0;
					num2 = Mod.Locations?.CountOtherLevelsCompleted() ?? 0;
					flag4 = num2 < num;
					if (ButtonTextCache.TryGetData(levelInfoByUniqueSHName.ID, out string cleanData))
					{
						button.data = $"\n\n{num2}/{num} LEVELS COMPLETED\n\n" + cleanData;
					}
				}
				string text5 = (flag3 ? $"{num2}/{num}".PadRight(8) : null);
				bool flag5 = value.Order == 1 || flag || UnlockState.IsUnlocked(levelInfoByUniqueSHName.ID);
				if (!ButtonTextCache.TryGet(levelInfoByUniqueSHName.ID, out string cleanName))
				{
					button.SetLocked(!flag5);
					continue;
				}
				if (!flag5)
				{
					string scrambledString = StringScrambler.GetScrambledString(cleanName.ToUpper(), 0.9f, "▀▄█▌▐░▒▓■▪01 ");
					button.ButtonText = scrambledString + "│" + (text5 ?? LocalizationAccessHelperExtensions.T("MENU_LOCKED8CHARS", Array.Empty<string>()));
					button.RefreshText();
					button.SetLocked(true);
					continue;
				}
				bool flag6 = Mod.Locations?.IsLevelCompleted(levelInfoByUniqueSHName.ID) ?? false;
				if (flag3 && flag4)
				{
					button.ButtonText = cleanName + "│" + text5;
					button.RefreshText();
					button.IsLocked = true;
					((SHGUIview)button).color = 'z';
					((SHGUIview)button).SetColorRecursive('z');
					continue;
				}
				string text6 = (flag ? StringScrambler.GetScrambledString(cleanName.ToUpper(), 0.9f, "▀▄█▌▐░▒▓■▪01 ") : cleanName);
				string text7 = (flag3 ? text5 : (flag ? "INACTIVE" : LocalizationAccessHelperExtensions.T("MENU_LEVEL8CHARS", Array.Empty<string>())));
				button.ButtonText = text6 + "│" + text7;
				button.RefreshText();
				button.IsLocked = flag;
				((SHGUIview)button).SetColorRecursive(((SHGUIview)button).color = (flag ? 'b' : (flag6 ? 'w' : 'z')));
				if (flag)
				{
					RecursiveColorSetField.SetValue(button, true);
				}
			}
		}
	}
	[HarmonyPatch(typeof(piOsMenu), "PrepareLevelCommanderButtonForLevel")]
	public static class LevelButtonCapturePatch
	{
		public static void Postfix(ref SHGUIcommanderbutton b, LevelInfo element, string customName)
		{
			try
			{
				if (!(customName != "") && b != null && element != null && LevelCatalog.LevelIdToLevel.ContainsKey(element.ID))
				{
					int num = b.ButtonText.IndexOf('│');
					string cleanName = ((num >= 0) ? b.ButtonText.Substring(0, num) : b.ButtonText);
					ButtonTextCache.Remember(element.ID, cleanName);
					ButtonTextCache.RememberData(element.ID, b.data);
				}
			}
			catch (Exception arg)
			{
				Instance? log = Mod.Log;
				if (log != null)
				{
					log.Error("LevelButtonCapturePatch.Postfix threw for element.ID=" + (element?.ID.ToString() ?? "(null)") + ", customName='" + customName + "', " + string.Format("b.ButtonText='{0}': {1}", b?.ButtonText ?? "(null)", arg));
				}
			}
		}
	}
	[HarmonyPatch(typeof(LevelSetup), "UnlockNextLevel")]
	public static class LevelCompletePatch
	{
		public static void Postfix()
		{
			if (Mod.IsEnabled)
			{
				LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo;
				if (currentLevelInfo != null)
				{
					Mod.Locations?.CheckLocation(currentLevelInfo.ID);
				}
			}
		}
	}
	[HarmonyPatch(typeof(SHGUI), "LaunchLevelAppTunnels", new Type[]
	{
		typeof(LevelInfo),
		typeof(bool),
		typeof(bool)
	})]
	public static class LevelGatePatch
	{
		public static bool Prefix(LevelInfo level)
		{
			if (!LevelAccessGuard.ShouldBlock(level, out string blockMessage))
			{
				return true;
			}
			Instance? log = Mod.Log;
			if (log != null)
			{
				log.Msg("Blocked launch of '" + level.SceneFileName + "' via LaunchLevelAppTunnels -- " + blockMessage);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(piOsMenu), "ShouldBeShown")]
	public static class MenuVisibilityPatch
	{
		public static bool Prefix(ref bool __result)
		{
			if (!Mod.IsEnabled)
			{
				return true;
			}
			__result = true;
			return false;
		}
	}
	public static class NotificationSettingsButtonPatch
	{
		private const string ButtonLabel = "SETTINGS";

		internal static void AddTo(SHGUIcommanderview parentView)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderbutton val = new SHGUIcommanderbutton("SETTINGS".PadRight(12, ' ') + "│>FOLDER<", 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				OpenFolder();
			}).SetListLink(parentView).SetData("Choose which item classes pop up during play. Everything still shows up in AP LOG regardless of these settings.");
			parentView.AddButtonView(val);
		}

		private static void OpenFolder()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderview val = new SHGUIcommanderview
			{
				isRoot = false,
				path = "C:\\ARCHIPELAGO\\SETTINGS\\"
			};
			SHGUIcommanderbutton val2 = new SHGUIcommanderbutton("/..         │<UP-FOL>", 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				SHGUI.current.PopView();
			}).SetListLink(val).SetData("Go back.");
			val.AddButtonView(val2);
			AddToggle(val, "PROGRESSION", Config.NotifyProgression, "Popup when a progression item is sent or received.");
			AddToggle(val, "USEFUL", Config.NotifyUseful, "Popup when a useful item is sent or received.");
			AddToggle(val, "NORMAL", Config.NotifyFiller, "Popup for normal (filler) items -- most checks.");
			AddToggle(val, "TRAP", Config.NotifyTrap, "Popup when a trap item is sent or received.");
			SHGUI.current.AddViewOnTop((SHGUIview)(object)val);
		}

		private static void AddToggle(SHGUIcommanderview view, string label, MelonPreferences_Entry<bool> pref, string description)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			SHGUIcommanderbutton button = null;
			button = new SHGUIcommanderbutton(BuildLabel(label, pref.Value), 'w', (Action<SHGUIcommanderbutton>)delegate
			{
				pref.Value = !pref.Value;
				Config.Save();
				button.ButtonText = BuildLabel(label, pref.Value);
				button.RefreshText();
			}).SetListLink(view).SetData(description);
			view.AddButtonView(button);
		}

		private static string BuildLabel(string label, bool value)
		{
			return label.PadRight(12, ' ') + "│" + (value ? "ON" : "OFF");
		}
	}
	[HarmonyPatch(typeof(TerminalActivator), "OnActivate")]
	public static class SecretFoundPatch
	{
		public static void Prefix(bool ___secretFound, out bool __state)
		{
			__state = ___secretFound;
		}

		public static void Postfix(bool ___secretFound, bool __state)
		{
			if (Mod.IsEnabled && !__state && ___secretFound)
			{
				LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo;
				if (currentLevelInfo != null)
				{
					Mod.Locations?.CheckSecretLocation(currentLevelInfo.ID);
				}
			}
		}
	}
	[HarmonyPatch(typeof(piOsMenu), "CreateViewFromNode")]
	public static class StoryLevelsUnlockPatch
	{
		public static void Postfix(piOsMenu __instance, XElement e)
		{
			try
			{
				if (Mod.IsEnabled && e != null && ((XContainer)e).Elements().Any(delegate(XElement child)
				{
					if (((object)child.Name).ToString() == "item")
					{
						XAttribute obj = child.Attribute(XName.op_Implicit("type"));
						return ((obj != null) ? obj.Value : null) == "storylevels";
					}
					return false;
				}))
				{
					__instance.LockUnfinishedLevels();
				}
			}
			catch (Exception arg)
			{
				Instance? log = Mod.Log;
				if (log != null)
				{
					log.Error($"StoryLevelsUnlockPatch.Postfix threw: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(SHGUIcommanderview), "AddButtonView")]
	public static class SuperhotExeButtonPatch
	{
		private const string SuperhotExeCustomName = "superhot.exe";

		public static bool Prefix(SHGUIcommanderbutton button)
		{
			if (button == null || button.ButtonText == null)
			{
				return true;
			}
			if (button.ButtonText.StartsWith("superhot.exe"))
			{
				return false;
			}
			return true;
		}
	}
	internal static class TitleCardGate
	{
		public static void SuppressAdvanceIfBlocked(ref InputData inputData)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if ((inputData.skipButton & 4) == 0 && (int)inputData.skipButton != 3)
			{
				return;
			}
			LevelInfo nextLevelInfo = LevelSetup.GetNextLevelInfo();
			if (LevelAccessGuard.ShouldBlock(nextLevelInfo, out string blockMessage))
			{
				inputData.skipButton = (ButtonState)1;
				Instance? log = Mod.Log;
				if (log != null)
				{
					log.Msg("Suppressed SUPERHOT title-card click-through to '" + nextLevelInfo.SceneFileName + "' -- " + blockMessage + ". Returning to hub.");
				}
				if ((Object)(object)SHGUI.current != (Object)null)
				{
					SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f);
				}
			}
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEnding")]
	public static class TitleCardGatePatch_Ending
	{
		public static void Prefix(ref InputData ___inputData)
		{
			TitleCardGate.SuppressAdvanceIfBlocked(ref ___inputData);
		}
	}
	[HarmonyPatch(typeof(LevelFlowControl), "SuperHotSuperHotEndingClickThrough")]
	public static class TitleCardGatePatch_EndingClickThrough
	{
		public static void Prefix(ref InputData ___inputData)
		{
			TitleCardGate.SuppressAdvanceIfBlocked(ref ___inputData);
		}
	}
	[HarmonyPatch(typeof(SHGUI), "LaunchLevelViaApp", new Type[]
	{
		typeof(LevelInfo),
		typeof(float)
	})]
	public static class ViaAppGatePatch
	{
		public static bool Prefix(LevelInfo level)
		{
			if (!LevelAccessGuard.ShouldBlock(level, out string blockMessage))
			{
				return true;
			}
			Instance? log = Mod.Log;
			if (log != null)
			{
				log.Msg("Blocked launch of '" + level.SceneFileName + "' via LaunchLevelViaApp -- " + blockMessage + ". Returning to hub.");
			}
			if ((Object)(object)SHGUI.current != (Object)null)
			{
				SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f);
			}
			return false;
		}
	}
}
namespace SuperhotArchipelago.Core
{
	public class ArchipelagoConnectApp : SHGUIappbase
	{
		private const int FieldCount = 3;

		private const int StatusLineWidth = 50;

		private readonly SHGUItext[] _labels = (SHGUItext[])(object)new SHGUItext[3];

		private readonly SHGUItext[] _values = (SHGUItext[])(object)new SHGUItext[3];

		private readonly string[] _labelText = new string[3] { "SERVER", "SLOT", "PASSWORD" };

		private readonly string[] _buffers = new string[3];

		private readonly bool[] _masked = new bool[3] { false, false, true };

		private SHGUItext _statusField;

		private int _focusedField;

		public ArchipelagoConnectApp()
			: base("ARCHIPELAGO", true)
		{
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Expected O, but got Unknown
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Expected O, but got Unknown
			_buffers[0] = Config.Server.Value;
			_buffers[1] = Config.Slot.Value;
			_buffers[2] = Config.Password.Value;
			int num = 3;
			for (int i = 0; i < 3; i++)
			{
				ref SHGUItext reference = ref _labels[i];
				SHGUIview obj = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext(_labelText[i], 3, num, 'z', false));
				reference = (SHGUItext)(object)((obj is SHGUItext) ? obj : null);
				ref SHGUItext reference2 = ref _values[i];
				SHGUIview obj2 = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num + 1, 'w', false));
				reference2 = (SHGUItext)(object)((obj2 is SHGUItext) ? obj2 : null);
				num += 3;
			}
			ref SHGUItext statusField = ref _statusField;
			SHGUIview obj3 = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num, 'z', false));
			statusField = (SHGUItext)(object)((obj3 is SHGUItext) ? obj3 : null);
			num += 5;
			((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("[TAB] switch field   [ENTER] next / connect", 3, num, 'z', false));
			RefreshDisplay();
		}

		public override void Update()
		{
			((SHGUIview)this).Update();
			if (Input.GetKeyDown((KeyCode)27))
			{
				SHGUI.current.PopView();
				Input.ResetInputAxes();
				return;
			}
			if (Input.GetKeyDown((KeyCode)9))
			{
				_focusedField = (_focusedField + 1) % 3;
			}
			string text = _buffers[_focusedField];
			if (Input.inputString.Length > 0)
			{
				text += Input.inputString;
			}
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == '\b')
				{
					text = text.Remove(i--, 1);
					if (i >= 0)
					{
						text = text.Remove(i--, 1);
					}
				}
			}
			bool flag = false;
			for (int j = 0; j < text.Length; j++)
			{
				if (text[j] == '\r' || text[j] == '\n')
				{
					text = text.Remove(j, text.Length - j);
					flag = true;
					break;
				}
			}
			_buffers[_focusedField] = text;
			if (flag)
			{
				if (_focusedField < 2)
				{
					_focusedField++;
				}
				else
				{
					Connect();
				}
			}
			RefreshDisplay();
			Input.ResetInputAxes();
		}

		public override void ReactToInputKeyboard(SHGUIinput key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if ((int)key == 6)
			{
				((SHGUIappbase)this).ReactToInputKeyboard(key);
			}
		}

		private void Connect()
		{
			Mod.ApplyConnectionSettingsAndConnect(_buffers[0], _buffers[1], _buffers[2]);
			RefreshDisplay();
		}

		private void RefreshDisplay()
		{
			string text = ((Mathf.Sin(Time.realtimeSinceStartup * 10f) > 0f) ? "_" : "");
			for (int i = 0; i < 3; i++)
			{
				bool flag = i == _focusedField;
				((SHGUIview)_labels[i]).color = (flag ? 'w' : 'z');
				string text2 = (_masked[i] ? new string('*', _buffers[i].Length) : _buffers[i]);
				_values[i].text = text2 + (flag ? text : "");
			}
			_statusField.text = "STATUS: " + StatusText();
			_statusField.BreakTextForLineLength(50);
		}

		private string StatusText()
		{
			ArchipelagoConnection connection = Mod.Connection;
			if (connection == null)
			{
				return "NOT INITIALIZED";
			}
			if (connection.IsConnected)
			{
				return "CONNECTED AS '" + Config.Slot.Value + "'";
			}
			if (!string.IsNullOrEmpty(connection.LastError))
			{
				return "ERROR -- " + connection.LastError;
			}
			return "NOT CONNECTED";
		}
	}
	public class ArchipelagoConnection
	{
		private readonly Instance _log;

		public ArchipelagoSession? Session { get; private set; }

		public bool IsConnected { get; private set; }

		public string? LastError { get; private set; }

		public int LevelsRequiredForFree { get; private set; } = 25;

		public DateTime ConnectedAtUtc { get; private set; }

		public HashSet<int> ExcludedLevelOrders { get; private set; } = new HashSet<int>();

		public event Action? Connected;

		public ArchipelagoConnection(Instance log)
		{
			_log = log;
		}

		public void Connect(string server, string slotName, string? password = null)
		{
			LastError = null;
			IsConnected = false;
			if (string.IsNullOrEmpty(server) || string.IsNullOrEmpty(slotName))
			{
				LastError = "Server and Slot are both required.";
				_log.Warning("Not connecting: " + LastError);
				return;
			}
			Session = ArchipelagoSessionFactory.CreateSession(server, 38281);
			try
			{
				LoginResult val = Session.TryConnectAndLogin("SUPERHOT", slotName, (ItemsHandlingFlags)7, (Version)null, (string[])null, (string)null, password, true);
				if (!val.Successful)
				{
					LastError = "Login failed -- check server address, slot name, and password.";
					_log.Error("Failed to connect to Archipelago server: " + server);
					return;
				}
				LoginSuccessful val2 = (LoginSuccessful)(object)((val is LoginSuccessful) ? val : null);
				if (val2 != null && val2.SlotData.TryGetValue("levels_required_for_free", out var value))
				{
					LevelsRequiredForFree = Convert.ToInt32(value);
					_log.Msg($"Slot data: {LevelsRequiredForFree} other levels required to enter '34 - Free'.");
				}
				else
				{
					_log.Warning("Slot data had no 'levels_required_for_free' -- falling back to " + $"{LevelsRequiredForFree}. Expected if this room was generated from an " + "apworld version older than this feature.");
				}
				LoginSuccessful val3 = (LoginSuccessful)(object)((val is LoginSuccessful) ? val : null);
				if (val3 == null || !val3.SlotData.TryGetValue("excluded_level_orders", out var value2))
				{
					goto IL_019c;
				}
				JArray val4 = (JArray)((value2 is JArray) ? value2 : null);
				if (val4 == null)
				{
					goto IL_019c;
				}
				ExcludedLevelOrders = new HashSet<int>(((JToken)val4).ToObject<List<int>>() ?? new List<int>());
				if (ExcludedLevelOrders.Count > 0)
				{
					_log.Msg($"Slot data: {ExcludedLevelOrders.Count} level(s) excluded from " + "tracking (ExcludeSlowLevels) -- always unlocked, no checks sent for them.");
				}
				goto end_IL_0056;
				IL_019c:
				ExcludedLevelOrders = new HashSet<int>();
				end_IL_0056:;
			}
			catch (Exception ex)
			{
				LastError = ex.Message;
				_log.Error("Failed to connect to Archipelago server '" + server + "': " + ex.Message);
				return;
			}
			IsConnected = true;
			ConnectedAtUtc = DateTime.UtcNow;
			NotificationLog.Clear();
			UnlockState.Clear();
			_log.Msg("Connected to Archipelago as '" + slotName + "'.");
			if (this.Connected == null)
			{
				return;
			}
			Delegate[] invocationList = this.Connected.GetInvocationList();
			foreach (Delegate obj in invocationList)
			{
				try
				{
					((Action)obj)();
				}
				catch (Exception arg)
				{
					_log.Error($"A Connected event handler threw and was skipped: {arg}");
				}
			}
		}

		public bool IsLevelExcluded(int order)
		{
			return ExcludedLevelOrders.Contains(order);
		}

		public void Disconnect()
		{
			if (Session == null)
			{
				IsConnected = false;
				return;
			}
			try
			{
				Session.Socket.DisconnectAsync();
			}
			catch (Exception ex)
			{
				_log.Warning("Error while disconnecting from the Archipelago server: " + ex.Message);
			}
			Session = null;
			IsConnected = false;
			_log.Msg("Disconnected from the Archipelago server.");
		}
	}
	public class ArchipelagoLogApp : SHGUIappbase
	{
		private const int VisibleLines = 14;

		private const int LineWidth = 58;

		private const int MaxSegmentsPerLine = 6;

		private const int BaseX = 3;

		private readonly SHGUItext[][] _lineSegments = new SHGUItext[14][];

		private readonly SHGUItext _footer;

		private int _scrollOffset;

		public ArchipelagoLogApp()
			: base("LOG", true)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			int num = 3;
			for (int i = 0; i < 14; i++)
			{
				_lineSegments[i] = (SHGUItext[])(object)new SHGUItext[6];
				for (int j = 0; j < 6; j++)
				{
					ref SHGUItext reference = ref _lineSegments[i][j];
					SHGUIview obj = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num, 'w', false));
					reference = (SHGUItext)(object)((obj is SHGUItext) ? obj : null);
				}
				num++;
			}
			num++;
			ref SHGUItext footer = ref _footer;
			SHGUIview obj2 = ((SHGUIview)this).AddSubView((SHGUIview)new SHGUItext("", 3, num, 'z', false));
			footer = (SHGUItext)(object)((obj2 is SHGUItext) ? obj2 : null);
			RefreshDisplay();
		}

		public override void Update()
		{
			((SHGUIview)this).Update();
			if (Input.GetKeyDown((KeyCode)27))
			{
				SHGUI.current.PopView();
				Input.ResetInputAxes();
				return;
			}
			int val = Math.Max(0, NotificationLog.Entries.Count - 14);
			if (Input.GetKeyDown((KeyCode)274))
			{
				_scrollOffset = Math.Min(val, _scrollOffset + 1);
				RefreshDisplay();
			}
			else if (Input.GetKeyDown((KeyCode)273))
			{
				_scrollOffset = Math.Max(0, _scrollOffset - 1);
				RefreshDisplay();
			}
			else if (Input.GetKeyDown((KeyCode)281))
			{
				_scrollOffset = Math.Min(val, _scrollOffset + 14);
				RefreshDisplay();
			}
			else if (Input.GetKeyDown((KeyCode)280))
			{
				_scrollOffset = Math.Max(0, _scrollOffset - 14);
				RefreshDisplay();
			}
		}

		private void RefreshDisplay()
		{
			IReadOnlyList<LogSegment[]> entries = NotificationLog.Entries;
			List<LogSegment[]> list = new List<LogSegment[]>(entries.Count);
			for (int num = entries.Count - 1; num >= 0; num--)
			{
				list.Add(entries[num]);
			}
			for (int i = 0; i < 14; i++)
			{
				int num2 = _scrollOffset + i;
				if (num2 < list.Count)
				{
					SetRowSegments(i, list[num2]);
				}
				else if (num2 == 0)
				{
					SetRowSegments(i, new LogSegment[1]
					{
						new LogSegment("No notifications yet.", 'w')
					});
				}
				else
				{
					SetRowSegments(i, Array.Empty<LogSegment>());
				}
			}
			_footer.text = ((list.Count == 0) ? "[ESC] close" : ($"[UP/DOWN] scroll  [ESC] close   ({_scrollOffset + 1}-" + $"{Math.Min(_scrollOffset + 14, list.Count)} of {list.Count})"));
		}

		private void SetRowSegments(int rowIndex, LogSegment[] segments)
		{
			SHGUItext[] array = _lineSegments[rowIndex];
			int num = 0;
			int i = 0;
			for (int j = 0; j < segments.Length; j++)
			{
				LogSegment logSegment = segments[j];
				if (i >= array.Length)
				{
					break;
				}
				int num2 = 58 - num;
				string text = ((num2 > 0) ? ((logSegment.Text.Length > num2) ? ((num2 <= 3) ? logSegment.Text.Substring(0, num2) : (logSegment.Text.Substring(0, num2 - 3) + "...")) : logSegment.Text) : "");
				SHGUItext obj = array[i];
				((SHGUIview)obj).x = 3 + num;
				((SHGUIview)obj).color = logSegment.Color;
				obj.text = text;
				num += text.Length;
				i++;
			}
			for (; i < array.Length; i++)
			{
				array[i].text = "";
			}
		}
	}
	public static class ButtonTextCache
	{
		private static readonly Dictionary<int, string> _cleanNameByLevelId = new Dictionary<int, string>();

		private static readonly Dictionary<int, string> _cleanDataByLevelId = new Dictionary<int, string>();

		public static void Remember(int levelId, string cleanName)
		{
			_cleanNameByLevelId[levelId] = cleanName;
		}

		public static bool TryGet(int levelId, out string cleanName)
		{
			return _cleanNameByLevelId.TryGetValue(levelId, out cleanName);
		}

		public static void RememberData(int levelId, string cleanData)
		{
			_cleanDataByLevelId[levelId] = cleanData;
		}

		public static bool TryGetData(int levelId, out string cleanData)
		{
			return _cleanDataByLevelId.TryGetValue(levelId, out cleanData);
		}
	}
	public static class Config
	{
		private static MelonPreferences_Category _category;

		public static MelonPreferences_Entry<string> Server { get; private set; }

		public static MelonPreferences_Entry<string> Slot { get; private set; }

		public static MelonPreferences_Entry<string> Password { get; private set; }

		public static MelonPreferences_Entry<bool> Enabled { get; private set; }

		public static MelonPreferences_Entry<bool> NotifyProgression { get; private set; }

		public static MelonPreferences_Entry<bool> NotifyUseful { get; private set; }

		public static MelonPreferences_Entry<bool> NotifyFiller { get; private set; }

		public static MelonPreferences_Entry<bool> NotifyTrap { get; private set; }

		public static MelonPreferences_Entry<float> EndingClickBufferSeconds { get; private set; }

		public static bool IsConfigured
		{
			get
			{
				if (Server.Value != "")
				{
					return Slot.Value != "";
				}
				return false;
			}
		}

		public static void Load()
		{
			_category = MelonPreferences.CreateCategory("SuperhotArchipelago", "SUPERHOT Archipelago");
			Server = _category.CreateEntry<string>("Server", "", "Server", "Archipelago server address, e.g. archipelago.gg:38281 or localhost:38281", false, false, (ValueValidator)null, (string)null);
			Slot = _category.CreateEntry<string>("Slot", "", "Slot", "Your player/slot name, matching the name in your player YAML.", false, false, (ValueValidator)null, (string)null);
			Password = _category.CreateEntry<string>("Password", "", "Password", "Room password, if the server has one set. Leave blank otherwise.", false, false, (ValueValidator)null, (string)null);
			Enabled = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Whether Archipelago mode is on. Turn off to play vanilla SUPERHOT (no level gating, no hub overlay) without uninstalling the mod.", false, false, (ValueValidator)null, (string)null);
			NotifyProgression = _category.CreateEntry<bool>("NotifyProgression", true, "NotifyProgression", "Popup for progression items.", false, false, (ValueValidator)null, (string)null);
			NotifyUseful = _category.CreateEntry<bool>("NotifyUseful", true, "NotifyUseful", "Popup for useful items.", false, false, (ValueValidator)null, (string)null);
			NotifyFiller = _category.CreateEntry<bool>("NotifyFiller", true, "NotifyFiller", "Popup for normal (filler) items -- most checks.", false, false, (ValueValidator)null, (string)null);
			NotifyTrap = _category.CreateEntry<bool>("NotifyTrap", true, "NotifyTrap", "Popup for trap items.", false, false, (ValueValidator)null, (string)null);
			EndingClickBufferSeconds = _category.CreateEntry<float>("EndingClickBufferSeconds", 1.5f, "EndingClickBufferSeconds", "Seconds after a level's ending sequence starts before a click can skip through it. Higher values give more protection against finishing-blow clicks accidentally skipping ahead; 0 disables the buffer entirely.", false, false, (ValueValidator)null, (string)null);
			_category.SaveToFile(false);
		}

		public static bool ShouldNotify(NotificationColors.ItemClass itemClass)
		{
			return itemClass switch
			{
				NotificationColors.ItemClass.Progression => NotifyProgression.Value, 
				NotificationColors.ItemClass.Useful => NotifyUseful.Value, 
				NotificationColors.ItemClass.Trap => NotifyTrap.Value, 
				_ => NotifyFiller.Value, 
			};
		}

		public static void Save()
		{
			_category.SaveToFile(false);
		}
	}
	public class ItemManager
	{
		private readonly struct QueuedItem
		{
			public readonly long ItemId;

			public readonly long LocationId;

			public readonly string SenderDisplayName;

			public readonly int SenderSlot;

			public readonly bool IsCatchUp;

			public readonly ItemFlags Flags;

			public QueuedItem(long itemId, long locationId, string senderDisplayName, int senderSlot, ItemFlags flags, bool isCatchUp)
			{
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				ItemId = itemId;
				LocationId = locationId;
				SenderDisplayName = senderDisplayName;
				SenderSlot = senderSlot;
				Flags = flags;
				IsCatchUp = isCatchUp;
			}
		}

		private static readonly TimeSpan CatchUpWindow = TimeSpan.FromSeconds(5.0);

		private readonly ArchipelagoConnection _connection;

		private readonly Instance _log;

		private readonly ConcurrentQueue<QueuedItem> _itemQueue = new ConcurrentQueue<QueuedItem>();

		public ItemManager(ArchipelagoConnection connection, Instance log)
		{
			_connection = connection;
			_log = log;
			_connection.Connected += OnConnected;
		}

		private void OnConnected()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_connection.Session == null)
				{
					return;
				}
				_connection.Session.Items.ItemReceived += (ItemReceivedHandler)delegate(ReceivedItemsHelper helper)
				{
					//IL_0052: Unknown result type (might be due to invalid IL or missing references)
					try
					{
						ItemInfo val2 = helper.DequeueItem();
						bool isCatchUp = DateTime.UtcNow - _connection.ConnectedAtUtc < CatchUpWindow;
						string alias = val2.Player.Alias;
						_itemQueue.Enqueue(new QueuedItem(val2.ItemId, val2.LocationId, alias, val2.Player.Slot, val2.Flags, isCatchUp));
					}
					catch (Exception arg3)
					{
						_log.Error("ItemReceived handler threw for one item -- it may not unlock its " + $"level, but every other item is unaffected: {arg3}");
					}
				};
				while (_connection.Session.Items.Any())
				{
					try
					{
						ItemInfo val = _connection.Session.Items.DequeueItem();
						_itemQueue.Enqueue(new QueuedItem(val.ItemId, val.LocationId, val.Player.Alias, val.Player.Slot, val.Flags, isCatchUp: true));
					}
					catch (Exception arg)
					{
						_log.Error("Draining one historical item from the received-items queue threw -- it " + $"may not unlock its level, but every other item is unaffected: {arg}");
					}
				}
			}
			catch (Exception arg2)
			{
				_log.Error("ItemManager.OnConnected failed -- items received this connection " + $"may not unlock levels correctly: {arg2}");
			}
		}

		public void ProcessQueue()
		{
			QueuedItem result;
			while (_itemQueue.TryDequeue(out result))
			{
				ApplyItem(result);
			}
		}

		private void ApplyItem(QueuedItem queued)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			long itemId = queued.ItemId;
			bool isSelfSend = _connection.Session != null && queued.SenderSlot == _connection.Session.ConnectionInfo.Slot;
			switch (itemId)
			{
			case 0L:
				return;
			case 3901100L:
				_log.Msg("Received 'White Space' -- filler, nothing to apply.");
				Notify("White Space", queued.SenderDisplayName, queued.IsCatchUp, queued.LocationId, isSelfSend, queued.Flags);
				return;
			}
			if (!LevelCatalog.ItemIdToLevel.TryGetValue(itemId, out LevelEntry value))
			{
				_log.Warning($"Received unknown item id {itemId} -- no matching level in LevelCatalog.");
				return;
			}
			UnlockState.Unlock(value.LevelId);
			_log.Msg($"Unlocked '{value.DisplayName}' (level id {value.LevelId}, scene '{value.SceneName}') from a received item.");
			Notify(value.DisplayName, queued.SenderDisplayName, queued.IsCatchUp, queued.LocationId, isSelfSend, queued.Flags);
		}

		private void Notify(string itemDisplayName, string senderDisplayName, bool isCatchUp, long locationId, bool isSelfSend, ItemFlags flags)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if (!isSelfSend)
			{
				char color = NotificationColors.ForItemFlags(flags);
				LogSegment[] segments = new LogSegment[4]
				{
					new LogSegment("Received ", 'w'),
					new LogSegment(itemDisplayName, color),
					new LogSegment(" from ", 'w'),
					new LogSegment(senderDisplayName, 'D')
				};
				string popupText = (Config.ShouldNotify(NotificationColors.Classify(flags)) ? ("Received " + itemDisplayName + " from " + senderDisplayName) : null);
				if (isCatchUp)
				{
					NotificationLog.Add(segments, null, locationId);
				}
				else
				{
					NotificationLog.Add(segments, popupText);
				}
			}
		}
	}
	public static class LevelAccessGuard
	{
		public static bool ShouldBlock(LevelInfo level, out string blockMessage)
		{
			blockMessage = "";
			if (!Mod.IsEnabled)
			{
				return false;
			}
			if (level == null || string.IsNullOrEmpty(level.SceneFileName))
			{
				return false;
			}
			LevelInfo val = ResolveToTrackedLevel(level);
			if (val == null)
			{
				return false;
			}
			if (!LevelCatalog.LevelIdToLevel.TryGetValue(val.ID, out LevelEntry value))
			{
				return false;
			}
			if (!LevelCatalog.LevelIdToLevel.ContainsKey(level.ID))
			{
				blockMessage = "Return to hub to continue";
				return true;
			}
			if (value.Order == 1)
			{
				return false;
			}
			if (Mod.Connection != null && Mod.Connection.IsLevelExcluded(value.Order))
			{
				return false;
			}
			if (!UnlockState.IsUnlocked(val.ID))
			{
				blockMessage = "LOCKED: '" + value.DisplayName + "' needs an Archipelago item to unlock";
				return true;
			}
			if (value.Order == LevelCatalog.Levels.Count)
			{
				int num = Mod.Locations?.GetLevelsRequiredForFree() ?? 0;
				int num2 = Mod.Locations?.CountOtherLevelsCompleted() ?? 0;
				if (num2 < num)
				{
					blockMessage = $"LOCKED: {num2}/{num} levels needed";
					return true;
				}
			}
			return false;
		}

		private static LevelInfo? ResolveToTrackedLevel(LevelInfo level)
		{
			LevelInfo val = level;
			int num = 0;
			while (val != null && !string.IsNullOrEmpty(val.SceneFileName) && !LevelCatalog.LevelIdToLevel.ContainsKey(val.ID) && num < 64)
			{
				int num2 = LevelSetup.GetLevelIndexByID(val.ID) + 1;
				val = ((num2 < LevelSetup.Levels.Count) ? LevelSetup.Levels[num2] : null);
				num++;
			}
			return val;
		}
	}
	public class LevelEntry
	{
		public int Order;

		public string SceneName = "";

		public string DisplayName = "";

		public int LevelId;

		public bool HasSecret;
	}
	public static class LevelCatalog
	{
		public const long BaseId = 3891000L;

		public const long ItemIdOffset = 10000L;

		public const long SecretLocationIdOffset = 20000L;

		public const long WhiteSpaceItemId = 3901100L;

		public static List<LevelEntry> Levels { get; private set; } = new List<LevelEntry>();

		public static Dictionary<long, LevelEntry> LocationIdToLevel { get; private set; } = new Dictionary<long, LevelEntry>();

		public static Dictionary<long, LevelEntry> ItemIdToLevel { get; private set; } = new Dictionary<long, LevelEntry>();

		public static Dictionary<long, LevelEntry> SecretLocationIdToLevel { get; private set; } = new Dictionary<long, LevelEntry>();

		public static Dictionary<int, LevelEntry> LevelIdToLevel { get; private set; } = new Dictionary<int, LevelEntry>();

		public static void Load(Instance log)
		{
			string text = Path.Combine(Path.GetDirectoryName(typeof(LevelCatalog).Assembly.Location), "data", "levels.json");
			if (!File.Exists(text))
			{
				log.Error("levels.json not found at '" + text + "' -- location/item id mapping will be empty.");
				return;
			}
			foreach (JToken item in (IEnumerable<JToken>)JObject.Parse(File.ReadAllText(text))["levels"])
			{
				LevelEntry obj = new LevelEntry
				{
					Order = Extensions.Value<int>((IEnumerable<JToken>)item[(object)"order"]),
					SceneName = Extensions.Value<string>((IEnumerable<JToken>)item[(object)"id"]),
					DisplayName = Extensions.Value<string>((IEnumerable<JToken>)item[(object)"name"]),
					LevelId = Extensions.Value<int>((IEnumerable<JToken>)item[(object)"gameId"])
				};
				JToken obj2 = item[(object)"hasSecret"];
				obj.HasSecret = obj2 != null && Extensions.Value<bool>((IEnumerable<JToken>)obj2);
				LevelEntry levelEntry = obj;
				Levels.Add(levelEntry);
				long key = 3891000L + (long)levelEntry.Order;
				long key2 = 3901000L + (long)levelEntry.Order;
				LocationIdToLevel[key] = levelEntry;
				ItemIdToLevel[key2] = levelEntry;
				if (levelEntry.HasSecret)
				{
					long key3 = 3911000L + (long)levelEntry.Order;
					SecretLocationIdToLevel[key3] = levelEntry;
				}
				LevelIdToLevel[levelEntry.LevelId] = levelEntry;
			}
			log.Msg($"LevelCatalog loaded {Levels.Count} levels from '{text}'.");
		}

		public static string? TryGetShortItemDisplayName(long itemId)
		{
			if (itemId == 3901100)
			{
				return "White Space";
			}
			if (!ItemIdToLevel.TryGetValue(itemId, out LevelEntry value))
			{
				return null;
			}
			return value.DisplayName;
		}
	}
	public class LocationManager
	{
		private readonly ArchipelagoConnection _connection;

		private readonly Instance _log;

		private readonly ConcurrentQueue<(LogSegment[] Segments, string? PopupText, long OrderKey)> _pendingNotifications = new ConcurrentQueue<(LogSegment[], string, long)>();

		public LocationManager(ArchipelagoConnection connection, Instance log)
		{
			_connection = connection;
			_log = log;
			_connection.Connected += OnConnected;
		}

		private void OnConnected()
		{
			try
			{
				OnConnectedCore();
			}
			catch (Exception arg)
			{
				_log.Error("LocationManager.OnConnected failed, notification history resync " + $"skipped for this connection: {arg}");
			}
		}

		private void OnConnectedCore()
		{
			if (_connection.Session == null)
			{
				return;
			}
			long[] array = _connection.Session.Locations.AllLocationsChecked.Where((long id) => LevelCatalog.LocationIdToLevel.ContainsKey(id) || LevelCatalog.SecretLocationIdToLevel.ContainsKey(id)).ToArray();
			if (array.Length == 0)
			{
				return;
			}
			_connection.Session.Locations.ScoutLocationsAsync(array).ContinueWith(delegate(Task<Dictionary<long, ScoutedItemInfo>> task)
			{
				//IL_006b: Unknown result type (might be due to invalid IL or missing references)
				if (task.IsFaulted || task.IsCanceled || task.Result == null)
				{
					_log.Warning("Failed to scout this slot's already-checked locations -- the AP LOG screen won't show full history until the next successful reconnect.");
					return;
				}
				foreach (KeyValuePair<long, ScoutedItemInfo> item2 in task.Result)
				{
					try
					{
						string text = LevelCatalog.TryGetShortItemDisplayName(((ItemInfo)item2.Value).ItemId) ?? ((ItemInfo)item2.Value).ItemDisplayName;
						char color = NotificationColors.ForItemFlags(((ItemInfo)item2.Value).Flags);
						LogSegment[] item = ((_connection.Session == null || item2.Value.Player.Slot != _connection.Session.ConnectionInfo.Slot) ? new LogSegment[5]
						{
							new LogSegment("Sent ", 'w'),
							new LogSegment(text, color),
							new LogSegment(" to ", 'w'),
							new LogSegment(item2.Value.Player.Alias, 'D'),
							new LogSegment(" from " + ResolveLocationDisplayText(item2.Key), 'w')
						} : new LogSegment[3]
						{
							new LogSegment(item2.Value.Player.Alias, 'D'),
							new LogSegment(" found their ", 'w'),
							new LogSegment(text, color)
						});
						_pendingNotifications.Enqueue((item, null, item2.Key));
					}
					catch (Exception arg)
					{
						_log.Error("Processing one scouted historical location threw -- it may be " + $"missing from the AP LOG, but every other entry is unaffected: {arg}");
					}
				}
			});
		}

		public void ProcessPendingNotifications()
		{
			(LogSegment[], string, long) result;
			while (_pendingNotifications.TryDequeue(out result))
			{
				NotificationLog.Add(result.Item1, result.Item2, result.Item3);
			}
		}

		public void CheckLocation(int levelId)
		{
			if (_connection.Session == null || !_connection.IsConnected)
			{
				_log.Warning($"CheckLocation({levelId}) called before connecting -- ignored.");
				return;
			}
			if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value))
			{
				_log.Warning($"Unknown level id {levelId} -- no matching entry in levels.json. " + "Either a level outside our catalog just finished, or levels.json's order/id list is out of sync with the real game.");
				return;
			}
			if (value.Order != LevelCatalog.Levels.Count && !_connection.IsLevelExcluded(value.Order))
			{
				long num = 3891000L + (long)value.Order;
				if (_connection.Session.Locations.AllLocationsChecked.Contains(num))
				{
					_log.Msg("'" + value.DisplayName + "' already checked -- skipping duplicate check/notification.");
				}
				else
				{
					_connection.Session.Locations.CompleteLocationChecks(new long[1] { num });
					_log.Msg($"Sent check for '{value.DisplayName}' (level id {levelId}, location id {num}).");
					ScoutAndQueueSentNotification(num, value.DisplayName, isLive: true);
				}
			}
			if (value.Order == LevelCatalog.Levels.Count)
			{
				_connection.Session.SetGoalAchieved();
				_log.Msg("'" + value.DisplayName + "' was the final level -- reported goal achieved.");
				SaveManager.Instance.SetValue("storyFinished", (object)true);
			}
		}

		public void CheckSecretLocation(int levelId)
		{
			if (_connection.Session == null || !_connection.IsConnected)
			{
				_log.Warning($"CheckSecretLocation({levelId}) called before connecting -- ignored.");
				return;
			}
			if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value))
			{
				_log.Warning($"Unknown level id {levelId} for a secret find -- no matching entry in " + "levels.json. Either a level outside our catalog has a secret we don't know about, or levels.json's order/id list is out of sync with the real game.");
				return;
			}
			if (!value.HasSecret)
			{
				_log.Warning("Secret found in '" + value.DisplayName + "', but levels.json says this level has none -- sending the check anyway, but this likely means levels.json's hasSecret is out of sync with the real game.");
			}
			if (_connection.IsLevelExcluded(value.Order))
			{
				_log.Msg("'" + value.DisplayName + "' secret found, but this level is excluded from tracking (ExcludeSlowLevels) -- no check to send.");
				return;
			}
			long num = 3911000L + (long)value.Order;
			if (_connection.Session.Locations.AllLocationsChecked.Contains(num))
			{
				_log.Msg("'" + value.DisplayName + "' secret already checked -- skipping duplicate check/notification.");
				return;
			}
			_connection.Session.Locations.CompleteLocationChecks(new long[1] { num });
			_log.Msg($"Sent secret check for '{value.DisplayName}' (level id {levelId}, location id {num}).");
			ScoutAndQueueSentNotification(num, value.DisplayName + " Secret", isLive: true);
		}

		private void ScoutAndQueueSentNotification(long locationId, string locationDisplayText, bool isLive)
		{
			if (_connection.Session == null)
			{
				return;
			}
			_connection.Session.Locations.ScoutLocationsAsync(new long[1] { locationId }).ContinueWith(delegate(Task<Dictionary<long, ScoutedItemInfo>> task)
			{
				//IL_020c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					ItemFlags? val = null;
					string text2;
					LogSegment[] item;
					if (!task.IsFaulted && !task.IsCanceled && task.Result != null && task.Result.TryGetValue(locationId, out var value))
					{
						val = ((ItemInfo)value).Flags;
						string text = LevelCatalog.TryGetShortItemDisplayName(((ItemInfo)value).ItemId) ?? ((ItemInfo)value).ItemDisplayName;
						char color = NotificationColors.ForItemFlags(((ItemInfo)value).Flags);
						if (_connection.Session != null && value.Player.Slot == _connection.Session.ConnectionInfo.Slot)
						{
							text2 = value.Player.Alias + " found their " + text;
							item = new LogSegment[3]
							{
								new LogSegment(value.Player.Alias, 'D'),
								new LogSegment(" found their ", 'w'),
								new LogSegment(text, color)
							};
						}
						else
						{
							text2 = "Sent " + text + " to " + value.Player.Alias;
							item = new LogSegment[5]
							{
								new LogSegment("Sent ", 'w'),
								new LogSegment(text, color),
								new LogSegment(" to ", 'w'),
								new LogSegment(value.Player.Alias, 'D'),
								new LogSegment(" from " + locationDisplayText, 'w')
							};
						}
					}
					else
					{
						_log.Warning($"Failed to scout location {locationId} for a notification -- " + "showing a generic message instead.");
						text2 = "Sent check for " + locationDisplayText;
						item = new LogSegment[1]
						{
							new LogSegment(text2, 'w')
						};
					}
					bool flag = isLive && (!val.HasValue || Config.ShouldNotify(NotificationColors.Classify(val.Value)));
					_pendingNotifications.Enqueue((item, flag ? text2 : null, long.MaxValue));
				}
				catch (Exception arg)
				{
					_log.Error($"Building a notification for location {locationId} threw -- it may be " + $"missing a popup/log entry, but nothing else is affected: {arg}");
				}
			});
		}

		private static string ResolveLocationDisplayText(long locationId)
		{
			if (LevelCatalog.LocationIdToLevel.TryGetValue(locationId, out LevelEntry value))
			{
				return value.DisplayName;
			}
			if (LevelCatalog.SecretLocationIdToLevel.TryGetValue(locationId, out LevelEntry value2))
			{
				return value2.DisplayName + " Secret";
			}
			return "an unknown location";
		}

		public bool IsLevelCompleted(int levelId)
		{
			if (_connection.Session == null || !_connection.IsConnected)
			{
				return false;
			}
			if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value))
			{
				return false;
			}
			if (value.Order == LevelCatalog.Levels.Count)
			{
				return UnlockState.IsUnlocked(levelId);
			}
			if (_connection.IsLevelExcluded(value.Order))
			{
				return true;
			}
			long value2 = 3891000L + (long)value.Order;
			return _connection.Session.Locations.AllLocationsChecked.Contains(value2);
		}

		public bool IsSecretCompleted(int levelId)
		{
			if (_connection.Session == null || !_connection.IsConnected)
			{
				return false;
			}
			if (!LevelCatalog.LevelIdToLevel.TryGetValue(levelId, out LevelEntry value))
			{
				return false;
			}
			if (_connection.IsLevelExcluded(value.Order))
			{
				return true;
			}
			long value2 = 3911000L + (long)value.Order;
			return _connection.Session.Locations.AllLocationsChecked.Contains(value2);
		}

		private IEnumerable<LevelEntry> OtherTrackedLevels()
		{
			foreach (LevelEntry level in LevelCatalog.Levels)
			{
				if (level.Order != LevelCatalog.Levels.Count && !_connection.IsLevelExcluded(level.Order))
				{
					yield return level;
				}
			}
		}

		public int CountOtherLevelsCompleted()
		{
			int num = 0;
			foreach (LevelEntry item in OtherTrackedLevels())
			{
				if (IsLevelCompleted(item.LevelId))
				{
					num++;
				}
			}
			return num;
		}

		public int GetLevelsRequiredForFree()
		{
			return Math.Min(_connection.LevelsRequiredForFree, OtherTrackedLevels().Count());
		}
	}
	public class Mod : MelonMod
	{
		private static Mod? _instance;

		private static bool _suppressAutoReconnect;

		public static ArchipelagoConnection? Connection { get; private set; }

		public static LocationManager? Locations { get; private set; }

		public static ItemManager? Items { get; private set; }

		public static Instance? Log { get; private set; }

		public static bool IsEnabled => Config.Enabled.Value;

		private static bool IsFullyConnected
		{
			get
			{
				if (Connection != null)
				{
					return Connection.IsConnected;
				}
				return false;
			}
		}

		public override void OnInitializeMelon()
		{
			_instance = this;
			Log = ((MelonBase)this).LoggerInstance;
			((MelonBase)this).LoggerInstance.Msg("SuperhotArchipelago loading.");
			Config.Load();
			LevelCatalog.Load(((MelonBase)this).LoggerInstance);
			Connection = new ArchipelagoConnection(((MelonBase)this).LoggerInstance);
			Locations = new LocationManager(Connection, ((MelonBase)this).LoggerInstance);
			Items = new ItemManager(Connection, ((MelonBase)this).LoggerInstance);
			if (!Config.Enabled.Value)
			{
				((MelonBase)this).LoggerInstance.Msg("Not connecting yet: Archipelago mode is turned off. Click the AP MODE icon on the hub's main screen to turn it back on.");
			}
			else if (Config.IsConfigured)
			{
				TryConnect();
			}
			else
			{
				((MelonBase)this).LoggerInstance.Msg("Not connecting yet: Server/Slot not set. Click the ARCHIPELAGO icon on the hub's main screen to open the connection menu and fill them in, or edit the [SuperhotArchipelago] section of UserData/MelonPreferences.cfg directly (Server/Slot/Password changes there also trigger a reconnect automatically).");
			}
			((MelonEventBase<LemonAction<string, string>>)(object)Config.Server.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				TryConnect();
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)Config.Slot.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				TryConnect();
			}, 0, false);
			((MelonEventBase<LemonAction<string, string>>)(object)Config.Password.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate
			{
				TryConnect();
			}, 0, false);
			((MelonEventBase<LemonAction<bool, bool>>)(object)Config.Enabled.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate
			{
				ApplyEnabledChange();
			}, 0, false);
		}

		private void TryConnect()
		{
			if (!_suppressAutoReconnect && Config.Enabled.Value && Config.IsConfigured)
			{
				((MelonBase)this).LoggerInstance.Msg("Connecting to '" + Config.Server.Value + "' as '" + Config.Slot.Value + "'...");
				Connection?.Connect(Config.Server.Value, Config.Slot.Value, (Config.Password.Value == "") ? null : Config.Password.Value);
			}
		}

		public static void SetEnabled(bool enabled)
		{
			if (_instance != null)
			{
				_suppressAutoReconnect = true;
				try
				{
					Config.Enabled.Value = enabled;
					Config.Save();
				}
				finally
				{
					_suppressAutoReconnect = false;
				}
				ApplyEnabledChange();
			}
		}

		private static void ApplyEnabledChange()
		{
			if (_suppressAutoReconnect)
			{
				return;
			}
			if (Config.Enabled.Value)
			{
				Instance? log = Log;
				if (log != null)
				{
					log.Msg("Archipelago mode turned ON.");
				}
				_instance?.TryConnect();
			}
			else
			{
				Instance? log2 = Log;
				if (log2 != null)
				{
					log2.Msg("Archipelago mode turned OFF -- SUPERHOT will play like vanilla until it's turned back on.");
				}
				Connection?.Disconnect();
			}
		}

		public static void ApplyConnectionSettingsAndConnect(string server, string slot, string password)
		{
			if (_instance != null)
			{
				_suppressAutoReconnect = true;
				try
				{
					Config.Server.Value = server;
					Config.Slot.Value = slot;
					Config.Password.Value = password;
					Config.Enabled.Value = true;
					Config.Save();
				}
				finally
				{
					_suppressAutoReconnect = false;
				}
				_instance.TryConnect();
			}
		}

		public override void OnUpdate()
		{
			RunStep("Items.ProcessQueue", delegate
			{
				Items?.ProcessQueue();
			});
			RunStep("Locations.ProcessPendingNotifications", delegate
			{
				Locations?.ProcessPendingNotifications();
			});
			RunStep("NotificationLog.FlushPendingPopups", NotificationLog.FlushPendingPopups);
			RunStep("PopupOverlay.Update", PopupOverlay.Update);
			RunStep("ConnectionButtonPatch.RefreshLabel", ConnectionButtonPatch.RefreshLabel);
			RunStep("ArchipelagoModeTogglePatch.RefreshLabel", ArchipelagoModeTogglePatch.RefreshLabel);
		}

		private void RunStep(string name, Action step)
		{
			try
			{
				step();
			}
			catch (Exception arg)
			{
				((MelonBase)this).LoggerInstance.Error($"[debug] OnUpdate step '{name}' threw: {arg}");
			}
		}

		public override void OnSceneWasLoaded(int buildIndex, string sceneName)
		{
			((MelonBase)this).LoggerInstance.Msg($"Scene loaded: {sceneName} (buildIndex {buildIndex})");
			try
			{
				RunSceneWasLoadedBody(sceneName);
			}
			catch (Exception arg)
			{
				((MelonBase)this).LoggerInstance.Error($"OnSceneWasLoaded('{sceneName}') threw: {arg}");
			}
		}

		private void RunSceneWasLoadedBody(string sceneName)
		{
			if (IsEnabled && IsFullyConnected && SaveManager.Instance != null && Locations != null)
			{
				foreach (LevelEntry level in LevelCatalog.Levels)
				{
					if (level.HasSecret)
					{
						bool flag = Locations.IsSecretCompleted(level.LevelId);
						SaveManager.Instance.SetValue(level.SceneName + "1unlocked", (object)flag);
					}
				}
			}
			LevelInfo currentLevelInfo = LevelSetup.CurrentLevelInfo;
			if (sceneName != "SHMenu" && currentLevelInfo != null && LevelAccessGuard.ShouldBlock(currentLevelInfo, out string _))
			{
				((MelonBase)this).LoggerInstance.Msg("Safety net: scene '" + sceneName + "' resolved to a locked level that no launch-time gate caught -- kicking back to hub.");
				if ((Object)(object)SHGUI.current != (Object)null)
				{
					SHGUI.current.LaunchLevelAppTunnels("SHMenu", false, 1f);
				}
			}
			PopupOverlay.OnSceneLoaded();
		}
	}
	public static class NotificationColors
	{
		public enum ItemClass
		{
			Progression,
			Useful,
			Trap,
			Filler
		}

		public const char Player = 'D';

		public const char ProgressionItem = 'g';

		public const char UsefulItem = 'b';

		public const char TrapItem = 'r';

		public const char FillerItem = 'z';

		public const char Default = 'w';

		public static ItemClass Classify(ItemFlags flags)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (((Enum)flags).HasFlag((Enum)(object)(ItemFlags)4))
			{
				return ItemClass.Trap;
			}
			if (((Enum)flags).HasFlag((Enum)(object)(ItemFlags)1))
			{
				return ItemClass.Progression;
			}
			if (((Enum)flags).HasFlag((Enum)(object)(ItemFlags)2))
			{
				return ItemClass.Useful;
			}
			return ItemClass.Filler;
		}

		public static char ForItemFlags(ItemFlags flags)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Classify(flags) switch
			{
				ItemClass.Trap => 'r', 
				ItemClass.Progression => 'g', 
				ItemClass.Useful => 'b', 
				_ => 'z', 
			};
		}
	}
	public readonly struct LogSegment
	{
		public readonly string Text;

		public readonly char Color;

		public LogSegment(string text, char color)
		{
			Text = text;
			Color = color;
		}
	}
	public static class NotificationLog
	{
		private const int MaxEntries = 200;

		private static readonly List<LogSegment[]> _entries = new List<LogSegment[]>();

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

		private static readonly List<string> _pendingPopups = new List<string>();

		private const int MaxPendingPopups = 4;

		public static IReadOnlyList<LogSegment[]> Entries => _entries;

		public static void Add(LogSegment[] segments, string? popupText)
		{
			Add(segments, popupText, long.MaxValue);
		}

		public static void Add(string plainText, string? popupText)
		{
			Add(new LogSegment[1]
			{
				new LogSegment(plainText, 'w')
			}, popupText, long.MaxValue);
		}

		public static void Add(LogSegment[] segments, string? popupText, long orderKey)
		{
			int num = _entries.Count;
			while (num > 0 && _entryOrderKeys[num - 1] > orderKey)
			{
				num--;
			}
			_entries.Insert(num, segments);
			_entryOrderKeys.Insert(num, orderKey);
			while (_entries.Count > 200)
			{
				_entries.RemoveAt(0);
				_entryOrderKeys.RemoveAt(0);
			}
			if (popupText != null)
			{
				_pendingPopups.Add(popupText);
				while (_pendingPopups.Count > 4)
				{
					_pendingPopups.RemoveAt(0);
				}
			}
		}

		public static void FlushPendingPopups()
		{
			if (_pendingPopups.Count == 0 || TimeControl.forcedTimeScale == 0f)
			{
				return;
			}
			foreach (string pendingPopup in _pendingPopups)
			{
				PopupOverlay.Show(pendingPopup);
			}
			_pendingPopups.Clear();
		}

		public static void Clear()
		{
			_entries.Clear();
			_entryOrderKeys.Clear();
			_pendingPopups.Clear();
		}
	}
	public static class PopupOverlay
	{
		private enum State
		{
			Hidden,
			FadingIn,
			Visible,
			FadingOut
		}

		private const int ReferenceWidth = 1920;

		private const int ReferenceHeight = 1080;

		private const float DedupeWindowSeconds = 1f;

		private static RectTransform? _panel;

		private static CanvasGroup? _canvasGroup;

		private static Text? _text;

		private static readonly Queue<string> _queue = new Queue<string>();

		private static float? _fixedCharAdvance;

		private static float? _fixedLineHeight;

		private static State _state = State.Hidden;

		private static float _timer;

		private static string? _lastShownMessage;

		private static float _lastShownRealtime = float.NegativeInfinity;

		private static GameObject? _canvasRoot;

		private const string GameFontAssetPath = "Fonts/SHGUIFontAsset_EU";

		private static readonly string[] PreferredMonospaceFonts = new string[5] { "Consolas", "Courier New", "DejaVu Sans Mono", "Lucida Console", "Monospace" };

		private static PopupTuning T => PopupTuning.Current;

		public static void Show(string text)
		{
			bool num = text == _lastShownMessage && Time.unscaledTime - _lastShownRealtime < 1f;
			_lastShownMessage = text;
			_lastShownRealtime = Time.unscaledTime;
			if (!num)
			{
				_queue.Enqueue(text);
			}
		}

		public static void OnSceneLoaded()
		{
		}

		public static void Update()
		{
			EnsureCanvas();
			switch (_state)
			{
			case State.Hidden:
				if (_queue.Count > 0)
				{
					DisplayNext();
				}
				break;
			case State.FadingIn:
				_timer += Time.unscaledDeltaTime;
				ApplyTransition(Mathf.Clamp01(_timer / T.TransitionSeconds));
				if (_timer >= T.TransitionSeconds)
				{
					_state = State.Visible;
					_timer = T.DisplaySeconds;
				}
				break;
			case State.Visible:
				_timer -= Time.unscaledDeltaTime;
				if (_timer <= 0f)
				{
					_state = State.FadingOut;
					_timer = 0f;
				}
				break;
			case State.FadingOut:
				_timer += Time.unscaledDeltaTime;
				ApplyTransition(1f - Mathf.Clamp01(_timer / T.TransitionSeconds));
				if (_timer >= T.TransitionSeconds)
				{
					_state = State.Hidden;
					((Component)_panel).gameObject.SetActive(false);
				}
				break;
			}
		}

		private static void ApplyTransition(float t)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			_canvasGroup.alpha = t;
			((Transform)_panel).localScale = new Vector3(1f, Mathf.Lerp(T.MinScaleY, 1f, t), 1f);
		}

		public static void ReloadTuning()
		{
			if ((Object)(object)_canvasRoot != (Object)null)
			{
				Object.Destroy((Object)(object)_canvasRoot);
			}
			_canvasRoot = null;
			_text = null;
			_panel = null;
			_canvasGroup = null;
			_fixedCharAdvance = null;
			_fixedLineHeight = null;
			_state = State.Hidden;
			_timer = 0f;
		}

		private static void EnsureCanvas()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_text != (Object)null))
			{
				PopupTuning.Load();
				GameObject val = (_canvasRoot = new GameObject("SuperhotArchipelago_PopupCanvas"));
				Object.DontDestroyOnLoad((Object)(object)val);
				Canvas obj = val.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 1000;
				CanvasScaler obj2 = val.AddComponent<CanvasScaler>();
				obj2.uiScaleMode = (ScaleMode)1;
				obj2.referenceResolution = new Vector2(1920f, 1080f);
				obj2.screenMatchMode = (ScreenMatchMode)0;
				obj2.matchWidthOrHeight = 0.5f;
				GameObject val2 = new GameObject("Panel");
				val2.transform.SetParent(val.transform, false);
				_panel = val2.AddComponent<RectTransform>();
				_panel.anchorMin = new Vector2(0.5f, T.TopAnchorY);
				_panel.anchorMax = new Vector2(0.5f, T.TopAnchorY);
				_panel.pivot = new Vector2(0.5f, 1f);
				_canvasGroup = val2.AddComponent<CanvasGroup>();
				CreateStretchImage((Transform)(object)_panel, "Background", T.BackgroundColor, 0f);
				Font val3 = ResolveFont();
				GameObject val4 = new GameObject("Text");
				val4.transform.SetParent((Transform)(object)_panel, false);
				RectTransform val5 = val4.AddComponent<RectTransform>();
				val5.anchorMin = new Vector2(0.5f, 0.5f);
				val5.anchorMax = new Vector2(0.5f, 0.5f);
				val5.pivot = new Vector2(0.5f, 0.5f);
				((Transform)val5).localScale = new Vector3(T.PixelScale, T.PixelScale, 1f);
				_text = val4.AddComponent<Text>();
				_text.font = val3;
				_text.fontSize = T.FontSize;
				_text.fontStyle = (FontStyle)1;
				_text.alignment = (TextAnchor)4;
				_text.horizontalOverflow = (HorizontalWrapMode)1;
				_text.verticalOverflow = (VerticalWrapMode)1;
				((Graphic)_text).color = Color.white;
				if (_fixedLineHeight.HasValue)
				{
					val5.anchoredPosition = new Vector2(0f, (0f - _fixedLineHeight.Value) * T.PixelScale * T.TextVerticalNudgeFraction);
				}
				if ((Object)(object)val3.material != (Object)null && (Object)(object)val3.material.mainTexture != (Object)null)
				{
					val3.material.mainTexture.filterMode = (FilterMode)0;
				}
				val2.SetActive(false);
			}
		}

		private static Image CreateStretchImage(Transform parent, string name, Color color, float inset)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent, false);
			RectTransform obj = val.AddComponent<RectTransform>();
			obj.anchorMin = Vector2.zero;
			obj.anchorMax = Vector2.one;
			obj.offsetMin = new Vector2(inset, inset);
			obj.offsetMax = new Vector2(0f - inset, 0f - inset);
			Image obj2 = val.AddComponent<Image>();
			((Graphic)obj2).color = color;
			return obj2;
		}

		private static Font ResolveFont()
		{
			Font val = BuildGameFont();
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			Font val2 = Font.CreateDynamicFontFromOSFont(PreferredMonospaceFonts, T.FontSize);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2;
			}
			Font builtinResource = Resources.GetBuiltinResource<Font>("Arial.ttf");
			if ((Object)(object)builtinResource != (Object)null)
			{
				return builtinResource;
			}
			return Font.CreateDynamicFontFromOSFont("Arial", T.FontSize);
		}

		private static Font? BuildGameFont()
		{
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Expected O, but got Unknown
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Expected O, but got Unknown
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			SHGUIFontAsset val = Resources.Load<SHGUIFontAsset>("Fonts/SHGUIFontAsset_EU");
			if ((Object)(object)val == (Object)null || (Object)(object)val.FontTexture == (Object)null || (Object)(object)val.FontDescription == (Object)null)
			{
				return null;
			}
			Texture2D fontTexture = val.FontTexture;
			float num = ((Texture)fontTexture).width;
			float num2 = ((Texture)fontTexture).height;
			List<CharacterInfo> list = new List<CharacterInfo>();
			foreach (XElement item2 in ((XContainer)XDocument.Parse(val.FontDescription.text)).Descendants(XName.op_Implicit("char")))
			{
				XAttribute obj = item2.Attribute(XName.op_Implicit("id"));
				if (!int.TryParse((obj != null) ? obj.Value : null, out var result))
				{
					continue;
				}
				XAttribute obj2 = item2.Attribute(XName.op_Implicit("x"));
				if (!int.TryParse((obj2 != null) ? obj2.Value : null, out var result2))
				{
					continue;
				}
				XAttribute obj3 = item2.Attribute(XName.op_Implicit("y"));
				if (!int.TryParse((obj3 != null) ? obj3.Value : null, out var result3))
				{
					continue;
				}
				XAttribute obj4 = item2.Attribute(XName.op_Implicit("width"));
				if (!int.TryParse((obj4 != null) ? obj4.Value : null, out var result4))
				{
					continue;
				}
				XAttribute obj5 = item2.Attribute(XName.op_Implicit("height"));
				if (int.TryParse((obj5 != null) ? obj5.Value : null, out var result5))
				{
					float num3 = (float)result2 / num;
					float num4 = (float)(result2 + result4) / num;
					float num5 = 1f - (float)result3 / num2;
					float num6 = 1f - (float)(result3 + result5) / num2;
					float num7 = val.CharSize.y;
					if (result >= 65 && result <= 90)
					{
						num7 -= T.CapitalLetterNudge;
					}
					CharacterInfo item = new CharacterInfo
					{
						index = result,
						uv = new Rect(num3, num6, num4 - num3, num5 - num6),
						vert = new Rect(0f, num7, (float)result4, (float)(-result5)),
						width = val.CharSize.x
					};
					list.Add(item);
				}
			}
			if (list.Count == 0)
			{
				return null;
			}
			Font val2 = new Font
			{
				material = new Material(Shader.Find("UI/Default"))
				{
					mainTexture = (Texture)(object)fontTexture
				},
				characterInfo = list.ToArray()
			};
			_fixedCharAdvance = val.CharSize.x;
			_fixedLineHeight = val.CharSize.y;
			return val2;
		}

		private static void DisplayNext()
		{
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			string text = _queue.Dequeue();
			((Component)_panel).gameObject.SetActive(true);
			float num = _fixedCharAdvance ?? ((float)T.FontSize * 0.6f);
			float num2 = _fixedLineHeight ?? ((float)T.FontSize * 1.2f);
			int maxCols = Mathf.Max(4, Mathf.FloorToInt(T.MaxWidthFraction * 1920f / (num * T.PixelScale)) - T.PaddingHorizontalCells * 2);
			text = WrapText(text, maxCols);
			_text.text = text;
			string[] array = text.Split(new char[1] { '\n' });
			int num3 = 0;
			string[] array2 = array;
			foreach (string text2 in array2)
			{
				num3 = Math.Max(num3, text2.Length);
			}
			int num4 = num3 + T.PaddingHorizontalCells * 2;
			int num5 = array.Length + T.PaddingVerticalCells * 2;
			_panel.sizeDelta = new Vector2(Mathf.Min((float)num4 * num * T.PixelScale, T.MaxWidthFraction * 1920f), Mathf.Min((float)num5 * num2 * T.PixelScale, T.MaxHeightFraction * 1080f));
			_state = State.FadingIn;
			_timer = 0f;
			ApplyTransition(0f);
		}

		private static string WrapText(string text, int maxCols)
		{
			List<string> list = new List<string>();
			string[] array = text.Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				if (text2.Length <= maxCols)
				{
					list.Add(text2);
					continue;
				}
				StringBuilder stringBuilder = new StringBuilder();
				string[] array2 = text2.Split(new char[1] { ' ' });
				foreach (string text3 in array2)
				{
					if (stringBuilder.Length == 0)
					{
						stringBuilder.Append(text3);
						continue;
					}
					if (stringBuilder.Length + 1 + text3.Length <= maxCols)
					{
						stringBuilder.Append(' ').Append(text3);
						continue;
					}
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
					stringBuilder.Append(text3);
				}
				if (stringBuilder.Length > 0)
				{
					list.Add(stringBuilder.ToString());
				}
			}
			return string.Join("\n", list);
		}
	}
	public class PopupTuning
	{
		public float DisplaySeconds = 3f;

		public float TransitionSeconds = 0.12f;

		public float MinScaleY = 0.01f;

		public int FontSize = 16;

		public float PixelScale = 0.9f;

		public float TextVerticalNudgeFraction = 0.48f;

		public float TopAnchorY = 0.95f;

		public float MaxWidthFraction = 1f;

		public float MaxHeightFraction = 0.08f;

		public int PaddingHorizontalCells = 2;

		public int PaddingVerticalCells = 1;

		public float CapitalLetterNudge = -1f;

		public float BackgroundR;

		public float BackgroundG = 0.01f;

		public float BackgroundB = 0.02f;

		public float BackgroundA = 0.75f;

		[JsonIgnore]
		public Color BackgroundColor => new Color(BackgroundR, BackgroundG, BackgroundB, BackgroundA);

		private static string FilePath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "PopupTuning.json");

		public static PopupTuning Current { get; private set; } = new PopupTuning();

		public static void Load()
		{
			try
			{
				if (File.Exists(FilePath))
				{
					PopupTuning popupTuning = JsonConvert.DeserializeObject<PopupTuning>(File.ReadAllText(FilePath));
					if (popupTuning != null)
					{
						Current = popupTuning;
						return;
					}
				}
				Current = new PopupTuning();
				Save();
			}
			catch (Exception arg)
			{
				Instance? log = Mod.Log;
				if (log != null)
				{
					log.Error($"PopupTuning.Load failed, using defaults: {arg}");
				}
				Current = new PopupTuning();
			}
		}

		private static void Save()
		{
			File.WriteAllText(FilePath, JsonConvert.SerializeObject((object)Current, (Formatting)1));
		}
	}
	public static class UnlockState
	{
		private static readonly HashSet<int> _unlockedLevelIds = new HashSet<int>();

		public static void Unlock(int levelId)
		{
			_unlockedLevelIds.Add(levelId);
		}

		public static bool IsUnlocked(int levelId)
		{
			return _unlockedLevelIds.Contains(levelId);
		}

		public static void Clear()
		{
			_unlockedLevelIds.Clear();
		}
	}
}

UserLibs/Archipelago.MultiClient.Net.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Archipelago.MultiClient.Net.Colors;
using Archipelago.MultiClient.Net.ConcurrentCollection;
using Archipelago.MultiClient.Net.Converters;
using Archipelago.MultiClient.Net.DataPackage;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Exceptions;
using Archipelago.MultiClient.Net.Extensions;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.MessageLog.Messages;
using Archipelago.MultiClient.Net.MessageLog.Parts;
using Archipelago.MultiClient.Net.Models;
using Archipelago.MultiClient.Net.Packets;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: ComVisible(false)]
[assembly: Guid("35a803ad-85ed-42e9-b1e3-c6b72096f0c1")]
[assembly: InternalsVisibleTo("Archipelago.MultiClient.Net.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")]
[assembly: AssemblyCompany("Jarno Westhof, Hussein Farran, Zach Parks")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyDescription("A client library for use with .NET based prog-langs for interfacing with Archipelago hosts.")]
[assembly: AssemblyFileVersion("6.7.1.0")]
[assembly: AssemblyInformationalVersion("6.7.1+0c57591db30f2497b0b4fef87164aa2bbe2e51b2")]
[assembly: AssemblyProduct("Archipelago.MultiClient.Net")]
[assembly: AssemblyTitle("Archipelago.MultiClient.Net")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ArchipelagoMW/Archipelago.MultiClient.Net")]
[assembly: AssemblyVersion("6.7.1.0")]
internal interface IConcurrentHashSet<T>
{
	bool TryAdd(T item);

	bool Contains(T item);

	void UnionWith(T[] otherSet);

	T[] ToArray();

	ReadOnlyCollection<T> AsToReadOnlyCollection();

	ReadOnlyCollection<T> AsToReadOnlyCollectionExcept(IConcurrentHashSet<T> otherSet);
}
public class AttemptingStringEnumConverter : StringEnumConverter
{
	public AttemptingStringEnumConverter()
	{
	}

	public AttemptingStringEnumConverter(Type namingStrategyType)
		: base(namingStrategyType)
	{
	}

	public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
	{
		try
		{
			return ((StringEnumConverter)this).ReadJson(reader, objectType, existingValue, serializer);
		}
		catch (JsonSerializationException)
		{
			return objectType.IsValueType ? Activator.CreateInstance(objectType) : null;
		}
	}
}
namespace Archipelago.MultiClient.Net
{
	[Serializable]
	public abstract class ArchipelagoPacketBase
	{
		[JsonIgnore]
		internal JObject jobject;

		[JsonProperty("cmd")]
		[JsonConverter(typeof(StringEnumConverter))]
		public abstract ArchipelagoPacketType PacketType { get; }

		public JObject ToJObject()
		{
			return jobject;
		}
	}
	public interface IArchipelagoSession : IArchipelagoSessionActions
	{
		IArchipelagoSocketHelper Socket { get; }

		IReceivedItemsHelper Items { get; }

		ILocationCheckHelper Locations { get; }

		IPlayerHelper Players { get; }

		IDataStorageHelper DataStorage { get; }

		IConnectionInfoProvider ConnectionInfo { get; }

		IRoomStateHelper RoomState { get; }

		IMessageLogHelper MessageLog { get; }

		IHintsHelper Hints { get; }

		Task<RoomInfoPacket> ConnectAsync();

		Task<LoginResult> LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true);

		LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true);
	}
	public class ArchipelagoSession : IArchipelagoSession, IArchipelagoSessionActions
	{
		private const int ArchipelagoConnectionTimeoutInSeconds = 4;

		private ConnectionInfoHelper connectionInfo;

		private TaskCompletionSource<LoginResult> loginResultTask = new TaskCompletionSource<LoginResult>();

		private TaskCompletionSource<RoomInfoPacket> roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>();

		public IArchipelagoSocketHelper Socket { get; }

		public IReceivedItemsHelper Items { get; }

		public ILocationCheckHelper Locations { get; }

		public IPlayerHelper Players { get; }

		public IDataStorageHelper DataStorage { get; }

		public IConnectionInfoProvider ConnectionInfo => connectionInfo;

		public IRoomStateHelper RoomState { get; }

		public IMessageLogHelper MessageLog { get; }

		public IHintsHelper Hints { get; }

		internal ArchipelagoSession(IArchipelagoSocketHelper socket, IReceivedItemsHelper items, ILocationCheckHelper locations, IPlayerHelper players, IRoomStateHelper roomState, ConnectionInfoHelper connectionInfoHelper, IDataStorageHelper dataStorage, IMessageLogHelper messageLog, IHintsHelper createHints)
		{
			Socket = socket;
			Items = items;
			Locations = locations;
			Players = players;
			RoomState = roomState;
			connectionInfo = connectionInfoHelper;
			DataStorage = dataStorage;
			MessageLog = messageLog;
			Hints = createHints;
			socket.PacketReceived += Socket_PacketReceived;
		}

		private void Socket_PacketReceived(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket) && !(packet is ConnectionRefusedPacket))
			{
				if (packet is RoomInfoPacket result)
				{
					roomInfoPacketTask.TrySetResult(result);
				}
			}
			else
			{
				loginResultTask.TrySetResult(LoginResult.FromPacket(packet));
			}
		}

		public Task<RoomInfoPacket> ConnectAsync()
		{
			roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>();
			Task.Factory.StartNew(delegate
			{
				try
				{
					Task task = Socket.ConnectAsync();
					task.Wait(TimeSpan.FromSeconds(4.0));
					if (!task.IsCompleted)
					{
						roomInfoPacketTask.TrySetCanceled();
					}
				}
				catch (AggregateException)
				{
					roomInfoPacketTask.TrySetCanceled();
				}
			});
			return roomInfoPacketTask.Task;
		}

		public Task<LoginResult> LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true)
		{
			loginResultTask = new TaskCompletionSource<LoginResult>();
			if (!roomInfoPacketTask.Task.IsCompleted)
			{
				loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first"));
				return loginResultTask.Task;
			}
			connectionInfo.SetConnectionParameters(game, tags, itemsHandlingFlags, uuid);
			try
			{
				Socket.SendPacket(BuildConnectPacket(name, password, version, requestSlotData));
			}
			catch (ArchipelagoSocketClosedException)
			{
				loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first"));
				return loginResultTask.Task;
			}
			SetResultAfterTimeout(loginResultTask, 4, new LoginFailure("Connection timed out."));
			return loginResultTask.Task;
		}

		private static void SetResultAfterTimeout<T>(TaskCompletionSource<T> task, int timeoutInSeconds, T result)
		{
			new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)).Token.Register(delegate
			{
				task.TrySetResult(result);
			});
		}

		public LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true)
		{
			Task<RoomInfoPacket> task = ConnectAsync();
			try
			{
				task.Wait(TimeSpan.FromSeconds(4.0));
			}
			catch (AggregateException ex)
			{
				if (ex.GetBaseException() is OperationCanceledException)
				{
					return new LoginFailure("Connection timed out.");
				}
				return new LoginFailure(ex.GetBaseException().Message);
			}
			if (!task.IsCompleted)
			{
				return new LoginFailure("Connection timed out.");
			}
			return LoginAsync(game, name, itemsHandlingFlags, version, tags, uuid, password, requestSlotData).Result;
		}

		private ConnectPacket BuildConnectPacket(string name, string password, Version version, bool requestSlotData)
		{
			return new ConnectPacket
			{
				Game = ConnectionInfo.Game,
				Name = name,
				Password = password,
				Tags = ConnectionInfo.Tags,
				Uuid = ConnectionInfo.Uuid,
				Version = ((version != null) ? new NetworkVersion(version) : new NetworkVersion(0, 6, 0)),
				ItemsHandling = ConnectionInfo.ItemsHandlingFlags,
				RequestSlotData = requestSlotData
			};
		}

		public void Say(string message)
		{
			Socket.SendPacket(new SayPacket
			{
				Text = message
			});
		}

		public void SetClientState(ArchipelagoClientState state)
		{
			Socket.SendPacket(new StatusUpdatePacket
			{
				Status = state
			});
		}

		public void SetGoalAchieved()
		{
			SetClientState(ArchipelagoClientState.ClientGoal);
		}
	}
	public interface IArchipelagoSessionActions
	{
		void Say(string message);

		void SetClientState(ArchipelagoClientState state);

		void SetGoalAchieved();
	}
	public static class ArchipelagoSessionFactory
	{
		public static ArchipelagoSession CreateSession(Uri uri)
		{
			ArchipelagoSocketHelper socket = new ArchipelagoSocketHelper(uri);
			DataPackageCache cache = new DataPackageCache(socket);
			ConnectionInfoHelper connectionInfoHelper = new ConnectionInfoHelper(socket);
			PlayerHelper playerHelper = new PlayerHelper(socket, connectionInfoHelper);
			ItemInfoResolver itemInfoResolver = new ItemInfoResolver(cache, connectionInfoHelper);
			LocationCheckHelper locationCheckHelper = new LocationCheckHelper(socket, itemInfoResolver, connectionInfoHelper, playerHelper);
			ReceivedItemsHelper items = new ReceivedItemsHelper(socket, locationCheckHelper, itemInfoResolver, connectionInfoHelper, playerHelper);
			RoomStateHelper roomStateHelper = new RoomStateHelper(socket, locationCheckHelper);
			DataStorageHelper dataStorageHelper = new DataStorageHelper(socket, connectionInfoHelper);
			MessageLogHelper messageLog = new MessageLogHelper(socket, itemInfoResolver, playerHelper, connectionInfoHelper);
			HintsHelper createHints = new HintsHelper(socket, playerHelper, locationCheckHelper, roomStateHelper, dataStorageHelper);
			return new ArchipelagoSession(socket, items, locationCheckHelper, playerHelper, roomStateHelper, connectionInfoHelper, dataStorageHelper, messageLog, createHints);
		}

		public static ArchipelagoSession CreateSession(string hostname, int port = 38281)
		{
			return CreateSession(ParseUri(hostname, port));
		}

		internal static Uri ParseUri(string hostname, int port)
		{
			string text = hostname;
			if (!text.StartsWith("ws://") && !text.StartsWith("wss://"))
			{
				text = "unspecified://" + text;
			}
			if (!text.Substring(text.IndexOf("://", StringComparison.Ordinal) + 3).Contains(":"))
			{
				text += $":{port}";
			}
			if (text.EndsWith(":"))
			{
				text += port;
			}
			return new Uri(text);
		}
	}
	public abstract class LoginResult
	{
		public abstract bool Successful { get; }

		public static LoginResult FromPacket(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket connectedPacket))
			{
				if (packet is ConnectionRefusedPacket connectionRefusedPacket)
				{
					return new LoginFailure(connectionRefusedPacket);
				}
				throw new ArgumentOutOfRangeException("packet", "packet is not a connection result packet");
			}
			return new LoginSuccessful(connectedPacket);
		}
	}
	public class LoginSuccessful : LoginResult
	{
		public override bool Successful => true;

		public int Team { get; }

		public int Slot { get; }

		public Dictionary<string, object> SlotData { get; }

		public LoginSuccessful(ConnectedPacket connectedPacket)
		{
			Team = connectedPacket.Team;
			Slot = connectedPacket.Slot;
			SlotData = connectedPacket.SlotData;
		}
	}
	public class LoginFailure : LoginResult
	{
		public override bool Successful => false;

		public ConnectionRefusedError[] ErrorCodes { get; }

		public string[] Errors { get; }

		public LoginFailure(ConnectionRefusedPacket connectionRefusedPacket)
		{
			if (connectionRefusedPacket.Errors != null)
			{
				ErrorCodes = connectionRefusedPacket.Errors.ToArray();
				Errors = ErrorCodes.Select(GetErrorMessage).ToArray();
			}
			else
			{
				ErrorCodes = new ConnectionRefusedError[0];
				Errors = new string[0];
			}
		}

		public LoginFailure(string message)
		{
			ErrorCodes = new ConnectionRefusedError[0];
			Errors = new string[1] { message };
		}

		private static string GetErrorMessage(ConnectionRefusedError errorCode)
		{
			return errorCode switch
			{
				ConnectionRefusedError.InvalidSlot => "The slot name did not match any slot on the server.", 
				ConnectionRefusedError.InvalidGame => "The slot is set to a different game on the server.", 
				ConnectionRefusedError.SlotAlreadyTaken => "The slot already has a connection with a different uuid established.", 
				ConnectionRefusedError.IncompatibleVersion => "The client and server version mismatch.", 
				ConnectionRefusedError.InvalidPassword => "The password is invalid.", 
				ConnectionRefusedError.InvalidItemsHandling => "The item handling flags provided are invalid.", 
				_ => $"Unknown error: {errorCode}.", 
			};
		}
	}
	internal class TwoWayLookup<TA, TB> : IEnumerable<KeyValuePair<TB, TA>>, IEnumerable
	{
		private readonly Dictionary<TA, TB> aToB = new Dictionary<TA, TB>();

		private readonly Dictionary<TB, TA> bToA = new Dictionary<TB, TA>();

		public TA this[TB b] => bToA[b];

		public TB this[TA a] => aToB[a];

		public void Add(TA a, TB b)
		{
			aToB[a] = b;
			bToA[b] = a;
		}

		public void Add(TB b, TA a)
		{
			Add(a, b);
		}

		public bool TryGetValue(TA a, out TB b)
		{
			return aToB.TryGetValue(a, out b);
		}

		public bool TryGetValue(TB b, out TA a)
		{
			return bToA.TryGetValue(b, out a);
		}

		public IEnumerator<KeyValuePair<TB, TA>> GetEnumerator()
		{
			return bToA.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
	}
}
namespace Archipelago.MultiClient.Net.Packets
{
	public class BouncedPacket : BouncePacket
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounced;
	}
	public class BouncePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounce;

		[JsonProperty("games")]
		public List<string> Games { get; set; } = new List<string>();


		[JsonProperty("slots")]
		public List<int> Slots { get; set; } = new List<int>();


		[JsonProperty("tags")]
		public List<string> Tags { get; set; } = new List<string>();


		[JsonProperty("data")]
		public Dictionary<string, JToken> Data { get; set; }
	}
	public class ConnectedPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connected;

		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("players")]
		public NetworkPlayer[] Players { get; set; }

		[JsonProperty("missing_locations")]
		public long[] MissingChecks { get; set; }

		[JsonProperty("checked_locations")]
		public long[] LocationsChecked { get; set; }

		[JsonProperty("slot_data")]
		public Dictionary<string, object> SlotData { get; set; }

		[JsonProperty("slot_info")]
		public Dictionary<int, NetworkSlot> SlotInfo { get; set; }

		[JsonProperty("hint_points")]
		public int? HintPoints { get; set; }
	}
	public class ConnectionRefusedPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectionRefused;

		[JsonProperty("errors", ItemConverterType = typeof(AttemptingStringEnumConverter))]
		public ConnectionRefusedError[] Errors { get; set; }
	}
	public class ConnectPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connect;

		[JsonProperty("password")]
		public string Password { get; set; }

		[JsonProperty("game")]
		public string Game { get; set; }

		[JsonProperty("name")]
		public string Name { get; set; }

		[JsonProperty("uuid")]
		public string Uuid { get; set; }

		[JsonProperty("version")]
		public NetworkVersion Version { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("items_handling")]
		public ItemsHandlingFlags ItemsHandling { get; set; }

		[JsonProperty("slot_data")]
		public bool RequestSlotData { get; set; }
	}
	public class ConnectUpdatePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectUpdate;

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("items_handling")]
		public ItemsHandlingFlags? ItemsHandling { get; set; }
	}
	public class CreateHintsPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.CreateHints;

		[JsonProperty("locations")]
		public long[] Locations { get; set; }

		[JsonProperty("player")]
		public int Player { get; set; }

		[JsonProperty("status")]
		public HintStatus Status { get; set; }
	}
	public class DataPackagePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.DataPackage;

		[JsonProperty("data")]
		public Archipelago.MultiClient.Net.Models.DataPackage DataPackage { get; set; }
	}
	public class GetDataPackagePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.GetDataPackage;

		[JsonProperty("games")]
		public string[] Games { get; set; }
	}
	public class GetPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Get;

		[JsonProperty("keys")]
		public string[] Keys { get; set; }
	}
	public class InvalidPacketPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.InvalidPacket;

		[JsonProperty("type")]
		public InvalidPacketErrorType ErrorType { get; set; }

		[JsonProperty("text")]
		public string ErrorText { get; set; }

		[JsonProperty("original_cmd")]
		public ArchipelagoPacketType OriginalCmd { get; set; }
	}
	public class LocationChecksPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationChecks;

		[JsonProperty("locations")]
		public long[] Locations { get; set; }
	}
	public class LocationInfoPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationInfo;

		[JsonProperty("locations")]
		public NetworkItem[] Locations { get; set; }
	}
	public class LocationScoutsPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationScouts;

		[JsonProperty("locations")]
		public long[] Locations { get; set; }

		[JsonProperty("create_as_hint")]
		public int CreateAsHint { get; set; }
	}
	public class PrintJsonPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.PrintJSON;

		[JsonProperty("data")]
		public JsonMessagePart[] Data { get; set; }

		[JsonProperty("type")]
		[JsonConverter(typeof(AttemptingStringEnumConverter))]
		public JsonMessageType? MessageType { get; set; }
	}
	public class ItemPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("receiving")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("item")]
		public NetworkItem Item { get; set; }
	}
	public class ItemCheatPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("receiving")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("item")]
		public NetworkItem Item { get; set; }

		[JsonProperty("team")]
		public int Team { get; set; }
	}
	public class HintPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("receiving")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("item")]
		public NetworkItem Item { get; set; }

		[JsonProperty("found")]
		public bool? Found { get; set; }
	}
	public class JoinPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }
	}
	public class LeavePrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class ChatPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("message")]
		public string Message { get; set; }
	}
	public class ServerChatPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("message")]
		public string Message { get; set; }
	}
	public class TutorialPrintJsonPacket : PrintJsonPacket
	{
	}
	public class TagsChangedPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }
	}
	public class CommandResultPrintJsonPacket : PrintJsonPacket
	{
	}
	public class AdminCommandResultPrintJsonPacket : PrintJsonPacket
	{
	}
	public class GoalPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class ReleasePrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class CollectPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class CountdownPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("countdown")]
		public int RemainingSeconds { get; set; }
	}
	public class ReceivedItemsPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ReceivedItems;

		[JsonProperty("index")]
		public int Index { get; set; }

		[JsonProperty("items")]
		public NetworkItem[] Items { get; set; }
	}
	public class RetrievedPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Retrieved;

		[JsonProperty("keys")]
		public Dictionary<string, JToken> Data { get; set; }
	}
	public class RoomInfoPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomInfo;

		[JsonProperty("version")]
		public NetworkVersion Version { get; set; }

		[JsonProperty("generator_version")]
		public NetworkVersion GeneratorVersion { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("password")]
		public bool Password { get; set; }

		[JsonProperty("permissions")]
		public Dictionary<string, Permissions> Permissions { get; set; }

		[JsonProperty("hint_cost")]
		public int HintCostPercentage { get; set; }

		[JsonProperty("location_check_points")]
		public int LocationCheckPoints { get; set; }

		[JsonProperty("players")]
		public NetworkPlayer[] Players { get; set; }

		[JsonProperty("games")]
		public string[] Games { get; set; }

		[JsonProperty("datapackage_checksums")]
		public Dictionary<string, string> DataPackageChecksums { get; set; }

		[JsonProperty("seed_name")]
		public string SeedName { get; set; }

		[JsonProperty("time")]
		public double Timestamp { get; set; }
	}
	public class RoomUpdatePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomUpdate;

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("password")]
		public bool? Password { get; set; }

		[JsonProperty("permissions")]
		public Dictionary<string, Permissions> Permissions { get; set; } = new Dictionary<string, Permissions>();


		[JsonProperty("hint_cost")]
		public int? HintCostPercentage { get; set; }

		[JsonProperty("location_check_points")]
		public int? LocationCheckPoints { get; set; }

		[JsonProperty("players")]
		public NetworkPlayer[] Players { get; set; }

		[JsonProperty("hint_points")]
		public int? HintPoints { get; set; }

		[JsonProperty("checked_locations")]
		public long[] CheckedLocations { get; set; }
	}
	public class SayPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Say;

		[JsonProperty("text")]
		public string Text { get; set; }
	}
	public class SetNotifyPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetNotify;

		[JsonProperty("keys")]
		public string[] Keys { get; set; }
	}
	public class SetPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Set;

		[JsonProperty("key")]
		public string Key { get; set; }

		[JsonProperty("default")]
		public JToken DefaultValue { get; set; }

		[JsonProperty("operations")]
		public OperationSpecification[] Operations { get; set; }

		[JsonProperty("want_reply")]
		public bool WantReply { get; set; }

		[JsonExtensionData]
		public Dictionary<string, JToken> AdditionalArguments { get; set; }

		[OnDeserialized]
		internal void OnDeserializedMethod(StreamingContext context)
		{
			AdditionalArguments?.Remove("cmd");
		}
	}
	public class SetReplyPacket : SetPacket
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetReply;

		[JsonProperty("value")]
		public JToken Value { get; set; }

		[JsonProperty("original_value")]
		public JToken OriginalValue { get; set; }
	}
	public class StatusUpdatePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.StatusUpdate;

		[JsonProperty("status")]
		public ArchipelagoClientState Status { get; set; }
	}
	public class SyncPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Sync;
	}
	internal class UnknownPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Unknown;
	}
	public class UpdateHintPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.UpdateHint;

		[JsonProperty("player")]
		public int Player { get; set; }

		[JsonProperty("location")]
		public long Location { get; set; }

		[JsonProperty("status")]
		public HintStatus Status { get; set; }
	}
}
namespace Archipelago.MultiClient.Net.Models
{
	public struct Color : IEquatable<Color>
	{
		public static Color Red = new Color(byte.MaxValue, 0, 0);

		public static Color Green = new Color(0, 128, 0);

		public static Color Yellow = new Color(byte.MaxValue, byte.MaxValue, 0);

		public static Color Blue = new Color(0, 0, byte.MaxValue);

		public static Color Magenta = new Color(byte.MaxValue, 0, byte.MaxValue);

		public static Color Cyan = new Color(0, byte.MaxValue, byte.MaxValue);

		public static Color Black = new Color(0, 0, 0);

		public static Color White = new Color(byte.MaxValue, byte.MaxValue, byte.MaxValue);

		public static Color SlateBlue = new Color(106, 90, 205);

		public static Color Salmon = new Color(250, 128, 114);

		public static Color Plum = new Color(221, 160, 221);

		public byte R { get; set; }

		public byte G { get; set; }

		public byte B { get; set; }

		public Color(byte r, byte g, byte b)
		{
			R = r;
			G = g;
			B = b;
		}

		public override bool Equals(object obj)
		{
			if (obj is Color color && R == color.R && G == color.G)
			{
				return B == color.B;
			}
			return false;
		}

		public bool Equals(Color other)
		{
			if (R == other.R && G == other.G)
			{
				return B == other.B;
			}
			return false;
		}

		public override int GetHashCode()
		{
			return ((-1520100960 * -1521134295 + R.GetHashCode()) * -1521134295 + G.GetHashCode()) * -1521134295 + B.GetHashCode();
		}

		public static bool operator ==(Color left, Color right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(Color left, Color right)
		{
			return !(left == right);
		}
	}
	public class DataPackage
	{
		[JsonProperty("games")]
		public Dictionary<string, GameData> Games { get; set; } = new Dictionary<string, GameData>();

	}
	public class DataStorageElement
	{
		internal DataStorageElementContext Context;

		internal List<OperationSpecification> Operations = new List<OperationSpecification>(0);

		internal DataStorageHelper.DataStorageUpdatedHandler Callbacks;

		internal Dictionary<string, JToken> AdditionalArguments = new Dictionary<string, JToken>(0);

		private JToken cachedValue;

		public event DataStorageHelper.DataStorageUpdatedHandler OnValueChanged
		{
			add
			{
				Context.AddHandler(Context.Key, value);
			}
			remove
			{
				Context.RemoveHandler(Context.Key, value);
			}
		}

		internal DataStorageElement(DataStorageElementContext context)
		{
			Context = context;
		}

		internal DataStorageElement(OperationType operationType, JToken value)
		{
			Operations = new List<OperationSpecification>(1)
			{
				new OperationSpecification
				{
					OperationType = operationType,
					Value = value
				}
			};
		}

		internal DataStorageElement(DataStorageElement source, OperationType operationType, JToken value)
			: this(source.Context)
		{
			Operations = source.Operations.ToList();
			Callbacks = source.Callbacks;
			AdditionalArguments = source.AdditionalArguments;
			Operations.Add(new OperationSpecification
			{
				OperationType = operationType,
				Value = value
			});
		}

		internal DataStorageElement(DataStorageElement source, Callback callback)
			: this(source.Context)
		{
			Operations = source.Operations.ToList();
			Callbacks = source.Callbacks;
			AdditionalArguments = source.AdditionalArguments;
			Callbacks = (DataStorageHelper.DataStorageUpdatedHandler)Delegate.Combine(Callbacks, callback.Method);
		}

		internal DataStorageElement(DataStorageElement source, AdditionalArgument additionalArgument)
			: this(source.Context)
		{
			Operations = source.Operations.ToList();
			Callbacks = source.Callbacks;
			AdditionalArguments = source.AdditionalArguments;
			AdditionalArguments[additionalArgument.Key] = additionalArgument.Value;
		}

		public static DataStorageElement operator ++(DataStorageElement a)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(1));
		}

		public static DataStorageElement operator --(DataStorageElement a)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(-1));
		}

		public static DataStorageElement operator +(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, string b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, JToken b)
		{
			return new DataStorageElement(a, OperationType.Add, b);
		}

		public static DataStorageElement operator +(DataStorageElement a, IEnumerable b)
		{
			return new DataStorageElement(a, OperationType.Add, (JToken)(object)JArray.FromObject((object)b));
		}

		public static DataStorageElement operator +(DataStorageElement a, OperationSpecification s)
		{
			return new DataStorageElement(a, s.OperationType, s.Value);
		}

		public static DataStorageElement operator +(DataStorageElement a, Callback c)
		{
			return new DataStorageElement(a, c);
		}

		public static DataStorageElement operator +(DataStorageElement a, AdditionalArgument arg)
		{
			return new DataStorageElement(a, arg);
		}

		public static DataStorageElement operator *(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator -(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0f - b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0.0 - b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / (double)b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / b)));
		}

		public static implicit operator DataStorageElement(bool b)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(b));
		}

		public static implicit operator DataStorageElement(int i)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(i));
		}

		public static implicit operator DataStorageElement(long l)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(l));
		}

		public static implicit operator DataStorageElement(decimal m)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(m));
		}

		public static implicit operator DataStorageElement(double d)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(d));
		}

		public static implicit operator DataStorageElement(float f)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(f));
		}

		public static implicit operator DataStorageElement(string s)
		{
			if (s != null)
			{
				return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(s));
			}
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull());
		}

		public static implicit operator DataStorageElement(JToken o)
		{
			return new DataStorageElement(OperationType.Replace, o);
		}

		public static implicit operator DataStorageElement(Array a)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)a));
		}

		public static implicit operator DataStorageElement(List<bool> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<int> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<long> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<decimal> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<double> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<float> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<string> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<object> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator bool(DataStorageElement e)
		{
			return RetrieveAndReturnBoolValue<bool>(e);
		}

		public static implicit operator bool?(DataStorageElement e)
		{
			return RetrieveAndReturnBoolValue<bool?>(e);
		}

		public static implicit operator int(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<int>(e);
		}

		public static implicit operator int?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<int?>(e);
		}

		public static implicit operator long(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<long>(e);
		}

		public static implicit operator long?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<long?>(e);
		}

		public static implicit operator float(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<float>(e);
		}

		public static implicit operator float?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<float?>(e);
		}

		public static implicit operator double(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<double>(e);
		}

		public static implicit operator double?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<double?>(e);
		}

		public static implicit operator decimal(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<decimal>(e);
		}

		public static implicit operator decimal?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<decimal?>(e);
		}

		public static implicit operator string(DataStorageElement e)
		{
			return RetrieveAndReturnStringValue(e);
		}

		public static implicit operator bool[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<bool[]>(e);
		}

		public static implicit operator int[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<int[]>(e);
		}

		public static implicit operator long[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<long[]>(e);
		}

		public static implicit operator decimal[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<decimal[]>(e);
		}

		public static implicit operator double[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<double[]>(e);
		}

		public static implicit operator float[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<float[]>(e);
		}

		public static implicit operator string[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<string[]>(e);
		}

		public static implicit operator object[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<object[]>(e);
		}

		public static implicit operator List<bool>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<bool>>(e);
		}

		public static implicit operator List<int>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<int>>(e);
		}

		public static implicit operator List<long>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<long>>(e);
		}

		public static implicit operator List<decimal>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<decimal>>(e);
		}

		public static implicit operator List<double>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<double>>(e);
		}

		public static implicit operator List<float>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<float>>(e);
		}

		public static implicit operator List<string>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<string>>(e);
		}

		public static implicit operator List<object>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<object>>(e);
		}

		public static implicit operator Array(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<Array>(e);
		}

		public static implicit operator JArray(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<JArray>(e);
		}

		public static implicit operator JToken(DataStorageElement e)
		{
			return e.Context.GetData(e.Context.Key);
		}

		public static DataStorageElement operator +(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator *(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator %(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator ^(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator -(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.Parse((-b).ToString()));
		}

		public static DataStorageElement operator /(DataStorageElement a, BigInteger b)
		{
			throw new InvalidOperationException("DataStorage[Key] / BigInterger is not supported, due to loss of precision when using integer division");
		}

		public static implicit operator DataStorageElement(BigInteger bi)
		{
			return new DataStorageElement(OperationType.Replace, JToken.Parse(bi.ToString()));
		}

		public static implicit operator BigInteger(DataStorageElement e)
		{
			return RetrieveAndReturnBigIntegerValue<BigInteger>(e);
		}

		public static implicit operator BigInteger?(DataStorageElement e)
		{
			return RetrieveAndReturnBigIntegerValue<BigInteger?>(e);
		}

		private static T RetrieveAndReturnBigIntegerValue<T>(DataStorageElement e)
		{
			if (e.cachedValue != null)
			{
				if (!BigInteger.TryParse(((object)e.cachedValue).ToString(), out var result))
				{
					return default(T);
				}
				return (T)Convert.ChangeType(result, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
			}
			BigInteger result2;
			BigInteger? bigInteger = (BigInteger.TryParse(((object)e.Context.GetData(e.Context.Key)).ToString(), out result2) ? new BigInteger?(result2) : null);
			if (!bigInteger.HasValue && !IsNullable<T>())
			{
				bigInteger = Activator.CreateInstance<BigInteger>();
			}
			foreach (OperationSpecification operation in e.Operations)
			{
				if (operation.OperationType == OperationType.Floor || operation.OperationType == OperationType.Ceil)
				{
					continue;
				}
				if (!BigInteger.TryParse(((object)operation.Value).ToString(), NumberStyles.AllowLeadingSign, null, out var result3))
				{
					throw new InvalidOperationException($"DataStorage[Key] cannot be converted to BigInterger as its value its not an integer number, value: {operation.Value}");
				}
				switch (operation.OperationType)
				{
				case OperationType.Replace:
					bigInteger = result3;
					break;
				case OperationType.Add:
					bigInteger += result3;
					break;
				case OperationType.Mul:
					bigInteger *= result3;
					break;
				case OperationType.Mod:
					bigInteger %= result3;
					break;
				case OperationType.Pow:
					bigInteger = BigInteger.Pow(bigInteger.Value, (int)operation.Value);
					break;
				case OperationType.Max:
				{
					BigInteger value = result3;
					BigInteger? bigInteger2 = bigInteger;
					if (value > bigInteger2)
					{
						bigInteger = result3;
					}
					break;
				}
				case OperationType.Min:
				{
					BigInteger value = result3;
					BigInteger? bigInteger2 = bigInteger;
					if (value < bigInteger2)
					{
						bigInteger = result3;
					}
					break;
				}
				case OperationType.Xor:
					bigInteger ^= result3;
					break;
				case OperationType.Or:
					bigInteger |= result3;
					break;
				case OperationType.And:
					bigInteger &= result3;
					break;
				case OperationType.LeftShift:
					bigInteger <<= (int)operation.Value;
					break;
				case OperationType.RightShift:
					bigInteger >>= (int)operation.Value;
					break;
				}
			}
			e.cachedValue = JToken.Parse(bigInteger.ToString());
			if (!bigInteger.HasValue)
			{
				return default(T);
			}
			return (T)Convert.ChangeType(bigInteger.Value, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
		}

		public void Initialize(JToken value)
		{
			Context.Initialize(Context.Key, value);
		}

		public void Initialize(IEnumerable value)
		{
			Context.Initialize(Context.Key, (JToken)(object)JArray.FromObject((object)value));
		}

		public Task<T> GetAsync<T>()
		{
			return GetAsync().ContinueWith((Task<JToken> r) => r.Result.ToObject<T>());
		}

		public Task<JToken> GetAsync()
		{
			return Context.GetAsync(Context.Key);
		}

		private static T RetrieveAndReturnArrayValue<T>(DataStorageElement e)
		{
			//IL_000e: 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)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Invalid comparison between Unknown and I4
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Invalid comparison between Unknown and I4
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (e.cachedValue != null)
			{
				return ((JToken)(JArray)e.cachedValue).ToObject<T>();
			}
			JArray val = (JArray)(((object)e.Context.GetData(e.Context.Key).ToObject<JArray>()) ?? ((object)new JArray()));
			foreach (OperationSpecification operation in e.Operations)
			{
				switch (operation.OperationType)
				{
				case OperationType.Add:
					if ((int)operation.Value.Type != 2)
					{
						throw new InvalidOperationException($"Cannot perform operation {OperationType.Add} on Array value, with a non Array value: {operation.Value}");
					}
					((JContainer)val).Merge((object)operation.Value);
					break;
				case OperationType.Replace:
					if ((int)operation.Value.Type != 2)
					{
						throw new InvalidOperationException($"Cannot replace Array value, with a non Array value: {operation.Value}");
					}
					val = (JArray)(((object)operation.Value.ToObject<JArray>()) ?? ((object)new JArray()));
					break;
				default:
					throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on Array value");
				}
			}
			e.cachedValue = (JToken)(object)val;
			return ((JToken)val).ToObject<T>();
		}

		private static string RetrieveAndReturnStringValue(DataStorageElement e)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Invalid comparison between Unknown and I4
			if (e.cachedValue != null)
			{
				return (string)e.cachedValue;
			}
			JToken val = e.Context.GetData(e.Context.Key);
			string text = (((int)val.Type == 10) ? null : ((object)val).ToString());
			foreach (OperationSpecification operation in e.Operations)
			{
				switch (operation.OperationType)
				{
				case OperationType.Add:
					text += (string)operation.Value;
					break;
				case OperationType.Mul:
					if ((int)operation.Value.Type != 6)
					{
						throw new InvalidOperationException($"Cannot perform operation {OperationType.Mul} on string value, with a non interger value: {operation.Value}");
					}
					text = string.Concat(Enumerable.Repeat(text, (int)operation.Value));
					break;
				case OperationType.Replace:
					text = (string)operation.Value;
					break;
				default:
					throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on string value");
				}
			}
			if (text == null)
			{
				e.cachedValue = (JToken)(object)JValue.CreateNull();
			}
			else
			{
				e.cachedValue = JToken.op_Implicit(text);
			}
			return (string)e.cachedValue;
		}

		private static T RetrieveAndReturnBoolValue<T>(DataStorageElement e)
		{
			if (e.cachedValue != null)
			{
				return e.cachedValue.ToObject<T>();
			}
			bool? flag = e.Context.GetData(e.Context.Key).ToObject<bool?>() ?? ((bool?)Activator.CreateInstance(typeof(T)));
			foreach (OperationSpecification operation in e.Operations)
			{
				if (operation.OperationType == OperationType.Replace)
				{
					flag = (bool?)operation.Value;
					continue;
				}
				throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on boolean value");
			}
			e.cachedValue = JToken.op_Implicit(flag);
			if (!flag.HasValue)
			{
				return default(T);
			}
			return (T)Convert.ChangeType(flag.Value, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
		}

		private static T RetrieveAndReturnDecimalValue<T>(DataStorageElement e)
		{
			if (e.cachedValue != null)
			{
				return e.cachedValue.ToObject<T>();
			}
			decimal? num = e.Context.GetData(e.Context.Key).ToObject<decimal?>();
			if (!num.HasValue && !IsNullable<T>())
			{
				num = Activator.CreateInstance<decimal>();
			}
			foreach (OperationSpecification operation in e.Operations)
			{
				switch (operation.OperationType)
				{
				case OperationType.Replace:
					num = (decimal)operation.Value;
					break;
				case OperationType.Add:
					num += (decimal?)(decimal)operation.Value;
					break;
				case OperationType.Mul:
					num *= (decimal?)(decimal)operation.Value;
					break;
				case OperationType.Mod:
					num %= (decimal?)(decimal)operation.Value;
					break;
				case OperationType.Pow:
					num = (decimal)Math.Pow((double)num.Value, (double)operation.Value);
					break;
				case OperationType.Max:
					num = Math.Max(num.Value, (decimal)operation.Value);
					break;
				case OperationType.Min:
					num = Math.Min(num.Value, (decimal)operation.Value);
					break;
				case OperationType.Xor:
					num = (long)num.Value ^ (long)operation.Value;
					break;
				case OperationType.Or:
					num = (long)num.Value | (long)operation.Value;
					break;
				case OperationType.And:
					num = (long)num.Value & (long)operation.Value;
					break;
				case OperationType.LeftShift:
					num = (long)num.Value << (int)operation.Value;
					break;
				case OperationType.RightShift:
					num = (long)num.Value >> (int)operation.Value;
					break;
				case OperationType.Floor:
					num = Math.Floor(num.Value);
					break;
				case OperationType.Ceil:
					num = Math.Ceiling(num.Value);
					break;
				}
			}
			e.cachedValue = JToken.op_Implicit(num);
			if (!num.HasValue)
			{
				return default(T);
			}
			return (T)Convert.ChangeType(num.Value, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
		}

		private static bool IsNullable<T>()
		{
			if (typeof(T).IsGenericType)
			{
				return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition();
			}
			return false;
		}

		public T To<T>()
		{
			if (Operations.Count != 0)
			{
				throw new InvalidOperationException("DataStorageElement.To<T>() cannot be used together with other operations on the DataStorageElement");
			}
			return Context.GetData(Context.Key).ToObject<T>();
		}

		public override string ToString()
		{
			return (Context?.ToString() ?? "(null)") + ", (" + ListOperations() + ")";
		}

		private string ListOperations()
		{
			if (Operations != null)
			{
				return string.Join(", ", Operations.Select((OperationSpecification o) => o.ToString()).ToArray());
			}
			return "none";
		}
	}
	internal class DataStorageElementContext
	{
		internal string Key { get; set; }

		internal Action<string, DataStorageHelper.DataStorageUpdatedHandler> AddHandler { get; set; }

		internal Action<string, DataStorageHelper.DataStorageUpdatedHandler> RemoveHandler { get; set; }

		internal Func<string, JToken> GetData { get; set; }

		internal Action<string, JToken> Initialize { get; set; }

		internal Func<string, Task<JToken>> GetAsync { get; set; }

		public override string ToString()
		{
			return "Key: " + Key;
		}
	}
	public class GameData
	{
		[JsonProperty("location_name_to_id")]
		public Dictionary<string, long> LocationLookup { get; set; } = new Dictionary<string, long>();


		[JsonProperty("item_name_to_id")]
		public Dictionary<string, long> ItemLookup { get; set; } = new Dictionary<string, long>();


		[Obsolete("use Checksum instead")]
		[JsonProperty("version")]
		public int Version { get; set; }

		[JsonProperty("checksum")]
		public string Checksum { get; set; }
	}
	public class Hint
	{
		[JsonProperty("receiving_player")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("finding_player")]
		public int FindingPlayer { get; set; }

		[JsonProperty("item")]
		public long ItemId { get; set; }

		[JsonProperty("location")]
		public long LocationId { get; set; }

		[JsonProperty("item_flags")]
		public ItemFlags ItemFlags { get; set; }

		[JsonProperty("found")]
		public bool Found { get; set; }

		[JsonProperty("entrance")]
		public string Entrance { get; set; }

		[JsonProperty("status")]
		public HintStatus Status { get; set; }
	}
	public class ItemInfo
	{
		private readonly IItemInfoResolver itemInfoResolver;

		public long ItemId { get; }

		public long LocationId { get; }

		public PlayerInfo Player { get; }

		public ItemFlags Flags { get; }

		public string ItemName => itemInfoResolver.GetItemName(ItemId, ItemGame);

		public string ItemDisplayName => ItemName ?? $"Item: {ItemId}";

		public string LocationName => itemInfoResolver.GetLocationName(LocationId, LocationGame);

		public string LocationDisplayName => LocationName ?? $"Location: {LocationId}";

		public string ItemGame { get; }

		public string LocationGame { get; }

		public ItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, PlayerInfo player)
		{
			this.itemInfoResolver = itemInfoResolver;
			ItemGame = receiverGame;
			LocationGame = senderGame;
			ItemId = item.Item;
			LocationId = item.Location;
			Flags = item.Flags;
			Player = player;
		}

		public SerializableItemInfo ToSerializable()
		{
			return new SerializableItemInfo
			{
				IsScout = (GetType() == typeof(ScoutedItemInfo)),
				ItemId = ItemId,
				LocationId = LocationId,
				PlayerSlot = Player,
				Player = Player,
				Flags = Flags,
				ItemGame = ItemGame,
				ItemName = ItemName,
				LocationGame = LocationGame,
				LocationName = LocationName
			};
		}
	}
	public class ScoutedItemInfo : ItemInfo
	{
		public new PlayerInfo Player => base.Player;

		public bool IsReceiverRelatedToActivePlayer { get; }

		public ScoutedItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, IPlayerHelper players, PlayerInfo player)
			: base(item, receiverGame, senderGame, itemInfoResolver, player)
		{
			IsReceiverRelatedToActivePlayer = (players.ActivePlayer ?? new PlayerInfo()).IsRelatedTo(player);
		}
	}
	public class JsonMessagePart
	{
		[JsonProperty("type")]
		[JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public JsonMessagePartType? Type { get; set; }

		[JsonProperty("color")]
		[JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public JsonMessagePartColor? Color { get; set; }

		[JsonProperty("text")]
		public string Text { get; set; }

		[JsonProperty("player")]
		public int? Player { get; set; }

		[JsonProperty("flags")]
		public ItemFlags? Flags { get; set; }

		[JsonProperty("hint_status")]
		public HintStatus? HintStatus { get; set; }
	}
	public struct NetworkItem
	{
		[JsonProperty("item")]
		public long Item { get; set; }

		[JsonProperty("location")]
		public long Location { get; set; }

		[JsonProperty("player")]
		public int Player { get; set; }

		[JsonProperty("flags")]
		public ItemFlags Flags { get; set; }
	}
	public struct NetworkPlayer
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("alias")]
		public string Alias { get; set; }

		[JsonProperty("name")]
		public string Name { get; set; }
	}
	public struct NetworkSlot
	{
		[JsonProperty("name")]
		public string Name { get; set; }

		[JsonProperty("game")]
		public string Game { get; set; }

		[JsonProperty("type")]
		public SlotType Type { get; set; }

		[JsonProperty("group_members")]
		public int[] GroupMembers { get; set; }
	}
	public class NetworkVersion
	{
		[JsonProperty("major")]
		public int Major { get; set; }

		[JsonProperty("minor")]
		public int Minor { get; set; }

		[JsonProperty("build")]
		public int Build { get; set; }

		[JsonProperty("class")]
		public string Class => "Version";

		public NetworkVersion()
		{
		}

		public NetworkVersion(int major, int minor, int build)
		{
			Major = major;
			Minor = minor;
			Build = build;
		}

		public NetworkVersion(Version version)
		{
			Major = version.Major;
			Minor = version.Minor;
			Build = version.Build;
		}

		public Version ToVersion()
		{
			return new Version(Major, Minor, Build);
		}
	}
	public class OperationSpecification
	{
		[JsonProperty("operation")]
		[JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public OperationType OperationType;

		[JsonProperty("value")]
		public JToken Value { get; set; }

		public override string ToString()
		{
			return $"{OperationType}: {Value}";
		}
	}
	public static class Operation
	{
		public static OperationSpecification Min(int i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(float i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(double i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(decimal i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(JToken i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = i
			};
		}

		public static OperationSpecification Min(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification Max(int i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(float i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(double i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(decimal i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(JToken i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = i
			};
		}

		public static OperationSpecification Max(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification Remove(JToken value)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Remove,
				Value = value
			};
		}

		public static OperationSpecification Pop(int value)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Pop,
				Value = JToken.op_Implicit(value)
			};
		}

		public static OperationSpecification Pop(JToken value)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Pop,
				Value = value
			};
		}

		public static OperationSpecification Update(IDictionary dictionary)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Update,
				Value = (JToken)(object)JObject.FromObject((object)dictionary)
			};
		}

		public static OperationSpecification Floor()
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Floor,
				Value = null
			};
		}

		public static OperationSpecification Ceiling()
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Ceil,
				Value = null
			};
		}
	}
	public static class Bitwise
	{
		public static OperationSpecification Xor(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Xor,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Xor(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Xor,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification Or(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Or,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Or(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Or,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification And(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.And,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification And(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.And,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification LeftShift(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.LeftShift,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification RightShift(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.RightShift,
				Value = JToken.op_Implicit(i)
			};
		}
	}
	public class Callback
	{
		internal DataStorageHelper.DataStorageUpdatedHandler Method { get; set; }

		private Callback()
		{
		}

		public static Callback Add(DataStorageHelper.DataStorageUpdatedHandler callback)
		{
			return new Callback
			{
				Method = callback
			};
		}
	}
	public class AdditionalArgument
	{
		internal string Key { get; set; }

		internal JToken Value { get; set; }

		private AdditionalArgument()
		{
		}

		public static AdditionalArgument Add(string name, JToken value)
		{
			return new AdditionalArgument
			{
				Key = name,
				Value = value
			};
		}
	}
	public class MinimalSerializableItemInfo
	{
		public long ItemId { get; set; }

		public long LocationId { get; set; }

		public int PlayerSlot { get; set; }

		public ItemFlags Flags { get; set; }

		public string ItemGame { get; set; }

		public string LocationGame { get; set; }
	}
	public class SerializableItemInfo : MinimalSerializableItemInfo
	{
		public bool IsScout { get; set; }

		public PlayerInfo Player { get; set; }

		public string ItemName { get; set; }

		public string LocationName { get; set; }

		[JsonIgnore]
		public string ItemDisplayName => ItemName ?? $"Item: {base.ItemId}";

		[JsonIgnore]
		public string LocationDisplayName => LocationName ?? $"Location: {base.LocationId}";

		public string ToJson(bool full = false)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			MinimalSerializableItemInfo minimalSerializableItemInfo = this;
			if (!full)
			{
				minimalSerializableItemInfo = new MinimalSerializableItemInfo
				{
					ItemId = base.ItemId,
					LocationId = base.LocationId,
					PlayerSlot = base.PlayerSlot,
					Flags = base.Flags
				};
				if (IsScout)
				{
					minimalSerializableItemInfo.ItemGame = base.ItemGame;
				}
				else
				{
					minimalSerializableItemInfo.LocationGame = base.LocationGame;
				}
			}
			JsonSerializerSettings val = new JsonSerializerSettings
			{
				NullValueHandling = (NullValueHandling)1,
				Formatting = (Formatting)0
			};
			return JsonConvert.SerializeObject((object)minimalSerializableItemInfo, val);
		}

		public static SerializableItemInfo FromJson(string json, IArchipelagoSession session = null)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			ItemInfoStreamingContext additional = ((session != null) ? new ItemInfoStreamingContext
			{
				Items = session.Items,
				Locations = session.Locations,
				PlayerHelper = session.Players,
				ConnectionInfo = session.ConnectionInfo
			} : null);
			JsonSerializerSettings val = new JsonSerializerSettings
			{
				Context = new StreamingContext(StreamingContextStates.Other, additional)
			};
			return JsonConvert.DeserializeObject<SerializableItemInfo>(json, val);
		}

		[OnDeserialized]
		internal void OnDeserializedMethod(StreamingContext streamingContext)
		{
			if (base.ItemGame == null && base.LocationGame != null)
			{
				IsScout = false;
			}
			else if (base.ItemGame != null && base.LocationGame == null)
			{
				IsScout = true;
			}
			if (streamingContext.Context is ItemInfoStreamingContext itemInfoStreamingContext)
			{
				if (IsScout && base.LocationGame == null)
				{
					base.LocationGame = itemInfoStreamingContext.ConnectionInfo.Game;
				}
				else if (!IsScout && base.ItemGame == null)
				{
					base.ItemGame = itemInfoStreamingContext.ConnectionInfo.Game;
				}
				if (ItemName == null)
				{
					ItemName = itemInfoStreamingContext.Items.GetItemName(base.ItemId, base.ItemGame);
				}
				if (LocationName == null)
				{
					LocationName = itemInfoStreamingContext.Locations.GetLocationNameFromId(base.LocationId, base.LocationGame);
				}
				if (Player == null)
				{
					Player = itemInfoStreamingContext.PlayerHelper.GetPlayerInfo(base.PlayerSlot);
				}
			}
		}
	}
	internal class ItemInfoStreamingContext
	{
		public IReceivedItemsHelper Items { get; set; }

		public ILocationCheckHelper Locations { get; set; }

		public IPlayerHelper PlayerHelper { get; set; }

		public IConnectionInfoProvider ConnectionInfo { get; set; }
	}
}
namespace Archipelago.MultiClient.Net.MessageLog.Parts
{
	public class EntranceMessagePart : MessagePart
	{
		internal EntranceMessagePart(JsonMessagePart messagePart)
			: base(MessagePartType.Entrance, messagePart, Archipelago.MultiClient.Net.Colors.PaletteColor.Blue)
		{
			base.Text = messagePart.Text;
		}
	}
	public class HintStatusMessagePart : MessagePart
	{
		internal HintStatusMessagePart(JsonMessagePart messagePart)
			: base(MessagePartType.HintStatus, messagePart)
		{
			base.Text = messagePart.Text;
			if (messagePart.HintStatus.HasValue)
			{
				base.PaletteColor = ColorUtils.GetColor(messagePart.HintStatus.Value);
			}
		}
	}
	public class ItemMessagePart : MessagePart
	{
		public ItemFlags Flags { get; }

		public long ItemId { get; }

		public int Player { get; }

		internal ItemMessagePart(IPlayerHelper players, IItemInfoResolver items, JsonMessagePart part)
			: base(MessagePartType.Item, part)
		{
			Flags = part.Flags.GetValueOrDefault();
			base.PaletteColor = ColorUtils.GetColor(Flags);
			Player = part.Player.GetValueOrDefault();
			string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game;
			JsonMessagePartType? type = part.Type;
			if (type.HasValue)
			{
				switch (type.GetValueOrDefault())
				{
				case JsonMessagePartType.ItemId:
					ItemId = long.Parse(part.Text);
					base.Text = items.GetItemName(ItemId, game) ?? $"Item: {ItemId}";
					break;
				case JsonMessagePartType.ItemName:
					ItemId = 0L;
					base.Text = part.Text;
					break;
				}
			}
		}
	}
	public class LocationMessagePart : MessagePart
	{
		public long LocationId { get; }

		public int Player { get; }

		internal LocationMessagePart(IPlayerHelper players, IItemInfoResolver itemInfoResolver, JsonMessagePart part)
			: base(MessagePartType.Location, part, Archipelago.MultiClient.Net.Colors.PaletteColor.Green)
		{
			Player = part.Player.GetValueOrDefault();
			string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game;
			JsonMessagePartType? type = part.Type;
			if (type.HasValue)
			{
				switch (type.GetValueOrDefault())
				{
				case JsonMessagePartType.LocationId:
					LocationId = long.Parse(part.Text);
					base.Text = itemInfoResolver.GetLocationName(LocationId, game) ?? $"Location: {LocationId}";
					break;
				case JsonMessagePartType.LocationName:
					LocationId = itemInfoResolver.GetLocationId(part.Text, game);
					base.Text = part.Text;
					break;
				}
			}
		}
	}
	public class MessagePart
	{
		public string Text { get; internal set; }

		public MessagePartType Type { get; internal set; }

		public Color Color => GetColor(BuiltInPalettes.Dark);

		public PaletteColor? PaletteColor { get; protected set; }

		public bool IsBackgroundColor { get; internal set; }

		internal MessagePart(MessagePartType type, JsonMessagePart messagePart, PaletteColor? color = null)
		{
			Type = type;
			Text = messagePart.Text;
			if (color.HasValue)
			{
				PaletteColor = color.Value;
			}
			else if (messagePart.Color.HasValue)
			{
				PaletteColor = ColorUtils.GetColor(messagePart.Color.Value);
				IsBackgroundColor = messagePart.Color.Value >= JsonMessagePartColor.BlackBg;
			}
			else
			{
				PaletteColor = null;
			}
		}

		public T GetColor<T>(Palette<T> palette)
		{
			return palette[PaletteColor];
		}

		public override string ToString()
		{
			return Text;
		}
	}
	public enum MessagePartType
	{
		Text,
		Player,
		Item,
		Location,
		Entrance,
		HintStatus
	}
	public class PlayerMessagePart : MessagePart
	{
		public bool IsActivePlayer { get; }

		public int SlotId { get; }

		internal PlayerMessagePart(IPlayerHelper players, IConnectionInfoProvider connectionInfo, JsonMessagePart part)
			: base(MessagePartType.Player, part)
		{
			switch (part.Type)
			{
			case JsonMessagePartType.PlayerId:
				SlotId = int.Parse(part.Text);
				IsActivePlayer = SlotId == connectionInfo.Slot;
				base.Text = players.GetPlayerAlias(SlotId) ?? $"Player {SlotId}";
				break;
			case JsonMessagePartType.PlayerName:
				SlotId = 0;
				IsActivePlayer = false;
				base.Text = part.Text;
				break;
			}
			base.PaletteColor = (IsActivePlayer ? Archipelago.MultiClient.Net.Colors.PaletteColor.Magenta : Archipelago.MultiClient.Net.Colors.PaletteColor.Yellow);
		}
	}
}
namespace Archipelago.MultiClient.Net.MessageLog.Messages
{
	public class AdminCommandResultLogMessage : LogMessage
	{
		internal AdminCommandResultLogMessage(MessagePart[] parts)
			: base(parts)
		{
		}
	}
	public class ChatLogMessage : PlayerSpecificLogMessage
	{
		public string Message { get; }

		internal ChatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string message)
			: base(parts, players, team, slot)
		{
			Message = message;
		}
	}
	public class CollectLogMessage : PlayerSpecificLogMessage
	{
		internal CollectLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class CommandResultLogMessage : LogMessage
	{
		internal CommandResultLogMessage(MessagePart[] parts)
			: base(parts)
		{
		}
	}
	public class CountdownLogMessage : LogMessage
	{
		public int RemainingSeconds { get; }

		internal CountdownLogMessage(MessagePart[] parts, int remainingSeconds)
			: base(parts)
		{
			RemainingSeconds = remainingSeconds;
		}
	}
	public class GoalLogMessage : PlayerSpecificLogMessage
	{
		internal GoalLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class HintItemSendLogMessage : ItemSendLogMessage
	{
		public bool IsFound { get; }

		internal HintItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, bool found, IItemInfoResolver itemInfoResolver)
			: base(parts, players, receiver, sender, item, itemInfoResolver)
		{
			IsFound = found;
		}
	}
	public class ItemCheatLogMessage : ItemSendLogMessage
	{
		internal ItemCheatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, NetworkItem item, IItemInfoResolver itemInfoResolver)
			: base(parts, players, slot, 0, item, team, itemInfoResolver)
		{
		}
	}
	public class ItemSendLogMessage : LogMessage
	{
		private PlayerInfo ActivePlayer { get; }

		public PlayerInfo Receiver { get; }

		public PlayerInfo Sender { get; }

		public bool IsReceiverTheActivePlayer => Receiver == ActivePlayer;

		public bool IsSenderTheActivePlayer => Sender == ActivePlayer;

		public bool IsRelatedToActivePlayer
		{
			get
			{
				if (!ActivePlayer.IsRelatedTo(Receiver))
				{
					return ActivePlayer.IsRelatedTo(Sender);
				}
				return true;
			}
		}

		public ItemInfo Item { get; }

		internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, IItemInfoResolver itemInfoResolver)
			: this(parts, players, receiver, sender, item, players.ActivePlayer.Team, itemInfoResolver)
		{
		}

		internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, int team, IItemInfoResolver itemInfoResolver)
			: base(parts)
		{
			ActivePlayer = players.ActivePlayer ?? new PlayerInfo();
			Receiver = players.GetPlayerInfo(team, receiver) ?? new PlayerInfo();
			Sender = players.GetPlayerInfo(team, sender) ?? new PlayerInfo();
			PlayerInfo player = players.GetPlayerInfo(team, item.Player) ?? new PlayerInfo();
			Item = new ItemInfo(item, Receiver.Game, Sender.Game, itemInfoResolver, player);
		}
	}
	public class JoinLogMessage : PlayerSpecificLogMessage
	{
		public string[] Tags { get; }

		internal JoinLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags)
			: base(parts, players, team, slot)
		{
			Tags = tags;
		}
	}
	public class LeaveLogMessage : PlayerSpecificLogMessage
	{
		internal LeaveLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class LogMessage
	{
		public MessagePart[] Parts { get; }

		internal LogMessage(MessagePart[] parts)
		{
			Parts = parts;
		}

		public override string ToString()
		{
			if (Parts.Length == 1)
			{
				return Parts[0].Text;
			}
			StringBuilder stringBuilder = new StringBuilder();
			MessagePart[] parts = Parts;
			foreach (MessagePart messagePart in parts)
			{
				stringBuilder.Append(messagePart.Text);
			}
			return stringBuilder.ToString();
		}
	}
	public abstract class PlayerSpecificLogMessage : LogMessage
	{
		private PlayerInfo ActivePlayer { get; }

		public PlayerInfo Player { get; }

		public bool IsActivePlayer => Player == ActivePlayer;

		public bool IsRelatedToActivePlayer => ActivePlayer.IsRelatedTo(Player);

		internal PlayerSpecificLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts)
		{
			ActivePlayer = players.ActivePlayer ?? new PlayerInfo();
			Player = players.GetPlayerInfo(team, slot) ?? new PlayerInfo();
		}
	}
	public class ReleaseLogMessage : PlayerSpecificLogMessage
	{
		internal ReleaseLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class ServerChatLogMessage : LogMessage
	{
		public string Message { get; }

		internal ServerChatLogMessage(MessagePart[] parts, string message)
			: base(parts)
		{
			Message = message;
		}
	}
	public class TagsChangedLogMessage : PlayerSpecificLogMessage
	{
		public string[] Tags { get; }

		internal TagsChangedLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags)
			: base(parts, players, team, slot)
		{
			Tags = tags;
		}
	}
	public class TutorialLogMessage : LogMessage
	{
		internal TutorialLogMessage(MessagePart[] parts)
			: base(parts)
		{
		}
	}
}
namespace Archipelago.MultiClient.Net.Helpers
{
	public class ArchipelagoSocketHelper : BaseArchipelagoSocketHelper<ClientWebSocket>, IArchipelagoSocketHelper
	{
		public Uri Uri { get; }

		internal ArchipelagoSocketHelper(Uri hostUri)
			: base(CreateWebSocket(), 1024)
		{
			Uri = hostUri;
			SecurityProtocolType securityProtocolType = SecurityProtocolType.Tls13;
			ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | securityProtocolType;
		}

		private static ClientWebSocket CreateWebSocket()
		{
			return new ClientWebSocket();
		}

		public async Task ConnectAsync()
		{
			await ConnectToProvidedUri(Uri);
			StartPolling();
		}

		private async Task ConnectToProvidedUri(Uri uri)
		{
			if (uri.Scheme != "unspecified")
			{
				try
				{
					await Socket.ConnectAsync(uri, CancellationToken.None);
					return;
				}
				catch (Exception e)
				{
					OnError(e);
					throw;
				}
			}
			List<Exception> errors = new List<Exception>(0);
			try
			{
				await Socket.ConnectAsync(uri.AsWss(), CancellationToken.None);
				if (Socket.State == WebSocketState.Open)
				{
					return;
				}
			}
			catch (Exception item)
			{
				errors.Add(item);
				Socket = CreateWebSocket();
			}
			try
			{
				await Socket.ConnectAsync(uri.AsWs(), CancellationToken.None);
			}
			catch (Exception item2)
			{
				errors.Add(item2);
				OnError(new AggregateException(errors));
				throw;
			}
		}
	}
	public class BaseArchipelagoSocketHelper<T> where T : WebSocket
	{
		private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter();

		private readonly BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>> sendQueue = new BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>>();

		internal T Socket;

		private readonly int bufferSize;

		public bool Connected
		{
			get
			{
				if (Socket.State != WebSocketState.Open)
				{
					return Socket.State == WebSocketState.CloseReceived;
				}
				return true;
			}
		}

		public event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived;

		public event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent;

		public event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived;

		public event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed;

		public event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened;

		internal BaseArchipelagoSocketHelper(T socket, int bufferSize = 1024)
		{
			Socket = socket;
			this.bufferSize = bufferSize;
		}

		internal void StartPolling()
		{
			if (this.SocketOpened != null)
			{
				this.SocketOpened();
			}
			Task.Run((Func<Task?>)PollingLoop);
			Task.Run((Func<Task?>)SendLoop);
		}

		private async Task PollingLoop()
		{
			byte[] buffer = new byte[bufferSize];
			while (Socket.State == WebSocketState.Open)
			{
				string message = null;
				try
				{
					message = await ReadMessageAsync(buffer);
				}
				catch (Exception e)
				{
					OnError(e);
				}
				OnMessageReceived(message);
			}
		}

		private async Task SendLoop()
		{
			while (Socket.State == WebSocketState.Open)
			{
				try
				{
					await HandleSendBuffer();
				}
				catch (Exception e)
				{
					OnError(e);
				}
				await Task.Delay(20);
			}
		}

		private async Task<string> ReadMessageAsync(byte[] buffer)
		{
			using MemoryStream readStream = new MemoryStream(buffer.Length);
			WebSocketReceiveResult result;
			do
			{
				result = await Socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
				if (result.MessageType == WebSocketMessageType.Close)
				{
					try
					{
						await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
					}
					catch
					{
					}
					OnSocketClosed();
				}
				else
				{
					readStream.Write(buffer, 0, result.Count);
				}
			}
			while (!result.EndOfMessage);
			return Encoding.UTF8.GetString(readStream.ToArray());
		}

		public async Task DisconnectAsync()
		{
			await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closure requested by client", CancellationToken.None);
			OnSocketClosed();
		}

		public void SendPacket(ArchipelagoPacketBase packet)
		{
			SendMultiplePackets(new List<ArchipelagoPacketBase> { packet });
		}

		public void SendMultiplePackets(List<ArchipelagoPacketBase> packets)
		{
			SendMultiplePackets(packets.ToArray());
		}

		public void SendMultiplePackets(params ArchipelagoPacketBase[] packets)
		{
			SendMultiplePacketsAsync(packets).Wait();
		}

		public Task SendPacketAsync(ArchipelagoPacketBase packet)
		{
			return SendMultiplePacketsAsync(new List<ArchipelagoPacketBase> { packet });
		}

		public Task SendMultiplePacketsAsync(List<ArchipelagoPacketBase> packets)
		{
			return SendMultiplePacketsAsync(packets.ToArray());
		}

		public Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets)
		{
			TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
			foreach (ArchipelagoPacketBase item in packets)
			{
				sendQueue.Add(new Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>(item, taskCompletionSource));
			}
			return taskCompletionSource.Task;
		}

		private async Task HandleSendBuffer()
		{
			List<ArchipelagoPacketBase> list = new List<ArchipelagoPacketBase>();
			List<TaskCompletionSource<bool>> tasks = new List<TaskCompletionSource<bool>>();
			Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> tuple = sendQueue.Take();
			list.Add(tuple.Item1);
			tasks.Add(tuple.Item2);
			Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> item;
			while (sendQueue.TryTake(out item))
			{
				list.Add(item.Item1);
				tasks.Add(item.Item2);
			}
			if (!list.Any())
			{
				return;
			}
			if (Socket.State != WebSocketState.Open)
			{
				throw new ArchipelagoSocketClosedException();
			}
			ArchipelagoPacketBase[] packets = list.ToArray();
			string s = JsonConvert.SerializeObject((object)packets);
			byte[] messageBuffer = Encoding.UTF8.GetBytes(s);
			int messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / (double)bufferSize);
			for (int i = 0; i < messagesCount; i++)
			{
				int num = bufferSize * i;
				int num2 = bufferSize;
				bool endOfMessage = i + 1 == messagesCount;
				if (num2 * (i + 1) > messageBuffer.Length)
				{
					num2 = messageBuffer.Length - num;
				}
				await Socket.SendAsync(new ArraySegment<byte>(messageBuffer, num, num2), WebSocketMessageType.Text, endOfMessage, CancellationToken.None);
			}
			foreach (TaskCompletionSource<bool> item2 in tasks)
			{
				item2.TrySetResult(result: true);
			}
			OnPacketSend(packets);
		}

		private void OnPacketSend(ArchipelagoPacketBase[] packets)
		{
			try
			{
				if (this.PacketsSent != null)
				{
					this.PacketsSent(packets);
				}
			}
			catch (Exception e)
			{
				OnError(e);
			}
		}

		private void OnSocketClosed()
		{
			try
			{
				if (this.SocketClosed != null)
				{
					this.SocketClosed("");
				}
			}
			catch (Exception e)
			{
				OnError(e);
			}
		}

		private void OnMessageReceived(string message)
		{
			try
			{
				if (string.IsNullOrEmpty(message) || this.PacketReceived == null)
				{
					return;
				}
				List<ArchipelagoPacketBase> list = null;
				try
				{
					list = JsonConvert.DeserializeObject<List<ArchipelagoPacketBase>>(message, (JsonConverter[])(object)new JsonConverter[1] { Converter });
				}
				catch (Exception e)
				{
					OnError(e);
				}
				if (list == null)
				{
					return;
				}
				foreach (ArchipelagoPacketBase item in list)
				{
					this.PacketReceived(item);
				}
			}
			catch (Exception e2)
			{
				OnError(e2);
			}
		}

		protected void OnError(Exception e)
		{
			try
			{
				if (this.ErrorReceived != null)
				{
					this.ErrorReceived(e, e.Message);
				}
			}
			catch (Exception ex)
			{
				Console.Out.WriteLine("Error occured during reporting of errorOuter Errror: " + e.Message + " " + e.StackTrace + "Inner Errror: " + ex.Message + " " + ex.StackTrace);
			}
		}
	}
	public interface IConnectionInfoProvider
	{
		string Game { get; }

		int Team { get; }

		int Slot { get; }

		string[] Tags { get; }

		ItemsHandlingFlags ItemsHandlingFlags { get; }

		string Uuid { get; }

		void UpdateConnectionOptions(string[] tags);

		void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags);

		void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags);
	}
	public class ConnectionInfoHelper : IConnectionInfoProvider
	{
		private readonly IArchipelagoSocketHelper socket;

		public string Game { get; private set; }

		public int Team { get; private set; }

		public int Slot { get; private set; }

		public string[] Tags { get; internal set; }

		public ItemsHandlingFlags ItemsHandlingFlags { get; internal set; }

		public string Uuid { get; private set; }

		internal ConnectionInfoHelper(IArchipelagoSocketHelper socket)
		{
			this.socket = socket;
			Reset();
			socket.PacketReceived += PacketReceived;
		}

		private void PacketReceived(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket connectedPacket))
			{
				if (packet is ConnectionRefusedPacket)
				{
					Reset();
				}
				return;
			}
			Team = connectedPacket.Team;
			Slot = connectedPacket.Slot;
			if (connectedPacket.SlotInfo != null && connectedPacket.SlotInfo.ContainsKey(Slot))
			{
				Game = connectedPacket.SlotInfo[Slot].Game;
			}
		}

		internal void SetConnectionParameters(string game, string[] tags, ItemsHandlingFlags itemsHandlingFlags, string uuid)
		{
			Game = game;
			Tags = tags ?? new string[0];
			ItemsHandlingFlags = itemsHandlingFlags;
			Uuid = uuid ?? Guid.NewGuid().ToString();
		}

		private void Reset()
		{
			Game = null;
			Team = -1;
			Slot = -1;
			Tags = new string[0];
			ItemsHandlingFlags = ItemsHandlingFlags.NoItems;
			Uuid = null;
		}

		public void UpdateConnectionOptions(string[] tags)
		{
			UpdateConnectionOptions(tags, ItemsHandlingFlags);
		}

		public void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags)
		{
			UpdateConnectionOptions(Tags, itemsHandlingFlags);
		}

		public void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags)
		{
			SetConnectionParameters(Game, tags, itemsHandlingFlags, Uuid);
			socket.SendPacket(new ConnectUpdatePacket
			{
				Tags = Tags,
				ItemsHandling = ItemsHandlingFlags
			});
		}
	}
	public interface IDataStorageHelper : IDataStorageWrapper
	{
		DataStorageElement this[Scope scope, string key] { get; set; }

		DataStorageElement this[string key] { get; set; }
	}
	public class DataStorageHelper : IDataStorageHelper, IDataStorageWrapper
	{
		public delegate void DataStorageUpdatedHandler(JToken originalValue, JToken newValue, Dictionary<string, JToken> additionalArguments);

		private readonly Dictionary<string, DataStorageUpdatedHandler> onValueChangedEventHandlers = new Dictionary<string, DataStorageUpdatedHandler>();

		private readonly Dictionary<Guid, DataStorageUpdatedHandler> operationSpecificCallbacks = new Dictionary<Guid, DataStorageUpdatedHandler>();

		private readonly Dictionary<string, TaskCompletionSource<JToken>> asyncRetrievalTasks = new Dictionary<string, TaskCompletionSource<JToken>>();

		private readonly IArchipelagoSocketHelper socket;

		private readonly IConnectionInfoProvider connectionInfoProvider;

		public DataStorageElement this[Scope scope, string key]
		{
			get
			{
				return this[AddScope(scope, key)];
			}
			set
			{
				this[AddScope(scope, key)] = value;
			}
		}

		public DataStorageElement this[string key]
		{
			get
			{
				return new DataStorageElement(GetContextForKey(key));
			}
			set
			{
				SetValue(key, value);
			}
		}

		internal DataStorageHelper(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider)
		{
			this.socket = socket;
			this.connectionInfoProvider = connectionInfoProvider;
			socket.PacketReceived += OnPacketReceived;
		}

		private void OnPacketReceived(ArchipelagoPacketBase packet)
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Invalid comparison between Unknown and I4
			if (!(packet is RetrievedPacket retrievedPacket))
			{
				if (packet is SetReplyPacket setReplyPacket)
				{
					if (setReplyPacket.AdditionalArguments != null && setReplyPacket.AdditionalArguments.ContainsKey("Reference") && (int)setReplyPacket.AdditionalArguments["Reference"].Type == 8 && ((string)setReplyPacket.AdditionalArguments["Reference"]).TryParseNGuid(out var g) && operationSpecificCallbacks.TryGetValue(g, out var value))
					{
						value(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments);
						operationSpecificCallbacks.Remove(g);
					}
					if (onValueChangedEventHandlers.TryGetValue(setReplyPacket.Key, out var value2))
					{
						value2(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments);
					}
				}
				return;
			}
			foreach (KeyValuePair<string, JToken> datum in retrievedPacket.Data)
			{
				if (asyncRetrievalTasks.TryGetValue(datum.Key, out var value3))
				{
					value3.TrySetResult(datum.Value);
					asyncRetrievalTasks.Remove(datum.Key);
				}
			}
		}

		private Task<JToken> GetAsync(string key)
		{
			if (asyncRetrievalTasks.TryGetValue(key, out var value))
			{
				return value.Task;
			}
			TaskCompletionSource<JToken> taskCompletionSource = new TaskCompletionSource<JToken>();
			asyncRetrievalTasks[key] = taskCompletionSource;
			socket.SendPacketAsync(new GetPacket
			{
				Keys = new string[1] { key }
			});
			return taskCompletionSource.Task;
		}

		private void Initialize(string key, JToken value)
		{
			socket.SendPacketAsync(new SetPacket
			{
				Key = key,
				DefaultValue = value,
				Operations = new OperationSpecification[1]
				{
					new OperationSpecification
					{
						OperationType = OperationType.Default
					}
				}
			});
		}

		private JToken GetValue(string key)
		{
			Task<JToken> async = GetAsync(key);
			if (!async.Wait(TimeSpan.FromSeconds(2.0)))
			{
				throw new TimeoutException("Timed out retrieving data for key `" + key + "`. This may be due to an attempt to retrieve a value from the DataStorageHelper in a synchronous fashion from within a PacketReceived handler. When using the DataStorageHelper from within code which runs on the websocket thread then use the asynchronous getters. Ex: `DataStorageHelper[\"" + key + "\"].GetAsync().ContinueWith(x => {});`Be aware that DataStorageHelper calls tend to cause packet responses, so making a call from within a PacketReceived handler may cause an infinite loop.");
			}
			return async.Result;
		}

		private void SetValue(string key, DataStorageElement e)
		{
			if (key.StartsWith("_read_"))
			{
				throw new InvalidOperationException("DataStorage write operation on readonly key '" + key + "' is not allowed");
			}
			if (e == null)
			{
				e = new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull());
			}
			if (e.Context == null)
			{
				e.Context = GetContextForKey(key);
			}
			else if (e.Context.Key != key)
			{
				e.Operations.Insert(0, new OperationSpecification
				{
					OperationType = OperationType.Replace,
					Value = GetValue(e.Context.Key)
				});
			}
			Dictionary<string, JToken> dictionary = e.AdditionalArguments ?? new Dictionary<string, JToken>(0);
			if (e.Callbacks != null)
			{
				Guid key2 = Guid.NewGuid();
				operationSpecificCallbacks[key2] = e.Callbacks;
				dictionary["Reference"] = JToken.op_Implicit(key2.ToString("N"));
				socket.SendPacketAsync(new SetPacket
				{
					Key = key,
					Operations = e.Operations.ToArray(),
					WantReply = true,
					AdditionalArguments = dictionary
				});
			}
			else
			{
				socket.SendPacketAsync(new SetPacket
				{
					Key = key,
					Operations = e.Operations.ToArray(),
					AdditionalArguments = dictionary
				});
			}
		}

		private DataStorageElementContext GetContextForKey(string key)
		{
			return new DataStorageElementContext
			{
				Key = key,
				GetData = GetValue,
				GetAsync = GetAsync,
				Initialize = Initialize,
				AddHandler = AddHandler,
				RemoveHandler = RemoveHandler
			};
		}

		private void AddHandler(string key, DataStorageUpdatedHandler handler)
		{
			if (onValueChangedEventHandlers.ContainsKey(key))
			{
				Dictionary<string, DataStorageUpdatedHandler> dictionary = onValueChangedEventHandlers;
				dictionary[key] = (DataStorageUpdatedHandler)Delegate.Combine(dictionary[key], handler);
			}
			else
			{
				onValueChangedEventHandlers[key] = handler;
			}
			socket.SendPacketAsync(new SetNotifyPacket
			{
				Keys = new string[1] { key }
			});
		}

		private void RemoveHandler(string key, DataStorageUpdatedHandler handler)
		{
			if (onValueChangedEventHandlers.ContainsKey(key))
			{
				Dictionary<string, DataStorageUpdatedHandler> dictionary = onValueChangedEventHandlers;
				dictionary[key] = (DataStorageUpdatedHandler)Delegate.Remove(dictionary[key], handler);
				if (onValueChangedEventHandlers[key] == null)
				{
					onValueChangedEventHandlers.Remove(key);
				}
			}
		}

		private string AddScope(Scope scope, string key)
		{
			return scope switch
			{
				Scope.Global => key, 
				Scope.Game => $"{scope}:{connectionInfoProvider.Game}:{key}", 
				Scope.Team => $"{scope}:{connectionInfoProvider.Team}:{key}", 
				Scope.Slot => $"{scope}:{connectionInfoProvider.Slot}:{key}", 
				Scope.ReadOnly => "_read_" + key, 
				_ => throw new ArgumentOutOfRangeException("scope", scope, "Invalid scope for key " + key), 
			};
		}

		private DataStorageElement GetHintsElement(int? slot = null, int? team = null)
		{
			return this[Scope.ReadOnly, $"hints_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"];
		}

		private DataStorageElement GetSlotDataElement(int? slot = null)
		{
			return this[Scope.ReadOnly, $"slot_data_{slot ?? connectionInfoProvider.Slot}"];
		}

		private DataStorageElement GetItemNameGroupsElement(string game = null)
		{
			return this[Scope.ReadOnly, "item_name_groups_" + (game ?? connectionInfoProvider.Game)];
		}

		private DataStorageElement GetLocationNameGroupsElement(string game = null)
		{
			return this[Scope.ReadOnly, "location_name_groups_" + (game ?? connectionInfoProvider.Game)];
		}

		private DataStorageElement GetClientStatusElement(int? slot = null, int? team = null)
		{
			return this[Scope.ReadOnly, $"client_status_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"];
		}

		private DataStorageElement GetRaceModeElement()
		{
			return this[Scope.ReadOnly, "race_mode"];
		}

		public Hint[] GetHints(int? slot = null, int? team = null)
		{
			return GetHintsElement(slot, team).To<Hint[]>();
		}

		public Task<Hint[]> GetHintsAsync(int? slot = null, int? team = null)
		{
			return GetHintsElement(slot, team).GetAsync<Hint[]>();
		}

		public void TrackHints(Action<Hint[]> onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null)
		{
			GetHintsElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x)
			{
				onHintsUpdated(newValue.ToObject<Hint[]>());
			};
			if (retrieveCurrentlyUnlockedHints)
			{
				GetHintsAsync(slot, team).ContinueWith(delegate(Task<Hint[]> t)
				{
					onHintsUpdated(t.Result);
				});
			}
		}

		public Dictionary<string, object> GetSlotData(int? slot = null)
		{
			return GetSlotData<Dictionary<string, object>>(slot);
		}

		public T GetSlotData<T>(int? slot = null) where T : class
		{
			return GetSlotDataElement(slot).To<T>();
		}

		public Task<Dictionary<string, object>> GetSlotDataAsync(int? slot = null)
		{
			return GetSlotDataAsync<Dictionary<string, object>>(slot);
		}

		public Task<T> GetSlotDataAsync<T>(int? slot = null) where T : class
		{
			return GetSlotDataElement(slot).GetAsync<T>();
		}

		public Dictionary<string, string[]> GetItemNameGroups(string game = null)
		{
			return GetItemNameGroupsElement(game).To<Dictionary<string, string[]>>();
		}

		public Task<Dictionary<string, string[]>> GetItemNameGroupsAsync(string game = null)
		{
			return GetItemNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>();
		}

		public Dictionary<string, string[]> GetLocationNameGroups(string game = null)
		{
			return GetLocationNameGroupsElement(game).To<Dictionary<string, string[]>>();
		}

		public Task<Dictionary<string, string[]>> GetLocationNameGroupsAsync(string game = null)
		{
			return GetLocationNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>();
		}

		public ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null)
		{
			return GetClientStatusElement(slot, team).To<ArchipelagoClientState?>().GetValueOrDefault();
		}

		public Task<ArchipelagoClientState> GetClientStatusAsync(int? slot = null, int? team = null)
		{
			return GetClientStatusElement(slot, team).GetAsync<ArchipelagoClientState?>().ContinueWith((Task<ArchipelagoClientState?> r) => r.Result.GetValueOrDefault());
		}

		public void TrackClientStatus(Action<ArchipelagoClientState> onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null)
		{
			Get