Decompiled source of SailwindCoop v0.1.4
LiteNetLib.dll
Decompiled 5 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.1")] [assembly: AssemblyInformationalVersion("1.0.0+f57b9eafe72b924681a371d671af911478b6ab88")] [assembly: AssemblyProduct("LiteNetLib")] [assembly: AssemblyTitle("LiteNetLib")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/RevenantX/LiteNetLib")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.1.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class IsUnmanagedAttribute : Attribute { } [CompilerGenerated] [Microsoft.CodeAnalysis.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 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 Slot { internal int HashCode; internal int Next; internal NetPeer Value; } private readonly List<IncomingData> _pingSimulationList = new List<IncomingData>(); 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 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 const int ReceivePollingTime = 500000; 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 bool IsRunning { get; private set; } 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(ref _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 = default(DisconnectInfo); disconnectInfo.Reason = evt.DisconnectReason; disconnectInfo.AdditionalData = evt.DataReader; disconnectInfo.SocketErrorCode = evt.ErrorCode; DisconnectInfo disconnectInfo2 = disconnectInfo; _netEventListener.OnPeerDisconnected(evt.Peer, disconnectInfo2); 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")] 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--; } } } } private void ProcessNtpRequests(float elapsedMilliseconds) { 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")] private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint) { if (!SimulateLatency) { return; } int num = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); if (num > 5) { lock (_pingSimulationList) { _pingSimulationList.Add(new IncomingData { Data = packet, EndPoint = remoteEndPoint, TimeWhenGet = DateTime.UtcNow.AddMilliseconds(num) }); } _dropPacket = true; } } [Conditional("DEBUG")] 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")] private void ClearPingSimulationList() { lock (_pingSimulationList) { _pingSimulationList.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 (((uint)candidate & (true ? 1u : 0u)) != 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(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 s2 = _udpSocketv6?.Handle ?? IntPtr.Zero; byte[] address2 = new byte[16]; byte[] address3 = 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, address2)) { break; } continue; } bool flag = false; if (udpSocketv.Available != 0 || list.Contains(udpSocketv)) { if (!NativeReceiveFrom(handle, address2)) { break; } flag = true; } if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2)) { if (!NativeReceiveFrom(s2, address3)) { break; } flag = true; } list.Clear(); if (!flag) { list.Add(udpSocketv); list.Add(udpSocketv2); Socket.Select(list, null, null, 500000); } } 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 s, byte[] address) { int socketAddressSize = address.Length; packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref socketAddressSize); if (packet.Size == 0) { return true; } if (packet.Size == -1) { return !ProcessError(new SocketException((int)NativeSocket.GetSocketError())); } short num = (short)((address[1] << 8) | address[0]); tempEndPoint.Port = (ushort)((address[2] << 8) | address[3]); if ((NativeSocket.UnixMode && num == 10) || (!NativeSocket.UnixMode && num == 23)) { uint num2 = (uint)((address[27] << 24) + (address[26] << 16) + (address[25] << 8) + address[24]); byte[] array = new byte[16]; Buffer.BlockCopy(address, 8, array, 0, 16); tempEndPoint.Address = new IPAddress(array, num2); } else { long newAddress = (uint)((address[4] & 0xFF) | ((address[5] << 8) & 0xFF00) | ((address[6] << 16) & 0xFF0000) | (address[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(500000, 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, 500000); } } 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 unsafe 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; } 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; } finally { if (netPacket != null) { PoolRecycle(netPacket); } } 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; _udpSocketv4?.Close(); _udpSocketv6?.Close(); _udpSocketv4 = null; _udpSocketv6 = null; if (_receiveThread != null && _receiveThread != Thread.CurrentThread) { _receiveThread.Join(); } _receiveThread = 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] & 0x1Fu); } set { RawData[0] = (byte)((RawData[0] & 0xE0u) | (uint)value); } } public byte ConnectionNumber { get { return (byte)((RawData[0] & 0x60) >> 5); } set { RawData[0] = (byte)((RawData[0] & 0x9Fu) | (uint)(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] & 0x1Fu); 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; if (NetManager.UseNativeSockets) { NativeAddress = new byte[_cachedSocketAddr.Size]; for (int i = 0; i < _cachedSocketAddr.Size; i++) { NativeAddress[i] = _cachedSocketAddr[i]; } } _cachedSocketAddr = base.Serialize(); _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.PoolGetPacket(mtu); if (deliveryMethod == DeliveryMethod.Unreliable) { netPacket.Property = PacketProperty.Unreliable; return new PooledPacket(netPacket, mtu, 0); } netPacket.Property = PacketProperty.Channeled; return new PooledPacket(netPacket, mtu, (byte)((uint)(channelNumber * 4) + (uint)deliveryMethod)); } public void SendPooledPacket(PooledPacket packet, int userDataSize) { if (_connectionState == ConnectionState.Connected) { packet._packet.Size = packet.UserDataOffset + userDataSize; if (packet._packet.Property == PacketProperty.Channeled) { CreateChannel(packet._channelNumber).AddToQueue(packet._packet); } else { EnqueueUnreliable(packet._packet); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnqueueUnreliable(NetPacket packet) { lock (_unreliableChannelLock) { if (_unreliablePendingCount == _unreliableChannel.Length) { Array.Resize(ref _unreliableChannel, _unreliablePendingCount * 2); } _unreliableChannel[_unreliablePendingCount++] = packet; } } private BaseChannel CreateChannel(byte idx) { BaseChannel baseChannel = _channels[idx]; if (baseChannel != null) { return baseChannel; } switch ((DeliveryMethod)(byte)(idx % 4)) { case DeliveryMethod.ReliableUnordered: baseChannel = new ReliableChannel(this, ordered: false, idx); break; case DeliveryMethod.Sequenced: baseChannel = new SequencedChannel(this, reliable: false, idx); break; case DeliveryM
SailwindCoop.dll
Decompiled 5 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.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LiteNetLib; using LiteNetLib.Layers; using LiteNetLib.Utils; using PsychoticLab; using SailwindCoop.Avatar; using SailwindCoop.Net; using SailwindCoop.Runtime; using SailwindCoop.Sync; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")] [assembly: AssemblyCompany("SailwindCoop")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b9182b1208195d9b9f018e19a846538a308e6d10")] [assembly: AssemblyProduct("SailwindCoop")] [assembly: AssemblyTitle("SailwindCoop")] [assembly: AssemblyVersion("1.0.0.0")] namespace SailwindCoop { [BepInPlugin("com.sailwind.coop", "Sailwind LAN Co-op", "0.1.3")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.sailwind.coop"; public const string Version = "0.1.3"; internal static Plugin Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } internal static CoopConfig Cfg { get; private set; } internal static string AvatarBundlePath => Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)Instance).Info.Location), "avatar.bundle"); private void Awake() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) Instance = this; Logger = ((BaseUnityPlugin)this).Logger; Cfg = new CoopConfig(((BaseUnityPlugin)this).Config); AvatarCatalog.Initialize(); Logger.LogInfo((object)"Sailwind LAN Co-op 0.1.3 loading..."); GameObject val = new GameObject("SailwindCoop"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<CoopBehaviour>(); Logger.LogInfo((object)"Sailwind LAN Co-op ready. Use the in-game Co-op menu."); } } public sealed class CoopConfig { public readonly ConfigEntry<string> ListenIp; public readonly ConfigEntry<int> Port; public readonly ConfigEntry<string> JoinIp; public readonly ConfigEntry<string> PlayerName; public readonly ConfigEntry<int> SnapshotHz; public readonly ConfigEntry<float> InterpDelayMs; public readonly ConfigEntry<float> AvatarVerticalOffset; public readonly ConfigEntry<float> HostAvatarVerticalOffset; public readonly ConfigEntry<int> MaxClients; public readonly ConfigEntry<int> DisconnectTimeoutMs; public readonly ConfigEntry<int> UpdateTimeMs; public readonly ConfigEntry<int> PingIntervalMs; public readonly ConfigEntry<int> ConnectAttempts; public readonly ConfigEntry<int> ReconnectDelayMs; public readonly ConfigEntry<int> CoopSaveSlot; public readonly ConfigEntry<bool> ForceHostSaveOnJoin; public readonly ConfigEntry<bool> PauseHostOnJoin; public readonly ConfigEntry<bool> EnableDebugPanel; public readonly ConfigEntry<KeyCode> MenuKey; public CoopConfig(ConfigFile c) { Port = c.Bind<int>("Network", "Port", 7777, "Host UDP port."); ListenIp = c.Bind<string>("Network", "ListenIp", "0.0.0.0", "IP/interface the host listens on (0.0.0.0 = all interfaces). Applied when starting the host: a specific address accepts connections only on that interface."); JoinIp = c.Bind<string>("Network", "JoinIp", "127.0.0.1", "Host IP for the client to join."); PlayerName = c.Bind<string>("Network", "PlayerName", "Player", "Displayed player name."); SnapshotHz = c.Bind<int>("Network", "SnapshotHz", 20, "State snapshot send rate (Hz), Stage 1+."); InterpDelayMs = c.Bind<float>("Network", "InterpDelayMs", 100f, "Interpolation buffer delay (ms), Stage 1+."); AvatarVerticalOffset = c.Bind<float>("Avatar", "VerticalOffset", -0.6f, "Vertical offset of the visual bundle model relative to the networked player position. Negative values move the model down."); HostAvatarVerticalOffset = c.Bind<float>("Avatar", "HostVerticalOffset", -0.6f, "Vertical offset of the host visual bundle model. Separate because the host root pose in Sailwind is usually higher than the client pose."); MaxClients = c.Bind<int>("Server", "MaxClients", 4, "Maximum number of clients connected to the host at once (1 = single guest only). Applied on incoming connections."); DisconnectTimeoutMs = c.Bind<int>("Server", "DisconnectTimeoutMs", 5000, "Timeout (ms) without packets from a peer before it is considered disconnected."); UpdateTimeMs = c.Bind<int>("Server", "UpdateTimeMs", 15, "Internal network manager update interval (ms). Lower = more frequent polling/sending, higher CPU load."); PingIntervalMs = c.Bind<int>("Server", "PingIntervalMs", 1000, "Ping interval (ms) for latency estimation and connection keepalive."); ConnectAttempts = c.Bind<int>("Client", "ConnectAttempts", 10, "How many times the client tries to reach the host before reporting a connection error."); ReconnectDelayMs = c.Bind<int>("Client", "ReconnectDelayMs", 500, "Delay (ms) between client connection attempts."); CoopSaveSlot = c.Bind<int>("Save", "CoopSaveSlot", 5, "Save slot (0..5) where the client writes the received host world and loads from it. WARNING: the local save in this slot on the client is overwritten. Join from the main menu."); ForceHostSaveOnJoin = c.Bind<bool>("Save", "ForceHostSaveOnJoin", true, "When a client joins, the host makes a fresh save so the client receives the current world (economy/objects/position). Disable to send the latest autosave without forcing a save."); PauseHostOnJoin = c.Bind<bool>("Save", "PauseHostOnJoin", true, "While the client loads the host world, the host world is paused (timeScale=0, like the settings menu) so items/anchor/moorings/waves match the snapshot on the client. The pause is lifted when the client reports loaded, disconnects, or after a 120 s timeout."); EnableDebugPanel = c.Bind<bool>("Debug", "EnableDebugPanel", false, "Developer/test panel for gold/spawn/reputation/world tools. Keep false for public builds."); MenuKey = c.Bind<KeyCode>("UI", "MenuKey", (KeyCode)289, "Show/hide the co-op menu."); } } } namespace SailwindCoop.Sync { public sealed class AnchorSync { private readonly CoopNet _net; private readonly NetTransform _slave = new NetTransform(); private PlayerEmbarkerNew _emb; private Transform _cachedBoat; private Anchor _anchor; private Transform _anchorTr; private static FieldInfo _fBody; private static FieldInfo _fSet; private float _sendTimer; private Vector3 _lastRealPos; private long _lastRealTick; private bool _haveLast; private CoordFrame _lastFrame; private Rigidbody _anchorRb; private bool _prevKinematic; private RigidbodyInterpolation _prevInterp; private bool _slaved; private CoordFrame _curFrame; private bool _haveFrame; public float SnapshotHz = 12f; private const float DeployedLen = 0.5f; public bool HasAnchor => (Object)(object)_anchor != (Object)null; public bool ClientSet { get; private set; } public bool Slaving => _slaved; public string AnchorText { get { if ((Object)(object)_anchor == (Object)null) { return "no anchor"; } if (_net.Role == Role.Host) { if (!_anchor.IsSet()) { return "host: raised"; } return "host: dropped"; } return (_slaved ? "slaved" : "—") + (ClientSet ? " dropped" : " raised") + " [" + _curFrame.ToString() + "]"; } } public AnchorSync(CoopNet net) { _net = net; } public void Tick(float dt) { //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: 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_01b4: 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_0135: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Host || _net.State != LinkState.Connected || !CoordSpace.Ready) { return; } RefreshAnchor(); if ((Object)(object)_anchor == (Object)null || (Object)(object)_anchorTr == (Object)null) { return; } Transform cachedBoat = _cachedBoat; if ((Object)(object)cachedBoat == (Object)null) { return; } float num = 1f / Mathf.Max(1f, SnapshotHz); _sendTimer += dt; if (_sendTimer < num) { return; } _sendTimer = 0f; long serverTick = _net.Clock.ServerTick; CoordFrame coordFrame = ((!_anchor.IsSet() && !(GetRopeLen() > 0.5f)) ? CoordFrame.Boat : CoordFrame.World); Vector3 vel = Vector3.zero; Vector3 val; Quaternion rot; if (coordFrame == CoordFrame.World) { val = CoordSpace.LocalToReal(_anchorTr.position); rot = _anchorTr.rotation; if (_haveLast && _lastFrame == CoordFrame.World) { float num2 = (float)(serverTick - _lastRealTick) / 1000f; if (num2 > 0.0001f) { vel = (val - _lastRealPos) / num2; } } _lastRealPos = val; _lastRealTick = serverTick; _haveLast = true; } else { val = cachedBoat.InverseTransformPoint(_anchorTr.position); rot = Quaternion.Inverse(cachedBoat.rotation) * _anchorTr.rotation; _haveLast = false; } _lastFrame = coordFrame; _net.Broadcast(new AnchorStateMsg { Tick = serverTick, Frame = coordFrame, Pos = val, Rot = rot, Vel = vel, Set = _anchor.IsSet() }, (DeliveryMethod)4); } public void OnAnchorState(AnchorStateMsg msg, NetPeer fromPeer) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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) if (_net.Role != Role.Client) { return; } RefreshAnchor(); if ((Object)(object)_anchor == (Object)null || (Object)(object)_anchorTr == (Object)null) { return; } EnsureSlaved(); if (!_haveFrame || msg.Frame != _curFrame) { _curFrame = msg.Frame; _haveFrame = true; _slave.Clear(); ApplyFrameConverters(); } _slave.Push(msg.Tick, msg.Pos, msg.Rot, msg.Vel); ClientSet = msg.Set; if (!(_fSet != null)) { return; } try { _fSet.SetValue(_anchor, msg.Set); } catch { } } public void ApplyRemote() { if (_net.Role == Role.Client && _slaved && !((Object)(object)_anchorTr == (Object)null) && _slave.HasData && CoordSpace.Ready && !(Time.timeScale <= 0.0001f)) { _slave.Apply(_anchorTr, _net.Clock.ServerTick); } } private void EnsureSlaved() { //IL_0049: 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) if (!_slaved && !((Object)(object)_anchor == (Object)null)) { _anchorRb = GetAnchorBody(); if ((Object)(object)_anchorRb != (Object)null) { _prevKinematic = _anchorRb.isKinematic; _prevInterp = _anchorRb.interpolation; _anchorRb.isKinematic = true; _anchorRb.interpolation = (RigidbodyInterpolation)0; } _slaved = true; Plugin.Logger.LogInfo((object)("[AnchorSync] Client anchor in slave mode (rb=" + ((Object)(object)_anchorRb != (Object)null) + ")")); } } private void RestoreSlaved() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_anchorRb != (Object)null) { _anchorRb.isKinematic = _prevKinematic; _anchorRb.interpolation = _prevInterp; } _anchorRb = null; _slaved = false; _haveFrame = false; _slave.Clear(); } private void RefreshAnchor() { if ((Object)(object)_emb == (Object)null) { _emb = Object.FindObjectOfType<PlayerEmbarkerNew>(); } Transform val = (((Object)(object)_emb != (Object)null) ? _emb.debugOutCurrentBoat : null); if ((Object)(object)val == (Object)(object)_cachedBoat) { return; } RestoreSlaved(); _cachedBoat = val; _anchor = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInChildren<Anchor>(true) : null); _anchorTr = (((Object)(object)_anchor != (Object)null) ? ((Component)_anchor).transform : null); _haveLast = false; if ((Object)(object)_anchor != (Object)null) { if (_fBody == null) { _fBody = typeof(Anchor).GetField("body", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_fSet == null) { _fSet = typeof(Anchor).GetField("set", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } Plugin.Logger.LogInfo((object)("[AnchorSync] Anchor found on '" + (((Object)(object)val != (Object)null) ? ((Object)val).name : "?") + "'")); } } private Rigidbody GetAnchorBody() { try { if (_fBody != null) { object? value = _fBody.GetValue(_anchor); Rigidbody val = (Rigidbody)((value is Rigidbody) ? value : null); if (val != null && (Object)(object)val != (Object)null) { return val; } } } catch { } if (!((Object)(object)_anchorTr != (Object)null)) { return null; } return ((Component)_anchorTr).GetComponent<Rigidbody>(); } private float GetRopeLen() { try { return _anchor.GetRopeLength(); } catch { return 0f; } } private void ApplyFrameConverters() { if (_curFrame == CoordFrame.Boat && (Object)(object)_cachedBoat != (Object)null) { Transform boat = _cachedBoat; _slave.ToWorldPos = (Vector3 p) => boat.TransformPoint(p); _slave.ToWorldRot = (Quaternion r) => boat.rotation * r; } else { _slave.ToWorldPos = CoordSpace.RealToLocal; _slave.ToWorldRot = (Quaternion r) => r; } } public void Clear() { RestoreSlaved(); _cachedBoat = null; _anchor = null; _anchorTr = null; _haveLast = false; _sendTimer = 0f; ClientSet = false; } } public sealed class BoatDamageSync { private readonly CoopNet _net; private PlayerEmbarkerNew _emb; private Transform _cachedBoat; private BoatDamage _damage; private BilgePump[] _pumps = Array.Empty<BilgePump>(); private readonly Dictionary<uint, HashSet<ushort>> _heldPumpsByActor = new Dictionary<uint, HashSet<ushort>>(); private float _sendTimer; private string _lastPump = "—"; private long _lastPumpTick; private string _lastRepair = "—"; private long _lastRepairTick; private const float PumpInput = 50f; public float SnapshotHz = 4f; public static BoatDamageSync Instance { get; private set; } public string DamageText { get { if ((Object)(object)_damage == (Object)null) { return "no BoatDamage"; } string text = "—"; if (_lastPumpTick != 0L) { long num = _net.Clock.ServerTick - _lastPumpTick; if (num < 0) { num = 0L; } text = _lastPump + " " + num + "ms"; } string text2 = "—"; if (_lastRepairTick != 0L) { long num2 = _net.Clock.ServerTick - _lastRepairTick; if (num2 < 0) { num2 = 0L; } text2 = _lastRepair + " " + num2 + "ms"; } return "water=" + _damage.waterLevel.ToString("0.000") + " hull=" + _damage.hullDamage.ToString("0.000") + " oakum=" + _damage.oakum.ToString("0.0") + " pump " + ActivePumpCount() + "/" + _pumps.Length + " · " + text + " · " + text2; } } public BoatDamageSync(CoopNet net) { _net = net; Instance = this; } public void Tick(float dt) { if (_net.State == LinkState.Connected) { RefreshBoat(); if (_net.Role == Role.Host) { ApplyRemotePumps(dt); SendSnapshot(dt); } } } public void SetRemotePump(ushort index, bool down, uint actorNetId) { if (_net.Role != Role.Host) { return; } RefreshBoat(); if (index >= _pumps.Length) { Plugin.Logger.LogWarning((object)("[BoatDamageSync] Pump request #" + index + ": boat only has " + _pumps.Length)); return; } if (!_heldPumpsByActor.TryGetValue(actorNetId, out var value)) { value = new HashSet<ushort>(); _heldPumpsByActor[actorNetId] = value; } if (down) { value.Add(index); } else { value.Remove(index); } if (value.Count == 0) { _heldPumpsByActor.Remove(actorNetId); } RememberPump((down ? "in down" : "in up") + " #" + index + " p" + actorNetId); Plugin.Logger.LogInfo((object)("[BoatDamageSync] Pump #" + index + " from player " + actorNetId + ": " + (down ? "held" : "released"))); } public void ClearRemoteActor(uint actorNetId) { if (_heldPumpsByActor.Remove(actorNetId)) { Plugin.Logger.LogInfo((object)("[BoatDamageSync] Cleared pump holds for player " + actorNetId)); } } public void OnDamageState(BoatDamageStateMsg msg, NetPeer fromPeer) { if (_net.Role == Role.Client) { RefreshBoat(); if (!((Object)(object)_damage == (Object)null)) { _damage.waterLevel = Mathf.Clamp01(msg.WaterLevel); _damage.hullDamage = Mathf.Clamp01(msg.HullDamage); _damage.oakum = Mathf.Max(0f, msg.Oakum); _damage.waterIntakeChunk = Mathf.Max(0f, msg.WaterIntakeChunk); _damage.sunk = msg.Sunk; } } } public void NotifyLocalDamageAction(DamageAction action, float amount) { if (_net.Role == Role.Client && _net.State == LinkState.Connected && !(amount <= 1E-05f)) { _net.Broadcast(new DamageRequestMsg { Action = action, Amount = amount }, (DeliveryMethod)2); RememberRepair("out " + action.ToString() + " +" + amount.ToString("0.000")); } } public void OnDamageRequest(DamageRequestMsg msg, NetPeer fromPeer) { if (_net.Role != Role.Host) { return; } RefreshBoat(); if ((Object)(object)_damage == (Object)null) { return; } float num = Mathf.Max(0f, msg.Amount); if (num <= 0f) { return; } if (msg.Action == DamageAction.AddOakum) { float num2 = Mathf.Max(0f, _damage.hullDamage * _damage.waterUnitsCapacity - _damage.oakum); float num3 = Mathf.Min(num, num2); if (num3 > 0f) { BoatDamage damage = _damage; damage.oakum += num3; } RememberRepair("in oakum +" + num3.ToString("0.000")); } else if (msg.Action == DamageAction.BailWater) { float waterLevel = _damage.waterLevel; _damage.waterLevel = Mathf.Clamp01(_damage.waterLevel - num); RememberRepair("in water -" + (waterLevel - _damage.waterLevel).ToString("0.000")); } BroadcastSnapshot(); } private void SendSnapshot(float dt) { if (!((Object)(object)_damage == (Object)null)) { float num = 1f / Mathf.Max(0.5f, SnapshotHz); _sendTimer += dt; if (!(_sendTimer < num)) { _sendTimer = 0f; BroadcastSnapshot(); } } } private void BroadcastSnapshot() { if (!((Object)(object)_damage == (Object)null)) { _net.Broadcast(new BoatDamageStateMsg { Tick = _net.Clock.ServerTick, WaterLevel = _damage.waterLevel, HullDamage = _damage.hullDamage, Oakum = _damage.oakum, WaterIntakeChunk = _damage.waterIntakeChunk, Sunk = _damage.sunk }, (DeliveryMethod)4); } } private void ApplyRemotePumps(float dt) { if ((Object)(object)_damage == (Object)null || _damage.sunk || _heldPumpsByActor.Count == 0) { return; } bool flag = false; for (int i = 0; i < _pumps.Length; i++) { if (IsPumpHeld((ushort)i)) { BilgePump val = _pumps[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.damage == (Object)null)) { float waterLevel = val.damage.waterLevel; val.damage.waterLevel = Mathf.Clamp01(val.damage.waterLevel - dt * 50f * val.drainRate); RotatePumpVisual(val, dt); flag = flag || val.damage.waterLevel != waterLevel; } } } } private static void RotatePumpVisual(BilgePump pump, float dt) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)pump == (Object)null)) { float num = (((Object)(object)pump.damage != (Object)null && pump.damage.waterLevel > 0f) ? 0.75f : 2.25f); ((Component)pump).transform.Rotate(Vector3.forward, 50f * dt * 1.4f * pump.rotationSpeed * num, (Space)1); } } private bool IsPumpHeld(ushort index) { foreach (HashSet<ushort> value in _heldPumpsByActor.Values) { if (value.Contains(index)) { return true; } } return false; } private int ActivePumpCount() { int num = 0; for (int i = 0; i < _pumps.Length; i++) { if (IsPumpHeld((ushort)i)) { num++; } } return num; } private void RefreshBoat() { if ((Object)(object)_emb == (Object)null) { _emb = Object.FindObjectOfType<PlayerEmbarkerNew>(); } Transform val = (((Object)(object)_emb != (Object)null) ? _emb.debugOutCurrentBoat : null); if (!((Object)(object)val == (Object)(object)_cachedBoat)) { _cachedBoat = val; _heldPumpsByActor.Clear(); _sendTimer = 0f; if ((Object)(object)val == (Object)null) { _damage = null; _pumps = Array.Empty<BilgePump>(); return; } _damage = ((Component)val).GetComponent<BoatDamage>() ?? ((Component)val).GetComponentInParent<BoatDamage>() ?? ((Component)val).GetComponentInChildren<BoatDamage>(true); _pumps = ((Component)val).GetComponentsInChildren<BilgePump>(true); Plugin.Logger.LogInfo((object)("[BoatDamageSync] Boat changed: damage=" + ((Object)(object)_damage != (Object)null) + ", pumps=" + _pumps.Length + " ('" + ((Object)val).name + "')")); } } private void RememberPump(string text) { _lastPump = text; _lastPumpTick = _net.Clock.ServerTick; } private void RememberRepair(string text) { _lastRepair = text; _lastRepairTick = _net.Clock.ServerTick; } public void Clear() { _cachedBoat = null; _damage = null; _pumps = Array.Empty<BilgePump>(); _heldPumpsByActor.Clear(); _sendTimer = 0f; _lastPump = "—"; _lastPumpTick = 0L; _lastRepair = "—"; _lastRepairTick = 0L; } } public static class BoatDamagePatches { private struct WaterBailState { public float Water; public float Amount; public float Health; } public static void Apply(Harmony harmony) { bool flag = TryPatch(harmony, typeof(HullDamageButton), "OnItemClick", new Type[1] { typeof(PickupableItem) }, "PreHullOakum", "PostHullOakum"); bool flag2 = TryPatch(harmony, typeof(BoatDamageWaterButton), "OnItemClick", new Type[1] { typeof(PickupableItem) }, "PreWaterBail", "PostWaterBail"); bool flag3 = TryPatch(harmony, typeof(ShipItemOakum), "OnAltActivate", Type.EmptyTypes, "PreOakumAlt", "PostOakumAlt"); Plugin.Logger.LogInfo((object)("[BoatDamagePatches] Damage patches: HullOakum=" + flag + ", WaterBail=" + flag2 + ", OakumAlt=" + flag3)); PatchHealth.Report("Damage", (flag ? 1 : 0) + (flag2 ? 1 : 0) + (flag3 ? 1 : 0), 3); } private static bool TryPatch(Harmony harmony, Type type, string method, Type[] args, string prefixName, string postfixName) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown try { MethodInfo method2 = type.GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, args, null); if (method2 == null) { Plugin.Logger.LogWarning((object)("[BoatDamagePatches] Not found " + type.Name + "." + method)); return false; } HarmonyMethod val = new HarmonyMethod(typeof(BoatDamagePatches).GetMethod(prefixName, BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BoatDamagePatches).GetMethod(postfixName, BindingFlags.Static | BindingFlags.NonPublic)); harmony.Patch((MethodBase)method2, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BoatDamagePatches] " + type.Name + "." + method + ": " + ex.Message)); return false; } } private static void PreHullOakum(HullDamageButton __instance, out float __state) { __state = ReadOakum(GetHullDamage(__instance)); } private static void PostHullOakum(HullDamageButton __instance, float __state) { try { float num = ReadOakum(GetHullDamage(__instance)) - __state; if (num > 1E-05f) { BoatDamageSync.Instance?.NotifyLocalDamageAction(DamageAction.AddOakum, num); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BoatDamagePatches] PostHullOakum: " + ex.Message)); } } private static void PreWaterBail(BoatDamageWaterButton __instance, PickupableItem __0, out WaterBailState __state) { __state = new WaterBailState { Water = ReadWater(GetWaterDamage(__instance)) }; ShipItemBottle val = (ShipItemBottle)(object)((__0 is ShipItemBottle) ? __0 : null); if ((Object)(object)val != (Object)null) { __state.Amount = ((ShipItem)val).amount; __state.Health = ((ShipItem)val).health; } } private static void PostWaterBail(BoatDamageWaterButton __instance, PickupableItem __0, WaterBailState __state) { try { float num = ReadWater(GetWaterDamage(__instance)); float num2 = __state.Water - num; ShipItemBottle val = (ShipItemBottle)(object)((__0 is ShipItemBottle) ? __0 : null); bool flag = (Object)(object)val != (Object)null && (Mathf.Abs(((ShipItem)val).amount - __state.Amount) > 0.0001f || Mathf.Abs(((ShipItem)val).health - __state.Health) > 0.0001f); if (num2 > 1E-05f) { BoatDamageSync.Instance?.NotifyLocalDamageAction(DamageAction.BailWater, num2); } if (flag) { ItemSync.Instance?.NotifyHeldItemStateChanged((ShipItem)(object)val, "bail-water"); } if (num2 > 1E-05f || flag) { Plugin.Logger.LogInfo((object)("[BoatDamagePatches] WaterBail item=" + (((Object)(object)__0 != (Object)null) ? ((object)__0).GetType().Name : "null") + " delta=" + num2.ToString("0.####") + " amount " + __state.Amount.ToString("0.##") + "->" + (((Object)(object)val != (Object)null) ? ((ShipItem)val).amount.ToString("0.##") : "?") + " health " + __state.Health.ToString("0.##") + "->" + (((Object)(object)val != (Object)null) ? ((ShipItem)val).health.ToString("0.##") : "?"))); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BoatDamagePatches] PostWaterBail: " + ex.Message)); } } private static void PreOakumAlt(ShipItemOakum __instance, out float __state) { __state = ReadOakum(CurrentBoatDamage()); } private static void PostOakumAlt(ShipItemOakum __instance, float __state) { try { float num = ReadOakum(CurrentBoatDamage()) - __state; if (num > 1E-05f) { BoatDamageSync.Instance?.NotifyLocalDamageAction(DamageAction.AddOakum, num); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BoatDamagePatches] PostOakumAlt: " + ex.Message)); } } private static BoatDamage GetHullDamage(HullDamageButton button) { HullDamageTexture val = (((Object)(object)button != (Object)null) ? ((Component)button).GetComponent<HullDamageTexture>() : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.damage; } private static BoatDamage GetWaterDamage(BoatDamageWaterButton button) { BoatDamageWater val = (((Object)(object)button != (Object)null) ? ((Component)button).GetComponent<BoatDamageWater>() : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.damage; } private static BoatDamage CurrentBoatDamage() { try { return ((Object)(object)GameState.currentBoat != (Object)null && (Object)(object)GameState.currentBoat.parent != (Object)null) ? ((Component)GameState.currentBoat.parent).GetComponent<BoatDamage>() : null; } catch { return null; } } private static float ReadOakum(BoatDamage damage) { if (!((Object)(object)damage != (Object)null)) { return 0f; } return damage.oakum; } private static float ReadWater(BoatDamage damage) { if (!((Object)(object)damage != (Object)null)) { return 0f; } return damage.waterLevel; } } public static class BoatLocator { public const ushort NoBoat = ushort.MaxValue; public static List<Transform> FindBoats() { HashSet<Transform> hashSet = new HashSet<Transform>(); BoatEmbarkCollider[] array = Object.FindObjectsOfType<BoatEmbarkCollider>(); foreach (BoatEmbarkCollider val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).transform == (Object)null) && !((Object)(object)((Component)val).transform.parent == (Object)null)) { Transform parent = ((Component)val).transform.parent; if (IsNetworkBoat(parent)) { hashSet.Add(parent); } } } List<Transform> list = new List<Transform>(hashSet); list.Sort(delegate(Transform a, Transform b) { int num = (IsPurchasable(a) ? 1 : 0); int num2 = (IsPurchasable(b) ? 1 : 0); return (num != num2) ? (num - num2) : string.CompareOrdinal(PathOf(a), PathOf(b)); }); return list; } private static bool IsPurchasable(Transform worldBoat) { if ((Object)(object)worldBoat == (Object)null) { return false; } return (Object)(object)((Component)(((Object)(object)worldBoat.parent != (Object)null) ? worldBoat.parent : worldBoat)).GetComponent("PurchasableBoat") != (Object)null; } private static bool IsNetworkBoat(Transform worldBoat) { if ((Object)(object)worldBoat == (Object)null) { return false; } Transform obj = (((Object)(object)worldBoat.parent != (Object)null) ? worldBoat.parent : worldBoat); SaveableObject component = ((Component)obj).GetComponent<SaveableObject>(); Component component2 = ((Component)obj).GetComponent("BoatProbes"); if ((Object)(object)component != (Object)null && (Object)(object)component2 != (Object)null && !component.extraSetting) { return false; } return true; } public static Transform FindByIndex(ushort index) { if (index == ushort.MaxValue) { return null; } List<Transform> list = FindBoats(); if (index >= list.Count) { return null; } return list[index]; } public static ushort IndexOf(Transform boat) { if ((Object)(object)boat == (Object)null) { return ushort.MaxValue; } List<Transform> list = FindBoats(); for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] == (Object)(object)boat) { return (ushort)i; } } return ushort.MaxValue; } public static string PathOf(Transform t) { if ((Object)(object)t == (Object)null) { return ""; } string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } public sealed class BoatSync { private sealed class HostBoat { public ushort Index; public uint NetId; public Transform Boat; public Vector3 LastRealPos; public long LastRealTick; public bool HaveLast; } private sealed class ClientBoat { public ushort Index; public Transform Boat; public readonly NetTransform Net = new NetTransform(); public Rigidbody Rb; public bool PrevKinematic; public RigidbodyInterpolation PrevInterp; public Component PhysSwitcher; public bool PrevPaused; public bool PrevSwitcherEnabled; } private readonly CoopNet _net; private readonly Dictionary<ushort, HostBoat> _hostBoats = new Dictionary<ushort, HostBoat>(); private readonly Dictionary<ushort, ClientBoat> _clientBoats = new Dictionary<ushort, ClientBoat>(); private float _sendTimer; private float _refreshTimer; private uint _firstBoatNetId; private static Type _physSwitcherType; public float InterpDelayMs = 100f; public int SnapshotHz = 20; public bool IsSlaving => _clientBoats.Count > 0; public uint BoatNetId => _firstBoatNetId; public int BoatCount { get { if (_net.Role != Role.Host) { return _clientBoats.Count; } return _hostBoats.Count; } } public BoatSync(CoopNet net) { _net = net; } public void Tick(float dt) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Host || _net.State != LinkState.Connected || !CoordSpace.Ready) { return; } RefreshHostBoats(dt); if (_hostBoats.Count == 0) { return; } float num = 1f / (float)Mathf.Max(1, SnapshotHz); _sendTimer += dt; if (_sendTimer < num) { return; } _sendTimer = 0f; long serverTick = _net.Clock.ServerTick; foreach (HostBoat value in _hostBoats.Values) { if ((Object)(object)value.Boat == (Object)null) { continue; } Vector3 val = CoordSpace.LocalToReal(value.Boat.position); Vector3 realVel = Vector3.zero; if (value.HaveLast) { float num2 = (float)(serverTick - value.LastRealTick) / 1000f; if (num2 > 0.0001f) { realVel = (val - value.LastRealPos) / num2; } } value.LastRealPos = val; value.LastRealTick = serverTick; value.HaveLast = true; _net.Broadcast(new BoatStateMsg { NetId = value.NetId, BoatIndex = value.Index, Tick = serverTick, RealPos = val, Rot = value.Boat.rotation, RealVel = realVel }, (DeliveryMethod)4); } } public void OnBoatState(BoatStateMsg msg, NetPeer fromPeer) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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) if (_net.Role == Role.Client) { Transform val = BoatLocator.FindByIndex(msg.BoatIndex); if (!((Object)(object)val == (Object)null)) { ClientBoat clientBoat = EnsureClientBoat(msg.BoatIndex, msg.NetId, val); clientBoat.Net.InterpDelayMs = InterpDelayMs; clientBoat.Net.Push(msg.Tick, msg.RealPos, msg.Rot, msg.RealVel); } } } public void ApplyRemote() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Client || !CoordSpace.Ready || Time.timeScale <= 0.0001f) { return; } foreach (ClientBoat value in _clientBoats.Values) { if (!((Object)(object)value.Boat == (Object)null) && value.Net.HasData) { if ((Object)(object)value.Rb != (Object)null && (int)value.Rb.interpolation != 0) { value.Rb.interpolation = (RigidbodyInterpolation)0; } value.Net.Apply(value.Boat, _net.Clock.ServerTick); } } } public Transform GetBoatByIndex(ushort index) { if (_net.Role == Role.Client && _clientBoats.TryGetValue(index, out var value) && (Object)(object)value.Boat != (Object)null) { return value.Boat; } if (_net.Role == Role.Host && _hostBoats.TryGetValue(index, out var value2) && (Object)(object)value2.Boat != (Object)null) { return value2.Boat; } return BoatLocator.FindByIndex(index); } private void RefreshHostBoats(float dt) { _refreshTimer += dt; if (_refreshTimer < 1f && _hostBoats.Count > 0) { return; } _refreshTimer = 0f; List<Transform> list = BoatLocator.FindBoats(); HashSet<ushort> hashSet = new HashSet<ushort>(); for (int i = 0; i < list.Count && i <= 65534; i++) { Transform val = list[i]; if ((Object)(object)val == (Object)null) { continue; } ushort num = (ushort)i; hashSet.Add(num); if (!_hostBoats.TryGetValue(num, out var value)) { value = new HostBoat { Index = num, NetId = _net.Registry.AllocateId(), Boat = val }; _hostBoats[num] = value; _net.Registry.Register(value.NetId, NetObjKind.Boat, 0u, val); if (_firstBoatNetId == 0) { _firstBoatNetId = value.NetId; } Plugin.Logger.LogInfo((object)("[BoatSync] Boat #" + num + " registered NetId=" + value.NetId + " ('" + BoatLocator.PathOf(val) + "')")); } else if ((Object)(object)value.Boat != (Object)(object)val) { value.Boat = val; value.HaveLast = false; _net.Registry.Register(value.NetId, NetObjKind.Boat, 0u, val); Plugin.Logger.LogInfo((object)("[BoatSync] Boat #" + num + " rebound ('" + BoatLocator.PathOf(val) + "')")); } } List<ushort> list2 = new List<ushort>(); foreach (KeyValuePair<ushort, HostBoat> hostBoat in _hostBoats) { if (!hashSet.Contains(hostBoat.Key)) { list2.Add(hostBoat.Key); } } foreach (ushort item in list2) { _net.Registry.Remove(_hostBoats[item].NetId); _hostBoats.Remove(item); } } private ClientBoat EnsureClientBoat(ushort index, uint netId, Transform boat) { //IL_007a: 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) if (_clientBoats.TryGetValue(index, out var value)) { if ((Object)(object)value.Boat == (Object)(object)boat) { return value; } RestoreClientBoat(value); _clientBoats.Remove(index); } value = new ClientBoat { Index = index, Boat = boat }; value.Rb = ((Component)boat).GetComponent<Rigidbody>(); if ((Object)(object)value.Rb != (Object)null) { value.PrevKinematic = value.Rb.isKinematic; value.PrevInterp = value.Rb.interpolation; value.Rb.isKinematic = true; value.Rb.interpolation = (RigidbodyInterpolation)0; } TrySetPhysicsPaused(value, paused: true); TrySetSwitcherEnabled(value, slave: true); _clientBoats[index] = value; _net.Registry.Register(netId, NetObjKind.Boat, 0u, boat); if (_firstBoatNetId == 0) { _firstBoatNetId = netId; } Plugin.Logger.LogInfo((object)("[BoatSync] Client boat #" + index + " in slave mode: NetId=" + netId + " ('" + BoatLocator.PathOf(boat) + "'), rb=" + ((Object)(object)value.Rb != (Object)null) + ", physSwitcher=" + ((Object)(object)value.PhysSwitcher != (Object)null))); return value; } private void RestoreClientBoat(ClientBoat cb) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (cb != null) { if ((Object)(object)cb.Rb != (Object)null) { cb.Rb.isKinematic = cb.PrevKinematic; cb.Rb.interpolation = cb.PrevInterp; } TrySetSwitcherEnabled(cb, slave: false); TrySetPhysicsPaused(cb, paused: false, restore: true); cb.Net.Clear(); cb.Rb = null; cb.PhysSwitcher = null; cb.Boat = null; } } private void TrySetSwitcherEnabled(ClientBoat cb, bool slave) { try { if (cb == null || (Object)(object)cb.Boat == (Object)null) { return; } if ((Object)(object)cb.PhysSwitcher == (Object)null) { if (_physSwitcherType == null) { _physSwitcherType = Type.GetType("BoatPhysicsSwitcher, Assembly-CSharp"); } if (_physSwitcherType == null) { return; } cb.PhysSwitcher = ((Component)cb.Boat).GetComponentInChildren(_physSwitcherType); } Component physSwitcher = cb.PhysSwitcher; Behaviour val = (Behaviour)(object)((physSwitcher is Behaviour) ? physSwitcher : null); if (!((Object)(object)val == (Object)null)) { if (slave) { cb.PrevSwitcherEnabled = val.enabled; val.enabled = false; } else { val.enabled = cb.PrevSwitcherEnabled; } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BoatSync] BoatPhysicsSwitcher enable-toggle failed: " + ex.Message)); } } private void TrySetPhysicsPaused(ClientBoat cb, bool paused, bool restore = false) { try { if (cb == null || (Object)(object)cb.Boat == (Object)null) { return; } if (_physSwitcherType == null) { _physSwitcherType = Type.GetType("BoatPhysicsSwitcher, Assembly-CSharp"); } if (_physSwitcherType == null) { return; } if ((Object)(object)cb.PhysSwitcher == (Object)null) { cb.PhysSwitcher = ((Component)cb.Boat).GetComponentInChildren(_physSwitcherType); } if ((Object)(object)cb.PhysSwitcher == (Object)null) { return; } FieldInfo field = _physSwitcherType.GetField("paused"); PropertyInfo propertyInfo = ((field == null) ? _physSwitcherType.GetProperty("paused") : null); if (!(field == null) || !(propertyInfo == null)) { if (!restore) { cb.PrevPaused = ((field != null) ? ((bool)field.GetValue(cb.PhysSwitcher)) : ((bool)propertyInfo.GetValue(cb.PhysSwitcher, null))); } bool flag = (restore ? cb.PrevPaused : paused); if (field != null) { field.SetValue(cb.PhysSwitcher, flag); } else { propertyInfo.SetValue(cb.PhysSwitcher, flag, null); } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BoatSync] BoatPhysicsSwitcher.paused unavailable: " + ex.Message)); } } public void Clear() { foreach (ClientBoat value in _clientBoats.Values) { RestoreClientBoat(value); } _clientBoats.Clear(); _hostBoats.Clear(); _firstBoatNetId = 0u; _sendTimer = 0f; _refreshTimer = 0f; } } public sealed class ControlsSync { private struct Node { public Transform T; public Rigidbody Rb; } private readonly CoopNet _net; private PlayerEmbarkerNew _emb; private Transform _cachedBoat; private RopeController[] _ropes = Array.Empty<RopeController>(); private GPButtonRopeWinch[] _winches = Array.Empty<GPButtonRopeWinch>(); private Node[] _nodes = Array.Empty<Node>(); private GPButtonSteeringWheel[] _wheels = Array.Empty<GPButtonSteeringWheel>(); private float[] _steerLastSent = Array.Empty<float>(); private float _steerTimer; private MethodInfo _miApplyRudder; private const float SteerEps = 0.01f; private RopeControllerSteeringWheel _steerRope; private RudderNew _rudderNew; private Rudder _rudderOld; private Quaternion[] _targetRots = Array.Empty<Quaternion>(); private bool _haveTargets; private readonly List<KeyValuePair<Rigidbody, bool>> _kinematicSaved = new List<KeyValuePair<Rigidbody, bool>>(); private float[] _hostLen = Array.Empty<float>(); private long[] _localUntil = Array.Empty<long>(); private float[] _lastSentLen = Array.Empty<float>(); private float _reqTimer; private const float LocalHoldMs = 600f; private const float LenEps = 1E-05f; private int _lastReqIndex = -1; private float _lastReqLength; private bool _lastReqHasWinchRotation; private bool _lastReqIncoming; private long _lastReqTick; private float _sendTimer; private bool _warnedMismatch; private GoPointer _gp; private FieldInfo _fSticky; public float ControlHz = 12f; public float RotSmoothing = 14f; private FieldInfo _fRudderNewAngle; private FieldInfo _fRudderOldAngle; public int RopeCount => _ropes.Length; public int NodeCount => _nodes.Length; public int WinchCount => _winches.Length; public string SteeringText { get { if (_wheels.Length == 0) { return "no wheel"; } string text = (((Object)(object)_steerRope != (Object)null) ? ((RopeController)_steerRope).currentLength.ToString("0.000") : "—"); string text2 = RudderAngleText(); return _wheels.Length + " pcs len=" + text + " angle=" + text2; } } public string LastControlRequestText { get { if (_lastReqIndex < 0) { return "—"; } long num = _net.Clock.ServerTick - _lastReqTick; if (num < 0) { num = 0L; } string text = (_lastReqIncoming ? "in" : "out"); string text2 = (_lastReqHasWinchRotation ? "+handle" : "no handle"); return text + " #" + _lastReqIndex + " len=" + _lastReqLength.ToString("0.00") + " " + text2 + " " + num + "ms"; } } public ControlsSync(CoopNet net) { _net = net; } private string RudderAngleText() { try { if ((Object)(object)_rudderNew != (Object)null) { if (_fRudderNewAngle == null) { _fRudderNewAngle = typeof(RudderNew).GetField("currentAngle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_fRudderNewAngle != null) { return ((float)_fRudderNewAngle.GetValue(_rudderNew)).ToString("0.0"); } } if ((Object)(object)_rudderOld != (Object)null) { if (_fRudderOldAngle == null) { _fRudderOldAngle = typeof(Rudder).GetField("currentAngle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_fRudderOldAngle != null) { return ((float)_fRudderOldAngle.GetValue(_rudderOld)).ToString("0.0"); } } } catch { } return "—"; } public void Tick(float dt) { //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Host || _net.State != LinkState.Connected) { return; } RefreshNodes(); if (_ropes.Length == 0 && _nodes.Length == 0) { return; } float num = 1f / Mathf.Max(1f, ControlHz); _sendTimer += dt; if (!(_sendTimer < num)) { _sendTimer = 0f; float[] array = new float[_ropes.Length]; for (int i = 0; i < _ropes.Length; i++) { array[i] = (((Object)(object)_ropes[i] != (Object)null) ? _ropes[i].currentLength : 0f); } Quaternion[] array2 = (Quaternion[])(object)new Quaternion[_nodes.Length]; for (int j = 0; j < _nodes.Length; j++) { array2[j] = (((Object)(object)_nodes[j].T != (Object)null) ? _nodes[j].T.localRotation : Quaternion.identity); } _net.Broadcast(new ControlStateMsg { Tick = _net.Clock.ServerTick, Lengths = array, Rotations = array2 }, (DeliveryMethod)4); } } public void OnControlState(ControlStateMsg msg, NetPeer fromPeer) { if (_net.Role != Role.Client) { return; } RefreshNodes(); if (_ropes.Length != 0) { if (msg.Lengths.Length != _ropes.Length) { WarnMismatch("ropes", msg.Lengths.Length, _ropes.Length); } else { long serverTick = _net.Clock.ServerTick; for (int i = 0; i < _ropes.Length; i++) { RopeController val = _ropes[i]; if (!((Object)(object)val == (Object)null)) { _hostLen[i] = msg.Lengths[i]; if (serverTick >= _localUntil[i] && val.currentLength != msg.Lengths[i]) { val.currentLength = msg.Lengths[i]; val.changed = true; } } } } } if (_nodes.Length != 0) { if (msg.Rotations.Length != _nodes.Length) { WarnMismatch("nodes", msg.Rotations.Length, _nodes.Length); return; } EnsureKinematic(); _targetRots = msg.Rotations; _haveTargets = true; } } public void ApplyClient(float dt) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Client) { return; } ForwardLocalRopeChanges(dt); GoPointerButton heldBtn = HeldButton(); ForwardSteering(dt, heldBtn); if (!_haveTargets || _nodes.Length == 0 || _targetRots.Length != _nodes.Length) { return; } float num = 1f - Mathf.Exp((0f - RotSmoothing) * dt); for (int i = 0; i < _nodes.Length; i++) { Transform t = _nodes[i].T; if (!((Object)(object)t == (Object)null)) { t.localRotation = Quaternion.Slerp(t.localRotation, _targetRots[i], num); } } } private void ForwardSteering(float dt, GoPointerButton heldBtn) { _steerTimer += dt; float num = 1f / Mathf.Max(1f, ControlHz); if (_steerTimer < num) { return; } _steerTimer = 0f; GPButtonSteeringWheel val = (GPButtonSteeringWheel)(object)((heldBtn is GPButtonSteeringWheel) ? heldBtn : null); if ((Object)(object)val == (Object)null) { return; } int num2 = Array.IndexOf(_wheels, val); if (num2 < 0) { return; } float currentInput = val.currentInput; if (num2 >= _steerLastSent.Length || !(Mathf.Abs(currentInput - _steerLastSent[num2]) < 0.01f)) { _net.Broadcast(new SteerRequestMsg { Index = (ushort)num2, Input = currentInput }, (DeliveryMethod)2); if (num2 < _steerLastSent.Length) { _steerLastSent[num2] = currentInput; } } } public void OnSteerRequest(SteerRequestMsg msg, NetPeer fromPeer) { if (_net.Role != Role.Host) { return; } RefreshNodes(); int index = msg.Index; if (index < 0 || index >= _wheels.Length) { return; } GPButtonSteeringWheel val = _wheels[index]; if ((Object)(object)val == (Object)null) { return; } val.currentInput = msg.Input; try { if (_miApplyRudder == null) { _miApplyRudder = typeof(GPButtonSteeringWheel).GetMethod("ApplyRudderRotation", BindingFlags.Instance | BindingFlags.NonPublic); } _miApplyRudder?.Invoke(val, null); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[ControlsSync] ApplyRudderRotation failed: " + ex.Message)); } } private GoPointerButton HeldButton() { try { if ((Object)(object)_gp == (Object)null) { _gp = Object.FindObjectOfType<GoPointer>(); } if ((Object)(object)_gp == (Object)null) { return null; } if (_fSticky == null) { _fSticky = typeof(GoPointer).GetField("stickyClickedButton", BindingFlags.Instance | BindingFlags.NonPublic); } return (GoPointerButton)((_fSticky != null) ? /*isinst with value type is only supported in some contexts*/: null); } catch { return null; } } private void ForwardLocalRopeChanges(float dt) { //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) if (_ropes.Length == 0 || _hostLen.Length != _ropes.Length) { return; } long serverTick = _net.Clock.ServerTick; for (int i = 0; i < _ropes.Length; i++) { RopeController val = _ropes[i]; if (!((Object)(object)val == (Object)null) && Mathf.Abs(val.currentLength - _hostLen[i]) > 1E-05f) { _localUntil[i] = serverTick + 600; } } _reqTimer += dt; float num = 1f / Mathf.Max(1f, ControlHz); if (_reqTimer < num) { return; } _reqTimer = 0f; for (int j = 0; j < _ropes.Length; j++) { RopeController val2 = _ropes[j]; if (!((Object)(object)val2 == (Object)null) && serverTick < _localUntil[j] && Mathf.Abs(val2.currentLength - _lastSentLen[j]) > 1E-05f) { ControlRequestMsg controlRequestMsg = new ControlRequestMsg { Index = (ushort)j, Length = val2.currentLength }; GPButtonRopeWinch val3 = FindWinchForRope(val2); if ((Object)(object)val3 != (Object)null) { controlRequestMsg.HasWinchRotation = true; controlRequestMsg.WinchRotation = ((Component)val3).transform.localRotation; } _net.Broadcast(controlRequestMsg, (DeliveryMethod)2); RememberControlRequest(j, val2.currentLength, controlRequestMsg.HasWinchRotation, incoming: false); _lastSentLen[j] = val2.currentLength; } } } public void OnControlRequest(ControlRequestMsg msg, NetPeer fromPeer) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Host) { return; } RefreshNodes(); int index = msg.Index; if (index < 0 || index >= _ropes.Length) { return; } RopeController val = _ropes[index]; if ((Object)(object)val == (Object)null) { return; } RememberControlRequest(index, msg.Length, msg.HasWinchRotation, incoming: true); if (val.currentLength != msg.Length) { val.currentLength = msg.Length; val.changed = true; } if (msg.HasWinchRotation) { GPButtonRopeWinch val2 = FindWinchForRope(val); if ((Object)(object)val2 != (Object)null) { ((Component)val2).transform.localRotation = msg.WinchRotation; } } } private void RefreshNodes() { if ((Object)(object)_emb == (Object)null) { _emb = Object.FindObjectOfType<PlayerEmbarkerNew>(); } Transform val = (((Object)(object)_emb != (Object)null) ? _emb.debugOutCurrentBoat : null); if ((Object)(object)val == (Object)null) { val = BoatLocator.FindByIndex(0); } if ((Object)(object)val == (Object)(object)_cachedBoat) { return; } RestoreKinematic(); _cachedBoat = val; _haveTargets = false; _warnedMismatch = false; _steerRope = null; _rudderNew = null; _rudderOld = null; if ((Object)(object)val == (Object)null) { _ropes = Array.Empty<RopeController>(); _winches = Array.Empty<GPButtonRopeWinch>(); _wheels = Array.Empty<GPButtonSteeringWheel>(); _nodes = Array.Empty<Node>(); _hostLen = Array.Empty<float>(); _localUntil = Array.Empty<long>(); _lastSentLen = Array.Empty<float>(); return; } _ropes = ((Component)val).GetComponentsInChildren<RopeController>(true); _winches = ((Component)val).GetComponentsInChildren<GPButtonRopeWinch>(true); _wheels = ((Component)val).GetComponentsInChildren<GPButtonSteeringWheel>(true); _steerLastSent = new float[_wheels.Length]; for (int i = 0; i < _wheels.Length; i++) { _steerLastSent[i] = (((Object)(object)_wheels[i] != (Object)null) ? _wheels[i].currentInput : 0f); } _steerRope = ((Component)val).GetComponentInChildren<RopeControllerSteeringWheel>(true); _rudderNew = ((Component)val).GetComponentInChildren<RudderNew>(true); _rudderOld = ((Component)val).GetComponentInChildren<Rudder>(true); _hostLen = new float[_ropes.Length]; _localUntil = new long[_ropes.Length]; _lastSentLen = new float[_ropes.Length]; for (int j = 0; j < _ropes.Length; j++) { float num = (((Object)(object)_ropes[j] != (Object)null) ? _ropes[j].currentLength : 0f); _hostLen[j] = num; _lastSentLen[j] = num; } List<Node> list = new List<Node>(); HingeJoint[] componentsInChildren = ((Component)val).GetComponentsInChildren<HingeJoint>(true); foreach (HingeJoint val2 in componentsInChildren) { list.Add(new Node { T = ((Component)val2).transform, Rb = ((Component)val2).GetComponent<Rigidbody>() }); } _nodes = list.ToArray(); Plugin.Logger.LogInfo((object)("[ControlsSync] Boat changed: ropes=" + _ropes.Length + ", nodes=" + _nodes.Length + ", wheels=" + _wheels.Length + (((Object)(object)val != (Object)null) ? (" ('" + ((Object)val).name + "')") : ""))); } private void EnsureKinematic() { if (_kinematicSaved.Count > 0) { return; } Node[] nodes = _nodes; for (int i = 0; i < nodes.Length; i++) { Node node = nodes[i]; if (!((Object)(object)node.Rb == (Object)null)) { _kinematicSaved.Add(new KeyValuePair<Rigidbody, bool>(node.Rb, node.Rb.isKinematic)); node.Rb.isKinematic = true; } } } private void RestoreKinematic() { foreach (KeyValuePair<Rigidbody, bool> item in _kinematicSaved) { if ((Object)(object)item.Key != (Object)null) { item.Key.isKinematic = item.Value; } } _kinematicSaved.Clear(); } private void WarnMismatch(string what, int host, int client) { if (!_warnedMismatch) { _warnedMismatch = true; Plugin.Logger.LogWarning((object)("[ControlsSync] Count mismatch for " + what + ": host=" + host + ", client=" + client + " - this part is not applied")); } } private GPButtonRopeWinch FindWinchForRope(RopeController rope) { if ((Object)(object)rope == (Object)null) { return null; } for (int i = 0; i < _winches.Length; i++) { GPButtonRopeWinch val = _winches[i]; if ((Object)(object)val != (Object)null && (Object)(object)val.rope == (Object)(object)rope) { return val; } } return null; } private void RememberControlRequest(int index, float length, bool hasWinchRotation, bool incoming) { _lastReqIndex = index; _lastReqLength = length; _lastReqHasWinchRotation = hasWinchRotation; _lastReqIncoming = incoming; _lastReqTick = _net.Clock.ServerTick; } public void Clear() { RestoreKinematic(); _cachedBoat = null; _ropes = Array.Empty<RopeController>(); _winches = Array.Empty<GPButtonRopeWinch>(); _wheels = Array.Empty<GPButtonSteeringWheel>(); _steerLastSent = Array.Empty<float>(); _steerTimer = 0f; _steerRope = null; _rudderNew = null; _rudderOld = null; _nodes = Array.Empty<Node>(); _targetRots = Array.Empty<Quaternion>(); _hostLen = Array.Empty<float>(); _localUntil = Array.Empty<long>(); _lastSentLen = Array.Empty<float>(); _haveTargets = false; _sendTimer = 0f; _reqTimer = 0f; _lastReqIndex = -1; _lastReqLength = 0f; _lastReqHasWinchRotation = false; _lastReqIncoming = false; _lastReqTick = 0L; _warnedMismatch = false; } } public static class CoopProfile { public static string ProfilePath => Path.Combine(Application.persistentDataPath, "coop_profile.dat"); public static bool Exists() { try { return File.Exists(ProfilePath); } catch { return false; } } public static bool SaveFromGame() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown try { SaveContainer val = new SaveContainer(); FillCharacterFromGame(val); using (FileStream serializationStream = File.Create(ProfilePath)) { new BinaryFormatter().Serialize(serializationStream, val); } Plugin.Logger.LogInfo((object)("[CoopProfile] Character profile saved: " + ProfilePath)); return true; } catch (Exception ex) { Plugin.Logger.LogError((object)("[CoopProfile] Failed to save profile: " + ex)); return false; } } private static void FillCharacterFromGame(SaveContainer c) { c.playerGold = 0; Try("currency", delegate { c.playerCurrency = PlayerGold.currency; c.currentCurrency = GameState.currentCurrency; }); Try("reputation", delegate { c.playerReputation = PlayerReputation.GetSaveData(); }); Try("knownPrices", delegate { c.playerKnownPrices = GameState.playerKnownPrices; }); Try("tradeReceipts", delegate { c.tradeReceipts = TradeReceiptsUI.instance.GetData(); }); Try("quests", delegate { c.quests = Quests.instance.currentQuests; }); Try("needs", delegate { c.food = PlayerNeeds.food; c.foodDebt = PlayerNeeds.foodDebt; c.water = PlayerNeeds.water; c.sleep = PlayerNeeds.sleep; c.sleepDebt = PlayerNeeds.sleepDebt; c.vitamins = PlayerNeeds.vitamins; c.protein = PlayerNeeds.protein; c.alcohol = PlayerNeeds.alcohol; }); Try("tobacco", delegate { PlayerTobacco instance = PlayerTobacco.instance; c.tobaccoWhite = instance.white; c.tobaccoGreen = instance.green; c.tobaccoBlack = instance.black; c.tobaccoBrown = instance.brown; }); Try("missions", delegate { Mission[] missions = PlayerMissions.missions; c.savedMissions = (SaveMissionData[])(object)new SaveMissionData[missions.Length]; for (int i = 0; i < missions.Length; i++) { if (missions[i] != null) { c.savedMissions[i] = missions[i].PrepareSaveData(); } } }); Try("loggedMissions", delegate { c.loggedMissions = MissionLog.instance.GetLogSaveData(); }); Try("dayLogs", delegate { int num = ((PlayerGold.currency != null) ? PlayerGold.currency.Length : 0); c.currencyDayLogs = new DaySheet[num][]; for (int i = 0; i < num; i++) { c.currencyDayLogs[i] = DayLogs.instance.dayLogs[i].GetSaveData(); } }); Try("belt", delegate { List<SavePrefabData> list = new List<SavePrefabData>(); GPButtonInventorySlot[] inventorySlots = GPButtonInventorySlot.inventorySlots; if (inventorySlots != null) { GPButtonInventorySlot[] array = inventorySlots; foreach (GPButtonInventorySlot val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.currentItem == (Object)null)) { SaveablePrefab component = ((Component)val.currentItem).GetComponent<SaveablePrefab>(); if (!((Object)(object)component == (Object)null)) { SavePrefabData val2 = component.PrepareSaveData(); if (val2 != null) { list.Add(val2); } } } } } c.savedPrefabs = list; }); } private static bool IsPersonalBelt(SavePrefabData p) { if (p != null && p.itemParentObject <= 0 && p.inventorySlot >= 0) { return p.inventorySlot < 100; } return false; } public static void MergeInto(SaveContainer host) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (host == null) { return; } int num = StripPersonalBelt(host); if (!Exists()) { Plugin.Logger.LogInfo((object)("[CoopProfile] Profile missing - first session (host belt stripped: " + num + ", own belt empty)")); return; } try { SaveContainer val; using (FileStream serializationStream = File.Open(ProfilePath, FileMode.Open)) { val = (SaveContainer)new BinaryFormatter().Deserialize(serializationStream); } CopyCharacterFields(val, host); int num2 = InjectPersonalBelt(val, host); Plugin.Logger.LogInfo((object)("[CoopProfile] Profile applied (host belt stripped: " + num + ", own belt injected: " + num2 + ")")); } catch (Exception ex) { Plugin.Logger.LogError((object)("[CoopProfile] Failed to apply profile (using host character): " + ex)); } } private static int StripPersonalBelt(SaveContainer host) { if (host == null || host.savedPrefabs == null) { return 0; } return host.savedPrefabs.RemoveAll(IsPersonalBelt); } private static int InjectPersonalBelt(SaveContainer profile, SaveContainer host) { if (profile == null || profile.savedPrefabs == null || host == null) { return 0; } if (host.savedPrefabs == null) { host.savedPrefabs = new List<SavePrefabData>(); } int num = MaxInstanceId(host) + 1000; int num2 = 0; foreach (SavePrefabData savedPrefab in profile.savedPrefabs) { if (IsPersonalBelt(savedPrefab)) { savedPrefab.instanceId = num++; host.savedPrefabs.Add(savedPrefab); num2++; } } return num2; } private static int MaxInstanceId(SaveContainer host) { int num = 0; if (host != null && host.savedPrefabs != null) { foreach (SavePrefabData savedPrefab in host.savedPrefabs) { if (savedPrefab != null && savedPrefab.instanceId > num) { num = savedPrefab.instanceId; } } } return num; } private static void CopyCharacterFields(SaveContainer src, SaveContainer dst) { dst.playerGold = src.playerGold; if (src.playerCurrency != null) { dst.playerCurrency = src.playerCurrency; } dst.currentCurrency = src.currentCurrency; if (src.playerReputation != null) { dst.playerReputation = src.playerReputation; } if (src.playerKnownPrices != null) { dst.playerKnownPrices = src.playerKnownPrices; } if (src.tradeReceipts != null) { dst.tradeReceipts = src.tradeReceipts; } if (src.quests != null) { dst.quests = src.quests; } dst.food = src.food; dst.foodDebt = src.foodDebt; dst.water = src.water; dst.sleep = src.sleep; dst.sleepDebt = src.sleepDebt; dst.vitamins = src.vitamins; dst.protein = src.protein; dst.alcohol = src.alcohol; dst.tobaccoWhite = src.tobaccoWhite; dst.tobaccoGreen = src.tobaccoGreen; dst.tobaccoBlack = src.tobaccoBlack; dst.tobaccoBrown = src.tobaccoBrown; if (src.savedMissions != null) { dst.savedMissions = src.savedMissions; } if (src.loggedMissions != null) { dst.loggedMissions = src.loggedMissions; } if (src.currencyDayLogs != null) { dst.currencyDayLogs = src.currencyDayLogs; } } private static void Try(string what, Action a) { try { a(); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[CoopProfile] field '" + what + "' skipped: " + ex.Message)); } } } public static class CoordSpace { public static bool Ready => (Object)(object)FloatingOriginManager.instance != (Object)null; public static bool ShiftingThisFrame => FloatingOriginManager.ShiftingThisFrame; public static Vector3 LocalToReal(Vector3 localPos) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) FloatingOriginManager instance = FloatingOriginManager.instance; if (!((Object)(object)instance != (Object)null)) { return localPos; } return instance.ShiftingPosToRealPos(localPos); } public static Vector3 RealToLocal(Vector3 realPos) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) FloatingOriginManager instance = FloatingOriginManager.instance; if (!((Object)(object)instance != (Object)null)) { return realPos; } return instance.RealPosToShiftingPos(realPos); } } public sealed class EnvironmentSync { private readonly CoopNet _net; private float _sendTimer; private bool _windDisabled; private bool _wavesDisabled; private WavesInertia _waves; private float _waveTimeBase; private float _waveBaseLocal; private float _hostTimeScale; private bool _waveBaseValid; private float _waveClock; public static float WaveClock; public static volatile bool WaveClockActive; public float EnvHz = 4f; public float WaveClockError { get; private set; } public float HostTimeScale => _hostTimeScale; public bool WaveClockValid => _waveBaseValid; public EnvironmentSync(CoopNet net) { _net = net; } public void Tick(float dt) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) if (_net.Role == Role.Client) { TickWaveClock(); } else { if (_net.Role != Role.Host || _net.State != LinkState.Connected) { return; } float num = 1f / Mathf.Max(0.5f, EnvHz); _sendTimer += dt; if (!(_sendTimer < num)) { _sendTimer = 0f; EnvStateMsg envStateMsg = new EnvStateMsg { Tick = _net.Clock.ServerTick, Wind = Wind.currentWind, BaseWind = Wind.currentBaseWind, WindRot = Wind.windRotation, Day = GameState.day }; Sun sun = Sun.sun; if ((Object)(object)sun != (Object)null) { envStateMsg.GlobalTime = sun.globalTime; envStateMsg.LocalTime = sun.localTime; envStateMsg.Timescale = sun.timescale; } Moon instance = Moon.instance; if ((Object)(object)instance != (Object)null) { envStateMsg.MoonPhase = instance.currentPhase; } WavesInertia val = FindWaves(); if ((Object)(object)val != (Object)null) { envStateMsg.HasWaves = true; envStateMsg.WavesRot = ((Component)val).transform.rotation; envStateMsg.WavesInertia = val.currentInertia; envStateMsg.WavesMagnitude = val.currentMagnitude; } envStateMsg.WaveTime = Time.time; envStateMsg.HostTimeScale = Time.timeScale; _net.Broadcast(envStateMsg, (DeliveryMethod)4); } } } private WavesInertia FindWaves() { if ((Object)(object)_waves == (Object)null) { _waves = Object.FindObjectOfType<WavesInertia>(); } return _waves; } public void OnEnvState(EnvStateMsg msg, NetPeer fromPeer) { //IL_0035: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Client) { return; } Wind instance = Wind.instance; if ((Object)(object)instance != (Object)null && ((Behaviour)instance).enabled) { ((Behaviour)instance).enabled = false; _windDisabled = true; } Wind.currentWind = msg.Wind; Wind.currentBaseWind = msg.BaseWind; Wind.windRotation = msg.WindRot; Sun sun = Sun.sun; if ((Object)(object)sun != (Object)null) { sun.globalTime = msg.GlobalTime; sun.localTime = msg.LocalTime; sun.timescale = msg.Timescale; } GameState.day = msg.Day; Moon instance2 = Moon.instance; if ((Object)(object)instance2 != (Object)null) { instance2.currentPhase = msg.MoonPhase; } if (msg.HasWaves) { WavesInertia val = FindWaves(); if ((Object)(object)val != (Object)null) { if (((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; _wavesDisabled = true; } val.LoadInertia(msg.WavesRot, msg.WavesInertia, msg.WavesMagnitude); } } _waveTimeBase = msg.WaveTime; _waveBaseLocal = Time.unscaledTime; _hostTimeScale = msg.HostTimeScale; _waveBaseValid = true; } private void TickWaveClock() { if (!_waveBaseValid || _net.State != LinkState.Connected) { WaveClockActive = false; WaveClockError = 0f; return; } float num = _waveTimeBase + (Time.unscaledTime - _waveBaseLocal) * _hostTimeScale; float num2 = num - _waveClock; if (Mathf.Abs(num2) > 1f) { _waveClock = num; } else { _waveClock += Time.unscaledDeltaTime * _hostTimeScale + num2 * 0.1f; } WaveClockError = num2; WaveClock = _waveClock; WaveClockActive = true; } public void Clear() { if (_windDisabled) { Wind instance = Wind.instance; if ((Object)(object)instance != (Object)null) { ((Behaviour)instance).enabled = true; } _windDisabled = false; } if (_wavesDisabled) { if ((Object)(object)_waves != (Object)null) { ((Behaviour)_waves).enabled = true; } _wavesDisabled = false; } _waves = null; _sendTimer = 0f; _waveBaseValid = false; WaveClockActive = false; WaveClockError = 0f; } } public static class OceanPatches { private const int GaussSeed = 727272; public static void Apply(Harmony harmony) { bool flag = TryPatch(harmony, "calcComplex", "PreCalcComplex"); bool flag2 = TryPatch(harmony, "InitWaveGenerator", "PreInitWaveGenerator"); Plugin.Logger.LogInfo((object)("[OceanPatches] Ocean patches: phase=" + flag + " table=" + flag2)); PatchHealth.Report("Ocean", (flag ? 1 : 0) + (flag2 ? 1 : 0), 2); } private static bool TryPatch(Harmony harmony, string method, string prefixName) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown try { MethodInfo method2 = typeof(Ocean).GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method2 == null) { return false; } HarmonyMethod val = new HarmonyMethod(typeof(OceanPatches).GetMethod(prefixName, BindingFlags.Static | BindingFlags.NonPublic)); harmony.Patch((MethodBase)method2, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[OceanPatches] " + method + ": " + ex.Message)); return false; } } private static void PreCalcComplex(ref float time) { try { if (EnvironmentSync.WaveClockActive) { time = EnvironmentSync.WaveClock; } } catch { } } private static void PreInitWaveGenerator(Ocean __instance, bool skip, ref bool useMyRandom) { try { if (skip) { return; } int num = __instance.width * __instance.height; if (num > 0) { if (__instance.gaussRandom1 == null || __instance.gaussRandom1.Length != num) { __instance.gaussRandom1 = new float[num]; } if (__instance.gaussRandom2 == null || __instance.gaussRandom2.Length != num) { __instance.gaussRandom2 = new float[num]; } Random rng = new Random(727272); for (int i = 0; i < num; i++) { __instance.gaussRandom1[i] = Gaussian(rng); __instance.gaussRandom2[i] = Gaussian(rng); } useMyRandom = true; } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[OceanPatches] PreInitWaveGenerator: " + ex.Message)); } } private static float Gaussian(Random rng) { double num = rng.NextDouble(); double num2 = rng.NextDouble(); if (num < 0.01) { num = 0.01; } return (float)(Math.Sqrt(-2.0 * Math.Log(num)) * Math.Cos(Math.PI * 2.0 * num2)); } } public enum InteractPolicy { Shared, Item, Local, HostOnly } public static class InteractionPolicy { private static readonly HashSet<string> HostOnly = new HashSet<string> { "GPButtonBed", "GPButtonTavernSleep", "GPButtonOnsenEntrance", "ShipItemBed", "GPButtonAutosaveToggle" }; private static readonly HashSet<string> Local = new HashSet<string> { "GPButtonAntiAliasing", "GPButtonLightQuality", "GPButtonScreenResolution", "GPButtonResolutionUI", "GPButtonWindowMode", "GPButtonTargetFramerate", "GPButtonKeybinding", "GPButtonResetKeybindings", "GPButtonSliderVolume", "GPButtonSettingsCheckbo", "GPButtonInterface", "GPButtonExtraMenus", "GPButtonControlToggle", "GPButtonLogMode", "GPButtonDayLogDay", "GPButtonMapZoom", "StartMenuButton", "GPButtonInventorySlot", "MouseoverTextTrigger", "GPButtonBuyItem", "EconomyUIButton", "CurrencyExchangeUIButton", "CurrencySwitchButton", "TradeReceiptsUIButton", "CrateInventoryButton", "CargoCarrierButton", "CargoStorageUIButton", "GPButtonPortMissions", "GPButtonListedMission", "GPButtonSetMission", "GPButtonMissionListBack", "GPButtonMissionListPage", "GPButtonMissionListWorld", "ShipyardButton", "ShipyardDocuments", "GPButtonPurchaseBoat", "BoatLadder", "GPButtonRatlines" }; private static readonly HashSet<string> SharedPickupable = new HashSet<string>(); public static InteractPolicy Classify(GoPointerButton btn) { if ((Object)(object)btn == (Object)null) { return InteractPolicy.Local; } string name = ((object)btn).GetType().Name; if (HostOnly.Contains(name)) { return InteractPolicy.HostOnly; } if (Local.Contains(name)) { return InteractPolicy.Local; } if (SharedPickupable.Contains(name)) { return InteractPolicy.Shared; } if (btn is PickupableItem) { return InteractPolicy.Item; } return InteractPolicy.Shared; } } public sealed class InteractionSync { private readonly CoopNet _net; private PlayerEmbarkerNew _emb; private Transform _cachedBoat; private GoPointerButton[] _buttons = Array.Empty<GoPointerButton>(); private readonly Dictionary<GoPointerButton, int> _index = new Dictionary<GoPointerButton, int>(); private GoPointer _hostPointer; private bool _replaying; private GoPointer _gp; private FieldInfo _fClicked; private FieldInfo _fHeldItem; private float _pushTimer; private float _pushAccumDt; private string _lastPush = "—"; private long _lastPushTick; private const float PushHz = 20f; private string _lastEvent = "—"; private long _lastEventTick; public static InteractionSync Instance { get; private set; } public string LastEventText { get { if (_lastEventTick == 0L) { return "—"; } long num = _net.Clock.ServerTick - _lastEventTick; if (num < 0) { num = 0L; } string text = "—"; if (_lastPushTick != 0L) { long num2 = _net.Clock.ServerTick - _lastPushTick; if (num2 < 0) { num2 = 0L; } text = _lastPush + " " + num2 + "ms"; } return _lastEvent + " " + num + "ms · push " + text; } } public int ButtonCount => _buttons.Length; public InteractionSync(CoopNet net) { _net = net; Instance = this; } public static bool ShouldBlockLocally(GoPointerButton btn) { InteractionSync instance = Instance; if (instance == null || (Object)(object)btn == (Object)null) { return false; } if (instance._net.Role != Role.Client || instance._net.State != LinkState.Connected) { return false; } if (InteractionPolicy.Classify(btn) != InteractPolicy.HostOnly) { return false; } instance.Remember("block(host-only) '" + ButtonLabel(btn) + "'"); return true; } public static bool ShouldBlockPushWhileHolding(GoPointerButton btn) { InteractionSync instance = Instance; if (instance == null || (Object)(object)btn == (Object)null) { return false; } if (instance._net.Role != Role.Client || instance._net.State != LinkState.Connected) { return false; } if (!IsPushButton(btn)) { return false; } if (!instance.HasHeldItem()) { return false; } instance.RememberPush("block held '" + ButtonLabel(btn) + "'"); return true; } public void Tick(float dt) { if (_net.State == LinkState.Connected) { RefreshButtons(); ForwardPushRequests(dt); } } private void RefreshButtons() { if ((Object)(object)_emb == (Object)null) { _emb = Object.FindObjectOfType<PlayerEmbarkerNew>(); } Transform val = (((Object)(object)_emb != (Object)null) ? _emb.debugOutCurrentBoat : null); if ((Object)(object)val == (Object)(object)_cachedBoat) { return; } _cachedBoat = val; _index.Clear(); if ((Object)(object)val == (Object)null) { _buttons = Array.Empty<GoPointerButton>(); return; } _buttons = ((Component)val).GetComponentsInChildren<GoPointerButton>(true); for (int i = 0; i < _buttons.Length; i++) { if ((Object)(object)_buttons[i] != (Object)null) { _index[_buttons[i]] = i; } } Plugin.Logger.LogInfo((object)("[InteractionSync] Boat changed: buttons=" + _buttons.Length + (((Object)(object)val != (Object)null) ? (" ('" + ((Object)val).name + "')") : ""))); } public void NotifyLocalInteract(GoPointerButton btn, InteractKind kind) { if (!_replaying && _net.Role == Role.Client && _net.State == LinkState.Connected && !((Object)(object)btn == (Object)null) && InteractionPolicy.Classify(btn) == InteractPolicy.Shared && !IsExcluded(btn, kind)) { RefreshButtons(); if (_index.TryGetValue(btn, out var value)) { _net.Broadcast(new ControlEventMsg { Index = (ushort)value, Kind = kind }, (DeliveryMethod)2); Remember("out " + kind.ToString() + " #" + value + " '" + ButtonLabel(btn) + "'"); } } } public void NotifyLocalHold(GoPointerButton btn, InteractKind kind, bool down) { if (!_replaying && _net.Role == Role.Client && _net.State == LinkState.Connected && !((Object)(object)btn == (Object)null) && HasHeldChannel(btn, kind) && InteractionPolicy.Classify(btn) == InteractPolicy.Shared) { RefreshButtons(); if (_index.TryGetValue(btn, out var value)) { _net.Broadcast(new HoldRequestMsg { Index = (ushort)value, Kind = kind, Down = down }, (DeliveryMethod)2); Remember("out hold " + (down ? "down" : "up") + " #" + value + " '" + ButtonLabel(btn) + "'"); } } } public void OnControlEvent(ControlEventMsg msg, NetPeer fromPeer) { if (_net.Role != Role.Host) { return; } RefreshButtons(); int index = msg.Index; if (index < 0 || index >= _buttons.Length) { return; } GoPointerButton val = _buttons[index]; if ((Object)(object)val == (Object)null || InteractionPolicy.Classify(val) != InteractPolicy.Shared || IsExcluded(val, msg.Kind)) { return; } string text = ((msg.Kind == InteractKind.AltActivate) ? "OnAltActivate" : "OnActivate"); Remember("in " + msg.Kind.ToString() + " #" + index + " '" + ButtonLabel(val) + "'"); try { Type[] types = ((msg.Kind == InteractKind.ActivateNoArg) ? Type.EmptyTypes : new Type[1] { typeof(GoPointer) }); object[] parameters = ((msg.Kind == InteractKind.ActivateNoArg) ? null : new object[1] { HostPointer() }); MethodInfo method = ((object)val).GetType().GetMethod(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, types, null); if (method == null) { Plugin.Logger.LogWarning((object)("[InteractionSync] '" + ((object)val).GetType().Name + "' missing " + text + ((msg.Kind == InteractKind.ActivateNoArg) ? "()" : "(GoPointer)"))); } else { _replaying = true; method.Invoke(val, parameters); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[InteractionSync] Replay error " + text + " on '" + ((object)val).GetType().Name + "': " + ex.Message)); } finally { _replaying = false; } } public void OnHoldRequest(HoldRequestMsg msg, NetPeer fromPeer) { if (_net.Role != Role.Host) { return; } RefreshButtons(); int index = msg.Index; if (index < 0 || index >= _buttons.Length) { return; } GoPointerButton val = _buttons[index]; if (!((Object)(object)val == (Object)null) && InteractionPolicy.Classify(val) == InteractPolicy.Shared && HasHeldChannel(val, msg.Kind)) { uint actorNetId = _net.PlayerNetIdForPeer(fromPeer); if (val is BilgePump) { BoatDamageSync.Instance?.SetRemotePump(msg.Index, msg.Down, actorNetId); Remember("in hold " + (msg.Down ? "down" : "up") + " #" + index + " '" + ButtonLabel(val) + "'"); } } } public void OnPushRequest(PushRequestMsg msg, NetPeer fromPeer) { //IL_006a: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Host || !CoordSpace.Ready) { return; } RefreshButtons(); int index = msg.Index; if (index < 0 || index >= _buttons.Length) { return; } GoPointerButton val = _buttons[index]; if (!((Object)(object)val == (Object)null) && IsPushButton(val) && InteractionPolicy.Classify(val) == InteractPolicy.Shared) { Rigidbody val2 = PushTargetBody(val); if (!((Object)(object)val2 == (Object)null)) { Vector3 val3 = CoordSpace.RealToLocal(msg.RealPos); float num = Mathf.Clamp(msg.DeltaTime, 0.001f, 0.2f); val2.AddForceAtPosition(msg.Force * num, val3, (ForceMode)1); RememberPush("in #" + index + " '" + ButtonLabel(val) + "'"); } } } private static bool IsExcluded(GoPointerButton btn, InteractKind kind) { if (kind != InteractKind.Activate) { return false; } if (!(btn is GPButtonRopeWinch) && !(btn is GPButtonSteeringWheel) && !(btn is BilgePump)) { return btn is PickupableItem; } return true; } private static bool HasHeldChannel(GoPointerButton btn, InteractKind kind) { if (kind == InteractKind.Activate) { return btn is BilgePump; } return false; } private void ForwardPushRequests(float dt) { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: 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_0132: Unknown result type (might be due to invalid IL or missing references) if (_net.Role != Role.Client || _net.State != LinkState.Connected || !CoordSpace.Ready) { return; } if (HasHeldItem()) { _pushAccumDt = 0f; _pushTimer = 0f; return; } _pushAccumDt += dt; _pushTimer += dt; float num = 0.05f; if (_pushTimer < num) { return; } _pushTimer = 0f; GoPointerButton val = ClickedButton(); if ((Object)(object)val == (Object)null || !IsPushButton(val)) { _pushAccumDt = 0f; return; } if (InteractionPolicy.Classify(val) != InteractPolicy.Shared) { _pushAccumDt = 0f; return; } RefreshButtons(); if (!_index.TryGetValue(val, out var value)) { _pushAccumDt = 0f; return; } if (!TryBuildPush(val, out var force, out var atPos)) { _pushAccumDt = 0f; return; } float deltaTime = Mathf.Clamp(_pushAccumDt, 0.001f, 0.2f); _pushAccumDt = 0f; _net.Broadcast(new PushRequestMsg { Index = (ushort)value, RealPos = CoordSpace.LocalToReal(atPos), Force = force, DeltaTime = deltaTime }, (DeliveryMethod)2); RememberPush("out #" + value + " '" + ButtonLabel(val) + "'"); } private GoPointerButton ClickedButton() { try { if ((Object)(object)_gp == (Object)null) { _gp = Object.FindObjectOfType<GoPointer>(); } if ((Object)(object)_gp == (Object)null) { return null; } if (_fClicked == null) { _fClicked = typeof(GoPointer).GetField("clickedButton", BindingFlags.Instance | BindingFlags.NonPublic); } return (GoPointerButton)((_fClicked != null) ? /*isinst with value type is only supported in some contexts*/: null); } catch { return null; } } private bool HasHeldItem() { try { if ((Object)(object)_gp == (Object)null) { _gp = Object.FindObjectOfType<GoPointer>(); } if ((Object)(object)_gp == (Object)null) { return false; } if (_fHeldItem == null) { _fHeldItem = typeof(GoPointer).GetField("heldItem", BindingFlags.Instance | BindingFlags.NonPublic); } return _fHeldItem != null && _fHeldItem.GetValue(_gp) != null; } catch { return false; } } private GoPointer HostPointer() { if ((Object)(object)_hostPointer == (Object)null) { _hostPointer = Object.FindObjectOfType<GoPointer>(); } return _hostPointer; } private bool TryBuildPush(GoPointerButton btn, out Vector3 force, out Vector3 atPos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_0011: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: 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_0184: 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_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0262: 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_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) force = Vector3.zero; atPos = Vector3.zero; if ((Object)(object)_gp == (Object)null) { _gp = Object.FindObjectOfType<GoPointer>(); } if ((Object)(object)_gp == (Object)null) { return false; } Transform val = (((Object)(object)_gp.movement != (Object)null) ? ((Component)_gp.movement).transform : ((Component)_gp).transform); atPos = val.position; GPButtonSailPusher val2 = (GPButtonSailPusher)(object)((btn is GPButtonSailPusher) ? btn : null); if (val2 != null) { force = val2.pushForceMult * val.forward * 2.5f; return ((Vector3)(ref force)).sqrMagnitude > 1E-06f; } Vector3 velocity; if (btn is GPButtonBoatPushCol) { Rigidbody val3 = PushTargetBody(btn); if ((Object)(object)val3 == (Object)null) { return false; } velocity = val3.velocity; float num = Mathf.Max(1f, ((Vector3)(ref velocity)).magnitude); float field = GetField(btn, "pushForceMult", 3f); float field2 = GetField(btn, "upForceMult", 1f); float field3 = GetField(btn, "verticalOffset", -2f); float num2 = (PlayerSwimming.swimming ? 0f : 1f); force = (field * val3.mass * val.forward + field2 * val3.mass * Vector3.up) * num2 / num; atPos = val.position + Vector3.up * field3; return ((Vector3)(ref force)).sqrMagnitude > 1E-06f; } if (btn is DockPushCol) { Rigidbody val4 = (((Object)(object)GameState.currentBoat != (Object)null && (Object)(object)GameState.currentBoat.parent != (Object)null) ? ((Component)GameState.currentBoat.parent).GetComponent<Rigidbody>() : null); if ((Object)(object)val4 == (Object)null) { return false; } velocity = val4.velocity; float num3 = Mathf.Max(1f, ((Vector3)(ref velocity)).magnitude); float field4 = GetField(btn, "pushForceMult", -0.55f); float field5 = GetField(btn, "upForceMult", 0f); float field6 = GetField(btn, "verticalOffset", 0f); force = (field4 * val4.mass * val.forward + field5 * val4.mass * Vector3.up) / num3; atPos = val.position + Vector3.up * field6; return ((Vector3)(ref force)).sqrMagnitude > 1E-06f; } return false; } private static bool IsPushButton(GoPointerButton btn) { if (!(btn is GPButtonBoatPushCol) && !(btn is DockPushCol)) { return btn is GPButtonSailPusher; } return true; } private static Rigidbody PushTargetBody(GoPointerButton btn) { if ((Object)(object)btn == (Object)null) { return null; } if (btn is GPButtonSailPusher) { Rigidbody field = GetField<Rigidbody>(btn, "body"); if (!((Object)(object)field != (Object)null)) { if (!((Object)(object)((Component)btn).transform.parent != (Object)null)) { return null; } return ((Component)((Component)btn).transform.parent).GetComponent<Rigidbody>(); } return field; } if (btn is GPButtonBoatPushCol) { Transform transform = ((Component)btn).transform; if ((Object)(object)transform.parent != (Object)null && (Object)(object)transform.parent.parent != (Object)null) { Rigidbody component = ((Component)transform.parent.parent).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { return component; } if ((Object)(object)transform.parent.parent.parent != (Object)null) { return ((Component)transform.parent.parent.parent).GetComponent<Rigidbody>(); } } return null; } if (btn is DockPushCol) { if (!((Object)(object)GameState.currentBoat != (Object)null) || !((Object)(object)GameState.currentBoat.parent != (Object)null)) { return null; } return ((Component)GameState.currentBoat.parent).GetComponent<Rigidbody>(); } return null; } private static float GetField(object obj, string name, float fallback) { try { FieldInfo field = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return (field != null) ? ((float)field.GetValue(obj)) : fallback; } catch { return fallback; } } private static T GetField<T>(object obj, string name) where T : class { try { FieldInfo field = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return (field != null) ? (field.GetValue(obj) as T) : null; } catch { return null; } } private static string ButtonLabel(GoPointerButton btn) { if ((Object)(object)btn == (Object)null) { return "—"; } if (!string.IsNullOrEmpty(btn.lookText)) { return btn.lookText; } return ((object)btn).GetType().Name; } private void Remember(string text) { _lastEvent = text; _lastEventTick = _net.Clock.ServerTick; } private void RememberPush(string text) { _lastPush = text; _lastPushTick = _net.Clock.ServerTick; } public void Clear() { _cachedBoat = null; _buttons = Array.Empty<GoPointerButton>(); _index.Clear(); _hostPointer = null; _replaying = false; _gp = null; _fClicked = null; _pushTimer = 0f; _pushAccumDt = 0f; _lastPush = "—"; _lastPushTick = 0L; _lastEvent = "—"; _lastEventTick = 0L; } } public static class InteractionPatches { public static void Apply(Harmony harmony) { int num = PatchAll(harmony, "OnActivate", "PostActivateNoArg", Type.EmptyTypes); int num2 = PatchAll(harmony, "OnActivate", "PostActivate"); int num3 = PatchAll(harmony, "OnAltActivate", "PostAltActivate"); int num4 = PatchAll(harmony, "OnUnactivate", "PostUnactivate", null, withBlockPrefix: false); int num5 = 0; if (PatchPushFixedUpdate(harmony, typeof(GPButtonBoatPushCol))) { num5++; } if (PatchPushFixedUpdate(harmony, typeof(DockPushCol))) { num5++; } if (PatchPushFixedUpdate(harmony, typeof(GPButtonSailPusher))) { num5++; } int ok = ((num > 0) ? 1 : 0) + ((num2 > 0) ? 1 : 0) + ((num3 > 0) ? 1 : 0) + ((num4 > 0) ? 1 : 0) + num5; PatchHealth.Report("Interactions", ok, 7, "activate=" + num2 + ", alt=" + num3 + ", unactivate=" + num4 + ", push=" + num5 + "/3"); } private static bool PatchPushFixedUpdate(Harmony harmony, Type type) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown try { MethodI