fishy facepunch

This commit is contained in:
2023-05-31 12:47:21 -04:00
parent 12779859cd
commit 6a28112faa
338 changed files with 2161 additions and 58 deletions

View File

@ -0,0 +1,92 @@
using System.Collections;
using System.Collections.Generic;
namespace FishyFacepunch
{
public class BidirectionalDictionary<T1, T2> : IEnumerable
{
private Dictionary<T1, T2> t1ToT2Dict = new Dictionary<T1, T2>();
private Dictionary<T2, T1> t2ToT1Dict = new Dictionary<T2, T1>();
public IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
public int Count => t1ToT2Dict.Count;
public Dictionary<T1, T2> First => t1ToT2Dict;
public Dictionary<T2, T1> Second => t2ToT1Dict;
public void Add(T1 key, T2 value)
{
if (t1ToT2Dict.ContainsKey(key))
{
Remove(key);
}
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
public void Add(T2 key, T1 value)
{
if (t2ToT1Dict.ContainsKey(key))
{
Remove(key);
}
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
public T2 Get(T1 key) => t1ToT2Dict[key];
public T1 Get(T2 key) => t2ToT1Dict[key];
public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value);
public bool TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value);
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key);
public void Remove(T1 key)
{
if (Contains(key))
{
T2 val = t1ToT2Dict[key];
t1ToT2Dict.Remove(key);
t2ToT1Dict.Remove(val);
}
}
public void Remove(T2 key)
{
if (Contains(key))
{
T1 val = t2ToT1Dict[key];
t1ToT2Dict.Remove(val);
t2ToT1Dict.Remove(key);
}
}
public T1 this[T2 key]
{
get => t2ToT1Dict[key];
set
{
Add(key, value);
}
}
public T2 this[T1 key]
{
get => t1ToT2Dict[key];
set
{
Add(key, value);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 336587b490f535a4aa3b1618c9ebac55
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,126 @@
#if !FishyFacepunch
using FishNet.Transporting;
using FishyFacepunch.Server;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace FishyFacepunch.Client
{
/// <summary>
/// Creates a fake client connection to interact with the ServerSocket when acting as host.
/// </summary>
public class ClientHostSocket : CommonSocket
{
#region Private.
/// <summary>
/// Socket for the server.
/// </summary>
private ServerSocket _server;
/// <summary>
/// Incomimg data.
/// </summary>
private Queue<LocalPacket> _incoming = new Queue<LocalPacket>();
#endregion
/// <summary>
/// Checks to set localCLient started.
/// </summary>
internal void CheckSetStarted()
{
//Check to set as started.
if (_server != null && base.GetLocalConnectionState() == LocalConnectionState.Starting)
{
if (_server.GetLocalConnectionState() == LocalConnectionState.Started)
SetLocalConnectionState(LocalConnectionState.Started, false);
}
}
/// <summary>
/// Starts the client connection.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="channelsCount"></param>
/// <param name="pollTime"></param>
internal bool StartConnection(ServerSocket serverSocket)
{
_server = serverSocket;
_server.SetClientHostSocket(this);
if (_server.GetLocalConnectionState() != LocalConnectionState.Started)
return false;
SetLocalConnectionState(LocalConnectionState.Starting, false);
return true;
}
/// <summary>
/// Sets a new connection state.
/// </summary>
protected override void SetLocalConnectionState(LocalConnectionState connectionState, bool server)
{
base.SetLocalConnectionState(connectionState, server);
if (connectionState == LocalConnectionState.Started)
_server.OnClientHostState(true);
else
_server.OnClientHostState(false);
}
/// <summary>
/// Stops the local socket.
/// </summary>
internal bool StopConnection()
{
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped || base.GetLocalConnectionState() == LocalConnectionState.Stopping)
return false;
base.ClearQueue(_incoming);
//Immediately set stopped since no real connection exists.
SetLocalConnectionState(LocalConnectionState.Stopping, false);
SetLocalConnectionState(LocalConnectionState.Stopped, false);
_server.SetClientHostSocket(null);
return true;
}
/// <summary>
/// Iterations data received.
/// </summary>
internal void IterateIncoming()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
while (_incoming.Count > 0)
{
LocalPacket packet = _incoming.Dequeue();
ArraySegment<byte> segment = new ArraySegment<byte>(packet.Data, 0, packet.Length);
base.Transport.HandleClientReceivedDataArgs(new ClientReceivedDataArgs(segment, (Channel)packet.Channel, Transport.Index));
packet.Dispose();
}
}
/// <summary>
/// Called when the server sends the local client data.
/// </summary>
internal void ReceivedFromLocalServer(LocalPacket packet)
{
_incoming.Enqueue(packet);
}
/// <summary>
/// Queues data to be sent to server.
/// </summary>
internal void SendToServer(byte channelId, ArraySegment<byte> segment)
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
if (_server.GetLocalConnectionState() != LocalConnectionState.Started)
return;
LocalPacket packet = new LocalPacket(segment, channelId);
_server.ReceivedFromClientHost(packet);
}
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 112bec8b5281832468d85543d0b6e31c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,238 @@
#if !FishyFacepunch
using FishNet.Managing.Logging;
using FishNet.Transporting;
using Steamworks;
using Steamworks.Data;
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace FishyFacepunch.Client
{
public class ClientSocket : CommonSocket
{
#region Private.
/// <summary>
/// SteamId for host.
/// </summary>
private SteamId _hostSteamID = 0;
/// <summary>
/// Socket to use.
/// </summary>
private Connection HostConnection => HostConnectionManager.Connection;
/// <summary>
/// Use the internal connection manager from steam.
/// </summary>
private FishyConnectionManager HostConnectionManager;
/// <summary>
/// Task used to check for timeout.
/// </summary>
private CancellationTokenSource cancelToken;
private TaskCompletionSource<Task> connectedComplete;
private TimeSpan ConnectionTimeout;
/// <summary>
/// Task used to check for timeout.
/// </summary>
private bool _Error = false;
#endregion
/// <summary>
/// Initializes this for use.
/// </summary>
/// <param name="t"></param>
internal override void Initialize(Transport t)
{
base.Initialize(t);
}
/// <summary>
/// Starts the client connection.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="channelsCount"></param>
/// <param name="pollTime"></param>
internal async void StartConnection(string address, ushort port)
{
cancelToken = new CancellationTokenSource();
SteamNetworkingSockets.OnConnectionStatusChanged += OnConnectionStatusChanged;
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, base.Transport.GetTimeout(false)));
SetLocalConnectionState(LocalConnectionState.Starting, false);
try
{
if (SteamClient.IsValid)
{
connectedComplete = new TaskCompletionSource<Task>();
if (!IsValidAddress(address))
{
_hostSteamID = UInt64.Parse(address);
HostConnectionManager = SteamNetworkingSockets.ConnectRelay<FishyConnectionManager>(_hostSteamID);
}
else
{
HostConnectionManager = SteamNetworkingSockets.ConnectNormal<FishyConnectionManager>(NetAddress.From(address, port));
}
HostConnectionManager.ForwardMessage = OnMessageReceived;
Task connectedCompleteTask = connectedComplete.Task;
Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token);
if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask)
{
if (cancelToken.IsCancellationRequested)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"The connection attempt was cancelled.");
}
else if (timeOutTask.IsCompleted)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Connection to {address} timed out.");
StopConnection();
}
SetLocalConnectionState(LocalConnectionState.Stopped, false);
}
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError("SteamWorks not initialized");
SetLocalConnectionState(LocalConnectionState.Stopped, false);
}
}
catch (FormatException)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?");
SetLocalConnectionState(LocalConnectionState.Stopped, false);
_Error = true;
}
catch (Exception ex)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError(ex.Message);
SetLocalConnectionState(LocalConnectionState.Stopped, false);
_Error = true;
}
finally
{
if (_Error)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError("Connection failed.");
SetLocalConnectionState(LocalConnectionState.Stopped, false);
}
}
}
/// <summary>
/// Called when local connection state changes.
/// </summary>
private void OnConnectionStatusChanged(Connection conn, ConnectionInfo info)
{
if (info.State == ConnectionState.Connected)
{
SetLocalConnectionState(LocalConnectionState.Started, false);
connectedComplete.SetResult(connectedComplete.Task);
}
else if (info.State == ConnectionState.ClosedByPeer || info.State == ConnectionState.ProblemDetectedLocally)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection was closed by peer, {info.EndReason}");
StopConnection();
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection state changed: {info.State.ToString()} - {info.EndReason}");
}
}
/// <summary>
/// Stops the local socket.
/// </summary>
internal bool StopConnection()
{
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped || base.GetLocalConnectionState() == LocalConnectionState.Stopping)
return false;
SetLocalConnectionState(LocalConnectionState.Stopping, false);
cancelToken?.Cancel();
//Reset callback.
SteamNetworkingSockets.OnConnectionStatusChanged -= OnConnectionStatusChanged;
if (HostConnectionManager != null)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log("Sending Disconnect message");
HostConnection.Close(false, 0, "Graceful disconnect");
HostConnectionManager = null;
}
SetLocalConnectionState(LocalConnectionState.Stopped, false);
return true;
}
/// <summary>
/// Iterations data received.
/// </summary>
internal void IterateIncoming()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
HostConnectionManager.Receive(MAX_MESSAGES);
}
private void OnMessageReceived(IntPtr dataPtr, int size)
{
(byte[] data, int ch) = ProcessMessage(dataPtr, size);
base.Transport.HandleClientReceivedDataArgs(new ClientReceivedDataArgs(new ArraySegment<byte>(data), (Channel)ch, Transport.Index));
}
/// <summary>
/// Queues data to be sent to server.
/// </summary>
/// <param name="channelId"></param>
/// <param name="segment"></param>
internal void SendToServer(byte channelId, ArraySegment<byte> segment)
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
Result res = base.Send(HostConnection, segment, channelId);
if (res == Result.NoConnection || res == Result.InvalidParam)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection to server was lost.");
StopConnection();
}
else if (res != Result.OK)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Could not send: {res.ToString()}");
}
}
/// <summary>
/// Sends queued data to server.
/// </summary>
internal void IterateOutgoing()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
HostConnection.Flush();
}
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c5c258174868ee54cb570bef71c82404
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,177 @@
#if !FishyFacepunch
using FishNet.Managing.Logging;
using FishNet.Transporting;
using Steamworks;
using Steamworks.Data;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using UnityEngine;
namespace FishyFacepunch
{
public abstract class CommonSocket
{
#region Public.
/// <summary>
/// Current ConnectionState.
/// </summary>
private LocalConnectionState _connectionState = LocalConnectionState.Stopped;
/// <summary>
/// Returns the current ConnectionState.
/// </summary>
/// <returns></returns>
internal LocalConnectionState GetLocalConnectionState()
{
return _connectionState;
}
/// <summary>
/// Sets a new connection state.
/// </summary>
/// <param name="connectionState"></param>
protected virtual void SetLocalConnectionState(LocalConnectionState connectionState, bool asServer)
{
//If state hasn't changed.
if (connectionState == _connectionState)
return;
_connectionState = connectionState;
if (asServer)
Transport.HandleServerConnectionState(new ServerConnectionStateArgs(connectionState, Transport.Index));
else
Transport.HandleClientConnectionState(new ClientConnectionStateArgs(connectionState, Transport.Index));
}
#endregion
#region Protected.
/// <summary>
/// Transport controlling this socket.
/// </summary>
protected Transport Transport = null;
/// <summary>
/// Pointers for received messages per connection.
/// </summary>
protected IntPtr[] MessagePointers = new IntPtr[MAX_MESSAGES];
/// <summary>
/// Buffer used to receive data.
/// </summary>
protected byte[] InboundBuffer = null;
#endregion
#region Const.
/// <summary>
/// Maximum number of messages which can be received per connection.
/// </summary>
protected const int MAX_MESSAGES = 256;
#endregion
internal void ClearQueue(Queue<LocalPacket> lpq)
{
while (lpq.Count > 0)
{
LocalPacket lp = lpq.Dequeue();
lp.Dispose();
}
}
/// <summary>
/// Initializes this for use.
/// </summary>
/// <param name="t"></param>
internal virtual void Initialize(Transport t)
{
Transport = t;
//Get whichever channel has max MTU and resize buffer.
int maxMTU = Transport.GetMTU(0);
maxMTU = Math.Max(maxMTU, Transport.GetMTU(1));
InboundBuffer = new byte[maxMTU];
}
/// <summary>
/// Check if this is a valid address to start a p2p or c2s session.
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
protected bool IsValidAddress(string address)
{
//If address is required then make sure it can be parsed.
if (!string.IsNullOrEmpty(address))
{
if (!IPAddress.TryParse(address, out IPAddress result))
{
return false;
}
else
{
return true;
}
}
else
{
return false;
}
}
/// <summary>
/// Sends data over the steamConnection.
/// </summary>
/// <param name="steamConnection"></param>
/// <param name="segment"></param>
/// <param name="channelId"></param>
/// <returns></returns>
protected Result Send(Connection conn, ArraySegment<byte> segment, byte channelId)
{
/* Have to resize array to include channel index
* if array isn't large enough to fit it. This is because
* we don't know what channel data comes in on so
* the channel has to be packed into the data sent.
* Odds of the array having to resize are extremely low
* so while this is not ideal, it's still very low risk. */
if ((segment.Array.Length - 1) <= (segment.Offset + segment.Count))
{
byte[] arr = segment.Array;
Array.Resize(ref arr, arr.Length + 1);
arr[arr.Length - 1] = channelId;
}
//If large enough just increase the segment and set the channel byte.
else
{
segment.Array[segment.Offset + segment.Count] = channelId;
}
//Make a new segment so count is right.
segment = new ArraySegment<byte>(segment.Array, segment.Offset, segment.Count + 1);
GCHandle pinnedArray = GCHandle.Alloc(segment.Array, GCHandleType.Pinned);
IntPtr pData = pinnedArray.AddrOfPinnedObject() + segment.Offset;
SendType sendFlag = (channelId == (byte)Channel.Unreliable) ? SendType.Unreliable : SendType.Reliable;
Result result = conn.SendMessage(pData, segment.Count, sendFlag);
if (result != Result.OK)
{
if (Transport.NetworkManager.CanLog(LoggingType.Warning))
Debug.LogWarning($"Send issue: {result}");
}
pinnedArray.Free();
return result;
}
/// <summary>
/// Returns a message from the steam network.
/// </summary>
/// <param name="ptr"></param>
/// <param name="buffer"></param>
/// <returns></returns>
protected (byte[], int) ProcessMessage(IntPtr ptrs, int size)
{
byte[] managedArray = new byte[size];
Marshal.Copy(ptrs, managedArray, 0, size);
int channel = managedArray[managedArray.Length - 1];
Array.Resize(ref managedArray, managedArray.Length - 1);
return (managedArray, channel);
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3bc85d49720cf5844bf19af677e4f5f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using Steamworks;
using System;
public class FishyConnectionManager : ConnectionManager
{
public Action<IntPtr, int> ForwardMessage;
public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel)
{
ForwardMessage(data, size);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4200e3c1bd9ce214baf0cc323ef78429
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,13 @@
using Steamworks;
using Steamworks.Data;
using System;
public class FishySocketManager : SocketManager
{
public Action<Connection, IntPtr, int> ForwardMessage;
public override void OnMessage(Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
{
ForwardMessage(connection, data, size);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1180bddcdfeaad44e80261cedd2b806d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,25 @@
using FishNet.Utility.Performance;
using System;
namespace FishyFacepunch
{
internal struct LocalPacket
{
public byte[] Data;
public int Length;
public byte Channel;
public LocalPacket(ArraySegment<byte> data, byte channel)
{
Data = ByteArrayPool.Retrieve(data.Count);
Length = data.Count;
Buffer.BlockCopy(data.Array, data.Offset, Data, 0, Length);
Channel = channel;
}
public void Dispose()
{
if (Data != null)
ByteArrayPool.Store(Data);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1e0f33bdfade44489a2decb96d23c72
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,425 @@
#if !FishyFacepunch
using FishNet.Managing.Logging;
using FishNet.Transporting;
using FishyFacepunch.Client;
using Steamworks;
using Steamworks.Data;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishyFacepunch.Server
{
public class ServerSocket : CommonSocket
{
#region Public.
/// <summary>
/// Gets the current ConnectionState of a remote client on the server.
/// </summary>
/// <param name="connectionId">ConnectionId to get ConnectionState for.</param>
internal RemoteConnectionState GetConnectionState(int connectionId)
{
//Remote clients can only have Started or Stopped states since we cannot know in between.
if (_steamConnections.Second.ContainsKey(connectionId))
return RemoteConnectionState.Started;
else
return RemoteConnectionState.Stopped;
}
#endregion
#region Private.
/// <summary>
/// SteamConnections for ConnectionIds.
/// </summary>
private BidirectionalDictionary<Connection, int> _steamConnections = new BidirectionalDictionary<Connection, int>();
/// <summary>
/// SteamIds for ConnectionIds.
/// </summary>
private BidirectionalDictionary<SteamId, int> _steamIds = new BidirectionalDictionary<SteamId, int>();
/// <summary>
/// Maximum number of remote connections.
/// </summary>
private int _maximumClients;
/// <summary>
/// Next Id to use for a connection.
/// </summary>
private int _nextConnectionId;
/// <summary>
/// Socket for the connection.
/// </summary>
private FishySocketManager _socket;
/// <summary>
/// ConnectionIds which can be reused.
/// </summary>
private Queue<int> _cachedConnectionIds = new Queue<int>();
/// <summary>
/// Contains state of the client host. True is started, false is stopped.
/// </summary>
private bool _clientHostStarted = false;
/// <summary>
/// Packets received from local client.
/// </summary>
private Queue<LocalPacket> _clientHostIncoming = new Queue<LocalPacket>();
/// <summary>
/// Socket for client host. Will be null if not being used.
/// </summary>
private ClientHostSocket _clientHost;
#endregion
/// <summary>
/// Initializes this for use.
/// </summary>
/// <param name="t"></param>
internal override void Initialize(Transport t)
{
base.Initialize(t);
}
/// <summary>
/// Resets the socket if invalid.
/// </summary>
internal void ResetInvalidSocket()
{
/* Force connection state to stopped if listener is invalid.
* Not sure if steam may change this internally so better
* safe than sorry and check before trying to connect
* rather than being stuck in the incorrect state. */
if (_socket == default)
base.SetLocalConnectionState(LocalConnectionState.Stopped, true);
}
/// <summary>
/// Starts the server.
/// </summary>
internal bool StartConnection(string address, ushort port, int maximumClients)
{
SteamNetworkingSockets.OnConnectionStatusChanged += OnRemoteConnectionState;
SetMaximumClients(maximumClients);
_nextConnectionId = 0;
_cachedConnectionIds.Clear();
base.SetLocalConnectionState(LocalConnectionState.Starting, true);
if (_socket != null)
{
_socket?.Close();
_socket = default;
}
#if UNITY_SERVER
_socket = SteamNetworkingSockets.CreateNormalSocket<FishySocketManager>(NetAddress.From(address, port));
#else
_socket = SteamNetworkingSockets.CreateRelaySocket<FishySocketManager>();
#endif
_socket.ForwardMessage = OnMessageReceived;
base.SetLocalConnectionState(LocalConnectionState.Started, true);
return true;
}
/// <summary>
/// Stops the local socket.
/// </summary>
internal bool StopConnection()
{
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped)
return false;
base.SetLocalConnectionState(LocalConnectionState.Stopping, true);
if (_socket != null)
{
SteamNetworkingSockets.OnConnectionStatusChanged -= OnRemoteConnectionState;
_socket?.Close();
_socket = default;
}
base.SetLocalConnectionState(LocalConnectionState.Stopped, true);
return true;
}
/// <summary>
/// Stops a remote client from the server, disconnecting the client.
/// </summary>
/// <param name="connectionId">ConnectionId of the client to disconnect.</param>
internal bool StopConnection(int connectionId)
{
if (connectionId == FishyFacepunch.CLIENT_HOST_ID)
{
if (_clientHost != null)
{
_clientHost.StopConnection();
return true;
}
else
{
return false;
}
}
else
{
if (_steamConnections.Second.TryGetValue(connectionId, out Connection steamConn))
{
return StopConnection(connectionId, steamConn);
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Steam connection not found for connectionId {connectionId}.");
return false;
}
}
}
/// <summary>
/// Stops a remote client from the server, disconnecting the client.
/// </summary>
/// <param name="connectionId"></param>
/// <param name="socket"></param>
private bool StopConnection(int connectionId, Connection socket)
{
socket.Close(false, 0, "Graceful disconnect");
_steamConnections.Remove(connectionId);
_steamIds.Remove(connectionId);
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Client with ConnectionID {connectionId} disconnected.");
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Stopped, connectionId, Transport.Index));
_cachedConnectionIds.Enqueue(connectionId);
return true;
}
/// <summary>
/// Called when a remote connection state changes.
/// </summary>
private void OnRemoteConnectionState(Connection conn, ConnectionInfo info)
{
ulong clientSteamID = info.Identity.SteamId;
if (info.State == ConnectionState.Connecting)
{
if (_steamConnections.Count >= _maximumClients)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Incoming connection {clientSteamID} was rejected because would exceed the maximum connection count.");
conn.Close(false, 0, "Max Connection Count");
return;
}
Result res;
if ((res = conn.Accept()) == Result.OK)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Accepting connection {clientSteamID}");
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection {clientSteamID} could not be accepted: {res.ToString()}");
}
}
else if (info.State == ConnectionState.Connected)
{
int connectionId = (_cachedConnectionIds.Count > 0) ? _cachedConnectionIds.Dequeue() : _nextConnectionId++;
_steamConnections.Add(conn, connectionId);
_steamIds.Add(clientSteamID, connectionId);
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Started, connectionId, Transport.Index));
}
else if (info.State == ConnectionState.ClosedByPeer || info.State == ConnectionState.ProblemDetectedLocally)
{
if (_steamConnections.TryGetValue(conn, out int connId))
{
StopConnection(connId, conn);
}
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection {clientSteamID} state changed: {info.State.ToString()}");
}
}
/// <summary>
/// Allows for Outgoing queue to be iterated.
/// </summary>
internal void IterateOutgoing()
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
foreach (Connection conn in _steamConnections.FirstTypes)
{
conn.Flush();
}
}
/// <summary>
/// Iterates the Incoming queue.
/// </summary>
/// <param name="transport"></param>
internal void IterateIncoming()
{
//Stopped or trying to stop.
if (base.GetLocalConnectionState() == LocalConnectionState.Stopped || base.GetLocalConnectionState() == LocalConnectionState.Stopping)
return;
//Iterate local client packets first.
while (_clientHostIncoming.Count > 0)
{
LocalPacket packet = _clientHostIncoming.Dequeue();
ArraySegment<byte> segment = new ArraySegment<byte>(packet.Data, 0, packet.Length);
base.Transport.HandleServerReceivedDataArgs(new ServerReceivedDataArgs(segment, (Channel)packet.Channel, FishyFacepunch.CLIENT_HOST_ID, Transport.Index));
packet.Dispose();
}
_socket.Receive(MAX_MESSAGES);
}
private void OnMessageReceived(Connection conn, IntPtr dataPtr, int size)
{
(byte[] data, int ch) = ProcessMessage(dataPtr, size);
base.Transport.HandleServerReceivedDataArgs(new ServerReceivedDataArgs(new ArraySegment<byte>(data), (Channel)ch, _steamConnections[conn], Transport.Index));
}
/// <summary>
/// Sends data to a client.
/// </summary>
/// <param name="channelId"></param>
/// <param name="segment"></param>
/// <param name="connectionId"></param>
internal void SendToClient(byte channelId, ArraySegment<byte> segment, int connectionId)
{
if (base.GetLocalConnectionState() != LocalConnectionState.Started)
return;
//Check if sending local client first, send and exit if so.
if (connectionId == FishyFacepunch.CLIENT_HOST_ID)
{
if (_clientHost != null)
{
LocalPacket packet = new LocalPacket(segment, channelId);
_clientHost.ReceivedFromLocalServer(packet);
}
return;
}
if (_steamConnections.TryGetValue(connectionId, out Connection steamConn))
{
Result res = base.Send(steamConn, segment, channelId);
if (res == Result.NoConnection || res == Result.InvalidParam)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Common))
Debug.Log($"Connection to {connectionId} was lost.");
StopConnection(connectionId, steamConn);
}
else if (res != Result.OK)
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Could not send: {res.ToString()}");
}
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"ConnectionId {connectionId} does not exist, data will not be sent.");
}
}
/// <summary>
/// Gets the address of a remote connection Id.
/// </summary>
/// <param name="connectionId"></param>
/// <returns></returns>
internal string GetConnectionAddress(int connectionId)
{
if (_steamIds.TryGetValue(connectionId, out SteamId steamId))
{
return steamId.ToString();
}
else
{
if (base.Transport.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"ConnectionId {connectionId} is invalid; address cannot be returned.");
return string.Empty;
}
}
/// <summary>
/// Sets maximum number of clients allowed to connect to the server. If applied at runtime and clients exceed this value existing clients will stay connected but new clients may not connect.
/// </summary>
/// <param name="value"></param>
internal void SetMaximumClients(int value)
{
_maximumClients = Math.Min(value, FishyFacepunch.CLIENT_HOST_ID - 1);
}
internal int GetMaximumClients()
{
return _maximumClients;
}
#region ClientHost (local client).
/// <summary>
/// Sets ClientHost value.
/// </summary>
/// <param name="socket"></param>
internal void SetClientHostSocket(ClientHostSocket socket)
{
_clientHost = socket;
}
/// <summary>
/// Called when the local client stops.
/// </summary>
internal void OnClientHostState(bool started)
{
_clientHostStarted = started;
FishyFacepunch ff = (FishyFacepunch)base.Transport;
SteamId steamId = new SteamId()
{
Value = ff.LocalUserSteamID
};
//If not started flush incoming from local client.
if (!started && _clientHostStarted)
{
base.ClearQueue(_clientHostIncoming);
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Stopped, FishyFacepunch.CLIENT_HOST_ID, Transport.Index));
_steamIds.Remove(steamId);
}
//If started.
else if (started)
{
_steamIds[steamId] = FishyFacepunch.CLIENT_HOST_ID;
base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Started, FishyFacepunch.CLIENT_HOST_ID, Transport.Index));
}
_clientHostStarted = started;
}
/// <summary>
/// Queues a received packet from the local client.
/// </summary>
internal void ReceivedFromClientHost(LocalPacket packet)
{
if (!_clientHostStarted)
{
packet.Dispose();
return;
}
_clientHostIncoming.Enqueue(packet);
}
#endregion
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0f5d2e6e0da6a244a3fa589e4f6bba2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: