Decompiled source of Gungeon Archipelago v1.0.1

Archipelago.MultiClient.Net.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.Text;
using System.Threading;
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;
using WebSocketSharp;

[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: AssemblyCompany("Jarno Westhof, Hussein Farran, Zach Parks")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyDescription("A client library for use with .NET based prog-langs for interfacing with Archipelago hosts.")]
[assembly: AssemblyFileVersion("6.3.0.0")]
[assembly: AssemblyInformationalVersion("6.3.0+3113ea01d1b0db22ada93b5f35c6d4272a8002b2")]
[assembly: AssemblyProduct("Archipelago.MultiClient.Net")]
[assembly: AssemblyTitle("Archipelago.MultiClient.Net")]
[assembly: AssemblyVersion("6.3.0.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);
}
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; }

		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 volatile bool awaitingRoomInfo;

		private volatile bool expectingLoginResult;

		private LoginResult loginResult;

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

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

		private void Socket_PacketReceived(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket) && !(packet is ConnectionRefusedPacket))
			{
				if (packet is RoomInfoPacket)
				{
					awaitingRoomInfo = false;
				}
				return;
			}
			if (packet is ConnectedPacket && RoomState.Version != null && RoomState.Version >= new Version(0, 3, 8))
			{
				LogUsedVersion();
			}
			if (expectingLoginResult)
			{
				expectingLoginResult = false;
				loginResult = LoginResult.FromPacket(packet);
			}
		}

		private void LogUsedVersion()
		{
			try
			{
				string fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
				Socket.SendPacketAsync(new SetPacket
				{
					Key = ".NetUsedVersions",
					DefaultValue = (JToken)(object)JObject.FromObject((object)new Dictionary<string, bool>()),
					Operations = new OperationSpecification[1] { Operation.Update(new Dictionary<string, bool> { 
					{
						ConnectionInfo.Game + ":" + fileVersion + ":NET35",
						true
					} }) }
				});
			}
			catch
			{
			}
		}

		public LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true)
		{
			connectionInfo.SetConnectionParameters(game, tags, itemsHandlingFlags, uuid);
			try
			{
				awaitingRoomInfo = true;
				expectingLoginResult = true;
				loginResult = null;
				Socket.Connect();
				DateTime utcNow = DateTime.UtcNow;
				while (awaitingRoomInfo)
				{
					if (DateTime.UtcNow - utcNow > TimeSpan.FromSeconds(4.0))
					{
						Socket.Disconnect();
						return new LoginFailure("Connection timed out.");
					}
					Thread.Sleep(25);
				}
				Socket.SendPacket(BuildConnectPacket(name, password, version, requestSlotData));
				utcNow = DateTime.UtcNow;
				while (expectingLoginResult)
				{
					if (DateTime.UtcNow - utcNow > TimeSpan.FromSeconds(4.0))
					{
						Socket.Disconnect();
						return new LoginFailure("Connection timed out.");
					}
					Thread.Sleep(25);
				}
				Thread.Sleep(50);
				return loginResult;
			}
			catch (ArchipelagoSocketClosedException)
			{
				return new LoginFailure("Socket closed unexpectedly.");
			}
		}

		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, 4, 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 roomState = new RoomStateHelper(socket, locationCheckHelper);
			DataStorageHelper dataStorage = new DataStorageHelper(socket, connectionInfoHelper);
			MessageLogHelper messageLog = new MessageLogHelper(socket, itemInfoResolver, playerHelper, connectionInfoHelper);
			return new ArchipelagoSession(socket, items, locationCheckHelper, playerHelper, roomState, connectionInfoHelper, dataStorage, messageLog);
		}

		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(StringEnumConverter))]
		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 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(StringEnumConverter))]
		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;
	}
}
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 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 void GetAsync<T>(Action<T> callback)
		{
			GetAsync(delegate(JToken t)
			{
				callback(t.ToObject<T>());
			});
		}

		public void GetAsync(Action<JToken> callback)
		{
			Context.GetAsync(Context.Key, callback);
		}

		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_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Invalid comparison between Unknown and I4
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Invalid comparison between Unknown and I4
			//IL_00d7: 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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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((object?)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 (object)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 Action<string, Action<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; }
	}
	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 = ((object)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(StringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public JsonMessagePartType? Type { get; set; }

		[JsonProperty("color")]
		[JsonConverter(typeof(StringEnumConverter), 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; }
	}
	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(StringEnumConverter), 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 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 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 Or(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Or,
				Value = JToken.op_Implicit(i)
			};
		}

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

		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, Color.Blue)
		{
			base.Text = messagePart.Text;
		}
	}
	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.Color = 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;
				}
			}
		}

		private static Color GetColor(ItemFlags flags)
		{
			if (HasFlag(flags, ItemFlags.Advancement))
			{
				return Color.Plum;
			}
			if (HasFlag(flags, ItemFlags.NeverExclude))
			{
				return Color.SlateBlue;
			}
			if (HasFlag(flags, ItemFlags.Trap))
			{
				return Color.Salmon;
			}
			return Color.Cyan;
		}

		private static bool HasFlag(ItemFlags flags, ItemFlags flag)
		{
			return (flags & flag) > ItemFlags.None;
		}
	}
	public class LocationMessagePart : MessagePart
	{
		public long LocationId { get; }

		public int Player { get; }

		internal LocationMessagePart(IPlayerHelper players, IItemInfoResolver itemInfoResolver, JsonMessagePart part)
			: base(MessagePartType.Location, part, Color.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 { get; internal set; }

		public bool IsBackgroundColor { get; internal set; }

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

		private static Color GetColor(JsonMessagePartColor color)
		{
			switch (color)
			{
			case JsonMessagePartColor.Red:
			case JsonMessagePartColor.RedBg:
				return Color.Red;
			case JsonMessagePartColor.Green:
			case JsonMessagePartColor.GreenBg:
				return Color.Green;
			case JsonMessagePartColor.Yellow:
			case JsonMessagePartColor.YellowBg:
				return Color.Yellow;
			case JsonMessagePartColor.Blue:
			case JsonMessagePartColor.BlueBg:
				return Color.Blue;
			case JsonMessagePartColor.Magenta:
			case JsonMessagePartColor.MagentaBg:
				return Color.Magenta;
			case JsonMessagePartColor.Cyan:
			case JsonMessagePartColor.CyanBg:
				return Color.Cyan;
			case JsonMessagePartColor.Black:
			case JsonMessagePartColor.BlackBg:
				return Color.Black;
			case JsonMessagePartColor.White:
			case JsonMessagePartColor.WhiteBg:
				return Color.White;
			default:
				return Color.White;
			}
		}

		public override string ToString()
		{
			return Text;
		}
	}
	public enum MessagePartType
	{
		Text,
		Player,
		Item,
		Location,
		Entrance
	}
	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.Color = GetColor(IsActivePlayer);
		}

		private static Color GetColor(bool isActivePlayer)
		{
			if (isActivePlayer)
			{
				return Color.Magenta;
			}
			return Color.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 : IArchipelagoSocketHelper
	{
		private const SslProtocols Tls13 = SslProtocols.Tls13;

		private const SslProtocols Tls12 = SslProtocols.Tls12;

		private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter();

		internal WebSocket webSocket;

		public Uri Uri { get; }

		public bool Connected
		{
			get
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0014: Invalid comparison between Unknown and I4
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Invalid comparison between Unknown and I4
				if (webSocket != null)
				{
					if ((int)webSocket.ReadyState != 1)
					{
						return (int)webSocket.ReadyState == 2;
					}
					return true;
				}
				return false;
			}
		}

		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 ArchipelagoSocketHelper(Uri hostUrl)
		{
			Uri = hostUrl;
		}

		private WebSocket CreateWebSocket(Uri uri)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			WebSocket val = new WebSocket(uri.ToString(), new string[0]);
			if (val.IsSecure)
			{
				val.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
			}
			val.OnMessage += OnMessageReceived;
			val.OnError += OnError;
			val.OnClose += OnClose;
			val.OnOpen += OnOpen;
			return val;
		}

		public void Connect()
		{
			ConnectToProvidedUri(Uri);
		}

		private void ConnectToProvidedUri(Uri uri)
		{
			if (uri.Scheme != "unspecified")
			{
				try
				{
					webSocket = CreateWebSocket(uri);
					webSocket.Connect();
					return;
				}
				catch (Exception e)
				{
					OnError(e);
					return;
				}
			}
			List<Exception> list = new List<Exception>();
			try
			{
				try
				{
					ConnectToProvidedUri(uri.AsWss());
				}
				catch (Exception item)
				{
					list.Add(item);
					throw;
				}
				if (webSocket.IsAlive)
				{
					return;
				}
				try
				{
					ConnectToProvidedUri(uri.AsWs());
				}
				catch (Exception item2)
				{
					list.Add(item2);
					throw;
				}
			}
			catch
			{
				try
				{
					ConnectToProvidedUri(uri.AsWs());
				}
				catch (Exception item3)
				{
					list.Add(item3);
					OnError(new Archipelago.MultiClient.Net.Exceptions.AggregateException(list));
				}
			}
		}

		public void Disconnect()
		{
			if (webSocket != null && webSocket.IsAlive)
			{
				webSocket.Close();
			}
		}

		public void DisconnectAsync()
		{
			if (webSocket != null && webSocket.IsAlive)
			{
				webSocket.CloseAsync();
			}
		}

		public void SendPacket(ArchipelagoPacketBase packet)
		{
			SendMultiplePackets(packet);
		}

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

		public void SendMultiplePackets(params ArchipelagoPacketBase[] packets)
		{
			if (webSocket != null && webSocket.IsAlive)
			{
				string text = JsonConvert.SerializeObject((object)packets);
				webSocket.Send(text);
				if (this.PacketsSent != null)
				{
					this.PacketsSent(packets);
				}
				return;
			}
			throw new ArchipelagoSocketClosedException();
		}

		public void SendPacketAsync(ArchipelagoPacketBase packet, Action<bool> onComplete = null)
		{
			SendMultiplePacketsAsync(new List<ArchipelagoPacketBase> { packet }, onComplete);
		}

		public void SendMultiplePacketsAsync(List<ArchipelagoPacketBase> packets, Action<bool> onComplete = null)
		{
			SendMultiplePacketsAsync(onComplete, packets.ToArray());
		}

		public void SendMultiplePacketsAsync(Action<bool> onComplete = null, params ArchipelagoPacketBase[] packets)
		{
			if (webSocket.IsAlive)
			{
				string text = JsonConvert.SerializeObject((object)packets);
				webSocket.SendAsync(text, onComplete);
				if (this.PacketsSent != null)
				{
					this.PacketsSent(packets);
				}
				return;
			}
			throw new ArchipelagoSocketClosedException();
		}

		private void OnOpen(object sender, EventArgs e)
		{
			if (this.SocketOpened != null)
			{
				this.SocketOpened();
			}
		}

		private void OnClose(object sender, CloseEventArgs e)
		{
			if ((!(Uri.Scheme == "unspecified") || sender != webSocket || !(webSocket.Url.Scheme == "wss")) && this.SocketClosed != null)
			{
				this.SocketClosed(e.Reason);
			}
		}

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

		private void OnError(object sender, ErrorEventArgs e)
		{
			if (this.ErrorReceived != null)
			{
				this.ErrorReceived(e.Exception, e.Message);
			}
		}

		private void OnError(Exception e)
		{
			if (this.ErrorReceived != null)
			{
				this.ErrorReceived(e, e.Message);
			}
		}
	}
	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, Action<JToken>> asyncRetrievalCallbacks = new Dictionary<string, Action<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_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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 == 15 && operationSpecificCallbacks.TryGetValue((Guid)setReplyPacket.AdditionalArguments["Reference"], out var value))
					{
						value(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments);
						operationSpecificCallbacks.Remove((Guid)setReplyPacket.AdditionalArguments["Reference"]);
					}
					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 (asyncRetrievalCallbacks.TryGetValue(datum.Key, out var value3))
				{
					value3(datum.Value);
					asyncRetrievalCallbacks.Remove(datum.Key);
				}
			}
		}

		private void GetAsync(string key, Action<JToken> callback)
		{
			if (!asyncRetrievalCallbacks.ContainsKey(key))
			{
				asyncRetrievalCallbacks[key] = callback;
			}
			else
			{
				Dictionary<string, Action<JToken>> dictionary = asyncRetrievalCallbacks;
				dictionary[key] = (Action<JToken>)Delegate.Combine(dictionary[key], callback);
			}
			socket.SendPacketAsync(new GetPacket
			{
				Keys = new string[1] { key }
			});
		}

		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)
		{
			JToken value = null;
			GetAsync(key, delegate(JToken v)
			{
				value = v;
			});
			int num = 0;
			while (value == null)
			{
				Thread.Sleep(10);
				if (++num > 200)
				{
					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(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 value;
		}

		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 guid = Guid.NewGuid();
				operationSpecificCallbacks[guid] = e.Callbacks;
				dictionary["Reference"] = JToken.FromObject((object)guid);
				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}"];
		}

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

		public void GetHintsAsync(Action<Hint[]> onHintsRetrieved, int? slot = null, int? team = null)
		{
			GetHintsElement(slot, team).GetAsync(delegate(JToken t)
			{
				onHintsRetrieved((t != null) ? t.ToObject<Hint[]>() : null);
			});
		}

		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(onHintsUpdated, slot, team);
			}
		}

		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 void GetSlotDataAsync(Action<Dictionary<string, object>> onSlotDataRetrieved, int? slot = null)
		{
			GetSlotDataElement(slot).GetAsync(delegate(JToken t)
			{
				onSlotDataRetrieved((t != null) ? t.ToObject<Dictionary<string, object>>() : null);
			});
		}

		public void GetSlotDataAsync<T>(Action<T> onSlotDataRetrieved, int? slot = null) where T : class
		{
			GetSlotDataElement(slot).GetAsync(delegate(JToken t)
			{
				onSlotDataRetrieved((t != null) ? t.ToObject<T>() : null);
			});
		}

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

		public void GetItemNameGroupsAsync(Action<Dictionary<string, string[]>> onItemNameGroupsRetrieved, string game = null)
		{
			GetItemNameGroupsElement(game).GetAsync(delegate(JToken t)
			{
				onItemNameGroupsRetrieved((t != null) ? t.ToObject<Dictionary<string, string[]>>() : null);
			});
		}

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

		public void GetLocationNameGroupsAsync(Action<Dictionary<string, string[]>> onLocationNameGroupsRetrieved, string game = null)
		{
			GetLocationNameGroupsElement(game).GetAsync(delegate(JToken t)
			{
				onLocationNameGroupsRetrieved((t != null) ? t.ToObject<Dictionary<string, string[]>>() : null);
			});
		}

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

		public void GetClientStatusAsync(Action<ArchipelagoClientState> onStatusRetrieved, int? slot = null, int? team = null)
		{
			GetClientStatusElement(slot, team).GetAsync(delegate(JToken t)
			{
				onStatusRetrieved(t.ToObject<ArchipelagoClientState?>().GetValueOrDefault());
			});
		}

		public void TrackClientStatus(Action<ArchipelagoClientState> onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null)
		{
			GetClientStatusElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x)
			{
				onStatusUpdated(newValue.ToObject<ArchipelagoClientState>());
			};
			if (retrieveCurrentClientStatus)
			{
				GetClientStatusAsync(onStatusUpdated, slot, team);
			}
		}
	}
	public interface IDataStorageWrapper
	{
		Hint[] GetHints(int? slot = null, int? team = null);

		void GetHintsAsync(Action<Hint[]> onHintsRetrieved, int? slot = null, int? team = null);

		void TrackHints(Action<Hint[]> onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null);

		Dictionary<string, object> GetSlotData(int? slot = null);

		T GetSlotData<T>(int? slot = null) where T : class;

		void GetSlotDataAsync(Action<Dictionary<string, object>> onSlotDataRetrieved, int? slot = null);

		void GetSlotDataAsync<T>(Action<T> onSlotDataRetrieved, int? slot = null) where T : class;

		Dictionary<string, string[]> GetItemNameGroups(string game = null);

		void GetItemNameGroupsAsync(Action<Dictionary<string, string[]>> onItemNameGroupsRetrieved, string game = null);

		Dictionary<string, string[]> GetLocationNameGroups(string game = null);

		void GetLocationNameGroupsAsync(Action<Dictionary<string, string[]>> onLocationNameGroupsRetrieved, string game = null);

		ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null);

		void GetClientStatusAsync(Action<ArchipelagoClientState> onStatusRetrieved, int? slot = null, int? team = null);

		void TrackClientStatus(Action<ArchipelagoClientState> onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null);
	}
	public class ArchipelagoSocketHelperDelagates
	{
		public delegate void PacketReceivedHandler(ArchipelagoPacketBase packet);

		public delegate void PacketsSentHandler(ArchipelagoPacketBase[] packets);

		public delegate void ErrorReceivedHandler(Exception e, string message);

		public delegate void SocketClosedHandler(string reason);

		public delegate void SocketOpenedHandler();
	}
	public interface IArchipelagoSocketHelper
	{
		Uri Uri { get; }

		bool Connected { get; }

		event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived;

		event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent;

		event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived;

		event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed;

		event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened;

		void SendPacket(ArchipelagoPacketBase packet);

		void SendMultiplePackets(List<ArchipelagoPacketBase> packets);

		void SendMultiplePackets(params ArchipelagoPacketBase[] packets);

		void Connect();

		void Disconnect();

		void SendPacketAsync(ArchipelagoPacketBase packet, Action<bool> onComplete = null);

		void SendMultiplePacketsAsync(List<ArchipelagoPacketBase> packets, Action<bool> onComplete = null);

		void SendMultiplePacketsAsync(Action<bool> onComplete = null, params ArchipelagoPacketBase[] packets);
	}
	public interface ILocationCheckHelper
	{
		ReadOnlyCollection<long> AllLocations { get; }

		ReadOnlyCollection<long> AllLocationsChecked { get; }

		ReadOnlyCollection<long> AllMissingLocations { get; }

		event LocationCheckHelper.CheckedLocationsUpdatedHandler CheckedLocationsUpdated;

		void CompleteLocationChecks(params long[] ids);

		void CompleteLocationChecksAsync(Action<bool> onComplete, params long[] ids);

		void ScoutLocationsAsync(Action<Dictionary<long, ScoutedItemInfo>> callback = null, HintCreationPolicy hintCreationPolicy = HintCreationPolicy.None, params long[] ids);

		void ScoutLocationsAsync(Action<Dictionary<long, ScoutedItemInfo>> callback = null, bool createAsHint = false, params long[] ids);

		void ScoutLocationsAsync(Action<Dictionary<long, ScoutedItemInfo>> callback = null, params long[] ids);

		long GetLocationIdFromName(string game, string locationName);

		string GetLocationNameFromId(long locationId, string game = null);
	}
	public class LocationCheckHelper : ILocationCheckHelper
	{
		public delegate void CheckedLocationsUpdatedHandler(ReadOnlyCollection<long> newCheckedLocations);

		private readonly IConcurrentHashSet<long> allLocations = new ConcurrentHashSet<long>();

		private readonly IConcurrentHashSet<long> locationsChecked = new ConcurrentHashSet<long>();

		private readonly IConcurrentHashSet<long> serverConfirmedChecks = new ConcurrentHashSet<long>();

		private ReadOnlyCollection<long> missingLocations = new ReadOnlyCollection<long>(new long[0]);

		private readonly IArchipelagoSocketHelper socket;

		private readonly IItemInfoResolver itemInfoResolver;

		private readonly IConnectionInfoProvider connectionInfoProvider;

		private readonly IPlayerHelper players;

		private bool awaitingLocationInfoPacket;

		private Action<LocationInfoPacket> locationInfoPacketCallback;

		public ReadOnlyCollection<long> AllLocations => allLocations.AsToReadOnlyCollection();

		public ReadOnlyCollection<long> AllLocationsChecked => locationsChecked.AsToReadOnlyCollection();

		public ReadOnlyCollection<long> AllMissingLocations => missingLocations;

		public event CheckedLocationsUpdatedHandler CheckedLocationsUpdated;

		internal LocationCheckHelper(IArchipelagoSocketHelper socket, IItemInfoResolver itemInfoResolver, IConnectionInfoProvider connectionInfoProvider, IPlayerHelper players)
		{
			this.socket = socket;
			this.itemInfoResolver = itemInfoResolver;
			this.connectionInfoProvider = connectionInfoProvider;
			this.players = players;
			socket.PacketReceived += Socket_PacketReceived;
		}

		private void Socket_PacketReceived(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket connectedPacket))
			{
				if (!(packet is RoomUpdatePacket roomUpdatePacket))
				{
					if (!(packet is LocationInfoPacket obj))
					{
						if (packet is InvalidPacketPacket invalidPacketPacket && awaitingLocationInfoPacket && invalidPacketPacket.OriginalCmd == ArchipelagoPacketType.LocationScouts)
						{
							locationInfoPacketCallback(null);
							awaitingLocationInfoPacket = false;
							locationInfoPacketCallback = null;
						}
					}
					else if (awaitingLocationInfoPacket)
					{
						if (locationInfoPacketCallback != null)
						{
							locationInfoPacketCallback(obj);
						}
						awaitingLocationInfoPacket = false;
						locationInfoPacketCallback = null;
					}
				}
				else
				{
					CheckLocations(roomUpdatePacket.CheckedLocations);
					if (roomUpdatePacket.CheckedLocations != null)
					{
						serverConfirmedChecks.UnionWith(roomUpdatePacket.CheckedLocations);
					}
				}
			}
			else
			{
				allLocations.UnionWith(connectedPacket.LocationsChecked);
				allLocations.UnionWith(connectedPacket.MissingChecks);
				serverConfirmedChecks.UnionWith(connectedPacket.LocationsChecked);
				missingLocations = new ReadOnlyCollection<long>(connectedPacket.MissingChecks);
				CheckLocations(connectedPacket.LocationsChecked);
			}
		}

		public void CompleteLocationChecks(params long[] ids)
		{
			CheckLocations(ids);
			LocationChecksPacket locationChecksPacket = GetLocationChecksPacket();
			if (locationChecksPacket.Locations.Any())
			{
				socket.SendPacket(locationChecksPacket);
			}
		}

		public void CompleteLocationChecksAsync(Action<bool> onComplete, params long[] ids)
		{
			CheckLocations(ids);
			if (GetLocationChecksPacket().Locations.Any())
			{
				socket.SendPacketAsync(GetLocationChecksPacket(), onComplete);
			}
		}

		private LocationChecksPacket GetLocationChecksPacket()
		{
			return new LocationChecksPacket
			{
				Locations = locationsChecked.AsToReadOnlyCollectionExcept(serverConfirmedChecks).ToArray()
			};
		}

		public void ScoutLocationsAsync(Action<Dictionary<long, ScoutedItemInfo>> callback = null, HintCreationPolicy hintCreationPolicy = HintCreationPolicy.None, params long[] ids)
		{
			long[] array = ids.Where((long i) => allLocations.Contains(i)).ToArray();
			if (!array.Any())
			{
				callback?.Invoke(new Dictionary<long, ScoutedItemInfo>());
				return;
			}
			socket.SendPacketAsync(new LocationScoutsPacket
			{
				Locations = array,
				CreateAsHint = (int)hintCreationPolicy
			});
			awaitingLocationInfoPacket = true;
			locationInfoPacketCallback = delegate(LocationInfoPacket scoutResult)
			{
				Dictionary<long, ScoutedItemInfo> obj = scoutResult.Locations.ToDictionary((NetworkItem item) => item.Location, delegate(NetworkItem item)
				{
					PlayerInfo playerInfo = players.GetPlayerInfo(item.Player) ?? new PlayerInfo();
					return new ScoutedItemInfo(item, playerInfo.Game, connectionInfoProvider.Game, itemInfoResolver, players, playerInfo);
				});
				callback?.Invoke(obj);
			};
		}

		public void ScoutLocationsAsync(Action<Dictionary<long, ScoutedItemInfo>> callback = null, bool createAsHint = false, params long[] ids)
		{
			ScoutLocationsAsync(callback, createAsHint ? HintCreationPolicy.CreateAndAnnounce : HintCreationPolicy.None, ids);
		}

		public void ScoutLocationsAsync(Action<Dictionary<long, ScoutedItemInfo>> callback = null, params long[] ids)
		{
			ScoutLocationsAsync(callback, createAsHint: false, ids);
		}

		public long GetLocationIdFromName(string game, string locationName)
		{
			return itemInfoResolver.Ge

GungeonArchipelago.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using Alexandria.ItemAPI;
using Archipelago.MultiClient.Net;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.MessageLog.Messages;
using Archipelago.MultiClient.Net.Models;
using Archipelago.MultiClient.Net.Packets;
using BepInEx;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json.Linq;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Mod")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Mod")]
[assembly: AssemblyCopyright("Copyright ©  2020")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("d6d7a494-722e-4763-959b-c2d6b6a42b01")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 GungeonArchipelago
{
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("kintheinfinite.etg.archipelago", "Archipelago", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Action<Chest, PlayerController> <0>__OnChestOpen;

			public static Action<PlayerController, HealthHaver> <1>__OnEnemyKill;

			public static PacketReceivedHandler <2>__OnPacketReceived;

			public static MessageReceivedHandler <3>__OnMessageReceived;

			public static ItemReceivedHandler <4>__OnItemReceived;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Action<string[]> <>9__12_0;

			public static Action<string[]> <>9__12_1;

			public static Action<string[]> <>9__12_2;

			public static DataStorageUpdatedHandler <>9__18_0;

			internal void <GMStart>b__12_0(string[] args)
			{
				ArchipelagoConnect(args[0], args[1], args[2]);
			}

			internal void <GMStart>b__12_1(string[] args)
			{
				ArchipelagoItems();
			}

			internal void <GMStart>b__12_2(string[] args)
			{
				ArchipelagoChat(args[0]);
			}

			internal void <ArchipelagoConnect>b__18_0(JToken old_value, JToken new_value, Dictionary<string, JToken> _)
			{
				for (int i = 0; i < new_value.ToObject<int>(); i++)
				{
					session.Locations.CompleteLocationChecks(new long[1] { chest_starting_id + i });
				}
			}
		}

		public const string GUID = "kintheinfinite.etg.archipelago";

		public const string NAME = "Archipelago";

		public const string VERSION = "1.0.0";

		public const string TEXT_COLOR = "#FFFFFF";

		public static ArchipelagoSession session;

		private static int chest_starting_id = 8755000;

		public static Dictionary<string, int> slot_data = new Dictionary<string, int>();

		public static List<long> items_received = new List<long>();

		public static Random random = new Random();

		public static PassiveItem archipelago_pickup;

		public static bool allow_traps = false;

		public void Start()
		{
			ETGModMainBehaviour.WaitForGameManagerStart((Action<GameManager>)GMStart);
		}

		public void GMStart(GameManager g)
		{
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Expected O, but got Unknown
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			ETGModConsole.CommandDescriptions.Add("archipelago connect", "Input the ip, port, and player name seperated by spaces");
			ETGModConsole.CommandDescriptions.Add("archipelago items", "Use when you are starting a new run but are already connected to spawn your items in");
			ETGModConsole.CommandDescriptions.Add("archipelago chat", "Sends a chat message to your connected server");
			ETGModConsole.Commands.AddGroup("archipelago");
			ETGModConsole.Commands.GetGroup("archipelago").AddGroup("connect", (Action<string[]>)delegate(string[] args)
			{
				ArchipelagoConnect(args[0], args[1], args[2]);
			});
			ETGModConsole.Commands.GetGroup("archipelago").AddGroup("items", (Action<string[]>)delegate
			{
				ArchipelagoItems();
			});
			ETGModConsole.Commands.GetGroup("archipelago").AddGroup("chat", (Action<string[]>)delegate(string[] args)
			{
				ArchipelagoChat(args[0]);
			});
			Chest.OnPostOpen = (Action<Chest, PlayerController>)Delegate.Combine(Chest.OnPostOpen, new Action<Chest, PlayerController>(OnChestOpen));
			string text = "Archipelago Item";
			string text2 = "GungeonArchipelago/Resources/archipelago_sprite";
			GameObject val = new GameObject(text);
			archipelago_pickup = val.AddComponent<PassiveItem>();
			ItemBuilder.AddSpriteToObject(text, text2, val, (Assembly)null);
			ItemBuilder.SetupItem((PickupObject)(object)archipelago_pickup, "An archipelago item.", "An archipelago item\n\nA randomized item received from the archipelago server", "Archipelago");
			((PickupObject)archipelago_pickup).quality = (ItemQuality)(-100);
			Log("Archipelago v1.0.0 started successfully.");
		}

		public void Update()
		{
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0339: Unknown result type (might be due to invalid IL or missing references)
			//IL_033e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0343: Unknown result type (might be due to invalid IL or missing references)
			//IL_0383: Unknown result type (might be due to invalid IL or missing references)
			//IL_0388: Unknown result type (might be due to invalid IL or missing references)
			//IL_038d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0417: Unknown result type (might be due to invalid IL or missing references)
			//IL_041c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0421: Unknown result type (might be due to invalid IL or missing references)
			PlayerController player = GameManager.Instance.m_player;
			if ((Object)(object)player != (Object)null)
			{
				player.OnKilledEnemyContext -= OnEnemyKill;
				player.OnKilledEnemyContext += OnEnemyKill;
			}
			if (items_received.Count == 0)
			{
				return;
			}
			long num = items_received[0];
			items_received.RemoveAt(0);
			long num2 = num;
			long num3 = num2;
			long num4 = num3 - 8754000;
			if ((ulong)num4 <= 12uL)
			{
				switch (num4)
				{
				case 0L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomGunOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)1 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 1L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomGunOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)2 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 2L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomGunOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)3 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 3L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomGunOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)4 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 4L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomGunOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)5 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 5L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomPassiveOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)1 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 6L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomPassiveOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)2 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 7L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomPassiveOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)3 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 8L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomPassiveOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)4 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 9L:
					LootEngine.SpawnItem(((Component)PickupObjectDatabase.GetRandomPassiveOfQualities(random, new List<int>(), (ItemQuality[])(object)new ItemQuality[1] { (ItemQuality)5 })).gameObject, Vector2.op_Implicit(((GameActor)GameManager.Instance.PrimaryPlayer).CenterPosition), Vector2.down, 0f, true, false, false);
					return;
				case 10L:
					ETGModConsole.SpawnItem(new string[2] { "gnawed_key", "1" });
					return;
				case 11L:
					ETGModConsole.SpawnItem(new string[2] { "old_crest", "1" });
					return;
				case 12L:
					ETGModConsole.SpawnItem(new string[2] { "weird_egg", "1" });
					return;
				}
			}
			long num5 = num3 - 8754100;
			if ((ulong)num5 <= 6uL)
			{
				switch (num5)
				{
				case 0L:
					ETGModConsole.Spawn(new string[2] { "chance_kin", "10" });
					return;
				case 1L:
					ETGModConsole.SpawnItem(new string[2] { "50_casing", "1" });
					return;
				case 2L:
					ETGModConsole.SpawnItem(new string[2] { "key", "1" });
					return;
				case 3L:
					ETGModConsole.SpawnItem(new string[2] { "blank", "1" });
					return;
				case 4L:
					ETGModConsole.SpawnItem(new string[2] { "armor", "1" });
					return;
				case 5L:
					ETGModConsole.SpawnItem(new string[2] { "heart", "1" });
					return;
				case 6L:
					ETGModConsole.SpawnItem(new string[2] { "ammo", "1" });
					return;
				}
			}
			long num6 = num3 - 8754200;
			if ((ulong)num6 <= 7uL)
			{
				switch (num6)
				{
				case 0L:
					ETGModConsole.Spawn(new string[2] { "rat", "100" });
					break;
				case 1L:
					ETGModConsole.Spawn(new string[2] { "shelleton", "3" });
					break;
				case 2L:
					ETGModConsole.Spawn(new string[2] { "shotgrub", "3" });
					break;
				case 3L:
					ETGModConsole.Spawn(new string[2] { "tanker", "12" });
					ETGModConsole.Spawn(new string[2] { "professional", "2" });
					break;
				case 4L:
					ETGModConsole.Spawn(new string[2] { "hollowpoint", "6" });
					ETGModConsole.Spawn(new string[2] { "bombshee", "2" });
					ETGModConsole.Spawn(new string[2] { "gunreaper", "1" });
					break;
				case 5L:
					ETGModConsole.Spawn(new string[2] { "gun_nut", "1" });
					ETGModConsole.Spawn(new string[2] { "chain_gunner", "2" });
					ETGModConsole.Spawn(new string[2] { "spectral_gun_nut", "3" });
					break;
				case 6L:
					ETGModConsole.Spawn(new string[2] { "jamerlengo", "3" });
					ETGModConsole.Spawn(new string[2] { "spirat", "15" });
					break;
				case 7L:
					GameManager.Instance.Dungeon.SpawnCurseReaper();
					break;
				}
			}
		}

		public static void OnEnemyKill(PlayerController player, HealthHaver enemy)
		{
			if (session != null && session.Socket.Connected)
			{
				if (((Object)enemy).name.Equals("Blobulord(Clone)"))
				{
					session.DataStorage[(Scope)3, "Blobulord Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
				if (((Object)enemy).name.Equals("OldBulletKing(Clone)"))
				{
					session.DataStorage[(Scope)3, "Old King Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
				if (((Object)enemy).name.Equals("MetalGearRat(Clone)"))
				{
					session.DataStorage[(Scope)3, "Resourceful Rat Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
				if (((Object)enemy).name.Equals("Helicopter(Clone)"))
				{
					session.DataStorage[(Scope)3, "Agunim Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
				if (((Object)enemy).name.Equals("AdvancedDraGun(Clone)"))
				{
					session.DataStorage[(Scope)3, "Dragun Killed"] = DataStorageElement.op_Implicit(1);
					session.DataStorage[(Scope)3, "Advanced Dragun Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
				if (((Object)enemy).name.Equals("DraGun(Clone)"))
				{
					session.DataStorage[(Scope)3, "Dragun Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
				if (((Object)enemy).name.Equals("Infinilich(Clone)"))
				{
					session.DataStorage[(Scope)3, "Lich Killed"] = DataStorageElement.op_Implicit(1);
					CheckCompletion();
				}
			}
		}

		public static void CheckCompletion()
		{
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Expected O, but got Unknown
			if ((slot_data["Blobulord Goal"] != 1 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Blobulord Killed"])) && (slot_data["Old King Goal"] != 1 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Old King Killed"])) && (slot_data["Resourceful Rat Goal"] != 1 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Resourceful Rat Killed"])) && (slot_data["Agunim Goal"] != 1 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Agunim Killed"])) && (slot_data["Advanced Dragun Goal"] != 1 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Advanced Dragun Killed"])) && (slot_data["Goal"] != 0 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Dragun Killed"])) && (slot_data["Goal"] != 1 || DataStorageElement.op_Implicit(session.DataStorage[(Scope)3, "Lich Killed"])))
			{
				StatusUpdatePacket val = new StatusUpdatePacket();
				val.Status = (ArchipelagoClientState)30;
				session.Socket.SendPacket((ArchipelagoPacketBase)(object)val);
			}
		}

		public static void Log(string text, string color = "#FFFFFF")
		{
			ETGModConsole.Log((object)("<color=" + color + ">" + text + "</color>"), false);
		}

		public static void OnChestOpen(Chest chest, PlayerController controller)
		{
			if (session != null && session.Socket.Connected)
			{
				chest.contents.Clear();
				chest.ExplodeInSadness();
				IDataStorageHelper dataStorage = session.DataStorage;
				dataStorage[(Scope)3, "ChestsOpened"] = dataStorage[(Scope)3, "ChestsOpened"] + 1;
			}
		}

		public static void ArchipelagoConnect(string ip, string port, string name)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Expected O, but got Unknown
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Expected O, but got Unknown
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b1: Expected O, but got Unknown
			if (session != null && session.Socket.Connected)
			{
				Log("You are already connected!");
				return;
			}
			Log("Connecting To: " + ip + ":" + port + " as " + name);
			allow_traps = false;
			session = ArchipelagoSessionFactory.CreateSession(ip, int.Parse(port));
			IArchipelagoSocketHelper socket = session.Socket;
			object obj = <>O.<2>__OnPacketReceived;
			if (obj == null)
			{
				PacketReceivedHandler val = OnPacketReceived;
				<>O.<2>__OnPacketReceived = val;
				obj = (object)val;
			}
			socket.PacketReceived += (PacketReceivedHandler)obj;
			IMessageLogHelper messageLog = session.MessageLog;
			object obj2 = <>O.<3>__OnMessageReceived;
			if (obj2 == null)
			{
				MessageReceivedHandler val2 = OnMessageReceived;
				<>O.<3>__OnMessageReceived = val2;
				obj2 = (object)val2;
			}
			messageLog.OnMessageReceived += (MessageReceivedHandler)obj2;
			IReceivedItemsHelper items = session.Items;
			object obj3 = <>O.<4>__OnItemReceived;
			if (obj3 == null)
			{
				ItemReceivedHandler val3 = OnItemReceived;
				<>O.<4>__OnItemReceived = val3;
				obj3 = (object)val3;
			}
			items.ItemReceived += (ItemReceivedHandler)obj3;
			LoginResult val4 = session.TryConnectAndLogin("Enter The Gungeon", name, (ItemsHandlingFlags)3, new Version(1, 0, 0), (string[])null, (string)null, (string)null, true);
			if (!val4.Successful)
			{
				LoginFailure val5 = (LoginFailure)val4;
				string text = "Failed to Connect to " + ip + ":" + port + " as " + name;
				string[] errors = val5.Errors;
				foreach (string text2 in errors)
				{
					text = text + "\n    " + text2;
				}
				ConnectionRefusedError[] errorCodes = val5.ErrorCodes;
				foreach (ConnectionRefusedError val6 in errorCodes)
				{
					text += $"\n    {val6}";
				}
				Log(text);
				return;
			}
			LoginSuccessful val7 = (LoginSuccessful)val4;
			foreach (string key in val7.SlotData.Keys)
			{
				int value = int.Parse(val7.SlotData[key].ToString());
				slot_data[key] = value;
			}
			Log("Connected to Archipelago server.");
			allow_traps = true;
			session.DataStorage[(Scope)3, "ChestsOpened"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Blobulord Killed"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Old King Killed"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Resourceful Rat Killed"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Agunim Killed"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Dragun Killed"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Advanced Dragun Killed"].Initialize(JToken.op_Implicit(0));
			session.DataStorage[(Scope)3, "Lich Killed"].Initialize(JToken.op_Implicit(0));
			DataStorageElement obj4 = session.DataStorage[(Scope)3, "ChestsOpened"];
			object obj5 = <>c.<>9__18_0;
			if (obj5 == null)
			{
				DataStorageUpdatedHandler val8 = delegate(JToken old_value, JToken new_value, Dictionary<string, JToken> _)
				{
					for (int k = 0; k < new_value.ToObject<int>(); k++)
					{
						session.Locations.CompleteLocationChecks(new long[1] { chest_starting_id + k });
					}
				};
				<>c.<>9__18_0 = val8;
				obj5 = (object)val8;
			}
			obj4.OnValueChanged += (DataStorageUpdatedHandler)obj5;
		}

		public static void ArchipelagoItems()
		{
			if (session == null || !session.Socket.Connected)
			{
				Log("You are not connected!");
				return;
			}
			foreach (ItemInfo item in session.Items.AllItemsReceived)
			{
				if (item.ItemId < 8754200)
				{
					items_received.Add(item.ItemId);
				}
			}
		}

		public static void OnItemReceived(ReceivedItemsHelper helper)
		{
			ItemInfo val = helper.PeekItem();
			if (allow_traps || (!allow_traps && val.ItemId < 8754200))
			{
				items_received.Add(val.ItemId);
			}
			helper.DequeueItem();
		}

		public static void ArchipelagoPickupNotification(string title, string text)
		{
			GameUIRoot.Instance.notificationController.DoCustomNotification(title, text, ((BraveBehaviour)archipelago_pickup).sprite.collection, ((BraveBehaviour)archipelago_pickup).sprite.spriteId, (NotificationColor)0, false, false);
		}

		public static void ArchipelagoChat(string message)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			session.Socket.SendPacketAsync((ArchipelagoPacketBase)new SayPacket
			{
				Text = message
			}, (Action<bool>)null);
		}

		public static void OnMessageReceived(LogMessage message)
		{
			Log(((object)message).ToString());
		}

		public static void OnPacketReceived(ArchipelagoPacketBase packet)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			if (!(packet is ItemPrintJsonPacket))
			{
				return;
			}
			ItemPrintJsonPacket val = (ItemPrintJsonPacket)packet;
			NetworkItem item;
			if (val.ReceivingPlayer != session.ConnectionInfo.Slot)
			{
				ReadOnlyCollection<long> allLocations = session.Locations.AllLocations;
				item = val.Item;
				if (!allLocations.Contains(((NetworkItem)(ref item)).Location))
				{
					return;
				}
			}
			ReadOnlyCollection<PlayerInfo> readOnlyCollection = session.Players.Players[session.ConnectionInfo.Team];
			item = val.Item;
			string game = readOnlyCollection[((NetworkItem)(ref item)).Player].Game;
			string game2 = session.Players.Players[session.ConnectionInfo.Team][val.ReceivingPlayer].Game;
			string playerName = session.Players.GetPlayerName(val.ReceivingPlayer);
			IReceivedItemsHelper items = session.Items;
			item = val.Item;
			string title = playerName + " got " + items.GetItemName(((NetworkItem)(ref item)).Item, game2);
			ILocationCheckHelper locations = session.Locations;
			item = val.Item;
			ArchipelagoPickupNotification(title, "from " + locations.GetLocationNameFromId(((NetworkItem)(ref item)).Location, game));
		}
	}
}

websocket-sharp.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using WebSocketSharp.Net;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace WebSocketSharp
{
	internal class DllDirectory : IDisposable
	{
		private static object _lock = new object();

		private bool _disposed = false;

		private static string _oldDllDirectory = null;

		private DllDirectory()
		{
		}

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern int GetDllDirectory(int nBufferLength, StringBuilder lpPathName);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool SetDllDirectory(string lpPathName);

		private static void set(string newDllDirectory)
		{
			if (_oldDllDirectory != null)
			{
				throw new InvalidOperationException("Please reset dll directory before setting it again!");
			}
			StringBuilder stringBuilder = new StringBuilder(10240);
			int dllDirectory = GetDllDirectory(10240, stringBuilder);
			if (dllDirectory > 10240)
			{
				throw new Exception("Could not SetDllDirectory");
			}
			string oldDllDirectory = stringBuilder.ToString(0, dllDirectory);
			SetDllDirectory(newDllDirectory);
			_oldDllDirectory = oldDllDirectory;
		}

		private static void reset()
		{
			if (_oldDllDirectory != null)
			{
				SetDllDirectory(_oldDllDirectory);
				_oldDllDirectory = null;
			}
		}

		internal static void Set(string newDllDirectory)
		{
			lock (_lock)
			{
				set(newDllDirectory);
			}
		}

		internal static void Reset()
		{
			lock (_lock)
			{
				reset();
			}
		}

		internal static DllDirectory Context(string newDllDirectory)
		{
			Monitor.Enter(_lock);
			try
			{
				set(newDllDirectory);
				return new DllDirectory();
			}
			catch (Exception ex)
			{
				Monitor.Exit(_lock);
				throw ex;
			}
		}

		public void Dispose()
		{
			Dispose(disposing: true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				try
				{
					_disposed = true;
					reset();
				}
				finally
				{
					Monitor.Exit(_lock);
				}
			}
		}

		~DllDirectory()
		{
			Dispose(disposing: false);
		}
	}
	public class MessageEventArgs : EventArgs
	{
		private byte[] _data;

		private OpCode _opcode;

		private string _str;

		public bool IsBinary => _opcode == OpCode.Binary;

		public bool IsPing => _opcode == OpCode.Ping;

		public bool IsText => _opcode == OpCode.Text;

		public byte[] RawData => _data;

		public string Data
		{
			get
			{
				if (_str == null)
				{
					_str = Encoding.UTF8.GetString(_data);
				}
				return _str;
			}
		}

		internal MessageEventArgs(byte[] data, OpCode opcode)
		{
			_data = data;
			_opcode = opcode;
			_str = null;
		}
	}
	public class CloseEventArgs : EventArgs
	{
		private ushort _code;

		private string _reason;

		public ushort Code => _code;

		public string Reason => _reason;

		public bool WasClean => _code >= 1000 && _code != 1005;

		internal CloseEventArgs(ushort code, string reason)
		{
			_code = code;
			_reason = reason;
		}
	}
	public class ErrorEventArgs : EventArgs
	{
		private string _message;

		private Exception _exception;

		public string Message => _message;

		public Exception Exception => _exception;

		internal ErrorEventArgs(string message, Exception exception)
		{
			_message = message;
			_exception = exception;
		}
	}
	public static class Ext
	{
		public static bool IsNullOrEmpty(this string value)
		{
			return value == null || value.Length == 0;
		}
	}
}
namespace WebSocketSharp.Net
{
	public class ClientSslConfiguration
	{
		public SslProtocols EnabledSslProtocols
		{
			get
			{
				return SslProtocols.None;
			}
			set
			{
			}
		}
	}
}
namespace WebSocketSharp
{
	internal enum OpCode
	{
		Text = 1,
		Binary = 2,
		Ping = 8,
		Pong = 10
	}
	public class WebSocket : IDisposable
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void OnMessageCallback(IntPtr data, ulong len, int opCode);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void OnOpenCallback();

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void OnCloseCallback();

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void OnErrorCallback(IntPtr msg);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void OnPongCallback(IntPtr data, ulong len);

		private Uri uri;

		private WebSocketWorker worker = null;

		private WebSocketEventDispatcher dispatcher = null;

		private object dispatcherLock = new object();

		private List<byte[]> pings = new List<byte[]>();

		private DateTime lastPong;

		private volatile WebSocketState readyState = WebSocketState.New;

		private string lastError;

		private int _id;

		private static object _lastIdLock = new object();

		private static int _lastId = 0;

		private UIntPtr ws;

		private OnMessageCallback messageHandler;

		private OnOpenCallback openHandler;

		private OnCloseCallback closeHandler;

		private OnErrorCallback errorHandler;

		private OnPongCallback pongHandler;

		internal const CallingConvention CALLING_CONVENTION = CallingConvention.Cdecl;

		internal const string DLL_NAME = "c-wspp.dll";

		public WebSocketState ReadyState => readyState;

		public bool IsAlive
		{
			get
			{
				if (readyState != WebSocketState.Open)
				{
					return false;
				}
				if (DateTime.UtcNow - lastPong < new TimeSpan(0, 0, 0, 0, 300))
				{
					return true;
				}
				Random random = new Random();
				byte[] array = new byte[16];
				random.NextBytes(array);
				try
				{
					pingBlocking(array);
					return true;
				}
				catch (InvalidOperationException ex)
				{
					Console.WriteLine(ex.ToString());
					return true;
				}
				catch (TimeoutException)
				{
				}
				catch (Exception ex3)
				{
					Console.WriteLine(ex3.ToString());
				}
				return false;
			}
		}

		public bool IsSecure => uri.Scheme.ToLower() == "wss";

		public Uri Url => uri;

		public ClientSslConfiguration SslConfiguration => new ClientSslConfiguration();

		internal static string directory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

		public event EventHandler OnOpen;

		public event EventHandler<CloseEventArgs> OnClose;

		public event EventHandler<ErrorEventArgs> OnError;

		public event EventHandler<MessageEventArgs> OnMessage;

		public WebSocket(string uriString)
		{
			lock (_lastIdLock)
			{
				_id = _lastId + 1;
				_lastId = _id;
			}
			debug("new (\"" + uriString + "\")");
			uri = new Uri(uriString);
			ws = wspp_new_from(uriString, directory);
			setHandlers();
		}

		public WebSocket(string uriString, string[] protocols)
		{
			lock (_lastIdLock)
			{
				_id = _lastId + 1;
				_lastId = _id;
			}
			debug("new (\"" + uriString + "\", " + ((protocols != null) ? ("[" + string.Join(", ", protocols) + "]") : "null") + ")");
			uri = new Uri(uriString);
			ws = wspp_new_from(uriString, directory);
			setHandlers();
		}

		private void error(string message, Exception exception = null)
		{
			debug("Error: " + message);
			if (exception == null)
			{
				exception = new Exception(message);
			}
			ErrorEventArgs e = new ErrorEventArgs(message, exception);
			dispatcher.Enqueue(e);
		}

		public void Dispose()
		{
			Dispose(disposing: true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			debug("disposing");
			if (!(ws != UIntPtr.Zero))
			{
				return;
			}
			clearHandlers();
			UIntPtr uIntPtr = ws;
			ws = UIntPtr.Zero;
			debug("shutting down");
			close(uIntPtr, 1001, "Going away");
			try
			{
				if (worker != null)
				{
					worker.Dispose();
				}
			}
			catch (Exception)
			{
			}
			worker = null;
			try
			{
				if (dispatcher != null)
				{
					dispatcher.Dispose();
				}
			}
			catch (InvalidOperationException)
			{
				WebSocketEventDispatcher tmp = dispatcher;
				new Thread((ThreadStart)delegate
				{
					try
					{
						tmp.Dispose();
					}
					catch (Exception)
					{
					}
				}).Start();
			}
			catch (Exception)
			{
			}
			dispatcher = null;
			debug("wspp_delete");
			wspp_delete(uIntPtr);
			openHandler = null;
			closeHandler = null;
			messageHandler = null;
			errorHandler = null;
			pongHandler = null;
			dispatcherLock = null;
		}

		~WebSocket()
		{
			Dispose(disposing: false);
		}

		private void debug(string msg)
		{
		}

		private static void sdebug(string msg)
		{
		}

		private void warn(string msg)
		{
			Console.WriteLine("WARNING: WebSocket " + _id + ": " + msg);
		}

		private void pingBlocking(byte[] data, int timeout = 15000)
		{
			if (worker.IsCurrentThread)
			{
				throw new InvalidOperationException("Can't wait for reply from worker thread");
			}
			lock (pings)
			{
				pings.Add(data);
			}
			Ping(data);
			for (int i = 0; i < timeout; i++)
			{
				Thread.Sleep(1);
				lock (pings)
				{
					bool flag = true;
					foreach (byte[] ping in pings)
					{
						if (ping == data)
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						return;
					}
				}
			}
			lock (pings)
			{
				pings.Remove(data);
			}
			debug("pong timeout");
			throw new TimeoutException();
		}

		public void Connect()
		{
			lastError = "";
			connect();
			while (worker != null && worker.IsAlive && readyState != WebSocketState.Open)
			{
				Thread.Sleep(1);
			}
			if (readyState != WebSocketState.Open)
			{
				throw new Exception("Connect failed" + ((!(lastError == "")) ? (": " + lastError) : ""));
			}
		}

		public void ConnectAsync()
		{
			lastError = "";
			connect();
			if (readyState != WebSocketState.Open && readyState != WebSocketState.Connecting)
			{
				throw new Exception("Connect failed" + ((!(lastError == "")) ? (": " + lastError) : ""));
			}
		}

		private void connect()
		{
			if (readyState != WebSocketState.Closed && readyState != 0)
			{
				throw new InvalidOperationException("Invalid state: " + readyState);
			}
			if (readyState != 0)
			{
				Thread.Sleep(1);
			}
			debug("ReadyState = Connecting");
			readyState = WebSocketState.Connecting;
			if (ws == UIntPtr.Zero)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
			if (worker != null && !worker.IsAlive)
			{
				worker = null;
			}
			lock (dispatcherLock)
			{
				if (dispatcher == null)
				{
					debug("creating dispatcher");
					dispatcher = new WebSocketEventDispatcher();
					dispatcher.OnOpen += dispatchOnOpen;
					dispatcher.OnClose += dispatchOnClose;
					dispatcher.OnError += dispatchOnError;
					dispatcher.OnMessage += dispatchOnMessage;
					dispatcher.Start();
				}
			}
			debug("wspp_connect");
			wspp_connect(ws);
			if (worker == null)
			{
				debug("creating worker");
				worker = new WebSocketWorker(ws);
				worker.Start();
			}
			else
			{
				debug("worker already running");
			}
		}

		public void Close()
		{
			Close(1001);
		}

		public void Close(ushort code)
		{
			Close(code, "");
		}

		public void Close(ushort code, string reason)
		{
			debug("Close(" + code + ", \"" + reason + "\")");
			if (ws == UIntPtr.Zero)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
			debug("ReadyState = Closing");
			readyState = WebSocketState.Closing;
			close(ws, code, reason);
			if (worker.IsCurrentThread)
			{
				throw new InvalidOperationException("Can't wait for reply from worker thread");
			}
			while (worker != null && worker.IsAlive && readyState != WebSocketState.Closed)
			{
				Thread.Sleep(1);
			}
		}

		public void CloseAsync()
		{
			CloseAsync(1001);
		}

		public void CloseAsync(ushort code)
		{
			CloseAsync(code, "");
		}

		public void CloseAsync(ushort code, string reason)
		{
			debug("CloseAsync(" + code + ", \"" + reason + "\")");
			if (ws == UIntPtr.Zero)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
			debug("ReadyState = Closing");
			readyState = WebSocketState.Closing;
			close(ws, code, reason);
		}

		public void Send(string message)
		{
			if (ws == UIntPtr.Zero)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
			IntPtr intPtr = StringToHGlobalUTF8(message);
			WsppRes wsppRes = (WsppRes)wspp_send_text(ws, intPtr);
			Marshal.FreeHGlobal(intPtr);
			if (wsppRes != 0)
			{
				throw new Exception(Enum.GetName(typeof(WsppRes), wsppRes) ?? "Unknown error");
			}
		}

		public void Send(byte[] data)
		{
			if (ws == UIntPtr.Zero)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
			WsppRes wsppRes = (WsppRes)wspp_send_binary(ws, data, (ulong)data.Length);
			if (wsppRes != 0)
			{
				throw new Exception(Enum.GetName(typeof(WsppRes), wsppRes) ?? "Unknown error");
			}
		}

		public void SendAsync(string message, Action<bool> onComplete = null)
		{
			Send(message);
			onComplete?.Invoke(obj: true);
		}

		public void SendAsync(byte[] data, Action<bool> onComplete = null)
		{
			Send(data);
			onComplete?.Invoke(obj: true);
		}

		public void Ping(byte[] data)
		{
			if (ws == UIntPtr.Zero)
			{
				throw new ObjectDisposedException(GetType().FullName);
			}
			WsppRes wsppRes = (WsppRes)wspp_ping(ws, data, (ulong)data.Length);
			if (wsppRes != 0)
			{
				throw new Exception(Enum.GetName(typeof(WsppRes), wsppRes) ?? "Unknown error");
			}
		}

		private void dispatchOnOpen(object sender, EventArgs e)
		{
			if (this.OnOpen != null)
			{
				this.OnOpen(this, e);
			}
		}

		private void dispatchOnClose(object sender, CloseEventArgs e)
		{
			lock (dispatcherLock)
			{
				WebSocketEventDispatcher tmp = dispatcher;
				if (tmp != null)
				{
					dispatcher = null;
					new Thread((ThreadStart)delegate
					{
						tmp.Dispose();
					}).Start();
				}
				else
				{
					warn("duplicate close event");
				}
			}
			if (this.OnError != null)
			{
				new Thread((ThreadStart)delegate
				{
					Thread.Sleep(1);
					this.OnClose(this, e);
				}).Start();
			}
		}

		private void dispatchOnError(object sender, ErrorEventArgs e)
		{
			if (readyState == WebSocketState.Closed)
			{
				lock (dispatcherLock)
				{
					WebSocketEventDispatcher tmp = dispatcher;
					if (tmp != null)
					{
						dispatcher = null;
						new Thread((ThreadStart)delegate
						{
							tmp.Dispose();
						}).Start();
					}
					else
					{
						warn("duplicate close event");
					}
				}
			}
			if (readyState == WebSocketState.Closed)
			{
				if (this.OnError != null)
				{
					new Thread((ThreadStart)delegate
					{
						Thread.Sleep(1);
						this.OnError(this, e);
					}).Start();
				}
			}
			else if (this.OnError != null)
			{
				this.OnError(this, e);
			}
		}

		private void dispatchOnMessage(object sender, MessageEventArgs e)
		{
			if (this.OnMessage != null)
			{
				this.OnMessage(this, e);
			}
		}

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
		internal static extern UIntPtr wspp_new(IntPtr uri);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void wspp_delete(UIntPtr ws);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern ulong wspp_poll(UIntPtr ws);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern ulong wspp_run(UIntPtr ws);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern bool wspp_stopped(UIntPtr ws);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern int wspp_connect(UIntPtr ws);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern int wspp_close(UIntPtr ws, ushort code, IntPtr reason);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern int wspp_send_text(UIntPtr ws, IntPtr message);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern int wspp_send_binary(UIntPtr ws, byte[] data, ulong len);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern int wspp_ping(UIntPtr ws, byte[] data, ulong len);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void wspp_set_open_handler(UIntPtr ws, OnOpenCallback f);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void wspp_set_close_handler(UIntPtr ws, OnCloseCallback f);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void wspp_set_message_handler(UIntPtr ws, OnMessageCallback f);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void wspp_set_error_handler(UIntPtr ws, OnErrorCallback f);

		[DllImport("c-wspp.dll", CallingConvention = CallingConvention.Cdecl)]
		internal static extern void wspp_set_pong_handler(UIntPtr ws, OnPongCallback f);

		internal static IntPtr StringToHGlobalUTF8(string s, out int length)
		{
			if (s == null)
			{
				length = 0;
				return IntPtr.Zero;
			}
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			IntPtr intPtr = Marshal.AllocHGlobal(bytes.Length + 1);
			Marshal.Copy(bytes, 0, intPtr, bytes.Length);
			Marshal.WriteByte(intPtr, bytes.Length, 0);
			length = bytes.Length;
			return intPtr;
		}

		internal static IntPtr StringToHGlobalUTF8(string s)
		{
			int length;
			return StringToHGlobalUTF8(s, out length);
		}

		internal bool sequenceEqual(byte[] a, byte[] b)
		{
			if (a.Length != b.Length)
			{
				return false;
			}
			for (int i = 0; i < a.Length; i++)
			{
				if (a[i] != b[i])
				{
					return false;
				}
			}
			return true;
		}

		private void OpenHandler()
		{
			debug("on Open");
			if (!(ws == UIntPtr.Zero))
			{
				debug("ReadyState = Open");
				readyState = WebSocketState.Open;
				EventArgs e = new EventArgs();
				dispatcher.Enqueue(e);
			}
		}

		private void CloseHandler()
		{
			debug("on Close");
			if (!(ws == UIntPtr.Zero))
			{
				debug("ReadyState = Closed");
				readyState = WebSocketState.Closed;
				CloseEventArgs e = new CloseEventArgs(0, "");
				dispatcher.Enqueue(e);
			}
		}

		private void MessageHandler(IntPtr data, ulong len, int opCode)
		{
			debug("on Message");
			if (!(ws == UIntPtr.Zero))
			{
				if (len > int.MaxValue)
				{
					error("Received message that was too long");
					return;
				}
				byte[] array = new byte[(uint)len];
				Marshal.Copy(data, array, 0, (int)len);
				MessageEventArgs e = new MessageEventArgs(array, (OpCode)opCode);
				dispatcher.Enqueue(e);
			}
		}

		private void ErrorHandler(IntPtr msgPtr)
		{
			debug("on Error");
			if (!(ws == UIntPtr.Zero))
			{
				string text = "Unknown";
				if (msgPtr != IntPtr.Zero)
				{
					text = Marshal.PtrToStringAnsi(msgPtr);
				}
				if (readyState == WebSocketState.Connecting)
				{
					debug("ReadyState = Closed");
					readyState = WebSocketState.Closed;
				}
				else if (readyState == WebSocketState.Open)
				{
					Close();
				}
				lastError = text;
				error("Connect error: " + text);
			}
		}

		private void PongHandler(IntPtr data, ulong len)
		{
			byte[] array = new byte[(uint)len];
			Marshal.Copy(data, array, 0, (int)len);
			lock (pings)
			{
				foreach (byte[] ping in pings)
				{
					if (sequenceEqual(array, ping))
					{
						pings.Remove(ping);
						lastPong = DateTime.UtcNow;
						return;
					}
				}
			}
			MessageEventArgs e = new MessageEventArgs(array, OpCode.Pong);
			dispatcher.Enqueue(e);
		}

		private static UIntPtr wspp_new(string uriString)
		{
			IntPtr hglobal = StringToHGlobalUTF8(uriString);
			try
			{
				return wspp_new(hglobal);
			}
			finally
			{
				Marshal.FreeHGlobal(hglobal);
			}
		}

		private static UIntPtr wspp_new_from(string uriString, string dllDirectory)
		{
			sdebug("wspp_new(\"" + uriString + "\") in c-wspp.dll from " + dllDirectory);
			using (DllDirectory.Context(dllDirectory))
			{
				return wspp_new(uriString);
			}
		}

		private static void close(UIntPtr ws, ushort code, string reason)
		{
			sdebug("wspp_close(" + code + ", \"" + reason + "')");
			IntPtr intPtr = StringToHGlobalUTF8(reason);
			wspp_close(ws, code, intPtr);
			Marshal.FreeHGlobal(intPtr);
		}

		private void setHandlers()
		{
			openHandler = OpenHandler;
			closeHandler = CloseHandler;
			messageHandler = MessageHandler;
			errorHandler = ErrorHandler;
			pongHandler = PongHandler;
			wspp_set_open_handler(ws, openHandler);
			wspp_set_close_handler(ws, closeHandler);
			wspp_set_message_handler(ws, messageHandler);
			wspp_set_error_handler(ws, errorHandler);
			wspp_set_pong_handler(ws, pongHandler);
		}

		private void clearHandlers()
		{
			wspp_set_open_handler(ws, null);
			wspp_set_close_handler(ws, null);
			wspp_set_message_handler(ws, null);
			wspp_set_error_handler(ws, null);
			wspp_set_pong_handler(ws, null);
		}
	}
	internal enum WsppRes
	{
		OK = 0,
		InvalidState = 1,
		Unknown = -1
	}
	internal class WebSocketEventDispatcher : IDisposable
	{
		private Thread _thread;

		private bool _stop;

		private Queue<EventArgs> _queue;

		private int _id;

		private static object _lastIdLock = new object();

		private static int _lastId = 0;

		public bool IsCurrentThread => Thread.CurrentThread == _thread;

		public bool IsAlive => _thread.IsAlive;

		public event EventHandler OnOpen;

		public event EventHandler<CloseEventArgs> OnClose;

		public event EventHandler<ErrorEventArgs> OnError;

		public event EventHandler<MessageEventArgs> OnMessage;

		public WebSocketEventDispatcher()
		{
			lock (_lastIdLock)
			{
				_id = _lastId + 1;
				_lastId = _id;
			}
			_thread = new Thread(work);
			_stop = false;
			_queue = new Queue<EventArgs>();
		}

		public void Start()
		{
			_thread.Start();
		}

		public void Join()
		{
			_thread.Join();
		}

		private void debug(string msg)
		{
		}

		private void work()
		{
			debug("running");
			while (!_stop)
			{
				EventArgs eventArgs;
				lock (_queue)
				{
					eventArgs = ((_queue.Count <= 0) ? null : _queue.Dequeue());
				}
				if (eventArgs != null)
				{
					if (eventArgs is MessageEventArgs)
					{
						if (this.OnMessage != null)
						{
							this.OnMessage(this, (MessageEventArgs)eventArgs);
						}
					}
					else if (eventArgs is CloseEventArgs)
					{
						if (this.OnClose != null)
						{
							this.OnClose(this, (CloseEventArgs)eventArgs);
						}
					}
					else if (eventArgs is ErrorEventArgs)
					{
						if (this.OnError != null)
						{
							this.OnError(this, (ErrorEventArgs)eventArgs);
						}
					}
					else if (this.OnOpen != null)
					{
						this.OnOpen(this, eventArgs);
					}
				}
				else
				{
					Thread.Sleep(1);
				}
			}
			debug("stopped");
			_queue = null;
		}

		public void Dispose()
		{
			Dispose(disposing: true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (IsCurrentThread)
			{
				throw new InvalidOperationException("Can't dispose self");
			}
			debug("disposing");
			if (!_stop)
			{
				_stop = true;
				_thread.Join();
				debug("joined");
				_queue = null;
			}
		}

		~WebSocketEventDispatcher()
		{
			Dispose(disposing: false);
		}

		public void Enqueue(EventArgs e)
		{
			lock (_queue)
			{
				_queue.Enqueue(e);
			}
		}
	}
	public enum WebSocketState : ushort
	{
		New,
		Connecting,
		Open,
		Closing,
		Closed
	}
	internal class WebSocketWorker : IDisposable
	{
		private UIntPtr _ws;

		private Thread _thread;

		private bool _stop;

		private int _id;

		private static object _lastIdLock = new object();

		private static int _lastId = 0;

		public bool IsCurrentThread => Thread.CurrentThread == _thread;

		public bool IsAlive => _thread.IsAlive;

		public WebSocketWorker(UIntPtr ws)
		{
			lock (_lastIdLock)
			{
				_id = _lastId + 1;
				_lastId = _id;
			}
			_ws = ws;
			_thread = new Thread(work);
			_stop = false;
		}

		public void Start()
		{
			_thread.Start();
		}

		public void Join()
		{
			_thread.Join();
		}

		private void debug(string msg)
		{
		}

		private void work()
		{
			while (!_stop && !WebSocket.wspp_stopped(_ws))
			{
				WebSocket.wspp_poll(_ws);
				Thread.Sleep(1);
			}
			if (!WebSocket.wspp_stopped(_ws))
			{
				debug("stopping");
			}
			for (int i = 0; i < 1000; i++)
			{
				if (WebSocket.wspp_stopped(_ws))
				{
					break;
				}
				WebSocket.wspp_poll(_ws);
				Thread.Sleep(1);
			}
			debug("stopped");
		}

		public void Dispose()
		{
			Dispose(disposing: true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			debug("disposing");
			if (!_stop)
			{
				_stop = true;
				_thread.Join();
				debug("joined");
			}
		}

		~WebSocketWorker()
		{
			Dispose(disposing: false);
		}
	}
}