├── IClient.cs ├── IServer.cs ├── FizzyConnectionManager.cs ├── FizzySocketManager.cs ├── LICENSE ├── NextCommon.cs ├── .gitignore ├── BidirectionalDictionary.cs ├── README.md ├── LegacyCommon.cs ├── LegacyClient.cs ├── LegacyServer.cs ├── NextClient.cs ├── NextServer.cs └── FizzyFacepunch.cs /IClient.cs: -------------------------------------------------------------------------------- 1 | namespace Mirror.FizzySteam 2 | { 3 | public interface IClient 4 | { 5 | bool Connected { get; } 6 | bool Error { get; } 7 | void ReceiveData(); 8 | void Disconnect(); 9 | void FlushData(); 10 | void Send(byte[] data, int channelId); 11 | } 12 | } -------------------------------------------------------------------------------- /IServer.cs: -------------------------------------------------------------------------------- 1 | namespace Mirror.FizzySteam 2 | { 3 | public interface IServer 4 | { 5 | void ReceiveData(); 6 | void Send(int connectionId, byte[] data, int channelId); 7 | void Disconnect(int connectionId); 8 | void FlushData(); 9 | string ServerGetClientAddress(int connectionId); 10 | void Shutdown(); 11 | } 12 | } -------------------------------------------------------------------------------- /FizzyConnectionManager.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | 4 | public class FizzyConnectionManager : ConnectionManager 5 | { 6 | public Action ForwardMessage; 7 | 8 | public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) 9 | { 10 | ForwardMessage(data, size); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FizzySocketManager.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using Steamworks.Data; 3 | using System; 4 | 5 | public class FizzySocketManager : SocketManager 6 | { 7 | public Action ForwardMessage; 8 | 9 | public override void OnMessage(Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel) 10 | { 11 | ForwardMessage(connection, data, size); 12 | } 13 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright Marco Hoffmann (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NextCommon.cs: -------------------------------------------------------------------------------- 1 | using Mirror; 2 | using Steamworks; 3 | using Steamworks.Data; 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using UnityEngine; 7 | 8 | public abstract class NextCommon 9 | { 10 | protected const int MAX_MESSAGES = 256; 11 | 12 | protected Result SendSocket(Connection conn, byte[] data, int channelId) 13 | { 14 | Array.Resize(ref data, data.Length + 1); 15 | data[data.Length - 1] = (byte)channelId; 16 | 17 | GCHandle pinnedArray = GCHandle.Alloc(data, GCHandleType.Pinned); 18 | IntPtr pData = pinnedArray.AddrOfPinnedObject(); 19 | SendType sendFlag = channelId == Channels.Unreliable ? SendType.Unreliable : SendType.Reliable; 20 | Result res = conn.SendMessage( pData, data.Length, sendFlag); 21 | if(res != Result.OK) 22 | { 23 | Debug.LogWarning($"Send issue: {res}"); 24 | } 25 | 26 | pinnedArray.Free(); 27 | return res; 28 | } 29 | 30 | protected (byte[], int) ProcessMessage(IntPtr ptrs, int size) 31 | { 32 | byte[] managedArray = new byte[size]; 33 | Marshal.Copy(ptrs, managedArray, 0, size); 34 | int channel = managedArray[managedArray.Length - 1]; 35 | Array.Resize(ref managedArray, managedArray.Length - 1); 36 | return (managedArray, channel); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.meta 2 | 3 | # This .gitignore file should be placed at the root of your Unity project directory 4 | # 5 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 6 | # 7 | /[Ll]ibrary/ 8 | /[Tt]emp/ 9 | /[Oo]bj/ 10 | /[Bb]uild/ 11 | /[Bb]uilds/ 12 | /[Ll]ogs/ 13 | /[Uu]ser[Ss]ettings/ 14 | 15 | # MemoryCaptures can get excessive in size. 16 | # They also could contain extremely sensitive data 17 | /[Mm]emoryCaptures/ 18 | 19 | # Recordings can get excessive in size 20 | /[Rr]ecordings/ 21 | 22 | # Uncomment this line if you wish to ignore the asset store tools plugin 23 | # /[Aa]ssets/AssetStoreTools* 24 | 25 | # Autogenerated Jetbrains Rider plugin 26 | /[Aa]ssets/Plugins/Editor/JetBrains* 27 | 28 | # Visual Studio cache directory 29 | .vs/ 30 | 31 | # Gradle cache directory 32 | .gradle/ 33 | 34 | # Autogenerated VS/MD/Consulo solution and project files 35 | ExportedObj/ 36 | .consulo/ 37 | *.csproj 38 | *.unityproj 39 | *.sln 40 | *.suo 41 | *.tmp 42 | *.user 43 | *.userprefs 44 | *.pidb 45 | *.booproj 46 | *.svd 47 | *.pdb 48 | *.mdb 49 | *.opendb 50 | *.VC.db 51 | 52 | # Unity3D generated meta files 53 | *.pidb.meta 54 | *.pdb.meta 55 | *.mdb.meta 56 | 57 | # Unity3D generated file on crash reports 58 | sysinfo.txt 59 | 60 | # Builds 61 | *.apk 62 | *.aab 63 | *.unitypackage 64 | *.app 65 | 66 | # Crashlytics generated file 67 | crashlytics-build.properties 68 | 69 | # Packed Addressables 70 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 71 | 72 | # Temporary auto-generated Android Assets 73 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 74 | /[Aa]ssets/[Ss]treamingAssets/aa/* 75 | -------------------------------------------------------------------------------- /BidirectionalDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Mirror.FizzySteam 5 | { 6 | public class BidirectionalDictionary : IEnumerable 7 | { 8 | private Dictionary t1ToT2Dict = new Dictionary(); 9 | private Dictionary t2ToT1Dict = new Dictionary(); 10 | 11 | public IEnumerable FirstTypes => t1ToT2Dict.Keys; 12 | public IEnumerable SecondTypes => t2ToT1Dict.Keys; 13 | 14 | public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator(); 15 | 16 | public int Count => t1ToT2Dict.Count; 17 | 18 | public void Add(T1 key, T2 value) 19 | { 20 | t1ToT2Dict[key] = value; 21 | t2ToT1Dict[value] = key; 22 | } 23 | 24 | public void Add(T2 key, T1 value) 25 | { 26 | t2ToT1Dict[key] = value; 27 | t1ToT2Dict[value] = key; 28 | } 29 | 30 | public T2 Get(T1 key) => t1ToT2Dict[key]; 31 | 32 | public T1 Get(T2 key) => t2ToT1Dict[key]; 33 | 34 | public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value); 35 | 36 | public bool TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value); 37 | 38 | public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key); 39 | 40 | public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key); 41 | 42 | public void Remove(T1 key) 43 | { 44 | if (Contains(key)) 45 | { 46 | T2 val = t1ToT2Dict[key]; 47 | t1ToT2Dict.Remove(key); 48 | t2ToT1Dict.Remove(val); 49 | } 50 | } 51 | public void Remove(T2 key) 52 | { 53 | if (Contains(key)) 54 | { 55 | T1 val = t2ToT1Dict[key]; 56 | t1ToT2Dict.Remove(val); 57 | t2ToT1Dict.Remove(key); 58 | } 59 | } 60 | 61 | public T1 this[T2 key] 62 | { 63 | get => t2ToT1Dict[key]; 64 | set 65 | { 66 | t2ToT1Dict[key] = value; 67 | t1ToT2Dict[value] = key; 68 | } 69 | } 70 | 71 | public T2 this[T1 key] 72 | { 73 | get => t1ToT2Dict[key]; 74 | set 75 | { 76 | t1ToT2Dict[key] = value; 77 | t2ToT1Dict[value] = key; 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FizzyFacepunch 2 | 3 | This is an alternative version of **[FizzySteamworks](https://github.com/Chykary/FizzySteamworks)** that uses Facepunch instead of Steamworks.NET. 4 | 5 | Mirror **[docs](https://mirror-networking.gitbook.io/docs/transports/fizzyfacepunch-transport)** and the official community **[Discord](https://discord.gg/N9QVxbM)**. 6 | 7 | FizzyFacepunch brings together **[Steam](https://store.steampowered.com)** and **[Mirror](https://github.com/vis2k/Mirror)** . It supports both the old SteamNetworking as well as the new SteamSockets. 8 | 9 | ## Dependencies 10 | Both of these projects need to be installed and working before you can use this transport. 11 | 1. **[Facepunch](https://wiki.facepunch.com/steamworks/)** FizzyFacepunch relies on Facepunch to communicate with the **[Steamworks API](https://partner.steamgames.com/doc/sdk)**. **Requires .Net 4.x** 12 | 2. **[Mirror](https://mirror-networking.com/)** FizzyFacepunch is also obviously dependant on Mirror which is a streamline, bug fixed, maintained version of UNET for Unity. 13 | 14 | ## Setting Up 15 | 16 | 1. Install Mirror **(Requires Mirror 66.0+)** from the official repo **[unitypackage](https://github.com/vis2k/Mirror/releases)** or from the **[Asset Store](https://assetstore.unity.com/packages/tools/network/mirror-129321)**. 17 | 2. Install FizzyFacepunch **[unitypackage](https://github.com/Chykary/FizzyFacepunch/releases)** from the release section. 18 | 3. In your **"NetworkManager"** object replace **"Telepathy/KCP"** script with **"FizzyFacepunch"** script. 19 | 4. Enter your Steam App ID in the **"FizzyFacepunch"** script. 20 | 21 | If the latest FizzyFacepunch unitypackage doesn't work then download the source of this repo, 22 | and copy it to the `Assets/Mirror/Runtime/Transports/FizzyFacepunch` folder in your Unity project. 23 | (because maybe someone already fixed it) 24 | 25 | **Note: The default 480(Spacewar) appid is a very grey area, technically, it's not allowed but they don't really do anything about it. When you have your own appid from steam then replace the 480 with your own game appid. 26 | If you know a better way around this please make a [Issue ticket.](https://github.com/axvemi/FizzyFacepunch/issues)** 27 | 28 | ## Host 29 | To be able to have your game working you need to make sure you have Steam running in the background. 30 | 31 | ## Client 32 | 1. Send the game to your buddy. 33 | 2. Your buddy needs your **steamID64** to be able to connect. The transport shows your Steam User ID after you have started a server. 34 | 3. Place the **steamID64** into **"localhost"** then click **"Client"** 35 | 5. Then they will be connected to you. 36 | 37 | ## Testing your game locally 38 | You can't connect to yourself locally while using **FizzyFacepunch** since it's using steams P2P. If you want to test your game locally you'll have to use **"Telepathy/KCP Transport"** instead of **FizzyFacepunch**. 39 | -------------------------------------------------------------------------------- /LegacyCommon.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections; 4 | using UnityEngine; 5 | 6 | namespace Mirror.FizzySteam 7 | { 8 | public abstract class LegacyCommon 9 | { 10 | private P2PSend[] channels; 11 | private int internal_ch => channels.Length; 12 | 13 | protected enum InternalMessages : byte 14 | { 15 | CONNECT, 16 | ACCEPT_CONNECT, 17 | DISCONNECT 18 | } 19 | 20 | protected readonly FizzyFacepunch transport; 21 | 22 | protected LegacyCommon(FizzyFacepunch transport) 23 | { 24 | channels = transport.Channels; 25 | 26 | SteamNetworking.OnP2PSessionRequest += OnNewConnection; 27 | SteamNetworking.OnP2PConnectionFailed += OnConnectFail; 28 | 29 | this.transport = transport; 30 | } 31 | 32 | protected void WaitForClose(SteamId cSteamID) => transport.StartCoroutine(DelayedClose(cSteamID)); 33 | private IEnumerator DelayedClose(SteamId cSteamID) 34 | { 35 | yield return null; 36 | CloseP2PSessionWithUser(cSteamID); 37 | } 38 | 39 | protected void Dispose() 40 | { 41 | SteamNetworking.OnP2PSessionRequest -= OnNewConnection; 42 | SteamNetworking.OnP2PConnectionFailed -= OnConnectFail; 43 | } 44 | 45 | protected abstract void OnNewConnection(SteamId steamID); 46 | 47 | private void OnConnectFail(SteamId id, P2PSessionError err) 48 | { 49 | OnConnectionFailed(id); 50 | CloseP2PSessionWithUser(id); 51 | 52 | switch (err) 53 | { 54 | case P2PSessionError.NotRunningApp: 55 | throw new Exception("Connection failed: The target user is not running the same game."); 56 | case P2PSessionError.NoRightsToApp: 57 | throw new Exception("Connection failed: The local user doesn't own the app that is running."); 58 | case P2PSessionError.DestinationNotLoggedIn: 59 | throw new Exception("Connection failed: Target user isn't connected to Steam."); 60 | case P2PSessionError.Timeout: 61 | throw new Exception("Connection failed: The connection timed out because the target user didn't respond."); 62 | default: 63 | throw new Exception("Connection failed: Unknown error."); 64 | } 65 | } 66 | 67 | protected bool SendInternal(SteamId target, InternalMessages type) => SteamNetworking.SendP2PPacket(target, new byte[] { (byte)type }, 1, internal_ch); 68 | protected void Send(SteamId host, byte[] msgBuffer, int channel) => SteamNetworking.SendP2PPacket(host, msgBuffer, msgBuffer.Length, channel, channels[Mathf.Min(channel, channels.Length - 1)]); 69 | private bool Receive(out SteamId clientSteamID, out byte[] receiveBuffer, int channel) 70 | { 71 | if (SteamNetworking.IsP2PPacketAvailable(channel)) 72 | { 73 | var data = SteamNetworking.ReadP2PPacket(channel); 74 | 75 | if (data != null) 76 | { 77 | receiveBuffer = data.Value.Data; 78 | clientSteamID = data.Value.SteamId; 79 | return true; 80 | } 81 | } 82 | 83 | receiveBuffer = null; 84 | clientSteamID = 0; 85 | return false; 86 | } 87 | 88 | protected void CloseP2PSessionWithUser(SteamId clientSteamID) => SteamNetworking.CloseP2PSessionWithUser(clientSteamID); 89 | 90 | public void ReceiveData() 91 | { 92 | try 93 | { 94 | while (transport.enabled && Receive(out SteamId clientSteamID, out byte[] internalMessage, internal_ch)) 95 | { 96 | if (internalMessage.Length == 1) 97 | { 98 | OnReceiveInternalData((InternalMessages)internalMessage[0], clientSteamID); 99 | return; 100 | } 101 | else 102 | { 103 | Debug.Log("Incorrect package length on internal channel."); 104 | } 105 | } 106 | 107 | for (int chNum = 0; chNum < channels.Length; chNum++) 108 | { 109 | while (transport.enabled && Receive(out SteamId clientSteamID, out byte[] receiveBuffer, chNum)) 110 | { 111 | OnReceiveData(receiveBuffer, clientSteamID, chNum); 112 | } 113 | } 114 | } 115 | catch (Exception e) 116 | { 117 | Debug.LogException(e); 118 | } 119 | } 120 | 121 | protected abstract void OnReceiveInternalData(InternalMessages type, SteamId clientSteamID); 122 | protected abstract void OnReceiveData(byte[] data, SteamId clientSteamID, int channel); 123 | protected abstract void OnConnectionFailed(SteamId remoteId); 124 | } 125 | } -------------------------------------------------------------------------------- /LegacyClient.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using UnityEngine; 6 | 7 | namespace Mirror.FizzySteam 8 | { 9 | public class LegacyClient : LegacyCommon, IClient 10 | { 11 | public bool Error { get; private set; } 12 | public bool Connected { get; private set; } 13 | 14 | private event Action OnReceivedData; 15 | private event Action OnConnected; 16 | private event Action OnDisconnected; 17 | 18 | private TimeSpan ConnectionTimeout; 19 | 20 | private SteamId hostSteamID = 0; 21 | private TaskCompletionSource connectedComplete; 22 | private CancellationTokenSource cancelToken; 23 | 24 | private LegacyClient(FizzyFacepunch transport) : base(transport) 25 | { 26 | ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout)); 27 | } 28 | 29 | public static LegacyClient CreateClient(FizzyFacepunch transport, string host) 30 | { 31 | LegacyClient c = new LegacyClient(transport); 32 | 33 | c.OnConnected += () => transport.OnClientConnected.Invoke(); 34 | c.OnDisconnected += () => transport.OnClientDisconnected.Invoke(); 35 | c.OnReceivedData += (data, channel) => transport.OnClientDataReceived.Invoke(new ArraySegment(data), channel); 36 | 37 | if (SteamClient.IsValid) 38 | { 39 | c.Connect(host); 40 | } 41 | else 42 | { 43 | Debug.LogError("SteamWorks not initialized."); 44 | c.OnConnectionFailed(new SteamId()); 45 | } 46 | 47 | return c; 48 | } 49 | 50 | private async void Connect(string host) 51 | { 52 | cancelToken = new CancellationTokenSource(); 53 | try 54 | { 55 | hostSteamID = ulong.Parse(host); 56 | connectedComplete = new TaskCompletionSource(); 57 | 58 | OnConnected += SetConnectedComplete; 59 | SendInternal(hostSteamID, InternalMessages.CONNECT); 60 | Task connectedCompleteTask = connectedComplete.Task; 61 | Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token); 62 | 63 | if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask) 64 | { 65 | if (cancelToken.IsCancellationRequested) 66 | { 67 | Debug.LogError($"The connection attempt was cancelled."); 68 | } 69 | else if (timeOutTask.IsCompleted) 70 | { 71 | Debug.LogError($"Connection to {host} timed out."); 72 | } 73 | OnConnected -= SetConnectedComplete; 74 | Debug.LogError("Connection timed out."); 75 | OnConnectionFailed(hostSteamID); 76 | } 77 | 78 | OnConnected -= SetConnectedComplete; 79 | } 80 | catch (FormatException) 81 | { 82 | Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?"); 83 | Error = true; 84 | } 85 | catch (Exception ex) 86 | { 87 | Debug.LogError(ex.Message); 88 | Error = true; 89 | } 90 | finally 91 | { 92 | if (Error) 93 | { 94 | OnConnectionFailed(new SteamId()); 95 | } 96 | } 97 | } 98 | 99 | public void Disconnect() 100 | { 101 | Debug.Log("Sending Disconnect message"); 102 | SendInternal(hostSteamID, InternalMessages.DISCONNECT); 103 | Dispose(); 104 | cancelToken?.Cancel(); 105 | 106 | WaitForClose(hostSteamID); 107 | } 108 | 109 | private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task); 110 | 111 | protected override void OnReceiveData(byte[] data, SteamId clientSteamID, int channel) 112 | { 113 | if (clientSteamID != hostSteamID) 114 | { 115 | Debug.LogError("Received a message from an unknown"); 116 | return; 117 | } 118 | 119 | OnReceivedData.Invoke(data, channel); 120 | } 121 | 122 | protected override void OnNewConnection(SteamId id) 123 | { 124 | if (hostSteamID == id) 125 | { 126 | SteamNetworking.AcceptP2PSessionWithUser(id); 127 | } 128 | else 129 | { 130 | Debug.LogError("P2P Acceptance Request from unknown host ID."); 131 | } 132 | } 133 | 134 | protected override void OnReceiveInternalData(InternalMessages type, SteamId clientSteamID) 135 | { 136 | switch (type) 137 | { 138 | case InternalMessages.ACCEPT_CONNECT: 139 | if (!Connected) 140 | { 141 | Connected = true; 142 | Debug.Log("Connection established."); 143 | OnConnected.Invoke(); 144 | } 145 | break; 146 | case InternalMessages.DISCONNECT: 147 | if (Connected) 148 | { 149 | Connected = false; 150 | Debug.Log("Disconnected."); 151 | OnDisconnected.Invoke(); 152 | } 153 | break; 154 | default: 155 | Debug.Log("Received unknown message type"); 156 | break; 157 | } 158 | } 159 | 160 | public void Send(byte[] data, int channelId) => Send(hostSteamID, data, channelId); 161 | protected override void OnConnectionFailed(SteamId remoteId) => OnDisconnected.Invoke(); 162 | public void FlushData() { } 163 | } 164 | } -------------------------------------------------------------------------------- /LegacyServer.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace Mirror.FizzySteam 7 | { 8 | public class LegacyServer : LegacyCommon, IServer 9 | { 10 | private event Action OnConnected; 11 | private event Action OnReceivedData; 12 | private event Action OnDisconnected; 13 | private event Action OnReceivedError; 14 | 15 | private BidirectionalDictionary steamToMirrorIds; 16 | private int maxConnections; 17 | private int nextConnectionID; 18 | 19 | public static LegacyServer CreateServer(FizzyFacepunch transport, int maxConnections) 20 | { 21 | LegacyServer s = new LegacyServer(transport, maxConnections); 22 | 23 | s.OnConnected += (id) => transport.OnServerConnected.Invoke(id); 24 | s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id); 25 | s.OnReceivedData += (id, data, channel) => transport.OnServerDataReceived.Invoke(id, new ArraySegment(data), channel); 26 | s.OnReceivedError += (id, exception) => transport.OnServerError.Invoke(id, TransportError.Unexpected, exception.ToString()); 27 | 28 | SteamNetworking.OnP2PSessionRequest = (steamid) => 29 | { 30 | Debug.Log($"Incoming request from SteamId {steamid}."); 31 | SteamNetworking.AcceptP2PSessionWithUser(steamid); 32 | }; 33 | 34 | if (!SteamClient.IsValid) 35 | { 36 | Debug.LogError("SteamWorks not initialized."); 37 | } 38 | 39 | return s; 40 | } 41 | 42 | private LegacyServer(FizzyFacepunch transport, int maxConnections) : base(transport) 43 | { 44 | this.maxConnections = maxConnections; 45 | steamToMirrorIds = new BidirectionalDictionary(); 46 | nextConnectionID = 1; 47 | } 48 | 49 | protected override void OnNewConnection(SteamId id) => SteamNetworking.AcceptP2PSessionWithUser(id); 50 | 51 | protected override void OnReceiveInternalData(InternalMessages type, SteamId clientSteamID) 52 | { 53 | switch (type) 54 | { 55 | case InternalMessages.CONNECT: 56 | if (steamToMirrorIds.Count >= maxConnections) 57 | { 58 | SendInternal(clientSteamID, InternalMessages.DISCONNECT); 59 | return; 60 | } 61 | 62 | SendInternal(clientSteamID, InternalMessages.ACCEPT_CONNECT); 63 | 64 | int connectionId = nextConnectionID++; 65 | steamToMirrorIds.Add(clientSteamID, connectionId); 66 | OnConnected.Invoke(connectionId); 67 | Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}"); 68 | break; 69 | case InternalMessages.DISCONNECT: 70 | if (steamToMirrorIds.TryGetValue(clientSteamID, out int connId)) 71 | { 72 | OnDisconnected.Invoke(connId); 73 | CloseP2PSessionWithUser(clientSteamID); 74 | steamToMirrorIds.Remove(clientSteamID); 75 | Debug.Log($"Client with SteamID {clientSteamID} disconnected."); 76 | } 77 | else 78 | { 79 | OnReceivedError.Invoke(-1, new Exception("ERROR Unknown SteamID while receiving disconnect message.")); 80 | } 81 | 82 | break; 83 | default: 84 | Debug.Log("Received unknown message type"); 85 | break; 86 | } 87 | } 88 | 89 | protected override void OnReceiveData(byte[] data, SteamId clientSteamID, int channel) 90 | { 91 | if (steamToMirrorIds.TryGetValue(clientSteamID, out int connectionId)) 92 | { 93 | OnReceivedData.Invoke(connectionId, data, channel); 94 | } 95 | else 96 | { 97 | CloseP2PSessionWithUser(clientSteamID); 98 | Debug.LogError("Data received from steam client thats not known " + clientSteamID); 99 | OnReceivedError.Invoke(-1, new Exception("ERROR Unknown SteamID")); 100 | } 101 | } 102 | 103 | public void Disconnect(int connectionId) 104 | { 105 | if (steamToMirrorIds.TryGetValue(connectionId, out SteamId steamID)) 106 | { 107 | SendInternal(steamID, InternalMessages.DISCONNECT); 108 | steamToMirrorIds.Remove(connectionId); 109 | } 110 | else 111 | { 112 | Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId); 113 | } 114 | } 115 | 116 | public void Shutdown() 117 | { 118 | foreach (KeyValuePair client in steamToMirrorIds) 119 | { 120 | Disconnect(client.Value); 121 | WaitForClose(client.Key); 122 | } 123 | 124 | SteamNetworking.OnP2PSessionRequest = null; 125 | Dispose(); 126 | } 127 | 128 | 129 | public void Send(int connectionId, byte[] data, int channelId) 130 | { 131 | if (steamToMirrorIds.TryGetValue(connectionId, out SteamId steamId)) 132 | { 133 | Send(steamId, data, channelId); 134 | } 135 | else 136 | { 137 | Debug.LogError("Trying to send on unknown connection: " + connectionId); 138 | OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection")); 139 | } 140 | } 141 | 142 | public string ServerGetClientAddress(int connectionId) 143 | { 144 | if (steamToMirrorIds.TryGetValue(connectionId, out SteamId steamId)) 145 | { 146 | return steamId.ToString(); 147 | } 148 | else 149 | { 150 | Debug.LogError("Trying to get info on unknown connection: " + connectionId); 151 | OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection")); 152 | return string.Empty; 153 | } 154 | } 155 | 156 | protected override void OnConnectionFailed(SteamId remoteId) 157 | { 158 | int connectionId = steamToMirrorIds.TryGetValue(remoteId, out int connId) ? connId : nextConnectionID++; 159 | OnDisconnected.Invoke(connectionId); 160 | } 161 | 162 | public void FlushData() {} 163 | } 164 | } -------------------------------------------------------------------------------- /NextClient.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using Steamworks.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using UnityEngine; 8 | 9 | namespace Mirror.FizzySteam 10 | { 11 | public class NextClient : NextCommon, IClient 12 | { 13 | public bool Connected { get; private set; } 14 | public bool Error { get; private set; } 15 | 16 | private TimeSpan ConnectionTimeout; 17 | 18 | private event Action OnReceivedData; 19 | private event Action OnConnected; 20 | private event Action OnDisconnected; 21 | 22 | private CancellationTokenSource cancelToken; 23 | private TaskCompletionSource connectedComplete; 24 | private SteamId hostSteamID = 0; 25 | private FizzyConnectionManager HostConnectionManager; 26 | private Connection HostConnection => HostConnectionManager.Connection; 27 | private List BufferedData; 28 | 29 | private NextClient(FizzyFacepunch transport) 30 | { 31 | ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout)); 32 | BufferedData = new List(); 33 | } 34 | 35 | public static NextClient CreateClient(FizzyFacepunch transport, string host) 36 | { 37 | NextClient c = new NextClient(transport); 38 | 39 | c.OnConnected += () => transport.OnClientConnected.Invoke(); 40 | c.OnDisconnected += () => transport.OnClientDisconnected.Invoke(); 41 | c.OnReceivedData += (data, ch) => transport.OnClientDataReceived.Invoke(new ArraySegment(data), ch); 42 | 43 | if (SteamClient.IsValid) 44 | { 45 | c.Connect(host); 46 | } 47 | else 48 | { 49 | Debug.LogError("SteamWorks not initialized"); 50 | c.OnConnectionFailed(); 51 | } 52 | 53 | return c; 54 | } 55 | 56 | private async void Connect(string host) 57 | { 58 | cancelToken = new CancellationTokenSource(); 59 | SteamNetworkingSockets.OnConnectionStatusChanged += OnConnectionStatusChanged; 60 | 61 | try 62 | { 63 | hostSteamID = UInt64.Parse(host); 64 | connectedComplete = new TaskCompletionSource(); 65 | OnConnected += SetConnectedComplete; 66 | HostConnectionManager = SteamNetworkingSockets.ConnectRelay(hostSteamID); 67 | HostConnectionManager.ForwardMessage = OnMessageReceived; 68 | Task connectedCompleteTask = connectedComplete.Task; 69 | Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token); 70 | 71 | if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask) 72 | { 73 | if (cancelToken.IsCancellationRequested) 74 | { 75 | Debug.LogError($"The connection attempt was cancelled."); 76 | } 77 | else if (timeOutTask.IsCompleted) 78 | { 79 | Debug.LogError($"Connection to {host} timed out."); 80 | } 81 | 82 | OnConnected -= SetConnectedComplete; 83 | OnConnectionFailed(); 84 | } 85 | 86 | OnConnected -= SetConnectedComplete; 87 | } 88 | catch (FormatException) 89 | { 90 | Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?"); 91 | Error = true; 92 | OnConnectionFailed(); 93 | } 94 | catch (Exception ex) 95 | { 96 | Debug.LogError(ex.Message); 97 | Error = true; 98 | OnConnectionFailed(); 99 | } 100 | finally 101 | { 102 | if (Error) 103 | { 104 | Debug.LogError("Connection failed."); 105 | OnConnectionFailed(); 106 | } 107 | } 108 | } 109 | 110 | private void OnMessageReceived(IntPtr dataPtr, int size) 111 | { 112 | (byte[] data, int ch) = ProcessMessage(dataPtr, size); 113 | if (Connected) 114 | { 115 | OnReceivedData(data, ch); 116 | } 117 | else 118 | { 119 | BufferedData.Add(() => OnReceivedData(data, ch)); 120 | } 121 | } 122 | 123 | private void OnConnectionStatusChanged(Connection conn, ConnectionInfo info) 124 | { 125 | ulong clientSteamID = info.Identity.SteamId; 126 | if (info.State == ConnectionState.Connected) 127 | { 128 | Connected = true; 129 | OnConnected.Invoke(); 130 | Debug.Log("Connection established."); 131 | 132 | if(BufferedData.Count > 0) 133 | { 134 | Debug.Log($"{BufferedData.Count} received before connection was established. Processing now."); 135 | { 136 | foreach(Action a in BufferedData) 137 | { 138 | a(); 139 | } 140 | } 141 | } 142 | } 143 | else if (info.State == ConnectionState.ClosedByPeer) 144 | { 145 | Connected = false; 146 | OnDisconnected.Invoke(); 147 | Debug.Log("Disconnected."); 148 | conn.Close(false, 0, "Disconnected"); 149 | } 150 | else 151 | { 152 | Debug.Log($"Connection state changed: {info.State.ToString()}"); 153 | } 154 | } 155 | 156 | public void Disconnect() 157 | { 158 | cancelToken?.Cancel(); 159 | SteamNetworkingSockets.OnConnectionStatusChanged -= OnConnectionStatusChanged; 160 | 161 | if (HostConnectionManager != null) 162 | { 163 | Debug.Log("Sending Disconnect message"); 164 | HostConnection.Close(false, 0, "Graceful disconnect"); 165 | HostConnectionManager = null; 166 | } 167 | } 168 | 169 | public void ReceiveData() 170 | { 171 | HostConnectionManager.Receive(MAX_MESSAGES); 172 | } 173 | 174 | public void Send(byte[] data, int channelId) 175 | { 176 | Result res = SendSocket(HostConnection, data, channelId); 177 | 178 | if (res != Result.OK) 179 | { 180 | Debug.LogError($"Could not send: {res.ToString()}"); 181 | } 182 | } 183 | 184 | private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task); 185 | private void OnConnectionFailed() => OnDisconnected.Invoke(); 186 | public void FlushData() 187 | { 188 | HostConnection.Flush(); 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /NextServer.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using Steamworks.Data; 3 | using System; 4 | using UnityEngine; 5 | 6 | namespace Mirror.FizzySteam 7 | { 8 | public class NextServer : NextCommon, IServer 9 | { 10 | private event Action OnConnected; 11 | private event Action OnReceivedData; 12 | private event Action OnDisconnected; 13 | private event Action OnReceivedError; 14 | 15 | private BidirectionalDictionary connToMirrorID; 16 | private BidirectionalDictionary steamIDToMirrorID; 17 | private int maxConnections; 18 | private int nextConnectionID; 19 | 20 | private FizzySocketManager listenSocket; 21 | 22 | private NextServer(int maxConnections) 23 | { 24 | this.maxConnections = maxConnections; 25 | connToMirrorID = new BidirectionalDictionary(); 26 | steamIDToMirrorID = new BidirectionalDictionary(); 27 | nextConnectionID = 1; 28 | SteamNetworkingSockets.OnConnectionStatusChanged += OnConnectionStatusChanged; 29 | } 30 | 31 | public static NextServer CreateServer(FizzyFacepunch transport, int maxConnections) 32 | { 33 | NextServer s = new NextServer(maxConnections); 34 | 35 | s.OnConnected += (id) => transport.OnServerConnected.Invoke(id); 36 | s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id); 37 | s.OnReceivedData += (id, data, ch) => transport.OnServerDataReceived.Invoke(id, new ArraySegment(data), ch); 38 | s.OnReceivedError += (id, exception) => transport.OnServerError.Invoke(id, TransportError.Unexpected, exception.ToString()); 39 | 40 | if (!SteamClient.IsValid) 41 | { 42 | Debug.LogError("SteamWorks not initialized."); 43 | } 44 | 45 | s.Host(); 46 | 47 | return s; 48 | } 49 | 50 | private void Host() 51 | { 52 | listenSocket = SteamNetworkingSockets.CreateRelaySocket(); 53 | listenSocket.ForwardMessage = OnMessageReceived; 54 | } 55 | 56 | private void OnConnectionStatusChanged(Connection conn, ConnectionInfo info) 57 | { 58 | ulong clientSteamID = info.Identity.SteamId; 59 | if (info.State == ConnectionState.Connecting) 60 | { 61 | if (connToMirrorID.Count >= maxConnections) 62 | { 63 | Debug.Log($"Incoming connection {clientSteamID} would exceed max connection count. Rejecting."); 64 | conn.Close(false, 0, "Max Connection Count"); 65 | return; 66 | } 67 | 68 | Result res; 69 | 70 | if((res = conn.Accept()) == Result.OK) 71 | { 72 | Debug.Log($"Accepting connection {clientSteamID}"); 73 | } 74 | else 75 | { 76 | Debug.Log($"Connection {clientSteamID} could not be accepted: {res.ToString()}"); 77 | } 78 | } 79 | else if (info.State == ConnectionState.Connected) 80 | { 81 | int connectionId = nextConnectionID++; 82 | connToMirrorID.Add(conn, connectionId); 83 | steamIDToMirrorID.Add(clientSteamID, connectionId); 84 | OnConnected.Invoke(connectionId); 85 | Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}"); 86 | } 87 | else if(info.State == ConnectionState.ClosedByPeer) 88 | { 89 | if (connToMirrorID.TryGetValue(conn, out int connId)) 90 | { 91 | InternalDisconnect(connId, conn); 92 | } 93 | } 94 | else 95 | { 96 | Debug.Log($"Connection {clientSteamID} state changed: {info.State.ToString()}"); 97 | } 98 | } 99 | 100 | private void InternalDisconnect(int connId, Connection socket) 101 | { 102 | OnDisconnected.Invoke(connId); 103 | socket.Close(false, 0, "Graceful disconnect"); 104 | connToMirrorID.Remove(connId); 105 | steamIDToMirrorID.Remove(connId); 106 | Debug.Log($"Client with SteamID {connId} disconnected."); 107 | } 108 | 109 | public void Disconnect(int connectionId) 110 | { 111 | if (connToMirrorID.TryGetValue(connectionId, out Connection conn)) 112 | { 113 | Debug.Log($"Connection id {connectionId} disconnected."); 114 | conn.Close(false, 0, "Disconnected by server"); 115 | steamIDToMirrorID.Remove(connectionId); 116 | connToMirrorID.Remove(connectionId); 117 | OnDisconnected(connectionId); 118 | } 119 | else 120 | { 121 | Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId); 122 | } 123 | } 124 | 125 | public void FlushData() 126 | { 127 | foreach (Connection conn in connToMirrorID.FirstTypes) 128 | { 129 | conn.Flush(); 130 | } 131 | } 132 | 133 | public void ReceiveData() 134 | { 135 | listenSocket.Receive(MAX_MESSAGES); 136 | } 137 | 138 | private void OnMessageReceived(Connection conn, IntPtr dataPtr, int size) 139 | { 140 | (byte[] data, int ch) = ProcessMessage(dataPtr, size); 141 | OnReceivedData(connToMirrorID[conn], data, ch); 142 | } 143 | 144 | public void Send(int connectionId, byte[] data, int channelId) 145 | { 146 | if (connToMirrorID.TryGetValue(connectionId, out Connection conn)) 147 | { 148 | Result res = SendSocket(conn, data, channelId); 149 | 150 | if (res == Result.NoConnection || res == Result.InvalidParam) 151 | { 152 | Debug.Log($"Connection to {connectionId} was lost."); 153 | InternalDisconnect(connectionId, conn); 154 | } 155 | else if (res != Result.OK) 156 | { 157 | Debug.LogError($"Could not send: {res.ToString()}"); 158 | } 159 | } 160 | else 161 | { 162 | Debug.LogError("Trying to send on unknown connection: " + connectionId); 163 | OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection")); 164 | } 165 | } 166 | 167 | public string ServerGetClientAddress(int connectionId) 168 | { 169 | if (steamIDToMirrorID.TryGetValue(connectionId, out SteamId steamId)) 170 | { 171 | return steamId.ToString(); 172 | } 173 | else 174 | { 175 | Debug.LogError("Trying to get info on unknown connection: " + connectionId); 176 | OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection")); 177 | return string.Empty; 178 | } 179 | } 180 | 181 | public void Shutdown() 182 | { 183 | if(listenSocket != null) 184 | { 185 | SteamNetworkingSockets.OnConnectionStatusChanged -= OnConnectionStatusChanged; 186 | listenSocket.Close(); 187 | } 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /FizzyFacepunch.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using UnityEngine; 6 | 7 | namespace Mirror.FizzySteam 8 | { 9 | [HelpURL("https://github.com/Chykary/FizzyFacepunch")] 10 | public class FizzyFacepunch : Transport 11 | { 12 | private const string STEAM_SCHEME = "steam"; 13 | 14 | private static IClient client; 15 | private static IServer server; 16 | 17 | [SerializeField] 18 | public P2PSend[] Channels = new P2PSend[2] { P2PSend.Reliable, P2PSend.UnreliableNoDelay }; 19 | 20 | [Tooltip("Timeout for connecting in seconds.")] 21 | public int Timeout = 25; 22 | [Tooltip("The Steam ID for your application.")] 23 | public uint SteamAppID = 480; 24 | [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.")] 25 | public bool AllowSteamRelay = true; 26 | 27 | [Tooltip("Use SteamSockets instead of the (deprecated) SteamNetworking. This will always use Relay.")] 28 | public bool UseNextGenSteamNetworking = true; 29 | 30 | [Tooltip("Check this if you want the transport to initialise Facepunch.")] 31 | public bool InitFacepunch = true; 32 | 33 | [Header("Info")] 34 | [Tooltip("This will display your Steam User ID when you start or connect to a server.")] 35 | public ulong SteamUserID; 36 | 37 | private void Awake() 38 | { 39 | Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for FizzySteamworks."); 40 | 41 | if (!InitFacepunch) return; 42 | 43 | var initialised = InitialiseSteamworks(SteamAppID); 44 | if (!initialised) return; 45 | 46 | Debug.Log("SteamWorks initialised"); 47 | FetchSteamID(); 48 | } 49 | 50 | public override void ClientEarlyUpdate() 51 | { 52 | if (enabled) 53 | { 54 | client?.ReceiveData(); 55 | } 56 | } 57 | 58 | public override void ServerEarlyUpdate() 59 | { 60 | if (enabled) 61 | { 62 | server?.ReceiveData(); 63 | } 64 | } 65 | 66 | public override void ClientLateUpdate() 67 | { 68 | if (enabled) 69 | { 70 | client?.FlushData(); 71 | } 72 | } 73 | 74 | public override void ServerLateUpdate() 75 | { 76 | if (enabled) 77 | { 78 | server?.FlushData(); 79 | } 80 | } 81 | 82 | public override bool ClientConnected() => ClientActive() && client.Connected; 83 | public override void ClientConnect(string address) 84 | { 85 | if (!SteamClient.IsValid) 86 | { 87 | Debug.LogError("SteamWorks not initialized. Client could not be started."); 88 | OnClientDisconnected.Invoke(); 89 | return; 90 | } 91 | 92 | if (address == SteamUserID.ToString()) 93 | { 94 | Debug.Log("You can't connect to yourself."); 95 | return; 96 | } 97 | 98 | FetchSteamID(); 99 | 100 | if (ServerActive()) 101 | { 102 | Debug.LogError("Transport already running as server!"); 103 | return; 104 | } 105 | 106 | if (!ClientActive() || client.Error) 107 | { 108 | if (UseNextGenSteamNetworking) 109 | { 110 | Debug.Log($"Starting client [SteamSockets], target address {address}."); 111 | client = NextClient.CreateClient(this, address); 112 | } 113 | else 114 | { 115 | Debug.Log($"Starting client [DEPRECATED SteamNetworking], target address {address}. Relay enabled: {AllowSteamRelay}"); 116 | SteamNetworking.AllowP2PPacketRelay(AllowSteamRelay); 117 | client = LegacyClient.CreateClient(this, address); 118 | } 119 | } 120 | else 121 | { 122 | Debug.LogError("Client already running!"); 123 | } 124 | } 125 | 126 | public override void ClientConnect(Uri uri) 127 | { 128 | if (uri.Scheme != STEAM_SCHEME) 129 | throw new ArgumentException($"Invalid url {uri}, use {STEAM_SCHEME}://SteamID instead", nameof(uri)); 130 | 131 | ClientConnect(uri.Host); 132 | } 133 | 134 | public override void ClientSend(ArraySegment segment, int channelId) 135 | { 136 | byte[] data = new byte[segment.Count]; 137 | Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count); 138 | client.Send(data, channelId); 139 | } 140 | 141 | public override void ClientDisconnect() 142 | { 143 | if (ClientActive()) 144 | { 145 | Shutdown(); 146 | } 147 | } 148 | public bool ClientActive() => client != null; 149 | 150 | 151 | public override bool ServerActive() => server != null; 152 | public override void ServerStart() 153 | { 154 | if (!SteamClient.IsValid) 155 | { 156 | Debug.LogError("SteamWorks not initialized. Server could not be started."); 157 | return; 158 | } 159 | 160 | FetchSteamID(); 161 | 162 | if (ClientActive()) 163 | { 164 | Debug.LogError("Transport already running as client!"); 165 | return; 166 | } 167 | 168 | if (!ServerActive()) 169 | { 170 | if (UseNextGenSteamNetworking) 171 | { 172 | Debug.Log($"Starting server [SteamSockets]."); 173 | server = NextServer.CreateServer(this, NetworkManager.singleton.maxConnections); 174 | } 175 | else 176 | { 177 | Debug.Log($"Starting server [DEPRECATED SteamNetworking]. Relay enabled: {AllowSteamRelay}"); 178 | SteamNetworking.AllowP2PPacketRelay(AllowSteamRelay); 179 | server = LegacyServer.CreateServer(this, NetworkManager.singleton.maxConnections); 180 | } 181 | } 182 | else 183 | { 184 | Debug.LogError("Server already started!"); 185 | } 186 | } 187 | 188 | public override Uri ServerUri() 189 | { 190 | var steamBuilder = new UriBuilder 191 | { 192 | Scheme = STEAM_SCHEME, 193 | Host = SteamClient.SteamId.Value.ToString() 194 | }; 195 | 196 | return steamBuilder.Uri; 197 | } 198 | 199 | public override void ServerSend(int connectionId, ArraySegment segment, int channelId) 200 | { 201 | if (ServerActive()) 202 | { 203 | byte[] data = new byte[segment.Count]; 204 | Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count); 205 | server.Send(connectionId, data, channelId); 206 | } 207 | } 208 | public override void ServerDisconnect(int connectionId) 209 | { 210 | if (ServerActive()) 211 | { 212 | server.Disconnect(connectionId); 213 | } 214 | } 215 | public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty; 216 | public override void ServerStop() 217 | { 218 | if (ServerActive()) 219 | { 220 | Shutdown(); 221 | } 222 | } 223 | 224 | public override void Shutdown() 225 | { 226 | if (server != null) 227 | { 228 | server.Shutdown(); 229 | server = null; 230 | Debug.Log("Transport shut down - was server."); 231 | } 232 | 233 | if (client != null) 234 | { 235 | client.Disconnect(); 236 | client = null; 237 | Debug.Log("Transport shut down - was client."); 238 | } 239 | } 240 | 241 | public override int GetMaxPacketSize(int channelId) 242 | { 243 | if (channelId >= Channels.Length) 244 | { 245 | Debug.LogError("Channel Id exceeded configured channels! Please configure more channels."); 246 | return 1200; 247 | } 248 | 249 | switch (Channels[channelId]) 250 | { 251 | case P2PSend.Unreliable: 252 | case P2PSend.UnreliableNoDelay: 253 | return 1200; 254 | case P2PSend.Reliable: 255 | case P2PSend.ReliableWithBuffering: 256 | return 1048576; 257 | default: 258 | throw new NotSupportedException(); 259 | } 260 | } 261 | 262 | public override bool Available() 263 | { 264 | try 265 | { 266 | return SteamClient.IsValid; 267 | } 268 | catch 269 | { 270 | return false; 271 | } 272 | } 273 | 274 | private void FetchSteamID() 275 | { 276 | if (SteamClient.IsValid) 277 | { 278 | if (UseNextGenSteamNetworking) 279 | { 280 | SteamNetworkingUtils.InitRelayNetworkAccess(); 281 | } 282 | 283 | SteamUserID = SteamClient.SteamId; 284 | } 285 | } 286 | 287 | private bool InitialiseSteamworks(uint appid) 288 | { 289 | try 290 | { 291 | SteamClient.Init( appid, true ); 292 | } 293 | catch ( Exception e ) 294 | { 295 | Debug.LogError($"Could be one of the following: Steam is closed, Can't find steam_api dlls or Don't have permission to open appid. Exception: {e.Message}"); 296 | return false; 297 | } 298 | return true; 299 | } 300 | 301 | private void OnDestroy() 302 | { 303 | Shutdown(); 304 | } 305 | } 306 | } 307 | --------------------------------------------------------------------------------