Decompiled source of SULFUR Together v1.0.0

LiteNetLib.dll

Decompiled 19 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using LiteNetLib.Layers;
using LiteNetLib.Utils;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("Ruslan Pyrch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright 2024 Ruslan Pyrch")]
[assembly: AssemblyDescription("Lite reliable UDP library for .NET, Mono, and .NET Core")]
[assembly: AssemblyFileVersion("1.3.5")]
[assembly: AssemblyInformationalVersion("1.0.0+cf54c7d7a15a45bd2460cd4a460807329ee7026a")]
[assembly: AssemblyProduct("LiteNetLib")]
[assembly: AssemblyTitle("LiteNetLib")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/RevenantX/LiteNetLib")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.5.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsUnmanagedAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsByRefLikeAttribute : Attribute
	{
	}
}
namespace LiteNetLib
{
	internal abstract class BaseChannel
	{
		protected readonly NetPeer Peer;

		protected readonly Queue<NetPacket> OutgoingQueue = new Queue<NetPacket>(64);

		private int _isAddedToPeerChannelSendQueue;

		public int PacketsInQueue => OutgoingQueue.Count;

		protected BaseChannel(NetPeer peer)
		{
			Peer = peer;
		}

		public void AddToQueue(NetPacket packet)
		{
			lock (OutgoingQueue)
			{
				OutgoingQueue.Enqueue(packet);
			}
			AddToPeerChannelSendQueue();
		}

		protected void AddToPeerChannelSendQueue()
		{
			if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0)
			{
				Peer.AddToReliableChannelSendQueue(this);
			}
		}

		public bool SendAndCheckQueue()
		{
			bool num = SendNextPackets();
			if (!num)
			{
				Interlocked.Exchange(ref _isAddedToPeerChannelSendQueue, 0);
			}
			return num;
		}

		protected abstract bool SendNextPackets();

