// This file is provided under The MIT License as part of RiptideSteamTransport. // Copyright (c) Tom Weiland // For additional information please see the included LICENSE.md file or view it on GitHub: // https://github.com/tom-weiland/RiptideSteamTransport/blob/main/LICENSE.md using Steamworks; using System; using System.Collections.Generic; using UnityEngine; namespace Riptide.Transports.Steam { public class SteamServer : SteamPeer, IServer { public event EventHandler Connected; public event EventHandler DataReceived; public event EventHandler Disconnected; public ushort Port { get; private set; } private Dictionary connections; private HSteamListenSocket listenSocket; private Callback connectionStatusChanged; public void Start(ushort port) { Port = port; connections = new Dictionary(); connectionStatusChanged = Callback.Create(OnConnectionStatusChanged); try { #if UNITY_SERVER SteamGameServerNetworkingUtils.InitRelayNetworkAccess(); #else SteamNetworkingUtils.InitRelayNetworkAccess(); #endif } catch (Exception ex) { Debug.LogException(ex); } SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { }; listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(port, options.Length, options); } private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback) { CSteamID clientSteamId = callback.m_info.m_identityRemote.GetSteamID(); switch (callback.m_info.m_eState) { case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting: Accept(callback.m_hConn); break; case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected: Add(new SteamConnection(clientSteamId, callback.m_hConn, this)); break; case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer: SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Closed by peer", false); OnDisconnected(clientSteamId, DisconnectReason.Disconnected); break; case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally: SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Problem detected", false); OnDisconnected(clientSteamId, DisconnectReason.TransportError); break; default: Debug.Log($"{LogName}: {clientSteamId}'s connection state changed - {callback.m_info.m_eState}"); break; } } internal void Add(SteamConnection connection) { if (!connections.ContainsKey(connection.SteamId)) { connections.Add(connection.SteamId, connection); OnConnected(connection); } else Debug.Log($"{LogName}: Connection from {connection.SteamId} could not be accepted: Already connected"); } private void Accept(HSteamNetConnection connection) { EResult result = SteamNetworkingSockets.AcceptConnection(connection); if (result != EResult.k_EResultOK) Debug.LogWarning($"{LogName}: Connection could not be accepted: {result}"); } public void Close(Connection connection) { if (connection is SteamConnection steamConnection) { SteamNetworkingSockets.CloseConnection(steamConnection.SteamNetConnection, 0, "Disconnected by server", false); connections.Remove(steamConnection.SteamId); } } public void Poll() { foreach (SteamConnection connection in connections.Values) Receive(connection); } // TODO: disable nagle so this isn't needed //public void Flush() //{ // foreach (SteamConnection connection in connections.Values) // SteamNetworkingSockets.FlushMessagesOnConnection(connection.SteamNetConnection); //} public void Shutdown() { if (connectionStatusChanged != null) { connectionStatusChanged.Dispose(); connectionStatusChanged = null; } foreach (SteamConnection connection in connections.Values) SteamNetworkingSockets.CloseConnection(connection.SteamNetConnection, 0, "Server stopped", false); connections.Clear(); SteamNetworkingSockets.CloseListenSocket(listenSocket); } protected internal virtual void OnConnected(Connection connection) { Connected?.Invoke(this, new ConnectedEventArgs(connection)); } protected override void OnDataReceived(byte[] dataBuffer, int amount, SteamConnection fromConnection) { if ((MessageHeader)dataBuffer[0] == MessageHeader.Connect) { if (fromConnection.DidReceiveConnect) return; fromConnection.DidReceiveConnect = true; } DataReceived?.Invoke(this, new DataReceivedEventArgs(dataBuffer, amount, fromConnection)); } protected virtual void OnDisconnected(CSteamID steamId, DisconnectReason reason) { if (connections.TryGetValue(steamId, out SteamConnection connection)) { Disconnected?.Invoke(this, new DisconnectedEventArgs(connection, reason)); connections.Remove(steamId); } } } }