copy from fizzy steamworks

This commit is contained in:
JohnCorby 2023-06-09 18:23:40 -07:00
parent e7237e0b16
commit 6b1b729bea
15 changed files with 1511 additions and 734 deletions

View File

@ -1,88 +1,89 @@
using System.Collections;
using System.Collections.Generic;
namespace Mirror.FizzySteam;
public class BidirectionalDictionary<T1, T2> : IEnumerable
namespace Mirror.FizzySteam
{
private Dictionary<T1, T2> t1ToT2Dict = new Dictionary<T1, T2>();
private Dictionary<T2, T1> t2ToT1Dict = new Dictionary<T2, T1>();
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 IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
public int Count => t1ToT2Dict.Count;
public int Count => t1ToT2Dict.Count;
public void Add(T1 key, T2 value)
{
if (t1ToT2Dict.ContainsKey(key))
{
Remove(key);
}
public void Add(T1 key, T2 value)
{
if (t1ToT2Dict.ContainsKey(key))
{
Remove(key);
}
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
public void Add(T2 key, T1 value)
{
if (t2ToT1Dict.ContainsKey(key))
{
Remove(key);
}
public void Add(T2 key, T1 value)
{
if (t2ToT1Dict.ContainsKey(key))
{
Remove(key);
}
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
public T2 Get(T1 key) => t1ToT2Dict[key];
public T2 Get(T1 key) => t1ToT2Dict[key];
public T1 Get(T2 key) => t2ToT1Dict[key];
public T1 Get(T2 key) => t2ToT1Dict[key];
public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value);
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 TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value);
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
public bool Contains(T2 key) => t2ToT1Dict.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 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 T1 this[T2 key]
{
get => t2ToT1Dict[key];
set
{
Add(key, value);
}
}
public T2 this[T1 key]
{
get => t1ToT2Dict[key];
set
{
Add(key, value);
}
}
}
public T2 this[T1 key]
{
get => t1ToT2Dict[key];
set
{
Add(key, value);
}
}
}
}

View File

