Decompiled source of SULFUR Together v1.0.0
LiteNetLib.dll
Decompiled 19 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using LiteNetLib.Layers; using LiteNetLib.Utils; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Ruslan Pyrch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright 2024 Ruslan Pyrch")] [assembly: AssemblyDescription("Lite reliable UDP library for .NET, Mono, and .NET Core")] [assembly: AssemblyFileVersion("1.3.5")] [assembly: AssemblyInformationalVersion("1.0.0+cf54c7d7a15a45bd2460cd4a460807329ee7026a")] [assembly: AssemblyProduct("LiteNetLib")] [assembly: AssemblyTitle("LiteNetLib")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/RevenantX/LiteNetLib")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.5.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] internal sealed class IsUnmanagedAttribute : Attribute { } [CompilerGenerated] [Embedded] internal sealed class IsByRefLikeAttribute : Attribute { } } namespace LiteNetLib { internal abstract class BaseChannel { protected readonly NetPeer Peer; protected readonly Queue<NetPacket> OutgoingQueue = new Queue<NetPacket>(64); private int _isAddedToPeerChannelSendQueue; public int PacketsInQueue => OutgoingQueue.Count; protected BaseChannel(NetPeer peer) { Peer = peer; } public void AddToQueue(NetPacket packet) { lock (OutgoingQueue) { OutgoingQueue.Enqueue(packet); } AddToPeerChannelSendQueue(); } protected void AddToPeerChannelSendQueue() { if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0) { Peer.AddToReliableChannelSendQueue(this); } } public bool SendAndCheckQueue() { bool num = SendNextPackets(); if (!num) { Interlocked.Exchange(ref _isAddedToPeerChannelSendQueue, 0); } return num; } protected abstract bool SendNextPackets(); public abstract bool ProcessPacket(NetPacket packet); } internal enum ConnectionRequestResult { None, Accept, Reject, RejectForce } public class ConnectionRequest { private readonly NetManager _listener; private int _used; internal NetConnectRequestPacket InternalPacket; public readonly IPEndPoint RemoteEndPoint; public NetDataReader Data => InternalPacket.Data; internal ConnectionRequestResult Result { get; private set; } internal void UpdateRequest(NetConnectRequestPacket connectRequest) { if (connectRequest.ConnectionTime >= InternalPacket.ConnectionTime && (connectRequest.ConnectionTime != InternalPacket.ConnectionTime || connectRequest.ConnectionNumber != InternalPacket.ConnectionNumber)) { InternalPacket = connectRequest; } } private bool TryActivate() { return Interlocked.CompareExchange(ref _used, 1, 0) == 0; } internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, NetManager listener) { InternalPacket = requestPacket; RemoteEndPoint = remoteEndPoint; _listener = listener; } public NetPeer AcceptIfKey(string key) { if (!TryActivate()) { return null; } try { if (Data.GetString() == key) { Result = ConnectionRequestResult.Accept; } } catch { NetDebug.WriteError("[AC] Invalid incoming data"); } if (Result == ConnectionRequestResult.Accept) { return _listener.OnConnectionSolved(this, null, 0, 0); } Result = ConnectionRequestResult.Reject; _listener.OnConnectionSolved(this, null, 0, 0); return null; } public NetPeer Accept() { if (!TryActivate()) { return null; } Result = ConnectionRequestResult.Accept; return _listener.OnConnectionSolved(this, null, 0, 0); } public void Reject(byte[] rejectData, int start, int length, bool force) { if (TryActivate()) { Result = (force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject); _listener.OnConnectionSolved(this, rejectData, start, length); } } public void Reject(byte[] rejectData, int start, int length) { Reject(rejectData, start, length, force: false); } public void RejectForce(byte[] rejectData, int start, int length) { Reject(rejectData, start, length, force: true); } public void RejectForce() { Reject(null, 0, 0, force: true); } public void RejectForce(byte[] rejectData) { Reject(rejectData, 0, rejectData.Length, force: true); } public void RejectForce(NetDataWriter rejectData) { Reject(rejectData.Data, 0, rejectData.Length, force: true); } public void Reject() { Reject(null, 0, 0, force: false); } public void Reject(byte[] rejectData) { Reject(rejectData, 0, rejectData.Length, force: false); } public void Reject(NetDataWriter rejectData) { Reject(rejectData.Data, 0, rejectData.Length, force: false); } } public enum UnconnectedMessageType { BasicMessage, Broadcast } public enum DisconnectReason { ConnectionFailed, Timeout, HostUnreachable, NetworkUnreachable, RemoteConnectionClose, DisconnectPeerCalled, ConnectionRejected, InvalidProtocol, UnknownHost, Reconnect, PeerToPeerConnection, PeerNotFound } public struct DisconnectInfo { public DisconnectReason Reason; public SocketError SocketErrorCode; public NetPacketReader AdditionalData; } public interface INetEventListener { void OnPeerConnected(NetPeer peer); void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); void OnNetworkError(IPEndPoint endPoint, SocketError socketError); void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod); void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); void OnNetworkLatencyUpdate(NetPeer peer, int latency); void OnConnectionRequest(ConnectionRequest request); } public interface IDeliveryEventListener { void OnMessageDelivered(NetPeer peer, object userData); } public interface INtpEventListener { void OnNtpResponse(NtpPacket packet); } public interface IPeerAddressChangedListener { void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress); } public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener { public delegate void OnPeerConnected(NetPeer peer); public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError); public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod); public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency); public delegate void OnConnectionRequest(ConnectionRequest request); public delegate void OnDeliveryEvent(NetPeer peer, object userData); public delegate void OnNtpResponseEvent(NtpPacket packet); public delegate void OnPeerAddressChangedEvent(NetPeer peer, IPEndPoint previousAddress); public event OnPeerConnected PeerConnectedEvent; public event OnPeerDisconnected PeerDisconnectedEvent; public event OnNetworkError NetworkErrorEvent; public event OnNetworkReceive NetworkReceiveEvent; public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent; public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent; public event OnConnectionRequest ConnectionRequestEvent; public event OnDeliveryEvent DeliveryEvent; public event OnNtpResponseEvent NtpResponseEvent; public event OnPeerAddressChangedEvent PeerAddressChangedEvent; public void ClearPeerConnectedEvent() { this.PeerConnectedEvent = null; } public void ClearPeerDisconnectedEvent() { this.PeerDisconnectedEvent = null; } public void ClearNetworkErrorEvent() { this.NetworkErrorEvent = null; } public void ClearNetworkReceiveEvent() { this.NetworkReceiveEvent = null; } public void ClearNetworkReceiveUnconnectedEvent() { this.NetworkReceiveUnconnectedEvent = null; } public void ClearNetworkLatencyUpdateEvent() { this.NetworkLatencyUpdateEvent = null; } public void ClearConnectionRequestEvent() { this.ConnectionRequestEvent = null; } public void ClearDeliveryEvent() { this.DeliveryEvent = null; } public void ClearNtpResponseEvent() { this.NtpResponseEvent = null; } public void ClearPeerAddressChangedEvent() { this.PeerAddressChangedEvent = null; } void INetEventListener.OnPeerConnected(NetPeer peer) { if (this.PeerConnectedEvent != null) { this.PeerConnectedEvent(peer); } } void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { if (this.PeerDisconnectedEvent != null) { this.PeerDisconnectedEvent(peer, disconnectInfo); } } void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) { if (this.NetworkErrorEvent != null) { this.NetworkErrorEvent(endPoint, socketErrorCode); } } void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) { if (this.NetworkReceiveEvent != null) { this.NetworkReceiveEvent(peer, reader, channelNumber, deliveryMethod); } } void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { if (this.NetworkReceiveUnconnectedEvent != null) { this.NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType); } } void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) { if (this.NetworkLatencyUpdateEvent != null) { this.NetworkLatencyUpdateEvent(peer, latency); } } void INetEventListener.OnConnectionRequest(ConnectionRequest request) { if (this.ConnectionRequestEvent != null) { this.ConnectionRequestEvent(request); } } void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData) { if (this.DeliveryEvent != null) { this.DeliveryEvent(peer, userData); } } void INtpEventListener.OnNtpResponse(NtpPacket packet) { if (this.NtpResponseEvent != null) { this.NtpResponseEvent(packet); } } void IPeerAddressChangedListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) { if (this.PeerAddressChangedEvent != null) { this.PeerAddressChangedEvent(peer, previousAddress); } } } internal sealed class NetConnectRequestPacket { public const int HeaderSize = 18; public readonly long ConnectionTime; public byte ConnectionNumber; public readonly byte[] TargetAddress; public readonly NetDataReader Data; public readonly int PeerId; private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int localId, byte[] targetAddress, NetDataReader data) { ConnectionTime = connectionTime; ConnectionNumber = connectionNumber; TargetAddress = targetAddress; Data = data; PeerId = localId; } public static int GetProtocolId(NetPacket packet) { return BitConverter.ToInt32(packet.RawData, 1); } public static NetConnectRequestPacket FromData(NetPacket packet) { if (packet.ConnectionNumber >= 4) { return null; } long connectionTime = BitConverter.ToInt64(packet.RawData, 5); int localId = BitConverter.ToInt32(packet.RawData, 13); int num = packet.RawData[17]; if (num != 16 && num != 28) { return null; } byte[] array = new byte[num]; Buffer.BlockCopy(packet.RawData, 18, array, 0, num); NetDataReader netDataReader = new NetDataReader(null, 0, 0); if (packet.Size > 18 + num) { netDataReader.SetSource(packet.RawData, 18 + num, packet.Size); } return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, localId, array, netDataReader); } public static NetPacket Make(NetDataWriter connectData, SocketAddress addressBytes, long connectTime, int localId) { NetPacket netPacket = new NetPacket(PacketProperty.ConnectRequest, connectData.Length + addressBytes.Size); FastBitConverter.GetBytes(netPacket.RawData, 1, 13); FastBitConverter.GetBytes(netPacket.RawData, 5, connectTime); FastBitConverter.GetBytes(netPacket.RawData, 13, localId); netPacket.RawData[17] = (byte)addressBytes.Size; for (int i = 0; i < addressBytes.Size; i++) { netPacket.RawData[18 + i] = addressBytes[i]; } Buffer.BlockCopy(connectData.Data, 0, netPacket.RawData, 18 + addressBytes.Size, connectData.Length); return netPacket; } } internal sealed class NetConnectAcceptPacket { public const int Size = 15; public readonly long ConnectionTime; public readonly byte ConnectionNumber; public readonly int PeerId; public readonly bool PeerNetworkChanged; private NetConnectAcceptPacket(long connectionTime, byte connectionNumber, int peerId, bool peerNetworkChanged) { ConnectionTime = connectionTime; ConnectionNumber = connectionNumber; PeerId = peerId; PeerNetworkChanged = peerNetworkChanged; } public static NetConnectAcceptPacket FromData(NetPacket packet) { if (packet.Size != 15) { return null; } long connectionTime = BitConverter.ToInt64(packet.RawData, 1); byte b = packet.RawData[9]; if (b >= 4) { return null; } byte b2 = packet.RawData[10]; if (b2 > 1) { return null; } int num = BitConverter.ToInt32(packet.RawData, 11); if (num < 0) { return null; } return new NetConnectAcceptPacket(connectionTime, b, num, b2 == 1); } public static NetPacket Make(long connectTime, byte connectNum, int localPeerId) { NetPacket netPacket = new NetPacket(PacketProperty.ConnectAccept, 0); FastBitConverter.GetBytes(netPacket.RawData, 1, connectTime); netPacket.RawData[9] = connectNum; FastBitConverter.GetBytes(netPacket.RawData, 11, localPeerId); return netPacket; } public static NetPacket MakeNetworkChanged(NetPeer peer) { NetPacket netPacket = new NetPacket(PacketProperty.PeerNotFound, 14); FastBitConverter.GetBytes(netPacket.RawData, 1, peer.ConnectTime); netPacket.RawData[9] = peer.ConnectionNum; netPacket.RawData[10] = 1; FastBitConverter.GetBytes(netPacket.RawData, 11, peer.RemoteId); return netPacket; } } internal static class NativeSocket { private static class WinSock { private const string LibName = "ws2_32.dll"; [DllImport("ws2_32.dll", SetLastError = true)] public static extern int recvfrom(IntPtr socketHandle, [In][Out] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In][Out] ref int socketAddressSize); [DllImport("ws2_32.dll", SetLastError = true)] internal unsafe static extern int sendto(IntPtr socketHandle, byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize); } private static class UnixSock { private const string LibName = "libc"; [DllImport("libc", SetLastError = true)] public static extern int recvfrom(IntPtr socketHandle, [In][Out] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In][Out] ref int socketAddressSize); [DllImport("libc", SetLastError = true)] internal unsafe static extern int sendto(IntPtr socketHandle, byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize); } public static readonly bool IsSupported; public static readonly bool UnixMode; public const int IPv4AddrSize = 16; public const int IPv6AddrSize = 28; public const int AF_INET = 2; public const int AF_INET6 = 10; private static readonly Dictionary<int, SocketError> NativeErrorToSocketError; static NativeSocket() { IsSupported = false; UnixMode = false; NativeErrorToSocketError = new Dictionary<int, SocketError> { { 13, SocketError.AccessDenied }, { 98, SocketError.AddressAlreadyInUse }, { 99, SocketError.AddressNotAvailable }, { 97, SocketError.AddressFamilyNotSupported }, { 11, SocketError.WouldBlock }, { 114, SocketError.AlreadyInProgress }, { 9, SocketError.OperationAborted }, { 125, SocketError.OperationAborted }, { 103, SocketError.ConnectionAborted }, { 111, SocketError.ConnectionRefused }, { 104, SocketError.ConnectionReset }, { 89, SocketError.DestinationAddressRequired }, { 14, SocketError.Fault }, { 112, SocketError.HostDown }, { 6, SocketError.HostNotFound }, { 113, SocketError.HostUnreachable }, { 115, SocketError.InProgress }, { 4, SocketError.Interrupted }, { 22, SocketError.InvalidArgument }, { 106, SocketError.IsConnected }, { 24, SocketError.TooManyOpenSockets }, { 90, SocketError.MessageSize }, { 100, SocketError.NetworkDown }, { 102, SocketError.NetworkReset }, { 101, SocketError.NetworkUnreachable }, { 23, SocketError.TooManyOpenSockets }, { 105, SocketError.NoBufferSpaceAvailable }, { 61, SocketError.NoData }, { 2, SocketError.AddressNotAvailable }, { 92, SocketError.ProtocolOption }, { 107, SocketError.NotConnected }, { 88, SocketError.NotSocket }, { 3440, SocketError.OperationNotSupported }, { 1, SocketError.AccessDenied }, { 32, SocketError.Shutdown }, { 96, SocketError.ProtocolFamilyNotSupported }, { 93, SocketError.ProtocolNotSupported }, { 91, SocketError.ProtocolType }, { 94, SocketError.SocketNotSupported }, { 108, SocketError.Disconnecting }, { 110, SocketError.TimedOut }, { 0, SocketError.Success } }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { IsSupported = true; UnixMode = true; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { IsSupported = true; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RecvFrom(IntPtr socketHandle, byte[] pinnedBuffer, int len, byte[] socketAddress, ref int socketAddressSize) { if (!UnixMode) { return WinSock.recvfrom(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, ref socketAddressSize); } return UnixSock.recvfrom(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, ref socketAddressSize); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe static int SendTo(IntPtr socketHandle, byte* pinnedBuffer, int len, byte[] socketAddress, int socketAddressSize) { if (!UnixMode) { return WinSock.sendto(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, socketAddressSize); } return UnixSock.sendto(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, socketAddressSize); } public static SocketError GetSocketError() { int lastWin32Error = Marshal.GetLastWin32Error(); if (UnixMode) { if (!NativeErrorToSocketError.TryGetValue(lastWin32Error, out var value)) { return SocketError.SocketError; } return value; } return (SocketError)lastWin32Error; } public static SocketException GetSocketException() { int lastWin32Error = Marshal.GetLastWin32Error(); if (UnixMode) { if (!NativeErrorToSocketError.TryGetValue(lastWin32Error, out var value)) { return new SocketException(-1); } return new SocketException((int)value); } return new SocketException(lastWin32Error); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint) { if (!UnixMode) { return (short)remoteEndPoint.AddressFamily; } return (short)((remoteEndPoint.AddressFamily == AddressFamily.InterNetwork) ? 2 : 10); } } public enum NatAddressType { Internal, External } public interface INatPunchListener { void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token); void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token); } public class EventBasedNatPunchListener : INatPunchListener { public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token); public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token); public event OnNatIntroductionRequest NatIntroductionRequest; public event OnNatIntroductionSuccess NatIntroductionSuccess; void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token) { if (this.NatIntroductionRequest != null) { this.NatIntroductionRequest(localEndPoint, remoteEndPoint, token); } } void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token) { if (this.NatIntroductionSuccess != null) { this.NatIntroductionSuccess(targetEndPoint, type, token); } } } public sealed class NatPunchModule { private struct RequestEventData { public IPEndPoint LocalEndPoint; public IPEndPoint RemoteEndPoint; public string Token; } private struct SuccessEventData { public IPEndPoint TargetEndPoint; public NatAddressType Type; public string Token; } private class NatIntroduceRequestPacket { public IPEndPoint Internal { [Preserve] get; [Preserve] set; } public string Token { [Preserve] get; [Preserve] set; } } private class NatIntroduceResponsePacket { public IPEndPoint Internal { [Preserve] get; [Preserve] set; } public IPEndPoint External { [Preserve] get; [Preserve] set; } public string Token { [Preserve] get; [Preserve] set; } } private class NatPunchPacket { public string Token { [Preserve] get; [Preserve] set; } public bool IsExternal { [Preserve] get; [Preserve] set; } } private readonly NetManager _socket; private readonly ConcurrentQueue<RequestEventData> _requestEvents = new ConcurrentQueue<RequestEventData>(); private readonly ConcurrentQueue<SuccessEventData> _successEvents = new ConcurrentQueue<SuccessEventData>(); private readonly NetDataReader _cacheReader = new NetDataReader(); private readonly NetDataWriter _cacheWriter = new NetDataWriter(); private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(256); private INatPunchListener _natPunchListener; public const int MaxTokenLength = 256; public bool UnsyncedEvents; internal NatPunchModule(NetManager socket) { _socket = socket; _netPacketProcessor.SubscribeReusable<NatIntroduceResponsePacket>(OnNatIntroductionResponse); _netPacketProcessor.SubscribeReusable<NatIntroduceRequestPacket, IPEndPoint>(OnNatIntroductionRequest); _netPacketProcessor.SubscribeReusable<NatPunchPacket, IPEndPoint>(OnNatPunch); } internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet) { lock (_cacheReader) { _cacheReader.SetSource(packet.RawData, 1, packet.Size); _netPacketProcessor.ReadAllPackets(_cacheReader, senderEndPoint); } } public void Init(INatPunchListener listener) { _natPunchListener = listener; } private void Send<T>(T packet, IPEndPoint target) where T : class, new() { _cacheWriter.Reset(); _cacheWriter.Put((byte)16); _netPacketProcessor.Write(_cacheWriter, packet); _socket.SendRaw(_cacheWriter.Data, 0, _cacheWriter.Length, target); } public void NatIntroduce(IPEndPoint hostInternal, IPEndPoint hostExternal, IPEndPoint clientInternal, IPEndPoint clientExternal, string additionalInfo) { NatIntroduceResponsePacket natIntroduceResponsePacket = new NatIntroduceResponsePacket { Token = additionalInfo }; natIntroduceResponsePacket.Internal = hostInternal; natIntroduceResponsePacket.External = hostExternal; Send(natIntroduceResponsePacket, clientExternal); natIntroduceResponsePacket.Internal = clientInternal; natIntroduceResponsePacket.External = clientExternal; Send(natIntroduceResponsePacket, hostExternal); } public void PollEvents() { if (!UnsyncedEvents && _natPunchListener != null && (!_successEvents.IsEmpty || !_requestEvents.IsEmpty)) { SuccessEventData result; while (_successEvents.TryDequeue(out result)) { _natPunchListener.OnNatIntroductionSuccess(result.TargetEndPoint, result.Type, result.Token); } RequestEventData result2; while (_requestEvents.TryDequeue(out result2)) { _natPunchListener.OnNatIntroductionRequest(result2.LocalEndPoint, result2.RemoteEndPoint, result2.Token); } } } public void SendNatIntroduceRequest(string host, int port, string additionalInfo) { SendNatIntroduceRequest(NetUtils.MakeEndPoint(host, port), additionalInfo); } public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo) { string localIp = NetUtils.GetLocalIp(LocalAddrType.IPv4); if (string.IsNullOrEmpty(localIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6) { localIp = NetUtils.GetLocalIp(LocalAddrType.IPv6); } Send(new NatIntroduceRequestPacket { Internal = NetUtils.MakeEndPoint(localIp, _socket.LocalPort), Token = additionalInfo }, masterServerEndPoint); } private void OnNatIntroductionRequest(NatIntroduceRequestPacket req, IPEndPoint senderEndPoint) { if (UnsyncedEvents) { _natPunchListener.OnNatIntroductionRequest(req.Internal, senderEndPoint, req.Token); return; } _requestEvents.Enqueue(new RequestEventData { LocalEndPoint = req.Internal, RemoteEndPoint = senderEndPoint, Token = req.Token }); } private void OnNatIntroductionResponse(NatIntroduceResponsePacket req) { NatPunchPacket natPunchPacket = new NatPunchPacket { Token = req.Token }; Send(natPunchPacket, req.Internal); _socket.Ttl = 2; _socket.SendRaw(new byte[1] { 17 }, 0, 1, req.External); _socket.Ttl = 255; natPunchPacket.IsExternal = true; Send(natPunchPacket, req.External); } private void OnNatPunch(NatPunchPacket req, IPEndPoint senderEndPoint) { if (UnsyncedEvents) { _natPunchListener.OnNatIntroductionSuccess(senderEndPoint, req.IsExternal ? NatAddressType.External : NatAddressType.Internal, req.Token); return; } _successEvents.Enqueue(new SuccessEventData { TargetEndPoint = senderEndPoint, Type = (req.IsExternal ? NatAddressType.External : NatAddressType.Internal), Token = req.Token }); } } public enum DeliveryMethod : byte { Unreliable = 4, ReliableUnordered = 0, Sequenced = 1, ReliableOrdered = 2, ReliableSequenced = 3 } public static class NetConstants { public const int DefaultWindowSize = 64; public const int SocketBufferSize = 1048576; public const int SocketTTL = 255; public const int HeaderSize = 1; public const int ChanneledHeaderSize = 4; public const int FragmentHeaderSize = 6; public const int FragmentedHeaderTotalSize = 10; public const ushort MaxSequence = 32768; public const ushort HalfMaxSequence = 16384; internal const int ProtocolId = 13; internal const int MaxUdpHeaderSize = 68; internal const int ChannelTypeCount = 4; internal const int FragmentedChannelsCount = 2; internal const int MaxFragmentsInWindow = 32; internal static readonly int[] PossibleMtu = new int[6] { 1024, 1164, 1392, 1404, 1424, 1432 }; public static readonly int InitialMtu = PossibleMtu[0]; public static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1]; public static readonly int MaxUnreliableDataSize = MaxPacketSize - 1; public const byte MaxConnectionNumber = 4; } public class InvalidPacketException : ArgumentException { public InvalidPacketException(string message) : base(message) { } } public class TooBigPacketException : InvalidPacketException { public TooBigPacketException(string message) : base(message) { } } public enum NetLogLevel { Warning, Error, Trace, Info } public interface INetLogger { void WriteNet(NetLogLevel level, string str, params object[] args); } public static class NetDebug { public static INetLogger Logger = null; private static readonly object DebugLogLock = new object(); private static void WriteLogic(NetLogLevel logLevel, string str, params object[] args) { lock (DebugLogLock) { if (Logger == null) { Console.WriteLine(str, args); } else { Logger.WriteNet(logLevel, str, args); } } } [Conditional("DEBUG_MESSAGES")] internal static void Write(string str) { WriteLogic(NetLogLevel.Trace, str); } [Conditional("DEBUG_MESSAGES")] internal static void Write(NetLogLevel level, string str) { WriteLogic(level, str); } [Conditional("DEBUG_MESSAGES")] [Conditional("DEBUG")] internal static void WriteForce(string str) { WriteLogic(NetLogLevel.Trace, str); } [Conditional("DEBUG_MESSAGES")] [Conditional("DEBUG")] internal static void WriteForce(NetLogLevel level, string str) { WriteLogic(level, str); } internal static void WriteError(string str) { WriteLogic(NetLogLevel.Error, str); } } public sealed class NetPacketReader : NetDataReader { private NetPacket _packet; private readonly NetManager _manager; private readonly NetEvent _evt; internal NetPacketReader(NetManager manager, NetEvent evt) { _manager = manager; _evt = evt; } internal void SetSource(NetPacket packet, int headerSize) { if (packet != null) { _packet = packet; SetSource(packet.RawData, headerSize, packet.Size); } } internal void RecycleInternal() { Clear(); if (_packet != null) { _manager.PoolRecycle(_packet); } _packet = null; _manager.RecycleEvent(_evt); } public void Recycle() { if (!_manager.AutoRecycle) { RecycleInternal(); } } } internal sealed class NetEvent { public enum EType { Connect, Disconnect, Receive, ReceiveUnconnected, Error, ConnectionLatencyUpdated, Broadcast, ConnectionRequest, MessageDelivered, PeerAddressChanged } public NetEvent Next; public EType Type; public NetPeer Peer; public IPEndPoint RemoteEndPoint; public object UserData; public int Latency; public SocketError ErrorCode; public DisconnectReason DisconnectReason; public ConnectionRequest ConnectionRequest; public DeliveryMethod DeliveryMethod; public byte ChannelNumber; public readonly NetPacketReader DataReader; public NetEvent(NetManager manager) { DataReader = new NetPacketReader(manager, this); } } public class NetManager : IEnumerable<NetPeer>, IEnumerable { public struct NetPeerEnumerator : IEnumerator<NetPeer>, IEnumerator, IDisposable { private readonly NetPeer _initialPeer; private NetPeer _p; public NetPeer Current => _p; object IEnumerator.Current => _p; public NetPeerEnumerator(NetPeer p) { _initialPeer = p; _p = null; } public void Dispose() { } public bool MoveNext() { _p = ((_p == null) ? _initialPeer : _p.NextPeer); return _p != null; } public void Reset() { throw new NotSupportedException(); } } private struct IncomingData { public NetPacket Data; public IPEndPoint EndPoint; public DateTime TimeWhenGet; } private struct OutboundDelayedPacket { public byte[] Data; public int Start; public int Length; public IPEndPoint EndPoint; public DateTime TimeWhenSend; } private struct Slot { internal int HashCode; internal int Next; internal NetPeer Value; } private readonly List<IncomingData> _pingSimulationList = new List<IncomingData>(); private readonly List<OutboundDelayedPacket> _outboundSimulationList = new List<OutboundDelayedPacket>(); private readonly Random _randomGenerator = new Random(); private const int MinLatencyThreshold = 5; private Thread _logicThread; private bool _manualMode; private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(initialState: true); private NetEvent _pendingEventHead; private NetEvent _pendingEventTail; private NetEvent _netEventPoolHead; private readonly INetEventListener _netEventListener; private readonly IDeliveryEventListener _deliveryEventListener; private readonly INtpEventListener _ntpEventListener; private readonly IPeerAddressChangedListener _peerAddressChangedListener; private readonly Dictionary<IPEndPoint, ConnectionRequest> _requestsDict = new Dictionary<IPEndPoint, ConnectionRequest>(); private readonly ConcurrentDictionary<IPEndPoint, NtpRequest> _ntpRequests = new ConcurrentDictionary<IPEndPoint, NtpRequest>(); private long _connectedPeersCount; private readonly List<NetPeer> _connectedPeerListCache = new List<NetPeer>(); private readonly PacketLayerBase _extraPacketLayer; private int _lastPeerId; private ConcurrentQueue<int> _peerIds = new ConcurrentQueue<int>(); private byte _channelsCount = 1; private readonly object _eventLock = new object(); private volatile bool _isRunning; private bool _dropPacket; public bool UnconnectedMessagesEnabled; public bool NatPunchEnabled; public int UpdateTime = 15; public int PingInterval = 1000; public int DisconnectTimeout = 5000; public bool SimulatePacketLoss; public bool SimulateLatency; public int SimulationPacketLossChance = 10; public int SimulationMinLatency = 30; public int SimulationMaxLatency = 100; public bool UnsyncedEvents; public bool UnsyncedReceiveEvent; public bool UnsyncedDeliveryEvent; public bool BroadcastReceiveEnabled; public int ReconnectDelay = 500; public int MaxConnectAttempts = 10; public bool ReuseAddress; public bool DontRoute; public readonly NetStatistics Statistics = new NetStatistics(); public bool EnableStatistics; public readonly NatPunchModule NatPunchModule; public bool AutoRecycle; public bool IPv6Enabled = true; public int MtuOverride; public bool MtuDiscovery; public bool UseNativeSockets; public bool DisconnectOnUnreachable; public bool AllowPeerAddressChange; private const int MaxPrimeArrayLength = 2147483587; private const int HashPrime = 101; private const int Lower31BitMask = int.MaxValue; private static readonly int[] Primes; private int[] _buckets; private Slot[] _slots; private int _count; private int _lastIndex; private int _freeList = -1; private NetPeer[] _peersArray = new NetPeer[32]; private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); private volatile NetPeer _headPeer; private NetPacket _poolHead; private int _poolCount; private readonly object _poolLock = new object(); public int PacketPoolSize = 1000; private Socket _udpSocketv4; private Socket _udpSocketv6; private Thread _receiveThread; private IPEndPoint _bufferEndPointv4; private IPEndPoint _bufferEndPointv6; private const int SioUdpConnreset = -1744830452; private static readonly IPAddress MulticastAddressV6; public static readonly bool IPv6Support; internal bool NotConnected; public int ReceivePollingTime = 50000; public bool IsRunning => _isRunning; public int LocalPort { get; private set; } public NetPeer FirstPeer => _headPeer; public byte ChannelsCount { get { return _channelsCount; } set { if (value < 1 || value > 64) { throw new ArgumentException("Channels count must be between 1 and 64"); } _channelsCount = value; } } public List<NetPeer> ConnectedPeerList { get { GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected); return _connectedPeerListCache; } } public int ConnectedPeersCount => (int)Interlocked.Read(in _connectedPeersCount); public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0; public int PoolCount => _poolCount; public short Ttl { get { return _udpSocketv4.Ttl; } internal set { _udpSocketv4.Ttl = value; } } public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) { _netEventListener = listener; _deliveryEventListener = listener as IDeliveryEventListener; _ntpEventListener = listener as INtpEventListener; _peerAddressChangedListener = listener as IPeerAddressChangedListener; NatPunchModule = new NatPunchModule(this); _extraPacketLayer = extraPacketLayer; } internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency) { CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, null, SocketError.Success, latency, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); } internal void MessageDelivered(NetPeer fromPeer, object userData) { if (_deliveryEventListener != null) { CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, null, userData); } } internal void DisconnectPeerForce(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, NetPacket eventData) { DisconnectPeer(peer, reason, socketErrorCode, force: true, null, 0, 0, eventData); } private void DisconnectPeer(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, bool force, byte[] data, int start, int count, NetPacket eventData) { switch (peer.Shutdown(data, start, count, force)) { case ShutdownResult.None: return; case ShutdownResult.WasConnected: Interlocked.Decrement(ref _connectedPeersCount); break; } CreateEvent(NetEvent.EType.Disconnect, peer, null, socketErrorCode, 0, reason, null, DeliveryMethod.Unreliable, 0, eventData); } private void CreateEvent(NetEvent.EType type, NetPeer peer = null, IPEndPoint remoteEndPoint = null, SocketError errorCode = SocketError.Success, int latency = 0, DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, ConnectionRequest connectionRequest = null, DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, byte channelNumber = 0, NetPacket readerSource = null, object userData = null) { bool flag = UnsyncedEvents; switch (type) { case NetEvent.EType.Connect: Interlocked.Increment(ref _connectedPeersCount); break; case NetEvent.EType.MessageDelivered: flag = UnsyncedDeliveryEvent; break; } NetEvent netEvent; lock (_eventLock) { netEvent = _netEventPoolHead; if (netEvent == null) { netEvent = new NetEvent(this); } else { _netEventPoolHead = netEvent.Next; } } netEvent.Next = null; netEvent.Type = type; netEvent.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0); netEvent.Peer = peer; netEvent.RemoteEndPoint = remoteEndPoint; netEvent.Latency = latency; netEvent.ErrorCode = errorCode; netEvent.DisconnectReason = disconnectReason; netEvent.ConnectionRequest = connectionRequest; netEvent.DeliveryMethod = deliveryMethod; netEvent.ChannelNumber = channelNumber; netEvent.UserData = userData; if (flag || _manualMode) { ProcessEvent(netEvent); return; } lock (_eventLock) { if (_pendingEventTail == null) { _pendingEventHead = netEvent; } else { _pendingEventTail.Next = netEvent; } _pendingEventTail = netEvent; } } private void ProcessEvent(NetEvent evt) { bool isNull = evt.DataReader.IsNull; switch (evt.Type) { case NetEvent.EType.Connect: _netEventListener.OnPeerConnected(evt.Peer); break; case NetEvent.EType.Disconnect: { DisconnectInfo disconnectInfo = new DisconnectInfo { Reason = evt.DisconnectReason, AdditionalData = evt.DataReader, SocketErrorCode = evt.ErrorCode }; _netEventListener.OnPeerDisconnected(evt.Peer, disconnectInfo); break; } case NetEvent.EType.Receive: _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod); break; case NetEvent.EType.ReceiveUnconnected: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage); break; case NetEvent.EType.Broadcast: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast); break; case NetEvent.EType.Error: _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode); break; case NetEvent.EType.ConnectionLatencyUpdated: _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency); break; case NetEvent.EType.ConnectionRequest: _netEventListener.OnConnectionRequest(evt.ConnectionRequest); break; case NetEvent.EType.MessageDelivered: _deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData); break; case NetEvent.EType.PeerAddressChanged: { _peersLock.EnterUpgradeableReadLock(); IPEndPoint iPEndPoint = null; if (ContainsPeer(evt.Peer)) { _peersLock.EnterWriteLock(); RemovePeerFromSet(evt.Peer); iPEndPoint = new IPEndPoint(evt.Peer.Address, evt.Peer.Port); evt.Peer.FinishEndPointChange(evt.RemoteEndPoint); AddPeerToSet(evt.Peer); _peersLock.ExitWriteLock(); } _peersLock.ExitUpgradeableReadLock(); if (iPEndPoint != null && _peerAddressChangedListener != null) { _peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, iPEndPoint); } break; } } if (isNull) { RecycleEvent(evt); } else if (AutoRecycle) { evt.DataReader.RecycleInternal(); } } internal void RecycleEvent(NetEvent evt) { evt.Peer = null; evt.ErrorCode = SocketError.Success; evt.RemoteEndPoint = null; evt.ConnectionRequest = null; lock (_eventLock) { evt.Next = _netEventPoolHead; _netEventPoolHead = evt; } } private void UpdateLogic() { List<NetPeer> list = new List<NetPeer>(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (_isRunning) { try { float num = (float)((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0); num = ((num <= 0f) ? 0.001f : num); stopwatch.Restart(); for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > (float)DisconnectTimeout) { list.Add(netPeer); } else { netPeer.Update(num); } } if (list.Count > 0) { _peersLock.EnterWriteLock(); for (int i = 0; i < list.Count; i++) { RemovePeer(list[i], enableWriteLock: false); } _peersLock.ExitWriteLock(); list.Clear(); } ProcessNtpRequests(num); int num2 = UpdateTime - (int)stopwatch.ElapsedMilliseconds; if (num2 > 0) { _updateTriggerEvent.WaitOne(num2); } } catch (ThreadAbortException) { return; } catch (Exception ex2) { NetDebug.WriteError("[NM] LogicThread error: " + ex2); } } stopwatch.Stop(); } [Conditional("DEBUG")] [Conditional("SIMULATE_NETWORK")] private void ProcessDelayedPackets() { if (!SimulateLatency) { return; } DateTime utcNow = DateTime.UtcNow; lock (_pingSimulationList) { for (int i = 0; i < _pingSimulationList.Count; i++) { IncomingData incomingData = _pingSimulationList[i]; if (incomingData.TimeWhenGet <= utcNow) { HandleMessageReceived(incomingData.Data, incomingData.EndPoint); _pingSimulationList.RemoveAt(i); i--; } } } lock (_outboundSimulationList) { for (int j = 0; j < _outboundSimulationList.Count; j++) { OutboundDelayedPacket outboundDelayedPacket = _outboundSimulationList[j]; if (outboundDelayedPacket.TimeWhenSend <= utcNow) { SendRawCore(outboundDelayedPacket.Data, outboundDelayedPacket.Start, outboundDelayedPacket.Length, outboundDelayedPacket.EndPoint); _outboundSimulationList.RemoveAt(j); j--; } } } } private void ProcessNtpRequests(float elapsedMilliseconds) { if (_ntpRequests.IsEmpty) { return; } List<IPEndPoint> list = null; foreach (KeyValuePair<IPEndPoint, NtpRequest> ntpRequest in _ntpRequests) { ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds); if (ntpRequest.Value.NeedToKill) { if (list == null) { list = new List<IPEndPoint>(); } list.Add(ntpRequest.Key); } } if (list == null) { return; } foreach (IPEndPoint item in list) { _ntpRequests.TryRemove(item, out var _); } } public void ManualUpdate(float elapsedMilliseconds) { if (!_manualMode) { return; } for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > (float)DisconnectTimeout) { RemovePeer(netPeer, enableWriteLock: false); } else { netPeer.Update(elapsedMilliseconds); } } ProcessNtpRequests(elapsedMilliseconds); } internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length) { NetPeer actualValue = null; if (request.Result == ConnectionRequestResult.RejectForce) { if (rejectData != null && length > 0) { NetPacket netPacket = PoolGetWithProperty(PacketProperty.Disconnect, length); netPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber; FastBitConverter.GetBytes(netPacket.RawData, 1, request.InternalPacket.ConnectionTime); if (netPacket.Size >= NetConstants.PossibleMtu[0]) { NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!"); } else { Buffer.BlockCopy(rejectData, start, netPacket.RawData, 9, length); } SendRawAndRecycle(netPacket, request.RemoteEndPoint); } lock (_requestsDict) { _requestsDict.Remove(request.RemoteEndPoint); } } else { lock (_requestsDict) { if (!TryGetPeer(request.RemoteEndPoint, out actualValue)) { if (request.Result == ConnectionRequestResult.Reject) { actualValue = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId()); actualValue.Reject(request.InternalPacket, rejectData, start, length); AddPeer(actualValue); } else { actualValue = new NetPeer(this, request, GetNextPeerId()); AddPeer(actualValue); CreateEvent(NetEvent.EType.Connect, actualValue, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); } } _requestsDict.Remove(request.RemoteEndPoint); } } return actualValue; } private int GetNextPeerId() { if (!_peerIds.TryDequeue(out var result)) { return _lastPeerId++; } return result; } private void ProcessConnectRequest(IPEndPoint remoteEndPoint, NetPeer netPeer, NetConnectRequestPacket connRequest) { if (netPeer != null) { ConnectRequestResult connectRequestResult = netPeer.ProcessConnectRequest(connRequest); switch (connectRequestResult) { default: return; case ConnectRequestResult.Reconnection: DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, SocketError.Success, null); RemovePeer(netPeer, enableWriteLock: true); break; case ConnectRequestResult.NewConnection: RemovePeer(netPeer, enableWriteLock: true); break; case ConnectRequestResult.P2PLose: DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, SocketError.Success, null); RemovePeer(netPeer, enableWriteLock: true); break; } if (connectRequestResult != ConnectRequestResult.P2PLose) { connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % 4); } } ConnectionRequest value; lock (_requestsDict) { if (_requestsDict.TryGetValue(remoteEndPoint, out value)) { value.UpdateRequest(connRequest); return; } value = new ConnectionRequest(remoteEndPoint, connRequest, this); _requestsDict.Add(remoteEndPoint, value); } CreateEvent(NetEvent.EType.ConnectionRequest, null, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, value, DeliveryMethod.Unreliable, 0); } private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) { if (packet.Size == 0) { PoolRecycle(packet); return; } _dropPacket = false; if (!_dropPacket) { HandleMessageReceived(packet, remoteEndPoint); } } [Conditional("DEBUG")] [Conditional("SIMULATE_NETWORK")] private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint) { if (!SimulateLatency) { return; } int num = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency) / 2; if (num > 5) { lock (_pingSimulationList) { _pingSimulationList.Add(new IncomingData { Data = packet, EndPoint = remoteEndPoint, TimeWhenGet = DateTime.UtcNow.AddMilliseconds(num) }); } _dropPacket = true; } } [Conditional("DEBUG")] [Conditional("SIMULATE_NETWORK")] private void HandleSimulatePacketLoss() { if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100.0 < (double)SimulationPacketLossChance) { _dropPacket = true; } } private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) { int size = packet.Size; if (EnableStatistics) { Statistics.IncrementPacketsReceived(); Statistics.AddBytesReceived(size); } if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out var _)) { if (packet.Size >= 48) { byte[] array = new byte[packet.Size]; Buffer.BlockCopy(packet.RawData, 0, array, 0, packet.Size); NtpPacket ntpPacket = NtpPacket.FromServerResponse(array, DateTime.UtcNow); try { ntpPacket.ValidateReply(); } catch (InvalidOperationException) { ntpPacket = null; } if (ntpPacket != null) { _ntpRequests.TryRemove(remoteEndPoint, out var _); _ntpEventListener?.OnNtpResponse(ntpPacket); } } return; } if (_extraPacketLayer != null) { _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size); if (packet.Size == 0) { return; } } if (!packet.Verify()) { NetDebug.WriteError("[NM] DataReceived: bad!"); PoolRecycle(packet); return; } switch (packet.Property) { case PacketProperty.ConnectRequest: if (NetConnectRequestPacket.GetProtocolId(packet) != 13) { SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint); return; } break; case PacketProperty.Broadcast: if (BroadcastReceiveEnabled) { CreateEvent(NetEvent.EType.Broadcast, null, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, packet); } return; case PacketProperty.UnconnectedMessage: if (UnconnectedMessagesEnabled) { CreateEvent(NetEvent.EType.ReceiveUnconnected, null, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, packet); } return; case PacketProperty.NatMessage: if (NatPunchEnabled) { NatPunchModule.ProcessMessage(remoteEndPoint, packet); } return; } NetPeer actualValue = remoteEndPoint as NetPeer; bool flag = actualValue != null || TryGetPeer(remoteEndPoint, out actualValue); if (flag && EnableStatistics) { actualValue.Statistics.IncrementPacketsReceived(); actualValue.Statistics.AddBytesReceived(size); } switch (packet.Property) { case PacketProperty.ConnectRequest: { NetConnectRequestPacket netConnectRequestPacket = NetConnectRequestPacket.FromData(packet); if (netConnectRequestPacket != null) { ProcessConnectRequest(remoteEndPoint, actualValue, netConnectRequestPacket); } break; } case PacketProperty.PeerNotFound: if (flag) { if (actualValue.ConnectionState == ConnectionState.Connected) { if (packet.Size == 1) { actualValue.ResetMtu(); SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(actualValue), remoteEndPoint); } else if (packet.Size == 2 && packet.RawData[1] == 1) { DisconnectPeerForce(actualValue, DisconnectReason.PeerNotFound, SocketError.Success, null); } } } else { if (packet.Size <= 1) { break; } bool flag2 = false; if (AllowPeerAddressChange) { NetConnectAcceptPacket netConnectAcceptPacket = NetConnectAcceptPacket.FromData(packet); if (netConnectAcceptPacket != null && netConnectAcceptPacket.PeerNetworkChanged && netConnectAcceptPacket.PeerId < _peersArray.Length) { _peersLock.EnterUpgradeableReadLock(); NetPeer netPeer = _peersArray[netConnectAcceptPacket.PeerId]; _peersLock.ExitUpgradeableReadLock(); if (netPeer != null && netPeer.ConnectTime == netConnectAcceptPacket.ConnectionTime && netPeer.ConnectionNum == netConnectAcceptPacket.ConnectionNumber) { if (netPeer.ConnectionState == ConnectionState.Connected) { netPeer.InitiateEndPointChange(); CreateEvent(NetEvent.EType.PeerAddressChanged, netPeer, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); } flag2 = true; } } } PoolRecycle(packet); if (!flag2) { NetPacket netPacket = PoolGetWithProperty(PacketProperty.PeerNotFound, 1); netPacket.RawData[1] = 1; SendRawAndRecycle(netPacket, remoteEndPoint); } } break; case PacketProperty.InvalidProtocol: if (flag && actualValue.ConnectionState == ConnectionState.Outgoing) { DisconnectPeerForce(actualValue, DisconnectReason.InvalidProtocol, SocketError.Success, null); } break; case PacketProperty.Disconnect: if (flag) { DisconnectResult disconnectResult = actualValue.ProcessDisconnect(packet); if (disconnectResult == DisconnectResult.None) { PoolRecycle(packet); break; } DisconnectPeerForce(actualValue, (disconnectResult == DisconnectResult.Disconnect) ? DisconnectReason.RemoteConnectionClose : DisconnectReason.ConnectionRejected, SocketError.Success, packet); } else { PoolRecycle(packet); } SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint); break; case PacketProperty.ConnectAccept: if (flag) { NetConnectAcceptPacket netConnectAcceptPacket2 = NetConnectAcceptPacket.FromData(packet); if (netConnectAcceptPacket2 != null && actualValue.ProcessConnectAccept(netConnectAcceptPacket2)) { CreateEvent(NetEvent.EType.Connect, actualValue, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); } } break; default: if (flag) { actualValue.ProcessPacket(packet); } else { SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint); } break; } } internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer) { if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode) { NetEvent netEvent; lock (_eventLock) { netEvent = _netEventPoolHead; if (netEvent == null) { netEvent = new NetEvent(this); } else { _netEventPoolHead = netEvent.Next; } } netEvent.Next = null; netEvent.Type = NetEvent.EType.Receive; netEvent.DataReader.SetSource(packet, headerSize); netEvent.Peer = fromPeer; netEvent.DeliveryMethod = method; netEvent.ChannelNumber = channelNumber; ProcessEvent(netEvent); return; } lock (_eventLock) { NetEvent netEvent = _netEventPoolHead; if (netEvent == null) { netEvent = new NetEvent(this); } else { _netEventPoolHead = netEvent.Next; } netEvent.Next = null; netEvent.Type = NetEvent.EType.Receive; netEvent.DataReader.SetSource(packet, headerSize); netEvent.Peer = fromPeer; netEvent.DeliveryMethod = method; netEvent.ChannelNumber = channelNumber; if (_pendingEventTail == null) { _pendingEventHead = netEvent; } else { _pendingEventTail.Next = netEvent; } _pendingEventTail = netEvent; } } public void SendToAll(NetDataWriter writer, DeliveryMethod options) { SendToAll(writer.Data, 0, writer.Length, options); } public void SendToAll(byte[] data, DeliveryMethod options) { SendToAll(data, 0, data.Length, options); } public void SendToAll(byte[] data, int start, int length, DeliveryMethod options) { SendToAll(data, start, length, 0, options); } public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) { SendToAll(writer.Data, 0, writer.Length, channelNumber, options); } public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) { SendToAll(data, 0, data.Length, channelNumber, options); } public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options) { try { _peersLock.EnterReadLock(); for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { netPeer.Send(data, start, length, channelNumber, options); } } finally { _peersLock.ExitReadLock(); } } public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer) { SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer); } public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, 0, data.Length, 0, options, excludePeer); } public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, start, length, 0, options, excludePeer); } public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer); } public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, 0, data.Length, channelNumber, options, excludePeer); } public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { try { _peersLock.EnterReadLock(); for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer != excludePeer) { netPeer.Send(data, start, length, channelNumber, options); } } } finally { _peersLock.ExitReadLock(); } } public bool Start() { return Start(0); } public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) { return Start(addressIPv4, addressIPv6, port, manualMode: false); } public bool Start(string addressIPv4, string addressIPv6, int port) { IPAddress addressIPv7 = NetUtils.ResolveAddress(addressIPv4); IPAddress addressIPv8 = NetUtils.ResolveAddress(addressIPv6); return Start(addressIPv7, addressIPv8, port); } public bool Start(int port) { return Start(IPAddress.Any, IPAddress.IPv6Any, port); } public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port) { return Start(addressIPv4, addressIPv6, port, manualMode: true); } public bool StartInManualMode(string addressIPv4, string addressIPv6, int port) { IPAddress addressIPv7 = NetUtils.ResolveAddress(addressIPv4); IPAddress addressIPv8 = NetUtils.ResolveAddress(addressIPv6); return StartInManualMode(addressIPv7, addressIPv8, port); } public bool StartInManualMode(int port) { return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port); } public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) { return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); } public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port) { IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port); return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); } public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) { return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); } public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint) { NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length); return SendRawAndRecycle(packet, remoteEndPoint) > 0; } public void TriggerUpdate() { _updateTriggerEvent.Set(); } public void PollEvents(int maxProcessedEvents = 0) { if (_manualMode) { if (_udpSocketv4 != null) { ManualReceive(_udpSocketv4, _bufferEndPointv4, maxProcessedEvents); } if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4) { ManualReceive(_udpSocketv6, _bufferEndPointv6, maxProcessedEvents); } } else { if (UnsyncedEvents) { return; } NetEvent netEvent; lock (_eventLock) { netEvent = _pendingEventHead; _pendingEventHead = null; _pendingEventTail = null; } int num = 0; while (netEvent != null) { NetEvent next = netEvent.Next; ProcessEvent(netEvent); netEvent = next; num++; if (num == maxProcessedEvents) { break; } } } } public NetPeer Connect(string address, int port, string key) { return Connect(address, port, NetDataWriter.FromString(key)); } public NetPeer Connect(string address, int port, NetDataWriter connectionData) { IPEndPoint target; try { target = NetUtils.MakeEndPoint(address, port); } catch { CreateEvent(NetEvent.EType.Disconnect, null, null, SocketError.Success, 0, DisconnectReason.UnknownHost, null, DeliveryMethod.Unreliable, 0); return null; } return Connect(target, connectionData); } public NetPeer Connect(IPEndPoint target, string key) { return Connect(target, NetDataWriter.FromString(key)); } public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) { if (!_isRunning) { throw new InvalidOperationException("Client is not running"); } lock (_requestsDict) { if (_requestsDict.ContainsKey(target)) { return null; } byte connectNum = 0; if (TryGetPeer(target, out var actualValue)) { ConnectionState connectionState = actualValue.ConnectionState; if (connectionState == ConnectionState.Outgoing || connectionState == ConnectionState.Connected) { return actualValue; } connectNum = (byte)((actualValue.ConnectionNum + 1) % 4); RemovePeer(actualValue, enableWriteLock: true); } actualValue = new NetPeer(this, target, GetNextPeerId(), connectNum, connectionData); AddPeer(actualValue); return actualValue; } } public void Stop() { Stop(sendDisconnectMessages: true); } public void Stop(bool sendDisconnectMessages) { if (_isRunning) { for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); } CloseSocket(); _updateTriggerEvent.Set(); if (!_manualMode) { _logicThread.Join(); _logicThread = null; } ClearPeerSet(); _peerIds = new ConcurrentQueue<int>(); _lastPeerId = 0; _connectedPeersCount = 0L; _pendingEventHead = null; _pendingEventTail = null; } } [Conditional("DEBUG")] [Conditional("SIMULATE_NETWORK")] private void ClearPingSimulationList() { lock (_pingSimulationList) { _pingSimulationList.Clear(); } } [Conditional("DEBUG")] [Conditional("SIMULATE_NETWORK")] private void ClearOutboundSimulationList() { lock (_outboundSimulationList) { _outboundSimulationList.Clear(); } } public int GetPeersCount(ConnectionState peerState) { int num = 0; _peersLock.EnterReadLock(); for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if ((netPeer.ConnectionState & peerState) != 0) { num++; } } _peersLock.ExitReadLock(); return num; } public void GetPeersNonAlloc(List<NetPeer> peers, ConnectionState peerState) { peers.Clear(); _peersLock.EnterReadLock(); for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if ((netPeer.ConnectionState & peerState) != 0) { peers.Add(netPeer); } } _peersLock.ExitReadLock(); } public void DisconnectAll() { DisconnectAll(null, 0, 0); } public void DisconnectAll(byte[] data, int start, int count) { _peersLock.EnterReadLock(); for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { DisconnectPeer(netPeer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, force: false, data, start, count, null); } _peersLock.ExitReadLock(); } public void DisconnectPeerForce(NetPeer peer) { DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, null); } public void DisconnectPeer(NetPeer peer) { DisconnectPeer(peer, null, 0, 0); } public void DisconnectPeer(NetPeer peer, byte[] data) { DisconnectPeer(peer, data, 0, data.Length); } public void DisconnectPeer(NetPeer peer, NetDataWriter writer) { DisconnectPeer(peer, writer.Data, 0, writer.Length); } public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count) { DisconnectPeer(peer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, force: false, data, start, count, null); } public void CreateNtpRequest(IPEndPoint endPoint) { _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); } public void CreateNtpRequest(string ntpServerAddress, int port) { IPEndPoint iPEndPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); _ntpRequests.TryAdd(iPEndPoint, new NtpRequest(iPEndPoint)); } public void CreateNtpRequest(string ntpServerAddress) { IPEndPoint iPEndPoint = NetUtils.MakeEndPoint(ntpServerAddress, 123); _ntpRequests.TryAdd(iPEndPoint, new NtpRequest(iPEndPoint)); } public NetPeerEnumerator GetEnumerator() { return new NetPeerEnumerator(_headPeer); } IEnumerator<NetPeer> IEnumerable<NetPeer>.GetEnumerator() { return new NetPeerEnumerator(_headPeer); } IEnumerator IEnumerable.GetEnumerator() { return new NetPeerEnumerator(_headPeer); } private static int HashSetGetPrime(int min) { int[] primes = Primes; foreach (int num in primes) { if (num >= min) { return num; } } for (int j = min | 1; j < int.MaxValue; j += 2) { if (IsPrime(j) && (j - 1) % 101 != 0) { return j; } } return min; static bool IsPrime(int candidate) { if ((candidate & 1) != 0) { int num2 = (int)Math.Sqrt(candidate); for (int k = 3; k <= num2; k += 2) { if (candidate % k == 0) { return false; } } return true; } return candidate == 2; } } private void ClearPeerSet() { _peersLock.EnterWriteLock(); _headPeer = null; if (_lastIndex > 0) { Array.Clear(_slots, 0, _lastIndex); Array.Clear(_buckets, 0, _buckets.Length); _lastIndex = 0; _count = 0; _freeList = -1; } _peersArray = new NetPeer[32]; _peersLock.ExitWriteLock(); } private bool ContainsPeer(NetPeer item) { if (item == null) { NetDebug.WriteError($"Contains peer null: {item}"); return false; } if (_buckets != null) { int num = item.GetHashCode() & 0x7FFFFFFF; for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next) { if (_slots[num2].HashCode == num && _slots[num2].Value.Equals(item)) { return true; } } } return false; } public NetPeer GetPeerById(int id) { if (id < 0 || id >= _peersArray.Length) { return null; } return _peersArray[id]; } public bool TryGetPeerById(int id, out NetPeer peer) { peer = GetPeerById(id); return peer != null; } private void AddPeer(NetPeer peer) { if (peer == null) { NetDebug.WriteError($"Add peer null: {peer}"); return; } _peersLock.EnterWriteLock(); if (_headPeer != null) { peer.NextPeer = _headPeer; _headPeer.PrevPeer = peer; } _headPeer = peer; AddPeerToSet(peer); if (peer.Id >= _peersArray.Length) { int num = _peersArray.Length * 2; while (peer.Id >= num) { num *= 2; } Array.Resize(ref _peersArray, num); } _peersArray[peer.Id] = peer; _peersLock.ExitWriteLock(); } private void RemovePeer(NetPeer peer, bool enableWriteLock) { if (enableWriteLock) { _peersLock.EnterWriteLock(); } if (!RemovePeerFromSet(peer)) { if (enableWriteLock) { _peersLock.ExitWriteLock(); } return; } if (peer == _headPeer) { _headPeer = peer.NextPeer; } if (peer.PrevPeer != null) { peer.PrevPeer.NextPeer = peer.NextPeer; } if (peer.NextPeer != null) { peer.NextPeer.PrevPeer = peer.PrevPeer; } peer.PrevPeer = null; _peersArray[peer.Id] = null; _peerIds.Enqueue(peer.Id); if (enableWriteLock) { _peersLock.ExitWriteLock(); } } private bool RemovePeerFromSet(NetPeer peer) { if (_buckets == null || peer == null) { return false; } int num = peer.GetHashCode() & 0x7FFFFFFF; int num2 = num % _buckets.Length; int num3 = -1; for (int num4 = _buckets[num2] - 1; num4 >= 0; num4 = _slots[num4].Next) { if (_slots[num4].HashCode == num && _slots[num4].Value.Equals(peer)) { if (num3 < 0) { _buckets[num2] = _slots[num4].Next + 1; } else { _slots[num3].Next = _slots[num4].Next; } _slots[num4].HashCode = -1; _slots[num4].Value = null; _slots[num4].Next = _freeList; _count--; if (_count == 0) { _lastIndex = 0; _freeList = -1; } else { _freeList = num4; } return true; } num3 = num4; } return false; } private bool TryGetPeer(IPEndPoint endPoint, out NetPeer actualValue) { if (_buckets != null) { int num = endPoint.GetHashCode() & 0x7FFFFFFF; _peersLock.EnterReadLock(); for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next) { if (_slots[num2].HashCode == num && _slots[num2].Value.Equals(endPoint)) { actualValue = _slots[num2].Value; _peersLock.ExitReadLock(); return true; } } _peersLock.ExitReadLock(); } actualValue = null; return false; } private bool TryGetPeer(SocketAddress saddr, out NetPeer actualValue) { if (_buckets != null) { int num = saddr.GetHashCode() & 0x7FFFFFFF; _peersLock.EnterReadLock(); for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next) { if (_slots[num2].HashCode == num && _slots[num2].Value.Serialize().Equals((object?)saddr)) { actualValue = _slots[num2].Value; _peersLock.ExitReadLock(); return true; } } _peersLock.ExitReadLock(); } actualValue = null; return false; } private bool AddPeerToSet(NetPeer value) { if (_buckets == null) { int num = HashSetGetPrime(0); _buckets = new int[num]; _slots = new Slot[num]; } int num2 = value.GetHashCode() & 0x7FFFFFFF; int num3 = num2 % _buckets.Length; for (int num4 = _buckets[num2 % _buckets.Length] - 1; num4 >= 0; num4 = _slots[num4].Next) { if (_slots[num4].HashCode == num2 && _slots[num4].Value.Equals(value)) { return false; } } int num5; if (_freeList >= 0) { num5 = _freeList; _freeList = _slots[num5].Next; } else { if (_lastIndex == _slots.Length) { int num6 = 2 * _count; num6 = (((uint)num6 > 2147483587u && 2147483587 > _count) ? 2147483587 : HashSetGetPrime(num6)); Slot[] array = new Slot[num6]; Array.Copy(_slots, 0, array, 0, _lastIndex); _buckets = new int[num6]; for (int i = 0; i < _lastIndex; i++) { int num7 = array[i].HashCode % num6; array[i].Next = _buckets[num7] - 1; _buckets[num7] = i + 1; } _slots = array; num3 = num2 % _buckets.Length; } num5 = _lastIndex; _lastIndex++; } _slots[num5].HashCode = num2; _slots[num5].Value = value; _slots[num5].Next = _buckets[num3] - 1; _buckets[num3] = num5 + 1; _count++; return true; } private NetPacket PoolGetWithData(PacketProperty property, byte[] data, int start, int length) { int headerSize = NetPacket.GetHeaderSize(property); NetPacket netPacket = PoolGetPacket(length + headerSize); netPacket.Property = property; Buffer.BlockCopy(data, start, netPacket.RawData, headerSize, length); return netPacket; } private NetPacket PoolGetWithProperty(PacketProperty property, int size) { NetPacket netPacket = PoolGetPacket(size + NetPacket.GetHeaderSize(property)); netPacket.Property = property; return netPacket; } private NetPacket PoolGetWithProperty(PacketProperty property) { NetPacket netPacket = PoolGetPacket(NetPacket.GetHeaderSize(property)); netPacket.Property = property; return netPacket; } internal NetPacket PoolGetPacket(int size) { if (size > NetConstants.MaxPacketSize) { return new NetPacket(size); } NetPacket poolHead; lock (_poolLock) { poolHead = _poolHead; if (poolHead == null) { return new NetPacket(size); } _poolHead = _poolHead.Next; _poolCount--; } poolHead.Size = size; if (poolHead.RawData.Length < size) { poolHead.RawData = new byte[size]; } return poolHead; } internal void PoolRecycle(NetPacket packet) { if (packet.RawData.Length > NetConstants.MaxPacketSize || _poolCount >= PacketPoolSize) { return; } packet.RawData[0] = 0; lock (_poolLock) { packet.Next = _poolHead; _poolHead = packet; _poolCount++; } } static NetManager() { Primes = new int[72] { 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; MulticastAddressV6 = IPAddress.Parse("ff02::1"); IPv6Support = Socket.OSSupportsIPv6; } private bool ProcessError(SocketException ex) { switch (ex.SocketErrorCode) { case SocketError.NotConnected: NotConnected = true; return true; case SocketError.OperationAborted: case SocketError.Interrupted: case SocketError.NotSocket: return true; default: NetDebug.WriteError($"[R]Error code: {(int)ex.SocketErrorCode} - {ex}"); CreateEvent(NetEvent.EType.Error, null, null, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); break; case SocketError.WouldBlock: case SocketError.MessageSize: case SocketError.NetworkReset: case SocketError.ConnectionReset: case SocketError.TimedOut: break; } return false; } private void ManualReceive(Socket socket, EndPoint bufferEndPoint, int maxReceive) { try { int num = 0; while (socket.Available > 0) { ReceiveFrom(socket, ref bufferEndPoint); num++; if (num == maxReceive) { break; } } } catch (SocketException ex) { ProcessError(ex); } catch (ObjectDisposedException) { } catch (Exception ex3) { NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex3); } } private void NativeReceiveLogic() { IntPtr handle = _udpSocketv4.Handle; IntPtr s = _udpSocketv6?.Handle ?? IntPtr.Zero; byte[] address = new byte[16]; byte[] address2 = new byte[28]; IPEndPoint tempEndPoint = new IPEndPoint(IPAddress.Any, 0); List<Socket> list = new List<Socket>(2); Socket udpSocketv = _udpSocketv4; Socket udpSocketv2 = _udpSocketv6; NetPacket packet = PoolGetPacket(NetConstants.MaxPacketSize); while (_isRunning) { try { if (udpSocketv2 == null) { if (!NativeReceiveFrom(handle, address)) { break; } continue; } bool flag = false; if (udpSocketv.Available != 0 || list.Contains(udpSocketv)) { if (!NativeReceiveFrom(handle, address)) { break; } flag = true; } if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2)) { if (!NativeReceiveFrom(s, address2)) { break; } flag = true; } list.Clear(); if (!flag) { list.Add(udpSocketv); list.Add(udpSocketv2); Socket.Select(list, null, null, ReceivePollingTime); } } catch (SocketException ex) { if (ProcessError(ex)) { break; } } catch (ObjectDisposedException) { break; } catch (ThreadAbortException) { break; } catch (Exception ex4) { NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex4); } } bool NativeReceiveFrom(IntPtr socketHandle, byte[] array) { int socketAddressSize = array.Length; packet.Size = NativeSocket.RecvFrom(socketHandle, packet.RawData, NetConstants.MaxPacketSize, array, ref socketAddressSize); if (packet.Size == 0) { return true; } if (packet.Size == -1) { return !ProcessError(new SocketException((int)NativeSocket.GetSocketError())); } short num = (short)((array[1] << 8) | array[0]); tempEndPoint.Port = (ushort)((array[2] << 8) | array[3]); if ((NativeSocket.UnixMode && num == 10) || (!NativeSocket.UnixMode && num == 23)) { uint num2 = (uint)((array[27] << 24) + (array[26] << 16) + (array[25] << 8) + array[24]); byte[] array2 = new byte[16]; Buffer.BlockCopy(array, 8, array2, 0, 16); tempEndPoint.Address = new IPAddress(array2, num2); } else { long newAddress = (uint)((array[4] & 0xFF) | ((array[5] << 8) & 0xFF00) | ((array[6] << 16) & 0xFF0000) | (array[7] << 24)); tempEndPoint.Address = new IPAddress(newAddress); } if (TryGetPeer(tempEndPoint, out var actualValue)) { OnMessageReceived(packet, actualValue); } else { OnMessageReceived(packet, tempEndPoint); tempEndPoint = new IPEndPoint(IPAddress.Any, 0); } packet = PoolGetPacket(NetConstants.MaxPacketSize); return true; } } private void ReceiveFrom(Socket s, ref EndPoint bufferEndPoint) { NetPacket netPacket = PoolGetPacket(NetConstants.MaxPacketSize); netPacket.Size = s.ReceiveFrom(netPacket.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint); OnMessageReceived(netPacket, (IPEndPoint)bufferEndPoint); } private void ReceiveLogic() { EndPoint bufferEndPoint = new IPEndPoint(IPAddress.Any, 0); EndPoint bufferEndPoint2 = new IPEndPoint(IPAddress.IPv6Any, 0); List<Socket> list = new List<Socket>(2); Socket udpSocketv = _udpSocketv4; Socket udpSocketv2 = _udpSocketv6; while (_isRunning) { try { if (udpSocketv2 == null) { if (udpSocketv.Available != 0 || udpSocketv.Poll(ReceivePollingTime, SelectMode.SelectRead)) { ReceiveFrom(udpSocketv, ref bufferEndPoint); } continue; } bool flag = false; if (udpSocketv.Available != 0 || list.Contains(udpSocketv)) { ReceiveFrom(udpSocketv, ref bufferEndPoint); flag = true; } if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2)) { ReceiveFrom(udpSocketv2, ref bufferEndPoint2); flag = true; } list.Clear(); if (!flag) { list.Add(udpSocketv); list.Add(udpSocketv2); Socket.Select(list, null, null, ReceivePollingTime); } } catch (SocketException ex) { if (ProcessError(ex)) { break; } } catch (ObjectDisposedException) { break; } catch (ThreadAbortException) { break; } catch (Exception ex4) { NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex4); } } } public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode) { if (IsRunning && !NotConnected) { return false; } NotConnected = false; _manualMode = manualMode; UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported; _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port))) { return false; } LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port; _isRunning = true; if (_manualMode) { _bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0); } if (IPv6Support && IPv6Enabled) { _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort))) { if (_manualMode) { _bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0); } } else { _udpSocketv6 = null; } } if (!manualMode) { ThreadStart start = ReceiveLogic; if (UseNativeSockets) { start = NativeReceiveLogic; } _receiveThread = new Thread(start) { Name = $"ReceiveThread({LocalPort})", IsBackground = true }; _receiveThread.Start(); if (_logicThread == null) { _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true }; _logicThread.Start(); } } return true; } private bool BindSocket(Socket socket, IPEndPoint ep) { socket.ReceiveTimeout = 500; socket.SendTimeout = 500; socket.ReceiveBufferSize = 1048576; socket.SendBufferSize = 1048576; socket.Blocking = true; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { try { socket.IOControl(-1744830452, new byte[1], null); } catch { } } try { socket.ExclusiveAddressUse = !ReuseAddress; socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute); } catch { } if (ep.AddressFamily == AddressFamily.InterNetwork) { Ttl = 255; try { socket.EnableBroadcast = true; } catch (SocketException ex) { NetDebug.WriteError($"[B]Broadcast error: {ex.SocketErrorCode}"); } if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { try { socket.DontFragment = true; } catch (SocketException ex2) { NetDebug.WriteError($"[B]DontFragment error: {ex2.SocketErrorCode}"); } } } try { socket.Bind(ep); if (ep.AddressFamily == AddressFamily.InterNetworkV6) { try { socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressV6)); } catch (Exception) { } } } catch (SocketException ex4) { switch (ex4.SocketErrorCode) { case SocketError.AddressAlreadyInUse: if (socket.AddressFamily == AddressFamily.InterNetworkV6) { try { socket.DualMode = false; socket.Bind(ep); } catch (SocketException ex5) { NetDebug.WriteError($"[B]Bind exception: {ex5}, errorCode: {ex5.SocketErrorCode}"); return false; } return true; } break; case SocketError.AddressFamilyNotSupported: return true; } NetDebug.WriteError($"[B]Bind exception: {ex4}, errorCode: {ex4.SocketErrorCode}"); return false; } return true; } internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint) { int result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); PoolRecycle(packet); return result; } internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) { return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); } internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint) { if (!_isRunning) { return 0; } NetPacket netPacket = null; if (_extraPacketLayer != null) { netPacket = PoolGetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer); Buffer.BlockCopy(message, start, netPacket.RawData, 0, length); start = 0; _extraPacketLayer.ProcessOutBoundPacket(ref remoteEndPoint, ref netPacket.RawData, ref start, ref length); message = netPacket.RawData; } return SendRawCoreWithCleanup(message, start, length, remoteEndPoint, netPacket); } private int SendRawCoreWithCleanup(byte[] message, int start, int length, IPEndPoint remoteEndPoint, NetPacket expandedPacket) { try { return SendRawCore(message, start, length, remoteEndPoint); } finally { if (expandedPacket != null) { PoolRecycle(expandedPacket); } } } internal unsafe int SendRawCore(byte[] message, int start, int length, IPEndPoint remoteEndPoint) { if (!_isRunning) { return 0; } Socket socket = _udpSocketv4; if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support) { socket = _udpSocketv6; if (socket == null) { return 0; } } int num; try { if (UseNativeSockets && remoteEndPoint is NetPeer netPeer) { fixed (byte* pinnedBuffer = &message[start]) { num = NativeSocket.SendTo(socket.Handle, pinnedBuffer, length, netPeer.NativeAddress, netPeer.NativeAddress.Length); } if (num == -1) { throw NativeSocket.GetSocketException(); } } else { num = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint); } } catch (SocketException ex) { switch (ex.SocketErrorCode) { case SocketError.Interrupted: case SocketError.NoBufferSpaceAvailable: return 0; case SocketError.MessageSize: return 0; case SocketError.NetworkUnreachable: case SocketError.HostUnreachable: if (DisconnectOnUnreachable && remoteEndPoint is NetPeer peer) { DisconnectPeerForce(peer, (ex.SocketErrorCode == SocketError.HostUnreachable) ? DisconnectReason.HostUnreachable : DisconnectReason.NetworkUnreachable, ex.SocketErrorCode, null); } CreateEvent(NetEvent.EType.Error, null, remoteEndPoint, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); return -1; case SocketError.Shutdown: CreateEvent(NetEvent.EType.Error, null, remoteEndPoint, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0); return -1; default: NetDebug.WriteError($"[S] {ex}"); return -1; } } catch (Exception arg) { NetDebug.WriteError($"[S] {arg}"); return 0; } if (num <= 0) { return 0; } if (EnableStatistics) { Statistics.IncrementPacketsSent(); Statistics.AddBytesSent(length); } return num; } public bool SendBroadcast(NetDataWriter writer, int port) { return SendBroadcast(writer.Data, 0, writer.Length, port); } public bool SendBroadcast(byte[] data, int port) { return SendBroadcast(data, 0, data.Length, port); } public bool SendBroadcast(byte[] data, int start, int length, int port) { if (!IsRunning) { return false; } NetPacket netPacket; if (_extraPacketLayer != null) { int headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast); netPacket = PoolGetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer); netPacket.Property = PacketProperty.Broadcast; Buffer.BlockCopy(data, start, netPacket.RawData, headerSize, length); int offset = 0; int length2 = length + headerSize; IPEndPoint endPoint = null; _extraPacketLayer.ProcessOutBoundPacket(ref endPoint, ref netPacket.RawData, ref offset, ref length2); } else { netPacket = PoolGetWithData(PacketProperty.Broadcast, data, start, length); } bool flag = false; bool flag2 = false; try { flag = _udpSocketv4.SendTo(netPacket.RawData, 0, netPacket.Size, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, port)) > 0; if (_udpSocketv6 != null) { flag2 = _udpSocketv6.SendTo(netPacket.RawData, 0, netPacket.Size, SocketFlags.None, new IPEndPoint(MulticastAddressV6, port)) > 0; } } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.HostUnreachable) { return flag; } NetDebug.WriteError($"[S][MCAST] {ex}"); return flag; } catch (Exception arg) { NetDebug.WriteError($"[S][MCAST] {arg}"); return flag; } finally { PoolRecycle(netPacket); } return flag || flag2; } private void CloseSocket() { _isRunning = false; if (_receiveThread != null && _receiveThread != Thread.CurrentThread) { _receiveThread.Join(); } _receiveThread = null; _udpSocketv4?.Close(); _udpSocketv6?.Close(); _udpSocketv4 = null; _udpSocketv6 = null; } } internal enum PacketProperty : byte { Unreliable, Channeled, Ack, Ping, Pong, ConnectRequest, ConnectAccept, Disconnect, UnconnectedMessage, MtuCheck, MtuOk, Broadcast, Merged, ShutdownOk, PeerNotFound, InvalidProtocol, NatMessage, Empty } internal sealed class NetPacket { private static readonly int PropertiesCount; private static readonly int[] HeaderSizes; public byte[] RawData; public int Size; public object UserData; public NetPacket Next; public PacketProperty Property { get { return (PacketProperty)(RawData[0] & 0x1F); } set { RawData[0] = (byte)((uint)(RawData[0] & 0xE0) | (uint)value); } } public byte ConnectionNumber { get { return (byte)((RawData[0] & 0x60) >> 5); } set { RawData[0] = (byte)((RawData[0] & 0x9F) | (value << 5)); } } public ushort Sequence { get { return BitConverter.ToUInt16(RawData, 1); } set { FastBitConverter.GetBytes(RawData, 1, value); } } public bool IsFragmented => (RawData[0] & 0x80) != 0; public byte ChannelId { get { return RawData[3]; } set { RawData[3] = value; } } public ushort FragmentId { get { return BitConverter.ToUInt16(RawData, 4); } set { FastBitConverter.GetBytes(RawData, 4, value); } } public ushort FragmentPart { get { return BitConverter.ToUInt16(RawData, 6); } set { FastBitConverter.GetBytes(RawData, 6, value); } } public ushort FragmentsTotal { get { return BitConverter.ToUInt16(RawData, 8); } set { FastBitConverter.GetBytes(RawData, 8, value); } } static NetPacket() { PropertiesCount = Enum.GetValues(typeof(PacketProperty)).Length; HeaderSizes = NetUtils.AllocatePinnedUninitializedArray<int>(PropertiesCount); for (int i = 0; i < HeaderSizes.Length; i++) { switch ((PacketProperty)(byte)i) { case PacketProperty.Channeled: case PacketProperty.Ack: HeaderSizes[i] = 4; break; case PacketProperty.Ping: HeaderSizes[i] = 3; break; case PacketProperty.ConnectRequest: HeaderSizes[i] = 18; break; case PacketProperty.ConnectAccept: HeaderSizes[i] = 15; break; case PacketProperty.Disconnect: HeaderSizes[i] = 9; break; case PacketProperty.Pong: HeaderSizes[i] = 11; break; default: HeaderSizes[i] = 1; break; } } } public void MarkFragmented() { RawData[0] |= 128; } public NetPacket(int size) { RawData = new byte[size]; Size = size; } public NetPacket(PacketProperty property, int size) { size += GetHeaderSize(property); RawData = new byte[size]; Property = property; Size = size; } public static int GetHeaderSize(PacketProperty property) { return HeaderSizes[(uint)property]; } public int GetHeaderSize() { return HeaderSizes[RawData[0] & 0x1F]; } public bool Verify() { byte b = (byte)(RawData[0] & 0x1F); if (b >= PropertiesCount) { return false; } int num = HeaderSizes[b]; bool flag = (RawData[0] & 0x80) != 0; if (Size >= num) { if (flag) { return Size >= num + 6; } return true; } return false; } } [Flags] public enum ConnectionState : byte { Outgoing = 2, Connected = 4, ShutdownRequested = 8, Disconnected = 0x10, EndPointChange = 0x20, Any = 0x2E } internal enum ConnectRequestResult { None, P2PLose, Reconnection, NewConnection } internal enum DisconnectResult { None, Reject, Disconnect } internal enum ShutdownResult { None, Success, WasConnected } public class NetPeer : IPEndPoint { private class IncomingFragments { public NetPacket[] Fragments; public int ReceivedCount; public int TotalSize; public byte ChannelId; } private int _rtt; private int _avgRtt; private int _rttCount; private double _resendDelay = 27.0; private float _pingSendTimer; private float _rttResetTimer; private readonly Stopwatch _pingTimer = new Stopwatch(); private volatile float _timeSinceLastPacket; private long _remoteDelta; private readonly object _shutdownLock = new object(); internal volatile NetPeer NextPeer; internal NetPeer PrevPeer; private NetPacket[] _unreliableSecondQueue; private NetPacket[] _unreliableChannel; private int _unreliablePendingCount; private readonly object _unreliableChannelLock = new object(); private readonly ConcurrentQueue<BaseChannel> _channelSendQueue; private readonly BaseChannel[] _channels; private int _mtu; private int _mtuIdx; private bool _finishMtu; private float _mtuCheckTimer; private int _mtuCheckAttempts; private const int MtuCheckDelay = 1000; private const int MaxMtuCheckAttempts = 4; private readonly object _mtuMutex = new object(); private int _fragmentId; private readonly Dictionary<ushort, IncomingFragments> _holdedFragments; private readonly Dictionary<ushort, ushort> _deliveredFragments; private readonly NetPacket _mergeData; private int _mergePos; private int _mergeCount; private int _connectAttempts; private float _connectTimer; private long _connectTime; private byte _connectNum; private ConnectionState _connectionState; private NetPacket _shutdownPacket; private const int ShutdownDelay = 300; private float _shutdownTimer; private readonly NetPacket _pingPacket; private readonly NetPacket _pongPacket; private readonly NetPacket _connectRequestPacket; private readonly NetPacket _connectAcceptPacket; public readonly NetManager NetManager; public readonly int Id; public object Tag; public readonly NetStatistics Statistics; private SocketAddress _cachedSocketAddr; private int _cachedHashCode; internal byte[] NativeAddress; internal byte ConnectionNum { get { return _connectNum; } private set { _connectNum = value; _mergeData.ConnectionNumber = value; _pingPacket.ConnectionNumber = value; _pongPacket.ConnectionNumber = value; } } public ConnectionState ConnectionState => _connectionState; internal long ConnectTime => _connectTime; public int RemoteId { get; private set; } public int Ping => _avgRtt / 2; public int RoundTripTime => _avgRtt; public int Mtu => _mtu; public long RemoteTimeDelta => _remoteDelta; public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); public float TimeSinceLastPacket => _timeSinceLastPacket; internal double ResendDelay => _resendDelay; public override SocketAddress Serialize() { return _cachedSocketAddr; } public override int GetHashCode() { return _cachedHashCode; } internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) : base(remoteEndPoint.Address, remoteEndPoint.Port) { Id = id; Statistics = new NetStatistics(); NetManager = netManager; _cachedSocketAddr = base.Serialize(); if (NetManager.UseNativeSockets) { NativeAddress = new byte[_cachedSocketAddr.Size]; for (int i = 0; i < _cachedSocketAddr.Size; i++) { NativeAddress[i] = _cachedSocketAddr[i]; } } _cachedHashCode = base.GetHashCode(); ResetMtu(); _connectionState = ConnectionState.Connected; _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize); _pongPacket = new NetPacket(PacketProperty.Pong, 0); _pingPacket = new NetPacket(PacketProperty.Ping, 0) { Sequence = 1 }; _unreliableSecondQueue = new NetPacket[8]; _unreliableChannel = new NetPacket[8]; _holdedFragments = new Dictionary<ushort, IncomingFragments>(); _deliveredFragments = new Dictionary<ushort, ushort>(); _channels = new BaseChannel[netManager.ChannelsCount * 4]; _channelSendQueue = new ConcurrentQueue<BaseChannel>(); } internal void InitiateEndPointChange() { ResetMtu(); _connectionState = ConnectionState.EndPointChange; } internal void FinishEndPointChange(IPEndPoint newEndPoint) { if (_connectionState != ConnectionState.EndPointChange) { return; } _connectionState = ConnectionState.Connected; base.Address = newEndPoint.Address; base.Port = newEndPoint.Port; _cachedSocketAddr = base.Serialize(); if (NetManager.UseNativeSockets) { NativeAddress = new byte[_cachedSocketAddr.Size]; for (int i = 0; i < _cachedSocketAddr.Size; i++) { NativeAddress[i] = _cachedSocketAddr[i]; } } _cachedHashCode = base.GetHashCode(); } internal void ResetMtu() { _finishMtu = !NetManager.MtuDiscovery; if (NetManager.MtuOverride > 0) { OverrideMtu(NetManager.MtuOverride); } else { SetMtu(0); } } private void SetMtu(int mtuIdx) { _mtuIdx = mtuIdx; _mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer; } private void OverrideMtu(int mtuValue) { _mtu = mtuValue; _finishMtu = true; } public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered) { int num = channelNumber * 4 + (ordered ? 2 : 0); BaseChannel baseChannel = _channels[num]; if (baseChannel == null) { return 0; } return ((ReliableChannel)baseChannel).PacketsInQueue; } public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber) { int mtu = _mtu; NetPacket netPacket = NetManager.PoolGetPac
SULFUR Together.dll
Decompiled 19 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LiteNetLib; using LiteNetLib.Layers; using LiteNetLib.Utils; using Microsoft.CodeAnalysis; using PerfectRandom.Sulfur.Core; using PerfectRandom.Sulfur.Core.CharacterStats; using PerfectRandom.Sulfur.Core.Items; using PerfectRandom.Sulfur.Core.LevelGeneration; using PerfectRandom.Sulfur.Core.Stats; using PerfectRandom.Sulfur.Core.UI; using PerfectRandom.Sulfur.Core.Units; using PerfectRandom.Sulfur.Core.Utilities; using PerfectRandom.Sulfur.Core.Weapons; using PerfectRandom.Sulfur.Core.World; using Ryuka.Sulfur.NativeUI; using SULFURTogether.Config; using SULFURTogether.Logging; using SULFURTogether.Networking; using SULFURTogether.Networking.Gameplay; using SULFURTogether.Networking.Gameplay.Boss; using SULFURTogether.Patches; using SULFURTogether.ReverseProbe; using SULFURTogether.UI; using SULFURTogether.UI.DownedRescueOverlay; using SULFURTogether.UI.RunStatsOverlay; using Steamworks; using TMPro; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SULFURTogether { internal static class ModInfo { public const string GUID = "com.ryuka.sulfur.together"; public const string Name = "SULFUR Together"; public const string Version = "1.0.0"; public const string Author = "ryuka"; } [BepInPlugin("com.ryuka.sulfur.together", "SULFUR Together", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private readonly Stopwatch _updateProf = new Stopwatch(); public static Plugin Instance { get; private set; } public static STLogger Log { get; private set; } public static CoopConfig Cfg { get; private set; } private void Awake() { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_06b7: Unknown result type (might be due to invalid IL or missing references) Instance = this; Cfg = new CoopConfig(((BaseUnityPlugin)this).Config); Log = new STLogger(((BaseUnityPlugin)this).Logger, Cfg); Log.Info("v1.0.0 by ryuka loading..."); Log.Info("[Build] CfgCleanup batch 11 (HostDrivenProxy pt2: proxy/attack-phase/interest-mgmt/target-proxy/coalescing + DisablePauseInMultiplayer): 30 functional/tuning flags hardcoded (Fixed), removed from .cfg. Log*/Debug diagnostics kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 10 (HostDrivenProxy pt1: health-sync + hit-request + terminal/flash/pending-death): 25 functional/tuning flags hardcoded (Fixed), removed from .cfg. 6 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 9 (NetworkEnemyTargetExperimental): 18 enemy projectile-visual + ranged-damage + target/combat-authority functional/tuning flags hardcoded (Fixed), removed from .cfg. 3 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 8 (NetworkEnemyStateExperimental): 28 enemy state/animation/snapshot/delta-compression functional+tuning flags hardcoded (Fixed), removed from .cfg. 4 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 7 (NetworkEnemyIntentExperimental): 8 intent-motion + host-authorized-intent functional/tuning flags hardcoded (Fixed; intent-driven motion stays OFF), removed from .cfg. 2 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 6 (PlayerWeapon): 16 weapon-sync + remote-player body/weapon appearance flags hardcoded (Fixed, appearance finalized), removed from .cfg. 5 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 5 (NetworkPlayerLifeExperimental): 8 downed/revive functional+tuning flags hardcoded (Fixed), removed from .cfg. LogPlayerLifeSync + PlayerReviveHoldKey (keybind) kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 4 (NetworkEnemy + NetworkGameplaySyncExperimental): 11 runtime-spawn + enemy-death-mirror functional/tuning flags hardcoded (Fixed), removed from .cfg. Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 3 (NetworkVisualProxy): 9 remote-visual-proxy functional/tuning flags hardcoded (Fixed), removed from .cfg. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 2 (NetworkRunState+NetworkLevelSeed): EnableRunStateNegotiation/RunStateBroadcastIntervalSeconds/EnableLevelSeedAuthority/RequireSameLevelSeedForSceneMatch/ApplyHostLevelSeedOnManualFollow/HideRemoteVisualWhenLevelSeedMismatch/SyncHostUsedSetsOnManualFollow hardcoded (Fixed), removed from .cfg. Warn*/Log* kept. 2026-06-30"); Log.Info("[Build] UI-CleanRole: networking role is runtime-only (NetworkMode/EnableNetworking dropped from the .cfg); connection settings (name/IP/port/key/maxplayers/version + EnableCoopToasts) moved to a private JSON store (CoopSettingsStore) so they stay out of Gale; retired .cfg keys pruned via OrphanedEntries reflection. + REGRESSION FIX: promoted Plan B targeting flags EnableRemotePlayerInPlayersList + EnableGhostPlayerHitbox into the dev-defaults (were local-cfg-only; a fresh/deleted config left enemies idle = 站桩). 2026-06-30"); PatchBootstrap.ApplyAll(new Harmony("com.ryuka.sulfur.together")); WireCoopUi(); CoopConnection.Initialize(); bool isAvailable = SteamNetworkingSupport.IsAvailable; Log.Info($"[Config] SteamNetworkingAvailable={isAvailable}"); SteamRichPresenceJoin.Initialize(); SteamRelayBridge.Initialize(); Log.Info("[ConfigPolicy] Private development build: active experimental gameplay defaults are forced on load; connection settings such as HostAddress/HostPort/PlayerName are left user-owned. The networking role is runtime-only (not persisted)."); Log.Info($"[Config] EnableDebugLog={Cfg.EnableDebugLog.Value} | EnableReverseProbe={Cfg.EnableReverseProbe.Value} | NetMode={NetConfig.GetMode()}"); Log.Info($"[Config] EnableInventorySerializationProbe={Cfg.EnableInventorySerializationProbe.Value} | EnableAiUpdateTargetProbe={Cfg.EnableAiUpdateTargetProbe.Value} | EnableAiSetDestinationProbe={Cfg.EnableAiSetDestinationProbe.Value}"); Log.Info($"[Config] EnableVerbosePickupProbe={Cfg.EnableVerbosePickupProbe.Value} | EnableVerboseLootProbe={Cfg.EnableVerboseLootProbe.Value} | CompactPickupLogs={Cfg.CompactPickupLogs.Value} | CompactLootLogs={Cfg.CompactLootLogs.Value}"); Log.Info($"[Config] EnableRunStateNegotiation={Cfg.EnableRunStateNegotiation.Value} | RunStateBroadcastIntervalSeconds={Cfg.RunStateBroadcastIntervalSeconds.Value} | WarnOnRunStateMismatch={Cfg.WarnOnRunStateMismatch.Value}"); Log.Info($"[Config] EnableHostSceneAuthority={Cfg.EnableHostSceneAuthority.Value} | WarnOnClientSceneDrift={Cfg.WarnOnClientSceneDrift.Value}"); Log.Info($"[Config] EnableHostSceneRequestProtocol={Cfg.EnableHostSceneRequestProtocol.Value} | AutoSendHostSceneRequestOnDrift={Cfg.AutoSendHostSceneRequestOnDrift.Value} | HostSceneRequestIntervalSeconds={Cfg.HostSceneRequestIntervalSeconds.Value}"); Log.Info($"[Config] EnableManualClientSceneFollow={Cfg.EnableManualClientSceneFollow.Value} | ManualClientSceneFollowKey={Cfg.ManualClientSceneFollowKey.Value} | ManualClientSceneFollowRequiresHostRequest={Cfg.ManualClientSceneFollowRequiresHostRequest.Value}"); Log.Info($"[Config] EnableLevelSeedAuthority={Cfg.EnableLevelSeedAuthority.Value} | RequireSameLevelSeedForSceneMatch={Cfg.RequireSameLevelSeedForSceneMatch.Value} | ApplyHostLevelSeedOnManualFollow={Cfg.ApplyHostLevelSeedOnManualFollow.Value} | HideRemoteVisualWhenLevelSeedMismatch={Cfg.HideRemoteVisualWhenLevelSeedMismatch.Value}"); Log.Info($"[Config] EnableRemotePlayerVisualProxy={Cfg.EnableRemotePlayerVisualProxy.Value} | RemotePlayerTransformSendRateHz={Cfg.RemotePlayerTransformSendRateHz.Value} | RemotePlayerVisualTimeoutSeconds={Cfg.RemotePlayerVisualTimeoutSeconds.Value} | RemotePlayerVisualInterpolationSpeed={Cfg.RemotePlayerVisualInterpolationSpeed.Value} | RemotePlayerVisualSnapDistance={Cfg.RemotePlayerVisualSnapDistance.Value}"); Log.Info($"[Config] EnableGameplayEntityProbe={Cfg.EnableGameplayEntityProbe.Value} | GameplayEntityProbeSummaryIntervalSeconds={Cfg.GameplayEntityProbeSummaryIntervalSeconds.Value} | LogGameplayEntitySpawn={Cfg.LogGameplayEntitySpawn.Value} | LogGameplayEntityDamage={Cfg.LogGameplayEntityDamage.Value} | LogGameplayEntityDeath={Cfg.LogGameplayEntityDeath.Value} | RequireStableSceneAndSeedForGameplayProbe={Cfg.RequireStableSceneAndSeedForGameplayProbe.Value}"); Log.Info($"[Config] EnableHostEnemyDeathEventMirror={Cfg.EnableHostEnemyDeathEventMirror.Value} | LogReceivedEnemyDeathEvents={Cfg.LogReceivedEnemyDeathEvents.Value} | ApplyReceivedEnemyDeathEvents={Cfg.ApplyReceivedEnemyDeathEvents.Value} | EnemyDeathMirrorPositionTolerance={Cfg.EnemyDeathMirrorPositionTolerance.Value} | EnemyDeathMirrorUseHorizontalPositionTolerance={Cfg.EnemyDeathMirrorUseHorizontalPositionTolerance.Value}"); Log.Info($"[Config] EnableClientEnemyDeathClaim={Cfg.EnableClientEnemyDeathClaim.Value} | LogReceivedClientEnemyDeathClaims={Cfg.LogReceivedClientEnemyDeathClaims.Value} | ApplyReceivedClientEnemyDeathClaimsOnHost={Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value}"); Log.Info($"[Config] EnableCoopPlayerDownedRevive={Cfg.EnableCoopPlayerDownedRevive.Value} | PlayerDownedRescueTimeoutSeconds={Cfg.PlayerDownedRescueTimeoutSeconds.Value} | PlayerReviveHoldSeconds={Cfg.PlayerReviveHoldSeconds.Value} | PlayerReviveDistance={Cfg.PlayerReviveDistance.Value} | PlayerReviveHoldKey={Cfg.PlayerReviveHoldKey.Value} | PlayerReviveHealthRatio={Cfg.PlayerReviveHealthRatio.Value} | RequireReviveDistanceValidationOnHost={Cfg.RequireReviveDistanceValidationOnHost.Value}"); Log.Info($"[Config] EnableHostEnemyStateSnapshotMirror={Cfg.EnableHostEnemyStateSnapshotMirror.Value} | EnemyStateSnapshotSendRateHz={Cfg.EnemyStateSnapshotSendRateHz.Value} | EnemyStateSnapshotMaxEnemiesPerPacket={Cfg.EnemyStateSnapshotMaxEnemiesPerPacket.Value} | OnlySendAliveEnemyStateSnapshots={Cfg.OnlySendAliveEnemyStateSnapshots.Value} | LogReceivedEnemyStateSnapshots={Cfg.LogReceivedEnemyStateSnapshots.Value} | ApplyReceivedEnemyStateSnapshots={Cfg.ApplyReceivedEnemyStateSnapshots.Value} | EnemyStateSnapshotPositionTolerance={Cfg.EnemyStateSnapshotPositionTolerance.Value}"); Log.Info($"[Config] EnemyStateSnapshotInterpolationSpeed={Cfg.EnemyStateSnapshotInterpolationSpeed.Value} | EnemyStateSnapshotPlaybackDurationMultiplier={Cfg.EnemyStateSnapshotPlaybackDurationMultiplier.Value} | EnemyStateSnapshotSnapDistance={Cfg.EnemyStateSnapshotSnapDistance.Value} | EnemyStateSnapshotApplyRotationY={Cfg.EnemyStateSnapshotApplyRotationY.Value} | EnableClientEnemyAiSuppressionExperiment={Cfg.EnableClientEnemyAiSuppressionExperiment.Value} | SuppressClientEnemyAiWhenStateMirrorEnabled={Cfg.SuppressClientEnemyAiWhenStateMirrorEnabled.Value} | EnableClientEnemyPuppetMode={Cfg.EnableClientEnemyPuppetMode.Value} | ClientEnemyPuppetStaleReleaseSeconds={Cfg.ClientEnemyPuppetStaleReleaseSeconds.Value} | LogClientEnemyPuppetMode={Cfg.LogClientEnemyPuppetMode.Value}"); Log.Info($"[Config] EnableHostEnemyAnimationMirror={Cfg.EnableHostEnemyAnimationMirror.Value} | ApplyReceivedEnemyAnimationMirror={Cfg.ApplyReceivedEnemyAnimationMirror.Value} | LogEnemyAnimationMirror={Cfg.LogEnemyAnimationMirror.Value} | EnemyAnimationMirrorCrossFadeSeconds={Cfg.EnemyAnimationMirrorCrossFadeSeconds.Value} | EnemyAnimationMirrorNormalizedTimeTolerance={Cfg.EnemyAnimationMirrorNormalizedTimeTolerance.Value} | EnemyAnimationMirrorApplyAnimatorStatePlayback={Cfg.EnemyAnimationMirrorApplyAnimatorStatePlayback.Value} | EnemyAnimationMirrorApplyHostCombatStatePlayback={Cfg.EnemyAnimationMirrorApplyHostCombatStatePlayback.Value} | EnemyAnimationMirrorReplayHostCombatMethods={Cfg.EnemyAnimationMirrorReplayHostCombatMethods.Value} | EnemyAnimationMirrorApplyCombatAnimatorFallback={Cfg.EnemyAnimationMirrorApplyCombatAnimatorFallback.Value} | EnemyAnimationMirrorHostCombatActionHoldSeconds={Cfg.EnemyAnimationMirrorHostCombatActionHoldSeconds.Value}"); Log.Info($"[Config] EnableHostOnlyEnemyTargetAuthority={Cfg.EnableHostOnlyEnemyTargetAuthority.Value} | EnemyProjectileVisualMirrorEnabled={Cfg.EnemyProjectileVisualMirrorEnabled.Value} | EnemyProjectileVisualMirrorUseNativeShootReplay={Cfg.EnemyProjectileVisualMirrorUseNativeShootReplay.Value} | EnableGenericHostCombatAnimatorStateMirror={Cfg.EnableGenericHostCombatAnimatorStateMirror.Value} | EnableHostAuthoritativeEnemyRangedDamage={Cfg.EnableHostAuthoritativeEnemyRangedDamage.Value} | EnableClientEnemyIntentDrivenMotion={Cfg.EnableClientEnemyIntentDrivenMotion.Value} | EnemyIntentCorrectionDistance={Cfg.EnemyIntentCorrectionDistance.Value} | EnemyIntentHardSnapDistance={Cfg.EnemyIntentHardSnapDistance.Value} | LogEnemyTargetAuthority={Cfg.LogEnemyTargetAuthority.Value} | EnemyTargetAuthorityProbeIntervalSeconds={Cfg.EnemyTargetAuthorityProbeIntervalSeconds.Value} | EnableEnemyCombatProbe={Cfg.EnableEnemyCombatProbe.Value} | LogEnemyCombatProbe={Cfg.LogEnemyCombatProbe.Value} | EnemyHostProjectileHitRadius={Cfg.EnemyHostProjectileHitRadius.Value} | EnemyHostProjectileDamage={Cfg.EnemyHostProjectileDamage.Value}"); Log.Info($"[Config] EnableEnemyStateSnapshotDeltaCompression={Cfg.EnableEnemyStateSnapshotDeltaCompression.Value} | EnemyStateSnapshotHeartbeatSeconds={Cfg.EnemyStateSnapshotHeartbeatSeconds.Value} | EnemyStateSnapshotPositionDeltaThreshold={Cfg.EnemyStateSnapshotPositionDeltaThreshold.Value} | EnemyStateSnapshotRotationDeltaThresholdDegrees={Cfg.EnemyStateSnapshotRotationDeltaThresholdDegrees.Value} | EnemyStateSnapshotAnimationTimeDeltaThreshold={Cfg.EnemyStateSnapshotAnimationTimeDeltaThreshold.Value}"); Log.Info("Ready."); } private void WireCoopUi() { try { CoopLoc.Wire(((BaseUnityPlugin)this).Info.Location); Type type = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurPopupApi"); if (type == null) { Log.Info("[ArenaLockdown] SULFUR Native UI Lib not present — confirm prompt will be logged only (UI optional)."); return; } MethodInfo show = AccessTools.Method(type, "ShowBanner", new Type[1] { typeof(string) }, (Type[])null); MethodInfo hide = AccessTools.Method(type, "HideBanner", Type.EmptyTypes, (Type[])null); if (show == null || hide == null) { Log.Warn("[ArenaLockdown] SulfurPopupApi found but ShowBanner/HideBanner missing — prompt logged only."); return; } ArenaLockdownManager.ShowPrompt = delegate(string text) { show.Invoke(null, new object[1] { text }); }; ArenaLockdownManager.HidePrompt = delegate { hide.Invoke(null, null); }; Log.Info("[ArenaLockdown] confirm prompt wired to SULFUR Native UI Lib banner (SulfurPopupApi)."); Type type2 = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurToastApi"); MethodInfo showToast = ((type2 == null) ? null : AccessTools.Method(type2, "Show", new Type[2] { typeof(string), typeof(string) }, (Type[])null)); if (showToast != null) { CoopToasts.Wire(ArenaLockdownManager.ShowToast = delegate(string title, string msg) { showToast.Invoke(null, new object[2] { title, msg }); }); Log.Info("[CoopUi] toasts wired to SULFUR Native UI Lib (SulfurToastApi)."); } else { Log.Info("[CoopUi] SulfurToastApi not present — toasts logged only."); } if (AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurOptionsApi") != null) { CoopConnectPage.Register(); Log.Info("[CoopUi] connect page registered (SulfurOptionsApi)."); } else { Log.Info("[CoopUi] SulfurOptionsApi not present — connect page unavailable."); } } catch (Exception ex) { Log.Warn("[CoopUi] failed to wire Native UI Lib surfaces: " + ex.Message); } } private void Update() { _updateProf.Restart(); ReverseProbeSummary.Tick(); PlayerVisualDiscoveryProbe.TryDumpOnce(); PlayerSpriteAssetScanProbe.TryScanOnce(); long elapsedMilliseconds = _updateProf.ElapsedMilliseconds; NetGameplayProbeManager.Tick(); long num = _updateProf.ElapsedMilliseconds - elapsedMilliseconds; NetPlayerLifeManager.Tick(); NetBossEncounterManager.Tick(); long bossMs = _updateProf.ElapsedMilliseconds - elapsedMilliseconds - num; EmperorWormDiagnostics.FrameWatchdogTick(); BossDynamicSpawnManifest.TickReleaseStaleGated(); ArenaLockdownManager.Tick(); RunStatsOverlayManager.Tick(); DownedRescueOverlayManager.Tick(); CoopConnection.Tick(); PauseControlPatches.Tick(); CoopConnectPage.Tick(); EmperorWormDiagnostics.ReportUpdateProfile(_updateProf.ElapsedMilliseconds, num, bossMs); NetGameplayProbeManager.ClientFrameHitchTick(_updateProf.ElapsedMilliseconds, num); } private void FixedUpdate() { CoopConnection.FixedTick(); } private void OnDestroy() { try { CoopConnectPage.Unregister(); } catch { } try { RunStatsOverlayManager.Shutdown(); } catch { } try { DownedRescueOverlayManager.Shutdown(); } catch { } CoopConnection.Stop("plugin destroyed"); } } } namespace SULFURTogether.UI { internal static class CoopConnectPage { private const string PageId = "ryuka.sulfur.together"; private const string RepoUrl = "https://github.com/ryuka-dev/SULFUR-Together"; private const string KoFiUrl = "https://ko-fi.com/ryukadev"; private static readonly Color ErrorColor = new Color(1f, 0.45f, 0.45f, 1f); private static readonly Color OkColor = new Color(0.55f, 1f, 0.65f, 1f); private static readonly Color NeutralColor = new Color(0.8f, 0.82f, 0.88f, 1f); private static bool _registered; private static SulfurOptionsContext _ctx; private static SulfurTextHandle _statusHandle; private static SulfurTextHandle _joinFeedbackHandle; private static SulfurTextHandle _gateHintHandle; private static SulfurTextHandle _lanIpHandle; private static SulfurButtonHandle _createHandle; private static SulfurButtonHandle _joinHandle; private static SulfurButtonHandle _closeHandle; private static SulfurListHandle _playerListHandle; private static string _lastPlayerSig = "\0"; private static SulfurTextHandle _steamUnavailableHandle; private static SulfurTextHandle _yourSteamIdHandle; private static SulfurButtonHandle _steamInviteHandle; private static SulfurButtonHandle _steamJoinHandle; private static SulfurTextHandle _steamPendingInviteHandle; private static TMP_InputField _steamIdInputField; private static string _autoFilledSteamId; private static SulfurTextHandle _ffSessionHandle; private static OptionsScreenOption _ffToggleOption; private static bool _ffLockApplied; private static readonly FieldInfo FfCheckboxField = typeof(OptionsScreenOption).GetField("checkboxToggle", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo FfIsLockedField = typeof(OptionsScreenOption).GetField("IsLocked", BindingFlags.Instance | BindingFlags.NonPublic); private static string _draftName; private static string _draftAddress; private static string _draftPort; private static string _draftKey; private static string _draftSteamId; private static string _lastSavedSig; private static float _nextTick; private static bool _closeMenuOnJoinSuccess; public static void Register() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if (!_registered) { SulfurOptionsApi.RegisterPage(new SulfurOptionsPage { PageId = "ryuka.sulfur.together", DisplayName = "SULFUR Together", SortOrder = 1000, BuildPage = BuildPage }); _registered = true; } } public static void Unregister() { if (_registered) { SulfurOptionsApi.UnregisterPage("ryuka.sulfur.together"); _registered = false; ResetHandles(); } } public static void Tick() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!_registered || _ctx == null || _statusHandle == null) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextTick) { return; } _nextTick = unscaledTime + 0.4f; try { AutoSaveDrafts(); string text = StatusLine(); _statusHandle.SetText(text); _statusHandle.SetColor(StatusColor()); ApplyButtonStates(); ApplyJoinFeedback(); ApplyHostLanIp(); ApplySteamState(); ApplySessionFriendlyFireControl(); RefreshPlayerList(); PollJoinClose(); } catch { } } private static void BuildPage(SulfurOptionsContext ctx) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Unknown result type (might be due to invalid IL or missing references) ResetHandles(); _ctx = ctx; LoadDraftsFromConfig(); ctx.AddSection("SULFUR Together"); ctx.AddDescription(CoopLoc.Get("connect.desc.intro", "Early-preview co-op. Set up your connection below.")); _statusHandle = ctx.AddTextRow(StatusLine()); _statusHandle.SetColor(StatusColor()); ctx.AddSection(CoopLoc.Get("connect.section.player", "Player")); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.playerName", "Player name"), _draftName, (Action<string>)delegate(string v) { _draftName = v; }, 160f, false); ctx.AddSection(CoopLoc.Get("connect.section.connection", "Connection")); ctx.AddDescription(CoopLoc.Get("connect.desc.connection", "Host a co-op session or join one. Your settings save automatically as you edit them. Close room (host) / Leave (client) ends the session for you.")); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.hostAddress", "Host address (IP)"), _draftAddress, (Action<string>)delegate(string v) { _draftAddress = v; }, 160f, false); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.port", "Port"), _draftPort, (Action<string>)delegate(string v) { _draftPort = v; }, 160f, false); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.connectionKey", "Connection key"), _draftKey, (Action<string>)delegate(string v) { _draftKey = v; }, 160f, false); IReadOnlyList<SulfurButtonHandle> handles = ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[3] { new SulfurButton(CoopLoc.Get("connect.button.create", "Create game"), (Action)OnCreate, 170f), new SulfurButton(CoopLoc.Get("connect.button.join", "Join game"), (Action)OnJoin, 170f), new SulfurButton(CoopLoc.Get("connect.button.closeRoom", "Close room"), (Action)OnCloseRoom, 150f) }); _createHandle = Handle(handles, 0); _joinHandle = Handle(handles, 1); _closeHandle = Handle(handles, 2); _gateHintHandle = ctx.AddTextRow(""); _gateHintHandle.SetVisible(false); _lanIpHandle = ctx.AddTextRow(""); _lanIpHandle.SetVisible(false); _joinFeedbackHandle = ctx.AddTextRow(""); _joinFeedbackHandle.SetVisible(false); ctx.AddSection("Steam"); ctx.AddDescription(CoopLoc.Get("connect.desc.steam", "Connect over Steam instead of a typed IP — no port forwarding needed. Works alongside Direct IP: a host can accept both at once. Create a game first — you can only invite friends while hosting.")); _steamUnavailableHandle = ctx.AddTextRow(CoopLoc.Get("connect.steam.unavailable", "Steam is not available — connect method disabled.")); _steamUnavailableHandle.SetColor(NeutralColor); _steamUnavailableHandle.SetVisible(false); _yourSteamIdHandle = ctx.AddTextRow(""); _yourSteamIdHandle.SetVisible(false); _steamInviteHandle = Handle(ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[1] { new SulfurButton(CoopLoc.Get("connect.button.inviteFriends", "Invite Friends via Steam"), (Action)OnInviteFriends, 220f) }), 0); _steamIdInputField = ctx.AddInlineTextInput(CoopLoc.Get("connect.label.steamIdToJoin", "Steam ID to join"), _draftSteamId, (Action<string>)delegate(string v) { _draftSteamId = v; }, 160f, false); _steamJoinHandle = Handle(ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[1] { new SulfurButton(CoopLoc.Get("connect.button.joinViaSteam", "Join via Steam"), (Action)OnJoinViaSteam, 170f) }), 0); _steamPendingInviteHandle = ctx.AddTextRow(""); _steamPendingInviteHandle.SetVisible(false); ctx.AddSection(CoopLoc.Get("connect.section.players", "Players in session")); _playerListHandle = ctx.AddList(); _lastPlayerSig = "\0"; ctx.AddSection(CoopLoc.Get("connect.section.localPrefs", "Local preferences (only affect you)")); ctx.AddToggle(CoopLoc.Get("connect.label.showToasts", "Show player join/leave notifications"), CoopLoc.Get("connect.desc.showToasts", "Brief top-right toasts when a player joins or leaves."), ReadBool(() => Plugin.Cfg.EnableCoopToasts.Value, fallback: true), (Action<bool>)delegate(bool v) { try { Plugin.Cfg.EnableCoopToasts.Value = v; } catch { } }); ctx.AddReadonlyText(CoopLoc.Get("connect.label.showHudStatus", "Show network status on HUD"), CoopLoc.Get("connect.value.comingSoon", "Coming soon")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.showNames", "Show other players' names"), CoopLoc.Get("connect.value.comingSoon", "Coming soon")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.rescueKey", "Rescue key"), KeyText(() => ((object)Plugin.Cfg.PlayerReviveHoldKey.Value/*cast due to .constrained prefix*/).ToString())); ctx.AddReadonlyText(CoopLoc.Get("connect.label.confirmEnterKey", "Confirm-enter-boss-room key"), KeyText(() => ((object)Plugin.Cfg.ArenaEnterConfirmKey.Value/*cast due to .constrained prefix*/).ToString())); ctx.AddSection(CoopLoc.Get("connect.section.sessionSettings", "Session settings (host)")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.lootMode", "Loot mode"), CoopLoc.Get("connect.value.lootIndependent", "Independent (Shared coming soon)")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.clientMayStart", "Client may start next level"), CoopLoc.Get("connect.value.comingSoon", "Coming soon")); _ffToggleOption = ctx.AddToggle(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), CoopLoc.Get("connect.desc.friendlyFire", "Players can damage each other. The host's setting applies to the whole session."), ReadBool(() => Plugin.Cfg.FriendlyFire.Value, fallback: false), (Action<bool>)OnFriendlyFireToggled); _ffSessionHandle = ctx.AddTextRow(""); _ffSessionHandle.SetVisible(false); ctx.AddSection(CoopLoc.Get("connect.section.about", "About")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.version", "Version"), "1.0.0"); ctx.AddSmallButton(CoopLoc.Get("connect.button.repo", "Open-source repo"), (Action)delegate { OpenUrl("https://github.com/ryuka-dev/SULFUR-Together"); }, 260f); ctx.AddSmallButton(CoopLoc.Get("connect.button.kofi", "Support on Ko-fi"), (Action)delegate { OpenUrl("https://ko-fi.com/ryukadev"); }, 260f); _lastSavedSig = DraftSig(); ApplyButtonStates(); ApplyJoinFeedback(); ApplyHostLanIp(); ApplySteamState(); ApplySessionFriendlyFireControl(); RefreshPlayerList(); } private static string StatusLine() { NetService service = CoopConnection.Service; if (service == null) { return "● " + CoopLoc.Get("connect.status.notConnected", "Not connected"); } if (NetConnectFeedback.Connecting) { return "◌ " + CoopLoc.Get("connect.status.connecting", "Connecting…"); } return "● " + service.GetConnectionSummary(); } private static Color StatusColor() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (CoopConnection.Service == null) { return NeutralColor; } if (!NetConnectFeedback.Connecting) { return OkColor; } return NeutralColor; } private static void ApplyButtonStates() { //IL_00be: Unknown result type (might be due to invalid IL or missing references) NetMode currentMode = CoopConnection.CurrentMode; bool flag = IsInGame(); SulfurButtonHandle createHandle = _createHandle; if (createHandle != null) { createHandle.SetInteractable(flag && currentMode != NetMode.Client); } SulfurButtonHandle joinHandle = _joinHandle; if (joinHandle != null) { joinHandle.SetInteractable(flag && currentMode == NetMode.Off); } SulfurButtonHandle closeHandle = _closeHandle; if (closeHandle != null) { closeHandle.SetInteractable(currentMode != NetMode.Off); } SulfurButtonHandle closeHandle2 = _closeHandle; if (closeHandle2 != null) { closeHandle2.SetLabel((currentMode == NetMode.Client) ? CoopLoc.Get("connect.button.leave", "Leave") : CoopLoc.Get("connect.button.closeRoom", "Close room")); } if (_gateHintHandle != null) { bool flag2 = !flag && currentMode == NetMode.Off; if (flag2) { _gateHintHandle.SetText(CoopLoc.Get("connect.gateHint", "Load a save first — co-op is hosted / joined from inside the game.")); _gateHintHandle.SetColor(NeutralColor); } _gateHintHandle.SetVisible(flag2); } } private static void ApplyJoinFeedback() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (_joinFeedbackHandle != null) { string lastError = NetConnectFeedback.LastError; if (string.IsNullOrEmpty(lastError)) { _joinFeedbackHandle.SetVisible(false); return; } _joinFeedbackHandle.SetText("⚠ " + lastError); _joinFeedbackHandle.SetColor(ErrorColor); _joinFeedbackHandle.SetVisible(true); } } private static void ApplyHostLanIp() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (_lanIpHandle == null) { return; } if (CoopConnection.CurrentMode == NetMode.Host && NetLocalAddress.TryGetLanIPv4(out string ip)) { int num = ReadInt(() => Plugin.Cfg.HostPort.Value, 9050); _lanIpHandle.SetText(CoopLoc.Format("connect.lanAddress", "Your LAN address: {ip}:{port} (others on your network join with this)", ("ip", ip.ToString()), ("port", num.ToString()))); _lanIpHandle.SetColor(OkColor); _lanIpHandle.SetVisible(true); } else { _lanIpHandle.SetVisible(false); } } private static void ApplySteamState() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) bool flag = SteamNetworkingSupportAvailable(); if (_steamUnavailableHandle != null) { _steamUnavailableHandle.SetVisible(!flag); } if (_yourSteamIdHandle != null) { if (flag && TryGetLocalSteamId(out var steamId)) { _yourSteamIdHandle.SetText(CoopLoc.Format("connect.steam.yourId", "Your Steam ID: {id} (share this, or use Invite Friends while hosting)", ("id", steamId.ToString()))); _yourSteamIdHandle.SetColor(NeutralColor); _yourSteamIdHandle.SetVisible(true); } else { _yourSteamIdHandle.SetVisible(false); } } NetMode currentMode = CoopConnection.CurrentMode; bool flag2 = IsInGame(); SulfurButtonHandle steamInviteHandle = _steamInviteHandle; if (steamInviteHandle != null) { steamInviteHandle.SetInteractable(flag && flag2 && currentMode == NetMode.Host); } SulfurButtonHandle steamInviteHandle2 = _steamInviteHandle; if (steamInviteHandle2 != null) { steamInviteHandle2.SetLabel(CoopConnection.SteamHostingEnabled ? CoopLoc.Get("connect.button.inviteMoreFriends", "Invite more friends via Steam") : CoopLoc.Get("connect.button.inviteFriends", "Invite Friends via Steam")); } SulfurButtonHandle steamJoinHandle = _steamJoinHandle; if (steamJoinHandle != null) { steamJoinHandle.SetInteractable(flag && flag2 && currentMode == NetMode.Off); } if (_steamPendingInviteHandle == null) { return; } CSteamID? pendingInviteHostId = SteamRichPresenceJoin.PendingInviteHostId; if (pendingInviteHostId.HasValue && currentMode == NetMode.Off) { string text = pendingInviteHostId.Value.m_SteamID.ToString(); string pendingInviteFriendName = SteamRichPresenceJoin.PendingInviteFriendName; if (flag2 && _autoFilledSteamId != text) { _autoFilledSteamId = text; _draftSteamId = text; if ((Object)(object)_steamIdInputField != (Object)null) { _steamIdInputField.text = text; } _steamPendingInviteHandle.SetText(string.IsNullOrEmpty(pendingInviteFriendName) ? CoopLoc.Get("connect.steam.joiningFriend", "Joining a Steam friend's game…") : CoopLoc.Format("connect.steam.joiningNamed", "Joining {name}'s SULFUR Together game…", ("name", pendingInviteFriendName))); _steamPendingInviteHandle.SetColor(OkColor); _steamPendingInviteHandle.SetVisible(true); JoinViaSteam(pendingInviteHostId.Value, "steam-invite-auto-join"); } else if (!flag2) { _steamPendingInviteHandle.SetText(string.IsNullOrEmpty(pendingInviteFriendName) ? CoopLoc.Get("connect.steam.invitedLoadSave", "A Steam friend invited you — load a save to join automatically.") : CoopLoc.Format("connect.steam.invitedLoadSaveNamed", "{name} invited you — load a save to join automatically.", ("name", pendingInviteFriendName))); _steamPendingInviteHandle.SetColor(OkColor); _steamPendingInviteHandle.SetVisible(true); } } else { _steamPendingInviteHandle.SetVisible(false); } } private static bool SteamNetworkingSupportAvailable() { try { return SteamNetworkingSupport.IsAvailable; } catch { return false; } } private static bool TryGetLocalSteamId(out ulong steamId64) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) steamId64 = 0uL; try { if (!SteamNetworkingSupport.TryGetLocalSteamId(out var id)) { return false; } steamId64 = id.m_SteamID; return true; } catch { return false; } } private static void RefreshPlayerList() { if (_playerListHandle == null) { return; } NetService service = CoopConnection.Service; IReadOnlyList<string> readOnlyList2; if (service == null) { IReadOnlyList<string> readOnlyList = Array.Empty<string>(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = service.GetPlayerRows(); } IReadOnlyList<string> rows = readOnlyList2; string text = string.Join("|", rows); if (text == _lastPlayerSig) { return; } _lastPlayerSig = text; _playerListHandle.Update((Action<SulfurOptionsContext>)delegate(SulfurOptionsContext c) { if (rows.Count == 0) { c.AddTextRow(CoopLoc.Get("connect.players.none", "No players connected.")); return; } foreach (string item in rows) { c.AddTextRow(item); } }); } private static void OnCreate() { if (IsInGame()) { SaveSettings(); CoopConnection.Apply(NetMode.Host, "ui-create"); ApplyButtonStates(); } } private static void OnJoin() { if (IsInGame()) { _closeMenuOnJoinSuccess = true; SaveSettings(); CoopConnection.Apply(NetMode.Client, "ui-join"); NetLinkState.SetClientLinked(on: true, "ui-join"); ApplyButtonStates(); ApplyJoinFeedback(); } } private static void PollJoinClose() { if (_closeMenuOnJoinSuccess && !NetConnectFeedback.Connecting) { bool num = CoopConnection.CurrentMode == NetMode.Client && string.IsNullOrEmpty(NetConnectFeedback.LastError); _closeMenuOnJoinSuccess = false; if (num) { CoopMenu.CloseIfOpen("ui-join-success"); } } } private static void OnInviteFriends() { if (IsInGame() && CoopConnection.CurrentMode == NetMode.Host) { CoopConnection.EnableSteamHosting("ui-steam-invite"); ApplySteamState(); } } private static void OnJoinViaSteam() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (IsInGame()) { if (!ulong.TryParse((_draftSteamId ?? "").Trim(), out var result) || result == 0L) { NetConnectFeedback.ReportError(CoopLoc.Get("connect.error.invalidSteamId", "Enter a valid Steam ID (numbers only) — or use Invite Friends / accept a Steam invite.")); } else { JoinViaSteam(new CSteamID(result), "ui-join-steam"); } } } private static void JoinViaSteam(CSteamID hostId, string reason) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) Plugin.Cfg.LastSteamIdToJoin.Value = hostId.m_SteamID.ToString(); SteamRichPresenceJoin.ConsumePendingInvite(); _closeMenuOnJoinSuccess = true; CoopConnection.ApplySteamClient(hostId, reason); NetLinkState.SetClientLinked(on: true, reason); ApplyButtonStates(); ApplyJoinFeedback(); } private static void OnCloseRoom() { _closeMenuOnJoinSuccess = false; CoopConnection.Stop("ui-close-room"); ApplyButtonStates(); } private static void LoadDraftsFromConfig() { _draftName = Plugin.Cfg.PlayerName.Value; _draftAddress = Plugin.Cfg.HostAddress.Value; _draftPort = Plugin.Cfg.HostPort.Value.ToString(); _draftKey = Plugin.Cfg.ConnectionKey.Value; _draftSteamId = Plugin.Cfg.LastSteamIdToJoin.Value; if ((string.IsNullOrWhiteSpace(_draftName) || _draftName == "Player") && SteamIdentity.TryGetPersonaName(out string name)) { _draftName = name; } } private static void SaveSettings() { Plugin.Cfg.PlayerName.Value = (string.IsNullOrWhiteSpace(_draftName) ? "Player" : _draftName.Trim()); Plugin.Cfg.HostAddress.Value = (string.IsNullOrWhiteSpace(_draftAddress) ? "127.0.0.1" : _draftAddress.Trim()); if (int.TryParse(_draftPort, out var result) && result > 0 && result < 65536) { Plugin.Cfg.HostPort.Value = result; } Plugin.Cfg.ConnectionKey.Value = _draftKey ?? ""; Plugin.Cfg.LastSteamIdToJoin.Value = _draftSteamId ?? ""; } private static string DraftSig() { return _draftName + "\0" + _draftAddress + "\0" + _draftPort + "\0" + _draftKey + "\0" + _draftSteamId; } private static void AutoSaveDrafts() { string text = DraftSig(); if (!(text == _lastSavedSig)) { _lastSavedSig = text; SaveSettings(); } } private static void ResetHandles() { _ctx = null; _statusHandle = null; _joinFeedbackHandle = null; _gateHintHandle = null; _lanIpHandle = null; _createHandle = null; _joinHandle = null; _playerListHandle = null; _lastPlayerSig = "\0"; _steamUnavailableHandle = null; _yourSteamIdHandle = null; _steamInviteHandle = null; _steamJoinHandle = null; _steamPendingInviteHandle = null; _steamIdInputField = null; _ffSessionHandle = null; _ffToggleOption = null; _ffLockApplied = false; } private static void OnFriendlyFireToggled(bool value) { if (CoopConnection.CurrentMode == NetMode.Client) { ApplySessionFriendlyFireControl(); return; } bool flag = false; try { flag = Plugin.Cfg.FriendlyFire.Value != value; Plugin.Cfg.FriendlyFire.Value = value; } catch { } try { if (CoopConnection.CurrentMode == NetMode.Host) { CoopConnection.Service?.BroadcastSessionSettings("ui-toggle"); if (flag) { CoopToasts.NotifySessionSetting(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), value); } } } catch (Exception ex) { Plugin.Log?.Warn("[CoopUi] friendly-fire broadcast failed: " + ex.Message); } } private static void ApplySessionFriendlyFireControl() { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) bool flag = CoopConnection.CurrentMode == NetMode.Client; if ((Object)(object)_ffToggleOption != (Object)null) { object? obj = FfCheckboxField?.GetValue(_ffToggleOption); Toggle val = (Toggle)((obj is Toggle) ? obj : null); if (flag != _ffLockApplied) { ApplyFfRowLock(flag); _ffLockApplied = flag; } bool flag2 = (flag ? NetSessionSettings.FriendlyFireEnabled : ReadBool(() => Plugin.Cfg.FriendlyFire.Value, fallback: false)); if ((Object)(object)val != (Object)null && val.isOn != flag2) { val.SetIsOnWithoutNotify(flag2); } } if (_ffSessionHandle != null) { if (flag) { _ffSessionHandle.SetText(CoopLoc.Format("connect.ffSession", "Session friendly fire: {state} (set by host)", ("state", NetSessionSettings.FriendlyFireEnabled ? CoopLoc.Get("common.onUpper", "ON") : CoopLoc.Get("common.offUpper", "OFF")))); _ffSessionHandle.SetColor(NeutralColor); _ffSessionHandle.SetVisible(true); } else { _ffSessionHandle.SetVisible(false); } } } private static void ApplyFfRowLock(bool locked) { if (!((Object)(object)_ffToggleOption == (Object)null)) { try { FfIsLockedField?.SetValue(_ffToggleOption, locked); } catch { } GameObject gameObject = ((Component)_ffToggleOption).gameObject; CanvasGroup val = gameObject.GetComponent<CanvasGroup>(); if ((Object)(object)val == (Object)null) { val = gameObject.AddComponent<CanvasGroup>(); } val.alpha = (locked ? 0.45f : 1f); val.interactable = !locked; val.blocksRaycasts = !locked; } } private static SulfurButtonHandle Handle(IReadOnlyList<SulfurButtonHandle> handles, int index) { if (handles == null || index < 0 || index >= handles.Count) { return null; } return handles[index]; } private static bool ReadBool(Func<bool> read, bool fallback) { try { return read(); } catch { return fallback; } } private static int ReadInt(Func<int> read, int fallback) { try { return read(); } catch { return fallback; } } private static string KeyText(Func<string> read) { try { return read(); } catch { return "(unset)"; } } private static void OpenUrl(string url) { try { Application.OpenURL(url); } catch (Exception ex) { Plugin.Log?.Warn("[CoopUi] open URL failed: " + ex.Message); } } private static bool IsInGame() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); return string.Equals(((Scene)(ref activeScene)).name ?? "", "GameScene", StringComparison.OrdinalIgnoreCase); } catch { return false; } } } internal static class CoopLoc { private static Func<string, string, string, string>? _get; private static Func<int>? _languageVersion; public static int LanguageVersion { get { Func<int> languageVersion = _languageVersion; if (languageVersion == null) { return 0; } try { return languageVersion(); } catch { return 0; } } } public static void Wire(string pluginAssemblyLocation) { try { Type type = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurLocalization"); if (type == null) { Plugin.Log?.Info("[CoopLoc] SulfurLocalization not present — player-facing text stays English."); return; } MethodInfo methodInfo = AccessTools.Method(type, "LoadPluginLocalization", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo != null && !string.IsNullOrEmpty(pluginAssemblyLocation)) { methodInfo.Invoke(null, new object[2] { "com.ryuka.sulfur.together", pluginAssemblyLocation }); } else { Plugin.Log?.Warn("[CoopLoc] LoadPluginLocalization missing or no assembly location — lang files not loaded."); } MethodInfo methodInfo2 = AccessTools.Method(type, "Get", new Type[3] { typeof(string), typeof(string), typeof(string) }, (Type[])null); if (methodInfo2 != null) { _get = (Func<string, string, string, string>)Delegate.CreateDelegate(typeof(Func<string, string, string, string>), methodInfo2); } MethodInfo methodInfo3 = AccessTools.PropertyGetter(type, "LanguageVersion"); if (methodInfo3 != null) { _languageVersion = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), methodInfo3); } Plugin.Log?.Info($"[CoopLoc] localization wired (lookup={_get != null}, languageVersion={_languageVersion != null})."); } catch (Exception ex) { Plugin.Log?.Warn("[CoopLoc] wire failed — player-facing text stays English: " + ex.Message); } } public static string Get(string key, string fallback) { Func<string, string, string, string> get = _get; if (get == null) { return fallback; } try { return get("com.ryuka.sulfur.together", key, fallback); } catch { return fallback; } } public static string Format(string key, string fallback, params (string token, string value)[] args) { string text = Get(key, fallback); if (args != null) { for (int i = 0; i < args.Length; i++) { (string token, string value) tuple = args[i]; string item = tuple.token; string item2 = tuple.value; text = text.Replace("{" + item + "}", item2 ?? ""); } } return text; } } internal static class CoopToasts { private static Action<string, string> _show; public static void Wire(Action<string, string> showToast) { _show = showToast; } public static void Notify(string message) { Notify(null, message); } public static void NotifySessionSetting(string settingLabel, bool enabled) { Notify(null, CoopLoc.Format("session.settingChanged", "{label}: {state}", ("label", settingLabel), ("state", enabled ? CoopLoc.Get("common.on", "On") : CoopLoc.Get("common.off", "Off"))), respectPreference: false); } public static void Notify(string title, string message) { Notify(title, message, respectPreference: true); } private static void Notify(string title, string message, bool respectPreference) { if (string.IsNullOrEmpty(message)) { return; } if (respectPreference) { bool flag = true; try { flag = Plugin.Cfg.EnableCoopToasts.Value; } catch { } if (!flag) { return; } } string text = (string.IsNullOrEmpty(title) ? CoopLoc.Get("toast.title.default", "Together") : title); Plugin.Log?.Info("[CoopToast] " + text + ": " + message); try { _show?.Invoke(text, message); } catch (Exception ex) { Plugin.Log?.Warn("[CoopToast] toast show failed: " + ex.Message); } } } internal static class NativeFontSampler { public static TMP_FontAsset? ResolveNativeFont() { try { TextMeshProUGUI[] array = Object.FindObjectsByType<TextMeshProUGUI>((FindObjectsSortMode)0); foreach (TextMeshProUGUI val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((TMP_Text)val).font == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { return ((TMP_Text)val).font; } } } catch { } try { return TMP_Settings.defaultFontAsset; } catch { return null; } } } } namespace SULFURTogether.UI.RunStatsOverlay { internal sealed class RunStatsBestMarks { public const int StatCount = 7; private static readonly bool[] LowerIsBetter = new bool[7] { false, false, false, true, false, true, false }; private readonly int[] _bestValue = new int[7]; private readonly bool[] _marked = new bool[7]; public static readonly RunStatsBestMarks None = new RunStatsBestMarks(); private RunStatsBestMarks() { } public static RunStatsBestMarks Compute(IReadOnlyList<NetRunStats> players) { RunStatsBestMarks runStatsBestMarks = new RunStatsBestMarks(); if (players.Count == 0) { return runStatsBestMarks; } for (int i = 0; i < 7; i++) { int num = int.MaxValue; int num2 = int.MinValue; foreach (NetRunStats player in players) { int stat = GetStat(player, i); if (stat < num) { num = stat; } if (stat > num2) { num2 = stat; } } runStatsBestMarks._bestValue[i] = (LowerIsBetter[i] ? num : num2); runStatsBestMarks._marked[i] = num != num2; } return runStatsBestMarks; } public bool IsBest(int statIndex, int value) { if (_marked[statIndex]) { return value == _bestValue[statIndex]; } return false; } private static int GetStat(NetRunStats stats, int statIndex) { return statIndex switch { 0 => stats.ShotsFired, 1 => stats.DamageDealt, 2 => stats.Kills, 3 => stats.TimesDowned, 4 => stats.Rescues, 5 => stats.DamageTaken, _ => stats.DestructiblesDestroyed, }; } } internal static class RunStatsCanvasBuilder { public const float CardWidth = 340f; public const float ViewportWidth = 1600f; public static float ActiveCardSpacing = 44f; private const float VerticalCenterBiasPx = 60f; private const float ViewportHeightPx = 560f; public static GameObject BuildRoot(out RectTransform viewport, out RectTransform track) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("RunStatsOverlayCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); Canvas component = val.GetComponent<Canvas>(); component.renderMode = (RenderMode)0; component.sortingOrder = 0; CanvasScaler component2 = val.GetComponent<CanvasScaler>(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.85f; GameObject val2 = new GameObject("Viewport", new Type[2] { typeof(RectTransform), typeof(RectMask2D) }); val2.transform.SetParent(val.transform, false); RectTransform val3 = (RectTransform)val2.transform; val3.anchorMin = new Vector2(0.5f, 0.5f); val3.anchorMax = new Vector2(0.5f, 0.5f); val3.pivot = new Vector2(0.5f, 0.5f); val3.anchoredPosition = new Vector2(0f, 60f); val3.sizeDelta = new Vector2(1600f, 560f); GameObject val4 = new GameObject("Track", new Type[3] { typeof(RectTransform), typeof(HorizontalLayoutGroup), typeof(ContentSizeFitter) }); val4.transform.SetParent(val2.transform, false); RectTransform val5 = (RectTransform)val4.transform; val5.anchorMin = new Vector2(0f, 0.5f); val5.anchorMax = new Vector2(0f, 0.5f); val5.pivot = new Vector2(0f, 0.5f); val5.anchoredPosition = Vector2.zero; HorizontalLayoutGroup component3 = val4.GetComponent<HorizontalLayoutGroup>(); ((LayoutGroup)component3).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)component3).spacing = ActiveCardSpacing; ((HorizontalOrVerticalLayoutGroup)component3).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)component3).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)component3).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)component3).childControlHeight = false; ContentSizeFitter component4 = val4.GetComponent<ContentSizeFitter>(); component4.horizontalFit = (FitMode)2; component4.verticalFit = (FitMode)2; viewport = val3; track = val5; return val; } } internal sealed class RunStatsCardHoverAnimator { private const float MaxLeanDegrees = 3.5f; private const float MaxSquash = 0.045f; private const float HotScale = 1.05f; private const float SlideTowardCursor = 5f; private const float LiftUp = 8f; private const float ShadowCounterSlide = 4f; private readonly RectTransform _rect; private readonly Shadow _shadow; private readonly Image _titleBandImage; private readonly Vector2 _baselinePos; private readonly Vector2 _shadowBaseDistance; private readonly Vector3 _restScale; private RunStatsSpring _aimX; private RunStatsSpring _aimY; private RunStatsSpring _hot; public RunStatsCardHoverAnimator(RunStatsCardView card) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) _rect = card.Rect; _shadow = card.ShadowFx; _titleBandImage = card.TitleBandImage; _baselinePos = _rect.anchoredPosition; _shadowBaseDistance = _shadow.effectDistance; _restScale = ((Transform)_rect).localScale; } public bool IsPointerOver(Vector2 pointerPosition) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return RectTransformUtility.RectangleContainsScreenPoint(_rect, pointerPosition, (Camera)null); } public void Tick(float deltaTime, bool isHot, Vector2 pointerPosition) { //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) float target = 0f; float target2 = 0f; float target3 = 0f; Vector2 val = default(Vector2); if (isHot && RectTransformUtility.ScreenPointToLocalPointInRectangle(_rect, pointerPosition, (Camera)null, ref val)) { Rect rect = _rect.rect; target = ((((Rect)(ref rect)).width > 0.01f) ? Mathf.Clamp((val.x - ((Rect)(ref rect)).center.x) / (((Rect)(ref rect)).width * 0.5f), -1f, 1f) : 0f); target2 = ((((Rect)(ref rect)).height > 0.01f) ? Mathf.Clamp((val.y - ((Rect)(ref rect)).center.y) / (((Rect)(ref rect)).height * 0.5f), -1f, 1f) : 0f); target3 = 1f; } float num = _aimX.Tick(target, deltaTime); float num2 = _aimY.Tick(target2, deltaTime); float num3 = Mathf.Clamp01(_hot.Tick(target3, deltaTime)); ((Transform)_rect).localRotation = Quaternion.Euler(0f, 0f, num * 3.5f); float num4 = 1f + 0.049999952f * num3; ((Transform)_rect).localScale = new Vector3(_restScale.x * num4 * (1f - 0.045f * Mathf.Abs(num)), _restScale.y * num4 * (1f - 0.045f * Mathf.Abs(num2)), _restScale.z); _rect.anchoredPosition = _baselinePos + new Vector2(num * 5f, num2 * 5f + 8f * num3); _shadow.effectDistance = _shadowBaseDistance + new Vector2((0f - num) * 4f, (0f - num2) * 4f); Color color = ((Graphic)_titleBandImage).color; color.a = Mathf.Lerp(0.1f, 0.16f, num3); ((Graphic)_titleBandImage).color = color; } } internal sealed class RunStatsCardView { private static readonly (string key, string en)[] LabelDefs = new(string, string)[7] { ("runstats.stat.shotsFired", "Shots Fired"), ("runstats.stat.damageDealt", "Damage Dealt"), ("runstats.stat.kills", "Kills"), ("runstats.stat.timesDowned", "Times Downed"), ("runstats.stat.rescues", "Rescues"), ("runstats.stat.damageTaken", "Damage Taken"), ("runstats.stat.destructiblesDestroyed", "Destructibles Destroyed") }; private static readonly Color BodyColor = new Color(0.085f, 0.065f, 0.05f, 0.97f); private static readonly Color InnerTint = new Color(1f, 0.58f, 0.28f, 0.045f); private static readonly Color NormalBorder = new Color(0.72f, 0.71f, 0.66f, 0.95f); private static readonly Color LocalBorder = new Color(0.99f, 0.72f, 0.18f, 1f); private static readonly Color LocalGlow = new Color(1f, 0.55f, 0.15f, 0.3f); private static readonly Color DropShadow = new Color(0f, 0f, 0f, 0.55f); private static readonly Color TitleBandColor = new Color(1f, 0.5f, 0.2f, 1f); private static readonly Color LabelColor = new Color(0.84f, 0.81f, 0.76f, 1f); private static readonly Color ValueColor = new Color(0.99f, 0.97f, 0.94f, 1f); private static readonly Color BestValueColor = new Color(1f, 0.6f, 0.25f, 1f); private static readonly Color NameColor = Color.white; private static readonly Color LocalNameColor = new Color(1f, 0.8f, 0.28f, 1f); public const float RestHighlightAlpha = 0.1f; public const float HotHighlightAlpha = 0.16f; private const float TitleBandHeight = 62f; private readonly GameObject _root; private readonly RectTransform _rect; private readonly TextMeshProUGUI _nameText; private readonly TextMeshProUGUI[] _valueTexts; private readonly Outline _border; private readonly Outline _glow; public GameObject Root => _root; public RectTransform Rect => _rect; public Shadow ShadowFx { get; } public RectTransform TitleBandRect { get; } public Image TitleBandImage { get; } private RunStatsCardView(GameObject root, TextMeshProUGUI nameText, TextMeshProUGUI[] valueTexts, Outline border, Outline glow, Shadow shadow, RectTransform titleBandRect, Image titleBandImage) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown _root = root; _rect = (RectTransform)root.transform; _nameText = nameText; _valueTexts = valueTexts; _border = border; _glow = glow; ShadowFx = shadow; TitleBandRect = titleBandRect; TitleBandImage = titleBandImage; } public static RunStatsCardView Create(Transform parent, TMP_FontAsset? font) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_043e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("RunStatsCard", new Type[4] { typeof(RectTransform), typeof(Image), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter) }); val.transform.SetParent(parent, false); RectTransform val2 = (RectTransform)val.transform; val2.sizeDelta = new Vector2(340f, val2.sizeDelta.y); Image component = val.GetComponent<Image>(); ((Graphic)component).color = BodyColor; ((Graphic)component).raycastTarget = false; Outline val3 = val.AddComponent<Outline>(); ((Shadow)val3).effectColor = NormalBorder; ((Shadow)val3).effectDistance = new Vector2(2f, -2f); ((Shadow)val3).useGraphicAlpha = false; Outline val4 = val.AddComponent<Outline>(); ((Shadow)val4).effectColor = LocalGlow; ((Shadow)val4).effectDistance = new Vector2(4.5f, -4.5f); ((Shadow)val4).useGraphicAlpha = false; ((Behaviour)val4).enabled = false; Shadow val5 = val.AddComponent<Shadow>(); val5.effectColor = DropShadow; val5.effectDistance = new Vector2(5f, -5f); val5.useGraphicAlpha = false; VerticalLayoutGroup component2 = val.GetComponent<VerticalLayoutGroup>(); ((LayoutGroup)component2).padding = new RectOffset(20, 20, 18, 18); ((HorizontalOrVerticalLayoutGroup)component2).spacing = 6f; ((LayoutGroup)component2).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = true; ContentSizeFitter component3 = val.GetComponent<ContentSizeFitter>(); component3.horizontalFit = (FitMode)0; component3.verticalFit = (FitMode)2; (RectTransform rect, Image image) tuple = CreateDecoration(val.transform, "InnerPanel", InnerTint); tuple.rect.anchorMin = Vector2.zero; tuple.rect.anchorMax = Vector2.one; tuple.rect.offsetMin = new Vector2(5f, 5f); tuple.rect.offsetMax = new Vector2(-5f, -5f); (RectTransform, Image) tuple2 = CreateDecoration(val.transform, "TitleBand", new Color(TitleBandColor.r, TitleBandColor.g, TitleBandColor.b, 0.1f)); tuple2.Item1.anchorMin = new Vector2(0f, 1f); tuple2.Item1.anchorMax = new Vector2(1f, 1f); tuple2.Item1.pivot = new Vector2(0.5f, 1f); tuple2.Item1.offsetMin = new Vector2(5f, -62f); tuple2.Item1.offsetMax = new Vector2(-5f, -5f); TextMeshProUGUI nameText = CreateText(val.transform, font, 30f, (FontStyles)1, (TextAlignmentOptions)514, 38f); GameObject val6 = new GameObject("NameSeparator", new Type[2] { typeof(RectTransform), typeof(LayoutElement) }); val6.transform.SetParent(val.transform, false); val6.GetComponent<LayoutElement>().minHeight = 6f; TextMeshProUGUI[] array = (TextMeshProUGUI[])(object)new TextMeshProUGUI[LabelDefs.Length]; for (int i = 0; i < LabelDefs.Length; i++) { GameObject val7 = new GameObject($"Row_{i}", new Type[3] { typeof(RectTransform), typeof(HorizontalLayoutGroup), typeof(LayoutElement) }); val7.transform.SetParent(val.transform, false); val7.GetComponent<LayoutElement>().minHeight = 30f; HorizontalLayoutGroup component4 = val7.GetComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)component4).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)component4).childForceExpandHeight = true; ((HorizontalOrVerticalLayoutGroup)component4).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)component4).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)component4).spacing = 14f; TextMeshProUGUI obj = CreateText(val7.transform, font, 19f, (FontStyles)0, (TextAlignmentOptions)513, 28f); ((TMP_Text)obj).text = CoopLoc.Get(LabelDefs[i].key, LabelDefs[i].en); ((Graphic)obj).color = LabelColor; ((Component)obj).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f; TextMeshProUGUI val8 = CreateText(val7.transform, font, 23f, (FontStyles)1, (TextAlignmentOptions)516, 28f); ((Graphic)val8).color = ValueColor; ((Component)val8).gameObject.AddComponent<LayoutElement>().minWidth = 70f; array[i] = val8; } return new RunStatsCardView(val, nameText, array, val3, val4, val5, tuple2.Item1, tuple2.Item2); } private static (RectTransform rect, Image image) CreateDecoration(Transform parent, string name, Color color) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(Image), typeof(LayoutElement) }); val.transform.SetParent(parent, false); val.GetComponent<LayoutElement>().ignoreLayout = true; Image component = val.GetComponent<Image>(); ((Graphic)component).color = color; ((Graphic)component).raycastTarget = false; return (rect: (RectTransform)val.transform, image: component); } private static TextMeshProUGUI CreateText(Transform parent, TMP_FontAsset? font, float size, FontStyles style, TextAlignmentOptions align, float minHeight) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Text", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val.transform.SetParent(parent, false); TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>(); if ((Object)(object)font != (Object)null) { ((TMP_Text)component).font = font; } ((TMP_Text)component).fontSize = size; ((TMP_Text)component).fontStyle = style; ((TMP_Text)component).alignment = align; ((Graphic)component).color = Color.white; ((Graphic)component).raycastTarget = false; ((TMP_Text)component).enableAutoSizing = true; ((TMP_Text)component).fontSizeMin = size * 0.7f; ((TMP_Text)component).fontSizeMax = size; val.AddComponent<LayoutElement>().minHeight = minHeight; return component; } public void Bind(NetRunStats stats, bool isLocalPlayer, RunStatsBestMarks best) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(stats.PlayerName) ? stats.PeerId : stats.PlayerName); ((TMP_Text)_nameText).text = (isLocalPlayer ? CoopLoc.Format("runstats.youSuffix", "{name} (You)", ("name", text)) : text); ((Graphic)_nameText).color = (isLocalPlayer ? LocalNameColor : NameColor); ((Shadow)_border).effectColor = (isLocalPlayer ? LocalBorder : NormalBorder); ((Behaviour)_glow).enabled = isLocalPlayer; SetValue(0, stats.ShotsFired, best); SetValue(1, stats.DamageDealt, best); SetValue(2, stats.Kills, best); SetValue(3, stats.TimesDowned, best); SetValue(4, stats.Rescues, best); SetValue(5, stats.DamageTaken, best); SetValue(6, stats.DestructiblesDestroyed, best); } public void SetEmpty() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)_nameText).text = "…"; ((Graphic)_nameText).color = NameColor; ((Shadow)_border).effectColor = NormalBorder; ((Behaviour)_glow).enabled = false; for (int i = 0; i < _valueTexts.Length; i++) { ((TMP_Text)_valueTexts[i]).text = "…"; ((Graphic)_valueTexts[i]).color = ValueColor; } } private void SetValue(int index, int value, RunStatsBestMarks best) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)_valueTexts[index]).text = value.ToString(); ((Graphic)_valueTexts[index]).color = (best.IsBest(index, value) ? BestValueColor : ValueColor); } } internal sealed class RunStatsCarouselController { private const int VisibleCards = 4; private const float OverflowLeftPad = 24f; private RectTransform? _track; private int _cardCount; private int _focusIndex; private RunStatsSpring _x; public void SetTrack(RectTransform track) { _track = track; } public void ResetForNewData(int cardCount) { _cardCount = Mathf.Max(0, cardCount); _focusIndex = 0; _x.Snap(TargetX()); Apply(); } public void MoveFocus(int delta) { _focusIndex = Mathf.Clamp(_focusIndex + delta, 0, MaxFocus()); } public void Tick(float deltaTime) { _x.Tick(TargetX(), deltaTime); Apply(); } private int MaxFocus() { return Mathf.Max(0, _cardCount - 4); } private float TargetX() { float activeCardSpacing = RunStatsCanvasBuilder.ActiveCardSpacing; float num = (float)_cardCount * 340f + (float)Mathf.Max(0, _cardCount - 1) * activeCardSpacing; return Mathf.Max(24f, (1600f - num) * 0.5f) - (float)_focusIndex * (340f + activeCardSpacing); } private void Apply() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_track == (Object)null)) { Vector2 anchoredPosition = _track.anchoredPosition; anchoredPosition.x = _x.Value; _track.anchoredPosition = anchoredPosition; } } } internal static class RunStatsCursorControl { private static bool _held; private static bool _savedVisible; private static CursorLockMode _savedLockState; public static void Acquire() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!_held) { _savedVisible = Cursor.visible; _savedLockState = Cursor.lockState; _held = true; Enforce(); } } public static void Enforce() { if (_held) { Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; } } public static void Release() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (_held) { _held = false; Cursor.visible = _savedVisible; Cursor.lockState = _savedLockState; } } } internal static class RunStatsDevInjector { private static readonly List<NetRunStats> _simulated = new List<NetRunStats>(); private static readonly Random _rng = new Random(); private static int _nextId = 1; private static bool _keyboardBroken; public static int Version { get; private set; } public static IReadOnlyList<NetRunStats> Simulated => _simulated; public static void Poll() { if (_keyboardBroken || !GameManager.DeveloperMode) { return; } try { if (!EndKeyPressedThisFrame()) { return; } } catch (Exception ex) { _keyboardBroken = true; NetLogger.Warn("[RunStatsOverlay] dev-injector keyboard poll failed — End key disabled: " + ex.GetType().Name + ": " + ex.Message); return; } int num = _nextId++; _simulated.Add(new NetRunStats { PeerId = $"sim-{num}", PlayerName = $"Tester {num}", ShotsFired = _rng.Next(0, 500), DamageDealt = _rng.Next(0, 8000), Kills = _rng.Next(0, 60), TimesDowned = _rng.Next(0, 6), Rescues = _rng.Next(0, 6), DamageTaken = _rng.Next(0, 2000), DestructiblesDestroyed = _rng.Next(0, 40) }); Version++; NetLogger.Info($"[RunStatsOverlay] dev-injector added simulated player {num} (total {_simulated.Count})"); } public static void Clear() { if (_simulated.Count != 0) { _simulated.Clear(); _nextId = 1; Version++; } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool EndKeyPressedThisFrame() { Keyboard current = Keyboard.current; if (current != null) { return ((ButtonControl)current.endKey).wasPressedThisFrame; } return false; } } internal static class RunStatsInputReader { private static bool _mouseBroken; private static bool _gamepadBroken; public static int PollDelta() { if (!_mouseBroken) { try { float num = ReadMouseScrollY(); if (num > 0.01f) { return -1; } if (num < -0.01f) { return 1; } } catch (Exception ex) { _mouseBroken = true; NetLogger.Warn("[RunStatsOverlay] mouse poll failed — mouse input disabled: " + ex.GetType().Name + ": " + ex.Message); } } if (!_gamepadBroken) { try { int num2 = PollGamepadDelta(); if (num2 != 0) { return num2; } } catch (Exception ex2) { _gamepadBroken = true; NetLogger.Warn("[RunStatsOverlay] gamepad poll failed — gamepad navigation disabled: " + ex2.GetType().Name + ": " + ex2.Message); } } return 0; } public static bool TryGetPointerPosition(out Vector2 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) position = default(Vector2); if (_mouseBroken) { return false; } try { return ReadMousePosition(out position); } catch (Exception ex) { _mouseBroken = true; NetLogger.Warn("[RunStatsOverlay] mouse poll failed — mouse input disabled: " + ex.GetType().Name + ": " + ex.Message); return false; } } [MethodImpl(MethodImplOptions.NoInlining)] private static float ReadMouseScrollY() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) Mouse current = Mouse.current; if (current == null) { return 0f; } return ((InputControl<Vector2>)(object)current.scroll).ReadValue().y; } [MethodImpl(MethodImplOptions.NoInlining)] private static bool ReadMousePosition(out Vector2 position) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Mouse current = Mouse.current; if (current == null) { position = default(Vector2); return false; } position = ((InputControl<Vector2>)(object)((Pointer)current).position).ReadValue(); return true; } [MethodImpl(MethodImplOptions.NoInlining)] private static int PollGamepadDelta() { Gamepad current = Gamepad.current; if (current == null) { return 0; } if (current.dpad.right.wasPressedThisFrame || current.rightShoulder.wasPressedThisFrame) { return 1; } if (current.dpad.left.wasPressedThisFrame || current.leftShoulder.wasPressedThisFrame) { return -1; } return 0; } } internal static class RunStatsOverlayManager { private static GameObject? _root; private static RectTransform? _viewport; private static RectTransform? _track; private static readonly RunStatsCarouselController _carousel = new RunStatsCarouselController(); private static readonly List<RunStatsCardView> _cards = new List<RunStatsCardView>(); private static readonly List<RunStatsCardHoverAnimator> _hoverAnimators = new List<RunStatsCardHoverAnimator>(); private static IReadOnlyList<NetRunStats>? _boundList; private static int _boundSimVersion = -1; private static bool _shownLastTick; private static string _lastWarned = ""; public static void Tick() { bool flag; try { flag = ApplyLifecycle(); } catch (Exception ex) { WarnOnce("lifecycle failed: " + ex.GetType().Name + ": " + ex.Message); return; } try { if (IsInGameScene()) { RunStatsDevInjector.Poll(); } } catch (Exception ex2) { WarnOnce("dev injector failed: " + ex2.GetType().Name + ": " + ex2.Message); } if (!flag) { return; } try { TickInteraction(Time.unscaledDeltaTime); } catch (Exception ex3) { WarnOnce("interaction failed (cards stay shown, animation skipped): " + ex3.GetType().Name + ": " + ex3.Message); } } private static bool ApplyLifecycle() { NetRunState state; bool flag = NetRunStateBridge.TryGetLocalRunState(out state) && state.IsLoadingLikeState; bool pendingRunEndDisplay = NetRunStatsClientCache.PendingRunEndDisplay; bool flag2 = pendingRunEndDisplay && flag && IsNetworkSessionActive() && IsInGameScene(); if (flag2) { EnsureBuilt(); if ((Object)(object)_root != (Object)null && !_root.activeSelf) { _root.SetActive(true); ApplyTopmostSortingOrder(); NetLogger.Info("[RunStatsOverlay] show"); } RunStatsCursorControl.Acquire(); RunStatsCursorControl.Enforce(); RefreshIfChanged(); } else if (_shownLastTick) { NetLogger.Info($"[RunStatsOverlay] hide (loading={flag} pending={pendingRunEndDisplay} networkActive={IsNetworkSessionActive()} inGameScene={IsInGameScene()})"); HideAndClear(); } _shownLastTick = flag2; return flag2; } private static void WarnOnce(string message) { if (!(message == _lastWarned)) { _lastWarned = message; NetLogger.Warn("[RunStatsOverlay] " + message); } } public static void Shutdown() { if (_shownLastTick) { HideAndClear(); } _shownLastTick = false; } private static void TickInteraction(float dt) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) int num = RunStatsInputReader.PollDelta(); if (num != 0) { _carousel.MoveFocus(num); } _carousel.Tick(dt); Vector2 position; bool num2 = RunStatsInputReader.TryGetPointerPosition(out position) && (Object)(object)_viewport != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_viewport, position, (Camera)null); int num3 = -1; if (num2) { for (int i = 0; i < _hoverAnimators.Count; i++) { if (_hoverAnimators[i].IsPointerOver(position)) { num3 = i; break; } } } for (int j = 0; j < _hoverAnimators.Count; j++) { _hoverAnimators[j].Tick(dt, j == num3, position); } } private static bool IsNetworkSessionActive() { return NetConfig.GetMode() switch { NetMode.Host => true, NetMode.Client => NetClientJoinFlow.SessionJoinedHost, _ => false, }; } private static bool IsInGameScene() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); return string.Equals(((Scene)(ref activeScene)).name, "GameScene", StringComparison.OrdinalIgnoreCase); } catch { return false; } } private static void EnsureBuilt() { if (!((Object)(object)_root != (Object)null)) { _root = RunStatsCanvasBuilder.BuildRoot(out RectTransform viewport, out RectTransform track); _viewport = viewport; _track = track; _carousel.SetTrack(track); Object.DontDestroyOnLoad((Object)(object)_root); _root.SetActive(false); } } private static void ApplyTopmostSortingOrder() { Canvas component = _root.GetComponent<Canvas>(); int num = 0; Canvas[] array = Object.FindObjectsByType<Canvas>((FindObjectsSortMode)0); foreach (Canvas val in array) { if (!((Object)(object)val == (Object)(object)component) && val.sortingOrder > num) { num = val.sortingOrder; } } component.sortingOrder = num + 100; } private static void RefreshIfChanged() { IReadOnlyList<NetRunStats> lastFinalized = NetRunStatsClientCache.LastFinalized; int version = RunStatsDevInjector.Version; if (lastFinalized != _boundList || version != _boundSimVersion) { _boundList = lastFinalized; _boundSimVersion = version; RebuildCards(ComposeDisplayList(lastFinalized)); } } private static IReadOnlyList<NetRunStats>? ComposeDisplayList(IReadOnlyList<NetRunStats>? real) { IReadOnlyList<NetRunStats> simulated = RunStatsDevInjector.Simulated; if (simulated.Count == 0) { return real; } List<NetRunStats> list = new List<NetRunStats>(); if (real != null) { list.AddRange(real); } list.AddRange(simulated); return list; } private static void RebuildCards(IReadOnlyList<NetRunStats>? list) { foreach (RunStatsCardView card in _cards) { card.Root.SetActive(false); Object.Destroy((Object)(object)card.Root); } _cards.Clear(); _hoverAnimators.Clear(); if ((Object)(object)_track == (Object)null) { return; } ApplyResponsiveSpacing(); TMP_FontAsset font = NativeFontSampler.ResolveNativeFont(); if (list == null || list.Count == 0) { RunStatsCardView runStatsCardView = RunStatsCardView.Create((Transform)(object)_track, font); runStatsCardView.SetEmpty(); _cards.Add(runStatsCardView); } else { RunStatsBestMarks best = RunStatsBestMarks.Compute(list); NetRunState state; string text = (NetRunStateBridge.TryGetLocalRunState(out state) ? state.PeerId : ""); foreach (NetRunStats item in list) { RunStatsCardView runStatsCardView2 = RunStatsCardView.Create((Transform)(object)_track, font); runStatsCardView2.Bind(item, item.PeerId == text, best); _cards.Add(runStatsCardView2); } } _carousel.ResetForNewData(_cards.Count); foreach (RunStatsCardView card2 in _cards) { LayoutRebuilder.ForceRebuildLayoutImmediate(card2.Rect); } LayoutRebuilder.ForceRebuildLayoutImmediate(_track); foreach (RunStatsCardView card3 in _cards) { _hoverAnimators.Add(new RunStatsCardHoverAnimator(card3)); } } private static void ApplyResponsiveSpacing() { float num = ((Screen.height > 0) ? ((float)Screen.width / (float)Screen.height) : 1.7777778f); float spacing = (RunStatsCanvasBuilder.ActiveCardSpacing = Mathf.Lerp(26f, 44f, Mathf.InverseLerp(1.25f, 1.7f, num))); HorizontalLayoutGroup val = (((Object)(object)_track != (Object)null) ? ((Component)_track).GetComponent<HorizontalLayoutGroup>() : null); if ((Object)(object)val != (Object)null) { ((HorizontalOrVerticalLayoutGroup)val).spacing = spacing; } } private static void HideAndClear() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } RunStatsCursorControl.Release(); NetRunStatsClientCache.ConsumeAndClear(); RunStatsDevInjector.Clear(); _boundList = null; _boundSimVersion = -1; } } internal struct RunStatsSpring { private const float AngularFrequency = 14f; private const float DampingRatio = 0.75f; private const float MaxStep = 1f / 30f; public float Value; private float _velocity; public void Snap(float value) { Value = value; _velocity = 0f; } public float Tick(float target, float deltaTime) { float num = Mathf.Min(deltaTime, 1f / 30f); _velocity += (-21f * _velocity - 196f * (Value - target)) * num; Value += _velocity * num; return Value; } } } namespace SULFURTogether.UI.DownedRescueOverlay { internal sealed class DownedRescueBorderProgress : MaskableGraphic { public float Thickness = 4f; public float Inset; private float _progress01 = 1f; private readonly Vector2[] _corners = (Vector2[])(object)new Vector2[6]; private readonly float[] _segLen = new float[5]; public void SetProgress(float value) { value = Mathf.Clamp01(value); if (!Mathf.Approximately(value, _progress01)) { _progress01 = value; ((Graphic)this).SetVerticesDirty(); } } protected override void OnPopulateMesh(VertexHelper vh) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) vh.Clear(); Rect pixelAdjustedRect = ((Graphic)this).GetPixelAdjustedRect(); float num = ((Rect)(ref pixelAdjustedRect)).width * 0.5f - Inset; float num2 = ((Rect)(ref pixelAdjustedRect)).height * 0.5f - Inset; if (num <= 0f || num2 <= 0f) { return; } _corners[0] = new Vector2(0f, num2); _corners[1] = new Vector2(num, num2); _corners[2] = new Vector2(num, 0f - num2); _corners[3] = new Vector2(0f - num, 0f - num2); _corners[4] = new Vector2(0f - num, num2); _corners[5] = new Vector2(0f, num2); _segLen[0] = num; _segLen[1] = 2f * num2; _segLen[2] = 2f * num; _segLen[3] = 2f * num2; _segLen[4] = num; float num3 = _segLen[0] + _segLen[1] + _segLen[2] + _segLen[3] + _segLen[4]; float num4 = _progress01 * num3; Color32 c = Color32.op_Implicit(((Graphic)this).color); float num5 = 0f; for (int i = 0; i < 5; i++) { float num6 = num5; float num7 = num5 + _segLen[i]; if (!(num4 <= num6)) { Vector2 val = _corners[i]; Vector2 val2 = _corners[i + 1]; bool num8 = num4 >= num7; Vector2 b = (num8 ? val2 : Vector2.Lerp(val, val2, (num4 - num6) / _segLen[i])); AddSegmentQuad(vh, val, b, c); if (num8 && i < 4) { AddCornerSquare(vh, val2, c); } num5 = num7; if (!num8) { break; } continue; } break; } } private void AddSegmentQuad(VertexHelper vh, Vector2 a, Vector2 b, Color32 c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) Vector2 val = b - a; if (!(((Vector2)(ref val)).sqrMagnitude < 0.0001f)) { val = b - a; Vector2 normalized = ((Vector2)(ref val)).normalized; Vector2 val2 = new Vector2(0f - normalized.y, normalized.x) * (Thickness * 0.5f); int currentVertCount = vh.currentVertCount; vh.AddVert(Vector2.op_Implicit(a - val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(a + val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(b + val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(b - val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2); vh.AddTriangle(currentVertCount, currentVertCount + 2, currentVertCount + 3); } } private void AddCornerSquare(VertexHelper vh, Vector2 center, Color32 c) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references)