diff --git a/Facepunch.Steamworks.Test/Client.Networking.cs b/Facepunch.Steamworks.Test/Client.Networking.cs new file mode 100644 index 0000000..948b0b2 --- /dev/null +++ b/Facepunch.Steamworks.Test/Client.Networking.cs @@ -0,0 +1,40 @@ +using System; +using System.Text; +using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Facepunch.Steamworks.Test +{ + public partial class Client + { + [TestMethod] + public void PeerToPeerSend() + { + using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) + { + var TestString = "This string will be transformed to bytes, sent over the Steam P2P network, then converted back to a string."; + var OutputReceived = false; + var data = Encoding.UTF8.GetBytes( TestString ); + + client.Networking.OnP2PData = ( steamid, ms, channel ) => + { + var str = Encoding.UTF8.GetString( ms.GetBuffer() ); + Assert.AreEqual( str, TestString ); + Assert.AreEqual( steamid, client.SteamId ); + OutputReceived = true; + }; + + client.Networking.SendP2PPacket( client.SteamId, data, data.Length ); + + while( true ) + { + Thread.Sleep( 10 ); + client.Update(); + + if ( OutputReceived ) + break; + } + } + } + } +} diff --git a/Facepunch.Steamworks.Test/Client.cs b/Facepunch.Steamworks.Test/Client.cs index 796f2ae..c50ea0e 100644 --- a/Facepunch.Steamworks.Test/Client.cs +++ b/Facepunch.Steamworks.Test/Client.cs @@ -6,7 +6,7 @@ namespace Facepunch.Steamworks.Test [TestClass] [DeploymentItem( "FacepunchSteamworksApi.dll" )] [DeploymentItem( "steam_appid.txt" )] - public class Client + public partial class Client { [TestMethod] public void Init() diff --git a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj index 53bbb4a..e42008d 100644 --- a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj +++ b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj @@ -87,6 +87,7 @@ + diff --git a/Facepunch.Steamworks/Client.Networking.cs b/Facepunch.Steamworks/Client.Networking.cs new file mode 100644 index 0000000..c103e3a --- /dev/null +++ b/Facepunch.Steamworks/Client.Networking.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Valve.Steamworks; + +namespace Facepunch.Steamworks +{ + public partial class Client : IDisposable + { + private Networking _net; + + public Networking Networking + { + get + { + if ( _net == null ) + _net = new Networking( this ); + + return _net; + } + } + } + + public class Networking + { + public Action OnP2PData; + + internal Client client; + + internal class Callback + { + internal delegate void P2PSessionRequest( P2PSessionRequest_t a ); + internal delegate void P2PSessionConnectFail( P2PSessionConnectFail_t a ); + } + + internal Networking( Client c ) + { + client = c; + + { + Callback.P2PSessionRequest cb = onP2PConnectionRequest; + client.InstallCallback( Valve.Steamworks.SteamAPI.k_iSteamNetworkingCallbacks + 2, cb ); + } + + { + Callback.P2PSessionConnectFail cb = onP2PConnectionFailed; + client.InstallCallback( Valve.Steamworks.SteamAPI.k_iSteamNetworkingCallbacks + 2, cb ); + } + } + + internal void Update() + { + for ( int i = 0; i < 32; i++ ) + { + // POOL ME + using ( var ms = new MemoryStream() ) + { + while( ReadP2PPacket( ms, i ) ) + { + // Nothing Here. + } + } + } + } + + private void onP2PConnectionRequest( P2PSessionRequest_t o ) + { + Console.WriteLine( "onP2PConnectionRequest " + o.m_steamIDRemote ); + } + + private void onP2PConnectionFailed( P2PSessionConnectFail_t o ) + { + Console.WriteLine( "onP2PConnectionFailed " + o.m_steamIDRemote ); + } + + public enum EP2PSend : int + { + /// + /// Basic UDP send. Packets can't be bigger than 1200 bytes (your typical MTU size). Can be lost, or arrive out of order (rare). + /// The sending API does have some knowledge of the underlying connection, so if there is no NAT-traversal accomplished or + /// there is a recognized adjustment happening on the connection, the packet will be batched until the connection is open again. + /// + + Unreliable = 0, + + /// + /// As above, but if the underlying p2p connection isn't yet established the packet will just be thrown away. Using this on the first + /// packet sent to a remote host almost guarantees the packet will be dropped. + /// This is only really useful for kinds of data that should never buffer up, i.e. voice payload packets + /// + UnreliableNoDelay = 1, + + //// + /// Reliable message send. Can send up to 1MB of data in a single message. + /// Does fragmentation/re-assembly of messages under the hood, as well as a sliding window for efficient sends of large chunks of data. + /// + Reliable = 2, + + /// + /// As above, but applies the Nagle algorithm to the send - sends will accumulate + /// until the current MTU size (typically ~1200 bytes, but can change) or ~200ms has passed (Nagle algorithm). + /// Useful if you want to send a set of smaller messages but have the coalesced into a single packet + /// Since the reliable stream is all ordered, you can do several small message sends with k_EP2PSendReliableWithBuffering and then + /// do a normal k_EP2PSendReliable to force all the buffered data to be sent. + /// + ReliableWithBuffering = 3, + + } + + public unsafe bool SendP2PPacket( ulong steamid, byte[] data, int length, EP2PSend eP2PSendType = EP2PSend.Reliable, int nChannel = 0 ) + { + fixed ( byte* p = data ) + { + return client._networking.SendP2PPacket( steamid, (IntPtr) p, (uint)length, (uint)eP2PSendType, nChannel ); + } + } + + private unsafe bool ReadP2PPacket( MemoryStream ms, int channel = 0 ) + { + uint DataAvailable = 0; + + if ( !client._networking.IsP2PPacketAvailable( ref DataAvailable, channel ) || DataAvailable == 0 ) + return false; + + if ( ms.Capacity < DataAvailable ) + ms.Capacity = (int) DataAvailable; + + ms.Position = 0; + ms.SetLength( DataAvailable ); + + fixed ( byte* p = ms.GetBuffer() ) + { + ulong steamid = 1; + if ( !client._networking.ReadP2PPacket( (IntPtr)p, (uint)DataAvailable, ref DataAvailable, ref steamid, channel ) || DataAvailable == 0 ) + return false; + + ms.SetLength( DataAvailable ); + + OnP2PData?.Invoke( steamid, ms, channel ); + return true; + } + } + } +} diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index 2cbfac0..122be17 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -15,6 +15,7 @@ public partial class Client : IDisposable internal Valve.Steamworks.ISteamFriends _friends; internal Valve.Steamworks.ISteamMatchmakingServers _servers; internal Valve.Steamworks.ISteamInventory _inventory; + internal Valve.Steamworks.ISteamNetworking _networking; /// /// Current running program's AppId @@ -62,6 +63,7 @@ public Client( uint appId ) _user = _client.GetISteamUser( _huser, _hpipe, "SteamUser019" ); _servers = _client.GetISteamMatchmakingServers( _huser, _hpipe, "SteamMatchMakingServers002" ); _inventory = _client.GetISteamInventory( _huser, _hpipe, "STEAMINVENTORY_INTERFACE_V001" ); + _networking = _client.GetISteamNetworking( _huser, _hpipe, "SteamNetworking005" ); AppId = appId; Username = _friends.GetPersonaName(); @@ -106,6 +108,7 @@ public void Update() Valve.Steamworks.SteamAPI.RunCallbacks(); Voice.Update(); Inventory.Update(); + Networking.Update(); } public bool Valid @@ -113,7 +116,7 @@ public bool Valid get { return _client != null; } } - internal Action InstallCallback( int type, Action action ) + internal Action InstallCallback( int type, Delegate action ) { var ptr = Marshal.GetFunctionPointerForDelegate( action ); Valve.Steamworks.SteamAPI.RegisterCallback( ptr, type ); diff --git a/Facepunch.Steamworks/Facepunch.Steamworks.csproj b/Facepunch.Steamworks/Facepunch.Steamworks.csproj index 510a2a1..b6e4825 100644 --- a/Facepunch.Steamworks/Facepunch.Steamworks.csproj +++ b/Facepunch.Steamworks/Facepunch.Steamworks.csproj @@ -113,6 +113,7 @@ + diff --git a/Facepunch.Steamworks/steam_api_interop.cs b/Facepunch.Steamworks/steam_api_interop.cs index bbd205b..98afd33 100644 --- a/Facepunch.Steamworks/steam_api_interop.cs +++ b/Facepunch.Steamworks/steam_api_interop.cs @@ -710,7 +710,7 @@ internal class NativeEntrypoints [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SteamAPI_ISteamNetworking_IsP2PPacketAvailable" )] internal static extern bool SteamAPI_ISteamNetworking_IsP2PPacketAvailable( IntPtr instancePtr, ref uint pcubMsgSize, int nChannel ); [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SteamAPI_ISteamNetworking_ReadP2PPacket" )] - internal static extern bool SteamAPI_ISteamNetworking_ReadP2PPacket( IntPtr instancePtr, IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref CSteamID psteamIDRemote, int nChannel ); + internal static extern bool SteamAPI_ISteamNetworking_ReadP2PPacket( IntPtr instancePtr, IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref ulong psteamIDRemote, int nChannel ); [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser" )] internal static extern bool SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser( IntPtr instancePtr, ulong steamIDRemote ); [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SteamAPI_ISteamNetworking_CloseP2PSessionWithUser" )] @@ -1982,7 +1982,7 @@ internal abstract class ISteamNetworking internal abstract IntPtr GetIntPtr(); internal abstract bool SendP2PPacket( ulong steamIDRemote, IntPtr pubData, uint cubData, uint eP2PSendType, int nChannel ); internal abstract bool IsP2PPacketAvailable( ref uint pcubMsgSize, int nChannel ); - internal abstract bool ReadP2PPacket( IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref CSteamID psteamIDRemote, int nChannel ); + internal abstract bool ReadP2PPacket( IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref ulong psteamIDRemote, int nChannel ); internal abstract bool AcceptP2PSessionWithUser( ulong steamIDRemote ); internal abstract bool CloseP2PSessionWithUser( ulong steamIDRemote ); internal abstract bool CloseP2PChannelWithUser( ulong steamIDRemote, int nChannel ); @@ -2475,7 +2475,7 @@ internal override ISteamNetworking GetISteamNetworking( uint hSteamUser, uint hS { CheckIfUsable(); IntPtr result = NativeEntrypoints.SteamAPI_ISteamClient_GetISteamNetworking(m_pSteamClient,hSteamUser,hSteamPipe,pchVersion); - return (ISteamNetworking)Marshal.PtrToStructure( result, typeof( ISteamNetworking ) ); + return new CSteamNetworking( result ); } internal override ISteamRemoteStorage GetISteamRemoteStorage( uint hSteamuser, uint hSteamPipe, string pchVersion ) { @@ -4730,7 +4730,7 @@ internal override bool IsP2PPacketAvailable( ref uint pcubMsgSize, int nChannel bool result = NativeEntrypoints.SteamAPI_ISteamNetworking_IsP2PPacketAvailable(m_pSteamNetworking,ref pcubMsgSize,nChannel); return result; } - internal override bool ReadP2PPacket( IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref CSteamID psteamIDRemote, int nChannel ) + internal override bool ReadP2PPacket( IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref ulong psteamIDRemote, int nChannel ) { CheckIfUsable(); pcubMsgSize = 0;