@ -1,228 +1,311 @@
using Steamworks;
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.IO;
using UnityEngine;
namespace Mirror.FizzySteam;
public class FizzySteamworks : Transport
namespace Mirror.FizzySteam
{
private const string STEAM_SCHEME = "steam";
[HelpURL("https://github.com/Chykary/FizzySteamworks")]
public class FizzySteamworks : Transport
{
private const string STEAM_SCHEME = "steam";
private static SteamClient client;
private static SteamServer server;
private static IClient client;
private static IServer server;
[SerializeField]
public EP2PSend[] Channels = new EP2PSend[2] { EP2PSend.k_EP2PSendReliable, EP2PSend.k_EP2PSendUnreliableNoDelay };
[SerializeField]
public EP2PSend[] Channels = new EP2PSend[2] { EP2PSend.k_EP2PSendReliable, EP2PSend.k_EP2PSendUnreliableNoDelay };
[Tooltip("Timeout for connecting in seconds.")]
public int Timeout = 25;
[Tooltip("Timeout for connecting in seconds.")]
public int Timeout = 25;
[Tooltip("Allow or disallow P2P connections to fall back to being relayed through the Steam servers if a direct connection or NAT-traversal cannot be established.")]
public bool AllowSteamRelay = true;
private void OnEnable()
{
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for FizzySteamworks.");
Invoke(nameof(InitRelayNetworkAccess), 1f);
}
[Tooltip("Use SteamSockets instead of the (deprecated) SteamNetworking. This will always use Relay.")]
public bool UseNextGenSteamNetworking = true;
public override void ClientEarlyUpdate()
{
if (enabled)
{
client?.ReceiveData();
}
}
private void OnEnable()
{
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for FizzySteamworks.");
Invoke(nameof(InitRelayNetworkAccess), 1f);
}
public override void ServerEarlyUpdate()
{
if (enabled)
{
server?.ReceiveData();
}
}
public override void ClientEarlyUpdate()
{
if (enabled)
{
client?.ReceiveData();
}
}
public override void ClientLateUpdate()
{
if (enabled)
{
client?.FlushData();
}
}
public override void ServerEarlyUpdate()
{
if (enabled)
{
server?.ReceiveData();
}
}
public override void ServerLateUpdate()
{
if (enabled)
{
server?.FlushData();
}
}
public override void ClientLateUpdate()
{
if (enabled)
{
client?.FlushData();
}
}
public override bool ClientConnected() => ClientActive() && client.Connected;
public override void ClientConnect(string address)
{
try
{
SteamNetworkingUtils.InitRelayNetworkAccess();
InitRelayNetworkAccess();
public override void ServerLateUpdate()
{
if (enabled)
{
server?.FlushData();
}
}
if (ServerActive())
{
Debug.LogError("Transport already running as server!");
return;
}
public override bool ClientConnected() => ClientActive() && client.Connected;
public override void ClientConnect(string address)
{
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
if (!ClientActive() || client.Error)
{
Debug.Log($"Starting client [SteamSockets], target address {address}.");
client = SteamClient.CreateClient(this, address);
}
else
{
Debug.LogError("Client already running!");
}
}
catch (Exception ex)
{
Debug.LogError("Exception: " + ex.Message + ". Client could not be started.");
OnClientDisconnected.Invoke();
}
}
InitRelayNetworkAccess();
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != STEAM_SCHEME)
{
throw new ArgumentException($"Invalid url {uri}, use {STEAM_SCHEME}://SteamID instead", nameof(uri));
}
if (ServerActive())
{
Debug.LogError("Transport already running as server!");
return;
}
ClientConnect(uri.Host);
}
if (!ClientActive() || client.Error)
{
if (UseNextGenSteamNetworking)
{
Debug.Log($"Starting client [SteamSockets], target address {address}.");
client = NextClient.CreateClient(this, address);
}
else
{
Debug.Log($"Starting client [DEPRECATED SteamNetworking], target address {address}. Relay enabled: {AllowSteamRelay}");
SteamNetworking.AllowP2PPacketRelay(AllowSteamRelay);
client = LegacyClient.CreateClient(this, address);
}
}
else
{
Debug.LogError("Client already running!");
}
}
catch (Exception ex)
{
Debug.LogError("Exception: " + ex.Message + ". Client could not be started.");
OnClientDisconnected.Invoke();
}
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
var data = new byte[segment.Count];
Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count);
client.Send(data, channelId);
}
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != STEAM_SCHEME)
throw new ArgumentException($"Invalid url {uri}, use {STEAM_SCHEME}://SteamID instead", nameof(uri));
public override void ClientDisconnect()
{
if (ClientActive())
{
Shutdown();
}
}
ClientConnect(uri.Host);
}
public bool ClientActive() => client != null;
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
byte[] data = new byte[segment.Count];
Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count);
client.Send(data, channelId);
}
public override bool ServerActive() => server != null;
public override void ClientDisconnect()
{
if (ClientActive())
{
Shutdown();
}
}
public bool ClientActive() => client != null;
public override void ServerStart()
{
try
{
SteamNetworkingUtils.InitRelayNetworkAccess();
InitRelayNetworkAccess();
if (ClientActive())
{
Debug.LogError("Transport already running as client!");
return;
}
public override bool ServerActive() => server != null;
public override void ServerStart()
{
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
if (!ServerActive())
{
Debug.Log($"Starting server [SteamSockets].");
server = SteamServer.CreateServer(this, NetworkManager.singleton.maxConnections);
}
else
{
Debug.LogError("Server already started!");
}
}
catch (Exception ex)
{
Debug.LogException(ex);
return;
}
}
public override Uri ServerUri()
{
var steamBuilder = new UriBuilder
{
Scheme = STEAM_SCHEME,
Host = SteamUser.GetSteamID().m_SteamID.ToString()
};
InitRelayNetworkAccess();
return steamBuilder.Uri;
}
if (ClientActive())
{
Debug.LogError("Transport already running as client!");
return;
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (ServerActive())
{
var data = new byte[segment.Count];
Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count);
server.Send(connectionId, data, channelId);
}
}
public override void ServerDisconnect(int connectionId)
{
if (ServerActive())
{
server.Disconnect(connectionId);
}
}
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
public override void ServerStop()
{
if (ServerActive())
{
Shutdown();
}
}
if (!ServerActive())
{
if (UseNextGenSteamNetworking)
{
Debug.Log($"Starting server [SteamSockets].");
server = NextServer.CreateServer(this, NetworkManager.singleton.maxConnections);
}
else
{
Debug.Log($"Starting server [DEPRECATED SteamNetworking]. Relay enabled: {AllowSteamRelay}");
#if UNITY_SERVER
SteamGameServerNetworking.AllowP2PPacketRelay(AllowSteamRelay);
#else
public override void Shutdown()
{
if (server != null)
{
server.Shutdown();
server = null;
Debug.Log("Transport shut down - was server.");
}
SteamNetworking.AllowP2PPacketRelay(AllowSteamRelay);
#endif
server = LegacyServer.CreateServer(this, NetworkManager.singleton.maxConnections);
}
}
else
{
Debug.LogError("Server already started!");
}
}
catch (Exception ex)
{
Debug.LogException(ex);
return;
}
}
if (client != null)
{
client.Disconnect();
client = null;
Debug.Log("Transport shut down - was client.");
}
}
public override Uri ServerUri()
{
var steamBuilder = new UriBuilder
{
Scheme = STEAM_SCHEME,
#if UNITY_SERVER
Host = SteamGameServer.GetSteamID().m_SteamID.ToString()
#else
Host = SteamUser.GetSteamID().m_SteamID.ToString()
#endif
};
public override int GetMaxPacketSize(int channelId)
=> Constants.k_cbMaxSteamNetworkingSocketsMessageSizeSend;
return steamBuilder.Uri;
}
public override bool Available()
{
try
{
SteamNetworkingUtils.InitRelayNetworkAccess();
return true;
}
catch
{
return false;
}
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (ServerActive())
{
byte[] data = new byte[segment.Count];
Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count);
server.Send(connectionId, data, channelId);
}
}
public override void ServerDisconnect(int connectionId)
{
if (ServerActive())
{
server.Disconnect(connectionId);
}
}
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
public override void ServerStop()
{
if (ServerActive())
{
Shutdown();
}
}
private void InitRelayNetworkAccess()
{
try
{
SteamNetworkingUtils.InitRelayNetworkAccess();
}
catch { }
}
public override void Shutdown()
{
if (server != null)
{
server.Shutdown();
server = null;
Debug.Log("Transport shut down - was server.");
}
private void OnDestroy()
=> Shutdown();
if (client != null)
{
client.Disconnect();
client = null;
Debug.Log("Transport shut down - was client.");
}
}
public override int GetMaxPacketSize(int channelId)
{
if (UseNextGenSteamNetworking)
{
return Constants.k_cbMaxSteamNetworkingSocketsMessageSizeSend;
}
else
{
if (channelId >= Channels.Length)
{
Debug.LogError("Channel Id exceeded configured channels! Please configure more channels.");
return 1200;
}
switch (Channels[channelId])
{
case EP2PSend.k_EP2PSendUnreliable:
case EP2PSend.k_EP2PSendUnreliableNoDelay:
return 1200;
case EP2PSend.k_EP2PSendReliable:
case EP2PSend.k_EP2PSendReliableWithBuffering:
return 1048576;
default:
throw new NotSupportedException();
}
}
}
public override bool Available()
{
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
return true;
}
catch
{
return false;
}
}
private void InitRelayNetworkAccess()
{
try
{
if (UseNextGenSteamNetworking)
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
}
}
catch { }
}
private void OnDestroy()
{
Shutdown();
}
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,14 @@
namespace Mirror.FizzySteam
{
public interface IClient
{
bool Connected { get; }
bool Error { get; }
void ReceiveData();
void Disconnect();
void FlushData();
void Send(byte[] data, int channelId);
}
}

View File

@ -0,0 +1,12 @@
namespace Mirror.FizzySteam
{
public interface IServer
{
void ReceiveData();
void Send(int connectionId, byte[] data, int channelId);
void Disconnect(int connectionId);
void FlushData();
string ServerGetClientAddress(int connectionId);
void Shutdown();
}
}

37
FizzySteamworks/LICENSE Normal file
View File

