Decompiled source of FusionNetworkingPlus v1.1.2

Mods/FNPlus.dll

Decompiled 2 months ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using FNPlus;
using FNPlus.Network;
using FNPlus.Patches;
using FNPlus.Utilities;
using HarmonyLib;
using Il2CppOculus.Platform;
using Il2CppOculus.Platform.Models;
using Il2CppTMPro;
using LabFusion.Menu;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.Senders;
using LabFusion.UI.Popups;
using LabFusion.Utilities;
using LabFusion.Voice;
using LabFusion.Voice.Unity;
using MelonLoader;
using Microsoft.CodeAnalysis;
using Riptide;
using Riptide.Transports;
using Riptide.Transports.Udp;
using Riptide.Utils;
using Steamworks;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: Guid("490e160d-251d-4ab4-a3bb-f473961ff8a1")]
[assembly: AssemblyTitle("FNPlus")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: MelonInfo(typeof(Mod), "FNPlus", "1.0.0", "KitchenBoy", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonPriority(-1000000)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Riptide
{
	public class Client : Peer
	{
		public delegate void MessageHandler(Message message);

		private Connection connection;

		private int connectionAttempts;

		private int maxConnectionAttempts;

		private Dictionary<ushort, MessageHandler> messageHandlers;

		private IClient transport;

		private Message connectMessage;

		public ushort Id => connection.Id;

		public short RTT => connection.RTT;

		public short SmoothRTT => connection.SmoothRTT;

		public override int TimeoutTime
		{
			set
			{
				defaultTimeout = value;
				connection.TimeoutTime = defaultTimeout;
			}
		}

		public bool IsNotConnected
		{
			get
			{
				if (connection != null)
				{
					return connection.IsNotConnected;
				}
				return true;
			}
		}

		public bool IsConnecting
		{
			get
			{
				if (connection != null)
				{
					return connection.IsConnecting;
				}
				return false;
			}
		}

		public bool IsPending
		{
			get
			{
				if (connection != null)
				{
					return connection.IsPending;
				}
				return false;
			}
		}

		public bool IsConnected
		{
			get
			{
				if (connection != null)
				{
					return connection.IsConnected;
				}
				return false;
			}
		}

		public Connection Connection => connection;

		public event EventHandler Connected;

		public event EventHandler<ConnectionFailedEventArgs> ConnectionFailed;

		public event EventHandler<MessageReceivedEventArgs> MessageReceived;

		public event EventHandler<DisconnectedEventArgs> Disconnected;

		public event EventHandler<ClientConnectedEventArgs> ClientConnected;

		public event EventHandler<ClientDisconnectedEventArgs> ClientDisconnected;

		public Client(IClient transport, string logName = "CLIENT")
			: base(logName)
		{
			this.transport = transport;
		}

		public Client(string logName = "CLIENT")
			: this(new Riptide.Transports.Udp.UdpClient(), logName)
		{
		}

		public void ChangeTransport(IClient newTransport)
		{
			Disconnect();
			transport = newTransport;
		}

		public bool Connect(string hostAddress, int maxConnectionAttempts = 5, byte messageHandlerGroupId = 0, Message message = null, bool useMessageHandlers = true)
		{
			Disconnect();
			SubToTransportEvents();
			if (!transport.Connect(hostAddress, out connection, out var connectError))
			{
				RiptideLogger.Log(LogType.Error, LogName, connectError);
				UnsubFromTransportEvents();
				return false;
			}
			this.maxConnectionAttempts = maxConnectionAttempts;
			connectionAttempts = 0;
			connection.Initialize(this, defaultTimeout);
			Peer.IncreaseActiveCount();
			base.useMessageHandlers = useMessageHandlers;
			if (useMessageHandlers)
			{
				CreateMessageHandlersDictionary(messageHandlerGroupId);
			}
			connectMessage = Message.Create(MessageHeader.Connect);
			if (message != null)
			{
				if (message.ReadBits != 0)
				{
					RiptideLogger.Log(LogType.Error, LogName, "Use the parameterless 'Message.Create()' overload when setting connection attempt data!");
				}
				connectMessage.AddMessage(message);
				message.Release();
			}
			StartTime();
			Heartbeat();
			RiptideLogger.Log(LogType.Info, LogName, $"Connecting to {connection}...");
			return true;
		}

		private void SubToTransportEvents()
		{
			transport.Connected += TransportConnected;
			transport.ConnectionFailed += TransportConnectionFailed;
			transport.DataReceived += base.HandleData;
			transport.Disconnected += TransportDisconnected;
		}

		private void UnsubFromTransportEvents()
		{
			transport.Connected -= TransportConnected;
			transport.ConnectionFailed -= TransportConnectionFailed;
			transport.DataReceived -= base.HandleData;
			transport.Disconnected -= TransportDisconnected;
		}

		protected override void CreateMessageHandlersDictionary(byte messageHandlerGroupId)
		{
			MethodInfo[] array = FindMessageHandlers();
			messageHandlers = new Dictionary<ushort, MessageHandler>(array.Length);
			MethodInfo[] array2 = array;
			foreach (MethodInfo methodInfo in array2)
			{
				MessageHandlerAttribute customAttribute = methodInfo.GetCustomAttribute<MessageHandlerAttribute>();
				if (customAttribute.GroupId != messageHandlerGroupId)
				{
					continue;
				}
				if (!methodInfo.IsStatic)
				{
					throw new NonStaticHandlerException(methodInfo.DeclaringType, methodInfo.Name);
				}
				Delegate @delegate = Delegate.CreateDelegate(typeof(MessageHandler), methodInfo, throwOnBindFailure: false);
				if ((object)@delegate != null)
				{
					if (messageHandlers.ContainsKey(customAttribute.MessageId))
					{
						MethodInfo methodInfo2 = messageHandlers[customAttribute.MessageId].GetMethodInfo();
						throw new DuplicateHandlerException(customAttribute.MessageId, methodInfo, methodInfo2);
					}
					messageHandlers.Add(customAttribute.MessageId, (MessageHandler)@delegate);
				}
				else if ((object)Delegate.CreateDelegate(typeof(Server.MessageHandler), methodInfo, throwOnBindFailure: false) == null)
				{
					throw new InvalidHandlerSignatureException(methodInfo.DeclaringType, methodInfo.Name);
				}
			}
		}

		internal override void Heartbeat()
		{
			if (IsConnecting)
			{
				if (connectionAttempts < maxConnectionAttempts)
				{
					Send(connectMessage, shouldRelease: false);
					connectionAttempts++;
				}
				else
				{
					LocalDisconnect(DisconnectReason.NeverConnected);
				}
			}
			else if (IsPending)
			{
				if (connection.HasConnectAttemptTimedOut)
				{
					LocalDisconnect(DisconnectReason.TimedOut);
					return;
				}
			}
			else if (IsConnected)
			{
				if (connection.HasTimedOut)
				{
					LocalDisconnect(DisconnectReason.TimedOut);
					return;
				}
				connection.SendHeartbeat();
			}
			ExecuteLater(base.HeartbeatInterval, new HeartbeatEvent(this));
		}

		public override void Update()
		{
			base.Update();
			transport.Poll();
			HandleMessages();
		}

		protected override void Handle(Message message, MessageHeader header, Connection connection)
		{
			switch (header)
			{
			case MessageHeader.Unreliable:
			case MessageHeader.Reliable:
				OnMessageReceived(message);
				break;
			case MessageHeader.Ack:
				connection.HandleAck(message);
				break;
			case MessageHeader.Connect:
				connection.SetPending();
				break;
			case MessageHeader.Reject:
				if (!IsConnected)
				{
					LocalDisconnect(DisconnectReason.ConnectionRejected, message, (RejectReason)message.GetByte());
				}
				break;
			case MessageHeader.Heartbeat:
				connection.HandleHeartbeatResponse(message);
				break;
			case MessageHeader.Disconnect:
				LocalDisconnect((DisconnectReason)message.GetByte(), message);
				break;
			case MessageHeader.Welcome:
				if (IsConnecting || IsPending)
				{
					connection.HandleWelcome(message);
					OnConnected();
				}
				break;
			case MessageHeader.ClientConnected:
				OnClientConnected(message.GetUShort());
				break;
			case MessageHeader.ClientDisconnected:
				OnClientDisconnected(message.GetUShort());
				break;
			default:
				RiptideLogger.Log(LogType.Warning, LogName, $"Unexpected message header '{header}'! Discarding {message.BytesInUse} bytes.");
				break;
			}
			message.Release();
		}

		public ushort Send(Message message, bool shouldRelease = true)
		{
			return connection.Send(message, shouldRelease);
		}

		public void Disconnect()
		{
			if (connection != null && !IsNotConnected)
			{
				Send(Message.Create(MessageHeader.Disconnect));
				LocalDisconnect(DisconnectReason.Disconnected);
			}
		}

		internal override void Disconnect(Connection connection, DisconnectReason reason)
		{
			if (connection.IsConnected && connection.CanQualityDisconnect)
			{
				LocalDisconnect(reason);
			}
		}

		private void LocalDisconnect(DisconnectReason reason, Message message = null, RejectReason rejectReason = RejectReason.NoConnection)
		{
			if (!IsNotConnected)
			{
				UnsubFromTransportEvents();
				Peer.DecreaseActiveCount();
				StopTime();
				transport.Disconnect();
				connection.LocalDisconnect();
				switch (reason)
				{
				case DisconnectReason.NeverConnected:
					OnConnectionFailed(RejectReason.NoConnection);
					break;
				case DisconnectReason.ConnectionRejected:
					OnConnectionFailed(rejectReason, message);
					break;
				default:
					OnDisconnected(reason, message);
					break;
				}
			}
		}

		private void TransportConnected(object sender, EventArgs e)
		{
		}

		private void TransportConnectionFailed(object sender, EventArgs e)
		{
			LocalDisconnect(DisconnectReason.NeverConnected);
		}

		private void TransportDisconnected(object sender, Riptide.Transports.DisconnectedEventArgs e)
		{
			if (connection == e.Connection)
			{
				LocalDisconnect(e.Reason);
			}
		}

		protected virtual void OnConnected()
		{
			connectMessage.Release();
			connectMessage = null;
			RiptideLogger.Log(LogType.Info, LogName, "Connected successfully!");
			this.Connected?.Invoke(this, EventArgs.Empty);
		}

		protected virtual void OnConnectionFailed(RejectReason reason, Message message = null)
		{
			connectMessage.Release();
			connectMessage = null;
			RiptideLogger.Log(LogType.Info, LogName, "Connection to server failed: " + Helper.GetReasonString(reason) + ".");
			this.ConnectionFailed?.Invoke(this, new ConnectionFailedEventArgs(reason, message));
		}

		protected virtual void OnMessageReceived(Message message)
		{
			ushort num = (ushort)message.GetVarULong();
			this.MessageReceived?.Invoke(this, new MessageReceivedEventArgs(connection, num, message));
			if (useMessageHandlers)
			{
				if (messageHandlers.TryGetValue(num, out var value))
				{
					value(message);
					return;
				}
				RiptideLogger.Log(LogType.Warning, LogName, $"No message handler method found for message ID {num}!");
			}
		}

		protected virtual void OnDisconnected(DisconnectReason reason, Message message)
		{
			RiptideLogger.Log(LogType.Info, LogName, "Disconnected from server: " + Helper.GetReasonString(reason) + ".");
			this.Disconnected?.Invoke(this, new DisconnectedEventArgs(reason, message));
		}

		protected virtual void OnClientConnected(ushort clientId)
		{
			RiptideLogger.Log(LogType.Info, LogName, $"Client {clientId} connected.");
			this.ClientConnected?.Invoke(this, new ClientConnectedEventArgs(clientId));
		}

		protected virtual void OnClientDisconnected(ushort clientId)
		{
			RiptideLogger.Log(LogType.Info, LogName, $"Client {clientId} disconnected.");
			this.ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(clientId));
		}
	}
	internal enum ConnectionState : byte
	{
		NotConnected,
		Connecting,
		Pending,
		Connected
	}
	public abstract class Connection
	{
		private abstract class Sequencer
		{
			private ushort _nextSequenceId = 1;

			protected readonly Connection connection;

			protected ushort lastReceivedSeqId;

			protected readonly Bitfield receivedSeqIds = new Bitfield();

			protected ushort lastAckedSeqId;

			protected readonly Bitfield ackedSeqIds = new Bitfield(isDynamicCapacity: false);

			internal ushort NextSequenceId => _nextSequenceId++;

			protected Sequencer(Connection connection)
			{
				this.connection = connection;
			}

			internal abstract bool ShouldHandle(ushort sequenceId);

			internal abstract void UpdateReceivedAcks(ushort remoteLastReceivedSeqId, ushort remoteReceivedSeqIds);
		}

		private class NotifySequencer : Sequencer
		{
			internal NotifySequencer(Connection connection)
				: base(connection)
			{
			}

			internal ushort InsertHeader(Message message)
			{
				ushort nextSequenceId = base.NextSequenceId;
				ulong bitfield = lastReceivedSeqId | ((ulong)receivedSeqIds.First8 << 16) | ((ulong)nextSequenceId << 24);
				message.SetBits(bitfield, 40, 4);
				return nextSequenceId;
			}

			internal override bool ShouldHandle(ushort sequenceId)
			{
				int sequenceGap = Helper.GetSequenceGap(sequenceId, lastReceivedSeqId);
				if (sequenceGap > 0)
				{
					receivedSeqIds.ShiftBy(sequenceGap);
					lastReceivedSeqId = sequenceId;
					if (receivedSeqIds.IsSet(sequenceGap))
					{
						return false;
					}
					receivedSeqIds.Set(sequenceGap);
					return true;
				}
				return false;
			}

			internal override void UpdateReceivedAcks(ushort remoteLastReceivedSeqId, ushort remoteReceivedSeqIds)
			{
				int num = Helper.GetSequenceGap(remoteLastReceivedSeqId, lastAckedSeqId);
				if (num <= 0)
				{
					return;
				}
				if (num > 1)
				{
					while (num > 9)
					{
						lastAckedSeqId++;
						num--;
						connection.NotifyLost?.Invoke(lastAckedSeqId);
					}
					int num2 = num - 1;
					int num3 = 1 << num2;
					for (int i = 0; i < num2; i++)
					{
						lastAckedSeqId++;
						num3 >>= 1;
						if ((remoteReceivedSeqIds & num3) == 0)
						{
							connection.OnNotifyLost(lastAckedSeqId);
						}
						else
						{
							connection.OnNotifyDelivered(lastAckedSeqId);
						}
					}
				}
				lastAckedSeqId = remoteLastReceivedSeqId;
				connection.OnNotifyDelivered(lastAckedSeqId);
			}
		}

		private class ReliableSequencer : Sequencer
		{
			internal ReliableSequencer(Connection connection)
				: base(connection)
			{
			}

			internal override bool ShouldHandle(ushort sequenceId)
			{
				bool result = false;
				int num = Helper.GetSequenceGap(sequenceId, lastReceivedSeqId);
				if (num != 0)
				{
					if (num > 0)
					{
						if (num > 64)
						{
							RiptideLogger.Log(LogType.Warning, connection.Peer.LogName, $"The gap between received sequence IDs was very large ({num})!");
						}
						receivedSeqIds.ShiftBy(num);
						lastReceivedSeqId = sequenceId;
					}
					else
					{
						num = -num;
					}
					result = !receivedSeqIds.IsSet(num);
					receivedSeqIds.Set(num);
				}
				connection.SendAck(sequenceId, lastReceivedSeqId, receivedSeqIds);
				return result;
			}

			internal override void UpdateReceivedAcks(ushort remoteLastReceivedSeqId, ushort remoteReceivedSeqIds)
			{
				int sequenceGap = Helper.GetSequenceGap(remoteLastReceivedSeqId, lastAckedSeqId);
				if (sequenceGap > 0)
				{
					if (!ackedSeqIds.HasCapacityFor(sequenceGap, out var overflow))
					{
						for (int i = 0; i < overflow; i++)
						{
							if (!ackedSeqIds.CheckAndTrimLast(out var checkedPosition))
							{
								connection.ResendMessage((ushort)(lastAckedSeqId - checkedPosition));
							}
							else
							{
								connection.ClearMessage((ushort)(lastAckedSeqId - checkedPosition));
							}
						}
					}
					ackedSeqIds.ShiftBy(sequenceGap);
					lastAckedSeqId = remoteLastReceivedSeqId;
					for (int j = 0; j < 16; j++)
					{
						if (!ackedSeqIds.IsSet(j + 1) && (remoteReceivedSeqIds & (1 << j)) != 0)
						{
							connection.ClearMessage((ushort)(lastAckedSeqId - (j + 1)));
						}
					}
					ackedSeqIds.Combine(remoteReceivedSeqIds);
					ackedSeqIds.Set(sequenceGap);
					connection.ClearMessage(remoteLastReceivedSeqId);
				}
				else if (sequenceGap < 0)
				{
					ackedSeqIds.Set(-sequenceGap);
				}
				else
				{
					ackedSeqIds.Combine(remoteReceivedSeqIds);
				}
			}
		}

		public Action<ushort> NotifyDelivered;

		public Action<ushort> NotifyLost;

		public Action<Message> NotifyReceived;

		public Action<ushort> ReliableDelivered;

		private short _rtt;

		private bool _canTimeout;

		public bool CanQualityDisconnect;

		public readonly ConnectionMetrics Metrics;

		public int MaxAvgSendAttempts;

		public int AvgSendAttemptsResilience;

		public int MaxSendAttempts;

		public float MaxNotifyLoss;

		public int NotifyLossResilience;

		private readonly NotifySequencer notify;

		private readonly ReliableSequencer reliable;

		private readonly Dictionary<ushort, PendingMessage> pendingMessages;

		private ConnectionState state;

		private int sendAttemptsViolations;

		private int lossRateViolations;

		private long lastHeartbeat;

		private byte lastPingId;

		private byte pendingPingId;

		private long pendingPingSendTime;

		public ushort Id { get; internal set; }

		public bool IsNotConnected => state == ConnectionState.NotConnected;

		public bool IsConnecting => state == ConnectionState.Connecting;

		public bool IsPending => state == ConnectionState.Pending;

		public bool IsConnected => state == ConnectionState.Connected;

		public short RTT
		{
			get
			{
				return _rtt;
			}
			private set
			{
				SmoothRTT = ((_rtt == -1) ? value : ((short)Math.Max(1f, (float)SmoothRTT * 0.7f + (float)value * 0.3f)));
				_rtt = value;
			}
		}

		public short SmoothRTT { get; private set; }

		public int TimeoutTime { get; set; }

		public bool CanTimeout
		{
			get
			{
				return _canTimeout;
			}
			set
			{
				if (value)
				{
					ResetTimeout();
				}
				_canTimeout = value;
			}
		}

		internal Peer Peer { get; private set; }

		internal bool HasTimedOut
		{
			get
			{
				if (_canTimeout)
				{
					return Peer.CurrentTime - lastHeartbeat > TimeoutTime;
				}
				return false;
			}
		}

		internal bool HasConnectAttemptTimedOut
		{
			get
			{
				if (_canTimeout)
				{
					return Peer.CurrentTime - lastHeartbeat > Peer.ConnectTimeoutTime;
				}
				return false;
			}
		}

		protected Connection()
		{
			Metrics = new ConnectionMetrics();
			notify = new NotifySequencer(this);
			reliable = new ReliableSequencer(this);
			state = ConnectionState.Connecting;
			_rtt = -1;
			SmoothRTT = -1;
			_canTimeout = true;
			CanQualityDisconnect = true;
			MaxAvgSendAttempts = 5;
			AvgSendAttemptsResilience = 64;
			MaxSendAttempts = 15;
			MaxNotifyLoss = 0.05f;
			NotifyLossResilience = 64;
			pendingMessages = new Dictionary<ushort, PendingMessage>();
		}

		internal void Initialize(Peer peer, int timeoutTime)
		{
			Peer = peer;
			TimeoutTime = timeoutTime;
		}

		public void ResetTimeout()
		{
			lastHeartbeat = Peer.CurrentTime;
		}

		public ushort Send(Message message, bool shouldRelease = true)
		{
			ushort num = 0;
			if (message.SendMode == MessageSendMode.Notify)
			{
				num = notify.InsertHeader(message);
				int bytesInUse = message.BytesInUse;
				Buffer.BlockCopy(message.Data, 0, Message.ByteBuffer, 0, bytesInUse);
				Send(Message.ByteBuffer, bytesInUse);
				Metrics.SentNotify(bytesInUse);
			}
			else if (message.SendMode == MessageSendMode.Unreliable)
			{
				int bytesInUse2 = message.BytesInUse;
				Buffer.BlockCopy(message.Data, 0, Message.ByteBuffer, 0, bytesInUse2);
				Send(Message.ByteBuffer, bytesInUse2);
				Metrics.SentUnreliable(bytesInUse2);
			}
			else
			{
				num = reliable.NextSequenceId;
				PendingMessage pendingMessage = PendingMessage.Create(num, message, this);
				pendingMessages.Add(num, pendingMessage);
				pendingMessage.TrySend();
				Metrics.ReliableUniques++;
			}
			if (shouldRelease)
			{
				message.Release();
			}
			return num;
		}

		protected internal abstract void Send(byte[] dataBuffer, int amount);

		internal void ProcessNotify(byte[] dataBuffer, int amount, Message message)
		{
			notify.UpdateReceivedAcks(Converter.UShortFromBits(dataBuffer, 4), Converter.ByteFromBits(dataBuffer, 20));
			Metrics.ReceivedNotify(amount);
			if (notify.ShouldHandle(Converter.UShortFromBits(dataBuffer, 28)))
			{
				Buffer.BlockCopy(dataBuffer, 1, message.Data, 1, amount - 1);
				NotifyReceived?.Invoke(message);
			}
			else
			{
				Metrics.NotifyDiscarded++;
			}
		}

		internal bool ShouldHandle(ushort sequenceId)
		{
			return reliable.ShouldHandle(sequenceId);
		}

		internal void LocalDisconnect()
		{
			state = ConnectionState.NotConnected;
			foreach (PendingMessage value in pendingMessages.Values)
			{
				value.Clear();
			}
			pendingMessages.Clear();
		}

		private void ResendMessage(ushort sequenceId)
		{
			if (pendingMessages.TryGetValue(sequenceId, out var value))
			{
				value.RetrySend();
			}
		}

		internal void ClearMessage(ushort sequenceId)
		{
			if (pendingMessages.TryGetValue(sequenceId, out var value))
			{
				ReliableDelivered?.Invoke(sequenceId);
				value.Clear();
				pendingMessages.Remove(sequenceId);
				UpdateSendAttemptsViolations();
			}
		}

		internal void SetPending()
		{
			if (IsConnecting)
			{
				state = ConnectionState.Pending;
				ResetTimeout();
			}
		}

		private void UpdateSendAttemptsViolations()
		{
			if (Metrics.RollingReliableSends.Mean > (double)MaxAvgSendAttempts)
			{
				sendAttemptsViolations++;
				if (sendAttemptsViolations >= AvgSendAttemptsResilience)
				{
					Peer.Disconnect(this, DisconnectReason.PoorConnection);
				}
			}
			else
			{
				sendAttemptsViolations = 0;
			}
		}

		private void UpdateLossViolations()
		{
			if (Metrics.RollingNotifyLossRate > MaxNotifyLoss)
			{
				lossRateViolations++;
				if (lossRateViolations >= NotifyLossResilience)
				{
					Peer.Disconnect(this, DisconnectReason.PoorConnection);
				}
			}
			else
			{
				lossRateViolations = 0;
			}
		}

		private void SendAck(ushort forSeqId, ushort lastReceivedSeqId, Bitfield receivedSeqIds)
		{
			Message message = Message.Create(MessageHeader.Ack);
			message.AddUShort(lastReceivedSeqId);
			message.AddUShort(receivedSeqIds.First16);
			if (forSeqId == lastReceivedSeqId)
			{
				message.AddBool(value: false);
			}
			else
			{
				message.AddBool(value: true);
				message.AddUShort(forSeqId);
			}
			Send(message);
		}

		internal void HandleAck(Message message)
		{
			ushort uShort = message.GetUShort();
			ushort uShort2 = message.GetUShort();
			ushort sequenceId = (message.GetBool() ? message.GetUShort() : uShort);
			ClearMessage(sequenceId);
			reliable.UpdateReceivedAcks(uShort, uShort2);
		}

		internal void SendWelcome()
		{
			Message message = Message.Create(MessageHeader.Welcome);
			message.AddUShort(Id);
			Send(message);
		}

		internal bool HandleWelcomeResponse(Message message)
		{
			if (!IsPending)
			{
				return false;
			}
			ushort uShort = message.GetUShort();
			if (Id != uShort)
			{
				RiptideLogger.Log(LogType.Error, Peer.LogName, $"Client has assumed ID {uShort} instead of {Id}!");
			}
			state = ConnectionState.Connected;
			ResetTimeout();
			return true;
		}

		internal void HandleHeartbeat(Message message)
		{
			if (IsConnected)
			{
				RespondHeartbeat(message.GetByte());
				RTT = message.GetShort();
				ResetTimeout();
			}
		}

		private void RespondHeartbeat(byte pingId)
		{
			Message message = Message.Create(MessageHeader.Heartbeat);
			message.AddByte(pingId);
			Send(message);
		}

		internal void HandleWelcome(Message message)
		{
			Id = message.GetUShort();
			state = ConnectionState.Connected;
			ResetTimeout();
			RespondWelcome();
		}

		private void RespondWelcome()
		{
			Message message = Message.Create(MessageHeader.Welcome);
			message.AddUShort(Id);
			Send(message);
		}

		internal void SendHeartbeat()
		{
			pendingPingId = lastPingId++;
			pendingPingSendTime = Peer.CurrentTime;
			Message message = Message.Create(MessageHeader.Heartbeat);
			message.AddByte(pendingPingId);
			message.AddShort(RTT);
			Send(message);
		}

		internal void HandleHeartbeatResponse(Message message)
		{
			byte @byte = message.GetByte();
			if (pendingPingId == @byte)
			{
				RTT = (short)Math.Max(1L, Peer.CurrentTime - pendingPingSendTime);
			}
			ResetTimeout();
		}

		protected virtual void OnNotifyDelivered(ushort sequenceId)
		{
			Metrics.DeliveredNotify();
			NotifyDelivered?.Invoke(sequenceId);
			UpdateLossViolations();
		}

		protected virtual void OnNotifyLost(ushort sequenceId)
		{
			Metrics.LostNotify();
			NotifyLost?.Invoke(sequenceId);
			UpdateLossViolations();
		}
	}
	public class ServerConnectedEventArgs : EventArgs
	{
		public readonly Connection Client;

		public ServerConnectedEventArgs(Connection client)
		{
			Client = client;
		}
	}
	public class ServerConnectionFailedEventArgs : EventArgs
	{
		public readonly Connection Client;

		public ServerConnectionFailedEventArgs(Connection client)
		{
			Client = client;
		}
	}
	public class ServerDisconnectedEventArgs : EventArgs
	{
		public readonly Connection Client;

		public readonly DisconnectReason Reason;

		public ServerDisconnectedEventArgs(Connection client, DisconnectReason reason)
		{
			Client = client;
			Reason = reason;
		}
	}
	public class MessageReceivedEventArgs : EventArgs
	{
		public readonly Connection FromConnection;

		public readonly ushort MessageId;

		public readonly Message Message;

		public MessageReceivedEventArgs(Connection fromConnection, ushort messageId, Message message)
		{
			FromConnection = fromConnection;
			MessageId = messageId;
			Message = message;
		}
	}
	public class ConnectionFailedEventArgs : EventArgs
	{
		public readonly RejectReason Reason;

		public readonly Message Message;

		public ConnectionFailedEventArgs(RejectReason reason, Message message)
		{
			Reason = reason;
			Message = message;
		}
	}
	public class DisconnectedEventArgs : EventArgs
	{
		public readonly DisconnectReason Reason;

		public readonly Message Message;

		public DisconnectedEventArgs(DisconnectReason reason, Message message)
		{
			Reason = reason;
			Message = message;
		}
	}
	public class ClientConnectedEventArgs : EventArgs
	{
		public readonly ushort Id;

		public ClientConnectedEventArgs(ushort id)
		{
			Id = id;
		}
	}
	public class ClientDisconnectedEventArgs : EventArgs
	{
		public readonly ushort Id;

		public ClientDisconnectedEventArgs(ushort id)
		{
			Id = id;
		}
	}
	public class InsufficientCapacityException : Exception
	{
		public readonly Message RiptideMessage;

		public readonly string TypeName;

		public readonly int RequiredBits;

		public InsufficientCapacityException()
		{
		}

		public InsufficientCapacityException(string message)
			: base(message)
		{
		}

		public InsufficientCapacityException(string message, Exception inner)
			: base(message, inner)
		{
		}

		public InsufficientCapacityException(Message message, int reserveBits)
			: base(GetErrorMessage(message, reserveBits))
		{
			RiptideMessage = message;
			TypeName = "reservation";
			RequiredBits = reserveBits;
		}

		public InsufficientCapacityException(Message message, string typeName, int requiredBits)
			: base(GetErrorMessage(message, typeName, requiredBits))
		{
			RiptideMessage = message;
			TypeName = typeName;
			RequiredBits = requiredBits;
		}

		public InsufficientCapacityException(Message message, int arrayLength, string typeName, int requiredBits)
			: base(GetErrorMessage(message, arrayLength, typeName, requiredBits))
		{
			RiptideMessage = message;
			TypeName = typeName + "[]";
			RequiredBits = requiredBits * arrayLength;
		}

		private static string GetErrorMessage(Message message, int reserveBits)
		{
			return $"Cannot reserve {reserveBits} {Helper.CorrectForm(reserveBits, "bit")} in a message with {message.UnwrittenBits} {Helper.CorrectForm(message.UnwrittenBits, "bit")} of remaining capacity!";
		}

		private static string GetErrorMessage(Message message, string typeName, int requiredBits)
		{
			return $"Cannot add a value of type '{typeName}' (requires {requiredBits} {Helper.CorrectForm(requiredBits, "bit")}) to a message with {message.UnwrittenBits} {Helper.CorrectForm(message.UnwrittenBits, "bit")} of remaining capacity!";
		}

		private static string GetErrorMessage(Message message, int arrayLength, string typeName, int requiredBits)
		{
			requiredBits *= arrayLength;
			return $"Cannot add an array of type '{typeName}[]' with {arrayLength} {Helper.CorrectForm(arrayLength, "element")} (requires {requiredBits} {Helper.CorrectForm(requiredBits, "bit")}) to a message with {message.UnwrittenBits} {Helper.CorrectForm(message.UnwrittenBits, "bit")} of remaining capacity!";
		}
	}
	public class NonStaticHandlerException : Exception
	{
		public readonly Type DeclaringType;

		public readonly string HandlerMethodName;

		public NonStaticHandlerException()
		{
		}

		public NonStaticHandlerException(string message)
			: base(message)
		{
		}

		public NonStaticHandlerException(string message, Exception inner)
			: base(message, inner)
		{
		}

		public NonStaticHandlerException(Type declaringType, string handlerMethodName)
			: base(GetErrorMessage(declaringType, handlerMethodName))
		{
			DeclaringType = declaringType;
			HandlerMethodName = handlerMethodName;
		}

		private static string GetErrorMessage(Type declaringType, string handlerMethodName)
		{
			return $"'{declaringType.Name}.{handlerMethodName}' is an instance method, but message handler methods must be static!";
		}
	}
	public class InvalidHandlerSignatureException : Exception
	{
		public readonly Type DeclaringType;

		public readonly string HandlerMethodName;

		public InvalidHandlerSignatureException()
		{
		}

		public InvalidHandlerSignatureException(string message)
			: base(message)
		{
		}

		public InvalidHandlerSignatureException(string message, Exception inner)
			: base(message, inner)
		{
		}

		public InvalidHandlerSignatureException(Type declaringType, string handlerMethodName)
			: base(GetErrorMessage(declaringType, handlerMethodName))
		{
			DeclaringType = declaringType;
			HandlerMethodName = handlerMethodName;
		}

		private static string GetErrorMessage(Type declaringType, string handlerMethodName)
		{
			return $"'{declaringType.Name}.{handlerMethodName}' doesn't match any acceptable message handler method signatures! Server message handler methods should have a 'ushort' and a '{"Message"}' parameter, while client message handler methods should only have a '{"Message"}' parameter.";
		}
	}
	public class DuplicateHandlerException : Exception
	{
		public readonly ushort Id;

		public readonly Type DeclaringType1;

		public readonly string HandlerMethodName1;

		public readonly Type DeclaringType2;

		public readonly string HandlerMethodName2;

		public DuplicateHandlerException()
		{
		}

		public DuplicateHandlerException(string message)
			: base(message)
		{
		}

		public DuplicateHandlerException(string message, Exception inner)
			: base(message, inner)
		{
		}

		public DuplicateHandlerException(ushort id, MethodInfo method1, MethodInfo method2)
			: base(GetErrorMessage(id, method1, method2))
		{
			Id = id;
			DeclaringType1 = method1.DeclaringType;
			HandlerMethodName1 = method1.Name;
			DeclaringType2 = method2.DeclaringType;
			HandlerMethodName2 = method2.Name;
		}

		private static string GetErrorMessage(ushort id, MethodInfo method1, MethodInfo method2)
		{
			return $"Message handler methods '{method1.DeclaringType.Name}.{method1.Name}' and '{method2.DeclaringType.Name}.{method2.Name}' are both set to handle messages with ID {id}! Only one handler method is allowed per message ID!";
		}
	}
	public interface IMessageSerializable
	{
		void Serialize(Message message);

		void Deserialize(Message message);
	}
	public enum MessageSendMode : byte
	{
		Notify = 6,
		Unreliable = 0,
		Reliable = 7
	}
	public class Message
	{
		public const int MaxHeaderSize = 44;

		internal const int HeaderBits = 4;

		internal const byte HeaderBitmask = 15;

		internal const int UnreliableHeaderBits = 4;

		internal const int ReliableHeaderBits = 20;

		internal const int NotifyHeaderBits = 44;

		internal const int MinUnreliableBytes = 1;

		internal const int MinReliableBytes = 3;

		internal const int MinNotifyBytes = 6;

		private const int BitsPerByte = 8;

		private const int BitsPerSegment = 64;

		internal static byte[] ByteBuffer;

		private static int maxBitCount;

		private static int maxArraySize;

		private static readonly List<Message> pool;

		private readonly ulong[] data;

		private int readBit;

		private int writeBit;

		private const string ByteName = "byte";

		private const string SByteName = "sbyte";

		private const string BoolName = "bool";

		private const string ShortName = "short";

		private const string UShortName = "ushort";

		private const string IntName = "int";

		private const string UIntName = "uint";

		private const string LongName = "long";

		private const string ULongName = "ulong";

		private const string FloatName = "float";

		private const string DoubleName = "double";

		private const string StringName = "string";

		private const string ArrayLengthName = "array length";

		public static int MaxSize { get; private set; }

		public static int MaxPayloadSize
		{
			get
			{
				return MaxSize - 6;
			}
			set
			{
				if (Peer.ActiveCount > 0)
				{
					throw new InvalidOperationException("Changing the 'MaxPayloadSize' is not allowed while a Server or Client is running!");
				}
				if (value < 0)
				{
					throw new ArgumentOutOfRangeException("value", "'MaxPayloadSize' cannot be negative!");
				}
				MaxSize = 6 + value;
				maxBitCount = MaxSize * 8;
				maxArraySize = MaxSize / 8 + ((MaxSize % 8 != 0) ? 1 : 0);
				ByteBuffer = new byte[MaxSize];
				TrimPool();
				PendingMessage.ClearPool();
			}
		}

		public static byte InstancesPerPeer { get; set; }

		public MessageSendMode SendMode { get; private set; }

		public int ReadBits => readBit;

		public int UnreadBits => writeBit - readBit;

		public int WrittenBits => writeBit;

		public int UnwrittenBits => maxBitCount - writeBit;

		public int BytesInUse => writeBit / 8 + ((writeBit % 8 != 0) ? 1 : 0);

		[Obsolete("Use ReadBits instead.")]
		public int ReadLength => ReadBits / 8 + ((ReadBits % 8 != 0) ? 1 : 0);

		[Obsolete("Use UnreadBits instead.")]
		public int UnreadLength => UnreadBits / 8 + ((UnreadBits % 8 != 0) ? 1 : 0);

		[Obsolete("Use WrittenBits instead.")]
		public int WrittenLength => WrittenBits / 8 + ((WrittenBits % 8 != 0) ? 1 : 0);

		internal ulong[] Data => data;

		static Message()
		{
			InstancesPerPeer = 4;
			pool = new List<Message>(InstancesPerPeer * 2);
			MaxSize = 1231;
			maxBitCount = MaxSize * 8;
			maxArraySize = MaxSize / 8 + ((MaxSize % 8 != 0) ? 1 : 0);
			ByteBuffer = new byte[MaxSize];
		}

		private Message()
		{
			data = new ulong[maxArraySize];
		}

		public static Message Create()
		{
			Message message = RetrieveFromPool();
			message.readBit = 0;
			message.writeBit = 0;
			return message;
		}

		public static Message Create(MessageSendMode sendMode)
		{
			return RetrieveFromPool().Init((MessageHeader)sendMode);
		}

		public static Message Create(MessageSendMode sendMode, ushort id)
		{
			return RetrieveFromPool().Init((MessageHeader)sendMode).AddVarULong(id);
		}

		public static Message Create(MessageSendMode sendMode, Enum id)
		{
			return Create(sendMode, (ushort)(object)id);
		}

		internal static Message Create(MessageHeader header)
		{
			return RetrieveFromPool().Init(header);
		}

		public static void TrimPool()
		{
			if (Peer.ActiveCount == 0)
			{
				pool.Clear();
				pool.Capacity = InstancesPerPeer * 2;
				return;
			}
			int num = Peer.ActiveCount * InstancesPerPeer;
			if (pool.Count > num)
			{
				pool.RemoveRange(Peer.ActiveCount * InstancesPerPeer, pool.Count - num);
				pool.Capacity = num * 2;
			}
		}

		private static Message RetrieveFromPool()
		{
			Message result;
			if (pool.Count > 0)
			{
				result = pool[0];
				pool.RemoveAt(0);
			}
			else
			{
				result = new Message();
			}
			return result;
		}

		public void Release()
		{
			if (pool.Count < pool.Capacity && !pool.Contains(this))
			{
				pool.Add(this);
			}
		}

		private Message Init(MessageHeader header)
		{
			data[0] = (ulong)header;
			SetHeader(header);
			return this;
		}

		internal Message Init(byte firstByte, int contentLength, out MessageHeader header)
		{
			data[contentLength / 8] = 0uL;
			data[0] = firstByte;
			header = (MessageHeader)(firstByte & 0xFu);
			SetHeader(header);
			writeBit = contentLength * 8;
			return this;
		}

		private void SetHeader(MessageHeader header)
		{
			if (header == MessageHeader.Notify)
			{
				readBit = 44;
				writeBit = 44;
				SendMode = MessageSendMode.Notify;
			}
			else if ((int)header >= 7)
			{
				readBit = 20;
				writeBit = 20;
				SendMode = MessageSendMode.Reliable;
			}
			else
			{
				readBit = 4;
				writeBit = 4;
				SendMode = MessageSendMode.Unreliable;
			}
		}

		public Message AddMessage(Message message)
		{
			return AddMessage(message, message.UnreadBits, message.readBit);
		}

		public Message AddMessage(Message message, int amount, int startBit)
		{
			if (UnwrittenBits < amount)
			{
				throw new InsufficientCapacityException(this, "Message", amount);
			}
			int num = startBit / 64;
			int num2 = startBit % 64;
			int num3 = writeBit / 64;
			int num4 = writeBit % 64;
			int num5 = num4 - num2;
			int num6 = (writeBit + amount) / 64 - num3 + 1;
			if (num5 == 0)
			{
				ulong num7 = message.data[num];
				if (num4 == 0)
				{
					data[num3] = num7;
				}
				else
				{
					data[num3] |= num7 & (ulong)(~((1L << num2) - 1));
				}
				for (int i = 1; i < num6; i++)
				{
					data[num3 + i] = message.data[num + i];
				}
			}
			else if (num5 > 0)
			{
				ulong num8 = message.data[num] & (ulong)(~((1L << num2) - 1));
				num8 <<= num5;
				if (num4 == 0)
				{
					data[num3] = num8;
				}
				else
				{
					data[num3] |= num8;
				}
				for (int j = 1; j < num6; j++)
				{
					data[num3 + j] = (message.data[num + j - 1] >> 64 - num5) | (message.data[num + j] << num5);
				}
			}
			else
			{
				num5 = -num5;
				ulong num9 = message.data[num] & (ulong)(~((1L << num2) - 1));
				num9 >>= num5;
				if (num4 == 0)
				{
					data[num3] = num9;
				}
				else
				{
					data[num3] |= num9;
				}
				int num10 = (startBit + amount) / 64 - num + 1;
				for (int k = 1; k < num10; k++)
				{
					data[num3 + k - 1] |= message.data[num + k] << 64 - num5;
					data[num3 + k] = message.data[num + k] >> num5;
				}
			}
			writeBit += amount;
			data[num3 + num6 - 1] &= (ulong)((1L << writeBit % 64) - 1);
			return this;
		}

		public Message ReserveBits(int amount)
		{
			if (UnwrittenBits < amount)
			{
				throw new InsufficientCapacityException(this, amount);
			}
			int num = writeBit % 64;
			writeBit += amount;
			if (num + amount >= 64)
			{
				data[writeBit / 64] = 0uL;
			}
			return this;
		}

		public Message SkipBits(int amount)
		{
			if (UnreadBits < amount)
			{
				RiptideLogger.Log(LogType.Error, $"Message only contains {UnreadBits} unread {Helper.CorrectForm(UnreadBits, "bit")}, which is not enough to skip {amount}!");
			}
			readBit += amount;
			return this;
		}

		public Message SetBits(ulong bitfield, int amount, int startBit)
		{
			if (amount > 64)
			{
				throw new ArgumentOutOfRangeException("amount", $"Cannot set more than {64} bits at a time!");
			}
			Converter.SetBits(bitfield, amount, data, startBit);
			return this;
		}

		public Message PeekBits(int amount, int startBit, out byte bitfield)
		{
			if (amount > 8)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"PeekBits"}' overload cannot be used to peek more than {8} bits at a time!");
			}
			Converter.GetBits(amount, data, startBit, out bitfield);
			return this;
		}

		public Message PeekBits(int amount, int startBit, out ushort bitfield)
		{
			if (amount > 16)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"PeekBits"}' overload cannot be used to peek more than {16} bits at a time!");
			}
			Converter.GetBits(amount, data, startBit, out bitfield);
			return this;
		}

		public Message PeekBits(int amount, int startBit, out uint bitfield)
		{
			if (amount > 32)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"PeekBits"}' overload cannot be used to peek more than {32} bits at a time!");
			}
			Converter.GetBits(amount, data, startBit, out bitfield);
			return this;
		}

		public Message PeekBits(int amount, int startBit, out ulong bitfield)
		{
			if (amount > 64)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"PeekBits"}' overload cannot be used to peek more than {64} bits at a time!");
			}
			Converter.GetBits(amount, data, startBit, out bitfield);
			return this;
		}

		public Message AddBits(byte bitfield, int amount)
		{
			if (amount > 8)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"AddBits"}' overload cannot be used to add more than {8} bits at a time!");
			}
			bitfield &= (byte)((1 << amount) - 1);
			Converter.ByteToBits(bitfield, data, writeBit);
			writeBit += amount;
			return this;
		}

		public Message AddBits(ushort bitfield, int amount)
		{
			if (amount > 16)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"AddBits"}' overload cannot be used to add more than {16} bits at a time!");
			}
			bitfield &= (ushort)((1 << amount) - 1);
			Converter.UShortToBits(bitfield, data, writeBit);
			writeBit += amount;
			return this;
		}

		public Message AddBits(uint bitfield, int amount)
		{
			if (amount > 32)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"AddBits"}' overload cannot be used to add more than {32} bits at a time!");
			}
			bitfield &= (uint)((1 << amount - 1 << 1) - 1);
			Converter.UIntToBits(bitfield, data, writeBit);
			writeBit += amount;
			return this;
		}

		public Message AddBits(ulong bitfield, int amount)
		{
			if (amount > 64)
			{
				throw new ArgumentOutOfRangeException("amount", $"This '{"AddBits"}' overload cannot be used to add more than {64} bits at a time!");
			}
			bitfield &= (ulong)((1L << amount - 1 << 1) - 1);
			Converter.ULongToBits(bitfield, data, writeBit);
			writeBit += amount;
			return this;
		}

		public Message GetBits(int amount, out byte bitfield)
		{
			PeekBits(amount, readBit, out bitfield);
			readBit += amount;
			return this;
		}

		public Message GetBits(int amount, out ushort bitfield)
		{
			PeekBits(amount, readBit, out bitfield);
			readBit += amount;
			return this;
		}

		public Message GetBits(int amount, out uint bitfield)
		{
			PeekBits(amount, readBit, out bitfield);
			readBit += amount;
			return this;
		}

		public Message GetBits(int amount, out ulong bitfield)
		{
			PeekBits(amount, readBit, out bitfield);
			readBit += amount;
			return this;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message AddVarLong(long value)
		{
			return AddVarULong((ulong)Converter.ZigZagEncode(value));
		}

		public Message AddVarULong(ulong value)
		{
			do
			{
				byte b = (byte)(value & 0x7F);
				value >>= 7;
				if (value != 0L)
				{
					b = (byte)(b | 0x80u);
				}
				AddByte(b);
			}
			while (value != 0L);
			return this;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public long GetVarLong()
		{
			return Converter.ZigZagDecode((long)GetVarULong());
		}

		public ulong GetVarULong()
		{
			ulong num = 0uL;
			int num2 = 0;
			ulong num3;
			do
			{
				num3 = GetByte();
				num |= (num3 & 0x7F) << num2;
				num2 += 7;
			}
			while ((num3 & 0x80) != 0L);
			return num;
		}

		public Message AddByte(byte value)
		{
			if (UnwrittenBits < 8)
			{
				throw new InsufficientCapacityException(this, "byte", 8);
			}
			Converter.ByteToBits(value, data, writeBit);
			writeBit += 8;
			return this;
		}

		public Message AddSByte(sbyte value)
		{
			if (UnwrittenBits < 8)
			{
				throw new InsufficientCapacityException(this, "sbyte", 8);
			}
			Converter.SByteToBits(value, data, writeBit);
			writeBit += 8;
			return this;
		}

		public byte GetByte()
		{
			if (UnreadBits < 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("byte", $"{0}"));
				return 0;
			}
			byte result = Converter.ByteFromBits(data, readBit);
			readBit += 8;
			return result;
		}

		public sbyte GetSByte()
		{
			if (UnreadBits < 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("sbyte", $"{0}"));
				return 0;
			}
			sbyte result = Converter.SByteFromBits(data, readBit);
			readBit += 8;
			return result;
		}

		public Message AddBytes(byte[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			int num = array.Length * 8;
			if (UnwrittenBits < num)
			{
				throw new InsufficientCapacityException(this, array.Length, "byte", 8);
			}
			if (writeBit % 8 == 0)
			{
				int num2 = writeBit % 64;
				if (num2 + num > 64)
				{
					data[(writeBit + num) / 64] = 0uL;
				}
				else if (num2 == 0)
				{
					data[writeBit / 64] = 0uL;
				}
				Buffer.BlockCopy(array, 0, data, writeBit / 8, array.Length);
				writeBit += num;
			}
			else
			{
				for (int i = 0; i < array.Length; i++)
				{
					Converter.ByteToBits(array[i], data, writeBit);
					writeBit += 8;
				}
			}
			return this;
		}

		public Message AddBytes(byte[] array, int startIndex, int amount, bool includeLength = true)
		{
			if (startIndex < 0 || startIndex >= array.Length)
			{
				throw new ArgumentOutOfRangeException("startIndex");
			}
			if (startIndex + amount > array.Length)
			{
				throw new ArgumentException("amount", $"The source array is not long enough to read {amount} {Helper.CorrectForm(amount, "byte")} starting at {startIndex}!");
			}
			if (includeLength)
			{
				AddVarULong((uint)amount);
			}
			int num = amount * 8;
			if (UnwrittenBits < num)
			{
				throw new InsufficientCapacityException(this, amount, "byte", 8);
			}
			if (writeBit % 8 == 0)
			{
				int num2 = writeBit % 64;
				if (num2 + num > 64)
				{
					data[(writeBit + num) / 64] = 0uL;
				}
				else if (num2 == 0)
				{
					data[writeBit / 64] = 0uL;
				}
				Buffer.BlockCopy(array, startIndex, data, writeBit / 8, amount);
				writeBit += num;
			}
			else
			{
				for (int i = startIndex; i < startIndex + amount; i++)
				{
					Converter.ByteToBits(array[i], data, writeBit);
					writeBit += 8;
				}
			}
			return this;
		}

		public Message AddSBytes(sbyte[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "sbyte", 8);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.SByteToBits(array[i], data, writeBit);
				writeBit += 8;
			}
			return this;
		}

		public byte[] GetBytes()
		{
			return GetBytes((int)GetVarULong());
		}

		public byte[] GetBytes(int amount)
		{
			byte[] array = new byte[amount];
			ReadBytes(amount, array);
			return array;
		}

		public void GetBytes(byte[] intoArray, int startIndex = 0)
		{
			GetBytes((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetBytes(int amount, byte[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "byte"));
			}
			ReadBytes(amount, intoArray, startIndex);
		}

		public sbyte[] GetSBytes()
		{
			return GetSBytes((int)GetVarULong());
		}

		public sbyte[] GetSBytes(int amount)
		{
			sbyte[] array = new sbyte[amount];
			ReadSBytes(amount, array);
			return array;
		}

		public void GetSBytes(sbyte[] intoArray, int startIndex = 0)
		{
			GetSBytes((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetSBytes(int amount, sbyte[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "sbyte"));
			}
			ReadSBytes(amount, intoArray, startIndex);
		}

		private void ReadBytes(int amount, byte[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "byte"));
				amount = UnreadBits / 8;
			}
			if (readBit % 8 == 0)
			{
				Buffer.BlockCopy(data, readBit / 8, intoArray, startIndex, amount);
				readBit += amount * 8;
				return;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.ByteFromBits(data, readBit);
				readBit += 8;
			}
		}

		private void ReadSBytes(int amount, sbyte[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "sbyte"));
				amount = UnreadBits / 8;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.SByteFromBits(data, readBit);
				readBit += 8;
			}
		}

		public Message AddBool(bool value)
		{
			if (UnwrittenBits < 1)
			{
				throw new InsufficientCapacityException(this, "bool", 1);
			}
			Converter.BoolToBit(value, data, writeBit++);
			return this;
		}

		public bool GetBool()
		{
			if (UnreadBits < 1)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("bool", $"{0}"));
				return false;
			}
			return Converter.BoolFromBit(data, readBit++);
		}

		public Message AddBools(bool[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length)
			{
				throw new InsufficientCapacityException(this, array.Length, "bool", 1);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.BoolToBit(array[i], data, writeBit++);
			}
			return this;
		}

		public bool[] GetBools()
		{
			return GetBools((int)GetVarULong());
		}

		public bool[] GetBools(int amount)
		{
			bool[] array = new bool[amount];
			ReadBools(amount, array);
			return array;
		}

		public void GetBools(bool[] intoArray, int startIndex = 0)
		{
			GetBools((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetBools(int amount, bool[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "bool"));
			}
			ReadBools(amount, intoArray, startIndex);
		}

		private void ReadBools(int amount, bool[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "bool"));
				amount = UnreadBits;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.BoolFromBit(data, readBit++);
			}
		}

		public Message AddShort(short value)
		{
			if (UnwrittenBits < 16)
			{
				throw new InsufficientCapacityException(this, "short", 16);
			}
			Converter.ShortToBits(value, data, writeBit);
			writeBit += 16;
			return this;
		}

		public Message AddUShort(ushort value)
		{
			if (UnwrittenBits < 16)
			{
				throw new InsufficientCapacityException(this, "ushort", 16);
			}
			Converter.UShortToBits(value, data, writeBit);
			writeBit += 16;
			return this;
		}

		public short GetShort()
		{
			if (UnreadBits < 16)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("short", $"{0}"));
				return 0;
			}
			short result = Converter.ShortFromBits(data, readBit);
			readBit += 16;
			return result;
		}

		public ushort GetUShort()
		{
			if (UnreadBits < 16)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("ushort", $"{0}"));
				return 0;
			}
			ushort result = Converter.UShortFromBits(data, readBit);
			readBit += 16;
			return result;
		}

		public Message AddShorts(short[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 2 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "short", 16);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.ShortToBits(array[i], data, writeBit);
				writeBit += 16;
			}
			return this;
		}

		public Message AddUShorts(ushort[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 2 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "ushort", 16);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.UShortToBits(array[i], data, writeBit);
				writeBit += 16;
			}
			return this;
		}

		public short[] GetShorts()
		{
			return GetShorts((int)GetVarULong());
		}

		public short[] GetShorts(int amount)
		{
			short[] array = new short[amount];
			ReadShorts(amount, array);
			return array;
		}

		public void GetShorts(short[] intoArray, int startIndex = 0)
		{
			GetShorts((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetShorts(int amount, short[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "short"));
			}
			ReadShorts(amount, intoArray, startIndex);
		}

		public ushort[] GetUShorts()
		{
			return GetUShorts((int)GetVarULong());
		}

		public ushort[] GetUShorts(int amount)
		{
			ushort[] array = new ushort[amount];
			ReadUShorts(amount, array);
			return array;
		}

		public void GetUShorts(ushort[] intoArray, int startIndex = 0)
		{
			GetUShorts((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetUShorts(int amount, ushort[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "ushort"));
			}
			ReadUShorts(amount, intoArray, startIndex);
		}

		private void ReadShorts(int amount, short[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 2 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "short"));
				amount = UnreadBits / 16;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.ShortFromBits(data, readBit);
				readBit += 16;
			}
		}

		private void ReadUShorts(int amount, ushort[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 2 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "ushort"));
				amount = UnreadBits / 16;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.UShortFromBits(data, readBit);
				readBit += 16;
			}
		}

		public Message AddInt(int value)
		{
			if (UnwrittenBits < 32)
			{
				throw new InsufficientCapacityException(this, "int", 32);
			}
			Converter.IntToBits(value, data, writeBit);
			writeBit += 32;
			return this;
		}

		public Message AddUInt(uint value)
		{
			if (UnwrittenBits < 32)
			{
				throw new InsufficientCapacityException(this, "uint", 32);
			}
			Converter.UIntToBits(value, data, writeBit);
			writeBit += 32;
			return this;
		}

		public int GetInt()
		{
			if (UnreadBits < 32)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("int", $"{0}"));
				return 0;
			}
			int result = Converter.IntFromBits(data, readBit);
			readBit += 32;
			return result;
		}

		public uint GetUInt()
		{
			if (UnreadBits < 32)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("uint", $"{0}"));
				return 0u;
			}
			uint result = Converter.UIntFromBits(data, readBit);
			readBit += 32;
			return result;
		}

		public Message AddInts(int[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 4 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "int", 32);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.IntToBits(array[i], data, writeBit);
				writeBit += 32;
			}
			return this;
		}

		public Message AddUInts(uint[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 4 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "uint", 32);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.UIntToBits(array[i], data, writeBit);
				writeBit += 32;
			}
			return this;
		}

		public int[] GetInts()
		{
			return GetInts((int)GetVarULong());
		}

		public int[] GetInts(int amount)
		{
			int[] array = new int[amount];
			ReadInts(amount, array);
			return array;
		}

		public void GetInts(int[] intoArray, int startIndex = 0)
		{
			GetInts((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetInts(int amount, int[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "int"));
			}
			ReadInts(amount, intoArray, startIndex);
		}

		public uint[] GetUInts()
		{
			return GetUInts((int)GetVarULong());
		}

		public uint[] GetUInts(int amount)
		{
			uint[] array = new uint[amount];
			ReadUInts(amount, array);
			return array;
		}

		public void GetUInts(uint[] intoArray, int startIndex = 0)
		{
			GetUInts((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetUInts(int amount, uint[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "uint"));
			}
			ReadUInts(amount, intoArray, startIndex);
		}

		private void ReadInts(int amount, int[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 4 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "int"));
				amount = UnreadBits / 32;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.IntFromBits(data, readBit);
				readBit += 32;
			}
		}

		private void ReadUInts(int amount, uint[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 4 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "uint"));
				amount = UnreadBits / 32;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.UIntFromBits(data, readBit);
				readBit += 32;
			}
		}

		public Message AddLong(long value)
		{
			if (UnwrittenBits < 64)
			{
				throw new InsufficientCapacityException(this, "long", 64);
			}
			Converter.LongToBits(value, data, writeBit);
			writeBit += 64;
			return this;
		}

		public Message AddULong(ulong value)
		{
			if (UnwrittenBits < 64)
			{
				throw new InsufficientCapacityException(this, "ulong", 64);
			}
			Converter.ULongToBits(value, data, writeBit);
			writeBit += 64;
			return this;
		}

		public long GetLong()
		{
			if (UnreadBits < 64)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("long", $"{0}"));
				return 0L;
			}
			long result = Converter.LongFromBits(data, readBit);
			readBit += 64;
			return result;
		}

		public ulong GetULong()
		{
			if (UnreadBits < 64)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("ulong", $"{0}"));
				return 0uL;
			}
			ulong result = Converter.ULongFromBits(data, readBit);
			readBit += 64;
			return result;
		}

		public Message AddLongs(long[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 8 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "long", 64);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.LongToBits(array[i], data, writeBit);
				writeBit += 64;
			}
			return this;
		}

		public Message AddULongs(ulong[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 8 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "ulong", 64);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.ULongToBits(array[i], data, writeBit);
				writeBit += 64;
			}
			return this;
		}

		public long[] GetLongs()
		{
			return GetLongs((int)GetVarULong());
		}

		public long[] GetLongs(int amount)
		{
			long[] array = new long[amount];
			ReadLongs(amount, array);
			return array;
		}

		public void GetLongs(long[] intoArray, int startIndex = 0)
		{
			GetLongs((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetLongs(int amount, long[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "long"));
			}
			ReadLongs(amount, intoArray, startIndex);
		}

		public ulong[] GetULongs()
		{
			return GetULongs((int)GetVarULong());
		}

		public ulong[] GetULongs(int amount)
		{
			ulong[] array = new ulong[amount];
			ReadULongs(amount, array);
			return array;
		}

		public void GetULongs(ulong[] intoArray, int startIndex = 0)
		{
			GetULongs((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetULongs(int amount, ulong[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "ulong"));
			}
			ReadULongs(amount, intoArray, startIndex);
		}

		private void ReadLongs(int amount, long[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 8 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "long"));
				amount = UnreadBits / 64;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.LongFromBits(data, readBit);
				readBit += 64;
			}
		}

		private void ReadULongs(int amount, ulong[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 8 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "ulong"));
				amount = UnreadBits / 64;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.ULongFromBits(data, readBit);
				readBit += 64;
			}
		}

		public Message AddFloat(float value)
		{
			if (UnwrittenBits < 32)
			{
				throw new InsufficientCapacityException(this, "float", 32);
			}
			Converter.FloatToBits(value, data, writeBit);
			writeBit += 32;
			return this;
		}

		public float GetFloat()
		{
			if (UnreadBits < 32)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("float", $"{0f}"));
				return 0f;
			}
			float result = Converter.FloatFromBits(data, readBit);
			readBit += 32;
			return result;
		}

		public Message AddFloats(float[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 4 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "float", 32);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.FloatToBits(array[i], data, writeBit);
				writeBit += 32;
			}
			return this;
		}

		public float[] GetFloats()
		{
			return GetFloats((int)GetVarULong());
		}

		public float[] GetFloats(int amount)
		{
			float[] array = new float[amount];
			ReadFloats(amount, array);
			return array;
		}

		public void GetFloats(float[] intoArray, int startIndex = 0)
		{
			GetFloats((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetFloats(int amount, float[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "float"));
			}
			ReadFloats(amount, intoArray, startIndex);
		}

		private void ReadFloats(int amount, float[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 4 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "float"));
				amount = UnreadBits / 32;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.FloatFromBits(data, readBit);
				readBit += 32;
			}
		}

		public Message AddDouble(double value)
		{
			if (UnwrittenBits < 64)
			{
				throw new InsufficientCapacityException(this, "double", 64);
			}
			Converter.DoubleToBits(value, data, writeBit);
			writeBit += 64;
			return this;
		}

		public double GetDouble()
		{
			if (UnreadBits < 64)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("double", $"{0.0}"));
				return 0.0;
			}
			double result = Converter.DoubleFromBits(data, readBit);
			readBit += 64;
			return result;
		}

		public Message AddDoubles(double[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			if (UnwrittenBits < array.Length * 8 * 8)
			{
				throw new InsufficientCapacityException(this, array.Length, "double", 64);
			}
			for (int i = 0; i < array.Length; i++)
			{
				Converter.DoubleToBits(array[i], data, writeBit);
				writeBit += 64;
			}
			return this;
		}

		public double[] GetDoubles()
		{
			return GetDoubles((int)GetVarULong());
		}

		public double[] GetDoubles(int amount)
		{
			double[] array = new double[amount];
			ReadDoubles(amount, array);
			return array;
		}

		public void GetDoubles(double[] intoArray, int startIndex = 0)
		{
			GetDoubles((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetDoubles(int amount, double[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "double"));
			}
			ReadDoubles(amount, intoArray, startIndex);
		}

		private void ReadDoubles(int amount, double[] intoArray, int startIndex = 0)
		{
			if (UnreadBits < amount * 8 * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, "double"));
				amount = UnreadBits / 64;
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = Converter.DoubleFromBits(data, readBit);
				readBit += 64;
			}
		}

		public Message AddString(string value)
		{
			AddBytes(Encoding.UTF8.GetBytes(value));
			return this;
		}

		public string GetString()
		{
			int num = (int)GetVarULong();
			if (UnreadBits < num * 8)
			{
				RiptideLogger.Log(LogType.Error, NotEnoughBitsError("string", "shortened string"));
				num = UnreadBits / 8;
			}
			return Encoding.UTF8.GetString(GetBytes(num), 0, num);
		}

		public Message AddStrings(string[] array, bool includeLength = true)
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			for (int i = 0; i < array.Length; i++)
			{
				AddString(array[i]);
			}
			return this;
		}

		public string[] GetStrings()
		{
			return GetStrings((int)GetVarULong());
		}

		public string[] GetStrings(int amount)
		{
			string[] array = new string[amount];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = GetString();
			}
			return array;
		}

		public void GetStrings(string[] intoArray, int startIndex = 0)
		{
			GetStrings((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetStrings(int amount, string[] intoArray, int startIndex = 0)
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, "string"));
			}
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = GetString();
			}
		}

		public Message AddSerializable<T>(T value) where T : IMessageSerializable
		{
			value.Serialize(this);
			return this;
		}

		public T GetSerializable<T>() where T : IMessageSerializable, new()
		{
			T result = new T();
			result.Deserialize(this);
			return result;
		}

		public Message AddSerializables<T>(T[] array, bool includeLength = true) where T : IMessageSerializable
		{
			if (includeLength)
			{
				AddVarULong((uint)array.Length);
			}
			for (int i = 0; i < array.Length; i++)
			{
				AddSerializable(array[i]);
			}
			return this;
		}

		public T[] GetSerializables<T>() where T : IMessageSerializable, new()
		{
			return GetSerializables<T>((int)GetVarULong());
		}

		public T[] GetSerializables<T>(int amount) where T : IMessageSerializable, new()
		{
			T[] array = new T[amount];
			ReadSerializables(amount, array);
			return array;
		}

		public void GetSerializables<T>(T[] intoArray, int startIndex = 0) where T : IMessageSerializable, new()
		{
			GetSerializables((int)GetVarULong(), intoArray, startIndex);
		}

		public void GetSerializables<T>(int amount, T[] intoArray, int startIndex = 0) where T : IMessageSerializable, new()
		{
			if (startIndex + amount > intoArray.Length)
			{
				throw new ArgumentException("amount", ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, typeof(T).Name));
			}
			ReadSerializables(amount, intoArray, startIndex);
		}

		private void ReadSerializables<T>(int amount, T[] intoArray, int startIndex = 0) where T : IMessageSerializable, new()
		{
			for (int i = 0; i < amount; i++)
			{
				intoArray[startIndex + i] = GetSerializable<T>();
			}
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(byte value)
		{
			return AddByte(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(sbyte value)
		{
			return AddSByte(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(bool value)
		{
			return AddBool(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(short value)
		{
			return AddShort(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(ushort value)
		{
			return AddUShort(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(int value)
		{
			return AddInt(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(uint value)
		{
			return AddUInt(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(long value)
		{
			return AddLong(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(ulong value)
		{
			return AddULong(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(float value)
		{
			return AddFloat(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(double value)
		{
			return AddDouble(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(string value)
		{
			return AddString(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add<T>(T value) where T : IMessageSerializable
		{
			return AddSerializable(value);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(byte[] array, bool includeLength = true)
		{
			return AddBytes(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(sbyte[] array, bool includeLength = true)
		{
			return AddSBytes(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(bool[] array, bool includeLength = true)
		{
			return AddBools(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(short[] array, bool includeLength = true)
		{
			return AddShorts(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(ushort[] array, bool includeLength = true)
		{
			return AddUShorts(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(int[] array, bool includeLength = true)
		{
			return AddInts(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(uint[] array, bool includeLength = true)
		{
			return AddUInts(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(long[] array, bool includeLength = true)
		{
			return AddLongs(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(ulong[] array, bool includeLength = true)
		{
			return AddULongs(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(float[] array, bool includeLength = true)
		{
			return AddFloats(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(double[] array, bool includeLength = true)
		{
			return AddDoubles(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add(string[] array, bool includeLength = true)
		{
			return AddStrings(array, includeLength);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Message Add<T>(T[] array, bool includeLength = true) where T : IMessageSerializable, new()
		{
			return AddSerializables(array, includeLength);
		}

		private string NotEnoughBitsError(string valueName, string defaultReturn)
		{
			return $"Message only contains {UnreadBits} unread {Helper.CorrectForm(UnreadBits, "bit")}, which is not enough to retrieve a value of type '{valueName}'! Returning {defaultReturn}.";
		}

		private string NotEnoughBitsError(int arrayLength, string valueName)
		{
			return $"Message only contains {UnreadBits} unread {Helper.CorrectForm(UnreadBits, "bit")}, which is not enough to retrieve {arrayLength} {Helper.CorrectForm(arrayLength, valueName)}! Returned array will contain default elements.";
		}

		private string ArrayNotLongEnoughError(int amount, int arrayLength, int startIndex, string valueName, string pluralValueName = "")
		{
			if (string.IsNullOrEmpty(pluralValueName))
			{
				pluralValueName = valueName + "s";
			}
			return $"The amount of {pluralValueName} to retrieve ({amount}) is greater than the number of elements from the start index ({startIndex}) to the end of the given array (length: {arrayLength})!";
		}
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
	public sealed class MessageHandlerAttribute : Attribute
	{
		public readonly ushort MessageId;

		public readonly byte GroupId;

		public MessageHandlerAttribute(ushort messageId, byte groupId = 0)
		{
			MessageId = messageId;
			GroupId = groupId;
		}
	}
	public class MessageRelayFilter
	{
		private const int BitsPerInt = 32;

		private int[] filter;

		public MessageRelayFilter(int size)
		{
			Set(size);
		}

		public MessageRelayFilter(Type idEnum)
		{
			Set(GetSizeFromEnum(idEnum));
		}

		public MessageRelayFilter(int size, params ushort[] idsToEnable)
		{
			Set(size);
			EnableIds(idsToEnable);
		}

		public MessageRelayFilter(Type idEnum, params Enum[] idsToEnable)
		{
			Set(GetSizeFromEnum(idEnum));
			EnableIds(idsToEnable.Cast<ushort>().ToArray());
		}

		private void EnableIds(ushort[] idsToEnable)
		{
			for (int i = 0; i < idsToEnable.Length; i++)
			{
				EnableRelay(idsToEnable[i]);
			}
		}

		private int GetSizeFromEnum(Type idEnum)
		{
			if (!idEnum.IsEnum)
			{
				throw new ArgumentException("Parameter 'idEnum' must be an enum type!", "idEnum");
			}
			return Enum.GetValues(idEnum).Cast<ushort>().Max() + 1;
		}

		private void Set(int size)
		{
			filter = new int[size / 32 + ((size % 32 > 0) ? 1 : 0)];
		}

		public void EnableRelay(ushort forMessageId)
		{
			filter[forMessageId / 32] |= 1 << forMessageId % 32;
		}

		public void EnableRelay(Enum forMessageId)
		{
			EnableRelay((ushort)(object)forMessageId);
		}

		public void DisableRelay(ushort forMessageId)
		{
			filter[forMessageId / 32] &= ~(1 << forMessageId % 32);
		}

		public void DisableRelay(Enum forMessageId)
		{
			DisableRelay((ushort)(object)forMessageId);
		}

		internal bool ShouldRelay(ushort forMessageId)
		{
			return (filter[forMessageId / 32] & (1 << forMessageId % 32)) != 0;
		}
	}
	public enum RejectReason : byte
	{
		NoConnection,
		AlreadyConnected,
		ServerFull,
		Rejected,
		Custom
	}
	public enum DisconnectReason : byte
	{
		NeverConnected,
		ConnectionRejected,
		TransportError,
		TimedOut,
		Kicked,
		ServerStopped,
		Disconnected,
		PoorConnection
	}
	public abstract class Peer
	{
		public readonly string LogName;

		protected bool useMessageHandlers;

		protected int defaultTimeout = 5000;

		private readonly Stopwatch time = new Stopwatch();

		private readonly Queue<MessageToHandle> messagesToHandle = new Queue<MessageToHandle>();

		private readonly Riptide.Utils.PriorityQueue<DelayedEvent, long> eventQueue = new Riptide.Utils.PriorityQueue<DelayedEvent, long>();

		public abstract int TimeoutTime { set; }

		public int HeartbeatInterval { get; set; } = 1000;


		internal static int ActiveCount { get; private set; }

		internal int ConnectTimeoutTime { get; set; } = 10000;


		internal long CurrentTime { get; private set; }

		public Peer(string logName)
		{
			LogName = logName;
		}

		protected MethodInfo[] FindMessageHandlers()
		{
			string thisAssemblyName = Assembly.GetExecutingAssembly().GetName().FullName;
			return (from m in (from a in AppDomain.CurrentDomain.GetAssemblies()
					where a.GetReferencedAssemblies().Any((AssemblyName n) => n.FullName == thisAssemblyName)
					select a).SelectMany((Assembly a) => a.GetTypes()).SelectMany((Type t) => t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
				where m.GetCustomAttributes(typeof(MessageHandlerAttribute), inherit: false).Length != 0
				select m).ToArray();
		}

		protected abstract void CreateMessageHandlersDictionary(byte messageHandlerGroupId);

		protected void StartTime()
		{
			CurrentTime = 0L;
			time.Restart();
		}

		protected void StopTime()
		{
			CurrentTime = 0L;
			time.Reset();
			eventQueue.Clear();
		}

		internal abstract void Heartbeat();

		public virtual void Update()
		{
			CurrentTime = time.ElapsedMilliseconds;
			while (eventQueue.Count > 0 && eventQueue.PeekPriority() <= CurrentTime)
			{
				eventQueue.Dequeue().Invoke();
			}
		}

		internal void ExecuteLater(long inMS, DelayedEvent delayedEvent)
		{
			eventQueue.Enqueue(delayedEvent, CurrentTime + inMS);
		}

		protected void HandleMessages()
		{
			while (messagesToHandle.Count > 0)
			{
				MessageToHandle messageToHandle = messagesToHandle.Dequeue();
				Handle(messageToHandle.Message, messageToHandle.Header, messageToHandle.FromConnection);
			}
		}

		protected void HandleData(object _, DataReceivedEventArgs e)
		{
			MessageHeader header;
			Message message = Message.Create().Init(e.DataBuffer[0], e.Amount, out header);
			if (message.SendMode == MessageSendMode.Notify)
			{
				if (e.Amount >= 6)
				{
					e.FromConnection.ProcessNotify(e.DataBuffer, e.Amount, message);
				}
			}
			else if (message.SendMode == MessageSendMode.Unreliable)
			{
				if (e.Amount > 1)
				{
					Buffer.BlockCopy(e.DataBuffer, 1, message.Data, 1, e.Amount - 1);
				}
				messagesToHandle.Enqueue(new MessageToHandle(message, header, e.FromConnection));
				e.FromConnection.Metrics.ReceivedUnreliable(e.Amount);
			}
			else if (e.Amount >= 3)
			{
				e.FromConnection.Metrics.ReceivedReliable(e.Amount);
				if (e.FromConnection.ShouldHandle(Converter.UShortFromBits(e.DataBuffer, 4)))
				{
					Buffer.BlockCopy(e.DataBuffer, 1, message.Data, 1, e.Amount - 1);
					messagesToHandle.Enqueue(new MessageToHandle(message, header, e.FromConnection));
				}
				else
				{
					e.FromConnection.Metrics.ReliableDiscarded++;
				}
			}
		}

		protected abstract void Handle(Message message, MessageHeader header, Connection connection);

		internal abstract void Disconnect(Connection connection, DisconnectReason reason);

		protected static void IncreaseActiveCount()
		{
			ActiveCount++;
		}

		protected static void DecreaseActiveCount()
		{
			ActiveCount--;
			if (ActiveCount < 0)
			{
				ActiveCount = 0;
			}
		}
	}
	internal readonly struct MessageToHandle
	{
		internal readonly Message Message;

		internal readonly MessageHeader Header;

		internal readonly Connection FromConnection;

		public MessageToHandle(Message message, MessageHeader header, Connection fromConnection)
		{
			Message = message;
			Header = header;
			FromConnection = fromConnection;
		}
	}
	internal class PendingMessage
	{
		private const float RetryTimeMultiplier = 1.2f;

		private static readonly List<PendingMessage> pool = new List<PendingMessage>();

		private Connection connection;

		private readonly byte[] data;

		private int size;

		private byte sendAttempts;

		private bool wasCleared;

		internal long LastSendTime { get; private set; }

		internal PendingMessage()
		{
			data = new byte[Message.MaxSize];
		}

		internal static PendingMessage Create(ushort sequenceId, Message message, Connection connection)
		{
			PendingMessage pendingMessage = RetrieveFromPool();
			pendingMessage.connection = connection;
			message.SetBits(sequenceId, 16, 4);
			pendingMessage.size = message.BytesInUse;
			Buffer.BlockCopy(message.Data, 0, pendingMessage.data, 0, pendingMessage.size);
			pendingMessage.sendAttempts = 0;
			pendingMessage.wasCleared = false;
			return pendingMessage;
		}

		private static PendingMessage RetrieveFromPool()
		{
			PendingMessage result;
			if (pool.Count > 0)
			{
				result = pool[0];
				pool.RemoveAt(0);
			}
			else
			{
				result = new PendingMessage();
			}
			return result;
		}

		public static void ClearPool()
		{
			pool.Clear();
		}

		private void Release()
		{
			if (!pool.Contains(this))
			{
				pool.Add(this);
			}
		}

		internal void RetrySend()
		{
			if (!wasCleared)
			{
				long currentTime = connection.Peer.CurrentTime;
				if (LastSendTime + ((connection.SmoothRTT < 0) ? 25 : (connection.SmoothRTT / 2)) <= currentTime)
				{
					TrySend();
				}
				else
				{
					connection.Peer.ExecuteLater((connection.SmoothRTT < 0) ? 50 : ((long)Math.Max(10f, (float)connection.SmoothRTT * 1.2f)), new ResendEvent(this, currentTime));
				}
			}
		}

		internal void TrySend()
		{
			if (sendAttempts >= connection.MaxSendAttempts && connection.CanQualityDisconnect)
			{
				RiptideLogger.Log(LogType.Info, connection.Peer.LogName, $"Could not guarantee delivery of a {(byte)(data[0] & 0xF)} message after {sendAttempts} attempts! Disconnecting...");
				connection.Peer.Disconnect(connection, DisconnectReason.PoorConnection);
			}
			else
			{
				connection.Send(data, size);
				connection.Metrics.SentReliable(size);
				LastSendTime = connection.Peer.CurrentTime;
				sendAttempts++;
				connection.Peer.ExecuteLater((connection.SmoothRTT < 0) ? 50 : ((long)Math.Max(10f, (float)connection.SmoothRTT * 1.2f)), new ResendEvent(this, connection.Peer.CurrentTime));
			}
		}

		internal void Clear()
		{
			connection.Metrics.RollingReliableSends.Add((int)sendAttempts);
			wasCleared = true;
			Release();
		}
	}
	public class Server : Peer
	{
		public delegate void MessageHandler(ushort fromClientId, Message message);

		public delegate void ConnectionAttemptHandler(Connection pendingConnection, Message connectMessage);

		public ConnectionAttemptHandler HandleConnection;

		public MessageRelayFilter RelayFilter;

		private readonly List<Connection> pendingConnections;

		private Dictionary<ushort, Connection> clients;

		private readonly List<Connection> timedOutClients;

		private Dictionary<ushort, MessageHandler> messageHandlers;

		private IServer transport;

		private Queue<ushort> availableClientIds;

		public bool IsRunning { get; private set; }

		public ushort Port => transport.Port;

		public override int TimeoutTime
		{
			set
			{
				defaultTimeout = value;
				foreach (Connection value2 in clients.Values)
				{
					value2.TimeoutTime = defaultTimeout;
				}
			}
		}

		public ushort MaxClientCount { get; private set; }

		public int ClientCount => clients.Count;

		public Connection[] Clients => clients.Values.ToArray();

		public event EventHandler<ServerConnectedEventArgs> ClientConnected;

		public event EventHandler<ServerConnectionFailedEventArgs> ConnectionFailed;

		public event EventHandler<MessageReceivedEventArgs> MessageReceived;

		public event EventHandler<ServerDisconnectedEventArgs> ClientDisconnected;

		public Server(IServer transport, string logName = "SERVER")
			: base(logName)
		{
			this.transport = transport;
			pendingConnections = new List<Connection>();
			clients = new Dictionary<ushort, Connection>();
			timedOutClients = new List<Connection>();
		}

		public Server(string logName = "SERVER")
			: this(new UdpServer(), logName)
		{
		}

		public void ChangeTransport(IServer newTransport)
		{
			Stop();
			transport = newTransport;
		}

		public void Start(ushort port, ushort maxClientCount, byte messageHandlerGroupId = 0, bool useMessageHandlers = true)
		{
			Stop();
			Peer.IncreaseActiveCount();
			base.useMessageHandlers = useMessageHandlers;
			if (useMessageHandlers)
			{
				CreateMessageHandlersDictionary(messageHandlerGroupId);
			}
			MaxClientCount = maxClientCount;
			clients = new Dictionary<ushort, Connection>(maxClientCount);
			InitializeClientIds();
			SubToTransportEvents();
			transport.Start(port);
			StartTime();
			Heartbeat();
			IsRunning = true;
			RiptideLogger.Log(LogType.Info, LogName, $"Started on port {port}.");
		}

		private void SubToTransportEvents()
		{
			transport.Connected += HandleConnectionAttempt;
			transport.DataReceived += base.HandleData;
			transport.Disconnected += TransportDisconnected;
		}

		private void UnsubFromTransportEvents()
		{
			transport.Connected -= HandleConnectionAttempt;
			transport.DataReceived -= base.HandleData;
			transport.Disconnected -= TransportDisconnected;
		}

		protected override void CreateMessageHandlersDictionary(byte messageHandlerGroupId)
		{
			MethodInfo[] array = FindMessageHandlers();
			messageHandlers = new Dictionary<ushort, MessageHandler>(array.Length);
			MethodInfo[] array2 = array;
			foreach (MethodInfo methodInfo in array2)
			{
				MessageHandlerAttribute customAttribute = methodInfo.GetCustomAttribute<MessageHandlerAttribute>();
				if (customAttribute.GroupId != messageHandlerGroupId)
				{
					continue;
				}
				if (!methodInfo.IsStatic)
				{
					throw new NonStaticHandlerException(methodInfo.DeclaringType, methodInfo.Name);
				}
				Delegate @delegate = Delegate.CreateDelegate(typeof(MessageHandler), methodInfo, throwOnBindFailure: false);
				if ((object)@delegate != null)
				{
					if (messageHandlers.ContainsKey(customAttribute.MessageId))
					{
						MethodInfo methodInfo2 = messageHandlers[customAttribute.MessageId].GetMethodInfo();
						throw new DuplicateHandlerException(customAttribute.MessageId, methodInfo, methodInfo2);
					}
					messageHandlers.Add(customAttribute.MessageId, (MessageHandler)@delegate);
				}
				else if ((object)Delegate.CreateDelegate(typeof(Client.MessageHandler), methodInfo, throwOnBindFailure: false) == null)
				{
					throw new InvalidHandlerSignatureException(methodInfo.DeclaringType, methodInfo.Name);
				}
			}
		}

		private void HandleConnectionAttempt(object _, ConnectedEventArgs e)
		{
			e.Connection.Initialize(this, defaultTimeout);
		}

		private void HandleConnect(Connection connection, Message connectMessage)
		{
			connection.SetPending();
			if (HandleConnection == null)
			{
				AcceptConnection(connection);
			}
			else if (ClientCount < MaxClientCount)
			{
				if (!clients.ContainsValue(connection) && !pendingConnections.Contains(connection))
				{
					pendingConnections.Add(connection);
					Send(Message.Create(MessageHeader.Connect), connection);
					HandleConnection(connection, connectMessage);
				}
				else
				{
					Reject(connection, RejectReason.AlreadyConnected);
				}
			}
			else
			{
				Reject(connection, RejectReason.ServerFull);
			}
		}

		public void Accept(Connection connection)
		{
			if (pendingConnections.Remove(connection))
			{
				AcceptConnection(connection);
				return;
			}
			RiptideLogger.Log(LogType.Warning, LogName, $"Couldn't accept connection from {connection} because no such connection was pending!");
		}

		public void Reject(Connection connection, Message message = null)
		{
			if (message != null && message.ReadBits != 0)
			{
				RiptideLogger.Log(LogType.Error, LogName, "Use the parameterless 'Message.Create()' overload when setting rejection data!");
			}
			if (pendingConnections.Remove(connection))
			{
				Reject(connection, (message == null) ? RejectReason.Rejected : RejectReason.Custom, message);
				return;
			}
			RiptideLogger.Log(LogType.Warning, LogName, $"Couldn't reject connection from {connection} because no such connection was pending!");
		}

		private void AcceptConnection(Connection connection)
		{
			if (ClientCount < MaxClientCount)
			{
				if (!clients.ContainsValue(connection))
				{
					ushort key = (connection.Id = GetAvailableClientId());
					clients.Add(key, connection);
					connection.ResetTimeout();
					connection.SendWelcome();
				}
				else
				{
					Reject(connection, RejectReason.AlreadyConnected);
				}
			}
			else
			{
				Reject(connection, RejectReason.ServerFull);
			}
		}

		private void Reject(Connection connection, RejectReason reason, Message rejectMessage = null)
		{
			if (reason != RejectReason.AlreadyConnected)
			{
				Message message = Message.Create(MessageHeader.Reject);
				message.AddByte((byte)reason);
				if (reason == RejectReason.Custom)
				{
					message.AddMessage(rejectMessage);
				}
				for (int i = 0; i < 3; i++)
				{
					connection.Send(message, shouldRelease: false);
				}
				message.Release();
			}
			connection.ResetTimeout();
			connection.LocalDisconnect();
			RiptideLogger.Log(LogType.Info, LogName, $"Rejected connection from {connection}: {Helper.GetReasonString(reason)}.");
		}

		internal override void Heartbeat()
		{
			foreach (Connection value in clients.Values)
			{
				if (value.HasTimedOut)
				{
					timedOutClients.Add(value);
				}
			}
			foreach (Connection pendingConnection in pendingConnections)
			{
				if (pendingConnection.HasConnectAttemptTimedOut)
				{
					timedOutClients.Add(pendingConnection);
				}
			}
			foreach (Connection timedOutClient in timedOutClients)
			{
				LocalDisconnect(timedOutClient, DisconnectReason.TimedOut);
			}
			timedOutClients.Clear();
			ExecuteLater(base.HeartbeatInterval, new HeartbeatEvent(this));
		}

		public override void Update()
		{
			base.Update();
			transport.Poll();
			HandleMessages();
		}

		protected override void Handle(Message message, MessageHeader header, Connection connection)
		{
			switch (header)
			{
			case MessageHeader.Unreliable:
			case MessageHeader.Reliable:
				OnMessageReceived(message, connection);
				break;
			case MessageHeader.Ack:
				connection.HandleAck(message);
				break;
			case MessageHeader.Connect:
				HandleConnect(connection, message);
				break;
			case MessageHeader.Heartbeat:
				connection.HandleHeartbeat(message);
				break;
			case MessageHeader.Disconnect:
				LocalDisconnect(connection, DisconnectReason.Disconnected);
				break;
			case MessageHeader.Welcome:
				if (connection.HandleWelcomeResponse(message))
				{
					OnClientConnected(connection);
				}
				break;
			default:
				RiptideLogger.Log(LogType.Warning, LogName, $"Unexpected message header '{header}'! Discarding {message.BytesInUse} bytes received from {connection}.");
				break;
			}
			message.Release();
		}

		public void Send(Message message, ushort toClient, bool shouldRelease = true)
		{
			if (clients.TryGetValue(toClient, out var value))
			{
				Send(message, value, shouldRelease);
			}
		}

		public ushort Send(Message message, Connection toClient, bool shouldRelease = true)
		{
			return toClient.Send(message, shouldRelease);
		}

		public void SendToAll(Message message, bool shouldRelease = true)
		{
			foreach (Connection value in clients.Values)
			{
				value.Send(message, shouldRelease: false);
			}
			if (shouldRelease)
			{
				message.Release();
			}
		}

		public void SendToAll(Message message, ushort exceptToClientId, bool shouldRelease = true)
		{
			foreach (Connection value in clients.Values)
			{
				if (value.Id != exceptToClientId)
				{
					value.Send(message, shouldRelease: false);
				}
			}
			if (shouldRelease)
			{
				message.Release();
			}
		}

		public bool TryGetClient(ushort id, out Connection client)
		{
			return clients.TryGetValue(id, out client);
		}

		public void DisconnectClient(ushort id, Message message = null)
		{
			if (message != null && message.ReadBits != 0)
			{
				RiptideLogger.Log(LogType.Error, LogName, "Use the parameterless 'Message.Create()' overload when setting disconnection data!");
			}
			if (clients.TryGetValue(id, out var value))
			{
				SendDisconnect(value, DisconnectReason.Kicked, message);
				LocalDisconnect(value, DisconnectReason.Kicked);
				return;
			}
			RiptideLogger.Log(LogType.Warning, LogName, $"Couldn't disconnect client {id} because it wasn't connected!");
		}

		public void DisconnectClient(Connection client, Message message = null)
		{
			if (message != null && message.ReadBits != 0)
			{
				RiptideLogger.Log(LogType.Error, LogName, "Use the parameterless 'Message.Create()' overload when setting disconnection data!");
			}
			if (clients.ContainsKey(client.Id))
			{
				SendDisconnect(client, DisconnectReason.Kicked, message);
				LocalDisconnect(client, DisconnectReason.Kicked);
				return;
			}
			RiptideLogger.Log(LogType.Warning, LogName, $"Couldn't disconnect client {client.Id} because it wasn't connected!");
		}

		internal override void Disconnect(Connection connection, DisconnectReason reason)
		{
			if (connection.IsConnected && connection.CanQualityDisconnect)
			{
				LocalDisconnect(connection, reason);
			}
		}

		private void LocalDisconnect(Connection client, DisconnectReason reason)
		{
			if (client.Peer == this)
			{
				transport.Close(client);
				if (clients.Remove(client.Id))
				{
					availableClientIds.Enqueue(client.Id);
				}
				if (client.IsConnected)
				{
					OnClientDisconnected(client, reason);
				}
				else if (client.IsPending)
				{
					OnConnectionFailed(client);
				}
				client.LocalDisconnect();
			}
		}

		private void TransportDisconnected(object sender, Riptide.Transports.DisconnectedEventArgs e)
		{
			LocalDisconnect(e.Connection, e.Reason);
		}

		public void Stop()
		{
			if (IsRunning)
			{
				pendingConnections.Clear();
				SendToAll(Message.Create(MessageHeader.Disconnect).AddByte(5));
				clients.Clear();
				transport.Shutdown();
				UnsubFromTransportEvents();
				Peer.DecreaseActiveCount();
				StopTime();
				IsRunning = false;
				RiptideLogger.Log(LogType.Info, LogName, "Server stopped.");
			}
		}

		private void InitializeClientIds()
		{
			if (MaxClientCount > 65534)
			{
				throw new Exception($"A server's max client count may not exceed {65534}!");
			}
			availableClientIds = new Queue<ushort>(MaxClientCount);
			for (ushort num = 1; num <= MaxClientCount; num++)
			{
				availableClientIds.Enqueue(num);
			}
		}

		private ushort GetAvailableClientId()
		{
			if (availableClientIds.Count > 0)
			{
				return availableClientIds.Dequeue();
			}
			RiptideLogger.Log(LogType.Error, LogName, "No available client IDs, assigned 0!");
			return 0;
		}

		private void SendDisconnect(Connection client, DisconnectReason reason, Message disconnectMessage)
		{
			Message message = Message.Create(MessageHeader.Disconnect);
			message.AddByte((byte)reason);
			if (reason == DisconnectReason.Kicked && disconnectMessage != null)
			{
				message.AddMessage(disconnectMessage);
			}
			Send(message, client);
		}

		private void SendClientConnected(Connection newClient)
		{
			Message message = Message.Create(MessageHeader.ClientConnected);
			message.AddUShort(newClient.Id);
			SendToAll(message, newClient.Id);
		}

		private void SendClientDisconnected(ushort id)
		{
			Message message = Message.Create(MessageHeader.ClientDisconnected);
			message.AddUShort(id);
			SendToAll(message);
		}

		protected virtual void OnClientConnected(Connection client)
		{
			RiptideLogger.Log(LogType.Info, LogName, $"Client {client.Id} ({client}) connected successfully!");
			SendClientConnected(client);
			this.ClientConnected?.Invoke(this, new ServerConnectedEventArgs(client));
		}

		protected virtual void OnConnectionFailed(Connection connection)
		{
			RiptideLogger.Log(LogType.Info, LogName, $"Client {connection} stopped responding before the connection was fully established!");
			this.ConnectionFailed?.Invoke(this, new ServerConnectionFailedEventArgs(connection));
		}

		protected virtual void OnMessageReceived(Message message, Connection fromConnection)
		{
			ushort num = (ushort)message.GetVarULong();
			if (RelayFilter != null && RelayFilter.ShouldRelay(num))
			{
				SendToAll(message, fromConnection.Id);
				return;
			}
			this.MessageReceived?.Invoke(this, new MessageReceivedEventArgs(fromConnection, num, message));
			if (useMessageHandlers)
			{
				if (messageHandlers.TryGetValue(num, out var value))
				{
					value(fromConnection.Id, message);
					return;
				}
				RiptideLogger.Log(LogType.Warning, LogName, $"No message handler method found for message ID {num}!");
			}
		}

		protected virtual void OnClientDisconnected(Connection connection, DisconnectReason reason)
		{
			RiptideLogger.Log(LogType.Info, LogName, $"Client {connection.Id} ({connection}) disconnected: {Helper.GetReasonString(reason)}.");
			SendClientDisconnected(connection.Id);
			this.ClientDisconnected?.Invoke(this, new ServerDisconnectedEventArgs(connection, reason));
		}
	}
}
namespace Riptide.Utils
{
	internal class Bitfield
	{
		private const int SegmentSize = 32;

		private readonly List<uint> segments;

		private readonly bool isDynamicCapacity;

		private int count;

		private int capacity;

		internal byte First8 => (byte)segments[0];

		internal ushort First16 => (ushort)segments[0];

		internal Bitfield(bool isDynamicCapacity = true)
		{
			segments = new List<uint>(4) { 0u };
			capacity = segments.Count * 32;
			this.isDynamicCapacity = isDynamicCapacity;
		}

		internal bool HasCapacityFor(int amount, out int overflow)
		{
			overflow = count + amount - capacity;
			return overflow < 0;
		}

		internal void ShiftBy(int amount)
		{
			int num = amount / 32;
			int num2 = amount % 32;
			int overflow;
			if (!isDynamicCapacity)
			{
				count = Math.Min(count + amount, 32);
			}
			else if (!HasCapacityFor(amount, out overflow))
			{
				Trim();
				count += amount;
				if (count > capacity)
				{
					int num3 = num + 1;
					for (int i = 0; i < num3; i++)
					{
						segments.Add(0u);
					}
					capacity = segments.Count * 32;
				}
			}
			else
			{
				count += amount;
			}
			int num4 = segments.Count - 1;
			segments[num4] <<= num2;
			for (num4 -= 1 + num; num4 > -1; num4--)
			{
				ulong num5 = (ulong)segments[num4] << num2;
				segments[num4] = (uint)num5;
				segments[num4 + 1 + num] |= (uint)(int)(num5 >> 32);
			}
		}

		internal bool CheckAndTrimLast(out int checkedPosition)
		{
			checkedPosition = count;
			uint num = (uint)(1 << (count - 1) % 32);
			bool result = (segments[segments.Count - 1] & num) != 0;
			count--;
			return result;
		}

		private void Trim()
		{
			while (count > 0 && IsSet(count))
			{
				count--;
			}
		}

		internal void Set(int bit)
		{
			if (bit < 1)
			{
				throw new ArgumentOutOfRangeException("bit", "'bit' must be greater than zero!");
			}
			bit--;
			int num = bit / 32;
			uint num2 = (uint)(1 << bit % 32);
			if (num < segments.Count)
			{
				segments[num] |= num2;
			}
		}

		internal bool IsSet(int bit)
		{
			if (bit > count)
			{
				return true;
			}
			if (bit < 1)
			{
				throw new ArgumentOutOfRangeException("bit", "'bit' must be greater than zero!");
			}
			bit--;
			int num = bit / 32;
			uint num2 = (uint)(1 << bit % 32);
			if (num < segments.Count)
			{
				return (segments[num] & num2) != 0;
			}
			return true;
		}

		internal void Combine(ushort other)
		{
			segments[0] |= other;
		}
	}
	public class ConnectionMetrics
	{
		public readonly RollingStat RollingReliableSends;

		private const ulong ULongLeftBit = 9223372036854775808uL;