From c3a1bf0bcfe116cc1f3a2ff1c4b62810dcbfec48 Mon Sep 17 00:00:00 2001 From: Garry Newman Date: Mon, 3 Oct 2016 15:25:19 +0100 Subject: [PATCH] Server Auth callbacks --- Facepunch.Steamworks.Test/Server/Server.cs | 64 ++++++++-- Facepunch.Steamworks/Callbacks/User.cs | 37 ++++++ Facepunch.Steamworks/Config.cs | 2 +- .../Facepunch.Steamworks.csproj | 6 +- Facepunch.Steamworks/Interop/Callback.This.cs | 52 ++++++++ Facepunch.Steamworks/Interop/Callback.cs | 119 ++++++++++++++++++ Facepunch.Steamworks/Server.cs | 41 ++++-- Facepunch.Steamworks/Server/Auth.cs | 46 ++++++- 8 files changed, 346 insertions(+), 21 deletions(-) create mode 100644 Facepunch.Steamworks/Callbacks/User.cs create mode 100644 Facepunch.Steamworks/Interop/Callback.This.cs create mode 100644 Facepunch.Steamworks/Interop/Callback.cs diff --git a/Facepunch.Steamworks.Test/Server/Server.cs b/Facepunch.Steamworks.Test/Server/Server.cs index 88b568b..68948c5 100644 --- a/Facepunch.Steamworks.Test/Server/Server.cs +++ b/Facepunch.Steamworks.Test/Server/Server.cs @@ -25,39 +25,87 @@ public void AuthCallback() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { - Assert.IsTrue( client.Valid ); - - var ticket = client.Auth.GetAuthSessionTicket(); - var ticketBinary = ticket.Data; - - using ( var server = new Facepunch.Steamworks.Server( 252490, 30000, 30001, 30002, 30003, false, "VersionString" ) ) + using ( var server = new Facepunch.Steamworks.Server( 252490, 0, 30001, 30002, 30003, true, "VersionString" ) ) { + Assert.IsTrue( client.Valid ); + + var ticket = client.Auth.GetAuthSessionTicket(); + var ticketBinary = ticket.Data; + Assert.IsTrue( server.Valid ); + var auth = server.Auth; + + var Authed = false; + + server.Auth.OnAuthChange = ( steamid, ownerid, status ) => + { + Authed = status == ServerAuth.Status.OK; + + Assert.AreEqual( steamid, client.SteamId ); + + Console.WriteLine( "steamid: {0}", steamid ); + Console.WriteLine( "ownerid: {0}", ownerid ); + Console.WriteLine( "status: {0}", status ); + }; + for ( int i = 0; i < 16; i++ ) { System.Threading.Thread.Sleep( 10 ); + GC.Collect(); server.Update(); + GC.Collect(); client.Update(); + GC.Collect(); } + GC.Collect(); if ( !server.Auth.StartSession( ticketBinary, client.SteamId ) ) { Assert.Fail( "Start Session returned false" ); } + GC.Collect(); - for( int i = 0; i<16; i++ ) + // + // Server should receive a ServerAuth.Status.OK + // message via the OnAuthChange callback + // + + for ( int i = 0; i< 100; i++ ) { - System.Threading.Thread.Sleep( 10 ); + GC.Collect(); + System.Threading.Thread.Sleep( 100 ); + GC.Collect(); server.Update(); client.Update(); + + if ( Authed ) + break; } + Assert.IsTrue( Authed ); + // // Client cancels ticket // ticket.Cancel(); + // + // Server should receive a ticket cancelled message + // + + for ( int i = 0; i < 100; i++ ) + { + System.Threading.Thread.Sleep( 100 ); + server.Update(); + client.Update(); + + if ( !Authed ) + break; + } + + Assert.IsTrue( !Authed ); + } } } diff --git a/Facepunch.Steamworks/Callbacks/User.cs b/Facepunch.Steamworks/Callbacks/User.cs new file mode 100644 index 0000000..b5768e0 --- /dev/null +++ b/Facepunch.Steamworks/Callbacks/User.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Facepunch.Steamworks.Callbacks.User +{ + [StructLayout( LayoutKind.Sequential )] + internal class ValidateAuthTicketResponse + { + public enum Response : int + { + Okay = 0, // Steam has verified the user is online, the ticket is valid and ticket has not been reused. + UserNotConnectedToSteam = 1, // The user in question is not connected to steam + NoLicenseOrExpired = 2, // The license has expired. + VACBanned = 3, // The user is VAC banned for this game. + LoggedInElseWhere = 4, // The user account has logged in elsewhere and the session containing the game instance has been disconnected. + VACCheckTimedOut = 5, // VAC has been unable to perform anti-cheat checks on this user + AuthTicketCanceled = 6, // The ticket has been canceled by the issuer + AuthTicketInvalidAlreadyUsed = 7, // This ticket has already been used, it is not valid. + AuthTicketInvalid = 8, // This ticket is not from a user instance currently connected to steam. + PublisherIssuedBan = 9, // The user is banned for this game. The ban came via the web api and not VAC + }; + + public ulong SteamID; + public Response AuthResponse; + public ulong OwnerSteamID; + + public const int CallbackId = Index.User + 43; + }; + + internal static class Index + { + internal const int User = 100; + } +} diff --git a/Facepunch.Steamworks/Config.cs b/Facepunch.Steamworks/Config.cs index 7c5a1a9..22af3b6 100644 --- a/Facepunch.Steamworks/Config.cs +++ b/Facepunch.Steamworks/Config.cs @@ -16,6 +16,6 @@ public static class Config /// for releasing his shit open source under the MIT license so we can all learn and iterate. /// /// - public static bool UseThisCall { get; set; } + public static bool UseThisCall { get; set; } = true; } } diff --git a/Facepunch.Steamworks/Facepunch.Steamworks.csproj b/Facepunch.Steamworks/Facepunch.Steamworks.csproj index c9738ce..29aaec7 100644 --- a/Facepunch.Steamworks/Facepunch.Steamworks.csproj +++ b/Facepunch.Steamworks/Facepunch.Steamworks.csproj @@ -113,6 +113,7 @@ + @@ -128,13 +129,16 @@ + - + + + diff --git a/Facepunch.Steamworks/Interop/Callback.This.cs b/Facepunch.Steamworks/Interop/Callback.This.cs new file mode 100644 index 0000000..ed60911 --- /dev/null +++ b/Facepunch.Steamworks/Interop/Callback.This.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Facepunch.Steamworks.Interop.VTable.This +{ + [StructLayout( LayoutKind.Sequential )] + internal struct Callback + { + [UnmanagedFunctionPointer( CallingConvention.ThisCall )] + public delegate void Result( IntPtr thisptr, IntPtr pvParam ); + + [UnmanagedFunctionPointer( CallingConvention.ThisCall )] + public delegate int GetSize( IntPtr thisptr ); + + [MarshalAs(UnmanagedType.FunctionPtr)] + public Result m_RunCallback; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public Result m_RunCallResult; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public GetSize m_GetCallbackSizeBytes; + + internal static IntPtr Get( Action onRunCallback, Func getSize ) + { + var size = Marshal.SizeOf( typeof( Callback ) ); + var ptr = Marshal.AllocHGlobal( size ); + + Callback.Result da = ( _, p ) => onRunCallback( p ); + Callback.GetSize dc = ( _ ) => getSize(); + + var a = GCHandle.Alloc( da ); + var c = GCHandle.Alloc( dc ); + + var table = new Callback() + { + m_RunCallResult = da, + m_RunCallback = da, + m_GetCallbackSizeBytes = dc + }; + + Marshal.StructureToPtr( table, ptr, false ); + + return ptr; + } + } + + +} diff --git a/Facepunch.Steamworks/Interop/Callback.cs b/Facepunch.Steamworks/Interop/Callback.cs new file mode 100644 index 0000000..f75bb73 --- /dev/null +++ b/Facepunch.Steamworks/Interop/Callback.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + + +// +// THANSK AGAIN TO STEAMWORKS.NET +// +// https://github.com/rlabrecque/Steamworks.NET/blob/97935154cf08f60da92c55e2c73ee60a8f456e03/Plugins/Steamworks.NET/CallbackDispatcher.cs +// + +// Calling Conventions: +// Unity x86 Windows - StdCall (No this pointer) +// Unity x86 Linux - Cdecl +// Unity x86 OSX - Cdecl +// Unity x64 Windows - Cdecl +// Unity x64 Linux - Cdecl +// Unity x64 OSX - Cdecl +// Microsoft x86 Windows - ThisCall +// Microsoft x64 Windows - ThisCall +// Mono x86 Linux - Cdecl +// Mono x86 OSX - Cdecl +// Mono x64 Linux - Cdecl +// Mono x64 OSX - Cdecl +// Mono on Windows is probably not supported. + +namespace Facepunch.Steamworks.Interop +{ + internal partial class Callback : IDisposable + { + public int CallbackId = 0; + public bool GameServer = false; + public Action Function; + + private IntPtr vTablePtr = IntPtr.Zero; + private GCHandle callbackPin; + + private readonly int m_size = Marshal.SizeOf(typeof(T)); + + public Callback( bool gameserver, int callbackid, Action func ) + { + GameServer = gameserver; + CallbackId = callbackid; + Function = func; + + BuildVTable(); + + Valve.Steamworks.SteamAPI.RegisterCallback( callbackPin.AddrOfPinnedObject(), CallbackId ); + } + + public virtual void Dispose() + { + if ( callbackPin.IsAllocated ) + { + Valve.Steamworks.SteamAPI.UnregisterCallback( callbackPin.AddrOfPinnedObject() ); + callbackPin.Free(); + } + + if ( vTablePtr != IntPtr.Zero ) + { + Marshal.FreeHGlobal( vTablePtr ); + vTablePtr = IntPtr.Zero; + } + } + + private void OnRunCallback( IntPtr ptr ) + { + T data = (T) Marshal.PtrToStructure( ptr, typeof(T) ); + Function( data ); + } + + + private int GetSize() + { + throw new System.NotImplementedException(); + } + + // Steamworks.NET Specific + private void BuildVTable() + { + InitVTable(); + + var callbackBase = new CallbackBase() + { + vTablePtr = vTablePtr, + CallbackFlags = GameServer ? (byte) CallbackBase.Flags.GameServer : (byte) 0, + CallbackId = CallbackId + }; + callbackPin = GCHandle.Alloc( callbackBase, GCHandleType.Pinned ); + } + + void InitVTable() + { + if ( Config.UseThisCall ) + { + vTablePtr = VTable.This.Callback.Get( OnRunCallback, GetSize ); + return; + } + + //vTablePtr = VTable.CDecl.Callback.Get( OnRunCallback, GetSize ); + } + } + + [StructLayout( LayoutKind.Sequential )] + internal class CallbackBase + { + internal enum Flags : byte + { + Registered = 0x01, + GameServer = 0x02 + } + + public IntPtr vTablePtr; + public byte CallbackFlags; + public int CallbackId; + }; +} \ No newline at end of file diff --git a/Facepunch.Steamworks/Server.cs b/Facepunch.Steamworks/Server.cs index ae130a3..c3e1523 100644 --- a/Facepunch.Steamworks/Server.cs +++ b/Facepunch.Steamworks/Server.cs @@ -1,6 +1,7 @@  using System; +using System.Collections.Generic; using System.Runtime.InteropServices; namespace Facepunch.Steamworks @@ -119,7 +120,9 @@ public Server( uint appId, uint IpAddress, ushort SteamPort, ushort GamePort, us // Set up warning hook callback // SteamAPIWarningMessageHook ptr = InternalOnWarning; - // native.client.SetWarningMessageHook( Marshal.GetFunctionPointerForDelegate( ptr ) ); + var d = Marshal.GetFunctionPointerForDelegate( ptr ); + var rr = GCHandle.Alloc( d ); + native.utils.SetWarningMessageHook( d ); // // Cache common, unchanging info @@ -129,6 +132,10 @@ public Server( uint appId, uint IpAddress, ushort SteamPort, ushort GamePort, us // // Initial settings // + native.gameServer.SetModDir( "rust" ); + native.gameServer.SetProduct( "rust" ); + native.gameServer.SetGameDescription( "rust" ); + native.gameServer.LogOnAnonymous(); native.gameServer.EnableHeartbeats( true ); MaxPlayers = 32; BotCount = 0; @@ -147,17 +154,25 @@ public void Dispose() native.Dispose(); native = null; } + + foreach ( var d in Disposables ) + { + d.Dispose(); + } + Disposables.Clear(); } [UnmanagedFunctionPointer( CallingConvention.Cdecl )] - public delegate void SteamAPIWarningMessageHook( int nSeverity, System.Text.StringBuilder pchDebugText ); + public delegate void SteamAPIWarningMessageHook( int nSeverity, string pchDebugText ); - private void InternalOnWarning( int nSeverity, System.Text.StringBuilder text ) + private void InternalOnWarning( int nSeverity, string text ) { if ( OnMessage != null ) { OnMessage( ( MessageType)nSeverity, text.ToString() ); } + + Console.WriteLine( "STEAM: {0}", text ); } internal event Action OnUpdate; @@ -171,9 +186,10 @@ public void Update() return; Valve.Interop.NativeEntrypoints.Extended.SteamGameServer_RunCallbacks(); + Valve.Steamworks.SteamAPI.RunCallbacks(); // Voice.Update(); // Inventory.Update(); - // Networking.Update(); + // Networking.Update(); if ( OnUpdate != null ) OnUpdate(); @@ -184,20 +200,27 @@ public bool Valid get { return native != null; } } - internal Action InstallCallback( int type, Delegate action ) + internal Action InstallCallback( Action action ) { - var del = Marshal.GetFunctionPointerForDelegate( action ); + // var del = Marshal.GetFunctionPointerForDelegate( action ); // var ptr = Marshal.GetFunctionPointerForDelegate( action ); - // Valve.Steamworks.SteamAPI.RegisterCallback( del, type ); + // Valve.Steamworks.SteamAPI.RegisterCallback( del, type ); - // Valve.Steamworks.SteamAPI.UnregisterCallback( del ); + // Valve.Steamworks.SteamAPI.UnregisterCallback( del ); //return () => Valve.Steamworks.SteamAPI.UnregisterCallback( ptr ); return null; } - + List Disposables = new List(); + + internal void CallResult( Action Callback, int id ) + { + var callback = new Facepunch.Steamworks.Interop.Callback( true, id, Callback ); + Disposables.Add( callback ); + } + /// /// Gets or sets the current MaxPlayers. diff --git a/Facepunch.Steamworks/Server/Auth.cs b/Facepunch.Steamworks/Server/Auth.cs index 9eb74df..e4af02b 100644 --- a/Facepunch.Steamworks/Server/Auth.cs +++ b/Facepunch.Steamworks/Server/Auth.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Facepunch.Steamworks.Callbacks.User; namespace Facepunch.Steamworks { @@ -14,7 +15,7 @@ public ServerAuth Auth get { if ( _auth == null ) - _auth = new ServerAuth { server = this }; + _auth = new ServerAuth( this ); return _auth; } @@ -26,7 +27,45 @@ public class ServerAuth internal Server server; /// - /// Start authorizing a ticket + /// Steamid, Ownerid, Status + /// + public Action OnAuthChange; + + /// + /// Steam authetication statuses + /// + public enum Status : int + { + /// + /// Steam has verified the user is online, the ticket is valid and ticket has not been reused. + /// + OK = 0, + UserNotConnectedToSteam = 1, // The user in question is not connected to steam + NoLicenseOrExpired = 2, // The license has expired. + VACBanned = 3, // The user is VAC banned for this game. + LoggedInElseWhere = 4, // The user account has logged in elsewhere and the session containing the game instance has been disconnected. + VACCheckTimedOut = 5, // VAC has been unable to perform anti-cheat checks on this user + AuthTicketCanceled = 6, // The ticket has been canceled by the issuer + AuthTicketInvalidAlreadyUsed = 7, // This ticket has already been used, it is not valid. + AuthTicketInvalid = 8, // This ticket is not from a user instance currently connected to steam. + PublisherIssuedBan = 9, // The user is banned for this game. The ban came via the web api and not VAC + } + + internal ServerAuth( Server s ) + { + server = s; + + server.CallResult( OnAuthTicketValidate, ValidateAuthTicketResponse.CallbackId ); + } + + void OnAuthTicketValidate( ValidateAuthTicketResponse data ) + { + if ( OnAuthChange != null ) + OnAuthChange( data.SteamID, data.OwnerSteamID, (Status) data.AuthResponse ); + } + + /// + /// Start authorizing a ticket. This user isn't authorized yet. Wait for a call to OnAuthChange. /// public unsafe bool StartSession( byte[] data, ulong steamid ) { @@ -41,6 +80,9 @@ public unsafe bool StartSession( byte[] data, ulong steamid ) } } + /// + /// Forget this guy. They're no longer in the game. + /// public void EndSession( ulong steamid ) { server.native.gameServer.EndAuthSession( steamid );