@ -0,0 +1,37 @@
MIT License
Copyright Fizz Cube Ltd (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
===
Copyright Marco Hoffmann (c) 2020
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License

View File

@ -0,0 +1,177 @@
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace Mirror.FizzySteam
{
public class LegacyClient : LegacyCommon, IClient
{
public bool Connected { get; private set; }
public bool Error { get; private set; }
private event Action<byte[], int> OnReceivedData;
private event Action OnConnected;
private event Action OnDisconnected;
private TimeSpan ConnectionTimeout;
private CSteamID hostSteamID = CSteamID.Nil;
private TaskCompletionSource<Task> connectedComplete;
private CancellationTokenSource cancelToken;
private LegacyClient(FizzySteamworks transport) : base(transport)
{
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout));
}
public static LegacyClient CreateClient(FizzySteamworks transport, string host)
{
LegacyClient c = new LegacyClient(transport);
c.OnConnected += () => transport.OnClientConnected.Invoke();
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
c.OnReceivedData += (data, channel) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), channel);
try
{
#if UNITY_SERVER
InteropHelp.TestIfAvailableGameServer();
#else
InteropHelp.TestIfAvailableClient();
#endif
c.Connect(host);
}
catch
{
Debug.LogError("SteamWorks not initialized.");
c.OnConnectionFailed(CSteamID.Nil);
}
return c;
}
private async void Connect(string host)
{
cancelToken = new CancellationTokenSource();
try
{
hostSteamID = new CSteamID(UInt64.Parse(host));
connectedComplete = new TaskCompletionSource<Task>();
OnConnected += SetConnectedComplete;
SendInternal(hostSteamID, InternalMessages.CONNECT);
Task connectedCompleteTask = connectedComplete.Task;
Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token);
if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask)
{
if (cancelToken.IsCancellationRequested)
{
Debug.LogError($"The connection attempt was cancelled.");
}
else if (timeOutTask.IsCompleted)
{
Debug.LogError($"Connection to {host} timed out.");
}
OnConnected -= SetConnectedComplete;
OnConnectionFailed(hostSteamID);
}
OnConnected -= SetConnectedComplete;
}
catch (FormatException)
{
Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?");
Error = true;
OnConnectionFailed(hostSteamID);
}
catch (Exception ex)
{
Debug.LogError(ex.Message);
Error = true;
OnConnectionFailed(hostSteamID);
}
finally
{
if (Error)
{
OnConnectionFailed(CSteamID.Nil);
}
}
}
public void Disconnect()
{
Debug.Log("Sending Disconnect message");
SendInternal(hostSteamID, InternalMessages.DISCONNECT);
Dispose();
cancelToken?.Cancel();
WaitForClose(hostSteamID);
}
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
protected override void OnReceiveData(byte[] data, CSteamID clientSteamID, int channel)
{
if (clientSteamID != hostSteamID)
{
Debug.LogError("Received a message from an unknown");
return;
}
OnReceivedData.Invoke(data, channel);
}
protected override void OnNewConnection(P2PSessionRequest_t result)
{
if (hostSteamID == result.m_steamIDRemote)
{
SteamNetworking.AcceptP2PSessionWithUser(result.m_steamIDRemote);
}
else
{
Debug.LogError("P2P Acceptance Request from unknown host ID.");
}
}
protected override void OnReceiveInternalData(InternalMessages type, CSteamID clientSteamID)
{
switch (type)
{
case InternalMessages.ACCEPT_CONNECT:
if (!Connected)
{
Connected = true;
OnConnected.Invoke();
Debug.Log("Connection established.");
}
break;
case InternalMessages.DISCONNECT:
if (Connected)
{
Connected = false;
Debug.Log("Disconnected.");
OnDisconnected.Invoke();
}
break;
default:
Debug.Log("Received unknown message type");
break;
}
}
public void Send(byte[] data, int channelId) => Send(hostSteamID, data, channelId);
protected override void OnConnectionFailed(CSteamID remoteId) => OnDisconnected.Invoke();
public void FlushData() { }
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,180 @@
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.Collections;
using UnityEngine;
namespace Mirror.FizzySteam
{
public abstract class LegacyCommon
{
private EP2PSend[] channels;
private int internal_ch => channels.Length;
protected enum InternalMessages : byte
{
CONNECT,
ACCEPT_CONNECT,
DISCONNECT
}
private Callback<P2PSessionRequest_t> callback_OnNewConnection = null;
private Callback<P2PSessionConnectFail_t> callback_OnConnectFail = null;
protected readonly FizzySteamworks transport;
protected LegacyCommon(FizzySteamworks transport)
{
channels = transport.Channels;
callback_OnNewConnection = Callback<P2PSessionRequest_t>.Create(OnNewConnection);
callback_OnConnectFail = Callback<P2PSessionConnectFail_t>.Create(OnConnectFail);
this.transport = transport;
}
protected void Dispose()
{
if (callback_OnNewConnection != null)
{
callback_OnNewConnection.Dispose();
callback_OnNewConnection = null;
}
if (callback_OnConnectFail != null)
{
callback_OnConnectFail.Dispose();
callback_OnConnectFail = null;
}
}
protected abstract void OnNewConnection(P2PSessionRequest_t result);
private void OnConnectFail(P2PSessionConnectFail_t result)
{
OnConnectionFailed(result.m_steamIDRemote);
CloseP2PSessionWithUser(result.m_steamIDRemote);
switch (result.m_eP2PSessionError)
{
case 1:
Debug.LogError("Connection failed: The target user is not running the same game.");
break;
case 2:
Debug.LogError("Connection failed: The local user doesn't own the app that is running.");
break;
case 3:
Debug.LogError("Connection failed: Target user isn't connected to Steam.");
break;
case 4:
Debug.LogError("Connection failed: The connection timed out because the target user didn't respond.");
break;
default:
Debug.LogError("Connection failed: Unknown error.");
break;
}
}
protected void SendInternal(CSteamID target, InternalMessages type)
{
#if UNITY_SERVER
SteamGameServerNetworking.SendP2PPacket(target, new byte[] { (byte)type }, 1, EP2PSend.k_EP2PSendReliable, internal_ch);
#else
SteamNetworking.SendP2PPacket(target, new byte[] { (byte)type }, 1, EP2PSend.k_EP2PSendReliable, internal_ch);
#endif
}
protected void Send(CSteamID host, byte[] msgBuffer, int channel)
{
#if UNITY_SERVER
SteamGameServerNetworking.SendP2PPacket(host, msgBuffer, (uint)msgBuffer.Length, channels[Mathf.Min(channel, channels.Length - 1)], channel);
#else
SteamNetworking.SendP2PPacket(host, msgBuffer, (uint)msgBuffer.Length, channels[Mathf.Min(channel, channels.Length - 1)], channel);
#endif
}
private bool Receive(out CSteamID clientSteamID, out byte[] receiveBuffer, int channel)
{
#if UNITY_SERVER
if (SteamGameServerNetworking.IsP2PPacketAvailable(out uint packetSize, channel))
{
receiveBuffer = new byte[packetSize];
return SteamGameServerNetworking.ReadP2PPacket(receiveBuffer, packetSize, out _, out clientSteamID, channel);
}
#else
if (SteamNetworking.IsP2PPacketAvailable(out uint packetSize, channel))
{
receiveBuffer = new byte[packetSize];
return SteamNetworking.ReadP2PPacket(receiveBuffer, packetSize, out _, out clientSteamID, channel);
}
#endif
receiveBuffer = null;
clientSteamID = CSteamID.Nil;
return false;
}
protected void CloseP2PSessionWithUser(CSteamID clientSteamID)
{
#if UNITY_SERVER
SteamGameServerNetworking.CloseP2PSessionWithUser(clientSteamID);
#else
SteamNetworking.CloseP2PSessionWithUser(clientSteamID);
#endif
}
protected void WaitForClose(CSteamID cSteamID)
{
if (transport.enabled)
{
transport.StartCoroutine(DelayedClose(cSteamID));
}
else
{
CloseP2PSessionWithUser(cSteamID);
}
}
private IEnumerator DelayedClose(CSteamID cSteamID)
{
yield return null;
CloseP2PSessionWithUser(cSteamID);
}
public void ReceiveData()
{
try
{
while (transport.enabled && Receive(out CSteamID clientSteamID, out byte[] internalMessage, internal_ch))
{
if (internalMessage.Length == 1)
{
OnReceiveInternalData((InternalMessages)internalMessage[0], clientSteamID);
return; // Wait one frame
}
else
{
Debug.Log("Incorrect package length on internal channel.");
}
}
for (int chNum = 0; chNum < channels.Length; chNum++)
{
while (transport.enabled && Receive(out CSteamID clientSteamID, out byte[] receiveBuffer, chNum))
{
OnReceiveData(receiveBuffer, clientSteamID, chNum);
}
}
}
catch (Exception e)
{
Debug.LogException(e);
}
}
protected abstract void OnReceiveInternalData(InternalMessages type, CSteamID clientSteamID);
protected abstract void OnReceiveData(byte[] data, CSteamID clientSteamID, int channel);
protected abstract void OnConnectionFailed(CSteamID remoteId);
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,170 @@
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Mirror.FizzySteam
{
public class LegacyServer : LegacyCommon, IServer
{
private event Action<int> OnConnected;
private event Action<int, byte[], int> OnReceivedData;
private event Action<int> OnDisconnected;
private event Action<int, TransportError, string> OnReceivedError;
private BidirectionalDictionary<CSteamID, int> steamToMirrorIds;
private int maxConnections;
private int nextConnectionID;
public static LegacyServer CreateServer(FizzySteamworks transport, int maxConnections)
{
LegacyServer s = new LegacyServer(transport, maxConnections);
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
s.OnReceivedData += (id, data, channel) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), channel);
s.OnReceivedError += (id, error, reason) => transport.OnServerError.Invoke(id, error, reason);
try
{
#if UNITY_SERVER
InteropHelp.TestIfAvailableGameServer();
#else
InteropHelp.TestIfAvailableClient();
#endif
}
catch
{
Debug.LogError("SteamWorks not initialized.");
}
return s;
}
private LegacyServer(FizzySteamworks transport, int maxConnections) : base(transport)
{
this.maxConnections = maxConnections;
steamToMirrorIds = new BidirectionalDictionary<CSteamID, int>();
nextConnectionID = 1;
}
protected override void OnNewConnection(P2PSessionRequest_t result)
{
#if UNITY_SERVER
SteamGameServerNetworking.AcceptP2PSessionWithUser(result.m_steamIDRemote);
#else
SteamNetworking.AcceptP2PSessionWithUser(result.m_steamIDRemote);
#endif
}
protected override void OnReceiveInternalData(InternalMessages type, CSteamID clientSteamID)
{
switch (type)
{
case InternalMessages.CONNECT:
if (steamToMirrorIds.Count >= maxConnections)
{
SendInternal(clientSteamID, InternalMessages.DISCONNECT);
return;
}
SendInternal(clientSteamID, InternalMessages.ACCEPT_CONNECT);
int connectionId = nextConnectionID++;
steamToMirrorIds.Add(clientSteamID, connectionId);
OnConnected.Invoke(connectionId);
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
break;
case InternalMessages.DISCONNECT:
if (steamToMirrorIds.TryGetValue(clientSteamID, out int connId))
{
OnDisconnected.Invoke(connId);
CloseP2PSessionWithUser(clientSteamID);
steamToMirrorIds.Remove(clientSteamID);
Debug.Log($"Client with SteamID {clientSteamID} disconnected.");
}
break;
default:
Debug.Log("Received unknown message type");
break;
}
}
protected override void OnReceiveData(byte[] data, CSteamID clientSteamID, int channel)
{
if (steamToMirrorIds.TryGetValue(clientSteamID, out int connectionId))
{
OnReceivedData.Invoke(connectionId, data, channel);
}
else
{
CloseP2PSessionWithUser(clientSteamID);
Debug.LogError("Data received from steam client thats not known " + clientSteamID);
OnReceivedError.Invoke(-1, TransportError.DnsResolve, "ERROR Unknown SteamID");
}
}
public void Disconnect(int connectionId)
{
if (steamToMirrorIds.TryGetValue(connectionId, out CSteamID steamID))
{
SendInternal(steamID, InternalMessages.DISCONNECT);
steamToMirrorIds.Remove(connectionId);
}
else
{
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
}
}
public void Shutdown()
{
foreach (KeyValuePair<CSteamID, int> client in steamToMirrorIds)
{
Disconnect(client.Value);
WaitForClose(client.Key);
}
Dispose();
}
public void Send(int connectionId, byte[] data, int channelId)
{
if (steamToMirrorIds.TryGetValue(connectionId, out CSteamID steamId))
{
Send(steamId, data, channelId);
}
else
{
Debug.LogError("Trying to send on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
}
}
public string ServerGetClientAddress(int connectionId)
{
if (steamToMirrorIds.TryGetValue(connectionId, out CSteamID steamId))
{
return steamId.ToString();
}
else
{
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
return string.Empty;
}
}
protected override void OnConnectionFailed(CSteamID remoteId)
{
int connectionId = steamToMirrorIds.TryGetValue(remoteId, out int connId) ? connId : nextConnectionID++;
OnDisconnected.Invoke(connectionId);
steamToMirrorIds.Remove(remoteId);
}
public void FlushData() { }
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,226 @@
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace Mirror.FizzySteam
{
public class NextClient : NextCommon, IClient
{
public bool Connected { get; private set; }
public bool Error { get; private set; }
private TimeSpan ConnectionTimeout;
private event Action<byte[], int> OnReceivedData;
private event Action OnConnected;
private event Action OnDisconnected;
private Callback<SteamNetConnectionStatusChangedCallback_t> c_onConnectionChange = null;
private CancellationTokenSource cancelToken;
private TaskCompletionSource<Task> connectedComplete;
private CSteamID hostSteamID = CSteamID.Nil;
private HSteamNetConnection HostConnection;
private List<Action> BufferedData;
private NextClient(FizzySteamworks transport)
{
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout));
BufferedData = new List<Action>();
}
public static NextClient CreateClient(FizzySteamworks transport, string host)
{
NextClient c = new NextClient(transport);
c.OnConnected += () => transport.OnClientConnected.Invoke();
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
c.OnReceivedData += (data, ch) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), ch);
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
c.Connect(host);
}
catch (Exception ex)
{
Debug.LogException(ex);
c.OnConnectionFailed();
}
return c;
}
private async void Connect(string host)
{
cancelToken = new CancellationTokenSource();
c_onConnectionChange = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
try
{
hostSteamID = new CSteamID(UInt64.Parse(host));
connectedComplete = new TaskCompletionSource<Task>();
OnConnected += SetConnectedComplete;
SteamNetworkingIdentity smi = new SteamNetworkingIdentity();
smi.SetSteamID(hostSteamID);
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
HostConnection = SteamNetworkingSockets.ConnectP2P(ref smi, 0, options.Length, options);
Task connectedCompleteTask = connectedComplete.Task;
Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token);
if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask)
{
if (cancelToken.IsCancellationRequested)
{
Debug.LogError($"The connection attempt was cancelled.");
}
else if (timeOutTask.IsCompleted)
{
Debug.LogError($"Connection to {host} timed out.");
}
OnConnected -= SetConnectedComplete;
OnConnectionFailed();
}
OnConnected -= SetConnectedComplete;
}
catch (FormatException)
{
Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?");
Error = true;
OnConnectionFailed();
}
catch (Exception ex)
{
Debug.LogError(ex.Message);
Error = true;
OnConnectionFailed();
}
finally
{
if (Error)
{
Debug.LogError("Connection failed.");
OnConnectionFailed();
}
}
}
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t param)
{
ulong clientSteamID = param.m_info.m_identityRemote.GetSteamID64();
if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
{
Connected = true;
OnConnected.Invoke();
Debug.Log("Connection established.");
if (BufferedData.Count > 0)
{
Debug.Log($"{BufferedData.Count} received before connection was established. Processing now.");
{
foreach (Action a in BufferedData)
{
a();
}
}
}
}
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
Debug.Log($"Connection was closed by peer, {param.m_info.m_szEndDebug}");
Disconnect();
}
else
{
Debug.Log($"Connection state changed: {param.m_info.m_eState.ToString()} - {param.m_info.m_szEndDebug}");
}
}
public void Disconnect()
{
cancelToken?.Cancel();
Dispose();
if (HostConnection.m_HSteamNetConnection != 0)
{
Debug.Log("Sending Disconnect message");
SteamNetworkingSockets.CloseConnection(HostConnection, 0, "Graceful disconnect", false);
HostConnection.m_HSteamNetConnection = 0;
}
}
protected void Dispose()
{
if (c_onConnectionChange != null)
{
c_onConnectionChange.Dispose();
c_onConnectionChange = null;
}
}
private void InternalDisconnect()
{
Connected = false;
OnDisconnected.Invoke();
Debug.Log("Disconnected.");
SteamNetworkingSockets.CloseConnection(HostConnection, 0, "Disconnected", false);
}
public void ReceiveData()
{
IntPtr[] ptrs = new IntPtr[MAX_MESSAGES];
int messageCount;
if ((messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(HostConnection, ptrs, MAX_MESSAGES)) > 0)
{
for (int i = 0; i < messageCount; i++)
{
(byte[] data, int ch) = ProcessMessage(ptrs[i]);
if (Connected)
{
OnReceivedData(data, ch);
}
else
{
BufferedData.Add(() => OnReceivedData(data, ch));
}
}
}
}
public void Send(byte[] data, int channelId)
{
EResult res = SendSocket(HostConnection, data, channelId);
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
{
Debug.Log($"Connection to server was lost.");
InternalDisconnect();
}
else if (res != EResult.k_EResultOK)
{
Debug.LogError($"Could not send: {res.ToString()}");
}
}
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
private void OnConnectionFailed() => OnDisconnected.Invoke();
public void FlushData()
{
SteamNetworkingSockets.FlushMessagesOnConnection(HostConnection);
}
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,48 @@
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Mirror.FizzySteam
{
public abstract class NextCommon
{
protected const int MAX_MESSAGES = 256;
protected EResult SendSocket(HSteamNetConnection conn, byte[] data, int channelId)
{
Array.Resize(ref data, data.Length + 1);
data[data.Length - 1] = (byte)channelId;
GCHandle pinnedArray = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr pData = pinnedArray.AddrOfPinnedObject();
int sendFlag = channelId == Channels.Unreliable ? Constants.k_nSteamNetworkingSend_Unreliable : Constants.k_nSteamNetworkingSend_Reliable;
#if UNITY_SERVER
EResult res = SteamGameServerNetworkingSockets.SendMessageToConnection(conn, pData, (uint)data.Length, sendFlag, out long _);
#else
EResult res = SteamNetworkingSockets.SendMessageToConnection(conn, pData, (uint)data.Length, sendFlag, out long _);
#endif
if (res != EResult.k_EResultOK)
{
Debug.LogWarning($"Send issue: {res}");
}
pinnedArray.Free();
return res;
}
protected (byte[], int) ProcessMessage(IntPtr ptrs)
{
SteamNetworkingMessage_t data = Marshal.PtrToStructure<SteamNetworkingMessage_t>(ptrs);
byte[] managedArray = new byte[data.m_cbSize];
Marshal.Copy(data.m_pData, managedArray, 0, data.m_cbSize);
SteamNetworkingMessage_t.Release(ptrs);
int channel = managedArray[managedArray.Length - 1];
Array.Resize(ref managedArray, managedArray.Length - 1);
return (managedArray, channel);
}
}
}
#endif // !DISABLESTEAMWORKS

