using System; using R3; using RebootKit.Engine.Foundation; using RebootKit.Engine.Main; using RebootKit.Engine.Multiplayer; using Steamworks; using Steamworks.Data; using Logger = RebootKit.Engine.Foundation.Logger; namespace RebootKit.Engine.Steam { class SteamNetworkTransport : INetworkTransport { static readonly Logger s_Logger = new Logger(nameof(SteamNetworkTransport)); const int k_DefaultPort = 420; ServerCallbacks m_SocketManager; ClientCallbacks m_ConnectionManager; IDisposable m_TickDisposable; SteamId m_HostSteamID; public void Initialize() { m_TickDisposable = Observable.EveryUpdate() .Subscribe(_ => Tick()); SteamNetworkingUtils.DebugLevel = NetDebugOutput.Debug; SteamNetworkingSockets.OnConnectionStatusChanged += OnConnectionStatusChanged; SteamNetworkingUtils.OnDebugOutput += OnSteamNetworkDebugOutput; SteamNetworkingUtils.InitRelayNetworkAccess(); } public void Shutdown() { Disconnect(); SteamNetworkingUtils.OnDebugOutput -= OnSteamNetworkDebugOutput; SteamNetworkingSockets.OnConnectionStatusChanged -= OnConnectionStatusChanged; m_TickDisposable.Dispose(); } public bool IsServer() { return m_SocketManager != null; } public bool IsClient() { return m_ConnectionManager != null; } public bool StartServer() { Disconnect(); s_Logger.Info("Creating server..."); try { m_HostSteamID = SteamNetworkingSockets.Identity.SteamId; m_SocketManager = SteamNetworkingSockets.CreateRelaySocket(k_DefaultPort); m_ConnectionManager = SteamNetworkingSockets.ConnectRelay(m_HostSteamID, k_DefaultPort); } catch (Exception e) { s_Logger.Error($"Failed to create server: {e.Message}"); m_SocketManager = null; m_ConnectionManager = null; return false; } return true; } public void StopServer() { Disconnect(); } public bool Connect(ulong serverID) { if (IsServer()) { s_Logger.Error("Cannot connect to a server while running as a server."); return false; } Disconnect(); s_Logger.Info("Connecting to server with steam ID: " + serverID); try { m_ConnectionManager = SteamNetworkingSockets.ConnectRelay(serverID, k_DefaultPort); m_HostSteamID = serverID; } catch (Exception e) { s_Logger.Error($"Failed to connect to server with ID {serverID}: {e.Message}"); m_ConnectionManager = null; return false; } return true; } public void Disconnect() { if (m_ConnectionManager != null) { s_Logger.Info("Disconnecting from the server..."); m_ConnectionManager.Close(); m_ConnectionManager = null; } if (m_SocketManager != null) { s_Logger.Info("Shutting down the server..."); m_SocketManager.Close(); m_SocketManager = null; } } public void Send(ulong clientID, ArraySegment data, SendMode mode) { if (clientID == 0) { clientID = m_HostSteamID; } } void OnSteamNetworkDebugOutput(NetDebugOutput level, string message) { LogLevel logLevel = level switch { NetDebugOutput.Debug => LogLevel.Debug, NetDebugOutput.Msg => LogLevel.Info, NetDebugOutput.Warning => LogLevel.Warning, NetDebugOutput.Error => LogLevel.Error, _ => LogLevel.Info }; s_Logger.Log(logLevel, message); } void Tick() { m_SocketManager?.Receive(); m_ConnectionManager?.Receive(); } void OnConnectionStatusChanged(Connection connection, ConnectionInfo info) { s_Logger.Info($"OnConnectionStatusChanged: {connection.Id} - {info.Identity} - Status: {info.State}"); } class ServerCallbacks : SocketManager { public override void OnConnecting(Connection connection, ConnectionInfo data) { base.OnConnecting(connection, data); connection.Accept(); s_Logger.Info($"OnConnecting: {connection.Id} - {data.Identity}"); } public override void OnConnected(Connection connection, ConnectionInfo data) { base.OnConnected(connection, data); s_Logger.Info($"OnConnected: {connection.Id} - {data.Identity}"); connection.SendMessage(new byte[] { 0xBE, 0xFE, 0x00, 0x00 }, 0, 4, 0); } public override void OnDisconnected(Connection connection, ConnectionInfo data) { base.OnDisconnected(connection, data); s_Logger.Info($"OnDisconnected: {connection.Id} - {data.Identity}"); } public override void OnMessage(Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel) { base.OnMessage(connection, identity, data, size, messageNum, recvTime, channel); byte[] buffer = new byte[size]; System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size); RR.OnServerDataReceived(buffer); s_Logger.Info($"OnMessage: {connection.Id} - {identity} - Size: {size} - Channel: {channel}"); } } class ClientCallbacks : ConnectionManager { public override void OnConnected(ConnectionInfo info) { base.OnConnected(info); s_Logger.Info("ConnectionOnConnected"); } public override void OnConnecting(ConnectionInfo info) { base.OnConnecting(info); s_Logger.Info("ConnectionOnConnecting"); } public override void OnDisconnected(ConnectionInfo info) { base.OnDisconnected(info); s_Logger.Info("ConnectionOnDisconnected"); } public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) { byte[] buffer = new byte[size]; System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size); RR.OnClientDataReceived(buffer); s_Logger.Info($"OnMessage: Size: {size} - Channel: {channel}"); } } } }