Decompiled source of SailwindCoop v0.1.4

LiteNetLib.dll

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

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("Ruslan Pyrch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright 2024 Ruslan Pyrch")]
[assembly: AssemblyDescription("Lite reliable UDP library for .NET, Mono, and .NET Core")]
[assembly: AssemblyFileVersion("1.3.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
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