diff --git a/Nebula.sln b/Nebula.sln index afe0d2be1..f8c01db74 100644 --- a/Nebula.sln +++ b/Nebula.sln @@ -19,11 +19,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaWorld", "NebulaWorld\NebulaWorld.csproj", "{28AEA139-FB22-4672-AF51-28B728CF2978}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "websocket-sharp", "dep\websocket-sharp\websocket-sharp\websocket-sharp.csproj", "{B357BAC7-529E-4D81-A0D2-71041B19C8DE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaNetwork", "NebulaNetwork\NebulaNetwork.csproj", "{36A3D8AE-2A0A-4A1F-8A65-4FB61B01F779}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NebulaAPI", "NebulaAPI\NebulaAPI.csproj", "{B59DB3BB-DBA2-42AF-B4F1-32A9A6A273D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAPI", "NebulaAPI\NebulaAPI.csproj", "{B59DB3BB-DBA2-42AF-B4F1-32A9A6A273D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValveSockets", "dep\valve.sockets\ValveSockets.csproj", "{D4EEDD8E-0ED6-4426-A3F0-3642A04434D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,6 +31,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Release|Any CPU.Build.0 = Release|Any CPU {C6237195-F77C-40C0-B06A-4AD51CAD314D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C6237195-F77C-40C0-B06A-4AD51CAD314D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C6237195-F77C-40C0-B06A-4AD51CAD314D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -39,22 +43,18 @@ Global {28AEA139-FB22-4672-AF51-28B728CF2978}.Debug|Any CPU.Build.0 = Debug|Any CPU {28AEA139-FB22-4672-AF51-28B728CF2978}.Release|Any CPU.ActiveCfg = Release|Any CPU {28AEA139-FB22-4672-AF51-28B728CF2978}.Release|Any CPU.Build.0 = Release|Any CPU - {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.Build.0 = Release|Any CPU {36A3D8AE-2A0A-4A1F-8A65-4FB61B01F779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {36A3D8AE-2A0A-4A1F-8A65-4FB61B01F779}.Debug|Any CPU.Build.0 = Debug|Any CPU {36A3D8AE-2A0A-4A1F-8A65-4FB61B01F779}.Release|Any CPU.ActiveCfg = Release|Any CPU {36A3D8AE-2A0A-4A1F-8A65-4FB61B01F779}.Release|Any CPU.Build.0 = Release|Any CPU - {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47B9362C-E91B-4AE4-979B-7D811AB4D1EA}.Release|Any CPU.Build.0 = Release|Any CPU {B59DB3BB-DBA2-42AF-B4F1-32A9A6A273D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B59DB3BB-DBA2-42AF-B4F1-32A9A6A273D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {B59DB3BB-DBA2-42AF-B4F1-32A9A6A273D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {B59DB3BB-DBA2-42AF-B4F1-32A9A6A273D2}.Release|Any CPU.Build.0 = Release|Any CPU + {D4EEDD8E-0ED6-4426-A3F0-3642A04434D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4EEDD8E-0ED6-4426-A3F0-3642A04434D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4EEDD8E-0ED6-4426-A3F0-3642A04434D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4EEDD8E-0ED6-4426-A3F0-3642A04434D6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NebulaAPI/Packets/NetworkOptionAttributes.cs b/NebulaAPI/Packets/NetworkOptionAttributes.cs new file mode 100644 index 000000000..a094830ae --- /dev/null +++ b/NebulaAPI/Packets/NetworkOptionAttributes.cs @@ -0,0 +1,10 @@ +using System; + +namespace NebulaAPI +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class NetworkOptionsAttribute : Attribute + { + public bool Reliable = true; + } +} diff --git a/NebulaModel/NebulaModel.csproj b/NebulaModel/NebulaModel.csproj index 60069a787..18df05dc1 100644 --- a/NebulaModel/NebulaModel.csproj +++ b/NebulaModel/NebulaModel.csproj @@ -1,7 +1,7 @@  - + diff --git a/NebulaModel/NetworkProvider.cs b/NebulaModel/NetworkProvider.cs index 24e745f8f..06cc0b4bf 100644 --- a/NebulaModel/NetworkProvider.cs +++ b/NebulaModel/NetworkProvider.cs @@ -1,6 +1,9 @@ using NebulaAPI; +using NebulaModel.Logger; using NebulaModel.Networking.Serialization; using System; +using System.Threading; +using Valve.Sockets; namespace NebulaModel { @@ -10,12 +13,67 @@ public abstract class NetworkProvider : IDisposable, INetworkProvider public IPlayerManager PlayerManager { get; } + protected static NetworkingSockets Sockets { get; private set; } + + protected static NetworkingUtils Utils { get; private set; } + + protected static NetworkProvider Provider { get; set; } + + protected static Thread Worker { get; private set; } + + protected static bool ShouldPoll { get; set; } = false; + + static NetworkProvider() + { + Library.Initialize(); + + Sockets = new NetworkingSockets(); + Utils = new NetworkingUtils(); + +#if DEBUG + Utils.SetDebugCallback(DebugType.Everything, (DebugType type, string message) => + { + Log.Debug(message); + }); +#endif + + // We have to store a static instance to the current NetworkProvider as this callback comes from native code and + // therefore has to be a flat cdecl call, it cannot capture anything or have any context + Utils.SetStatusCallback((ref StatusInfo info) => + { + Provider?.OnEvent(ref info); + }); + + // Set high speeds + Configuration configSendRateMax = new Configuration(); + configSendRateMax.data.Int32 = 0x10000000; + configSendRateMax.dataType = ConfigurationDataType.Int32; + configSendRateMax.value = ConfigurationValue.SendRateMax; + + Configuration configSendRateMin = new Configuration(); + configSendRateMin.data.Int32 = 5 * 1024 * 1024; // Make it user configurable? + configSendRateMin.dataType = ConfigurationDataType.Int32; + configSendRateMin.value = ConfigurationValue.SendRateMin; + + Configuration configSendBuffer = new Configuration(); + configSendBuffer.data.Int32 = 0x1000000; + configSendBuffer.dataType = ConfigurationDataType.Int32; + configSendBuffer.value = ConfigurationValue.SendBufferSize; + + Utils.SetConfigurationValue(configSendRateMax, ConfigurationScope.Global, new IntPtr()); + Utils.SetConfigurationValue(configSendRateMin, ConfigurationScope.Global, new IntPtr()); + Utils.SetConfigurationValue(configSendBuffer, ConfigurationScope.Global, new IntPtr()); + } + protected NetworkProvider(IPlayerManager playerManager) { + Provider = this; PacketProcessor = new NetPacketProcessor(); PlayerManager = playerManager; } + protected abstract void OnEvent(ref StatusInfo info); + public abstract void Start(); public abstract void Stop(); diff --git a/NebulaModel/Networking/NebulaConnection.cs b/NebulaModel/Networking/NebulaConnection.cs index 0521eba58..3a853f5c7 100644 --- a/NebulaModel/Networking/NebulaConnection.cs +++ b/NebulaModel/Networking/NebulaConnection.cs @@ -3,88 +3,261 @@ using NebulaModel.Networking.Serialization; using System; using System.Collections.Generic; +using System.Linq; using System.Net; -using WebSocketSharp; +using Valve.Sockets; namespace NebulaModel.Networking { public class NebulaConnection : INebulaConnection { + private class FragmentedPayload + { + public int Remaining; + public byte[] Data; + } + + private readonly NetworkingSockets sockets; private readonly EndPoint peerEndpoint; - private readonly WebSocket peerSocket; + private readonly uint peerSocket; private readonly NetPacketProcessor packetProcessor; - private readonly Queue pendingPackets = new Queue(); - private bool enable = true; + private readonly Queue sendQueue = new Queue(); + private Dictionary fragmentedPayloads = new Dictionary(); + private uint nextFragmentId = 0; + + private const int KMaxPacketSize = Library.maxMessageSize / 2; + private const int KMaxFragmentSize = 1 << 16; + + public bool IsAlive + { + get + { + ConnectionRealTimeStatus realTimeStatus = new ConnectionRealTimeStatus(); + ConnectionRealTimeLaneStatus realTimeLaneStatus = new ConnectionRealTimeLaneStatus(); + lock (sockets) + { + sockets.GetConnectionRealTimeStatus(peerSocket, ref realTimeStatus, 1, ref realTimeLaneStatus); + } - public bool IsAlive => peerSocket?.IsAlive ?? false; + return realTimeStatus.state == ConnectionState.Connected; + } + } - public NebulaConnection(WebSocket peerSocket, EndPoint peerEndpoint, NetPacketProcessor packetProcessor) + public NebulaConnection(NetworkingSockets sockets, uint peerSocket, EndPoint peerEndpoint, NetPacketProcessor packetProcessor) { + this.sockets = sockets; this.peerEndpoint = peerEndpoint; this.peerSocket = peerSocket; this.packetProcessor = packetProcessor; } - public void SendPacket(T packet) where T : class, new() + public void Update() { - lock (pendingPackets) + // Try to send the first packet, if we did, remove it + while(sendQueue.Count > 0 && SendImmediateRawPacket(sendQueue.Peek()) == Result.OK) { - byte[] rawData = packetProcessor.Write(packet); - pendingPackets.Enqueue(rawData); - ProcessPacketQueue(); - } + sendQueue.Dequeue(); + } } - public void SendRawPacket(byte[] rawData) + public void SendPacket(T packet) where T : class, new() { - lock (pendingPackets) + if (IsAlive) + { + var netAttribute = typeof(T).GetCustomAttributes(typeof(NetworkOptionsAttribute), false).FirstOrDefault() as NetworkOptionsAttribute; + + // By default we send everything as reliable data + SendFlags sendFlags = SendFlags.Reliable; + + // Packets can also specify explicitly if they are reliable or not + if (netAttribute != null && netAttribute.Reliable == false) + sendFlags = SendFlags.Unreliable; + + SendRawPacket(packetProcessor.Write(packet), sendFlags); + } + else { - pendingPackets.Enqueue(rawData); - ProcessPacketQueue(); - } + Log.Warn($"Cannot send packet {packet?.GetType()} to a closed connection {peerEndpoint.GetHashCode()}"); + } } - private void ProcessPacketQueue() + public bool SendRawPacket(byte[] rawData, SendFlags sendFlags = SendFlags.Reliable) { - if (enable && pendingPackets.Count > 0) + if (IsAlive) { - byte[] packet = pendingPackets.Dequeue(); - if (peerSocket.ReadyState == WebSocketState.Open) + // Valve's net lib has restrictions on packet size much larger than this (512KB) + // but we fragment into smaller pieces to prevent blocking other packets from going + // through + if (rawData.Length >= KMaxPacketSize) { - peerSocket.SendAsync(packet, OnSendCompleted); - enable = false; + FragmentPacket(rawData); } else { - Log.Warn($"Cannot send packet to a {peerSocket.ReadyState} connection {peerEndpoint.GetHashCode()}"); + // We prefix the data with a 0 byte as this is not a fragment + byte[] data = new byte[rawData.Length + 1]; + data[0] = 0; + Array.Copy(rawData, 0, data, 1, rawData.Length); + + Result result = Result.LimitExceeded; + + // If we are trying to send a reliable packet and we have packets queued, queue the packet to preserve send order + if (sendQueue.Count == 0 || sendFlags != SendFlags.Reliable) + { + lock (sockets) + { + result = sockets.SendMessageToConnection(peerSocket, data, sendFlags); + } + } + + // If the underlying send queue is full and we are not dealing with an unreliable packet, queue it for resend + if (result == Result.LimitExceeded && sendFlags != SendFlags.Unreliable) + { + sendQueue.Enqueue(data); + } + else if (result != Result.OK) + { + Log.Error($"Cannot send raw data because of error {result}"); + } + else + return true; } } + else + { + Log.Warn($"Cannot send raw packet to a closed connection {peerEndpoint.GetHashCode()}"); + } + + return false; } - private void OnSendCompleted(bool result) + private Result SendImmediateRawPacket(byte[] rawData) { - lock (pendingPackets) + Result result = Result.Fail; + lock (sockets) { - enable = true; - ProcessPacketQueue(); + result = sockets.SendMessageToConnection(peerSocket, rawData, SendFlags.Reliable); } + + // All immediate sends are reliable so queue them if we couldn't send them right now + if (result == Result.LimitExceeded) + { + return Result.LimitExceeded; + } + else if (result != Result.OK) + { + Log.Error($"Cannot send raw data because of error {result}"); + } + else + return Result.OK; + + return Result.Fail; } - public void Disconnect(DisconnectionReason reason = DisconnectionReason.Normal, string reasonString = null) + private void FragmentPacket(byte[] rawData) { - if (string.IsNullOrEmpty(reasonString)) + NetDataWriter writer = new NetDataWriter(); + var fragmentId = nextFragmentId++; + + for (var index = 0; index < rawData.Length; index += KMaxFragmentSize) + { + writer.Reset(); + + // We prefix the data with a 1 byte as this is a fragment + writer.Put((byte)1); + writer.Put(fragmentId); + writer.Put((int)rawData.Length); + writer.Put((uint)index); + + var dataToSend = rawData.Length - index; + var dataChunk = dataToSend > KMaxFragmentSize ? KMaxFragmentSize : dataToSend; + + writer.PutBytesWithLength(rawData, index, dataChunk); + + // Try to send fragments as they are processed, if we fail to send it will be queued for later send + var data = writer.CopyData(); + if (SendImmediateRawPacket(data) == Result.LimitExceeded) + { + Log.Warn("LimitExceeded!"); + sendQueue.Enqueue(data); + } + } + } + + public byte[] Receive(byte[] rawData) + { + byte[] payload = rawData.Skip(1).ToArray(); + + // Not a fragment, return the data for processing + if (rawData[0] == 0) { - peerSocket.Close((ushort)reason); + return payload; } + // Fragment, use it to reconstruct the packet else { - if (System.Text.Encoding.UTF8.GetBytes(reasonString).Length <= 123) + var data = ProcessFragment(payload); + // If the processed fragment was the last missing piece, we get the full packet, return it for processing + if (data != null) + { + return data; + } + } + + return null; + } + + private byte[] ProcessFragment(byte[] payload) + { + NetDataReader reader = new NetDataReader(payload); + var fragmentId = reader.GetUInt(); + var totalLength = reader.GetInt(); + var offset = reader.GetUInt(); + var data = reader.GetBytesWithLength(); + + FragmentedPayload fragmentedPayload; + if(!fragmentedPayloads.TryGetValue(fragmentId, out fragmentedPayload)) + { + // This fragment is for a packet we do not know yet, create it + fragmentedPayload = new FragmentedPayload(); + fragmentedPayload.Data = new byte[totalLength]; + fragmentedPayload.Remaining = totalLength; + fragmentedPayloads.Add(fragmentId, fragmentedPayload); + } + + fragmentedPayload.Remaining -= data.Length; + Array.Copy(data, 0, fragmentedPayload.Data, offset, data.Length); + + + if (fragmentedPayload.Remaining > 0) + { + Log.Info($"fragment[{fragmentId}] {fragmentedPayload.Data.Length - fragmentedPayload.Remaining:n0} / {fragmentedPayload.Data.Length:n0} bytes"); + return null; + } + + // We have filled all the gaps, return the data + fragmentedPayloads.Remove(fragmentId); + return fragmentedPayload.Data; + } + + public void Disconnect(DisconnectionReason reason = DisconnectionReason.Normal, string reasonString = null) + { + lock (sockets) + { + if (string.IsNullOrEmpty(reasonString)) { - peerSocket.Close((ushort)reason, reasonString); + sockets.CloseConnection(peerSocket, (int)reason, "", true); } else { - throw new ArgumentException("Reason string cannot take up more than 123 bytes"); + if (System.Text.Encoding.UTF8.GetBytes(reasonString).Length <= Library.maxCloseMessageLength) + { + sockets.CloseConnection(peerSocket, (int)reason, reasonString, true); + } + else + { + throw new ArgumentException("Reason string cannot take up more than 123 bytes"); + } } } } @@ -94,6 +267,8 @@ public void Disconnect(DisconnectionReason reason = DisconnectionReason.Normal, return Equals(left, right); } + + public static bool operator !=(NebulaConnection left, NebulaConnection right) { return !Equals(left, right); @@ -120,5 +295,7 @@ public override int GetHashCode() { return peerEndpoint?.GetHashCode() ?? 0; } + + } } diff --git a/NebulaModel/Packets/Players/PlayerMovement.cs b/NebulaModel/Packets/Players/PlayerMovement.cs index 5145ce8f5..e3c27604e 100644 --- a/NebulaModel/Packets/Players/PlayerMovement.cs +++ b/NebulaModel/Packets/Players/PlayerMovement.cs @@ -3,6 +3,7 @@ namespace NebulaModel.Packets.Players { [HidePacketInDebugLogs] + [NetworkOptions(Reliable = false)] public class PlayerMovement { public ushort PlayerId { get; set; } diff --git a/NebulaNetwork/Client.cs b/NebulaNetwork/Client.cs index f0698ba4a..e887751eb 100644 --- a/NebulaNetwork/Client.cs +++ b/NebulaNetwork/Client.cs @@ -9,10 +9,9 @@ using NebulaModel.Utils; using NebulaWorld; using System.Net; -using System.Net.Sockets; using System.Reflection; using UnityEngine; -using WebSocketSharp; +using Valve.Sockets; namespace NebulaNetwork { @@ -23,8 +22,9 @@ public class Client : NetworkProvider, IClient private readonly IPEndPoint serverEndpoint; public IPEndPoint ServerEndpoint => serverEndpoint; - - private WebSocket clientSocket; + + private Address serverAddress; + private uint connection; private NebulaConnection serverConnection; private float mechaSynchonizationTimer = 0f; @@ -38,7 +38,6 @@ public Client(string url, int port) public Client(IPEndPoint endpoint) : base(null) { serverEndpoint = endpoint; - } public override void Start() @@ -58,12 +57,16 @@ public override void Start() PacketProcessor.SimulateLatency = true; #endif - clientSocket = new WebSocket($"ws://{serverEndpoint}/socket"); - clientSocket.OnOpen += ClientSocket_OnOpen; - clientSocket.OnClose += ClientSocket_OnClose; - clientSocket.OnMessage += ClientSocket_OnMessage; + serverAddress = new Address(); + serverAddress.SetAddress(serverEndpoint.Address.ToString(), (ushort)serverEndpoint.Port); + + ShouldPoll = true; + + lock (Sockets) + { + connection = Sockets.Connect(ref serverAddress); + } - clientSocket.Connect(); ((LocalPlayer)Multiplayer.Session.LocalPlayer).IsHost = false; @@ -77,13 +80,40 @@ public override void Start() NebulaModAPI.OnMultiplayerGameStarted?.Invoke(); } + protected override void OnEvent(ref StatusInfo info) + { + switch (info.connectionInfo.state) + { + case ConnectionState.None: + break; + + case ConnectionState.Connected: + OnOpen(ref info); + Log.Info("Client connected to server - ID: " + connection); + break; + + case ConnectionState.ClosedByPeer: + case ConnectionState.ProblemDetectedLocally: + Sockets.CloseConnection(info.connection); + if (info.connection == connection) + OnClose(ref info); + break; + } + } + public override void Stop() { - clientSocket?.Close((ushort)DisconnectionReason.ClientRequestedDisconnect, "Player left the game"); + lock (Sockets) + { + Sockets.CloseConnection(connection, (int)DisconnectionReason.ClientRequestedDisconnect, "Player left the game", true); + } // load settings again to dispose the temp soil setting that could have been received from server Config.LoadOptions(); NebulaModAPI.OnMultiplayerGameEnded?.Invoke(); + + ShouldPoll = false; + Provider = null; } public override void Dispose() @@ -126,6 +156,20 @@ public override void SendPacketToStarExclude(T packet, int starId, INebulaCon public override void Update() { + lock (Sockets) + { + Sockets.RunCallbacks(); + + void message(in NetworkingMessage netMessage) + { + OnMessage(netMessage); + } + + Sockets.ReceiveMessagesOnConnection(connection, message, 100); + } + + serverConnection?.Update(); + PacketProcessor.ProcessPacketQueue(); if (Multiplayer.Session.IsGameLoaded) @@ -149,20 +193,25 @@ public override void Update() } } - private void ClientSocket_OnMessage(object sender, MessageEventArgs e) + private void OnMessage(NetworkingMessage message) { if (!Multiplayer.IsLeavingGame) { - PacketProcessor.EnqueuePacketForProcessing(e.RawData, new NebulaConnection(clientSocket, serverEndpoint, PacketProcessor)); + byte[] rawData = new byte[message.length]; + message.CopyTo(rawData); + + var data = serverConnection.Receive(rawData); + if (data != null) + { + PacketProcessor.EnqueuePacketForProcessing(data, serverConnection); + } } } - private void ClientSocket_OnOpen(object sender, System.EventArgs e) + private void OnOpen(ref StatusInfo info) { - DisableNagleAlgorithm(clientSocket); - Log.Info($"Server connection established"); - serverConnection = new NebulaConnection(clientSocket, serverEndpoint, PacketProcessor); + serverConnection = new NebulaConnection(Sockets, connection, serverEndpoint, PacketProcessor); //TODO: Maybe some challenge-response authentication mechanism? @@ -172,14 +221,18 @@ private void ClientSocket_OnOpen(object sender, System.EventArgs e) Config.Options.GetMechaColors())); } - private void ClientSocket_OnClose(object sender, CloseEventArgs e) + private void OnClose(ref StatusInfo info) { serverConnection = null; + var endReason = info.connectionInfo.endReason; + var endDebug = info.connectionInfo.endDebug; + var connection = info.connection; + UnityDispatchQueue.RunOnMainThread(() => { // If the client is Quitting by himself, we don't have to inform him of his disconnection. - if (e.Code == (ushort)DisconnectionReason.ClientRequestedDisconnect) + if (endReason == (int)DisconnectionReason.ClientRequestedDisconnect) { return; } @@ -190,29 +243,29 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) GameMain.instance._paused = true; } - if (e.Code == (ushort)DisconnectionReason.ModIsMissing) + if (endReason == (int)DisconnectionReason.ModIsMissing) { InGamePopup.ShowWarning( "Mod Mismatch", - $"You are missing mod {e.Reason}", + $"You are missing mod {endDebug}", "OK".Translate(), Multiplayer.LeaveGame); return; } - if (e.Code == (ushort)DisconnectionReason.ModIsMissingOnServer) + if (endReason == (int)DisconnectionReason.ModIsMissingOnServer) { InGamePopup.ShowWarning( "Mod Mismatch", - $"Server is missing mod {e.Reason}", + $"Server is missing mod {endDebug}", "OK".Translate(), Multiplayer.LeaveGame); return; } - if (e.Code == (ushort)DisconnectionReason.ModVersionMismatch) + if (endReason == (int)DisconnectionReason.ModVersionMismatch) { - string[] versions = e.Reason.Split(';'); + string[] versions = endDebug.Split(';'); InGamePopup.ShowWarning( "Mod Version Mismatch", $"Your mod {versions[0]} version is not the same as the Host version.\nYou:{versions[1]} - Remote:{versions[2]}", @@ -221,9 +274,9 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) return; } - if (e.Code == (ushort)DisconnectionReason.GameVersionMismatch) + if (endReason == (int)DisconnectionReason.GameVersionMismatch) { - string[] versions = e.Reason.Split(';'); + string[] versions = endDebug.Split(';'); InGamePopup.ShowWarning( "Game Version Mismatch", $"Your version of the game is not the same as the one used by the Host.\nYou:{versions[0]} - Remote:{versions[1]}", @@ -236,7 +289,7 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) { InGamePopup.ShowWarning( "Connection Lost", - $"You have been disconnected from the server.\n{e.Reason}", + $"You have been disconnected from the server.\n{endDebug}", "Quit", Multiplayer.LeaveGame); if (Multiplayer.Session.IsInLobby) @@ -256,14 +309,5 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) } }); } - - private static void DisableNagleAlgorithm(WebSocket socket) - { - TcpClient tcpClient = AccessTools.FieldRefAccess("_tcpClient")(socket); - if (tcpClient != null) - { - tcpClient.NoDelay = true; - } - } } } diff --git a/NebulaNetwork/NebulaNetwork.csproj b/NebulaNetwork/NebulaNetwork.csproj index a388a05e4..fdfcadef1 100644 --- a/NebulaNetwork/NebulaNetwork.csproj +++ b/NebulaNetwork/NebulaNetwork.csproj @@ -1,12 +1,10 @@ + - - - \ No newline at end of file diff --git a/NebulaNetwork/Server.cs b/NebulaNetwork/Server.cs index 07b51cbb2..ec04d1ae5 100644 --- a/NebulaNetwork/Server.cs +++ b/NebulaNetwork/Server.cs @@ -1,19 +1,17 @@ -using HarmonyLib; -using NebulaAPI; +using NebulaAPI; using NebulaModel; using NebulaModel.DataStructures; using NebulaModel.Networking; -using NebulaModel.Networking.Serialization; using NebulaModel.Packets.GameHistory; using NebulaModel.Packets.GameStates; using NebulaModel.Packets.Universe; using NebulaModel.Utils; using NebulaWorld; -using System.Net.Sockets; +using System.Collections.Generic; +using System.Net; using System.Reflection; using UnityEngine; -using WebSocketSharp; -using WebSocketSharp.Server; +using Valve.Sockets; namespace NebulaNetwork { @@ -31,10 +29,12 @@ public class Server : NetworkProvider, IServer private float dysonSphereUpdateTimer = 0; private float warningUpdateTimer = 0; - private WebSocketServer socket; - private readonly int port; private readonly bool loadSaveFile; + private uint listenSocket; + private uint pollGroup; + + private Dictionary connections = new Dictionary(); public int Port => port; @@ -66,20 +66,16 @@ public override void Start() PacketProcessor.SimulateLatency = true; #endif - socket = new WebSocketServer(System.Net.IPAddress.IPv6Any, port); - DisableNagleAlgorithm(socket); - WebSocketService.PacketProcessor = PacketProcessor; - WebSocketService.PlayerManager = PlayerManager; - socket.AddWebSocketService("/socket", wse => new WebSocketService()); - try - { - socket.KeepClean = Config.Options.CleanupInactiveSessions; - socket.Start(); - }catch(System.InvalidOperationException e) + ShouldPoll = true; + + lock (Sockets) { - InGamePopup.ShowError("Error", "An error occurred while hosting the game: " + e.Message, "Close"); - Stop(); - return; + pollGroup = Sockets.CreatePollGroup(); + + Address address = new Address(); + address.SetAddress("::0", (ushort)port); + + listenSocket = Sockets.CreateListenSocket(ref address); } ((LocalPlayer)Multiplayer.Session.LocalPlayer).IsHost = true; @@ -93,11 +89,56 @@ public override void Start() NebulaModAPI.OnMultiplayerGameStarted?.Invoke(); } + protected override void OnEvent(ref StatusInfo info) + { + switch (info.connectionInfo.state) + { + case ConnectionState.None: + break; + + case ConnectionState.Connecting: + if (Multiplayer.Session.IsGameLoaded == false && Multiplayer.Session.IsInLobby == false) + { + Sockets.CloseConnection(info.connection, (int)DisconnectionReason.HostStillLoading, "Host still loading, please try again later.", true); + } + else + { + Sockets.AcceptConnection(info.connection); + Sockets.SetConnectionPollGroup(pollGroup, info.connection); + } + break; + + case ConnectionState.Connected: + OnOpen(ref info); + break; + + case ConnectionState.ClosedByPeer: + case ConnectionState.ProblemDetectedLocally: + Sockets.CloseConnection(info.connection); + OnClose(ref info); + break; + } + } + public override void Stop() { - socket?.Stop(); + lock (Sockets) + { + foreach (var kvp in connections) + { + Sockets.CloseConnection(kvp.Key); + } + + Sockets.CloseListenSocket(listenSocket); + Sockets.DestroyPollGroup(pollGroup); + } NebulaModAPI.OnMultiplayerGameEnded?.Invoke(); + + connections.Clear(); + + ShouldPoll = false; + Provider = null; } public override void Dispose() @@ -180,83 +221,85 @@ public override void Update() warningUpdateTimer = 0; Multiplayer.Session.Warning.SendBroadcastIfNeeded(); } - } - } - - private void DisableNagleAlgorithm(WebSocketServer socketServer) - { - TcpListener listener = AccessTools.FieldRefAccess("_listener")(socketServer); - listener.Server.NoDelay = true; - } - - private class WebSocketService : WebSocketBehavior - { - public static IPlayerManager PlayerManager; - public static NetPacketProcessor PacketProcessor; - - public WebSocketService() { } - - public WebSocketService(IPlayerManager playerManager, NetPacketProcessor packetProcessor) - { - PlayerManager = playerManager; - PacketProcessor = packetProcessor; } - protected override void OnOpen() + lock (Sockets) { - if (Multiplayer.Session.IsGameLoaded == false && Multiplayer.Session.IsInLobby == false) + Sockets.RunCallbacks(); + + void message(in NetworkingMessage netMessage) { - // Reject any connection that occurs while the host's game is loading. - Context.WebSocket.Close((ushort)DisconnectionReason.HostStillLoading, "Host still loading, please try again later."); - return; + OnMessage(netMessage); } - NebulaModel.Logger.Log.Info($"Client connected ID: {ID}"); - NebulaConnection conn = new NebulaConnection(Context.WebSocket, Context.UserEndPoint, PacketProcessor); - PlayerManager.PlayerConnected(conn); + Sockets.ReceiveMessagesOnPollGroup(pollGroup, message, 100); } - protected override void OnMessage(MessageEventArgs e) + foreach (var kvp in connections) { - PacketProcessor.EnqueuePacketForProcessing(e.RawData, new NebulaConnection(Context.WebSocket, Context.UserEndPoint, PacketProcessor)); + kvp.Value.Update(); } - protected override void OnClose(CloseEventArgs e) + PacketProcessor.ProcessPacketQueue(); + } + + protected void OnOpen(ref StatusInfo info) + { + NebulaModel.Logger.Log.Info($"Client connected ID: {info.connection}"); + EndPoint endPoint = new IPEndPoint(IPAddress.Parse(info.connectionInfo.address.GetIP()), info.connectionInfo.address.port); + NebulaConnection conn = new NebulaConnection(Sockets, info.connection, endPoint, PacketProcessor); + + connections.Add(info.connection, conn); + + PlayerManager.PlayerConnected(conn); + } + + protected void OnMessage(NetworkingMessage message) + { + ConnectionInfo info = new ConnectionInfo(); + Sockets.GetConnectionInfo(message.connection, ref info); + EndPoint endPoint = new IPEndPoint(IPAddress.Parse(info.address.GetIP()), info.address.port); + + NebulaConnection connection; + if(connections.TryGetValue(message.connection, out connection)) { - // If the reason of a client disconnect is because we are still loading the game, - // we don't need to inform the other clients since the disconnected client never - // joined the game in the first place. - if (e.Code == (short)DisconnectionReason.HostStillLoading) + byte[] rawData = new byte[message.length]; + message.CopyTo(rawData); + + var data = connection.Receive(rawData); + if(data != null) { - return; + PacketProcessor.EnqueuePacketForProcessing(data, connection); } + } + } - NebulaModel.Logger.Log.Info($"Client disconnected: {ID}, reason: {e.Reason}"); - UnityDispatchQueue.RunOnMainThread(() => - { - // This is to make sure that we don't try to deal with player disconnection - // if it is because we have stopped the server and are not in a multiplayer game anymore. - if (Multiplayer.IsActive) - { - PlayerManager.PlayerDisconnected(new NebulaConnection(Context.WebSocket, Context.UserEndPoint, PacketProcessor)); - } - }); + protected void OnClose(ref StatusInfo info) + { + NebulaConnection connection = null; + connections.TryGetValue(info.connection, out connection); + + connections.Remove(info.connection); + + // If the reason of a client disconnect is because we are still loading the game, + // we don't need to inform the other clients since the disconnected client never + // joined the game in the first place. + if (info.connectionInfo.endReason == (int)DisconnectionReason.HostStillLoading) + { + return; } - protected override void OnError(ErrorEventArgs e) + + NebulaModel.Logger.Log.Info($"Client disconnected: {info.connection}, reason: {info.connectionInfo.endDebug}"); + UnityDispatchQueue.RunOnMainThread(() => { - // TODO: seems like clients erroring out in the sync process can lock the host with the joining player message, maybe this fixes it - NebulaModel.Logger.Log.Info($"Client disconnected because of an error: {ID}, reason: {e.Exception}"); - UnityDispatchQueue.RunOnMainThread(() => + // This is to make sure that we don't try to deal with player disconnection + // if it is because we have stopped the server and are not in a multiplayer game anymore. + if (Multiplayer.IsActive && connection != null) { - // This is to make sure that we don't try to deal with player disconnection - // if it is because we have stopped the server and are not in a multiplayer game anymore. - if (Multiplayer.IsActive) - { - PlayerManager.PlayerDisconnected(new NebulaConnection(Context.WebSocket, Context.UserEndPoint, PacketProcessor)); - } - }); - } + PlayerManager.PlayerDisconnected(connection); + } + }); } } } diff --git a/NebulaWorld/SimulatedWorld.cs b/NebulaWorld/SimulatedWorld.cs index a9685e02e..56f9121de 100644 --- a/NebulaWorld/SimulatedWorld.cs +++ b/NebulaWorld/SimulatedWorld.cs @@ -12,6 +12,7 @@ using NebulaWorld.MonoBehaviours.Remote; using System; using System.Collections.Generic; +using System.Diagnostics; using UnityEngine; using UnityEngine.UI; @@ -33,6 +34,7 @@ private sealed class ThreadSafe private Text pingIndicator; private LocalPlayerMovement localPlayerMovement; private LocalPlayerAnimation localPlayerAnimation; + private static readonly Stopwatch watch = new Stopwatch(); public Locker GetRemotePlayersModels(out Dictionary remotePlayersModels) { @@ -152,6 +154,11 @@ public void SetupInitialPlayerState() public void OnPlayerJoining(string Username) { + if (Multiplayer.Session.LocalPlayer.IsHost) + { + watch.Start(); + } + if (!IsPlayerJoining) { IsPlayerJoining = true; @@ -163,6 +170,13 @@ public void OnPlayerJoining(string Username) public void OnAllPlayersSyncCompleted() { + if (Multiplayer.Session.LocalPlayer.IsHost) + { + watch.Stop(); + Log.Debug($"Joined in {watch.Elapsed}"); + watch.Reset(); + } + IsPlayerJoining = false; InGamePopup.FadeOut(); GameMain.isFullscreenPaused = false; diff --git a/dep/Directory.Build.props b/dep/Directory.Build.props index dd0ccacef..deb3269c5 100644 --- a/dep/Directory.Build.props +++ b/dep/Directory.Build.props @@ -1,7 +1,4 @@ - - - \ No newline at end of file diff --git a/dep/valve.sockets/GameNetworkingSockets-prebuilt/GameNetworkingSockets.dll b/dep/valve.sockets/GameNetworkingSockets-prebuilt/GameNetworkingSockets.dll new file mode 100644 index 000000000..c9f6cde1a Binary files /dev/null and b/dep/valve.sockets/GameNetworkingSockets-prebuilt/GameNetworkingSockets.dll differ diff --git a/dep/valve.sockets/GameNetworkingSockets-prebuilt/abseil_dll.dll b/dep/valve.sockets/GameNetworkingSockets-prebuilt/abseil_dll.dll new file mode 100644 index 000000000..397c83127 Binary files /dev/null and b/dep/valve.sockets/GameNetworkingSockets-prebuilt/abseil_dll.dll differ diff --git a/dep/valve.sockets/GameNetworkingSockets-prebuilt/libcrypto-1_1-x64.dll b/dep/valve.sockets/GameNetworkingSockets-prebuilt/libcrypto-1_1-x64.dll new file mode 100644 index 000000000..451e4424d Binary files /dev/null and b/dep/valve.sockets/GameNetworkingSockets-prebuilt/libcrypto-1_1-x64.dll differ diff --git a/dep/valve.sockets/GameNetworkingSockets-prebuilt/libprotobuf.dll b/dep/valve.sockets/GameNetworkingSockets-prebuilt/libprotobuf.dll new file mode 100644 index 000000000..c32614b74 Binary files /dev/null and b/dep/valve.sockets/GameNetworkingSockets-prebuilt/libprotobuf.dll differ diff --git a/dep/valve.sockets/GameNetworkingSockets-prebuilt/libssl-1_1-x64.dll b/dep/valve.sockets/GameNetworkingSockets-prebuilt/libssl-1_1-x64.dll new file mode 100644 index 000000000..22cf6678b Binary files /dev/null and b/dep/valve.sockets/GameNetworkingSockets-prebuilt/libssl-1_1-x64.dll differ diff --git a/dep/valve.sockets/ValveSockets.cs b/dep/valve.sockets/ValveSockets.cs new file mode 100644 index 000000000..04a03966a --- /dev/null +++ b/dep/valve.sockets/ValveSockets.cs @@ -0,0 +1,1180 @@ +/* + * Managed C# wrapper for GameNetworkingSockets library by Valve Software + * Copyright (c) 2018 Stanislav Denisov + * + * 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. + */ + +using MonoMod.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace Valve.Sockets +{ + using ListenSocket = UInt32; + using Connection = UInt32; + using PollGroup = UInt32; + using Microseconds = Int64; + + [Flags] + public enum SendFlags + { + Unreliable = 0, + NoNagle = 1 << 0, + NoDelay = 1 << 2, + Reliable = 1 << 3 + } + + public enum IdentityType + { + Invalid = 0, + SteamID = 16, + IPAddress = 1, + GenericString = 2, + GenericBytes = 3 + } + + public enum ConnectionState + { + None = 0, + Connecting = 1, + FindingRoute = 2, + Connected = 3, + ClosedByPeer = 4, + ProblemDetectedLocally = 5 + } + + public enum ConfigurationScope + { + Global = 1, + SocketsInterface = 2, + ListenSocket = 3, + Connection = 4 + } + + public enum ConfigurationDataType + { + Int32 = 1, + Int64 = 2, + Float = 3, + String = 4, + FunctionPtr = 5 + } + + public enum ConfigurationValue + { + Invalid = 0, + FakePacketLossSend = 2, + FakePacketLossRecv = 3, + FakePacketLagSend = 4, + FakePacketLagRecv = 5, + FakePacketReorderSend = 6, + FakePacketReorderRecv = 7, + FakePacketReorderTime = 8, + FakePacketDupSend = 26, + FakePacketDupRecv = 27, + FakePacketDupTimeMax = 28, + TimeoutInitial = 24, + TimeoutConnected = 25, + SendBufferSize = 9, + SendRateMin = 10, + SendRateMax = 11, + NagleTime = 12, + IPAllowWithoutAuth = 23, + MTUPacketSize = 32, + MTUDataSize = 33, + Unencrypted = 34, + EnumerateDevVars = 35, + SymmetricConnect = 37, + LocalVirtualPort = 38, + ConnectionStatusChanged = 201, + AuthStatusChanged = 202, + RelayNetworkStatusChanged = 203, + MessagesSessionRequest = 204, + MessagesSessionFailed = 205, + P2PSTUNServerList = 103, + P2PTransportICEEnable = 104, + P2PTransportICEPenalty = 105, + P2PTransportSDRPenalty = 106, + SDRClientConsecutitivePingTimeoutsFailInitial = 19, + SDRClientConsecutitivePingTimeoutsFail = 20, + SDRClientMinPingsBeforePingAccurate = 21, + SDRClientSingleSocket = 22, + SDRClientForceRelayCluster = 29, + SDRClientDebugTicketAddress = 30, + SDRClientForceProxyAddr = 31, + SDRClientFakeClusterPing = 36, + LogLevelAckRTT = 13, + LogLevelPacketDecode = 14, + LogLevelMessage = 15, + LogLevelPacketGaps = 16, + LogLevelP2PRendezvous = 17, + LogLevelSDRRelayPings = 18 + } + + public enum ConfigurationValueResult + { + BadValue = -1, + BadScopeObject = -2, + BufferTooSmall = -3, + OK = 1, + OKInherited = 2 + } + + public enum DebugType + { + None = 0, + Bug = 1, + Error = 2, + Important = 3, + Warning = 4, + Message = 5, + Verbose = 6, + Debug = 7, + Everything = 8 + } + + public enum Result + { + OK = 1, + Fail = 2, + NoConnection = 3, + InvalidPassword = 5, + LoggedInElsewhere = 6, + InvalidProtocolVer = 7, + InvalidParam = 8, + FileNotFound = 9, + Busy = 10, + InvalidState = 11, + InvalidName = 12, + InvalidEmail = 13, + DuplicateName = 14, + AccessDenied = 15, + Timeout = 16, + Banned = 17, + AccountNotFound = 18, + InvalidSteamID = 19, + ServiceUnavailable = 20, + NotLoggedOn = 21, + Pending = 22, + EncryptionFailure = 23, + InsufficientPrivilege = 24, + LimitExceeded = 25, + Revoked = 26, + Expired = 27, + AlreadyRedeemed = 28, + DuplicateRequest = 29, + AlreadyOwned = 30, + IPNotFound = 31, + PersistFailed = 32, + LockingFailed = 33, + LogonSessionReplaced = 34, + ConnectFailed = 35, + HandshakeFailed = 36, + IOFailure = 37, + RemoteDisconnect = 38, + ShoppingCartNotFound = 39, + Blocked = 40, + Ignored = 41, + NoMatch = 42, + AccountDisabled = 43, + ServiceReadOnly = 44, + AccountNotFeatured = 45, + AdministratorOK = 46, + ContentVersion = 47, + TryAnotherCM = 48, + PasswordRequiredToKickSession = 49, + AlreadyLoggedInElsewhere = 50, + Suspended = 51, + Cancelled = 52, + DataCorruption = 53, + DiskFull = 54, + RemoteCallFailed = 55, + PasswordUnset = 56, + ExternalAccountUnlinked = 57, + PSNTicketInvalid = 58, + ExternalAccountAlreadyLinked = 59, + RemoteFileConflict = 60, + IllegalPassword = 61, + SameAsPreviousValue = 62, + AccountLogonDenied = 63, + CannotUseOldPassword = 64, + InvalidLoginAuthCode = 65, + AccountLogonDeniedNoMail = 66, + HardwareNotCapableOfIPT = 67, + IPTInitError = 68, + ParentalControlRestricted = 69, + FacebookQueryError = 70, + ExpiredLoginAuthCode = 71, + IPLoginRestrictionFailed = 72, + AccountLockedDown = 73, + AccountLogonDeniedVerifiedEmailRequired = 74, + NoMatchingURL = 75, + BadResponse = 76, + RequirePasswordReEntry = 77, + ValueOutOfRange = 78, + UnexpectedError = 79, + Disabled = 80, + InvalidCEGSubmission = 81, + RestrictedDevice = 82, + RegionLocked = 83, + RateLimitExceeded = 84, + AccountLoginDeniedNeedTwoFactor = 85, + ItemDeleted = 86, + AccountLoginDeniedThrottle = 87, + TwoFactorCodeMismatch = 88, + TwoFactorActivationCodeMismatch = 89, + AccountAssociatedToMultiplePartners = 90, + NotModified = 91, + NoMobileDevice = 92, + TimeNotSynced = 93, + SmsCodeFailed = 94, + AccountLimitExceeded = 95, + AccountActivityLimitExceeded = 96, + PhoneActivityLimitExceeded = 97, + RefundToWallet = 98, + EmailSendFailure = 99, + NotSettled = 100, + NeedCaptcha = 101, + GSLTDenied = 102, + GSOwnerDenied = 103, + InvalidItemType = 104, + IPBanned = 105, + GSLTExpired = 106, + InsufficientFunds = 107, + TooManyPending = 108, + NoSiteLicensesFound = 109, + WGNetworkSendExceeded = 110 + } + + [StructLayout(LayoutKind.Sequential)] + public struct Address : IEquatable
+ { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] ip; + public ushort port; + + public bool IsLocalHost + { + get + { + return Native.SteamAPI_SteamNetworkingIPAddr_IsLocalHost(ref this); + } + } + + public string GetIP() + { + return ip.ParseIP(); + } + + public void SetLocalHost(ushort port) + { + Native.SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost(ref this, port); + } + + public void SetAddress(string ip, ushort port) + { + if (!ip.Contains(":")) + Native.SteamAPI_SteamNetworkingIPAddr_SetIPv4(ref this, ip.ParseIPv4(), port); + else + Native.SteamAPI_SteamNetworkingIPAddr_SetIPv6(ref this, ip.ParseIPv6(), port); + } + + public bool Equals(Address other) + { + return Native.SteamAPI_SteamNetworkingIPAddr_IsEqualTo(ref this, ref other); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct Configuration + { + public ConfigurationValue value; + public ConfigurationDataType dataType; + public ConfigurationData data; + + [StructLayout(LayoutKind.Explicit)] + public struct ConfigurationData + { + [FieldOffset(0)] + public int Int32; + [FieldOffset(0)] + public long Int64; + [FieldOffset(0)] + public float Float; + [FieldOffset(0)] + public IntPtr String; + [FieldOffset(0)] + public IntPtr FunctionPtr; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct StatusInfo + { + private const int callback = Library.socketsCallbacks + 1; + public Connection connection; + public ConnectionInfo connectionInfo; + private ConnectionState oldState; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ConnectionInfo + { + public NetworkingIdentity identity; + public long userData; + public ListenSocket listenSocket; + public Address address; + private ushort pad; + private uint popRemote; + private uint popRelay; + public ConnectionState state; + public int endReason; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string endDebug; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string connectionDescription; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + private uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ConnectionRealTimeStatus + { + public ConnectionState state; + public int ping; + public float connectionQualityLocal; + public float connectionQualityRemote; + public float outPacketsPerSecond; + public float outBytesPerSecond; + public float inPacketsPerSecond; + public float inBytesPerSecond; + public int sendRateBytesPerSecond; + public int pendingUnreliable; + public int pendingReliable; + public int sentUnackedReliable; + public Microseconds queueTime; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + private uint[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ConnectionRealTimeLaneStatus + { + public int pendingUnreliable; + public int pendingReliable; + public int sentUnackedReliable; + public int _reservePad1; + public Microseconds queueTime; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + private uint[] reserved; + } + + [StructLayout(LayoutKind.Explicit, Size = 136)] + public struct NetworkingIdentity + { + [FieldOffset(0)] + public IdentityType type; + + public bool IsInvalid + { + get + { + return Native.SteamAPI_SteamNetworkingIdentity_IsInvalid(ref this); + } + } + + public ulong GetSteamID() + { + return Native.SteamAPI_SteamNetworkingIdentity_GetSteamID64(ref this); + } + + public void SetSteamID(ulong steamID) + { + Native.SteamAPI_SteamNetworkingIdentity_SetSteamID64(ref this, steamID); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct NetworkingMessage + { + public IntPtr data; + public int length; + public Connection connection; + public NetworkingIdentity identity; + public long connectionUserData; + public Microseconds timeReceived; + public long messageNumber; + internal IntPtr freeData; + internal IntPtr release; + public int channel; + public int flags; + public long userData; + + public void CopyTo(byte[] destination) + { + if (destination == null) + throw new ArgumentNullException("destination"); + + Marshal.Copy(data, destination, 0, length); + } + +#if !VALVESOCKETS_SPAN + public void Destroy() + { + if (release == IntPtr.Zero) + throw new InvalidOperationException("Message not created"); + + Native.SteamAPI_SteamNetworkingMessage_t_Release(release); + } +#endif + } + + public delegate void StatusCallback(ref StatusInfo info); + public delegate void DebugCallback(DebugType type, string message); + +#if VALVESOCKETS_SPAN + public delegate void MessageCallback(in NetworkingMessage message); +#endif + + internal static class ArrayPool + { + [ThreadStatic] + private static IntPtr[] pointerBuffer; + + public static IntPtr[] GetPointerBuffer() + { + if (pointerBuffer == null) + pointerBuffer = new IntPtr[Library.maxMessagesPerBatch]; + + return pointerBuffer; + } + } + + public class NetworkingSockets + { + private IntPtr nativeSockets; + + public NetworkingSockets() + { + nativeSockets = Native.SteamAPI_SteamNetworkingSockets_v009(); + + if (nativeSockets == IntPtr.Zero) + throw new InvalidOperationException("Networking sockets not created"); + } + + public ListenSocket CreateListenSocket(ref Address address) + { + return Native.SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(nativeSockets, ref address, 0, IntPtr.Zero); + } + + public ListenSocket CreateListenSocket(ref Address address, Configuration[] configurations) + { + if (configurations == null) + throw new ArgumentNullException("configurations"); + + return Native.SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP2(nativeSockets, ref address, configurations.Length, configurations); + } + + public Connection Connect(ref Address address) + { + return Native.SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(nativeSockets, ref address, 0, IntPtr.Zero); + } + + public Connection Connect(ref Address address, Configuration[] configurations) + { + if (configurations == null) + throw new ArgumentNullException("configurations"); + + return Native.SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress2(nativeSockets, ref address, configurations.Length, configurations); + } + + public Result AcceptConnection(Connection connection) + { + return Native.SteamAPI_ISteamNetworkingSockets_AcceptConnection(nativeSockets, connection); + } + + public bool CloseConnection(Connection connection) + { + return CloseConnection(connection, 0, String.Empty, false); + } + + public bool CloseConnection(Connection connection, int reason, string debug, bool enableLinger) + { + if (debug.Length > Library.maxCloseMessageLength) + throw new ArgumentOutOfRangeException("debug"); + + return Native.SteamAPI_ISteamNetworkingSockets_CloseConnection(nativeSockets, connection, reason, debug, enableLinger); + } + + public bool CloseListenSocket(ListenSocket socket) + { + return Native.SteamAPI_ISteamNetworkingSockets_CloseListenSocket(nativeSockets, socket); + } + + public bool SetConnectionUserData(Connection peer, long userData) + { + return Native.SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(nativeSockets, peer, userData); + } + + public long GetConnectionUserData(Connection peer) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(nativeSockets, peer); + } + + public void SetConnectionName(Connection peer, string name) + { + Native.SteamAPI_ISteamNetworkingSockets_SetConnectionName(nativeSockets, peer, name); + } + + public bool GetConnectionName(Connection peer, StringBuilder name, int maxLength) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetConnectionName(nativeSockets, peer, name, maxLength); + } + + public Result SendMessageToConnection(Connection connection, IntPtr data, uint length) + { + return SendMessageToConnection(connection, data, length, SendFlags.Unreliable); + } + + public Result SendMessageToConnection(Connection connection, IntPtr data, uint length, SendFlags flags) + { + return Native.SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(nativeSockets, connection, data, length, flags, IntPtr.Zero); + } + + public Result SendMessageToConnection(Connection connection, IntPtr data, int length, SendFlags flags) + { + return SendMessageToConnection(connection, data, (uint)length, flags); + } + + public Result SendMessageToConnection(Connection connection, byte[] data) + { + if (data == null) + throw new ArgumentNullException("data"); + + return SendMessageToConnection(connection, data, data.Length, SendFlags.Unreliable); + } + + public Result SendMessageToConnection(Connection connection, byte[] data, SendFlags flags) + { + if (data == null) + throw new ArgumentNullException("data"); + + return SendMessageToConnection(connection, data, data.Length, flags); + } + + public Result SendMessageToConnection(Connection connection, byte[] data, int length, SendFlags flags) + { + if (data == null) + throw new ArgumentNullException("data"); + + return Native.SteamAPI_ISteamNetworkingSockets_SendMessageToConnection2(nativeSockets, connection, data, (uint)length, flags, IntPtr.Zero); + } + + public Result FlushMessagesOnConnection(Connection connection) + { + return Native.SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(nativeSockets, connection); + } + + public bool GetConnectionInfo(Connection connection, ref ConnectionInfo info) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(nativeSockets, connection, ref info); + } + + public bool GetConnectionRealTimeStatus(Connection connection, ref ConnectionRealTimeStatus realTimeStatus, int nLanes, ref ConnectionRealTimeLaneStatus pLanes) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(nativeSockets, connection, ref realTimeStatus, nLanes, ref pLanes); + } + + public int GetDetailedConnectionStatus(Connection connection, StringBuilder status, int statusLength) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus(nativeSockets, connection, status, statusLength); + } + + public bool GetListenSocketAddress(ListenSocket socket, ref Address address) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress(nativeSockets, socket, ref address); + } + + public bool CreateSocketPair(Connection connectionLeft, Connection connectionRight, bool useNetworkLoopback, ref NetworkingIdentity identityLeft, ref NetworkingIdentity identityRight) + { + return Native.SteamAPI_ISteamNetworkingSockets_CreateSocketPair(nativeSockets, connectionLeft, connectionRight, useNetworkLoopback, ref identityLeft, ref identityRight); + } + + public bool GetIdentity(ref NetworkingIdentity identity) + { + return Native.SteamAPI_ISteamNetworkingSockets_GetIdentity(nativeSockets, ref identity); + } + + public PollGroup CreatePollGroup() + { + return Native.SteamAPI_ISteamNetworkingSockets_CreatePollGroup(nativeSockets); + } + + public bool DestroyPollGroup(PollGroup pollGroup) + { + return Native.SteamAPI_ISteamNetworkingSockets_DestroyPollGroup(nativeSockets, pollGroup); + } + + public bool SetConnectionPollGroup(PollGroup pollGroup, Connection connection) + { + return Native.SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(nativeSockets, connection, pollGroup); + } + + public void RunCallbacks() + { + Native.SteamAPI_ISteamNetworkingSockets_RunCallbacks(nativeSockets); + } + + public void Poll(int msMaxWaitTime) + { + Native.SteamNetworkingSockets_Poll(msMaxWaitTime); + } + + public void SetManualPollMode(bool bFlag) + { + Native.SteamNetworkingSockets_SetManualPollMode(bFlag); + } + +#if VALVESOCKETS_SPAN + [MethodImpl(256)] + public void ReceiveMessagesOnConnection(Connection connection, MessageCallback callback, int maxMessages) { + if (maxMessages > Library.maxMessagesPerBatch) + throw new ArgumentOutOfRangeException("maxMessages"); + + IntPtr[] nativeMessages = ArrayPool.GetPointerBuffer(); + int messagesCount = Native.SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(nativeSockets, connection, nativeMessages, maxMessages); + + for (int i = 0; i < messagesCount; i++) { + Span message; + + unsafe { + message = new Span((void*)nativeMessages[i], 1); + } + + callback(in message[0]); + + Native.SteamAPI_SteamNetworkingMessage_t_Release(nativeMessages[i]); + } + } + + [MethodImpl(256)] + public void ReceiveMessagesOnPollGroup(PollGroup pollGroup, MessageCallback callback, int maxMessages) { + if (maxMessages > Library.maxMessagesPerBatch) + throw new ArgumentOutOfRangeException("maxMessages"); + + IntPtr[] nativeMessages = ArrayPool.GetPointerBuffer(); + int messagesCount = Native.SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(nativeSockets, pollGroup, nativeMessages, maxMessages); + + for (int i = 0; i < messagesCount; i++) { + Span message; + + unsafe { + message = new Span((void*)nativeMessages[i], 1); + } + + callback(in message[0]); + + Native.SteamAPI_SteamNetworkingMessage_t_Release(nativeMessages[i]); + } + } +#else + [MethodImpl(256)] + public int ReceiveMessagesOnConnection(Connection connection, NetworkingMessage[] messages, int maxMessages) + { + if (messages == null) + throw new ArgumentNullException("messages"); + + if (maxMessages > Library.maxMessagesPerBatch) + throw new ArgumentOutOfRangeException("maxMessages"); + + IntPtr[] nativeMessages = ArrayPool.GetPointerBuffer(); + int messagesCount = Native.SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(nativeSockets, connection, nativeMessages, maxMessages); + + for (int i = 0; i < messagesCount; i++) + { + messages[i] = (NetworkingMessage)Marshal.PtrToStructure(nativeMessages[i], typeof(NetworkingMessage)); + messages[i].release = nativeMessages[i]; + } + + return messagesCount; + } + + [MethodImpl(256)] + public int ReceiveMessagesOnPollGroup(PollGroup pollGroup, NetworkingMessage[] messages, int maxMessages) + { + if (messages == null) + throw new ArgumentNullException("messages"); + + if (maxMessages > Library.maxMessagesPerBatch) + throw new ArgumentOutOfRangeException("maxMessages"); + + IntPtr[] nativeMessages = ArrayPool.GetPointerBuffer(); + int messagesCount = Native.SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(nativeSockets, pollGroup, nativeMessages, maxMessages); + + for (int i = 0; i < messagesCount; i++) + { + messages[i] = (NetworkingMessage)Marshal.PtrToStructure(nativeMessages[i], typeof(NetworkingMessage)); + messages[i].release = nativeMessages[i]; + } + + return messagesCount; + } +#endif + } + + public class NetworkingUtils : IDisposable + { + private IntPtr nativeUtils; + + public NetworkingUtils() + { + nativeUtils = Native.SteamAPI_SteamNetworkingUtils_v003(); + + if (nativeUtils == IntPtr.Zero) + throw new InvalidOperationException("Networking utils not created"); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (nativeUtils != IntPtr.Zero) + { + Native.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged(nativeUtils, IntPtr.Zero); + Native.SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(nativeUtils, DebugType.None, IntPtr.Zero); + nativeUtils = IntPtr.Zero; + } + } + + ~NetworkingUtils() + { + Dispose(false); + } + + public Microseconds Time + { + get + { + return Native.SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp(nativeUtils); + } + } + + public bool SetStatusCallback(StatusCallback callback) + { + return Native.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged2(nativeUtils, callback); + } + + public void SetDebugCallback(DebugType detailLevel, DebugCallback callback) + { + Native.SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction2(nativeUtils, detailLevel, callback); + } + + public bool SetConfigurationValue(ConfigurationValue configurationValue, ConfigurationScope configurationScope, IntPtr scopeObject, ConfigurationDataType dataType, IntPtr value) + { + return Native.SteamAPI_ISteamNetworkingUtils_SetConfigValue(nativeUtils, configurationValue, configurationScope, scopeObject, dataType, value); + } + + public bool SetConfigurationValue(Configuration configuration, ConfigurationScope configurationScope, IntPtr scopeObject) + { + return Native.SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct(nativeUtils, configuration, configurationScope, scopeObject); + } + + public ConfigurationValueResult GetConfigurationValue(ConfigurationValue configurationValue, ConfigurationScope configurationScope, IntPtr scopeObject, ref ConfigurationDataType dataType, ref IntPtr result, ref IntPtr resultLength) + { + return Native.SteamAPI_ISteamNetworkingUtils_GetConfigValue(nativeUtils, configurationValue, configurationScope, scopeObject, ref dataType, ref result, ref resultLength); + } + } + + public static class Extensions + { + public static uint ParseIPv4(this string ip) + { + IPAddress address = default(IPAddress); + + if (IPAddress.TryParse(ip, out address)) + { + if (address.AddressFamily != AddressFamily.InterNetwork) + throw new Exception("Incorrect format of an IPv4 address"); + } + + byte[] bytes = address.GetAddressBytes(); + + Array.Reverse(bytes); + + return BitConverter.ToUInt32(bytes, 0); + } + + public static byte[] ParseIPv6(this string ip) + { + IPAddress address = default(IPAddress); + + if (IPAddress.TryParse(ip, out address)) + { + if (address.AddressFamily != AddressFamily.InterNetworkV6) + throw new Exception("Incorrect format of an IPv6 address"); + } + + return address.GetAddressBytes(); + } + + public static string ParseIP(this byte[] ip) + { + IPAddress address = new IPAddress(ip); + string converted = address.ToString(); + + if (converted.Length > 7 && converted.Remove(7) == "::ffff:") + { + Address ipv4 = default(Address); + + ipv4.ip = ip; + + byte[] bytes = BitConverter.GetBytes(Native.SteamAPI_SteamNetworkingIPAddr_GetIPv4(ref ipv4)); + + Array.Reverse(bytes); + + address = new IPAddress(bytes); + } + + return address.ToString(); + } + } + + public static class Library + { + public const int maxCloseMessageLength = 128; + public const int maxErrorMessageLength = 1024; + public const int maxMessagesPerBatch = 256; + public const int maxMessageSize = 512 * 1024; + public const int socketsCallbacks = 1220; + private static bool initialized = false; + + public static bool Initialize() + { + if (initialized == true) + return false; + + return Initialize(null); + } + + public static bool Initialize(StringBuilder errorMessage) + { + + if (initialized == true) + return false; + + if (errorMessage != null && errorMessage.Capacity != maxErrorMessageLength) + throw new ArgumentOutOfRangeException("Capacity of the error message must be equal to " + maxErrorMessageLength); + + initialized = Native.GameNetworkingSockets_Init(IntPtr.Zero, errorMessage); + return initialized; + } + + public static bool Initialize(ref NetworkingIdentity identity, StringBuilder errorMessage) + { + if (initialized == true) + return false; + + if (errorMessage != null && errorMessage.Capacity != maxErrorMessageLength) + throw new ArgumentOutOfRangeException("Capacity of the error message must be equal to " + maxErrorMessageLength); + + if (Object.Equals(identity, null)) + throw new ArgumentNullException("identity"); + + initialized = Native.GameNetworkingSockets_Init2(ref identity, errorMessage); + return initialized; + } + + public static void Deinitialize() + { + if (initialized) + { + initialized = false; + Native.GameNetworkingSockets_Kill(); + } + } + } + + [SuppressUnmanagedCodeSecurity] + internal static class Native + { + private const string nativeLibrary = "GameNetworkingSockets.dll"; + + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr LoadLibrary(string fileName); + + private static void LoadDependencies() + { + var assemblyPath = Path.Combine(Path.GetDirectoryName(typeof(Native).Assembly.Location), "GameNetworkingSockets-prebuilt"); + + string[] nativeDlls = + { + "libcrypto-1_1-x64.dll", "libprotobuf.dll", "libssl-1_1-x64.dll", "abseil_dll.dll" + }; + + foreach (string dll in nativeDlls) + { + string nativeDllPath = Path.Combine(assemblyPath, dll); + if (LoadLibrary(nativeDllPath) == IntPtr.Zero) + throw new IOException($"Failed to load {nativeDllPath}, verify that the file exists and is not corrupted."); + } + } + + static Native() + { + string assemblyPath = System.Reflection.Assembly.GetAssembly(typeof(Native)).Location; + string root = string.Empty; + + LoadDependencies(); + + if (!string.IsNullOrEmpty(assemblyPath)) + { + root = Path.GetDirectoryName(assemblyPath); + } + var map = new Dictionary> + { + { + nativeLibrary, + new List{ + nativeLibrary, + Path.Combine(root, nativeLibrary), + Path.Combine(root, "GameNetworkingSockets-prebuilt", nativeLibrary) + } + }, + }; + typeof(Native).ResolveDynDllImports(map); + } + + [DynDllImport(nativeLibrary)] + internal static GameNetworkingSockets_Init_Delegate GameNetworkingSockets_Init; + internal delegate bool GameNetworkingSockets_Init_Delegate(IntPtr identity, StringBuilder errorMessage); + + [DynDllImport(libraryName: nativeLibrary, EntryPoints = new string[] { "GameNetworkingSockets_Init" })] + internal static GameNetworkingSockets_Init_Delegate2 GameNetworkingSockets_Init2; + internal delegate bool GameNetworkingSockets_Init_Delegate2(ref NetworkingIdentity identity, StringBuilder errorMessage); + + [DynDllImport(nativeLibrary)] + internal static GameNetworkingSockets_Kill_Delegate GameNetworkingSockets_Kill; + internal delegate void GameNetworkingSockets_Kill_Delegate(); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingSockets_v009_Delegate SteamAPI_SteamNetworkingSockets_v009; + internal delegate IntPtr SteamAPI_SteamNetworkingSockets_v009_Delegate(); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingUtils_v003_Delegate SteamAPI_SteamNetworkingUtils_v003; + internal delegate IntPtr SteamAPI_SteamNetworkingUtils_v003_Delegate(); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP_Delegate SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP; + internal delegate ListenSocket SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP_Delegate(IntPtr sockets, ref Address address, int configurationsCount, IntPtr configurations); + + [DynDllImport(libraryName: nativeLibrary, EntryPoints = new string[]{"SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP"} )] + internal static SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP_Delegate2 SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP2; + internal delegate ListenSocket SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP_Delegate2(IntPtr sockets, ref Address address, int configurationsCount, Configuration[] configurations); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress_Delegate SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress; + internal delegate Connection SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress_Delegate(IntPtr sockets, ref Address address, int configurationsCount, IntPtr configurations); + + [DynDllImport(libraryName: nativeLibrary, EntryPoints = new string[] { "SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress" })] + internal static SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress_Delegate2 SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress2; + internal delegate Connection SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress_Delegate2(IntPtr sockets, ref Address address, int configurationsCount, Configuration[] configurations); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_AcceptConnection_Delegate SteamAPI_ISteamNetworkingSockets_AcceptConnection; + internal delegate Result SteamAPI_ISteamNetworkingSockets_AcceptConnection_Delegate(IntPtr sockets, Connection connection); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_CloseConnection_Delegate SteamAPI_ISteamNetworkingSockets_CloseConnection; + internal delegate bool SteamAPI_ISteamNetworkingSockets_CloseConnection_Delegate(IntPtr sockets, Connection peer, int reason, string debug, bool enableLinger); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_CloseListenSocket_Delegate SteamAPI_ISteamNetworkingSockets_CloseListenSocket; + internal delegate bool SteamAPI_ISteamNetworkingSockets_CloseListenSocket_Delegate(IntPtr sockets, ListenSocket socket); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_SetConnectionUserData_Delegate SteamAPI_ISteamNetworkingSockets_SetConnectionUserData; + internal delegate bool SteamAPI_ISteamNetworkingSockets_SetConnectionUserData_Delegate(IntPtr sockets, Connection peer, long userData); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetConnectionUserData_Delegate SteamAPI_ISteamNetworkingSockets_GetConnectionUserData; + internal delegate long SteamAPI_ISteamNetworkingSockets_GetConnectionUserData_Delegate(IntPtr sockets, Connection peer); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_SetConnectionName_Delegate SteamAPI_ISteamNetworkingSockets_SetConnectionName; + internal delegate void SteamAPI_ISteamNetworkingSockets_SetConnectionName_Delegate(IntPtr sockets, Connection peer, string name); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetConnectionName_Delegate SteamAPI_ISteamNetworkingSockets_GetConnectionName; + internal delegate bool SteamAPI_ISteamNetworkingSockets_GetConnectionName_Delegate(IntPtr sockets, Connection peer, StringBuilder name, int maxLength); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_SendMessageToConnection_Delegate SteamAPI_ISteamNetworkingSockets_SendMessageToConnection; + internal delegate Result SteamAPI_ISteamNetworkingSockets_SendMessageToConnection_Delegate(IntPtr sockets, Connection connection, IntPtr data, uint length, SendFlags flags, IntPtr outMessageNumber); + + [DynDllImport(libraryName: nativeLibrary, EntryPoints = new string[] { "SteamAPI_ISteamNetworkingSockets_SendMessageToConnection" })] + internal static SteamAPI_ISteamNetworkingSockets_SendMessageToConnection_Delegate2 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection2; + internal delegate Result SteamAPI_ISteamNetworkingSockets_SendMessageToConnection_Delegate2(IntPtr sockets, Connection connection, byte[] data, uint length, SendFlags flags, IntPtr outMessageNumber); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection_Delegate SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection; + internal delegate Result SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection_Delegate(IntPtr sockets, Connection connection); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection_Delegate SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection; + internal delegate int SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection_Delegate(IntPtr sockets, Connection connection, IntPtr[] messages, int maxMessages); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetConnectionInfo_Delegate SteamAPI_ISteamNetworkingSockets_GetConnectionInfo; + internal delegate bool SteamAPI_ISteamNetworkingSockets_GetConnectionInfo_Delegate(IntPtr sockets, Connection connection, ref ConnectionInfo info); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus_Delegate SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus; + internal delegate bool SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus_Delegate(IntPtr sockets, Connection connection, ref ConnectionRealTimeStatus realTimeStatus, int nLanes, ref ConnectionRealTimeLaneStatus pLanes); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus_Delegate SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus; + internal delegate int SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus_Delegate(IntPtr sockets, Connection connection, StringBuilder status, int statusLength); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress_Delegate SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress; + internal delegate bool SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress_Delegate(IntPtr sockets, ListenSocket socket, ref Address address); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_RunCallbacks_Delegate SteamAPI_ISteamNetworkingSockets_RunCallbacks; + internal delegate void SteamAPI_ISteamNetworkingSockets_RunCallbacks_Delegate(IntPtr sockets); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_CreateSocketPair_Delegate SteamAPI_ISteamNetworkingSockets_CreateSocketPair; + internal delegate bool SteamAPI_ISteamNetworkingSockets_CreateSocketPair_Delegate(IntPtr sockets, Connection connectionLeft, Connection connectionRight, bool useNetworkLoopback, ref NetworkingIdentity identityLeft, ref NetworkingIdentity identityRight); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_GetIdentity_Delegate SteamAPI_ISteamNetworkingSockets_GetIdentity; + internal delegate bool SteamAPI_ISteamNetworkingSockets_GetIdentity_Delegate(IntPtr sockets, ref NetworkingIdentity identity); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_CreatePollGroup_Delegate SteamAPI_ISteamNetworkingSockets_CreatePollGroup; + internal delegate PollGroup SteamAPI_ISteamNetworkingSockets_CreatePollGroup_Delegate(IntPtr sockets); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_DestroyPollGroup_Delegate SteamAPI_ISteamNetworkingSockets_DestroyPollGroup; + internal delegate bool SteamAPI_ISteamNetworkingSockets_DestroyPollGroup_Delegate(IntPtr sockets, PollGroup pollGroup); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup_Delegate SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup; + internal delegate bool SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup_Delegate(IntPtr sockets, Connection connection, PollGroup pollGroup); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup_Delegate SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup; + internal delegate int SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup_Delegate(IntPtr sockets, PollGroup pollGroup, IntPtr[] messages, int maxMessages); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIPAddr_SetIPv6_Delegate SteamAPI_SteamNetworkingIPAddr_SetIPv6; + internal delegate void SteamAPI_SteamNetworkingIPAddr_SetIPv6_Delegate(ref Address address, byte[] ip, ushort port); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIPAddr_SetIPv4_Delegate SteamAPI_SteamNetworkingIPAddr_SetIPv4; + internal delegate void SteamAPI_SteamNetworkingIPAddr_SetIPv4_Delegate(ref Address address, uint ip, ushort port); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIPAddr_GetIPv4_Delegate SteamAPI_SteamNetworkingIPAddr_GetIPv4; + internal delegate uint SteamAPI_SteamNetworkingIPAddr_GetIPv4_Delegate(ref Address address); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost_Delegate SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost; + internal delegate void SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost_Delegate(ref Address address, ushort port); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIPAddr_IsLocalHost_Delegate SteamAPI_SteamNetworkingIPAddr_IsLocalHost; + internal delegate bool SteamAPI_SteamNetworkingIPAddr_IsLocalHost_Delegate(ref Address address); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIPAddr_IsEqualTo_Delegate SteamAPI_SteamNetworkingIPAddr_IsEqualTo; + internal delegate bool SteamAPI_SteamNetworkingIPAddr_IsEqualTo_Delegate(ref Address address, ref Address other); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIdentity_IsInvalid_Delegate SteamAPI_SteamNetworkingIdentity_IsInvalid; + internal delegate bool SteamAPI_SteamNetworkingIdentity_IsInvalid_Delegate(ref NetworkingIdentity identity); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIdentity_SetSteamID64_Delegate SteamAPI_SteamNetworkingIdentity_SetSteamID64; + internal delegate void SteamAPI_SteamNetworkingIdentity_SetSteamID64_Delegate(ref NetworkingIdentity identity, ulong steamID); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingIdentity_GetSteamID64_Delegate SteamAPI_SteamNetworkingIdentity_GetSteamID64; + internal delegate ulong SteamAPI_SteamNetworkingIdentity_GetSteamID64_Delegate(ref NetworkingIdentity identity); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp_Delegate SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp; + internal delegate Microseconds SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp_Delegate(IntPtr utils); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged_Delegate SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged; + internal delegate bool SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged_Delegate(IntPtr utils, IntPtr callback); + + [DynDllImport(libraryName: nativeLibrary, EntryPoints = new string[] { "SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged" })] + internal static SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged_Delegate2 SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged2; // TODO: fix + internal delegate bool SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged_Delegate2(IntPtr utils, StatusCallback callback); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction_Delegate SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction; + internal delegate void SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction_Delegate(IntPtr utils, DebugType detailLevel, IntPtr callback); + + [DynDllImport(libraryName: nativeLibrary, EntryPoints = new string[] { "SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction" })] + internal static SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction_Delegate2 SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction2; + internal delegate void SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction_Delegate2(IntPtr utils, DebugType detailLevel, DebugCallback callback); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingUtils_SetConfigValue_Delegate SteamAPI_ISteamNetworkingUtils_SetConfigValue; + internal delegate bool SteamAPI_ISteamNetworkingUtils_SetConfigValue_Delegate(IntPtr utils, ConfigurationValue configurationValue, ConfigurationScope configurationScope, IntPtr scopeObject, ConfigurationDataType dataType, IntPtr value); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct_Delegate SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct; + internal delegate bool SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct_Delegate(IntPtr utils, Configuration configuration, ConfigurationScope configurationScope, IntPtr scopeObject); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_ISteamNetworkingUtils_GetConfigValue_Delegate SteamAPI_ISteamNetworkingUtils_GetConfigValue; + internal delegate ConfigurationValueResult SteamAPI_ISteamNetworkingUtils_GetConfigValue_Delegate(IntPtr utils, ConfigurationValue configurationValue, ConfigurationScope configurationScope, IntPtr scopeObject, ref ConfigurationDataType dataType, ref IntPtr result, ref IntPtr resultLength); + + [DynDllImport(nativeLibrary)] + internal static SteamAPI_SteamNetworkingMessage_t_Release_Delegate SteamAPI_SteamNetworkingMessage_t_Release; + internal delegate void SteamAPI_SteamNetworkingMessage_t_Release_Delegate(IntPtr nativeMessage); + + [DynDllImport(nativeLibrary)] + internal static SteamNetworkingSockets_Poll_Delegate SteamNetworkingSockets_Poll; + internal delegate void SteamNetworkingSockets_Poll_Delegate(int msMaxWaitTime); + + [DynDllImport(nativeLibrary)] + internal static SteamNetworkingSockets_SetManualPollMode_Delegate SteamNetworkingSockets_SetManualPollMode; + internal delegate void SteamNetworkingSockets_SetManualPollMode_Delegate(bool bFlag); + } +} diff --git a/dep/valve.sockets/ValveSockets.csproj b/dep/valve.sockets/ValveSockets.csproj new file mode 100644 index 000000000..33556f390 --- /dev/null +++ b/dep/valve.sockets/ValveSockets.csproj @@ -0,0 +1,25 @@ + + + + net472 + false + VALVESOCKETS_SPAN + true + + + + none + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file