Decompiled source of FusionNetworkingPlus v1.1.2
Mods/FNPlus.dll
Decompiled 2 months ago
The result has been truncated due to the large size, download it to view full contents!
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;