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;