View File

@ -0,0 +1,246 @@
#if !DISABLESTEAMWORKS
using Steamworks;
using System;
using System.Linq;
using UnityEngine;
namespace Mirror.FizzySteam
{
public class NextServer : NextCommon, IServer
{
private event Action<int> OnConnected;
private event Action<int, byte[], int> OnReceivedData;
private event Action<int> OnDisconnected;
private event Action<int, TransportError, string> OnReceivedError;
private BidirectionalDictionary<HSteamNetConnection, int> connToMirrorID;
private BidirectionalDictionary<CSteamID, int> steamIDToMirrorID;
private int maxConnections;
private int nextConnectionID;
private HSteamListenSocket listenSocket;
private Callback<SteamNetConnectionStatusChangedCallback_t> c_onConnectionChange = null;
private NextServer(int maxConnections)
{
this.maxConnections = maxConnections;
connToMirrorID = new BidirectionalDictionary<HSteamNetConnection, int>();
steamIDToMirrorID = new BidirectionalDictionary<CSteamID, int>();
nextConnectionID = 1;
c_onConnectionChange = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
}
public static NextServer CreateServer(FizzySteamworks transport, int maxConnections)
{
NextServer s = new NextServer(maxConnections);
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
s.OnReceivedData += (id, data, ch) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), ch);
s.OnReceivedError += (id, error, reason) => transport.OnServerError.Invoke(id, error, reason);
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
}
catch (Exception ex)
{
Debug.LogException(ex);
}
s.Host();
return s;
}
private void Host()
{
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
#if UNITY_SERVER
listenSocket = SteamGameServerNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
#else
listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
#endif
}
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t param)
{
ulong clientSteamID = param.m_info.m_identityRemote.GetSteamID64();
if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting)
{
if (connToMirrorID.Count >= maxConnections)
{
Debug.Log($"Incoming connection {clientSteamID} would exceed max connection count. Rejecting.");
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseConnection(param.m_hConn, 0, "Max Connection Count", false);
#else
SteamNetworkingSockets.CloseConnection(param.m_hConn, 0, "Max Connection Count", false);
#endif
return;
}
EResult res;
#if UNITY_SERVER
if ((res = SteamGameServerNetworkingSockets.AcceptConnection(param.m_hConn)) == EResult.k_EResultOK)
#else
if ((res = SteamNetworkingSockets.AcceptConnection(param.m_hConn)) == EResult.k_EResultOK)
#endif
{
Debug.Log($"Accepting connection {clientSteamID}");
}
else
{
Debug.Log($"Connection {clientSteamID} could not be accepted: {res.ToString()}");
}
}
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
{
int connectionId = nextConnectionID++;
connToMirrorID.Add(param.m_hConn, connectionId);
steamIDToMirrorID.Add(param.m_info.m_identityRemote.GetSteamID(), connectionId);
OnConnected.Invoke(connectionId);
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
}
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
if (connToMirrorID.TryGetValue(param.m_hConn, out int connId))
{
InternalDisconnect(connId, param.m_hConn);
}
}
else
{
Debug.Log($"Connection {clientSteamID} state changed: {param.m_info.m_eState.ToString()}");
}
}
private void InternalDisconnect(int connId, HSteamNetConnection socket)
{
OnDisconnected.Invoke(connId);
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseConnection(socket, 0, "Graceful disconnect", false);
#else
SteamNetworkingSockets.CloseConnection(socket, 0, "Graceful disconnect", false);
#endif
connToMirrorID.Remove(connId);
steamIDToMirrorID.Remove(connId);
Debug.Log($"Client with ConnectionID {connId} disconnected.");
}
public void Disconnect(int connectionId)
{
if (connToMirrorID.TryGetValue(connectionId, out HSteamNetConnection conn))
{
Debug.Log($"Connection id {connectionId} disconnected.");
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseConnection(conn, 0, "Disconnected by server", false);
#else
SteamNetworkingSockets.CloseConnection(conn, 0, "Disconnected by server", false);
#endif
steamIDToMirrorID.Remove(connectionId);
connToMirrorID.Remove(connectionId);
OnDisconnected(connectionId);
}
else
{
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
}
}
public void FlushData()
{
foreach (HSteamNetConnection conn in connToMirrorID.FirstTypes)
{
#if UNITY_SERVER
SteamGameServerNetworkingSockets.FlushMessagesOnConnection(conn);
#else
SteamNetworkingSockets.FlushMessagesOnConnection(conn);
#endif
}
}
public void ReceiveData()
{
foreach (HSteamNetConnection conn in connToMirrorID.FirstTypes.ToList())
{
if (connToMirrorID.TryGetValue(conn, out int connId))
{
IntPtr[] ptrs = new IntPtr[MAX_MESSAGES];
int messageCount;
#if UNITY_SERVER
if ((messageCount = SteamGameServerNetworkingSockets.ReceiveMessagesOnConnection(conn, ptrs, MAX_MESSAGES)) > 0)
#else
if ((messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(conn, ptrs, MAX_MESSAGES)) > 0)
#endif
{
for (int i = 0; i < messageCount; i++)
{
(byte[] data, int ch) = ProcessMessage(ptrs[i]);
OnReceivedData(connId, data, ch);
}
}
}
}
}
public void Send(int connectionId, byte[] data, int channelId)
{
if (connToMirrorID.TryGetValue(connectionId, out HSteamNetConnection conn))
{
EResult res = SendSocket(conn, data, channelId);
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
{
Debug.Log($"Connection to {connectionId} was lost.");
InternalDisconnect(connectionId, conn);
}
else if (res != EResult.k_EResultOK)
{
Debug.LogError($"Could not send: {res.ToString()}");
}
}
else
{
Debug.LogError("Trying to send on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
}
}
public string ServerGetClientAddress(int connectionId)
{
if (steamIDToMirrorID.TryGetValue(connectionId, out CSteamID steamId))
{
return steamId.ToString();
}
else
{
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
return string.Empty;
}
}
public void Shutdown()
{
#if UNITY_SERVER
SteamGameServerNetworkingSockets.CloseListenSocket(listenSocket);
#else
SteamNetworkingSockets.CloseListenSocket(listenSocket);
#endif
if (c_onConnectionChange != null)
{
c_onConnectionChange.Dispose();
c_onConnectionChange = null;
}
}
}
}
#endif // !DISABLESTEAMWORKS