		public abstract bool ProcessPacket(NetPacket packet);
	}
	internal enum ConnectionRequestResult
	{
		None,
		Accept,
		Reject,
		RejectForce
	}
	public class ConnectionRequest
	{
		private readonly NetManager _listener;

		private int _used;

		internal NetConnectRequestPacket InternalPacket;

		public readonly IPEndPoint RemoteEndPoint;

		public NetDataReader Data => InternalPacket.Data;

		internal ConnectionRequestResult Result { get; private set; }

		internal void UpdateRequest(NetConnectRequestPacket connectRequest)
		{
			if (connectRequest.ConnectionTime >= InternalPacket.ConnectionTime && (connectRequest.ConnectionTime != InternalPacket.ConnectionTime || connectRequest.ConnectionNumber != InternalPacket.ConnectionNumber))
			{
				InternalPacket = connectRequest;
			}
		}

		private bool TryActivate()
		{
			return Interlocked.CompareExchange(ref _used, 1, 0) == 0;
		}

		internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, NetManager listener)
		{
			InternalPacket = requestPacket;
			RemoteEndPoint = remoteEndPoint;
			_listener = listener;
		}

		public NetPeer AcceptIfKey(string key)
		{
			if (!TryActivate())
			{
				return null;
			}
			try
			{
				if (Data.GetString() == key)
				{
					Result = ConnectionRequestResult.Accept;
				}
			}
			catch
			{
				NetDebug.WriteError("[AC] Invalid incoming data");
			}
			if (Result == ConnectionRequestResult.Accept)
			{
				return _listener.OnConnectionSolved(this, null, 0, 0);
			}
			Result = ConnectionRequestResult.Reject;
			_listener.OnConnectionSolved(this, null, 0, 0);
			return null;
		}

		public NetPeer Accept()
		{
			if (!TryActivate())
			{
				return null;
			}
			Result = ConnectionRequestResult.Accept;
			return _listener.OnConnectionSolved(this, null, 0, 0);
		}

		public void Reject(byte[] rejectData, int start, int length, bool force)
		{
			if (TryActivate())
			{
				Result = (force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject);
				_listener.OnConnectionSolved(this, rejectData, start, length);
			}
		}

		public void Reject(byte[] rejectData, int start, int length)
		{
			Reject(rejectData, start, length, force: false);
		}

		public void RejectForce(byte[] rejectData, int start, int length)
		{
			Reject(rejectData, start, length, force: true);
		}

		public void RejectForce()
		{
			Reject(null, 0, 0, force: true);
		}

		public void RejectForce(byte[] rejectData)
		{
			Reject(rejectData, 0, rejectData.Length, force: true);
		}

		public void RejectForce(NetDataWriter rejectData)
		{
			Reject(rejectData.Data, 0, rejectData.Length, force: true);
		}

		public void Reject()
		{
			Reject(null, 0, 0, force: false);
		}

		public void Reject(byte[] rejectData)
		{
			Reject(rejectData, 0, rejectData.Length, force: false);
		}

		public void Reject(NetDataWriter rejectData)
		{
			Reject(rejectData.Data, 0, rejectData.Length, force: false);
		}
	}
	public enum UnconnectedMessageType
	{
		BasicMessage,
		Broadcast
	}
	public enum DisconnectReason
	{
		ConnectionFailed,
		Timeout,
		HostUnreachable,
		NetworkUnreachable,
		RemoteConnectionClose,
		DisconnectPeerCalled,
		ConnectionRejected,
		InvalidProtocol,
		UnknownHost,
		Reconnect,
		PeerToPeerConnection,
		PeerNotFound
	}
	public struct DisconnectInfo
	{
		public DisconnectReason Reason;

		public SocketError SocketErrorCode;

		public NetPacketReader AdditionalData;
	}
	public interface INetEventListener
	{
		void OnPeerConnected(NetPeer peer);

		void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);

		void OnNetworkError(IPEndPoint endPoint, SocketError socketError);

		void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod);

		void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);

		void OnNetworkLatencyUpdate(NetPeer peer, int latency);

		void OnConnectionRequest(ConnectionRequest request);
	}
	public interface IDeliveryEventListener
	{
		void OnMessageDelivered(NetPeer peer, object userData);
	}
	public interface INtpEventListener
	{
		void OnNtpResponse(NtpPacket packet);
	}
	public interface IPeerAddressChangedListener
	{
		void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress);
	}
	public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener
	{
		public delegate void OnPeerConnected(NetPeer peer);

		public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);

		public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError);

		public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod);

		public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);

		public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency);

		public delegate void OnConnectionRequest(ConnectionRequest request);

		public delegate void OnDeliveryEvent(NetPeer peer, object userData);

		public delegate void OnNtpResponseEvent(NtpPacket packet);

		public delegate void OnPeerAddressChangedEvent(NetPeer peer, IPEndPoint previousAddress);

		public event OnPeerConnected PeerConnectedEvent;

		public event OnPeerDisconnected PeerDisconnectedEvent;

		public event OnNetworkError NetworkErrorEvent;

		public event OnNetworkReceive NetworkReceiveEvent;

		public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent;

		public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent;

		public event OnConnectionRequest ConnectionRequestEvent;

		public event OnDeliveryEvent DeliveryEvent;

		public event OnNtpResponseEvent NtpResponseEvent;

		public event OnPeerAddressChangedEvent PeerAddressChangedEvent;

		public void ClearPeerConnectedEvent()
		{
			this.PeerConnectedEvent = null;
		}

		public void ClearPeerDisconnectedEvent()
		{
			this.PeerDisconnectedEvent = null;
		}

		public void ClearNetworkErrorEvent()
		{
			this.NetworkErrorEvent = null;
		}

		public void ClearNetworkReceiveEvent()
		{
			this.NetworkReceiveEvent = null;
		}

		public void ClearNetworkReceiveUnconnectedEvent()
		{
			this.NetworkReceiveUnconnectedEvent = null;
		}

		public void ClearNetworkLatencyUpdateEvent()
		{
			this.NetworkLatencyUpdateEvent = null;
		}

		public void ClearConnectionRequestEvent()
		{
			this.ConnectionRequestEvent = null;
		}

		public void ClearDeliveryEvent()
		{
			this.DeliveryEvent = null;
		}

		public void ClearNtpResponseEvent()
		{
			this.NtpResponseEvent = null;
		}

		public void ClearPeerAddressChangedEvent()
		{
			this.PeerAddressChangedEvent = null;
		}

		void INetEventListener.OnPeerConnected(NetPeer peer)
		{
			if (this.PeerConnectedEvent != null)
			{
				this.PeerConnectedEvent(peer);
			}
		}

		void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
		{
			if (this.PeerDisconnectedEvent != null)
			{
				this.PeerDisconnectedEvent(peer, disconnectInfo);
			}
		}

		void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
		{
			if (this.NetworkErrorEvent != null)
			{
				this.NetworkErrorEvent(endPoint, socketErrorCode);
			}
		}

		void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
		{
			if (this.NetworkReceiveEvent != null)
			{
				this.NetworkReceiveEvent(peer, reader, channelNumber, deliveryMethod);
			}
		}

		void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
		{
			if (this.NetworkReceiveUnconnectedEvent != null)
			{
				this.NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType);
			}
		}

		void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
		{
			if (this.NetworkLatencyUpdateEvent != null)
			{
				this.NetworkLatencyUpdateEvent(peer, latency);
			}
		}

		void INetEventListener.OnConnectionRequest(ConnectionRequest request)
		{
			if (this.ConnectionRequestEvent != null)
			{
				this.ConnectionRequestEvent(request);
			}
		}

		void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData)
		{
			if (this.DeliveryEvent != null)
			{
				this.DeliveryEvent(peer, userData);
			}
		}

		void INtpEventListener.OnNtpResponse(NtpPacket packet)
		{
			if (this.NtpResponseEvent != null)
			{
				this.NtpResponseEvent(packet);
			}
		}

		void IPeerAddressChangedListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress)
		{
			if (this.PeerAddressChangedEvent != null)
			{
				this.PeerAddressChangedEvent(peer, previousAddress);
			}
		}
	}
	internal sealed class NetConnectRequestPacket
	{
		public const int HeaderSize = 18;

		public readonly long ConnectionTime;

		public byte ConnectionNumber;

		public readonly byte[] TargetAddress;

		public readonly NetDataReader Data;

		public readonly int PeerId;

		private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int localId, byte[] targetAddress, NetDataReader data)
		{
			ConnectionTime = connectionTime;
			ConnectionNumber = connectionNumber;
			TargetAddress = targetAddress;
			Data = data;
			PeerId = localId;
		}

		public static int GetProtocolId(NetPacket packet)
		{
			return BitConverter.ToInt32(packet.RawData, 1);
		}

		public static NetConnectRequestPacket FromData(NetPacket packet)
		{
			if (packet.ConnectionNumber >= 4)
			{
				return null;
			}
			long connectionTime = BitConverter.ToInt64(packet.RawData, 5);
			int localId = BitConverter.ToInt32(packet.RawData, 13);
			int num = packet.RawData[17];
			if (num != 16 && num != 28)
			{
				return null;
			}
			byte[] array = new byte[num];
			Buffer.BlockCopy(packet.RawData, 18, array, 0, num);
			NetDataReader netDataReader = new NetDataReader(null, 0, 0);
			if (packet.Size > 18 + num)
			{
				netDataReader.SetSource(packet.RawData, 18 + num, packet.Size);
			}
			return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, localId, array, netDataReader);
		}

		public static NetPacket Make(NetDataWriter connectData, SocketAddress addressBytes, long connectTime, int localId)
		{
			NetPacket netPacket = new NetPacket(PacketProperty.ConnectRequest, connectData.Length + addressBytes.Size);
			FastBitConverter.GetBytes(netPacket.RawData, 1, 13);
			FastBitConverter.GetBytes(netPacket.RawData, 5, connectTime);
			FastBitConverter.GetBytes(netPacket.RawData, 13, localId);
			netPacket.RawData[17] = (byte)addressBytes.Size;
			for (int i = 0; i < addressBytes.Size; i++)
			{
				netPacket.RawData[18 + i] = addressBytes[i];
			}
			Buffer.BlockCopy(connectData.Data, 0, netPacket.RawData, 18 + addressBytes.Size, connectData.Length);
			return netPacket;
		}
	}
	internal sealed class NetConnectAcceptPacket
	{
		public const int Size = 15;

		public readonly long ConnectionTime;

		public readonly byte ConnectionNumber;

		public readonly int PeerId;

		public readonly bool PeerNetworkChanged;

		private NetConnectAcceptPacket(long connectionTime, byte connectionNumber, int peerId, bool peerNetworkChanged)
		{
			ConnectionTime = connectionTime;
			ConnectionNumber = connectionNumber;
			PeerId = peerId;
			PeerNetworkChanged = peerNetworkChanged;
		}

		public static NetConnectAcceptPacket FromData(NetPacket packet)
		{
			if (packet.Size != 15)
			{
				return null;
			}
			long connectionTime = BitConverter.ToInt64(packet.RawData, 1);
			byte b = packet.RawData[9];
			if (b >= 4)
			{
				return null;
			}
			byte b2 = packet.RawData[10];
			if (b2 > 1)
			{
				return null;
			}
			int num = BitConverter.ToInt32(packet.RawData, 11);
			if (num < 0)
			{
				return null;
			}
			return new NetConnectAcceptPacket(connectionTime, b, num, b2 == 1);
		}

		public static NetPacket Make(long connectTime, byte connectNum, int localPeerId)
		{
			NetPacket netPacket = new NetPacket(PacketProperty.ConnectAccept, 0);
			FastBitConverter.GetBytes(netPacket.RawData, 1, connectTime);
			netPacket.RawData[9] = connectNum;
			FastBitConverter.GetBytes(netPacket.RawData, 11, localPeerId);
			return netPacket;
		}

		public static NetPacket MakeNetworkChanged(NetPeer peer)
		{
			NetPacket netPacket = new NetPacket(PacketProperty.PeerNotFound, 14);
			FastBitConverter.GetBytes(netPacket.RawData, 1, peer.ConnectTime);
			netPacket.RawData[9] = peer.ConnectionNum;
			netPacket.RawData[10] = 1;
			FastBitConverter.GetBytes(netPacket.RawData, 11, peer.RemoteId);
			return netPacket;
		}
	}
	internal static class NativeSocket
	{
		private static class WinSock
		{
			private const string LibName = "ws2_32.dll";

			[DllImport("ws2_32.dll", SetLastError = true)]
			public static extern int recvfrom(IntPtr socketHandle, [In][Out] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In][Out] ref int socketAddressSize);

			[DllImport("ws2_32.dll", SetLastError = true)]
			internal unsafe static extern int sendto(IntPtr socketHandle, byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize);
		}

		private static class UnixSock
		{
			private const string LibName = "libc";

			[DllImport("libc", SetLastError = true)]
			public static extern int recvfrom(IntPtr socketHandle, [In][Out] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In][Out] ref int socketAddressSize);

			[DllImport("libc", SetLastError = true)]
			internal unsafe static extern int sendto(IntPtr socketHandle, byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize);
		}

		public static readonly bool IsSupported;

		public static readonly bool UnixMode;

		public const int IPv4AddrSize = 16;

		public const int IPv6AddrSize = 28;

		public const int AF_INET = 2;

		public const int AF_INET6 = 10;

		private static readonly Dictionary<int, SocketError> NativeErrorToSocketError;

		static NativeSocket()
		{
			IsSupported = false;
			UnixMode = false;
			NativeErrorToSocketError = new Dictionary<int, SocketError>
			{
				{
					13,
					SocketError.AccessDenied
				},
				{
					98,
					SocketError.AddressAlreadyInUse
				},
				{
					99,
					SocketError.AddressNotAvailable
				},
				{
					97,
					SocketError.AddressFamilyNotSupported
				},
				{
					11,
					SocketError.WouldBlock
				},
				{
					114,
					SocketError.AlreadyInProgress
				},
				{
					9,
					SocketError.OperationAborted
				},
				{
					125,
					SocketError.OperationAborted
				},
				{
					103,
					SocketError.ConnectionAborted
				},
				{
					111,
					SocketError.ConnectionRefused
				},
				{
					104,
					SocketError.ConnectionReset
				},
				{
					89,
					SocketError.DestinationAddressRequired
				},
				{
					14,
					SocketError.Fault
				},
				{
					112,
					SocketError.HostDown
				},
				{
					6,
					SocketError.HostNotFound
				},
				{
					113,
					SocketError.HostUnreachable
				},
				{
					115,
					SocketError.InProgress
				},
				{
					4,
					SocketError.Interrupted
				},
				{
					22,
					SocketError.InvalidArgument
				},
				{
					106,
					SocketError.IsConnected
				},
				{
					24,
					SocketError.TooManyOpenSockets
				},
				{
					90,
					SocketError.MessageSize
				},
				{
					100,
					SocketError.NetworkDown
				},
				{
					102,
					SocketError.NetworkReset
				},
				{
					101,
					SocketError.NetworkUnreachable
				},
				{
					23,
					SocketError.TooManyOpenSockets
				},
				{
					105,
					SocketError.NoBufferSpaceAvailable
				},
				{
					61,
					SocketError.NoData
				},
				{
					2,
					SocketError.AddressNotAvailable
				},
				{
					92,
					SocketError.ProtocolOption
				},
				{
					107,
					SocketError.NotConnected
				},
				{
					88,
					SocketError.NotSocket
				},
				{
					3440,
					SocketError.OperationNotSupported
				},
				{
					1,
					SocketError.AccessDenied
				},
				{
					32,
					SocketError.Shutdown
				},
				{
					96,
					SocketError.ProtocolFamilyNotSupported
				},
				{
					93,
					SocketError.ProtocolNotSupported
				},
				{
					91,
					SocketError.ProtocolType
				},
				{
					94,
					SocketError.SocketNotSupported
				},
				{
					108,
					SocketError.Disconnecting
				},
				{
					110,
					SocketError.TimedOut
				},
				{
					0,
					SocketError.Success
				}
			};
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
			{
				IsSupported = true;
				UnixMode = true;
			}
			else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				IsSupported = true;
			}
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public static int RecvFrom(IntPtr socketHandle, byte[] pinnedBuffer, int len, byte[] socketAddress, ref int socketAddressSize)
		{
			if (!UnixMode)
			{
				return WinSock.recvfrom(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, ref socketAddressSize);
			}
			return UnixSock.recvfrom(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, ref socketAddressSize);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public unsafe static int SendTo(IntPtr socketHandle, byte* pinnedBuffer, int len, byte[] socketAddress, int socketAddressSize)
		{
			if (!UnixMode)
			{
				return WinSock.sendto(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, socketAddressSize);
			}
			return UnixSock.sendto(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, socketAddressSize);
		}

		public static SocketError GetSocketError()
		{
			int lastWin32Error = Marshal.GetLastWin32Error();
			if (UnixMode)
			{
				if (!NativeErrorToSocketError.TryGetValue(lastWin32Error, out var value))
				{
					return SocketError.SocketError;
				}
				return value;
			}
			return (SocketError)lastWin32Error;
		}

		public static SocketException GetSocketException()
		{
			int lastWin32Error = Marshal.GetLastWin32Error();
			if (UnixMode)
			{
				if (!NativeErrorToSocketError.TryGetValue(lastWin32Error, out var value))
				{
					return new SocketException(-1);
				}
				return new SocketException((int)value);
			}
			return new SocketException(lastWin32Error);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint)
		{
			if (!UnixMode)
			{
				return (short)remoteEndPoint.AddressFamily;
			}
			return (short)((remoteEndPoint.AddressFamily == AddressFamily.InterNetwork) ? 2 : 10);
		}
	}
	public enum NatAddressType
	{
		Internal,
		External
	}
	public interface INatPunchListener
	{
		void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);

		void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token);
	}
	public class EventBasedNatPunchListener : INatPunchListener
	{
		public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);

		public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token);

		public event OnNatIntroductionRequest NatIntroductionRequest;

		public event OnNatIntroductionSuccess NatIntroductionSuccess;

		void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token)
		{
			if (this.NatIntroductionRequest != null)
			{
				this.NatIntroductionRequest(localEndPoint, remoteEndPoint, token);
			}
		}

		void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token)
		{
			if (this.NatIntroductionSuccess != null)
			{
				this.NatIntroductionSuccess(targetEndPoint, type, token);
			}
		}
	}
	public sealed class NatPunchModule
	{
		private struct RequestEventData
		{
			public IPEndPoint LocalEndPoint;

			public IPEndPoint RemoteEndPoint;

			public string Token;
		}

		private struct SuccessEventData
		{
			public IPEndPoint TargetEndPoint;

			public NatAddressType Type;

			public string Token;
		}

		private class NatIntroduceRequestPacket
		{
			public IPEndPoint Internal
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public string Token
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}
		}

		private class NatIntroduceResponsePacket
		{
			public IPEndPoint Internal
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public IPEndPoint External
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public string Token
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}
		}

		private class NatPunchPacket
		{
			public string Token
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public bool IsExternal
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}
		}

		private readonly NetManager _socket;

		private readonly ConcurrentQueue<RequestEventData> _requestEvents = new ConcurrentQueue<RequestEventData>();

		private readonly ConcurrentQueue<SuccessEventData> _successEvents = new ConcurrentQueue<SuccessEventData>();

		private readonly NetDataReader _cacheReader = new NetDataReader();

		private readonly NetDataWriter _cacheWriter = new NetDataWriter();

		private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(256);

		private INatPunchListener _natPunchListener;

		public const int MaxTokenLength = 256;

		public bool UnsyncedEvents;

		internal NatPunchModule(NetManager socket)
		{
			_socket = socket;
			_netPacketProcessor.SubscribeReusable<NatIntroduceResponsePacket>(OnNatIntroductionResponse);
			_netPacketProcessor.SubscribeReusable<NatIntroduceRequestPacket, IPEndPoint>(OnNatIntroductionRequest);
			_netPacketProcessor.SubscribeReusable<NatPunchPacket, IPEndPoint>(OnNatPunch);
		}

		internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
		{
			lock (_cacheReader)
			{
				_cacheReader.SetSource(packet.RawData, 1, packet.Size);
				_netPacketProcessor.ReadAllPackets(_cacheReader, senderEndPoint);
			}
		}

		public void Init(INatPunchListener listener)
		{
			_natPunchListener = listener;
		}

		private void Send<T>(T packet, IPEndPoint target) where T : class, new()
		{
			_cacheWriter.Reset();
			_cacheWriter.Put((byte)16);
			_netPacketProcessor.Write(_cacheWriter, packet);
			_socket.SendRaw(_cacheWriter.Data, 0, _cacheWriter.Length, target);
		}

		public void NatIntroduce(IPEndPoint hostInternal, IPEndPoint hostExternal, IPEndPoint clientInternal, IPEndPoint clientExternal, string additionalInfo)
		{
			NatIntroduceResponsePacket natIntroduceResponsePacket = new NatIntroduceResponsePacket
			{
				Token = additionalInfo
			};
			natIntroduceResponsePacket.Internal = hostInternal;
			natIntroduceResponsePacket.External = hostExternal;
			Send(natIntroduceResponsePacket, clientExternal);
			natIntroduceResponsePacket.Internal = clientInternal;
			natIntroduceResponsePacket.External = clientExternal;
			Send(natIntroduceResponsePacket, hostExternal);
		}

		public void PollEvents()
		{
			if (!UnsyncedEvents && _natPunchListener != null && (!_successEvents.IsEmpty || !_requestEvents.IsEmpty))
			{
				SuccessEventData result;
				while (_successEvents.TryDequeue(out result))
				{
					_natPunchListener.OnNatIntroductionSuccess(result.TargetEndPoint, result.Type, result.Token);
				}
				RequestEventData result2;
				while (_requestEvents.TryDequeue(out result2))
				{
					_natPunchListener.OnNatIntroductionRequest(result2.LocalEndPoint, result2.RemoteEndPoint, result2.Token);
				}
			}
		}

		public void SendNatIntroduceRequest(string host, int port, string additionalInfo)
		{
			SendNatIntroduceRequest(NetUtils.MakeEndPoint(host, port), additionalInfo);
		}

		public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo)
		{
			string localIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
			if (string.IsNullOrEmpty(localIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
			{
				localIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
			}
			Send(new NatIntroduceRequestPacket
			{
				Internal = NetUtils.MakeEndPoint(localIp, _socket.LocalPort),
				Token = additionalInfo
			}, masterServerEndPoint);
		}

		private void OnNatIntroductionRequest(NatIntroduceRequestPacket req, IPEndPoint senderEndPoint)
		{
			if (UnsyncedEvents)
			{
				_natPunchListener.OnNatIntroductionRequest(req.Internal, senderEndPoint, req.Token);
				return;
			}
			_requestEvents.Enqueue(new RequestEventData
			{
				LocalEndPoint = req.Internal,
				RemoteEndPoint = senderEndPoint,
				Token = req.Token
			});
		}

		private void OnNatIntroductionResponse(NatIntroduceResponsePacket req)
		{
			NatPunchPacket natPunchPacket = new NatPunchPacket
			{
				Token = req.Token
			};
			Send(natPunchPacket, req.Internal);
			_socket.Ttl = 2;
			_socket.SendRaw(new byte[1] { 17 }, 0, 1, req.External);
			_socket.Ttl = 255;
			natPunchPacket.IsExternal = true;
			Send(natPunchPacket, req.External);
		}

		private void OnNatPunch(NatPunchPacket req, IPEndPoint senderEndPoint)
		{
			if (UnsyncedEvents)
			{
				_natPunchListener.OnNatIntroductionSuccess(senderEndPoint, req.IsExternal ? NatAddressType.External : NatAddressType.Internal, req.Token);
				return;
			}
			_successEvents.Enqueue(new SuccessEventData
			{
				TargetEndPoint = senderEndPoint,
				Type = (req.IsExternal ? NatAddressType.External : NatAddressType.Internal),
				Token = req.Token
			});
		}
	}
	public enum DeliveryMethod : byte
	{
		Unreliable = 4,
		ReliableUnordered = 0,
		Sequenced = 1,
		ReliableOrdered = 2,
		ReliableSequenced = 3
	}
	public static class NetConstants
	{
		public const int DefaultWindowSize = 64;

		public const int SocketBufferSize = 1048576;

		public const int SocketTTL = 255;

		public const int HeaderSize = 1;

		public const int ChanneledHeaderSize = 4;

		public const int FragmentHeaderSize = 6;

		public const int FragmentedHeaderTotalSize = 10;

		public const ushort MaxSequence = 32768;

		public const ushort HalfMaxSequence = 16384;

		internal const int ProtocolId = 13;

		internal const int MaxUdpHeaderSize = 68;

		internal const int ChannelTypeCount = 4;

		internal const int FragmentedChannelsCount = 2;

		internal const int MaxFragmentsInWindow = 32;

		internal static readonly int[] PossibleMtu = new int[6] { 1024, 1164, 1392, 1404, 1424, 1432 };

		public static readonly int InitialMtu = PossibleMtu[0];

		public static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1];

		public static readonly int MaxUnreliableDataSize = MaxPacketSize - 1;

		public const byte MaxConnectionNumber = 4;
	}
	public class InvalidPacketException : ArgumentException
	{
		public InvalidPacketException(string message)
			: base(message)
		{
		}
	}
	public class TooBigPacketException : InvalidPacketException
	{
		public TooBigPacketException(string message)
			: base(message)
		{
		}
	}
	public enum NetLogLevel
	{
		Warning,
		Error,
		Trace,
		Info
	}
	public interface INetLogger
	{
		void WriteNet(NetLogLevel level, string str, params object[] args);
	}
	public static class NetDebug
	{
		public static INetLogger Logger = null;

		private static readonly object DebugLogLock = new object();

		private static void WriteLogic(NetLogLevel logLevel, string str, params object[] args)
		{
			lock (DebugLogLock)
			{
				if (Logger == null)
				{
					Console.WriteLine(str, args);
				}
				else
				{
					Logger.WriteNet(logLevel, str, args);
				}
			}
		}

		[Conditional("DEBUG_MESSAGES")]
		internal static void Write(string str)
		{
			WriteLogic(NetLogLevel.Trace, str);
		}

		[Conditional("DEBUG_MESSAGES")]
		internal static void Write(NetLogLevel level, string str)
		{
			WriteLogic(level, str);
		}

		[Conditional("DEBUG_MESSAGES")]
		[Conditional("DEBUG")]
		internal static void WriteForce(string str)
		{
			WriteLogic(NetLogLevel.Trace, str);
		}

		[Conditional("DEBUG_MESSAGES")]
		[Conditional("DEBUG")]
		internal static void WriteForce(NetLogLevel level, string str)
		{
			WriteLogic(level, str);
		}

		internal static void WriteError(string str)
		{
			WriteLogic(NetLogLevel.Error, str);
		}
	}
	public sealed class NetPacketReader : NetDataReader
	{
		private NetPacket _packet;

		private readonly NetManager _manager;

		private readonly NetEvent _evt;

		internal NetPacketReader(NetManager manager, NetEvent evt)
		{
			_manager = manager;
			_evt = evt;
		}

		internal void SetSource(NetPacket packet, int headerSize)
		{
			if (packet != null)
			{
				_packet = packet;
				SetSource(packet.RawData, headerSize, packet.Size);
			}
		}

		internal void RecycleInternal()
		{
			Clear();
			if (_packet != null)
			{
				_manager.PoolRecycle(_packet);
			}
			_packet = null;
			_manager.RecycleEvent(_evt);
		}

		public void Recycle()
		{
			if (!_manager.AutoRecycle)
			{
				RecycleInternal();
			}
		}
	}
	internal sealed class NetEvent
	{
		public enum EType
		{
			Connect,
			Disconnect,
			Receive,
			ReceiveUnconnected,
			Error,
			ConnectionLatencyUpdated,
			Broadcast,
			ConnectionRequest,
			MessageDelivered,
			PeerAddressChanged
		}

		public NetEvent Next;

		public EType Type;

		public NetPeer Peer;

		public IPEndPoint RemoteEndPoint;

		public object UserData;

		public int Latency;

		public SocketError ErrorCode;

		public DisconnectReason DisconnectReason;

		public ConnectionRequest ConnectionRequest;

		public DeliveryMethod DeliveryMethod;

		public byte ChannelNumber;

		public readonly NetPacketReader DataReader;

		public NetEvent(NetManager manager)
		{
			DataReader = new NetPacketReader(manager, this);
		}
	}
	public class NetManager : IEnumerable<NetPeer>, IEnumerable
	{
		public struct NetPeerEnumerator : IEnumerator<NetPeer>, IEnumerator, IDisposable
		{
			private readonly NetPeer _initialPeer;

			private NetPeer _p;

			public NetPeer Current => _p;

			object IEnumerator.Current => _p;

			public NetPeerEnumerator(NetPeer p)
			{
				_initialPeer = p;
				_p = null;
			}

			public void Dispose()
			{
			}

			public bool MoveNext()
			{
				_p = ((_p == null) ? _initialPeer : _p.NextPeer);
				return _p != null;
			}

			public void Reset()
			{
				throw new NotSupportedException();
			}
		}

		private struct IncomingData
		{
			public NetPacket Data;

			public IPEndPoint EndPoint;

			public DateTime TimeWhenGet;
		}

		private struct OutboundDelayedPacket
		{
			public byte[] Data;

			public int Start;

			public int Length;

			public IPEndPoint EndPoint;

			public DateTime TimeWhenSend;
		}

		private struct Slot
		{
			internal int HashCode;

			internal int Next;

			internal NetPeer Value;
		}

		private readonly List<IncomingData> _pingSimulationList = new List<IncomingData>();

		private readonly List<OutboundDelayedPacket> _outboundSimulationList = new List<OutboundDelayedPacket>();

		private readonly Random _randomGenerator = new Random();

		private const int MinLatencyThreshold = 5;

		private Thread _logicThread;

		private bool _manualMode;

		private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(initialState: true);

		private NetEvent _pendingEventHead;

		private NetEvent _pendingEventTail;

		private NetEvent _netEventPoolHead;

		private readonly INetEventListener _netEventListener;

		private readonly IDeliveryEventListener _deliveryEventListener;

		private readonly INtpEventListener _ntpEventListener;

		private readonly IPeerAddressChangedListener _peerAddressChangedListener;

		private readonly Dictionary<IPEndPoint, ConnectionRequest> _requestsDict = new Dictionary<IPEndPoint, ConnectionRequest>();

		private readonly ConcurrentDictionary<IPEndPoint, NtpRequest> _ntpRequests = new ConcurrentDictionary<IPEndPoint, NtpRequest>();

		private long _connectedPeersCount;

		private readonly List<NetPeer> _connectedPeerListCache = new List<NetPeer>();

		private readonly PacketLayerBase _extraPacketLayer;

		private int _lastPeerId;

		private ConcurrentQueue<int> _peerIds = new ConcurrentQueue<int>();

		private byte _channelsCount = 1;

		private readonly object _eventLock = new object();

		private volatile bool _isRunning;

		private bool _dropPacket;

		public bool UnconnectedMessagesEnabled;

		public bool NatPunchEnabled;

		public int UpdateTime = 15;

		public int PingInterval = 1000;

		public int DisconnectTimeout = 5000;

		public bool SimulatePacketLoss;

		public bool SimulateLatency;

		public int SimulationPacketLossChance = 10;

		public int SimulationMinLatency = 30;

		public int SimulationMaxLatency = 100;

		public bool UnsyncedEvents;

		public bool UnsyncedReceiveEvent;

		public bool UnsyncedDeliveryEvent;

		public bool BroadcastReceiveEnabled;

		public int ReconnectDelay = 500;

		public int MaxConnectAttempts = 10;

		public bool ReuseAddress;

		public bool DontRoute;

		public readonly NetStatistics Statistics = new NetStatistics();

		public bool EnableStatistics;

		public readonly NatPunchModule NatPunchModule;

		public bool AutoRecycle;

		public bool IPv6Enabled = true;

		public int MtuOverride;

		public bool MtuDiscovery;

		public bool UseNativeSockets;

		public bool DisconnectOnUnreachable;

		public bool AllowPeerAddressChange;

		private const int MaxPrimeArrayLength = 2147483587;

		private const int HashPrime = 101;

		private const int Lower31BitMask = int.MaxValue;

		private static readonly int[] Primes;

		private int[] _buckets;

		private Slot[] _slots;

		private int _count;

		private int _lastIndex;

		private int _freeList = -1;

		private NetPeer[] _peersArray = new NetPeer[32];

		private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

		private volatile NetPeer _headPeer;

		private NetPacket _poolHead;

		private int _poolCount;

		private readonly object _poolLock = new object();

		public int PacketPoolSize = 1000;

		private Socket _udpSocketv4;

		private Socket _udpSocketv6;

		private Thread _receiveThread;

		private IPEndPoint _bufferEndPointv4;

		private IPEndPoint _bufferEndPointv6;

		private const int SioUdpConnreset = -1744830452;

		private static readonly IPAddress MulticastAddressV6;

		public static readonly bool IPv6Support;

		internal bool NotConnected;

		public int ReceivePollingTime = 50000;

		public bool IsRunning => _isRunning;

		public int LocalPort { get; private set; }

		public NetPeer FirstPeer => _headPeer;

		public byte ChannelsCount
		{
			get
			{
				return _channelsCount;
			}
			set
			{
				if (value < 1 || value > 64)
				{
					throw new ArgumentException("Channels count must be between 1 and 64");
				}
				_channelsCount = value;
			}
		}

		public List<NetPeer> ConnectedPeerList
		{
			get
			{
				GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected);
				return _connectedPeerListCache;
			}
		}

		public int ConnectedPeersCount => (int)Interlocked.Read(in _connectedPeersCount);

		public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0;

		public int PoolCount => _poolCount;

		public short Ttl
		{
			get
			{
				return _udpSocketv4.Ttl;
			}
			internal set
			{
				_udpSocketv4.Ttl = value;
			}
		}

		public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null)
		{
			_netEventListener = listener;
			_deliveryEventListener = listener as IDeliveryEventListener;
			_ntpEventListener = listener as INtpEventListener;
			_peerAddressChangedListener = listener as IPeerAddressChangedListener;
			NatPunchModule = new NatPunchModule(this);
			_extraPacketLayer = extraPacketLayer;
		}

		internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency)
		{
			CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, null, SocketError.Success, latency, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
		}

		internal void MessageDelivered(NetPeer fromPeer, object userData)
		{
			if (_deliveryEventListener != null)
			{
				CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, null, userData);
			}
		}

		internal void DisconnectPeerForce(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, NetPacket eventData)
		{
			DisconnectPeer(peer, reason, socketErrorCode, force: true, null, 0, 0, eventData);
		}

		private void DisconnectPeer(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, bool force, byte[] data, int start, int count, NetPacket eventData)
		{
			switch (peer.Shutdown(data, start, count, force))
			{
			case ShutdownResult.None:
				return;
			case ShutdownResult.WasConnected:
				Interlocked.Decrement(ref _connectedPeersCount);
				break;
			}
			CreateEvent(NetEvent.EType.Disconnect, peer, null, socketErrorCode, 0, reason, null, DeliveryMethod.Unreliable, 0, eventData);
		}

		private void CreateEvent(NetEvent.EType type, NetPeer peer = null, IPEndPoint remoteEndPoint = null, SocketError errorCode = SocketError.Success, int latency = 0, DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, ConnectionRequest connectionRequest = null, DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, byte channelNumber = 0, NetPacket readerSource = null, object userData = null)
		{
			bool flag = UnsyncedEvents;
			switch (type)
			{
			case NetEvent.EType.Connect:
				Interlocked.Increment(ref _connectedPeersCount);
				break;
			case NetEvent.EType.MessageDelivered:
				flag = UnsyncedDeliveryEvent;
				break;
			}
			NetEvent netEvent;
			lock (_eventLock)
			{
				netEvent = _netEventPoolHead;
				if (netEvent == null)
				{
					netEvent = new NetEvent(this);
				}
				else
				{
					_netEventPoolHead = netEvent.Next;
				}
			}
			netEvent.Next = null;
			netEvent.Type = type;
			netEvent.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0);
			netEvent.Peer = peer;
			netEvent.RemoteEndPoint = remoteEndPoint;
			netEvent.Latency = latency;
			netEvent.ErrorCode = errorCode;
			netEvent.DisconnectReason = disconnectReason;
			netEvent.ConnectionRequest = connectionRequest;
			netEvent.DeliveryMethod = deliveryMethod;
			netEvent.ChannelNumber = channelNumber;
			netEvent.UserData = userData;
			if (flag || _manualMode)
			{
				ProcessEvent(netEvent);
				return;
			}
			lock (_eventLock)
			{
				if (_pendingEventTail == null)
				{
					_pendingEventHead = netEvent;
				}
				else
				{
					_pendingEventTail.Next = netEvent;
				}
				_pendingEventTail = netEvent;
			}
		}

		private void ProcessEvent(NetEvent evt)
		{
			bool isNull = evt.DataReader.IsNull;
			switch (evt.Type)
			{
			case NetEvent.EType.Connect:
				_netEventListener.OnPeerConnected(evt.Peer);
				break;
			case NetEvent.EType.Disconnect:
			{
				DisconnectInfo disconnectInfo = new DisconnectInfo
				{
					Reason = evt.DisconnectReason,
					AdditionalData = evt.DataReader,
					SocketErrorCode = evt.ErrorCode
				};
				_netEventListener.OnPeerDisconnected(evt.Peer, disconnectInfo);
				break;
			}
			case NetEvent.EType.Receive:
				_netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod);
				break;
			case NetEvent.EType.ReceiveUnconnected:
				_netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage);
				break;
			case NetEvent.EType.Broadcast:
				_netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast);
				break;
			case NetEvent.EType.Error:
				_netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode);
				break;
			case NetEvent.EType.ConnectionLatencyUpdated:
				_netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency);
				break;
			case NetEvent.EType.ConnectionRequest:
				_netEventListener.OnConnectionRequest(evt.ConnectionRequest);
				break;
			case NetEvent.EType.MessageDelivered:
				_deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData);
				break;
			case NetEvent.EType.PeerAddressChanged:
			{
				_peersLock.EnterUpgradeableReadLock();
				IPEndPoint iPEndPoint = null;
				if (ContainsPeer(evt.Peer))
				{
					_peersLock.EnterWriteLock();
					RemovePeerFromSet(evt.Peer);
					iPEndPoint = new IPEndPoint(evt.Peer.Address, evt.Peer.Port);
					evt.Peer.FinishEndPointChange(evt.RemoteEndPoint);
					AddPeerToSet(evt.Peer);
					_peersLock.ExitWriteLock();
				}
				_peersLock.ExitUpgradeableReadLock();
				if (iPEndPoint != null && _peerAddressChangedListener != null)
				{
					_peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, iPEndPoint);
				}
				break;
			}
			}
			if (isNull)
			{
				RecycleEvent(evt);
			}
			else if (AutoRecycle)
			{
				evt.DataReader.RecycleInternal();
			}
		}

		internal void RecycleEvent(NetEvent evt)
		{
			evt.Peer = null;
			evt.ErrorCode = SocketError.Success;
			evt.RemoteEndPoint = null;
			evt.ConnectionRequest = null;
			lock (_eventLock)
			{
				evt.Next = _netEventPoolHead;
				_netEventPoolHead = evt;
			}
		}

		private void UpdateLogic()
		{
			List<NetPeer> list = new List<NetPeer>();
			Stopwatch stopwatch = new Stopwatch();
			stopwatch.Start();
			while (_isRunning)
			{
				try
				{
					float num = (float)((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0);
					num = ((num <= 0f) ? 0.001f : num);
					stopwatch.Restart();
					for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
					{
						if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > (float)DisconnectTimeout)
						{
							list.Add(netPeer);
						}
						else
						{
							netPeer.Update(num);
						}
					}
					if (list.Count > 0)
					{
						_peersLock.EnterWriteLock();
						for (int i = 0; i < list.Count; i++)
						{
							RemovePeer(list[i], enableWriteLock: false);
						}
						_peersLock.ExitWriteLock();
						list.Clear();
					}
					ProcessNtpRequests(num);
					int num2 = UpdateTime - (int)stopwatch.ElapsedMilliseconds;
					if (num2 > 0)
					{
						_updateTriggerEvent.WaitOne(num2);
					}
				}
				catch (ThreadAbortException)
				{
					return;
				}
				catch (Exception ex2)
				{
					NetDebug.WriteError("[NM] LogicThread error: " + ex2);
				}
			}
			stopwatch.Stop();
		}

		[Conditional("DEBUG")]
		[Conditional("SIMULATE_NETWORK")]
		private void ProcessDelayedPackets()
		{
			if (!SimulateLatency)
			{
				return;
			}
			DateTime utcNow = DateTime.UtcNow;
			lock (_pingSimulationList)
			{
				for (int i = 0; i < _pingSimulationList.Count; i++)
				{
					IncomingData incomingData = _pingSimulationList[i];
					if (incomingData.TimeWhenGet <= utcNow)
					{
						HandleMessageReceived(incomingData.Data, incomingData.EndPoint);
						_pingSimulationList.RemoveAt(i);
						i--;
					}
				}
			}
			lock (_outboundSimulationList)
			{
				for (int j = 0; j < _outboundSimulationList.Count; j++)
				{
					OutboundDelayedPacket outboundDelayedPacket = _outboundSimulationList[j];
					if (outboundDelayedPacket.TimeWhenSend <= utcNow)
					{
						SendRawCore(outboundDelayedPacket.Data, outboundDelayedPacket.Start, outboundDelayedPacket.Length, outboundDelayedPacket.EndPoint);
						_outboundSimulationList.RemoveAt(j);
						j--;
					}
				}
			}
		}

		private void ProcessNtpRequests(float elapsedMilliseconds)
		{
			if (_ntpRequests.IsEmpty)
			{
				return;
			}
			List<IPEndPoint> list = null;
			foreach (KeyValuePair<IPEndPoint, NtpRequest> ntpRequest in _ntpRequests)
			{
				ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds);
				if (ntpRequest.Value.NeedToKill)
				{
					if (list == null)
					{
						list = new List<IPEndPoint>();
					}
					list.Add(ntpRequest.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (IPEndPoint item in list)
			{
				_ntpRequests.TryRemove(item, out var _);
			}
		}

		public void ManualUpdate(float elapsedMilliseconds)
		{
			if (!_manualMode)
			{
				return;
			}
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > (float)DisconnectTimeout)
				{
					RemovePeer(netPeer, enableWriteLock: false);
				}
				else
				{
					netPeer.Update(elapsedMilliseconds);
				}
			}
			ProcessNtpRequests(elapsedMilliseconds);
		}

		internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length)
		{
			NetPeer actualValue = null;
			if (request.Result == ConnectionRequestResult.RejectForce)
			{
				if (rejectData != null && length > 0)
				{
					NetPacket netPacket = PoolGetWithProperty(PacketProperty.Disconnect, length);
					netPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber;
					FastBitConverter.GetBytes(netPacket.RawData, 1, request.InternalPacket.ConnectionTime);
					if (netPacket.Size >= NetConstants.PossibleMtu[0])
					{
						NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!");
					}
					else
					{
						Buffer.BlockCopy(rejectData, start, netPacket.RawData, 9, length);
					}
					SendRawAndRecycle(netPacket, request.RemoteEndPoint);
				}
				lock (_requestsDict)
				{
					_requestsDict.Remove(request.RemoteEndPoint);
				}
			}
			else
			{
				lock (_requestsDict)
				{
					if (!TryGetPeer(request.RemoteEndPoint, out actualValue))
					{
						if (request.Result == ConnectionRequestResult.Reject)
						{
							actualValue = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId());
							actualValue.Reject(request.InternalPacket, rejectData, start, length);
							AddPeer(actualValue);
						}
						else
						{
							actualValue = new NetPeer(this, request, GetNextPeerId());
							AddPeer(actualValue);
							CreateEvent(NetEvent.EType.Connect, actualValue, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
						}
					}
					_requestsDict.Remove(request.RemoteEndPoint);
				}
			}
			return actualValue;
		}

		private int GetNextPeerId()
		{
			if (!_peerIds.TryDequeue(out var result))
			{
				return _lastPeerId++;
			}
			return result;
		}

		private void ProcessConnectRequest(IPEndPoint remoteEndPoint, NetPeer netPeer, NetConnectRequestPacket connRequest)
		{
			if (netPeer != null)
			{
				ConnectRequestResult connectRequestResult = netPeer.ProcessConnectRequest(connRequest);
				switch (connectRequestResult)
				{
				default:
					return;
				case ConnectRequestResult.Reconnection:
					DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, SocketError.Success, null);
					RemovePeer(netPeer, enableWriteLock: true);
					break;
				case ConnectRequestResult.NewConnection:
					RemovePeer(netPeer, enableWriteLock: true);
					break;
				case ConnectRequestResult.P2PLose:
					DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, SocketError.Success, null);
					RemovePeer(netPeer, enableWriteLock: true);
					break;
				}
				if (connectRequestResult != ConnectRequestResult.P2PLose)
				{
					connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % 4);
				}
			}
			ConnectionRequest value;
			lock (_requestsDict)
			{
				if (_requestsDict.TryGetValue(remoteEndPoint, out value))
				{
					value.UpdateRequest(connRequest);
					return;
				}
				value = new ConnectionRequest(remoteEndPoint, connRequest, this);
				_requestsDict.Add(remoteEndPoint, value);
			}
			CreateEvent(NetEvent.EType.ConnectionRequest, null, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, value, DeliveryMethod.Unreliable, 0);
		}

		private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			if (packet.Size == 0)
			{
				PoolRecycle(packet);
				return;
			}
			_dropPacket = false;
			if (!_dropPacket)
			{
				HandleMessageReceived(packet, remoteEndPoint);
			}
		}

		[Conditional("DEBUG")]
		[Conditional("SIMULATE_NETWORK")]
		private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			if (!SimulateLatency)
			{
				return;
			}
			int num = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency) / 2;
			if (num > 5)
			{
				lock (_pingSimulationList)
				{
					_pingSimulationList.Add(new IncomingData
					{
						Data = packet,
						EndPoint = remoteEndPoint,
						TimeWhenGet = DateTime.UtcNow.AddMilliseconds(num)
					});
				}
				_dropPacket = true;
			}
		}

		[Conditional("DEBUG")]
		[Conditional("SIMULATE_NETWORK")]
		private void HandleSimulatePacketLoss()
		{
			if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100.0 < (double)SimulationPacketLossChance)
			{
				_dropPacket = true;
			}
		}

		private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			int size = packet.Size;
			if (EnableStatistics)
			{
				Statistics.IncrementPacketsReceived();
				Statistics.AddBytesReceived(size);
			}
			if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out var _))
			{
				if (packet.Size >= 48)
				{
					byte[] array = new byte[packet.Size];
					Buffer.BlockCopy(packet.RawData, 0, array, 0, packet.Size);
					NtpPacket ntpPacket = NtpPacket.FromServerResponse(array, DateTime.UtcNow);
					try
					{
						ntpPacket.ValidateReply();
					}
					catch (InvalidOperationException)
					{
						ntpPacket = null;
					}
					if (ntpPacket != null)
					{
						_ntpRequests.TryRemove(remoteEndPoint, out var _);
						_ntpEventListener?.OnNtpResponse(ntpPacket);
					}
				}
				return;
			}
			if (_extraPacketLayer != null)
			{
				_extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size);
				if (packet.Size == 0)
				{
					return;
				}
			}
			if (!packet.Verify())
			{
				NetDebug.WriteError("[NM] DataReceived: bad!");
				PoolRecycle(packet);
				return;
			}
			switch (packet.Property)
			{
			case PacketProperty.ConnectRequest:
				if (NetConnectRequestPacket.GetProtocolId(packet) != 13)
				{
					SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint);
					return;
				}
				break;
			case PacketProperty.Broadcast:
				if (BroadcastReceiveEnabled)
				{
					CreateEvent(NetEvent.EType.Broadcast, null, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, packet);
				}
				return;
			case PacketProperty.UnconnectedMessage:
				if (UnconnectedMessagesEnabled)
				{
					CreateEvent(NetEvent.EType.ReceiveUnconnected, null, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, packet);
				}
				return;
			case PacketProperty.NatMessage:
				if (NatPunchEnabled)
				{
					NatPunchModule.ProcessMessage(remoteEndPoint, packet);
				}
				return;
			}
			NetPeer actualValue = remoteEndPoint as NetPeer;
			bool flag = actualValue != null || TryGetPeer(remoteEndPoint, out actualValue);
			if (flag && EnableStatistics)
			{
				actualValue.Statistics.IncrementPacketsReceived();
				actualValue.Statistics.AddBytesReceived(size);
			}
			switch (packet.Property)
			{
			case PacketProperty.ConnectRequest:
			{
				NetConnectRequestPacket netConnectRequestPacket = NetConnectRequestPacket.FromData(packet);
				if (netConnectRequestPacket != null)
				{
					ProcessConnectRequest(remoteEndPoint, actualValue, netConnectRequestPacket);
				}
				break;
			}
			case PacketProperty.PeerNotFound:
				if (flag)
				{
					if (actualValue.ConnectionState == ConnectionState.Connected)
					{
						if (packet.Size == 1)
						{
							actualValue.ResetMtu();
							SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(actualValue), remoteEndPoint);
						}
						else if (packet.Size == 2 && packet.RawData[1] == 1)
						{
							DisconnectPeerForce(actualValue, DisconnectReason.PeerNotFound, SocketError.Success, null);
						}
					}
				}
				else
				{
					if (packet.Size <= 1)
					{
						break;
					}
					bool flag2 = false;
					if (AllowPeerAddressChange)
					{
						NetConnectAcceptPacket netConnectAcceptPacket = NetConnectAcceptPacket.FromData(packet);
						if (netConnectAcceptPacket != null && netConnectAcceptPacket.PeerNetworkChanged && netConnectAcceptPacket.PeerId < _peersArray.Length)
						{
							_peersLock.EnterUpgradeableReadLock();
							NetPeer netPeer = _peersArray[netConnectAcceptPacket.PeerId];
							_peersLock.ExitUpgradeableReadLock();
							if (netPeer != null && netPeer.ConnectTime == netConnectAcceptPacket.ConnectionTime && netPeer.ConnectionNum == netConnectAcceptPacket.ConnectionNumber)
							{
								if (netPeer.ConnectionState == ConnectionState.Connected)
								{
									netPeer.InitiateEndPointChange();
									CreateEvent(NetEvent.EType.PeerAddressChanged, netPeer, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
								}
								flag2 = true;
							}
						}
					}
					PoolRecycle(packet);
					if (!flag2)
					{
						NetPacket netPacket = PoolGetWithProperty(PacketProperty.PeerNotFound, 1);
						netPacket.RawData[1] = 1;
						SendRawAndRecycle(netPacket, remoteEndPoint);
					}
				}
				break;
			case PacketProperty.InvalidProtocol:
				if (flag && actualValue.ConnectionState == ConnectionState.Outgoing)
				{
					DisconnectPeerForce(actualValue, DisconnectReason.InvalidProtocol, SocketError.Success, null);
				}
				break;
			case PacketProperty.Disconnect:
				if (flag)
				{
					DisconnectResult disconnectResult = actualValue.ProcessDisconnect(packet);
					if (disconnectResult == DisconnectResult.None)
					{
						PoolRecycle(packet);
						break;
					}
					DisconnectPeerForce(actualValue, (disconnectResult == DisconnectResult.Disconnect) ? DisconnectReason.RemoteConnectionClose : DisconnectReason.ConnectionRejected, SocketError.Success, packet);
				}
				else
				{
					PoolRecycle(packet);
				}
				SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint);
				break;
			case PacketProperty.ConnectAccept:
				if (flag)
				{
					NetConnectAcceptPacket netConnectAcceptPacket2 = NetConnectAcceptPacket.FromData(packet);
					if (netConnectAcceptPacket2 != null && actualValue.ProcessConnectAccept(netConnectAcceptPacket2))
					{
						CreateEvent(NetEvent.EType.Connect, actualValue, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
					}
				}
				break;
			default:
				if (flag)
				{
					actualValue.ProcessPacket(packet);
				}
				else
				{
					SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint);
				}
				break;
			}
		}

		internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer)
		{
			if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode)
			{
				NetEvent netEvent;
				lock (_eventLock)
				{
					netEvent = _netEventPoolHead;
					if (netEvent == null)
					{
						netEvent = new NetEvent(this);
					}
					else
					{
						_netEventPoolHead = netEvent.Next;
					}
				}
				netEvent.Next = null;
				netEvent.Type = NetEvent.EType.Receive;
				netEvent.DataReader.SetSource(packet, headerSize);
				netEvent.Peer = fromPeer;
				netEvent.DeliveryMethod = method;
				netEvent.ChannelNumber = channelNumber;
				ProcessEvent(netEvent);
				return;
			}
			lock (_eventLock)
			{
				NetEvent netEvent = _netEventPoolHead;
				if (netEvent == null)
				{
					netEvent = new NetEvent(this);
				}
				else
				{
					_netEventPoolHead = netEvent.Next;
				}
				netEvent.Next = null;
				netEvent.Type = NetEvent.EType.Receive;
				netEvent.DataReader.SetSource(packet, headerSize);
				netEvent.Peer = fromPeer;
				netEvent.DeliveryMethod = method;
				netEvent.ChannelNumber = channelNumber;
				if (_pendingEventTail == null)
				{
					_pendingEventHead = netEvent;
				}
				else
				{
					_pendingEventTail.Next = netEvent;
				}
				_pendingEventTail = netEvent;
			}
		}

		public void SendToAll(NetDataWriter writer, DeliveryMethod options)
		{
			SendToAll(writer.Data, 0, writer.Length, options);
		}

		public void SendToAll(byte[] data, DeliveryMethod options)
		{
			SendToAll(data, 0, data.Length, options);
		}

		public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
		{
			SendToAll(data, start, length, 0, options);
		}

		public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options)
		{
			SendToAll(writer.Data, 0, writer.Length, channelNumber, options);
		}

		public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
		{
			SendToAll(data, 0, data.Length, channelNumber, options);
		}

		public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
		{
			try
			{
				_peersLock.EnterReadLock();
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					netPeer.Send(data, start, length, channelNumber, options);
				}
			}
			finally
			{
				_peersLock.ExitReadLock();
			}
		}

		public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer);
		}

		public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, 0, data.Length, 0, options, excludePeer);
		}

		public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, start, length, 0, options, excludePeer);
		}

		public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer);
		}

		public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, 0, data.Length, channelNumber, options, excludePeer);
		}

		public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			try
			{
				_peersLock.EnterReadLock();
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					if (netPeer != excludePeer)
					{
						netPeer.Send(data, start, length, channelNumber, options);
					}
				}
			}
			finally
			{
				_peersLock.ExitReadLock();
			}
		}

		public bool Start()
		{
			return Start(0);
		}

		public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port)
		{
			return Start(addressIPv4, addressIPv6, port, manualMode: false);
		}

		public bool Start(string addressIPv4, string addressIPv6, int port)
		{
			IPAddress addressIPv7 = NetUtils.ResolveAddress(addressIPv4);
			IPAddress addressIPv8 = NetUtils.ResolveAddress(addressIPv6);
			return Start(addressIPv7, addressIPv8, port);
		}

		public bool Start(int port)
		{
			return Start(IPAddress.Any, IPAddress.IPv6Any, port);
		}

		public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port)
		{
			return Start(addressIPv4, addressIPv6, port, manualMode: true);
		}

		public bool StartInManualMode(string addressIPv4, string addressIPv6, int port)
		{
			IPAddress addressIPv7 = NetUtils.ResolveAddress(addressIPv4);
			IPAddress addressIPv8 = NetUtils.ResolveAddress(addressIPv6);
			return StartInManualMode(addressIPv7, addressIPv8, port);
		}

		public bool StartInManualMode(int port)
		{
			return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port);
		}

		public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint)
		{
			return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
		}

		public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port)
		{
			IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port);
			return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
		}

		public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint)
		{
			return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
		}

		public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
		{
			NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length);
			return SendRawAndRecycle(packet, remoteEndPoint) > 0;
		}

		public void TriggerUpdate()
		{
			_updateTriggerEvent.Set();
		}

		public void PollEvents(int maxProcessedEvents = 0)
		{
			if (_manualMode)
			{
				if (_udpSocketv4 != null)
				{
					ManualReceive(_udpSocketv4, _bufferEndPointv4, maxProcessedEvents);
				}
				if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4)
				{
					ManualReceive(_udpSocketv6, _bufferEndPointv6, maxProcessedEvents);
				}
			}
			else
			{
				if (UnsyncedEvents)
				{
					return;
				}
				NetEvent netEvent;
				lock (_eventLock)
				{
					netEvent = _pendingEventHead;
					_pendingEventHead = null;
					_pendingEventTail = null;
				}
				int num = 0;
				while (netEvent != null)
				{
					NetEvent next = netEvent.Next;
					ProcessEvent(netEvent);
					netEvent = next;
					num++;
					if (num == maxProcessedEvents)
					{
						break;
					}
				}
			}
		}

		public NetPeer Connect(string address, int port, string key)
		{
			return Connect(address, port, NetDataWriter.FromString(key));
		}

		public NetPeer Connect(string address, int port, NetDataWriter connectionData)
		{
			IPEndPoint target;
			try
			{
				target = NetUtils.MakeEndPoint(address, port);
			}
			catch
			{
				CreateEvent(NetEvent.EType.Disconnect, null, null, SocketError.Success, 0, DisconnectReason.UnknownHost, null, DeliveryMethod.Unreliable, 0);
				return null;
			}
			return Connect(target, connectionData);
		}

		public NetPeer Connect(IPEndPoint target, string key)
		{
			return Connect(target, NetDataWriter.FromString(key));
		}

		public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
		{
			if (!_isRunning)
			{
				throw new InvalidOperationException("Client is not running");
			}
			lock (_requestsDict)
			{
				if (_requestsDict.ContainsKey(target))
				{
					return null;
				}
				byte connectNum = 0;
				if (TryGetPeer(target, out var actualValue))
				{
					ConnectionState connectionState = actualValue.ConnectionState;
					if (connectionState == ConnectionState.Outgoing || connectionState == ConnectionState.Connected)
					{
						return actualValue;
					}
					connectNum = (byte)((actualValue.ConnectionNum + 1) % 4);
					RemovePeer(actualValue, enableWriteLock: true);
				}
				actualValue = new NetPeer(this, target, GetNextPeerId(), connectNum, connectionData);
				AddPeer(actualValue);
				return actualValue;
			}
		}

		public void Stop()
		{
			Stop(sendDisconnectMessages: true);
		}

		public void Stop(bool sendDisconnectMessages)
		{
			if (_isRunning)
			{
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages);
				}
				CloseSocket();
				_updateTriggerEvent.Set();
				if (!_manualMode)
				{
					_logicThread.Join();
					_logicThread = null;
				}
				ClearPeerSet();
				_peerIds = new ConcurrentQueue<int>();
				_lastPeerId = 0;
				_connectedPeersCount = 0L;
				_pendingEventHead = null;
				_pendingEventTail = null;
			}
		}

		[Conditional("DEBUG")]
		[Conditional("SIMULATE_NETWORK")]
		private void ClearPingSimulationList()
		{
			lock (_pingSimulationList)
			{
				_pingSimulationList.Clear();
			}
		}

		[Conditional("DEBUG")]
		[Conditional("SIMULATE_NETWORK")]
		private void ClearOutboundSimulationList()
		{
			lock (_outboundSimulationList)
			{
				_outboundSimulationList.Clear();
			}
		}

		public int GetPeersCount(ConnectionState peerState)
		{
			int num = 0;
			_peersLock.EnterReadLock();
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				if ((netPeer.ConnectionState & peerState) != 0)
				{
					num++;
				}
			}
			_peersLock.ExitReadLock();
			return num;
		}

		public void GetPeersNonAlloc(List<NetPeer> peers, ConnectionState peerState)
		{
			peers.Clear();
			_peersLock.EnterReadLock();
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				if ((netPeer.ConnectionState & peerState) != 0)
				{
					peers.Add(netPeer);
				}
			}
			_peersLock.ExitReadLock();
		}

		public void DisconnectAll()
		{
			DisconnectAll(null, 0, 0);
		}

		public void DisconnectAll(byte[] data, int start, int count)
		{
			_peersLock.EnterReadLock();
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				DisconnectPeer(netPeer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, force: false, data, start, count, null);
			}
			_peersLock.ExitReadLock();
		}

		public void DisconnectPeerForce(NetPeer peer)
		{
			DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, null);
		}

		public void DisconnectPeer(NetPeer peer)
		{
			DisconnectPeer(peer, null, 0, 0);
		}

		public void DisconnectPeer(NetPeer peer, byte[] data)
		{
			DisconnectPeer(peer, data, 0, data.Length);
		}

		public void DisconnectPeer(NetPeer peer, NetDataWriter writer)
		{
			DisconnectPeer(peer, writer.Data, 0, writer.Length);
		}

		public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count)
		{
			DisconnectPeer(peer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, force: false, data, start, count, null);
		}

		public void CreateNtpRequest(IPEndPoint endPoint)
		{
			_ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
		}

		public void CreateNtpRequest(string ntpServerAddress, int port)
		{
			IPEndPoint iPEndPoint = NetUtils.MakeEndPoint(ntpServerAddress, port);
			_ntpRequests.TryAdd(iPEndPoint, new NtpRequest(iPEndPoint));
		}

		public void CreateNtpRequest(string ntpServerAddress)
		{
			IPEndPoint iPEndPoint = NetUtils.MakeEndPoint(ntpServerAddress, 123);
			_ntpRequests.TryAdd(iPEndPoint, new NtpRequest(iPEndPoint));
		}

		public NetPeerEnumerator GetEnumerator()
		{
			return new NetPeerEnumerator(_headPeer);
		}

		IEnumerator<NetPeer> IEnumerable<NetPeer>.GetEnumerator()
		{
			return new NetPeerEnumerator(_headPeer);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return new NetPeerEnumerator(_headPeer);
		}

		private static int HashSetGetPrime(int min)
		{
			int[] primes = Primes;
			foreach (int num in primes)
			{
				if (num >= min)
				{
					return num;
				}
			}
			for (int j = min | 1; j < int.MaxValue; j += 2)
			{
				if (IsPrime(j) && (j - 1) % 101 != 0)
				{
					return j;
				}
			}
			return min;
			static bool IsPrime(int candidate)
			{
				if ((candidate & 1) != 0)
				{
					int num2 = (int)Math.Sqrt(candidate);
					for (int k = 3; k <= num2; k += 2)
					{
						if (candidate % k == 0)
						{
							return false;
						}
					}
					return true;
				}
				return candidate == 2;
			}
		}

		private void ClearPeerSet()
		{
			_peersLock.EnterWriteLock();
			_headPeer = null;
			if (_lastIndex > 0)
			{
				Array.Clear(_slots, 0, _lastIndex);
				Array.Clear(_buckets, 0, _buckets.Length);
				_lastIndex = 0;
				_count = 0;
				_freeList = -1;
			}
			_peersArray = new NetPeer[32];
			_peersLock.ExitWriteLock();
		}

		private bool ContainsPeer(NetPeer item)
		{
			if (item == null)
			{
				NetDebug.WriteError($"Contains peer null: {item}");
				return false;
			}
			if (_buckets != null)
			{
				int num = item.GetHashCode() & 0x7FFFFFFF;
				for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next)
				{
					if (_slots[num2].HashCode == num && _slots[num2].Value.Equals(item))
					{
						return true;
					}
				}
			}
			return false;
		}

		public NetPeer GetPeerById(int id)
		{
			if (id < 0 || id >= _peersArray.Length)
			{
				return null;
			}
			return _peersArray[id];
		}

		public bool TryGetPeerById(int id, out NetPeer peer)
		{
			peer = GetPeerById(id);
			return peer != null;
		}

		private void AddPeer(NetPeer peer)
		{
			if (peer == null)
			{
				NetDebug.WriteError($"Add peer null: {peer}");
				return;
			}
			_peersLock.EnterWriteLock();
			if (_headPeer != null)
			{
				peer.NextPeer = _headPeer;
				_headPeer.PrevPeer = peer;
			}
			_headPeer = peer;
			AddPeerToSet(peer);
			if (peer.Id >= _peersArray.Length)
			{
				int num = _peersArray.Length * 2;
				while (peer.Id >= num)
				{
					num *= 2;
				}
				Array.Resize(ref _peersArray, num);
			}
			_peersArray[peer.Id] = peer;
			_peersLock.ExitWriteLock();
		}

		private void RemovePeer(NetPeer peer, bool enableWriteLock)
		{
			if (enableWriteLock)
			{
				_peersLock.EnterWriteLock();
			}
			if (!RemovePeerFromSet(peer))
			{
				if (enableWriteLock)
				{
					_peersLock.ExitWriteLock();
				}
				return;
			}
			if (peer == _headPeer)
			{
				_headPeer = peer.NextPeer;
			}
			if (peer.PrevPeer != null)
			{
				peer.PrevPeer.NextPeer = peer.NextPeer;
			}
			if (peer.NextPeer != null)
			{
				peer.NextPeer.PrevPeer = peer.PrevPeer;
			}
			peer.PrevPeer = null;
			_peersArray[peer.Id] = null;
			_peerIds.Enqueue(peer.Id);
			if (enableWriteLock)
			{
				_peersLock.ExitWriteLock();
			}
		}

		private bool RemovePeerFromSet(NetPeer peer)
		{
			if (_buckets == null || peer == null)
			{
				return false;
			}
			int num = peer.GetHashCode() & 0x7FFFFFFF;
			int num2 = num % _buckets.Length;
			int num3 = -1;
			for (int num4 = _buckets[num2] - 1; num4 >= 0; num4 = _slots[num4].Next)
			{
				if (_slots[num4].HashCode == num && _slots[num4].Value.Equals(peer))
				{
					if (num3 < 0)
					{
						_buckets[num2] = _slots[num4].Next + 1;
					}
					else
					{
						_slots[num3].Next = _slots[num4].Next;
					}
					_slots[num4].HashCode = -1;
					_slots[num4].Value = null;
					_slots[num4].Next = _freeList;
					_count--;
					if (_count == 0)
					{
						_lastIndex = 0;
						_freeList = -1;
					}
					else
					{
						_freeList = num4;
					}
					return true;
				}
				num3 = num4;
			}
			return false;
		}

		private bool TryGetPeer(IPEndPoint endPoint, out NetPeer actualValue)
		{
			if (_buckets != null)
			{
				int num = endPoint.GetHashCode() & 0x7FFFFFFF;
				_peersLock.EnterReadLock();
				for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next)
				{
					if (_slots[num2].HashCode == num && _slots[num2].Value.Equals(endPoint))
					{
						actualValue = _slots[num2].Value;
						_peersLock.ExitReadLock();
						return true;
					}
				}
				_peersLock.ExitReadLock();
			}
			actualValue = null;
			return false;
		}

		private bool TryGetPeer(SocketAddress saddr, out NetPeer actualValue)
		{
			if (_buckets != null)
			{
				int num = saddr.GetHashCode() & 0x7FFFFFFF;
				_peersLock.EnterReadLock();
				for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next)
				{
					if (_slots[num2].HashCode == num && _slots[num2].Value.Serialize().Equals((object?)saddr))
					{
						actualValue = _slots[num2].Value;
						_peersLock.ExitReadLock();
						return true;
					}
				}
				_peersLock.ExitReadLock();
			}
			actualValue = null;
			return false;
		}

		private bool AddPeerToSet(NetPeer value)
		{
			if (_buckets == null)
			{
				int num = HashSetGetPrime(0);
				_buckets = new int[num];
				_slots = new Slot[num];
			}
			int num2 = value.GetHashCode() & 0x7FFFFFFF;
			int num3 = num2 % _buckets.Length;
			for (int num4 = _buckets[num2 % _buckets.Length] - 1; num4 >= 0; num4 = _slots[num4].Next)
			{
				if (_slots[num4].HashCode == num2 && _slots[num4].Value.Equals(value))
				{
					return false;
				}
			}
			int num5;
			if (_freeList >= 0)
			{
				num5 = _freeList;
				_freeList = _slots[num5].Next;
			}
			else
			{
				if (_lastIndex == _slots.Length)
				{
					int num6 = 2 * _count;
					num6 = (((uint)num6 > 2147483587u && 2147483587 > _count) ? 2147483587 : HashSetGetPrime(num6));
					Slot[] array = new Slot[num6];
					Array.Copy(_slots, 0, array, 0, _lastIndex);
					_buckets = new int[num6];
					for (int i = 0; i < _lastIndex; i++)
					{
						int num7 = array[i].HashCode % num6;
						array[i].Next = _buckets[num7] - 1;
						_buckets[num7] = i + 1;
					}
					_slots = array;
					num3 = num2 % _buckets.Length;
				}
				num5 = _lastIndex;
				_lastIndex++;
			}
			_slots[num5].HashCode = num2;
			_slots[num5].Value = value;
			_slots[num5].Next = _buckets[num3] - 1;
			_buckets[num3] = num5 + 1;
			_count++;
			return true;
		}

		private NetPacket PoolGetWithData(PacketProperty property, byte[] data, int start, int length)
		{
			int headerSize = NetPacket.GetHeaderSize(property);
			NetPacket netPacket = PoolGetPacket(length + headerSize);
			netPacket.Property = property;
			Buffer.BlockCopy(data, start, netPacket.RawData, headerSize, length);
			return netPacket;
		}

		private NetPacket PoolGetWithProperty(PacketProperty property, int size)
		{
			NetPacket netPacket = PoolGetPacket(size + NetPacket.GetHeaderSize(property));
			netPacket.Property = property;
			return netPacket;
		}

		private NetPacket PoolGetWithProperty(PacketProperty property)
		{
			NetPacket netPacket = PoolGetPacket(NetPacket.GetHeaderSize(property));
			netPacket.Property = property;
			return netPacket;
		}

		internal NetPacket PoolGetPacket(int size)
		{
			if (size > NetConstants.MaxPacketSize)
			{
				return new NetPacket(size);
			}
			NetPacket poolHead;
			lock (_poolLock)
			{
				poolHead = _poolHead;
				if (poolHead == null)
				{
					return new NetPacket(size);
				}
				_poolHead = _poolHead.Next;
				_poolCount--;
			}
			poolHead.Size = size;
			if (poolHead.RawData.Length < size)
			{
				poolHead.RawData = new byte[size];
			}
			return poolHead;
		}

		internal void PoolRecycle(NetPacket packet)
		{
			if (packet.RawData.Length > NetConstants.MaxPacketSize || _poolCount >= PacketPoolSize)
			{
				return;
			}
			packet.RawData[0] = 0;
			lock (_poolLock)
			{
				packet.Next = _poolHead;
				_poolHead = packet;
				_poolCount++;
			}
		}

		static NetManager()
		{
			Primes = new int[72]
			{
				3, 7, 11, 17, 23, 29, 37, 47, 59, 71,
				89, 107, 131, 163, 197, 239, 293, 353, 431, 521,
				631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371,
				4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023,
				25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363,
				156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403,
				968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559,
				5999471, 7199369
			};
			MulticastAddressV6 = IPAddress.Parse("ff02::1");
			IPv6Support = Socket.OSSupportsIPv6;
		}

		private bool ProcessError(SocketException ex)
		{
			switch (ex.SocketErrorCode)
			{
			case SocketError.NotConnected:
				NotConnected = true;
				return true;
			case SocketError.OperationAborted:
			case SocketError.Interrupted:
			case SocketError.NotSocket:
				return true;
			default:
				NetDebug.WriteError($"[R]Error code: {(int)ex.SocketErrorCode} - {ex}");
				CreateEvent(NetEvent.EType.Error, null, null, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
				break;
			case SocketError.WouldBlock:
			case SocketError.MessageSize:
			case SocketError.NetworkReset:
			case SocketError.ConnectionReset:
			case SocketError.TimedOut:
				break;
			}
			return false;
		}

		private void ManualReceive(Socket socket, EndPoint bufferEndPoint, int maxReceive)
		{
			try
			{
				int num = 0;
				while (socket.Available > 0)
				{
					ReceiveFrom(socket, ref bufferEndPoint);
					num++;
					if (num == maxReceive)
					{
						break;
					}
				}
			}
			catch (SocketException ex)
			{
				ProcessError(ex);
			}
			catch (ObjectDisposedException)
			{
			}
			catch (Exception ex3)
			{
				NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex3);
			}
		}

		private void NativeReceiveLogic()
		{
			IntPtr handle = _udpSocketv4.Handle;
			IntPtr s = _udpSocketv6?.Handle ?? IntPtr.Zero;
			byte[] address = new byte[16];
			byte[] address2 = new byte[28];
			IPEndPoint tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
			List<Socket> list = new List<Socket>(2);
			Socket udpSocketv = _udpSocketv4;
			Socket udpSocketv2 = _udpSocketv6;
			NetPacket packet = PoolGetPacket(NetConstants.MaxPacketSize);
			while (_isRunning)
			{
				try
				{
					if (udpSocketv2 == null)
					{
						if (!NativeReceiveFrom(handle, address))
						{
							break;
						}
						continue;
					}
					bool flag = false;
					if (udpSocketv.Available != 0 || list.Contains(udpSocketv))
					{
						if (!NativeReceiveFrom(handle, address))
						{
							break;
						}
						flag = true;
					}
					if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2))
					{
						if (!NativeReceiveFrom(s, address2))
						{
							break;
						}
						flag = true;
					}
					list.Clear();
					if (!flag)
					{
						list.Add(udpSocketv);
						list.Add(udpSocketv2);
						Socket.Select(list, null, null, ReceivePollingTime);
					}
				}
				catch (SocketException ex)
				{
					if (ProcessError(ex))
					{
						break;
					}
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				catch (ThreadAbortException)
				{
					break;
				}
				catch (Exception ex4)
				{
					NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex4);
				}
			}
			bool NativeReceiveFrom(IntPtr socketHandle, byte[] array)
			{
				int socketAddressSize = array.Length;
				packet.Size = NativeSocket.RecvFrom(socketHandle, packet.RawData, NetConstants.MaxPacketSize, array, ref socketAddressSize);
				if (packet.Size == 0)
				{
					return true;
				}
				if (packet.Size == -1)
				{
					return !ProcessError(new SocketException((int)NativeSocket.GetSocketError()));
				}
				short num = (short)((array[1] << 8) | array[0]);
				tempEndPoint.Port = (ushort)((array[2] << 8) | array[3]);
				if ((NativeSocket.UnixMode && num == 10) || (!NativeSocket.UnixMode && num == 23))
				{
					uint num2 = (uint)((array[27] << 24) + (array[26] << 16) + (array[25] << 8) + array[24]);
					byte[] array2 = new byte[16];
					Buffer.BlockCopy(array, 8, array2, 0, 16);
					tempEndPoint.Address = new IPAddress(array2, num2);
				}
				else
				{
					long newAddress = (uint)((array[4] & 0xFF) | ((array[5] << 8) & 0xFF00) | ((array[6] << 16) & 0xFF0000) | (array[7] << 24));
					tempEndPoint.Address = new IPAddress(newAddress);
				}
				if (TryGetPeer(tempEndPoint, out var actualValue))
				{
					OnMessageReceived(packet, actualValue);
				}
				else
				{
					OnMessageReceived(packet, tempEndPoint);
					tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
				}
				packet = PoolGetPacket(NetConstants.MaxPacketSize);
				return true;
			}
		}

		private void ReceiveFrom(Socket s, ref EndPoint bufferEndPoint)
		{
			NetPacket netPacket = PoolGetPacket(NetConstants.MaxPacketSize);
			netPacket.Size = s.ReceiveFrom(netPacket.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint);
			OnMessageReceived(netPacket, (IPEndPoint)bufferEndPoint);
		}

		private void ReceiveLogic()
		{
			EndPoint bufferEndPoint = new IPEndPoint(IPAddress.Any, 0);
			EndPoint bufferEndPoint2 = new IPEndPoint(IPAddress.IPv6Any, 0);
			List<Socket> list = new List<Socket>(2);
			Socket udpSocketv = _udpSocketv4;
			Socket udpSocketv2 = _udpSocketv6;
			while (_isRunning)
			{
				try
				{
					if (udpSocketv2 == null)
					{
						if (udpSocketv.Available != 0 || udpSocketv.Poll(ReceivePollingTime, SelectMode.SelectRead))
						{
							ReceiveFrom(udpSocketv, ref bufferEndPoint);
						}
						continue;
					}
					bool flag = false;
					if (udpSocketv.Available != 0 || list.Contains(udpSocketv))
					{
						ReceiveFrom(udpSocketv, ref bufferEndPoint);
						flag = true;
					}
					if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2))
					{
						ReceiveFrom(udpSocketv2, ref bufferEndPoint2);
						flag = true;
					}
					list.Clear();
					if (!flag)
					{
						list.Add(udpSocketv);
						list.Add(udpSocketv2);
						Socket.Select(list, null, null, ReceivePollingTime);
					}
				}
				catch (SocketException ex)
				{
					if (ProcessError(ex))
					{
						break;
					}
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				catch (ThreadAbortException)
				{
					break;
				}
				catch (Exception ex4)
				{
					NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex4);
				}
			}
		}

		public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode)
		{
			if (IsRunning && !NotConnected)
			{
				return false;
			}
			NotConnected = false;
			_manualMode = manualMode;
			UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported;
			_udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port)))
			{
				return false;
			}
			LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port;
			_isRunning = true;
			if (_manualMode)
			{
				_bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0);
			}
			if (IPv6Support && IPv6Enabled)
			{
				_udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
				if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort)))
				{
					if (_manualMode)
					{
						_bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0);
					}
				}
				else
				{
					_udpSocketv6 = null;
				}
			}
			if (!manualMode)
			{
				ThreadStart start = ReceiveLogic;
				if (UseNativeSockets)
				{
					start = NativeReceiveLogic;
				}
				_receiveThread = new Thread(start)
				{
					Name = $"ReceiveThread({LocalPort})",
					IsBackground = true
				};
				_receiveThread.Start();
				if (_logicThread == null)
				{
					_logicThread = new Thread(UpdateLogic)
					{
						Name = "LogicThread",
						IsBackground = true
					};
					_logicThread.Start();
				}
			}
			return true;
		}

		private bool BindSocket(Socket socket, IPEndPoint ep)
		{
			socket.ReceiveTimeout = 500;
			socket.SendTimeout = 500;
			socket.ReceiveBufferSize = 1048576;
			socket.SendBufferSize = 1048576;
			socket.Blocking = true;
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				try
				{
					socket.IOControl(-1744830452, new byte[1], null);
				}
				catch
				{
				}
			}
			try
			{
				socket.ExclusiveAddressUse = !ReuseAddress;
				socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress);
				socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute);
			}
			catch
			{
			}
			if (ep.AddressFamily == AddressFamily.InterNetwork)
			{
				Ttl = 255;
				try
				{
					socket.EnableBroadcast = true;
				}
				catch (SocketException ex)
				{
					NetDebug.WriteError($"[B]Broadcast error: {ex.SocketErrorCode}");
				}
				if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
				{
					try
					{
						socket.DontFragment = true;
					}
					catch (SocketException ex2)
					{
						NetDebug.WriteError($"[B]DontFragment error: {ex2.SocketErrorCode}");
					}
				}
			}
			try
			{
				socket.Bind(ep);
				if (ep.AddressFamily == AddressFamily.InterNetworkV6)
				{
					try
					{
						socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressV6));
					}
					catch (Exception)
					{
					}
				}
			}
			catch (SocketException ex4)
			{
				switch (ex4.SocketErrorCode)
				{
				case SocketError.AddressAlreadyInUse:
					if (socket.AddressFamily == AddressFamily.InterNetworkV6)
					{
						try
						{
							socket.DualMode = false;
							socket.Bind(ep);
						}
						catch (SocketException ex5)
						{
							NetDebug.WriteError($"[B]Bind exception: {ex5}, errorCode: {ex5.SocketErrorCode}");
							return false;
						}
						return true;
					}
					break;
				case SocketError.AddressFamilyNotSupported:
					return true;
				}
				NetDebug.WriteError($"[B]Bind exception: {ex4}, errorCode: {ex4.SocketErrorCode}");
				return false;
			}
			return true;
		}

		internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			int result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
			PoolRecycle(packet);
			return result;
		}

		internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
		}

		internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
		{
			if (!_isRunning)
			{
				return 0;
			}
			NetPacket netPacket = null;
			if (_extraPacketLayer != null)
			{
				netPacket = PoolGetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer);
				Buffer.BlockCopy(message, start, netPacket.RawData, 0, length);
				start = 0;
				_extraPacketLayer.ProcessOutBoundPacket(ref remoteEndPoint, ref netPacket.RawData, ref start, ref length);
				message = netPacket.RawData;
			}
			return SendRawCoreWithCleanup(message, start, length, remoteEndPoint, netPacket);
		}

		private int SendRawCoreWithCleanup(byte[] message, int start, int length, IPEndPoint remoteEndPoint, NetPacket expandedPacket)
		{
			try
			{
				return SendRawCore(message, start, length, remoteEndPoint);
			}
			finally
			{
				if (expandedPacket != null)
				{
					PoolRecycle(expandedPacket);
				}
			}
		}

		internal unsafe int SendRawCore(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
		{
			if (!_isRunning)
			{
				return 0;
			}
			Socket socket = _udpSocketv4;
			if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
			{
				socket = _udpSocketv6;
				if (socket == null)
				{
					return 0;
				}
			}
			int num;
			try
			{
				if (UseNativeSockets && remoteEndPoint is NetPeer netPeer)
				{
					fixed (byte* pinnedBuffer = &message[start])
					{
						num = NativeSocket.SendTo(socket.Handle, pinnedBuffer, length, netPeer.NativeAddress, netPeer.NativeAddress.Length);
					}
					if (num == -1)
					{
						throw NativeSocket.GetSocketException();
					}
				}
				else
				{
					num = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint);
				}
			}
			catch (SocketException ex)
			{
				switch (ex.SocketErrorCode)
				{
				case SocketError.Interrupted:
				case SocketError.NoBufferSpaceAvailable:
					return 0;
				case SocketError.MessageSize:
					return 0;
				case SocketError.NetworkUnreachable:
				case SocketError.HostUnreachable:
					if (DisconnectOnUnreachable && remoteEndPoint is NetPeer peer)
					{
						DisconnectPeerForce(peer, (ex.SocketErrorCode == SocketError.HostUnreachable) ? DisconnectReason.HostUnreachable : DisconnectReason.NetworkUnreachable, ex.SocketErrorCode, null);
					}
					CreateEvent(NetEvent.EType.Error, null, remoteEndPoint, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
					return -1;
				case SocketError.Shutdown:
					CreateEvent(NetEvent.EType.Error, null, remoteEndPoint, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
					return -1;
				default:
					NetDebug.WriteError($"[S] {ex}");
					return -1;
				}
			}
			catch (Exception arg)
			{
				NetDebug.WriteError($"[S] {arg}");
				return 0;
			}
			if (num <= 0)
			{
				return 0;
			}
			if (EnableStatistics)
			{
				Statistics.IncrementPacketsSent();
				Statistics.AddBytesSent(length);
			}
			return num;
		}

		public bool SendBroadcast(NetDataWriter writer, int port)
		{
			return SendBroadcast(writer.Data, 0, writer.Length, port);
		}

		public bool SendBroadcast(byte[] data, int port)
		{
			return SendBroadcast(data, 0, data.Length, port);
		}

		public bool SendBroadcast(byte[] data, int start, int length, int port)
		{
			if (!IsRunning)
			{
				return false;
			}
			NetPacket netPacket;
			if (_extraPacketLayer != null)
			{
				int headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast);
				netPacket = PoolGetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer);
				netPacket.Property = PacketProperty.Broadcast;
				Buffer.BlockCopy(data, start, netPacket.RawData, headerSize, length);
				int offset = 0;
				int length2 = length + headerSize;
				IPEndPoint endPoint = null;
				_extraPacketLayer.ProcessOutBoundPacket(ref endPoint, ref netPacket.RawData, ref offset, ref length2);
			}
			else
			{
				netPacket = PoolGetWithData(PacketProperty.Broadcast, data, start, length);
			}
			bool flag = false;
			bool flag2 = false;
			try
			{
				flag = _udpSocketv4.SendTo(netPacket.RawData, 0, netPacket.Size, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, port)) > 0;
				if (_udpSocketv6 != null)
				{
					flag2 = _udpSocketv6.SendTo(netPacket.RawData, 0, netPacket.Size, SocketFlags.None, new IPEndPoint(MulticastAddressV6, port)) > 0;
				}
			}
			catch (SocketException ex)
			{
				if (ex.SocketErrorCode == SocketError.HostUnreachable)
				{
					return flag;
				}
				NetDebug.WriteError($"[S][MCAST] {ex}");
				return flag;
			}
			catch (Exception arg)
			{
				NetDebug.WriteError($"[S][MCAST] {arg}");
				return flag;
			}
			finally
			{
				PoolRecycle(netPacket);
			}
			return flag || flag2;
		}

		private void CloseSocket()
		{
			_isRunning = false;
			if (_receiveThread != null && _receiveThread != Thread.CurrentThread)
			{
				_receiveThread.Join();
			}
			_receiveThread = null;
			_udpSocketv4?.Close();
			_udpSocketv6?.Close();
			_udpSocketv4 = null;
			_udpSocketv6 = null;
		}
	}
	internal enum PacketProperty : byte
	{
		Unreliable,
		Channeled,
		Ack,
		Ping,
		Pong,
		ConnectRequest,
		ConnectAccept,
		Disconnect,
		UnconnectedMessage,
		MtuCheck,
		MtuOk,
		Broadcast,
		Merged,
		ShutdownOk,
		PeerNotFound,
		InvalidProtocol,
		NatMessage,
		Empty
	}
	internal sealed class NetPacket
	{
		private static readonly int PropertiesCount;

		private static readonly int[] HeaderSizes;

		public byte[] RawData;

		public int Size;

		public object UserData;

		public NetPacket Next;

		public PacketProperty Property
		{
			get
			{
				return (PacketProperty)(RawData[0] & 0x1F);
			}
			set
			{
				RawData[0] = (byte)((uint)(RawData[0] & 0xE0) | (uint)value);
			}
		}

		public byte ConnectionNumber
		{
			get
			{
				return (byte)((RawData[0] & 0x60) >> 5);
			}
			set
			{
				RawData[0] = (byte)((RawData[0] & 0x9F) | (value << 5));
			}
		}

		public ushort Sequence
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 1);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 1, value);
			}
		}

		public bool IsFragmented => (RawData[0] & 0x80) != 0;

		public byte ChannelId
		{
			get
			{
				return RawData[3];
			}
			set
			{
				RawData[3] = value;
			}
		}

		public ushort FragmentId
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 4);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 4, value);
			}
		}

		public ushort FragmentPart
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 6);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 6, value);
			}
		}

		public ushort FragmentsTotal
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 8);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 8, value);
			}
		}

		static NetPacket()
		{
			PropertiesCount = Enum.GetValues(typeof(PacketProperty)).Length;
			HeaderSizes = NetUtils.AllocatePinnedUninitializedArray<int>(PropertiesCount);
			for (int i = 0; i < HeaderSizes.Length; i++)
			{
				switch ((PacketProperty)(byte)i)
				{
				case PacketProperty.Channeled:
				case PacketProperty.Ack:
					HeaderSizes[i] = 4;
					break;
				case PacketProperty.Ping:
					HeaderSizes[i] = 3;
					break;
				case PacketProperty.ConnectRequest:
					HeaderSizes[i] = 18;
					break;
				case PacketProperty.ConnectAccept:
					HeaderSizes[i] = 15;
					break;
				case PacketProperty.Disconnect:
					HeaderSizes[i] = 9;
					break;
				case PacketProperty.Pong:
					HeaderSizes[i] = 11;
					break;
				default:
					HeaderSizes[i] = 1;
					break;
				}
			}
		}

		public void MarkFragmented()
		{
			RawData[0] |= 128;
		}

		public NetPacket(int size)
		{
			RawData = new byte[size];
			Size = size;
		}

		public NetPacket(PacketProperty property, int size)
		{
			size += GetHeaderSize(property);
			RawData = new byte[size];
			Property = property;
			Size = size;
		}

		public static int GetHeaderSize(PacketProperty property)
		{
			return HeaderSizes[(uint)property];
		}

		public int GetHeaderSize()
		{
			return HeaderSizes[RawData[0] & 0x1F];
		}

		public bool Verify()
		{
			byte b = (byte)(RawData[0] & 0x1F);
			if (b >= PropertiesCount)
			{
				return false;
			}
			int num = HeaderSizes[b];
			bool flag = (RawData[0] & 0x80) != 0;
			if (Size >= num)
			{
				if (flag)
				{
					return Size >= num + 6;
				}
				return true;
			}
			return false;
		}
	}
	[Flags]
	public enum ConnectionState : byte
	{
		Outgoing = 2,
		Connected = 4,
		ShutdownRequested = 8,
		Disconnected = 0x10,
		EndPointChange = 0x20,
		Any = 0x2E
	}
	internal enum ConnectRequestResult
	{
		None,
		P2PLose,
		Reconnection,
		NewConnection
	}
	internal enum DisconnectResult
	{
		None,
		Reject,
		Disconnect
	}
	internal enum ShutdownResult
	{
		None,
		Success,
		WasConnected
	}
	public class NetPeer : IPEndPoint
	{
		private class IncomingFragments
		{
			public NetPacket[] Fragments;

			public int ReceivedCount;

			public int TotalSize;

			public byte ChannelId;
		}

		private int _rtt;

		private int _avgRtt;

		private int _rttCount;

		private double _resendDelay = 27.0;

		private float _pingSendTimer;

		private float _rttResetTimer;

		private readonly Stopwatch _pingTimer = new Stopwatch();

		private volatile float _timeSinceLastPacket;

		private long _remoteDelta;

		private readonly object _shutdownLock = new object();

		internal volatile NetPeer NextPeer;

		internal NetPeer PrevPeer;

		private NetPacket[] _unreliableSecondQueue;

		private NetPacket[] _unreliableChannel;

		private int _unreliablePendingCount;

		private readonly object _unreliableChannelLock = new object();

		private readonly ConcurrentQueue<BaseChannel> _channelSendQueue;

		private readonly BaseChannel[] _channels;

		private int _mtu;

		private int _mtuIdx;

		private bool _finishMtu;

		private float _mtuCheckTimer;

		private int _mtuCheckAttempts;

		private const int MtuCheckDelay = 1000;

		private const int MaxMtuCheckAttempts = 4;

		private readonly object _mtuMutex = new object();

		private int _fragmentId;

		private readonly Dictionary<ushort, IncomingFragments> _holdedFragments;

		private readonly Dictionary<ushort, ushort> _deliveredFragments;

		private readonly NetPacket _mergeData;

		private int _mergePos;

		private int _mergeCount;

		private int _connectAttempts;

		private float _connectTimer;

		private long _connectTime;

		private byte _connectNum;

		private ConnectionState _connectionState;

		private NetPacket _shutdownPacket;

		private const int ShutdownDelay = 300;

		private float _shutdownTimer;

		private readonly NetPacket _pingPacket;

		private readonly NetPacket _pongPacket;

		private readonly NetPacket _connectRequestPacket;

		private readonly NetPacket _connectAcceptPacket;

		public readonly NetManager NetManager;

		public readonly int Id;

		public object Tag;

		public readonly NetStatistics Statistics;

		private SocketAddress _cachedSocketAddr;

		private int _cachedHashCode;

		internal byte[] NativeAddress;

		internal byte ConnectionNum
		{
			get
			{
				return _connectNum;
			}
			private set
			{
				_connectNum = value;
				_mergeData.ConnectionNumber = value;
				_pingPacket.ConnectionNumber = value;
				_pongPacket.ConnectionNumber = value;
			}
		}

		public ConnectionState ConnectionState => _connectionState;

		internal long ConnectTime => _connectTime;

		public int RemoteId { get; private set; }

		public int Ping => _avgRtt / 2;

		public int RoundTripTime => _avgRtt;

		public int Mtu => _mtu;

		public long RemoteTimeDelta => _remoteDelta;

		public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta);

		public float TimeSinceLastPacket => _timeSinceLastPacket;

		internal double ResendDelay => _resendDelay;

		public override SocketAddress Serialize()
		{
			return _cachedSocketAddr;
		}

		public override int GetHashCode()
		{
			return _cachedHashCode;
		}

		internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id)
			: base(remoteEndPoint.Address, remoteEndPoint.Port)
		{
			Id = id;
			Statistics = new NetStatistics();
			NetManager = netManager;
			_cachedSocketAddr = base.Serialize();
			if (NetManager.UseNativeSockets)
			{
				NativeAddress = new byte[_cachedSocketAddr.Size];
				for (int i = 0; i < _cachedSocketAddr.Size; i++)
				{
					NativeAddress[i] = _cachedSocketAddr[i];
				}
			}
			_cachedHashCode = base.GetHashCode();
			ResetMtu();
			_connectionState = ConnectionState.Connected;
			_mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize);
			_pongPacket = new NetPacket(PacketProperty.Pong, 0);
			_pingPacket = new NetPacket(PacketProperty.Ping, 0)
			{
				Sequence = 1
			};
			_unreliableSecondQueue = new NetPacket[8];
			_unreliableChannel = new NetPacket[8];
			_holdedFragments = new Dictionary<ushort, IncomingFragments>();
			_deliveredFragments = new Dictionary<ushort, ushort>();
			_channels = new BaseChannel[netManager.ChannelsCount * 4];
			_channelSendQueue = new ConcurrentQueue<BaseChannel>();
		}

		internal void InitiateEndPointChange()
		{
			ResetMtu();
			_connectionState = ConnectionState.EndPointChange;
		}

		internal void FinishEndPointChange(IPEndPoint newEndPoint)
		{
			if (_connectionState != ConnectionState.EndPointChange)
			{
				return;
			}
			_connectionState = ConnectionState.Connected;
			base.Address = newEndPoint.Address;
			base.Port = newEndPoint.Port;
			_cachedSocketAddr = base.Serialize();
			if (NetManager.UseNativeSockets)
			{
				NativeAddress = new byte[_cachedSocketAddr.Size];
				for (int i = 0; i < _cachedSocketAddr.Size; i++)
				{
					NativeAddress[i] = _cachedSocketAddr[i];
				}
			}
			_cachedHashCode = base.GetHashCode();
		}

		internal void ResetMtu()
		{
			_finishMtu = !NetManager.MtuDiscovery;
			if (NetManager.MtuOverride > 0)
			{
				OverrideMtu(NetManager.MtuOverride);
			}
			else
			{
				SetMtu(0);
			}
		}

		private void SetMtu(int mtuIdx)
		{
			_mtuIdx = mtuIdx;
			_mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer;
		}

		private void OverrideMtu(int mtuValue)
		{
			_mtu = mtuValue;
			_finishMtu = true;
		}

		public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
		{
			int num = channelNumber * 4 + (ordered ? 2 : 0);
			BaseChannel baseChannel = _channels[num];
			if (baseChannel == null)
			{
				return 0;
			}
			return ((ReliableChannel)baseChannel).PacketsInQueue;
		}

		public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber)
		{
			int mtu = _mtu;
			NetPacket netPacket = NetManager.PoolGetPac

SULFUR Together.dll

Decompiled 19 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LiteNetLib;
using LiteNetLib.Layers;
using LiteNetLib.Utils;
using Microsoft.CodeAnalysis;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.CharacterStats;
using PerfectRandom.Sulfur.Core.Items;
using PerfectRandom.Sulfur.Core.LevelGeneration;
using PerfectRandom.Sulfur.Core.Stats;
using PerfectRandom.Sulfur.Core.UI;
using PerfectRandom.Sulfur.Core.Units;
using PerfectRandom.Sulfur.Core.Utilities;
using PerfectRandom.Sulfur.Core.Weapons;
using PerfectRandom.Sulfur.Core.World;
using Ryuka.Sulfur.NativeUI;
using SULFURTogether.Config;
using SULFURTogether.Logging;
using SULFURTogether.Networking;
using SULFURTogether.Networking.Gameplay;
using SULFURTogether.Networking.Gameplay.Boss;
using SULFURTogether.Patches;
using SULFURTogether.ReverseProbe;
using SULFURTogether.UI;
using SULFURTogether.UI.DownedRescueOverlay;
using SULFURTogether.UI.RunStatsOverlay;
using Steamworks;
using TMPro;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SULFURTogether
{
	internal static class ModInfo
	{
		public const string GUID = "com.ryuka.sulfur.together";

		public const string Name = "SULFUR Together";

		public const string Version = "1.0.0";

		public const string Author = "ryuka";
	}
	[BepInPlugin("com.ryuka.sulfur.together", "SULFUR Together", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private readonly Stopwatch _updateProf = new Stopwatch();

		public static Plugin Instance { get; private set; }

		public static STLogger Log { get; private set; }

		public static CoopConfig Cfg { get; private set; }

		private void Awake()
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b7: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Cfg = new CoopConfig(((BaseUnityPlugin)this).Config);
			Log = new STLogger(((BaseUnityPlugin)this).Logger, Cfg);
			Log.Info("v1.0.0 by ryuka loading...");
			Log.Info("[Build] CfgCleanup batch 11 (HostDrivenProxy pt2: proxy/attack-phase/interest-mgmt/target-proxy/coalescing + DisablePauseInMultiplayer): 30 functional/tuning flags hardcoded (Fixed), removed from .cfg. Log*/Debug diagnostics kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 10 (HostDrivenProxy pt1: health-sync + hit-request + terminal/flash/pending-death): 25 functional/tuning flags hardcoded (Fixed), removed from .cfg. 6 Log* kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 9 (NetworkEnemyTargetExperimental): 18 enemy projectile-visual + ranged-damage + target/combat-authority functional/tuning flags hardcoded (Fixed), removed from .cfg. 3 Log* kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 8 (NetworkEnemyStateExperimental): 28 enemy state/animation/snapshot/delta-compression functional+tuning flags hardcoded (Fixed), removed from .cfg. 4 Log* kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 7 (NetworkEnemyIntentExperimental): 8 intent-motion + host-authorized-intent functional/tuning flags hardcoded (Fixed; intent-driven motion stays OFF), removed from .cfg. 2 Log* kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 6 (PlayerWeapon): 16 weapon-sync + remote-player body/weapon appearance flags hardcoded (Fixed, appearance finalized), removed from .cfg. 5 Log* kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 5 (NetworkPlayerLifeExperimental): 8 downed/revive functional+tuning flags hardcoded (Fixed), removed from .cfg. LogPlayerLifeSync + PlayerReviveHoldKey (keybind) kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 4 (NetworkEnemy + NetworkGameplaySyncExperimental): 11 runtime-spawn + enemy-death-mirror functional/tuning flags hardcoded (Fixed), removed from .cfg. Log* kept. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 3 (NetworkVisualProxy): 9 remote-visual-proxy functional/tuning flags hardcoded (Fixed), removed from .cfg. 2026-06-30");
			Log.Info("[Build] CfgCleanup batch 2 (NetworkRunState+NetworkLevelSeed): EnableRunStateNegotiation/RunStateBroadcastIntervalSeconds/EnableLevelSeedAuthority/RequireSameLevelSeedForSceneMatch/ApplyHostLevelSeedOnManualFollow/HideRemoteVisualWhenLevelSeedMismatch/SyncHostUsedSetsOnManualFollow hardcoded (Fixed), removed from .cfg. Warn*/Log* kept. 2026-06-30");
			Log.Info("[Build] UI-CleanRole: networking role is runtime-only (NetworkMode/EnableNetworking dropped from the .cfg); connection settings (name/IP/port/key/maxplayers/version + EnableCoopToasts) moved to a private JSON store (CoopSettingsStore) so they stay out of Gale; retired .cfg keys pruned via OrphanedEntries reflection. + REGRESSION FIX: promoted Plan B targeting flags EnableRemotePlayerInPlayersList + EnableGhostPlayerHitbox into the dev-defaults (were local-cfg-only; a fresh/deleted config left enemies idle = 站桩). 2026-06-30");
			PatchBootstrap.ApplyAll(new Harmony("com.ryuka.sulfur.together"));
			WireCoopUi();
			CoopConnection.Initialize();
			bool isAvailable = SteamNetworkingSupport.IsAvailable;
			Log.Info($"[Config] SteamNetworkingAvailable={isAvailable}");
			SteamRichPresenceJoin.Initialize();
			SteamRelayBridge.Initialize();
			Log.Info("[ConfigPolicy] Private development build: active experimental gameplay defaults are forced on load; connection settings such as HostAddress/HostPort/PlayerName are left user-owned. The networking role is runtime-only (not persisted).");
			Log.Info($"[Config] EnableDebugLog={Cfg.EnableDebugLog.Value} | EnableReverseProbe={Cfg.EnableReverseProbe.Value} | NetMode={NetConfig.GetMode()}");
			Log.Info($"[Config] EnableInventorySerializationProbe={Cfg.EnableInventorySerializationProbe.Value} | EnableAiUpdateTargetProbe={Cfg.EnableAiUpdateTargetProbe.Value} | EnableAiSetDestinationProbe={Cfg.EnableAiSetDestinationProbe.Value}");
			Log.Info($"[Config] EnableVerbosePickupProbe={Cfg.EnableVerbosePickupProbe.Value} | EnableVerboseLootProbe={Cfg.EnableVerboseLootProbe.Value} | CompactPickupLogs={Cfg.CompactPickupLogs.Value} | CompactLootLogs={Cfg.CompactLootLogs.Value}");
			Log.Info($"[Config] EnableRunStateNegotiation={Cfg.EnableRunStateNegotiation.Value} | RunStateBroadcastIntervalSeconds={Cfg.RunStateBroadcastIntervalSeconds.Value} | WarnOnRunStateMismatch={Cfg.WarnOnRunStateMismatch.Value}");
			Log.Info($"[Config] EnableHostSceneAuthority={Cfg.EnableHostSceneAuthority.Value} | WarnOnClientSceneDrift={Cfg.WarnOnClientSceneDrift.Value}");
			Log.Info($"[Config] EnableHostSceneRequestProtocol={Cfg.EnableHostSceneRequestProtocol.Value} | AutoSendHostSceneRequestOnDrift={Cfg.AutoSendHostSceneRequestOnDrift.Value} | HostSceneRequestIntervalSeconds={Cfg.HostSceneRequestIntervalSeconds.Value}");
			Log.Info($"[Config] EnableManualClientSceneFollow={Cfg.EnableManualClientSceneFollow.Value} | ManualClientSceneFollowKey={Cfg.ManualClientSceneFollowKey.Value} | ManualClientSceneFollowRequiresHostRequest={Cfg.ManualClientSceneFollowRequiresHostRequest.Value}");
			Log.Info($"[Config] EnableLevelSeedAuthority={Cfg.EnableLevelSeedAuthority.Value} | RequireSameLevelSeedForSceneMatch={Cfg.RequireSameLevelSeedForSceneMatch.Value} | ApplyHostLevelSeedOnManualFollow={Cfg.ApplyHostLevelSeedOnManualFollow.Value} | HideRemoteVisualWhenLevelSeedMismatch={Cfg.HideRemoteVisualWhenLevelSeedMismatch.Value}");
			Log.Info($"[Config] EnableRemotePlayerVisualProxy={Cfg.EnableRemotePlayerVisualProxy.Value} | RemotePlayerTransformSendRateHz={Cfg.RemotePlayerTransformSendRateHz.Value} | RemotePlayerVisualTimeoutSeconds={Cfg.RemotePlayerVisualTimeoutSeconds.Value} | RemotePlayerVisualInterpolationSpeed={Cfg.RemotePlayerVisualInterpolationSpeed.Value} | RemotePlayerVisualSnapDistance={Cfg.RemotePlayerVisualSnapDistance.Value}");
			Log.Info($"[Config] EnableGameplayEntityProbe={Cfg.EnableGameplayEntityProbe.Value} | GameplayEntityProbeSummaryIntervalSeconds={Cfg.GameplayEntityProbeSummaryIntervalSeconds.Value} | LogGameplayEntitySpawn={Cfg.LogGameplayEntitySpawn.Value} | LogGameplayEntityDamage={Cfg.LogGameplayEntityDamage.Value} | LogGameplayEntityDeath={Cfg.LogGameplayEntityDeath.Value} | RequireStableSceneAndSeedForGameplayProbe={Cfg.RequireStableSceneAndSeedForGameplayProbe.Value}");
			Log.Info($"[Config] EnableHostEnemyDeathEventMirror={Cfg.EnableHostEnemyDeathEventMirror.Value} | LogReceivedEnemyDeathEvents={Cfg.LogReceivedEnemyDeathEvents.Value} | ApplyReceivedEnemyDeathEvents={Cfg.ApplyReceivedEnemyDeathEvents.Value} | EnemyDeathMirrorPositionTolerance={Cfg.EnemyDeathMirrorPositionTolerance.Value} | EnemyDeathMirrorUseHorizontalPositionTolerance={Cfg.EnemyDeathMirrorUseHorizontalPositionTolerance.Value}");
			Log.Info($"[Config] EnableClientEnemyDeathClaim={Cfg.EnableClientEnemyDeathClaim.Value} | LogReceivedClientEnemyDeathClaims={Cfg.LogReceivedClientEnemyDeathClaims.Value} | ApplyReceivedClientEnemyDeathClaimsOnHost={Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value}");
			Log.Info($"[Config] EnableCoopPlayerDownedRevive={Cfg.EnableCoopPlayerDownedRevive.Value} | PlayerDownedRescueTimeoutSeconds={Cfg.PlayerDownedRescueTimeoutSeconds.Value} | PlayerReviveHoldSeconds={Cfg.PlayerReviveHoldSeconds.Value} | PlayerReviveDistance={Cfg.PlayerReviveDistance.Value} | PlayerReviveHoldKey={Cfg.PlayerReviveHoldKey.Value} | PlayerReviveHealthRatio={Cfg.PlayerReviveHealthRatio.Value} | RequireReviveDistanceValidationOnHost={Cfg.RequireReviveDistanceValidationOnHost.Value}");
			Log.Info($"[Config] EnableHostEnemyStateSnapshotMirror={Cfg.EnableHostEnemyStateSnapshotMirror.Value} | EnemyStateSnapshotSendRateHz={Cfg.EnemyStateSnapshotSendRateHz.Value} | EnemyStateSnapshotMaxEnemiesPerPacket={Cfg.EnemyStateSnapshotMaxEnemiesPerPacket.Value} | OnlySendAliveEnemyStateSnapshots={Cfg.OnlySendAliveEnemyStateSnapshots.Value} | LogReceivedEnemyStateSnapshots={Cfg.LogReceivedEnemyStateSnapshots.Value} | ApplyReceivedEnemyStateSnapshots={Cfg.ApplyReceivedEnemyStateSnapshots.Value} | EnemyStateSnapshotPositionTolerance={Cfg.EnemyStateSnapshotPositionTolerance.Value}");
			Log.Info($"[Config] EnemyStateSnapshotInterpolationSpeed={Cfg.EnemyStateSnapshotInterpolationSpeed.Value} | EnemyStateSnapshotPlaybackDurationMultiplier={Cfg.EnemyStateSnapshotPlaybackDurationMultiplier.Value} | EnemyStateSnapshotSnapDistance={Cfg.EnemyStateSnapshotSnapDistance.Value} | EnemyStateSnapshotApplyRotationY={Cfg.EnemyStateSnapshotApplyRotationY.Value} | EnableClientEnemyAiSuppressionExperiment={Cfg.EnableClientEnemyAiSuppressionExperiment.Value} | SuppressClientEnemyAiWhenStateMirrorEnabled={Cfg.SuppressClientEnemyAiWhenStateMirrorEnabled.Value} | EnableClientEnemyPuppetMode={Cfg.EnableClientEnemyPuppetMode.Value} | ClientEnemyPuppetStaleReleaseSeconds={Cfg.ClientEnemyPuppetStaleReleaseSeconds.Value} | LogClientEnemyPuppetMode={Cfg.LogClientEnemyPuppetMode.Value}");
			Log.Info($"[Config] EnableHostEnemyAnimationMirror={Cfg.EnableHostEnemyAnimationMirror.Value} | ApplyReceivedEnemyAnimationMirror={Cfg.ApplyReceivedEnemyAnimationMirror.Value} | LogEnemyAnimationMirror={Cfg.LogEnemyAnimationMirror.Value} | EnemyAnimationMirrorCrossFadeSeconds={Cfg.EnemyAnimationMirrorCrossFadeSeconds.Value} | EnemyAnimationMirrorNormalizedTimeTolerance={Cfg.EnemyAnimationMirrorNormalizedTimeTolerance.Value} | EnemyAnimationMirrorApplyAnimatorStatePlayback={Cfg.EnemyAnimationMirrorApplyAnimatorStatePlayback.Value} | EnemyAnimationMirrorApplyHostCombatStatePlayback={Cfg.EnemyAnimationMirrorApplyHostCombatStatePlayback.Value} | EnemyAnimationMirrorReplayHostCombatMethods={Cfg.EnemyAnimationMirrorReplayHostCombatMethods.Value} | EnemyAnimationMirrorApplyCombatAnimatorFallback={Cfg.EnemyAnimationMirrorApplyCombatAnimatorFallback.Value} | EnemyAnimationMirrorHostCombatActionHoldSeconds={Cfg.EnemyAnimationMirrorHostCombatActionHoldSeconds.Value}");
			Log.Info($"[Config] EnableHostOnlyEnemyTargetAuthority={Cfg.EnableHostOnlyEnemyTargetAuthority.Value} | EnemyProjectileVisualMirrorEnabled={Cfg.EnemyProjectileVisualMirrorEnabled.Value} | EnemyProjectileVisualMirrorUseNativeShootReplay={Cfg.EnemyProjectileVisualMirrorUseNativeShootReplay.Value} | EnableGenericHostCombatAnimatorStateMirror={Cfg.EnableGenericHostCombatAnimatorStateMirror.Value} | EnableHostAuthoritativeEnemyRangedDamage={Cfg.EnableHostAuthoritativeEnemyRangedDamage.Value} | EnableClientEnemyIntentDrivenMotion={Cfg.EnableClientEnemyIntentDrivenMotion.Value} | EnemyIntentCorrectionDistance={Cfg.EnemyIntentCorrectionDistance.Value} | EnemyIntentHardSnapDistance={Cfg.EnemyIntentHardSnapDistance.Value} | LogEnemyTargetAuthority={Cfg.LogEnemyTargetAuthority.Value} | EnemyTargetAuthorityProbeIntervalSeconds={Cfg.EnemyTargetAuthorityProbeIntervalSeconds.Value} | EnableEnemyCombatProbe={Cfg.EnableEnemyCombatProbe.Value} | LogEnemyCombatProbe={Cfg.LogEnemyCombatProbe.Value} | EnemyHostProjectileHitRadius={Cfg.EnemyHostProjectileHitRadius.Value} | EnemyHostProjectileDamage={Cfg.EnemyHostProjectileDamage.Value}");
			Log.Info($"[Config] EnableEnemyStateSnapshotDeltaCompression={Cfg.EnableEnemyStateSnapshotDeltaCompression.Value} | EnemyStateSnapshotHeartbeatSeconds={Cfg.EnemyStateSnapshotHeartbeatSeconds.Value} | EnemyStateSnapshotPositionDeltaThreshold={Cfg.EnemyStateSnapshotPositionDeltaThreshold.Value} | EnemyStateSnapshotRotationDeltaThresholdDegrees={Cfg.EnemyStateSnapshotRotationDeltaThresholdDegrees.Value} | EnemyStateSnapshotAnimationTimeDeltaThreshold={Cfg.EnemyStateSnapshotAnimationTimeDeltaThreshold.Value}");
			Log.Info("Ready.");
		}

		private void WireCoopUi()
		{
			try
			{
				CoopLoc.Wire(((BaseUnityPlugin)this).Info.Location);
				Type type = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurPopupApi");
				if (type == null)
				{
					Log.Info("[ArenaLockdown] SULFUR Native UI Lib not present — confirm prompt will be logged only (UI optional).");
					return;
				}
				MethodInfo show = AccessTools.Method(type, "ShowBanner", new Type[1] { typeof(string) }, (Type[])null);
				MethodInfo hide = AccessTools.Method(type, "HideBanner", Type.EmptyTypes, (Type[])null);
				if (show == null || hide == null)
				{
					Log.Warn("[ArenaLockdown] SulfurPopupApi found but ShowBanner/HideBanner missing — prompt logged only.");
					return;
				}
				ArenaLockdownManager.ShowPrompt = delegate(string text)
				{
					show.Invoke(null, new object[1] { text });
				};
				ArenaLockdownManager.HidePrompt = delegate
				{
					hide.Invoke(null, null);
				};
				Log.Info("[ArenaLockdown] confirm prompt wired to SULFUR Native UI Lib banner (SulfurPopupApi).");
				Type type2 = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurToastApi");
				MethodInfo showToast = ((type2 == null) ? null : AccessTools.Method(type2, "Show", new Type[2]
				{
					typeof(string),
					typeof(string)
				}, (Type[])null));
				if (showToast != null)
				{
					CoopToasts.Wire(ArenaLockdownManager.ShowToast = delegate(string title, string msg)
					{
						showToast.Invoke(null, new object[2] { title, msg });
					});
					Log.Info("[CoopUi] toasts wired to SULFUR Native UI Lib (SulfurToastApi).");
				}
				else
				{
					Log.Info("[CoopUi] SulfurToastApi not present — toasts logged only.");
				}
				if (AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurOptionsApi") != null)
				{
					CoopConnectPage.Register();
					Log.Info("[CoopUi] connect page registered (SulfurOptionsApi).");
				}
				else
				{
					Log.Info("[CoopUi] SulfurOptionsApi not present — connect page unavailable.");
				}
			}
			catch (Exception ex)
			{
				Log.Warn("[CoopUi] failed to wire Native UI Lib surfaces: " + ex.Message);
			}
		}

		private void Update()
		{
			_updateProf.Restart();
			ReverseProbeSummary.Tick();
			PlayerVisualDiscoveryProbe.TryDumpOnce();
			PlayerSpriteAssetScanProbe.TryScanOnce();
			long elapsedMilliseconds = _updateProf.ElapsedMilliseconds;
			NetGameplayProbeManager.Tick();
			long num = _updateProf.ElapsedMilliseconds - elapsedMilliseconds;
			NetPlayerLifeManager.Tick();
			NetBossEncounterManager.Tick();
			long bossMs = _updateProf.ElapsedMilliseconds - elapsedMilliseconds - num;
			EmperorWormDiagnostics.FrameWatchdogTick();
			BossDynamicSpawnManifest.TickReleaseStaleGated();
			ArenaLockdownManager.Tick();
			RunStatsOverlayManager.Tick();
			DownedRescueOverlayManager.Tick();
			CoopConnection.Tick();
			PauseControlPatches.Tick();
			CoopConnectPage.Tick();
			EmperorWormDiagnostics.ReportUpdateProfile(_updateProf.ElapsedMilliseconds, num, bossMs);
			NetGameplayProbeManager.ClientFrameHitchTick(_updateProf.ElapsedMilliseconds, num);
		}

		private void FixedUpdate()
		{
			CoopConnection.FixedTick();
		}

		private void OnDestroy()
		{
			try
			{
				CoopConnectPage.Unregister();
			}
			catch
			{
			}
			try
			{
				RunStatsOverlayManager.Shutdown();
			}
			catch
			{
			}
			try
			{
				DownedRescueOverlayManager.Shutdown();
			}
			catch
			{
			}
			CoopConnection.Stop("plugin destroyed");
		}
	}
}
namespace SULFURTogether.UI
{
	internal static class CoopConnectPage
	{
		private const string PageId = "ryuka.sulfur.together";

		private const string RepoUrl = "https://github.com/ryuka-dev/SULFUR-Together";

		private const string KoFiUrl = "https://ko-fi.com/ryukadev";

		private static readonly Color ErrorColor = new Color(1f, 0.45f, 0.45f, 1f);

		private static readonly Color OkColor = new Color(0.55f, 1f, 0.65f, 1f);

		private static readonly Color NeutralColor = new Color(0.8f, 0.82f, 0.88f, 1f);

		private static bool _registered;

		private static SulfurOptionsContext _ctx;

		private static SulfurTextHandle _statusHandle;

		private static SulfurTextHandle _joinFeedbackHandle;

		private static SulfurTextHandle _gateHintHandle;

		private static SulfurTextHandle _lanIpHandle;

		private static SulfurButtonHandle _createHandle;

		private static SulfurButtonHandle _joinHandle;

		private static SulfurButtonHandle _closeHandle;

		private static SulfurListHandle _playerListHandle;

		private static string _lastPlayerSig = "\0";

		private static SulfurTextHandle _steamUnavailableHandle;

		private static SulfurTextHandle _yourSteamIdHandle;

		private static SulfurButtonHandle _steamInviteHandle;

		private static SulfurButtonHandle _steamJoinHandle;

		private static SulfurTextHandle _steamPendingInviteHandle;

		private static TMP_InputField _steamIdInputField;

		private static string _autoFilledSteamId;

		private static SulfurTextHandle _ffSessionHandle;

		private static OptionsScreenOption _ffToggleOption;

		private static bool _ffLockApplied;

		private static readonly FieldInfo FfCheckboxField = typeof(OptionsScreenOption).GetField("checkboxToggle", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo FfIsLockedField = typeof(OptionsScreenOption).GetField("IsLocked", BindingFlags.Instance | BindingFlags.NonPublic);

		private static string _draftName;

		private static string _draftAddress;

		private static string _draftPort;

		private static string _draftKey;

		private static string _draftSteamId;

		private static string _lastSavedSig;

		private static float _nextTick;

		private static bool _closeMenuOnJoinSuccess;

		public static void Register()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			if (!_registered)
			{
				SulfurOptionsApi.RegisterPage(new SulfurOptionsPage
				{
					PageId = "ryuka.sulfur.together",
					DisplayName = "SULFUR Together",
					SortOrder = 1000,
					BuildPage = BuildPage
				});
				_registered = true;
			}
		}

		public static void Unregister()
		{
			if (_registered)
			{
				SulfurOptionsApi.UnregisterPage("ryuka.sulfur.together");
				_registered = false;
				ResetHandles();
			}
		}

		public static void Tick()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (!_registered || _ctx == null || _statusHandle == null)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _nextTick)
			{
				return;
			}
			_nextTick = unscaledTime + 0.4f;
			try
			{
				AutoSaveDrafts();
				string text = StatusLine();
				_statusHandle.SetText(text);
				_statusHandle.SetColor(StatusColor());
				ApplyButtonStates();
				ApplyJoinFeedback();
				ApplyHostLanIp();
				ApplySteamState();
				ApplySessionFriendlyFireControl();
				RefreshPlayerList();
				PollJoinClose();
			}
			catch
			{
			}
		}

		private static void BuildPage(SulfurOptionsContext ctx)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_0301: Unknown result type (might be due to invalid IL or missing references)
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_036e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0404: Unknown result type (might be due to invalid IL or missing references)
			ResetHandles();
			_ctx = ctx;
			LoadDraftsFromConfig();
			ctx.AddSection("SULFUR Together");
			ctx.AddDescription(CoopLoc.Get("connect.desc.intro", "Early-preview co-op. Set up your connection below."));
			_statusHandle = ctx.AddTextRow(StatusLine());
			_statusHandle.SetColor(StatusColor());
			ctx.AddSection(CoopLoc.Get("connect.section.player", "Player"));
			ctx.AddInlineTextInput(CoopLoc.Get("connect.label.playerName", "Player name"), _draftName, (Action<string>)delegate(string v)
			{
				_draftName = v;
			}, 160f, false);
			ctx.AddSection(CoopLoc.Get("connect.section.connection", "Connection"));
			ctx.AddDescription(CoopLoc.Get("connect.desc.connection", "Host a co-op session or join one. Your settings save automatically as you edit them. Close room (host) / Leave (client) ends the session for you."));
			ctx.AddInlineTextInput(CoopLoc.Get("connect.label.hostAddress", "Host address (IP)"), _draftAddress, (Action<string>)delegate(string v)
			{
				_draftAddress = v;
			}, 160f, false);
			ctx.AddInlineTextInput(CoopLoc.Get("connect.label.port", "Port"), _draftPort, (Action<string>)delegate(string v)
			{
				_draftPort = v;
			}, 160f, false);
			ctx.AddInlineTextInput(CoopLoc.Get("connect.label.connectionKey", "Connection key"), _draftKey, (Action<string>)delegate(string v)
			{
				_draftKey = v;
			}, 160f, false);
			IReadOnlyList<SulfurButtonHandle> handles = ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[3]
			{
				new SulfurButton(CoopLoc.Get("connect.button.create", "Create game"), (Action)OnCreate, 170f),
				new SulfurButton(CoopLoc.Get("connect.button.join", "Join game"), (Action)OnJoin, 170f),
				new SulfurButton(CoopLoc.Get("connect.button.closeRoom", "Close room"), (Action)OnCloseRoom, 150f)
			});
			_createHandle = Handle(handles, 0);
			_joinHandle = Handle(handles, 1);
			_closeHandle = Handle(handles, 2);
			_gateHintHandle = ctx.AddTextRow("");
			_gateHintHandle.SetVisible(false);
			_lanIpHandle = ctx.AddTextRow("");
			_lanIpHandle.SetVisible(false);
			_joinFeedbackHandle = ctx.AddTextRow("");
			_joinFeedbackHandle.SetVisible(false);
			ctx.AddSection("Steam");
			ctx.AddDescription(CoopLoc.Get("connect.desc.steam", "Connect over Steam instead of a typed IP — no port forwarding needed. Works alongside Direct IP: a host can accept both at once. Create a game first — you can only invite friends while hosting."));
			_steamUnavailableHandle = ctx.AddTextRow(CoopLoc.Get("connect.steam.unavailable", "Steam is not available — connect method disabled."));
			_steamUnavailableHandle.SetColor(NeutralColor);
			_steamUnavailableHandle.SetVisible(false);
			_yourSteamIdHandle = ctx.AddTextRow("");
			_yourSteamIdHandle.SetVisible(false);
			_steamInviteHandle = Handle(ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[1]
			{
				new SulfurButton(CoopLoc.Get("connect.button.inviteFriends", "Invite Friends via Steam"), (Action)OnInviteFriends, 220f)
			}), 0);
			_steamIdInputField = ctx.AddInlineTextInput(CoopLoc.Get("connect.label.steamIdToJoin", "Steam ID to join"), _draftSteamId, (Action<string>)delegate(string v)
			{
				_draftSteamId = v;
			}, 160f, false);
			_steamJoinHandle = Handle(ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[1]
			{
				new SulfurButton(CoopLoc.Get("connect.button.joinViaSteam", "Join via Steam"), (Action)OnJoinViaSteam, 170f)
			}), 0);
			_steamPendingInviteHandle = ctx.AddTextRow("");
			_steamPendingInviteHandle.SetVisible(false);
			ctx.AddSection(CoopLoc.Get("connect.section.players", "Players in session"));
			_playerListHandle = ctx.AddList();
			_lastPlayerSig = "\0";
			ctx.AddSection(CoopLoc.Get("connect.section.localPrefs", "Local preferences (only affect you)"));
			ctx.AddToggle(CoopLoc.Get("connect.label.showToasts", "Show player join/leave notifications"), CoopLoc.Get("connect.desc.showToasts", "Brief top-right toasts when a player joins or leaves."), ReadBool(() => Plugin.Cfg.EnableCoopToasts.Value, fallback: true), (Action<bool>)delegate(bool v)
			{
				try
				{
					Plugin.Cfg.EnableCoopToasts.Value = v;
				}
				catch
				{
				}
			});
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.showHudStatus", "Show network status on HUD"), CoopLoc.Get("connect.value.comingSoon", "Coming soon"));
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.showNames", "Show other players' names"), CoopLoc.Get("connect.value.comingSoon", "Coming soon"));
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.rescueKey", "Rescue key"), KeyText(() => ((object)Plugin.Cfg.PlayerReviveHoldKey.Value/*cast due to .constrained prefix*/).ToString()));
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.confirmEnterKey", "Confirm-enter-boss-room key"), KeyText(() => ((object)Plugin.Cfg.ArenaEnterConfirmKey.Value/*cast due to .constrained prefix*/).ToString()));
			ctx.AddSection(CoopLoc.Get("connect.section.sessionSettings", "Session settings (host)"));
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.lootMode", "Loot mode"), CoopLoc.Get("connect.value.lootIndependent", "Independent (Shared coming soon)"));
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.clientMayStart", "Client may start next level"), CoopLoc.Get("connect.value.comingSoon", "Coming soon"));
			_ffToggleOption = ctx.AddToggle(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), CoopLoc.Get("connect.desc.friendlyFire", "Players can damage each other. The host's setting applies to the whole session."), ReadBool(() => Plugin.Cfg.FriendlyFire.Value, fallback: false), (Action<bool>)OnFriendlyFireToggled);
			_ffSessionHandle = ctx.AddTextRow("");
			_ffSessionHandle.SetVisible(false);
			ctx.AddSection(CoopLoc.Get("connect.section.about", "About"));
			ctx.AddReadonlyText(CoopLoc.Get("connect.label.version", "Version"), "1.0.0");
			ctx.AddSmallButton(CoopLoc.Get("connect.button.repo", "Open-source repo"), (Action)delegate
			{
				OpenUrl("https://github.com/ryuka-dev/SULFUR-Together");
			}, 260f);
			ctx.AddSmallButton(CoopLoc.Get("connect.button.kofi", "Support on Ko-fi"), (Action)delegate
			{
				OpenUrl("https://ko-fi.com/ryukadev");
			}, 260f);
			_lastSavedSig = DraftSig();
			ApplyButtonStates();
			ApplyJoinFeedback();
			ApplyHostLanIp();
			ApplySteamState();
			ApplySessionFriendlyFireControl();
			RefreshPlayerList();
		}

		private static string StatusLine()
		{
			NetService service = CoopConnection.Service;
			if (service == null)
			{
				return "● " + CoopLoc.Get("connect.status.notConnected", "Not connected");
			}
			if (NetConnectFeedback.Connecting)
			{
				return "◌ " + CoopLoc.Get("connect.status.connecting", "Connecting…");
			}
			return "● " + service.GetConnectionSummary();
		}

		private static Color StatusColor()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			if (CoopConnection.Service == null)
			{
				return NeutralColor;
			}
			if (!NetConnectFeedback.Connecting)
			{
				return OkColor;
			}
			return NeutralColor;
		}

		private static void ApplyButtonStates()
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			NetMode currentMode = CoopConnection.CurrentMode;
			bool flag = IsInGame();
			SulfurButtonHandle createHandle = _createHandle;
			if (createHandle != null)
			{
				createHandle.SetInteractable(flag && currentMode != NetMode.Client);
			}
			SulfurButtonHandle joinHandle = _joinHandle;
			if (joinHandle != null)
			{
				joinHandle.SetInteractable(flag && currentMode == NetMode.Off);
			}
			SulfurButtonHandle closeHandle = _closeHandle;
			if (closeHandle != null)
			{
				closeHandle.SetInteractable(currentMode != NetMode.Off);
			}
			SulfurButtonHandle closeHandle2 = _closeHandle;
			if (closeHandle2 != null)
			{
				closeHandle2.SetLabel((currentMode == NetMode.Client) ? CoopLoc.Get("connect.button.leave", "Leave") : CoopLoc.Get("connect.button.closeRoom", "Close room"));
			}
			if (_gateHintHandle != null)
			{
				bool flag2 = !flag && currentMode == NetMode.Off;
				if (flag2)
				{
					_gateHintHandle.SetText(CoopLoc.Get("connect.gateHint", "Load a save first — co-op is hosted / joined from inside the game."));
					_gateHintHandle.SetColor(NeutralColor);
				}
				_gateHintHandle.SetVisible(flag2);
			}
		}

		private static void ApplyJoinFeedback()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			if (_joinFeedbackHandle != null)
			{
				string lastError = NetConnectFeedback.LastError;
				if (string.IsNullOrEmpty(lastError))
				{
					_joinFeedbackHandle.SetVisible(false);
					return;
				}
				_joinFeedbackHandle.SetText("⚠ " + lastError);
				_joinFeedbackHandle.SetColor(ErrorColor);
				_joinFeedbackHandle.SetVisible(true);
			}
		}

		private static void ApplyHostLanIp()
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			if (_lanIpHandle == null)
			{
				return;
			}
			if (CoopConnection.CurrentMode == NetMode.Host && NetLocalAddress.TryGetLanIPv4(out string ip))
			{
				int num = ReadInt(() => Plugin.Cfg.HostPort.Value, 9050);
				_lanIpHandle.SetText(CoopLoc.Format("connect.lanAddress", "Your LAN address: {ip}:{port}  (others on your network join with this)", ("ip", ip.ToString()), ("port", num.ToString())));
				_lanIpHandle.SetColor(OkColor);
				_lanIpHandle.SetVisible(true);
			}
			else
			{
				_lanIpHandle.SetVisible(false);
			}
		}

		private static void ApplySteamState()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			bool flag = SteamNetworkingSupportAvailable();
			if (_steamUnavailableHandle != null)
			{
				_steamUnavailableHandle.SetVisible(!flag);
			}
			if (_yourSteamIdHandle != null)
			{
				if (flag && TryGetLocalSteamId(out var steamId))
				{
					_yourSteamIdHandle.SetText(CoopLoc.Format("connect.steam.yourId", "Your Steam ID: {id}  (share this, or use Invite Friends while hosting)", ("id", steamId.ToString())));
					_yourSteamIdHandle.SetColor(NeutralColor);
					_yourSteamIdHandle.SetVisible(true);
				}
				else
				{
					_yourSteamIdHandle.SetVisible(false);
				}
			}
			NetMode currentMode = CoopConnection.CurrentMode;
			bool flag2 = IsInGame();
			SulfurButtonHandle steamInviteHandle = _steamInviteHandle;
			if (steamInviteHandle != null)
			{
				steamInviteHandle.SetInteractable(flag && flag2 && currentMode == NetMode.Host);
			}
			SulfurButtonHandle steamInviteHandle2 = _steamInviteHandle;
			if (steamInviteHandle2 != null)
			{
				steamInviteHandle2.SetLabel(CoopConnection.SteamHostingEnabled ? CoopLoc.Get("connect.button.inviteMoreFriends", "Invite more friends via Steam") : CoopLoc.Get("connect.button.inviteFriends", "Invite Friends via Steam"));
			}
			SulfurButtonHandle steamJoinHandle = _steamJoinHandle;
			if (steamJoinHandle != null)
			{
				steamJoinHandle.SetInteractable(flag && flag2 && currentMode == NetMode.Off);
			}
			if (_steamPendingInviteHandle == null)
			{
				return;
			}
			CSteamID? pendingInviteHostId = SteamRichPresenceJoin.PendingInviteHostId;
			if (pendingInviteHostId.HasValue && currentMode == NetMode.Off)
			{
				string text = pendingInviteHostId.Value.m_SteamID.ToString();
				string pendingInviteFriendName = SteamRichPresenceJoin.PendingInviteFriendName;
				if (flag2 && _autoFilledSteamId != text)
				{
					_autoFilledSteamId = text;
					_draftSteamId = text;
					if ((Object)(object)_steamIdInputField != (Object)null)
					{
						_steamIdInputField.text = text;
					}
					_steamPendingInviteHandle.SetText(string.IsNullOrEmpty(pendingInviteFriendName) ? CoopLoc.Get("connect.steam.joiningFriend", "Joining a Steam friend's game…") : CoopLoc.Format("connect.steam.joiningNamed", "Joining {name}'s SULFUR Together game…", ("name", pendingInviteFriendName)));
					_steamPendingInviteHandle.SetColor(OkColor);
					_steamPendingInviteHandle.SetVisible(true);
					JoinViaSteam(pendingInviteHostId.Value, "steam-invite-auto-join");
				}
				else if (!flag2)
				{
					_steamPendingInviteHandle.SetText(string.IsNullOrEmpty(pendingInviteFriendName) ? CoopLoc.Get("connect.steam.invitedLoadSave", "A Steam friend invited you — load a save to join automatically.") : CoopLoc.Format("connect.steam.invitedLoadSaveNamed", "{name} invited you — load a save to join automatically.", ("name", pendingInviteFriendName)));
					_steamPendingInviteHandle.SetColor(OkColor);
					_steamPendingInviteHandle.SetVisible(true);
				}
			}
			else
			{
				_steamPendingInviteHandle.SetVisible(false);
			}
		}

		private static bool SteamNetworkingSupportAvailable()
		{
			try
			{
				return SteamNetworkingSupport.IsAvailable;
			}
			catch
			{
				return false;
			}
		}

		private static bool TryGetLocalSteamId(out ulong steamId64)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			steamId64 = 0uL;
			try
			{
				if (!SteamNetworkingSupport.TryGetLocalSteamId(out var id))
				{
					return false;
				}
				steamId64 = id.m_SteamID;
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static void RefreshPlayerList()
		{
			if (_playerListHandle == null)
			{
				return;
			}
			NetService service = CoopConnection.Service;
			IReadOnlyList<string> readOnlyList2;
			if (service == null)
			{
				IReadOnlyList<string> readOnlyList = Array.Empty<string>();
				readOnlyList2 = readOnlyList;
			}
			else
			{
				readOnlyList2 = service.GetPlayerRows();
			}
			IReadOnlyList<string> rows = readOnlyList2;
			string text = string.Join("|", rows);
			if (text == _lastPlayerSig)
			{
				return;
			}
			_lastPlayerSig = text;
			_playerListHandle.Update((Action<SulfurOptionsContext>)delegate(SulfurOptionsContext c)
			{
				if (rows.Count == 0)
				{
					c.AddTextRow(CoopLoc.Get("connect.players.none", "No players connected."));
					return;
				}
				foreach (string item in rows)
				{
					c.AddTextRow(item);
				}
			});
		}

		private static void OnCreate()
		{
			if (IsInGame())
			{
				SaveSettings();
				CoopConnection.Apply(NetMode.Host, "ui-create");
				ApplyButtonStates();
			}
		}

		private static void OnJoin()
		{
			if (IsInGame())
			{
				_closeMenuOnJoinSuccess = true;
				SaveSettings();
				CoopConnection.Apply(NetMode.Client, "ui-join");
				NetLinkState.SetClientLinked(on: true, "ui-join");
				ApplyButtonStates();
				ApplyJoinFeedback();
			}
		}

		private static void PollJoinClose()
		{
			if (_closeMenuOnJoinSuccess && !NetConnectFeedback.Connecting)
			{
				bool num = CoopConnection.CurrentMode == NetMode.Client && string.IsNullOrEmpty(NetConnectFeedback.LastError);
				_closeMenuOnJoinSuccess = false;
				if (num)
				{
					CoopMenu.CloseIfOpen("ui-join-success");
				}
			}
		}

		private static void OnInviteFriends()
		{
			if (IsInGame() && CoopConnection.CurrentMode == NetMode.Host)
			{
				CoopConnection.EnableSteamHosting("ui-steam-invite");
				ApplySteamState();
			}
		}

		private static void OnJoinViaSteam()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (IsInGame())
			{
				if (!ulong.TryParse((_draftSteamId ?? "").Trim(), out var result) || result == 0L)
				{
					NetConnectFeedback.ReportError(CoopLoc.Get("connect.error.invalidSteamId", "Enter a valid Steam ID (numbers only) — or use Invite Friends / accept a Steam invite."));
				}
				else
				{
					JoinViaSteam(new CSteamID(result), "ui-join-steam");
				}
			}
		}

		private static void JoinViaSteam(CSteamID hostId, string reason)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			Plugin.Cfg.LastSteamIdToJoin.Value = hostId.m_SteamID.ToString();
			SteamRichPresenceJoin.ConsumePendingInvite();
			_closeMenuOnJoinSuccess = true;
			CoopConnection.ApplySteamClient(hostId, reason);
			NetLinkState.SetClientLinked(on: true, reason);
			ApplyButtonStates();
			ApplyJoinFeedback();
		}

		private static void OnCloseRoom()
		{
			_closeMenuOnJoinSuccess = false;
			CoopConnection.Stop("ui-close-room");
			ApplyButtonStates();
		}

		private static void LoadDraftsFromConfig()
		{
			_draftName = Plugin.Cfg.PlayerName.Value;
			_draftAddress = Plugin.Cfg.HostAddress.Value;
			_draftPort = Plugin.Cfg.HostPort.Value.ToString();
			_draftKey = Plugin.Cfg.ConnectionKey.Value;
			_draftSteamId = Plugin.Cfg.LastSteamIdToJoin.Value;
			if ((string.IsNullOrWhiteSpace(_draftName) || _draftName == "Player") && SteamIdentity.TryGetPersonaName(out string name))
			{
				_draftName = name;
			}
		}

		private static void SaveSettings()
		{
			Plugin.Cfg.PlayerName.Value = (string.IsNullOrWhiteSpace(_draftName) ? "Player" : _draftName.Trim());
			Plugin.Cfg.HostAddress.Value = (string.IsNullOrWhiteSpace(_draftAddress) ? "127.0.0.1" : _draftAddress.Trim());
			if (int.TryParse(_draftPort, out var result) && result > 0 && result < 65536)
			{
				Plugin.Cfg.HostPort.Value = result;
			}
			Plugin.Cfg.ConnectionKey.Value = _draftKey ?? "";
			Plugin.Cfg.LastSteamIdToJoin.Value = _draftSteamId ?? "";
		}

		private static string DraftSig()
		{
			return _draftName + "\0" + _draftAddress + "\0" + _draftPort + "\0" + _draftKey + "\0" + _draftSteamId;
		}

		private static void AutoSaveDrafts()
		{
			string text = DraftSig();
			if (!(text == _lastSavedSig))
			{
				_lastSavedSig = text;
				SaveSettings();
			}
		}

		private static void ResetHandles()
		{
			_ctx = null;
			_statusHandle = null;
			_joinFeedbackHandle = null;
			_gateHintHandle = null;
			_lanIpHandle = null;
			_createHandle = null;
			_joinHandle = null;
			_playerListHandle = null;
			_lastPlayerSig = "\0";
			_steamUnavailableHandle = null;
			_yourSteamIdHandle = null;
			_steamInviteHandle = null;
			_steamJoinHandle = null;
			_steamPendingInviteHandle = null;
			_steamIdInputField = null;
			_ffSessionHandle = null;
			_ffToggleOption = null;
			_ffLockApplied = false;
		}

		private static void OnFriendlyFireToggled(bool value)
		{
			if (CoopConnection.CurrentMode == NetMode.Client)
			{
				ApplySessionFriendlyFireControl();
				return;
			}
			bool flag = false;
			try
			{
				flag = Plugin.Cfg.FriendlyFire.Value != value;
				Plugin.Cfg.FriendlyFire.Value = value;
			}
			catch
			{
			}
			try
			{
				if (CoopConnection.CurrentMode == NetMode.Host)
				{
					CoopConnection.Service?.BroadcastSessionSettings("ui-toggle");
					if (flag)
					{
						CoopToasts.NotifySessionSetting(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), value);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log?.Warn("[CoopUi] friendly-fire broadcast failed: " + ex.Message);
			}
		}

		private static void ApplySessionFriendlyFireControl()
		{
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			bool flag = CoopConnection.CurrentMode == NetMode.Client;
			if ((Object)(object)_ffToggleOption != (Object)null)
			{
				object? obj = FfCheckboxField?.GetValue(_ffToggleOption);
				Toggle val = (Toggle)((obj is Toggle) ? obj : null);
				if (flag != _ffLockApplied)
				{
					ApplyFfRowLock(flag);
					_ffLockApplied = flag;
				}
				bool flag2 = (flag ? NetSessionSettings.FriendlyFireEnabled : ReadBool(() => Plugin.Cfg.FriendlyFire.Value, fallback: false));
				if ((Object)(object)val != (Object)null && val.isOn != flag2)
				{
					val.SetIsOnWithoutNotify(flag2);
				}
			}
			if (_ffSessionHandle != null)
			{
				if (flag)
				{
					_ffSessionHandle.SetText(CoopLoc.Format("connect.ffSession", "Session friendly fire: {state} (set by host)", ("state", NetSessionSettings.FriendlyFireEnabled ? CoopLoc.Get("common.onUpper", "ON") : CoopLoc.Get("common.offUpper", "OFF"))));
					_ffSessionHandle.SetColor(NeutralColor);
					_ffSessionHandle.SetVisible(true);
				}
				else
				{
					_ffSessionHandle.SetVisible(false);
				}
			}
		}

		private static void ApplyFfRowLock(bool locked)
		{
			if (!((Object)(object)_ffToggleOption == (Object)null))
			{
				try
				{
					FfIsLockedField?.SetValue(_ffToggleOption, locked);
				}
				catch
				{
				}
				GameObject gameObject = ((Component)_ffToggleOption).gameObject;
				CanvasGroup val = gameObject.GetComponent<CanvasGroup>();
				if ((Object)(object)val == (Object)null)
				{
					val = gameObject.AddComponent<CanvasGroup>();
				}
				val.alpha = (locked ? 0.45f : 1f);
				val.interactable = !locked;
				val.blocksRaycasts = !locked;
			}
		}

		private static SulfurButtonHandle Handle(IReadOnlyList<SulfurButtonHandle> handles, int index)
		{
			if (handles == null || index < 0 || index >= handles.Count)
			{
				return null;
			}
			return handles[index];
		}

		private static bool ReadBool(Func<bool> read, bool fallback)
		{
			try
			{
				return read();
			}
			catch
			{
				return fallback;
			}
		}

		private static int ReadInt(Func<int> read, int fallback)
		{
			try
			{
				return read();
			}
			catch
			{
				return fallback;
			}
		}

		private static string KeyText(Func<string> read)
		{
			try
			{
				return read();
			}
			catch
			{
				return "(unset)";
			}
		}

		private static void OpenUrl(string url)
		{
			try
			{
				Application.OpenURL(url);
			}
			catch (Exception ex)
			{
				Plugin.Log?.Warn("[CoopUi] open URL failed: " + ex.Message);
			}
		}

		private static bool IsInGame()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Scene activeScene = SceneManager.GetActiveScene();
				return string.Equals(((Scene)(ref activeScene)).name ?? "", "GameScene", StringComparison.OrdinalIgnoreCase);
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class CoopLoc
	{
		private static Func<string, string, string, string>? _get;

		private static Func<int>? _languageVersion;

		public static int LanguageVersion
		{
			get
			{
				Func<int> languageVersion = _languageVersion;
				if (languageVersion == null)
				{
					return 0;
				}
				try
				{
					return languageVersion();
				}
				catch
				{
					return 0;
				}
			}
		}

		public static void Wire(string pluginAssemblyLocation)
		{
			try
			{
				Type type = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurLocalization");
				if (type == null)
				{
					Plugin.Log?.Info("[CoopLoc] SulfurLocalization not present — player-facing text stays English.");
					return;
				}
				MethodInfo methodInfo = AccessTools.Method(type, "LoadPluginLocalization", new Type[2]
				{
					typeof(string),
					typeof(string)
				}, (Type[])null);
				if (methodInfo != null && !string.IsNullOrEmpty(pluginAssemblyLocation))
				{
					methodInfo.Invoke(null, new object[2] { "com.ryuka.sulfur.together", pluginAssemblyLocation });
				}
				else
				{
					Plugin.Log?.Warn("[CoopLoc] LoadPluginLocalization missing or no assembly location — lang files not loaded.");
				}
				MethodInfo methodInfo2 = AccessTools.Method(type, "Get", new Type[3]
				{
					typeof(string),
					typeof(string),
					typeof(string)
				}, (Type[])null);
				if (methodInfo2 != null)
				{
					_get = (Func<string, string, string, string>)Delegate.CreateDelegate(typeof(Func<string, string, string, string>), methodInfo2);
				}
				MethodInfo methodInfo3 = AccessTools.PropertyGetter(type, "LanguageVersion");
				if (methodInfo3 != null)
				{
					_languageVersion = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), methodInfo3);
				}
				Plugin.Log?.Info($"[CoopLoc] localization wired (lookup={_get != null}, languageVersion={_languageVersion != null}).");
			}
			catch (Exception ex)
			{
				Plugin.Log?.Warn("[CoopLoc] wire failed — player-facing text stays English: " + ex.Message);
			}
		}

		public static string Get(string key, string fallback)
		{
			Func<string, string, string, string> get = _get;
			if (get == null)
			{
				return fallback;
			}
			try
			{
				return get("com.ryuka.sulfur.together", key, fallback);
			}
			catch
			{
				return fallback;
			}
		}

		public static string Format(string key, string fallback, params (string token, string value)[] args)
		{
			string text = Get(key, fallback);
			if (args != null)
			{
				for (int i = 0; i < args.Length; i++)
				{
					(string token, string value) tuple = args[i];
					string item = tuple.token;
					string item2 = tuple.value;
					text = text.Replace("{" + item + "}", item2 ?? "");
				}
			}
			return text;
		}
	}
	internal static class CoopToasts
	{
		private static Action<string, string> _show;

		public static void Wire(Action<string, string> showToast)
		{
			_show = showToast;
		}

		public static void Notify(string message)
		{
			Notify(null, message);
		}

		public static void NotifySessionSetting(string settingLabel, bool enabled)
		{
			Notify(null, CoopLoc.Format("session.settingChanged", "{label}: {state}", ("label", settingLabel), ("state", enabled ? CoopLoc.Get("common.on", "On") : CoopLoc.Get("common.off", "Off"))), respectPreference: false);
		}

		public static void Notify(string title, string message)
		{
			Notify(title, message, respectPreference: true);
		}

		private static void Notify(string title, string message, bool respectPreference)
		{
			if (string.IsNullOrEmpty(message))
			{
				return;
			}
			if (respectPreference)
			{
				bool flag = true;
				try
				{
					flag = Plugin.Cfg.EnableCoopToasts.Value;
				}
				catch
				{
				}
				if (!flag)
				{
					return;
				}
			}
			string text = (string.IsNullOrEmpty(title) ? CoopLoc.Get("toast.title.default", "Together") : title);
			Plugin.Log?.Info("[CoopToast] " + text + ": " + message);
			try
			{
				_show?.Invoke(text, message);
			}
			catch (Exception ex)
			{
				Plugin.Log?.Warn("[CoopToast] toast show failed: " + ex.Message);
			}
		}
	}
	internal static class NativeFontSampler
	{
		public static TMP_FontAsset? ResolveNativeFont()
		{
			try
			{
				TextMeshProUGUI[] array = Object.FindObjectsByType<TextMeshProUGUI>((FindObjectsSortMode)0);
				foreach (TextMeshProUGUI val in array)
				{
					if (!((Object)(object)val == (Object)null) && !((Object)(object)((TMP_Text)val).font == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
					{
						return ((TMP_Text)val).font;
					}
				}
			}
			catch
			{
			}
			try
			{
				return TMP_Settings.defaultFontAsset;
			}
			catch
			{
				return null;
			}
		}
	}
}
namespace SULFURTogether.UI.RunStatsOverlay
{
	internal sealed class RunStatsBestMarks
	{
		public const int StatCount = 7;

		private static readonly bool[] LowerIsBetter = new bool[7] { false, false, false, true, false, true, false };

		private readonly int[] _bestValue = new int[7];

		private readonly bool[] _marked = new bool[7];

		public static readonly RunStatsBestMarks None = new RunStatsBestMarks();

		private RunStatsBestMarks()
		{
		}

		public static RunStatsBestMarks Compute(IReadOnlyList<NetRunStats> players)
		{
			RunStatsBestMarks runStatsBestMarks = new RunStatsBestMarks();
			if (players.Count == 0)
			{
				return runStatsBestMarks;
			}
			for (int i = 0; i < 7; i++)
			{
				int num = int.MaxValue;
				int num2 = int.MinValue;
				foreach (NetRunStats player in players)
				{
					int stat = GetStat(player, i);
					if (stat < num)
					{
						num = stat;
					}
					if (stat > num2)
					{
						num2 = stat;
					}
				}
				runStatsBestMarks._bestValue[i] = (LowerIsBetter[i] ? num : num2);
				runStatsBestMarks._marked[i] = num != num2;
			}
			return runStatsBestMarks;
		}

		public bool IsBest(int statIndex, int value)
		{
			if (_marked[statIndex])
			{
				return value == _bestValue[statIndex];
			}
			return false;
		}

		private static int GetStat(NetRunStats stats, int statIndex)
		{
			return statIndex switch
			{
				0 => stats.ShotsFired, 
				1 => stats.DamageDealt, 
				2 => stats.Kills, 
				3 => stats.TimesDowned, 
				4 => stats.Rescues, 
				5 => stats.DamageTaken, 
				_ => stats.DestructiblesDestroyed, 
			};
		}
	}
	internal static class RunStatsCanvasBuilder
	{
		public const float CardWidth = 340f;

		public const float ViewportWidth = 1600f;

		public static float ActiveCardSpacing = 44f;

		private const float VerticalCenterBiasPx = 60f;

		private const float ViewportHeightPx = 560f;

		public static GameObject BuildRoot(out RectTransform viewport, out RectTransform track)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: 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_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("RunStatsOverlayCanvas", new Type[4]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(CanvasScaler),
				typeof(GraphicRaycaster)
			});
			Canvas component = val.GetComponent<Canvas>();
			component.renderMode = (RenderMode)0;
			component.sortingOrder = 0;
			CanvasScaler component2 = val.GetComponent<CanvasScaler>();
			component2.uiScaleMode = (ScaleMode)1;
			component2.referenceResolution = new Vector2(1920f, 1080f);
			component2.screenMatchMode = (ScreenMatchMode)0;
			component2.matchWidthOrHeight = 0.85f;
			GameObject val2 = new GameObject("Viewport", new Type[2]
			{
				typeof(RectTransform),
				typeof(RectMask2D)
			});
			val2.transform.SetParent(val.transform, false);
			RectTransform val3 = (RectTransform)val2.transform;
			val3.anchorMin = new Vector2(0.5f, 0.5f);
			val3.anchorMax = new Vector2(0.5f, 0.5f);
			val3.pivot = new Vector2(0.5f, 0.5f);
			val3.anchoredPosition = new Vector2(0f, 60f);
			val3.sizeDelta = new Vector2(1600f, 560f);
			GameObject val4 = new GameObject("Track", new Type[3]
			{
				typeof(RectTransform),
				typeof(HorizontalLayoutGroup),
				typeof(ContentSizeFitter)
			});
			val4.transform.SetParent(val2.transform, false);
			RectTransform val5 = (RectTransform)val4.transform;
			val5.anchorMin = new Vector2(0f, 0.5f);
			val5.anchorMax = new Vector2(0f, 0.5f);
			val5.pivot = new Vector2(0f, 0.5f);
			val5.anchoredPosition = Vector2.zero;
			HorizontalLayoutGroup component3 = val4.GetComponent<HorizontalLayoutGroup>();
			((LayoutGroup)component3).childAlignment = (TextAnchor)3;
			((HorizontalOrVerticalLayoutGroup)component3).spacing = ActiveCardSpacing;
			((HorizontalOrVerticalLayoutGroup)component3).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)component3).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)component3).childControlWidth = false;
			((HorizontalOrVerticalLayoutGroup)component3).childControlHeight = false;
			ContentSizeFitter component4 = val4.GetComponent<ContentSizeFitter>();
			component4.horizontalFit = (FitMode)2;
			component4.verticalFit = (FitMode)2;
			viewport = val3;
			track = val5;
			return val;
		}
	}
	internal sealed class RunStatsCardHoverAnimator
	{
		private const float MaxLeanDegrees = 3.5f;

		private const float MaxSquash = 0.045f;

		private const float HotScale = 1.05f;

		private const float SlideTowardCursor = 5f;

		private const float LiftUp = 8f;

		private const float ShadowCounterSlide = 4f;

		private readonly RectTransform _rect;

		private readonly Shadow _shadow;

		private readonly Image _titleBandImage;

		private readonly Vector2 _baselinePos;

		private readonly Vector2 _shadowBaseDistance;

		private readonly Vector3 _restScale;

		private RunStatsSpring _aimX;

		private RunStatsSpring _aimY;

		private RunStatsSpring _hot;

		public RunStatsCardHoverAnimator(RunStatsCardView card)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			_rect = card.Rect;
			_shadow = card.ShadowFx;
			_titleBandImage = card.TitleBandImage;
			_baselinePos = _rect.anchoredPosition;
			_shadowBaseDistance = _shadow.effectDistance;
			_restScale = ((Transform)_rect).localScale;
		}

		public bool IsPointerOver(Vector2 pointerPosition)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return RectTransformUtility.RectangleContainsScreenPoint(_rect, pointerPosition, (Camera)null);
		}

		public void Tick(float deltaTime, bool isHot, Vector2 pointerPosition)
		{
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			float target = 0f;
			float target2 = 0f;
			float target3 = 0f;
			Vector2 val = default(Vector2);
			if (isHot && RectTransformUtility.ScreenPointToLocalPointInRectangle(_rect, pointerPosition, (Camera)null, ref val))
			{
				Rect rect = _rect.rect;
				target = ((((Rect)(ref rect)).width > 0.01f) ? Mathf.Clamp((val.x - ((Rect)(ref rect)).center.x) / (((Rect)(ref rect)).width * 0.5f), -1f, 1f) : 0f);
				target2 = ((((Rect)(ref rect)).height > 0.01f) ? Mathf.Clamp((val.y - ((Rect)(ref rect)).center.y) / (((Rect)(ref rect)).height * 0.5f), -1f, 1f) : 0f);
				target3 = 1f;
			}
			float num = _aimX.Tick(target, deltaTime);
			float num2 = _aimY.Tick(target2, deltaTime);
			float num3 = Mathf.Clamp01(_hot.Tick(target3, deltaTime));
			((Transform)_rect).localRotation = Quaternion.Euler(0f, 0f, num * 3.5f);
			float num4 = 1f + 0.049999952f * num3;
			((Transform)_rect).localScale = new Vector3(_restScale.x * num4 * (1f - 0.045f * Mathf.Abs(num)), _restScale.y * num4 * (1f - 0.045f * Mathf.Abs(num2)), _restScale.z);
			_rect.anchoredPosition = _baselinePos + new Vector2(num * 5f, num2 * 5f + 8f * num3);
			_shadow.effectDistance = _shadowBaseDistance + new Vector2((0f - num) * 4f, (0f - num2) * 4f);
			Color color = ((Graphic)_titleBandImage).color;
			color.a = Mathf.Lerp(0.1f, 0.16f, num3);
			((Graphic)_titleBandImage).color = color;
		}
	}
	internal sealed class RunStatsCardView
	{
		private static readonly (string key, string en)[] LabelDefs = new(string, string)[7]
		{
			("runstats.stat.shotsFired", "Shots Fired"),
			("runstats.stat.damageDealt", "Damage Dealt"),
			("runstats.stat.kills", "Kills"),
			("runstats.stat.timesDowned", "Times Downed"),
			("runstats.stat.rescues", "Rescues"),
			("runstats.stat.damageTaken", "Damage Taken"),
			("runstats.stat.destructiblesDestroyed", "Destructibles Destroyed")
		};

		private static readonly Color BodyColor = new Color(0.085f, 0.065f, 0.05f, 0.97f);

		private static readonly Color InnerTint = new Color(1f, 0.58f, 0.28f, 0.045f);

		private static readonly Color NormalBorder = new Color(0.72f, 0.71f, 0.66f, 0.95f);

		private static readonly Color LocalBorder = new Color(0.99f, 0.72f, 0.18f, 1f);

		private static readonly Color LocalGlow = new Color(1f, 0.55f, 0.15f, 0.3f);

		private static readonly Color DropShadow = new Color(0f, 0f, 0f, 0.55f);

		private static readonly Color TitleBandColor = new Color(1f, 0.5f, 0.2f, 1f);

		private static readonly Color LabelColor = new Color(0.84f, 0.81f, 0.76f, 1f);

		private static readonly Color ValueColor = new Color(0.99f, 0.97f, 0.94f, 1f);

		private static readonly Color BestValueColor = new Color(1f, 0.6f, 0.25f, 1f);

		private static readonly Color NameColor = Color.white;

		private static readonly Color LocalNameColor = new Color(1f, 0.8f, 0.28f, 1f);

		public const float RestHighlightAlpha = 0.1f;

		public const float HotHighlightAlpha = 0.16f;

		private const float TitleBandHeight = 62f;

		private readonly GameObject _root;

		private readonly RectTransform _rect;

		private readonly TextMeshProUGUI _nameText;

		private readonly TextMeshProUGUI[] _valueTexts;

		private readonly Outline _border;

		private readonly Outline _glow;

		public GameObject Root => _root;

		public RectTransform Rect => _rect;

		public Shadow ShadowFx { get; }

		public RectTransform TitleBandRect { get; }

		public Image TitleBandImage { get; }

		private RunStatsCardView(GameObject root, TextMeshProUGUI nameText, TextMeshProUGUI[] valueTexts, Outline border, Outline glow, Shadow shadow, RectTransform titleBandRect, Image titleBandImage)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			_root = root;
			_rect = (RectTransform)root.transform;
			_nameText = nameText;
			_valueTexts = valueTexts;
			_border = border;
			_glow = glow;
			ShadowFx = shadow;
			TitleBandRect = titleBandRect;
			TitleBandImage = titleBandImage;
		}

		public static RunStatsCardView Create(Transform parent, TMP_FontAsset? font)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: 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_0364: Unknown result type (might be due to invalid IL or missing references)
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_037b: Unknown result type (might be due to invalid IL or missing references)
			//IL_038b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0401: Unknown result type (might be due to invalid IL or missing references)
			//IL_043e: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("RunStatsCard", new Type[4]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(VerticalLayoutGroup),
				typeof(ContentSizeFitter)
			});
			val.transform.SetParent(parent, false);
			RectTransform val2 = (RectTransform)val.transform;
			val2.sizeDelta = new Vector2(340f, val2.sizeDelta.y);
			Image component = val.GetComponent<Image>();
			((Graphic)component).color = BodyColor;
			((Graphic)component).raycastTarget = false;
			Outline val3 = val.AddComponent<Outline>();
			((Shadow)val3).effectColor = NormalBorder;
			((Shadow)val3).effectDistance = new Vector2(2f, -2f);
			((Shadow)val3).useGraphicAlpha = false;
			Outline val4 = val.AddComponent<Outline>();
			((Shadow)val4).effectColor = LocalGlow;
			((Shadow)val4).effectDistance = new Vector2(4.5f, -4.5f);
			((Shadow)val4).useGraphicAlpha = false;
			((Behaviour)val4).enabled = false;
			Shadow val5 = val.AddComponent<Shadow>();
			val5.effectColor = DropShadow;
			val5.effectDistance = new Vector2(5f, -5f);
			val5.useGraphicAlpha = false;
			VerticalLayoutGroup component2 = val.GetComponent<VerticalLayoutGroup>();
			((LayoutGroup)component2).padding = new RectOffset(20, 20, 18, 18);
			((HorizontalOrVerticalLayoutGroup)component2).spacing = 6f;
			((LayoutGroup)component2).childAlignment = (TextAnchor)1;
			((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = true;
			ContentSizeFitter component3 = val.GetComponent<ContentSizeFitter>();
			component3.horizontalFit = (FitMode)0;
			component3.verticalFit = (FitMode)2;
			(RectTransform rect, Image image) tuple = CreateDecoration(val.transform, "InnerPanel", InnerTint);
			tuple.rect.anchorMin = Vector2.zero;
			tuple.rect.anchorMax = Vector2.one;
			tuple.rect.offsetMin = new Vector2(5f, 5f);
			tuple.rect.offsetMax = new Vector2(-5f, -5f);
			(RectTransform, Image) tuple2 = CreateDecoration(val.transform, "TitleBand", new Color(TitleBandColor.r, TitleBandColor.g, TitleBandColor.b, 0.1f));
			tuple2.Item1.anchorMin = new Vector2(0f, 1f);
			tuple2.Item1.anchorMax = new Vector2(1f, 1f);
			tuple2.Item1.pivot = new Vector2(0.5f, 1f);
			tuple2.Item1.offsetMin = new Vector2(5f, -62f);
			tuple2.Item1.offsetMax = new Vector2(-5f, -5f);
			TextMeshProUGUI nameText = CreateText(val.transform, font, 30f, (FontStyles)1, (TextAlignmentOptions)514, 38f);
			GameObject val6 = new GameObject("NameSeparator", new Type[2]
			{
				typeof(RectTransform),
				typeof(LayoutElement)
			});
			val6.transform.SetParent(val.transform, false);
			val6.GetComponent<LayoutElement>().minHeight = 6f;
			TextMeshProUGUI[] array = (TextMeshProUGUI[])(object)new TextMeshProUGUI[LabelDefs.Length];
			for (int i = 0; i < LabelDefs.Length; i++)
			{
				GameObject val7 = new GameObject($"Row_{i}", new Type[3]
				{
					typeof(RectTransform),
					typeof(HorizontalLayoutGroup),
					typeof(LayoutElement)
				});
				val7.transform.SetParent(val.transform, false);
				val7.GetComponent<LayoutElement>().minHeight = 30f;
				HorizontalLayoutGroup component4 = val7.GetComponent<HorizontalLayoutGroup>();
				((HorizontalOrVerticalLayoutGroup)component4).childForceExpandWidth = true;
				((HorizontalOrVerticalLayoutGroup)component4).childForceExpandHeight = true;
				((HorizontalOrVerticalLayoutGroup)component4).childControlWidth = true;
				((HorizontalOrVerticalLayoutGroup)component4).childControlHeight = true;
				((HorizontalOrVerticalLayoutGroup)component4).spacing = 14f;
				TextMeshProUGUI obj = CreateText(val7.transform, font, 19f, (FontStyles)0, (TextAlignmentOptions)513, 28f);
				((TMP_Text)obj).text = CoopLoc.Get(LabelDefs[i].key, LabelDefs[i].en);
				((Graphic)obj).color = LabelColor;
				((Component)obj).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f;
				TextMeshProUGUI val8 = CreateText(val7.transform, font, 23f, (FontStyles)1, (TextAlignmentOptions)516, 28f);
				((Graphic)val8).color = ValueColor;
				((Component)val8).gameObject.AddComponent<LayoutElement>().minWidth = 70f;
				array[i] = val8;
			}
			return new RunStatsCardView(val, nameText, array, val3, val4, val5, tuple2.Item1, tuple2.Item2);
		}

		private static (RectTransform rect, Image image) CreateDecoration(Transform parent, string name, Color color)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(LayoutElement)
			});
			val.transform.SetParent(parent, false);
			val.GetComponent<LayoutElement>().ignoreLayout = true;
			Image component = val.GetComponent<Image>();
			((Graphic)component).color = color;
			((Graphic)component).raycastTarget = false;
			return (rect: (RectTransform)val.transform, image: component);
		}

		private static TextMeshProUGUI CreateText(Transform parent, TMP_FontAsset? font, float size, FontStyles style, TextAlignmentOptions align, float minHeight)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Text", new Type[2]
			{
				typeof(RectTransform),
				typeof(TextMeshProUGUI)
			});
			val.transform.SetParent(parent, false);
			TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)component).font = font;
			}
			((TMP_Text)component).fontSize = size;
			((TMP_Text)component).fontStyle = style;
			((TMP_Text)component).alignment = align;
			((Graphic)component).color = Color.white;
			((Graphic)component).raycastTarget = false;
			((TMP_Text)component).enableAutoSizing = true;
			((TMP_Text)component).fontSizeMin = size * 0.7f;
			((TMP_Text)component).fontSizeMax = size;
			val.AddComponent<LayoutElement>().minHeight = minHeight;
			return component;
		}

		public void Bind(NetRunStats stats, bool isLocalPlayer, RunStatsBestMarks best)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			string text = (string.IsNullOrWhiteSpace(stats.PlayerName) ? stats.PeerId : stats.PlayerName);
			((TMP_Text)_nameText).text = (isLocalPlayer ? CoopLoc.Format("runstats.youSuffix", "{name} (You)", ("name", text)) : text);
			((Graphic)_nameText).color = (isLocalPlayer ? LocalNameColor : NameColor);
			((Shadow)_border).effectColor = (isLocalPlayer ? LocalBorder : NormalBorder);
			((Behaviour)_glow).enabled = isLocalPlayer;
			SetValue(0, stats.ShotsFired, best);
			SetValue(1, stats.DamageDealt, best);
			SetValue(2, stats.Kills, best);
			SetValue(3, stats.TimesDowned, best);
			SetValue(4, stats.Rescues, best);
			SetValue(5, stats.DamageTaken, best);
			SetValue(6, stats.DestructiblesDestroyed, best);
		}

		public void SetEmpty()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			((TMP_Text)_nameText).text = "…";
			((Graphic)_nameText).color = NameColor;
			((Shadow)_border).effectColor = NormalBorder;
			((Behaviour)_glow).enabled = false;
			for (int i = 0; i < _valueTexts.Length; i++)
			{
				((TMP_Text)_valueTexts[i]).text = "…";
				((Graphic)_valueTexts[i]).color = ValueColor;
			}
		}

		private void SetValue(int index, int value, RunStatsBestMarks best)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			((TMP_Text)_valueTexts[index]).text = value.ToString();
			((Graphic)_valueTexts[index]).color = (best.IsBest(index, value) ? BestValueColor : ValueColor);
		}
	}
	internal sealed class RunStatsCarouselController
	{
		private const int VisibleCards = 4;

		private const float OverflowLeftPad = 24f;

		private RectTransform? _track;

		private int _cardCount;

		private int _focusIndex;

		private RunStatsSpring _x;

		public void SetTrack(RectTransform track)
		{
			_track = track;
		}

		public void ResetForNewData(int cardCount)
		{
			_cardCount = Mathf.Max(0, cardCount);
			_focusIndex = 0;
			_x.Snap(TargetX());
			Apply();
		}

		public void MoveFocus(int delta)
		{
			_focusIndex = Mathf.Clamp(_focusIndex + delta, 0, MaxFocus());
		}

		public void Tick(float deltaTime)
		{
			_x.Tick(TargetX(), deltaTime);
			Apply();
		}

		private int MaxFocus()
		{
			return Mathf.Max(0, _cardCount - 4);
		}

		private float TargetX()
		{
			float activeCardSpacing = RunStatsCanvasBuilder.ActiveCardSpacing;
			float num = (float)_cardCount * 340f + (float)Mathf.Max(0, _cardCount - 1) * activeCardSpacing;
			return Mathf.Max(24f, (1600f - num) * 0.5f) - (float)_focusIndex * (340f + activeCardSpacing);
		}

		private void Apply()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_track == (Object)null))
			{
				Vector2 anchoredPosition = _track.anchoredPosition;
				anchoredPosition.x = _x.Value;
				_track.anchoredPosition = anchoredPosition;
			}
		}
	}
	internal static class RunStatsCursorControl
	{
		private static bool _held;

		private static bool _savedVisible;

		private static CursorLockMode _savedLockState;

		public static void Acquire()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (!_held)
			{
				_savedVisible = Cursor.visible;
				_savedLockState = Cursor.lockState;
				_held = true;
				Enforce();
			}
		}

		public static void Enforce()
		{
			if (_held)
			{
				Cursor.visible = true;
				Cursor.lockState = (CursorLockMode)0;
			}
		}

		public static void Release()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (_held)
			{
				_held = false;
				Cursor.visible = _savedVisible;
				Cursor.lockState = _savedLockState;
			}
		}
	}
	internal static class RunStatsDevInjector
	{
		private static readonly List<NetRunStats> _simulated = new List<NetRunStats>();

		private static readonly Random _rng = new Random();

		private static int _nextId = 1;

		private static bool _keyboardBroken;

		public static int Version { get; private set; }

		public static IReadOnlyList<NetRunStats> Simulated => _simulated;

		public static void Poll()
		{
			if (_keyboardBroken || !GameManager.DeveloperMode)
			{
				return;
			}
			try
			{
				if (!EndKeyPressedThisFrame())
				{
					return;
				}
			}
			catch (Exception ex)
			{
				_keyboardBroken = true;
				NetLogger.Warn("[RunStatsOverlay] dev-injector keyboard poll failed — End key disabled: " + ex.GetType().Name + ": " + ex.Message);
				return;
			}
			int num = _nextId++;
			_simulated.Add(new NetRunStats
			{
				PeerId = $"sim-{num}",
				PlayerName = $"Tester {num}",
				ShotsFired = _rng.Next(0, 500),
				DamageDealt = _rng.Next(0, 8000),
				Kills = _rng.Next(0, 60),
				TimesDowned = _rng.Next(0, 6),
				Rescues = _rng.Next(0, 6),
				DamageTaken = _rng.Next(0, 2000),
				DestructiblesDestroyed = _rng.Next(0, 40)
			});
			Version++;
			NetLogger.Info($"[RunStatsOverlay] dev-injector added simulated player {num} (total {_simulated.Count})");
		}

		public static void Clear()
		{
			if (_simulated.Count != 0)
			{
				_simulated.Clear();
				_nextId = 1;
				Version++;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool EndKeyPressedThisFrame()
		{
			Keyboard current = Keyboard.current;
			if (current != null)
			{
				return ((ButtonControl)current.endKey).wasPressedThisFrame;
			}
			return false;
		}
	}
	internal static class RunStatsInputReader
	{
		private static bool _mouseBroken;

		private static bool _gamepadBroken;

		public static int PollDelta()
		{
			if (!_mouseBroken)
			{
				try
				{
					float num = ReadMouseScrollY();
					if (num > 0.01f)
					{
						return -1;
					}
					if (num < -0.01f)
					{
						return 1;
					}
				}
				catch (Exception ex)
				{
					_mouseBroken = true;
					NetLogger.Warn("[RunStatsOverlay] mouse poll failed — mouse input disabled: " + ex.GetType().Name + ": " + ex.Message);
				}
			}
			if (!_gamepadBroken)
			{
				try
				{
					int num2 = PollGamepadDelta();
					if (num2 != 0)
					{
						return num2;
					}
				}
				catch (Exception ex2)
				{
					_gamepadBroken = true;
					NetLogger.Warn("[RunStatsOverlay] gamepad poll failed — gamepad navigation disabled: " + ex2.GetType().Name + ": " + ex2.Message);
				}
			}
			return 0;
		}

		public static bool TryGetPointerPosition(out Vector2 position)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			position = default(Vector2);
			if (_mouseBroken)
			{
				return false;
			}
			try
			{
				return ReadMousePosition(out position);
			}
			catch (Exception ex)
			{
				_mouseBroken = true;
				NetLogger.Warn("[RunStatsOverlay] mouse poll failed — mouse input disabled: " + ex.GetType().Name + ": " + ex.Message);
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static float ReadMouseScrollY()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			Mouse current = Mouse.current;
			if (current == null)
			{
				return 0f;
			}
			return ((InputControl<Vector2>)(object)current.scroll).ReadValue().y;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool ReadMousePosition(out Vector2 position)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			Mouse current = Mouse.current;
			if (current == null)
			{
				position = default(Vector2);
				return false;
			}
			position = ((InputControl<Vector2>)(object)((Pointer)current).position).ReadValue();
			return true;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static int PollGamepadDelta()
		{
			Gamepad current = Gamepad.current;
			if (current == null)
			{
				return 0;
			}
			if (current.dpad.right.wasPressedThisFrame || current.rightShoulder.wasPressedThisFrame)
			{
				return 1;
			}
			if (current.dpad.left.wasPressedThisFrame || current.leftShoulder.wasPressedThisFrame)
			{
				return -1;
			}
			return 0;
		}
	}
	internal static class RunStatsOverlayManager
	{
		private static GameObject? _root;

		private static RectTransform? _viewport;

		private static RectTransform? _track;

		private static readonly RunStatsCarouselController _carousel = new RunStatsCarouselController();

		private static readonly List<RunStatsCardView> _cards = new List<RunStatsCardView>();

		private static readonly List<RunStatsCardHoverAnimator> _hoverAnimators = new List<RunStatsCardHoverAnimator>();

		private static IReadOnlyList<NetRunStats>? _boundList;

		private static int _boundSimVersion = -1;

		private static bool _shownLastTick;

		private static string _lastWarned = "";

		public static void Tick()
		{
			bool flag;
			try
			{
				flag = ApplyLifecycle();
			}
			catch (Exception ex)
			{
				WarnOnce("lifecycle failed: " + ex.GetType().Name + ": " + ex.Message);
				return;
			}
			try
			{
				if (IsInGameScene())
				{
					RunStatsDevInjector.Poll();
				}
			}
			catch (Exception ex2)
			{
				WarnOnce("dev injector failed: " + ex2.GetType().Name + ": " + ex2.Message);
			}
			if (!flag)
			{
				return;
			}
			try
			{
				TickInteraction(Time.unscaledDeltaTime);
			}
			catch (Exception ex3)
			{
				WarnOnce("interaction failed (cards stay shown, animation skipped): " + ex3.GetType().Name + ": " + ex3.Message);
			}
		}

		private static bool ApplyLifecycle()
		{
			NetRunState state;
			bool flag = NetRunStateBridge.TryGetLocalRunState(out state) && state.IsLoadingLikeState;
			bool pendingRunEndDisplay = NetRunStatsClientCache.PendingRunEndDisplay;
			bool flag2 = pendingRunEndDisplay && flag && IsNetworkSessionActive() && IsInGameScene();
			if (flag2)
			{
				EnsureBuilt();
				if ((Object)(object)_root != (Object)null && !_root.activeSelf)
				{
					_root.SetActive(true);
					ApplyTopmostSortingOrder();
					NetLogger.Info("[RunStatsOverlay] show");
				}
				RunStatsCursorControl.Acquire();
				RunStatsCursorControl.Enforce();
				RefreshIfChanged();
			}
			else if (_shownLastTick)
			{
				NetLogger.Info($"[RunStatsOverlay] hide (loading={flag} pending={pendingRunEndDisplay} networkActive={IsNetworkSessionActive()} inGameScene={IsInGameScene()})");
				HideAndClear();
			}
			_shownLastTick = flag2;
			return flag2;
		}

		private static void WarnOnce(string message)
		{
			if (!(message == _lastWarned))
			{
				_lastWarned = message;
				NetLogger.Warn("[RunStatsOverlay] " + message);
			}
		}

		public static void Shutdown()
		{
			if (_shownLastTick)
			{
				HideAndClear();
			}
			_shownLastTick = false;
		}

		private static void TickInteraction(float dt)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			int num = RunStatsInputReader.PollDelta();
			if (num != 0)
			{
				_carousel.MoveFocus(num);
			}
			_carousel.Tick(dt);
			Vector2 position;
			bool num2 = RunStatsInputReader.TryGetPointerPosition(out position) && (Object)(object)_viewport != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_viewport, position, (Camera)null);
			int num3 = -1;
			if (num2)
			{
				for (int i = 0; i < _hoverAnimators.Count; i++)
				{
					if (_hoverAnimators[i].IsPointerOver(position))
					{
						num3 = i;
						break;
					}
				}
			}
			for (int j = 0; j < _hoverAnimators.Count; j++)
			{
				_hoverAnimators[j].Tick(dt, j == num3, position);
			}
		}

		private static bool IsNetworkSessionActive()
		{
			return NetConfig.GetMode() switch
			{
				NetMode.Host => true, 
				NetMode.Client => NetClientJoinFlow.SessionJoinedHost, 
				_ => false, 
			};
		}

		private static bool IsInGameScene()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Scene activeScene = SceneManager.GetActiveScene();
				return string.Equals(((Scene)(ref activeScene)).name, "GameScene", StringComparison.OrdinalIgnoreCase);
			}
			catch
			{
				return false;
			}
		}

		private static void EnsureBuilt()
		{
			if (!((Object)(object)_root != (Object)null))
			{
				_root = RunStatsCanvasBuilder.BuildRoot(out RectTransform viewport, out RectTransform track);
				_viewport = viewport;
				_track = track;
				_carousel.SetTrack(track);
				Object.DontDestroyOnLoad((Object)(object)_root);
				_root.SetActive(false);
			}
		}

		private static void ApplyTopmostSortingOrder()
		{
			Canvas component = _root.GetComponent<Canvas>();
			int num = 0;
			Canvas[] array = Object.FindObjectsByType<Canvas>((FindObjectsSortMode)0);
			foreach (Canvas val in array)
			{
				if (!((Object)(object)val == (Object)(object)component) && val.sortingOrder > num)
				{
					num = val.sortingOrder;
				}
			}
			component.sortingOrder = num + 100;
		}

		private static void RefreshIfChanged()
		{
			IReadOnlyList<NetRunStats> lastFinalized = NetRunStatsClientCache.LastFinalized;
			int version = RunStatsDevInjector.Version;
			if (lastFinalized != _boundList || version != _boundSimVersion)
			{
				_boundList = lastFinalized;
				_boundSimVersion = version;
				RebuildCards(ComposeDisplayList(lastFinalized));
			}
		}

		private static IReadOnlyList<NetRunStats>? ComposeDisplayList(IReadOnlyList<NetRunStats>? real)
		{
			IReadOnlyList<NetRunStats> simulated = RunStatsDevInjector.Simulated;
			if (simulated.Count == 0)
			{
				return real;
			}
			List<NetRunStats> list = new List<NetRunStats>();
			if (real != null)
			{
				list.AddRange(real);
			}
			list.AddRange(simulated);
			return list;
		}

		private static void RebuildCards(IReadOnlyList<NetRunStats>? list)
		{
			foreach (RunStatsCardView card in _cards)
			{
				card.Root.SetActive(false);
				Object.Destroy((Object)(object)card.Root);
			}
			_cards.Clear();
			_hoverAnimators.Clear();
			if ((Object)(object)_track == (Object)null)
			{
				return;
			}
			ApplyResponsiveSpacing();
			TMP_FontAsset font = NativeFontSampler.ResolveNativeFont();
			if (list == null || list.Count == 0)
			{
				RunStatsCardView runStatsCardView = RunStatsCardView.Create((Transform)(object)_track, font);
				runStatsCardView.SetEmpty();
				_cards.Add(runStatsCardView);
			}
			else
			{
				RunStatsBestMarks best = RunStatsBestMarks.Compute(list);
				NetRunState state;
				string text = (NetRunStateBridge.TryGetLocalRunState(out state) ? state.PeerId : "");
				foreach (NetRunStats item in list)
				{
					RunStatsCardView runStatsCardView2 = RunStatsCardView.Create((Transform)(object)_track, font);
					runStatsCardView2.Bind(item, item.PeerId == text, best);
					_cards.Add(runStatsCardView2);
				}
			}
			_carousel.ResetForNewData(_cards.Count);
			foreach (RunStatsCardView card2 in _cards)
			{
				LayoutRebuilder.ForceRebuildLayoutImmediate(card2.Rect);
			}
			LayoutRebuilder.ForceRebuildLayoutImmediate(_track);
			foreach (RunStatsCardView card3 in _cards)
			{
				_hoverAnimators.Add(new RunStatsCardHoverAnimator(card3));
			}
		}

		private static void ApplyResponsiveSpacing()
		{
			float num = ((Screen.height > 0) ? ((float)Screen.width / (float)Screen.height) : 1.7777778f);
			float spacing = (RunStatsCanvasBuilder.ActiveCardSpacing = Mathf.Lerp(26f, 44f, Mathf.InverseLerp(1.25f, 1.7f, num)));
			HorizontalLayoutGroup val = (((Object)(object)_track != (Object)null) ? ((Component)_track).GetComponent<HorizontalLayoutGroup>() : null);
			if ((Object)(object)val != (Object)null)
			{
				((HorizontalOrVerticalLayoutGroup)val).spacing = spacing;
			}
		}

		private static void HideAndClear()
		{
			if ((Object)(object)_root != (Object)null)
			{
				_root.SetActive(false);
			}
			RunStatsCursorControl.Release();
			NetRunStatsClientCache.ConsumeAndClear();
			RunStatsDevInjector.Clear();
			_boundList = null;
			_boundSimVersion = -1;
		}
	}
	internal struct RunStatsSpring
	{
		private const float AngularFrequency = 14f;

		private const float DampingRatio = 0.75f;

		private const float MaxStep = 1f / 30f;

		public float Value;

		private float _velocity;

		public void Snap(float value)
		{
			Value = value;
			_velocity = 0f;
		}

		public float Tick(float target, float deltaTime)
		{
			float num = Mathf.Min(deltaTime, 1f / 30f);
			_velocity += (-21f * _velocity - 196f * (Value - target)) * num;
			Value += _velocity * num;
			return Value;
		}
	}
}
namespace SULFURTogether.UI.DownedRescueOverlay
{
	internal sealed class DownedRescueBorderProgress : MaskableGraphic
	{
		public float Thickness = 4f;