55
FizzySteamworks/README.md Normal file
View File

@ -0,0 +1,55 @@
# FizzySteamworks
This is a community maintained repo forked from **[RayStorm](https://github.com/Raystorms/FizzySteamyMirror)**.
Mirror **[docs](https://mirror-networking.com/docs/Transports/Fizzy.html)** and the official community **[Discord](https://discord.gg/N9QVxbM)**.
FizzySteamworks brings together **[Steam](https://store.steampowered.com)** and **[Mirror](https://github.com/vis2k/Mirror)** . It supports both the old SteamNetworking and the new SteamSockets.
## Dependencies
You must have Mirror installed and working before you can use this transport.
**[Mirror](https://github.com/vis2k/Mirror)** FizzySteamworks is also obviously dependant on Mirror which is a streamline, bug fixed, maintained version of UNET for Unity.
You must have Steamworks.NET installed and working before you can use this transport.
**[Steamworks.NET](https://github.com/rlabrecque/Steamworks.NET)** FizzySteamworks relies on Steamworks.NET to communicate with the **[Steamworks API](https://partner.steamgames.com/doc/sdk)**. **Requires .Net 4.x**
## Installation
### Unity Package Manager
Unity Package Manager support is still fairly new but you can use it like so:
1. Open the Package Manager
2. Click the "+" (plus) button located in the upper left of the window
3. Select the "Add package from git URL..." option
4. Enter the following URL:
`https://github.com/Chykary/FizzySteamworks.git?path=/com.mirror.steamworks.net`
5. Click the "Add" button and wait several seconds for the system to download and install the Steamworks.NET package from GitHub.
### Manual
Fewer steps but more error prone and subject to being out of date with the latest changes:
1. Download the latest [unitypackage](https://github.com/Chykary/FizzySteamworks/releases) from the release section.
2. Import the package into Unity.
## Setting Up
1. Install Steamworks.NET instructions can be found [here](https://github.com/rlabrecque/Steamworks.NET).
2. Install Mirror **(Requires Mirror 35.0+)** from the Unity asset store **[Download Mirror](https://assetstore.unity.com/packages/tools/network/mirror-129321)**.
3. Install FizzySteamworks from package manager as discribed in the above Install step.
3. In your **"NetworkManager"** object replace **"KCP"** with **"FizzySteamworks"**.
## Host
To be able to have your game working you need to make sure you have Steam running in the background and that the Steam API initalized correctly. You can then call StartHost and use Mirror as you normally would.
## Client
To connect a client to a host or server you need the CSteamID of the target you wish to connect to this is used in place of IP/Port. If your creating a Peer to Peer architecture then you would use the CSteamID of the host, this is Steam user ID as a ulong value. If you are creating a Client Server architecture then you will be using the CSteamID issued to the Steam Game Server when it logs the Steam API on. This is an advanced use case supported by Heathen's Steamworks but requires additional custom code if your using Steamworks.NET directly.
1. Send the game to your buddy.
2. Your buddy needs the host or game server **steamID64** to be able to connect.
3. Place the **steamID64** into **"localhost"** then click **"Client"**
5. Then they will be connected to your server be that your machine as a P2P connection or yoru Steam Game Server as a Client Server connection.
## Testing your game locally
You cant connect to yourself locally while using **FizzySteamworks** since it's using Steams Networking which runs over Steam Client and addresses its connection based on the unique CSteamID of each actor. If you want to test your game locally you'll have to use **"Telepathy Transport"** instead of **"FizzySteamworks"**.

View File

@ -1,222 +0,0 @@
using Steamworks;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace Mirror.FizzySteam
{
public class SteamClient : SteamCommon
{
public bool Connected { get; private set; }
public bool Error { get; private set; }
private TimeSpan ConnectionTimeout;
private event Action<byte[], int> OnReceivedData;
private event Action OnConnected;
private event Action OnDisconnected;
private Callback<SteamNetConnectionStatusChangedCallback_t> c_onConnectionChange = null;
private CancellationTokenSource cancelToken;
private TaskCompletionSource<Task> connectedComplete;
private CSteamID hostSteamID = CSteamID.Nil;
private HSteamNetConnection HostConnection;
private List<Action> BufferedData;
private SteamClient(FizzySteamworks transport)
{
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout));
BufferedData = new List<Action>();
}
public static SteamClient CreateClient(FizzySteamworks transport, string host)
{
var c = new SteamClient(transport);
c.OnConnected += () => transport.OnClientConnected.Invoke();
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
c.OnReceivedData += (data, ch) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), ch);
try
{
SteamNetworkingUtils.InitRelayNetworkAccess();
c.Connect(host);
}
catch (Exception ex)
{
Debug.LogException(ex);
c.OnConnectionFailed();
}
return c;
}
private async void Connect(string host)
{
cancelToken = new CancellationTokenSource();
c_onConnectionChange = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
try
{
hostSteamID = new CSteamID(ulong.Parse(host));
connectedComplete = new TaskCompletionSource<Task>();
OnConnected += SetConnectedComplete;
var smi = new SteamNetworkingIdentity();
smi.SetSteamID(hostSteamID);
var options = new SteamNetworkingConfigValue_t[] { };
HostConnection = SteamNetworkingSockets.ConnectP2P(ref smi, 0, options.Length, options);
Task connectedCompleteTask = connectedComplete.Task;
var timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token);
if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask)
{
if (cancelToken.IsCancellationRequested)
{
Debug.LogError($"The connection attempt was cancelled.");
}
else if (timeOutTask.IsCompleted)
{
Debug.LogError($"Connection to {host} timed out.");
}
OnConnected -= SetConnectedComplete;
OnConnectionFailed();
}
OnConnected -= SetConnectedComplete;
}
catch (FormatException)
{
Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?");
Error = true;
OnConnectionFailed();
}
catch (Exception ex)
{
Debug.LogError(ex.Message);
Error = true;
OnConnectionFailed();
}
finally
{
if (Error)
{
Debug.LogError("Connection failed.");
OnConnectionFailed();
}
}
}
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t param)
{
var clientSteamID = param.m_info.m_identityRemote.GetSteamID64();
if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
{
Connected = true;
OnConnected.Invoke();
Debug.Log("Connection established.");
if (BufferedData.Count > 0)
{
Debug.Log($"{BufferedData.Count} received before connection was established. Processing now.");
{
foreach (var a in BufferedData)
{
a();
}
}
}
}
else if (param.m_info.m_eState is ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer or ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
Debug.Log($"Connection was closed by peer, {param.m_info.m_szEndDebug}");
Disconnect();
}
else
{
Debug.Log($"Connection state changed: {param.m_info.m_eState} - {param.m_info.m_szEndDebug}");
}
}
public void Disconnect()
{
cancelToken?.Cancel();
Dispose();
if (HostConnection.m_HSteamNetConnection != 0)
{
Debug.Log("Sending Disconnect message");
SteamNetworkingSockets.CloseConnection(HostConnection, 0, "Graceful disconnect", false);
HostConnection.m_HSteamNetConnection = 0;
}
}
protected void Dispose()
{
if (c_onConnectionChange != null)
{
c_onConnectionChange.Dispose();
c_onConnectionChange = null;
}
}
private void InternalDisconnect()
{
Connected = false;
OnDisconnected.Invoke();
Debug.Log("Disconnected.");
SteamNetworkingSockets.CloseConnection(HostConnection, 0, "Disconnected", false);
}
public void ReceiveData()
{
var ptrs = new IntPtr[MAX_MESSAGES];
int messageCount;
if ((messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(HostConnection, ptrs, MAX_MESSAGES)) > 0)
{
for (var i = 0; i < messageCount; i++)
{
(var data, var ch) = ProcessMessage(ptrs[i]);
if (Connected)
{
OnReceivedData(data, ch);
}
else
{
BufferedData.Add(() => OnReceivedData(data, ch));
}
}
}
}
public void Send(byte[] data, int channelId)
{
var res = SendSocket(HostConnection, data, channelId);
if (res is EResult.k_EResultNoConnection or EResult.k_EResultInvalidParam)
{
Debug.Log($"Connection to server was lost.");
InternalDisconnect();
}
else if (res != EResult.k_EResultOK)
{
Debug.LogError($"Could not send: {res}");
}
}
private void SetConnectedComplete()
=> connectedComplete.SetResult(connectedComplete.Task);
private void OnConnectionFailed()
=> OnDisconnected.Invoke();
public void FlushData()
=> SteamNetworkingSockets.FlushMessagesOnConnection(HostConnection);
}
}

View File

@ -1,42 +0,0 @@
using Steamworks;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Mirror.FizzySteam
{
public abstract class SteamCommon
{
protected const int MAX_MESSAGES = 256;
protected EResult SendSocket(HSteamNetConnection conn, byte[] data, int channelId)
{
Array.Resize(ref data, data.Length + 1);
data[data.Length - 1] = (byte)channelId;
GCHandle pinnedArray = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr pData = pinnedArray.AddrOfPinnedObject();
int sendFlag = channelId == Channels.Unreliable ? Constants.k_nSteamNetworkingSend_Unreliable : Constants.k_nSteamNetworkingSend_Reliable;
EResult res = SteamNetworkingSockets.SendMessageToConnection(conn, pData, (uint)data.Length, sendFlag, out long _);
if (res != EResult.k_EResultOK)
{
Debug.LogWarning($"Send issue: {res}");
}
pinnedArray.Free();
return res;
}
protected (byte[], int) ProcessMessage(IntPtr ptrs)
{
SteamNetworkingMessage_t data = Marshal.PtrToStructure<SteamNetworkingMessage_t>(ptrs);
byte[] managedArray = new byte[data.m_cbSize];
Marshal.Copy(data.m_pData, managedArray, 0, data.m_cbSize);
SteamNetworkingMessage_t.Release(ptrs);
int channel = managedArray[managedArray.Length - 1];
Array.Resize(ref managedArray, managedArray.Length - 1);
return (managedArray, channel);
}
}
}

View File

@ -1,208 +0,0 @@
using Steamworks;
using System;
using System.Linq;
using UnityEngine;
namespace Mirror.FizzySteam
{
public class SteamServer : SteamCommon
{
private event Action<int> OnConnected;
private event Action<int, byte[], int> OnReceivedData;
private event Action<int> OnDisconnected;
private event Action<int, TransportError, string> OnReceivedError;
private BidirectionalDictionary<HSteamNetConnection, int> connToMirrorID;
private BidirectionalDictionary<CSteamID, int> steamIDToMirrorID;
private int maxConnections;
private int nextConnectionID;
private HSteamListenSocket listenSocket;
private Callback<SteamNetConnectionStatusChangedCallback_t> c_onConnectionChange = null;
private SteamServer(int maxConnections)
{
this.maxConnections = maxConnections;
connToMirrorID = new BidirectionalDictionary<HSteamNetConnection, int>();
steamIDToMirrorID = new BidirectionalDictionary<CSteamID, int>();
nextConnectionID = 1;
c_onConnectionChange = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
}
public static SteamServer CreateServer(FizzySteamworks transport, int maxConnections)
{
SteamServer s = new SteamServer(maxConnections);
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
s.OnReceivedData += (id, data, ch) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), ch);
s.OnReceivedError += (id, error, reason) => transport.OnServerError.Invoke(id, error, reason);
try
{
SteamNetworkingUtils.InitRelayNetworkAccess();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
s.Host();
return s;
}
private void Host()
{
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
}
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t param)
{
ulong clientSteamID = param.m_info.m_identityRemote.GetSteamID64();
if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting)
{
if (connToMirrorID.Count >= maxConnections)
{
Debug.Log($"Incoming connection {clientSteamID} would exceed max connection count. Rejecting.");
SteamNetworkingSockets.CloseConnection(param.m_hConn, 0, "Max Connection Count", false);
return;
}
EResult res;
if ((res = SteamNetworkingSockets.AcceptConnection(param.m_hConn)) == EResult.k_EResultOK)
{
Debug.Log($"Accepting connection {clientSteamID}");
}
else
{
Debug.Log($"Connection {clientSteamID} could not be accepted: {res.ToString()}");
}
}
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
{
int connectionId = nextConnectionID++;
connToMirrorID.Add(param.m_hConn, connectionId);
steamIDToMirrorID.Add(param.m_info.m_identityRemote.GetSteamID(), connectionId);
OnConnected.Invoke(connectionId);
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
}
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
{
if (connToMirrorID.TryGetValue(param.m_hConn, out int connId))
{
InternalDisconnect(connId, param.m_hConn);
}
}
else
{
Debug.Log($"Connection {clientSteamID} state changed: {param.m_info.m_eState.ToString()}");
}
}
private void InternalDisconnect(int connId, HSteamNetConnection socket)
{
OnDisconnected.Invoke(connId);
SteamNetworkingSockets.CloseConnection(socket, 0, "Graceful disconnect", false);
connToMirrorID.Remove(connId);
steamIDToMirrorID.Remove(connId);
Debug.Log($"Client with ConnectionID {connId} disconnected.");
}
public void Disconnect(int connectionId)
{
if (connToMirrorID.TryGetValue(connectionId, out HSteamNetConnection conn))
{
Debug.Log($"Connection id {connectionId} disconnected.");
SteamNetworkingSockets.CloseConnection(conn, 0, "Disconnected by server", false);
steamIDToMirrorID.Remove(connectionId);
connToMirrorID.Remove(connectionId);
OnDisconnected(connectionId);
}
else
{
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
}
}
public void FlushData()
{
foreach (HSteamNetConnection conn in connToMirrorID.FirstTypes)
{
SteamNetworkingSockets.FlushMessagesOnConnection(conn);
}
}
public void ReceiveData()
{
foreach (HSteamNetConnection conn in connToMirrorID.FirstTypes.ToList())
{
if (connToMirrorID.TryGetValue(conn, out int connId))
{
IntPtr[] ptrs = new IntPtr[MAX_MESSAGES];
int messageCount;
if ((messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(conn, ptrs, MAX_MESSAGES)) > 0)
{
for (int i = 0; i < messageCount; i++)
{
(byte[] data, int ch) = ProcessMessage(ptrs[i]);
OnReceivedData(connId, data, ch);
}
}
}
}
}
public void Send(int connectionId, byte[] data, int channelId)
{
if (connToMirrorID.TryGetValue(connectionId, out HSteamNetConnection conn))
{
EResult res = SendSocket(conn, data, channelId);
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
{
Debug.Log($"Connection to {connectionId} was lost.");
InternalDisconnect(connectionId, conn);
}
else if (res != EResult.k_EResultOK)
{
Debug.LogError($"Could not send: {res.ToString()}");
}
}
else
{
Debug.LogError("Trying to send on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
}
}
public string ServerGetClientAddress(int connectionId)
{
if (steamIDToMirrorID.TryGetValue(connectionId, out CSteamID steamId))
{
return steamId.ToString();
}
else
{
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
return string.Empty;
}
}
public void Shutdown()
{
SteamNetworkingSockets.CloseListenSocket(listenSocket);
if (c_onConnectionChange != null)
{
c_onConnectionChange.Dispose();
c_onConnectionChange = null;
}
}
}
}