		public float Inset;

		private float _progress01 = 1f;

		private readonly Vector2[] _corners = (Vector2[])(object)new Vector2[6];

		private readonly float[] _segLen = new float[5];

		public void SetProgress(float value)
		{
			value = Mathf.Clamp01(value);
			if (!Mathf.Approximately(value, _progress01))
			{
				_progress01 = value;
				((Graphic)this).SetVerticesDirty();
			}
		}

		protected override void OnPopulateMesh(VertexHelper vh)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: 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_01d3: Unknown result type (might be due to invalid IL or missing references)
			vh.Clear();
			Rect pixelAdjustedRect = ((Graphic)this).GetPixelAdjustedRect();
			float num = ((Rect)(ref pixelAdjustedRect)).width * 0.5f - Inset;
			float num2 = ((Rect)(ref pixelAdjustedRect)).height * 0.5f - Inset;
			if (num <= 0f || num2 <= 0f)
			{
				return;
			}
			_corners[0] = new Vector2(0f, num2);
			_corners[1] = new Vector2(num, num2);
			_corners[2] = new Vector2(num, 0f - num2);
			_corners[3] = new Vector2(0f - num, 0f - num2);
			_corners[4] = new Vector2(0f - num, num2);
			_corners[5] = new Vector2(0f, num2);
			_segLen[0] = num;
			_segLen[1] = 2f * num2;
			_segLen[2] = 2f * num;
			_segLen[3] = 2f * num2;
			_segLen[4] = num;
			float num3 = _segLen[0] + _segLen[1] + _segLen[2] + _segLen[3] + _segLen[4];
			float num4 = _progress01 * num3;
			Color32 c = Color32.op_Implicit(((Graphic)this).color);
			float num5 = 0f;
			for (int i = 0; i < 5; i++)
			{
				float num6 = num5;
				float num7 = num5 + _segLen[i];
				if (!(num4 <= num6))
				{
					Vector2 val = _corners[i];
					Vector2 val2 = _corners[i + 1];
					bool num8 = num4 >= num7;
					Vector2 b = (num8 ? val2 : Vector2.Lerp(val, val2, (num4 - num6) / _segLen[i]));
					AddSegmentQuad(vh, val, b, c);
					if (num8 && i < 4)
					{
						AddCornerSquare(vh, val2, c);
					}
					num5 = num7;
					if (!num8)
					{
						break;
					}
					continue;
				}
				break;
			}
		}

		private void AddSegmentQuad(VertexHelper vh, Vector2 a, Vector2 b, Color32 c)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = b - a;
			if (!(((Vector2)(ref val)).sqrMagnitude < 0.0001f))
			{
				val = b - a;
				Vector2 normalized = ((Vector2)(ref val)).normalized;
				Vector2 val2 = new Vector2(0f - normalized.y, normalized.x) * (Thickness * 0.5f);
				int currentVertCount = vh.currentVertCount;
				vh.AddVert(Vector2.op_Implicit(a - val2), c, Vector4.op_Implicit(Vector2.zero));
				vh.AddVert(Vector2.op_Implicit(a + val2), c, Vector4.op_Implicit(Vector2.zero));
				vh.AddVert(Vector2.op_Implicit(b + val2), c, Vector4.op_Implicit(Vector2.zero));
				vh.AddVert(Vector2.op_Implicit(b - val2), c, Vector4.op_Implicit(Vector2.zero));
				vh.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2);
				vh.AddTriangle(currentVertCount, currentVertCount + 2, currentVertCount + 3);
			}
		}

		private void AddCornerSquare(VertexHelper vh, Vector2 center, Color32 c)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)