From cd30b8544484dc6d9212b248cfb2b763f99af9dc Mon Sep 17 00:00:00 2001 From: Garry Newman Date: Sun, 14 Apr 2019 20:48:14 +0100 Subject: [PATCH] SteamApps events --- Facepunch.Steamworks/Callbacks/Events.cs | 129 ++++++++++++++++++ Facepunch.Steamworks/Generated/SteamApi.cs | 11 +- Facepunch.Steamworks/Redux/Apps.cs | 21 ++- Facepunch.Steamworks/Redux/Steam.cs | 10 ++ .../SteamNative/SteamNative.Callback.cs | 90 +++++++++++- 5 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 Facepunch.Steamworks/Callbacks/Events.cs diff --git a/Facepunch.Steamworks/Callbacks/Events.cs b/Facepunch.Steamworks/Callbacks/Events.cs new file mode 100644 index 0000000..8426c5d --- /dev/null +++ b/Facepunch.Steamworks/Callbacks/Events.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using SteamNative; + +namespace Steamworks +{ + // + // Created on registration of a callback + // + internal class Event : IDisposable where T : struct, Steamworks.ISteamCallback + { + public Action Action; + + List Allocations = new List(); + internal IntPtr vTablePtr; + internal GCHandle PinnedCallback; + + public void Dispose() + { + UnregisterCallback(); + + foreach ( var a in Allocations ) + { + if ( a.IsAllocated ) + a.Free(); + } + + Allocations.Clear(); + + if ( PinnedCallback.IsAllocated ) + PinnedCallback.Free(); + + if ( vTablePtr != IntPtr.Zero ) + { + Marshal.FreeHGlobal( vTablePtr ); + vTablePtr = IntPtr.Zero; + } + } + + private void UnregisterCallback() + { + if ( !PinnedCallback.IsAllocated ) + return; + + Steam.UnregisterCallback( PinnedCallback.AddrOfPinnedObject() ); + } + + public virtual bool IsValid { get { return true; } } + + T template; + + internal Event( Action onresult, bool gameserver = false ) + { + Action = onresult; + + template = new T(); + + // + // Create the functions we need for the vtable + // + if ( Facepunch.Steamworks.Config.UseThisCall ) + { + // + // Create the VTable by manually allocating the memory and copying across + // + if ( Platform.IsWindows ) + { + vTablePtr = Callback.VTableWinThis.GetVTable( OnResultThis, OnResultWithInfoThis, OnGetSizeThis, Allocations ); + + } + else + { + vTablePtr = Callback.VTableThis.GetVTable( OnResultThis, OnResultWithInfoThis, OnGetSizeThis, Allocations ); + } + } + else + { + // + // Create the VTable by manually allocating the memory and copying across + // + if ( Platform.IsWindows ) + { + vTablePtr = Callback.VTableWin.GetVTable( OnResult, OnResultWithInfo, OnGetSize, Allocations ); + } + else + { + vTablePtr = Callback.VTable.GetVTable( OnResult, OnResultWithInfo, OnGetSize, Allocations ); + } + } + + // + // Create the callback object + // + var cb = new Callback(); + cb.vTablePtr = vTablePtr; + cb.CallbackFlags = gameserver ? (byte)0x02 : (byte)0; + cb.CallbackId = template.GetCallbackId(); + + // + // Pin the callback, so it doesn't get garbage collected and we can pass the pointer to native + // + PinnedCallback = GCHandle.Alloc( cb, GCHandleType.Pinned ); + + // + // Register the callback with Steam + // + Steam.RegisterCallback( PinnedCallback.AddrOfPinnedObject(), cb.CallbackId ); + } + + [MonoPInvokeCallback] internal void OnResultThis( IntPtr self, IntPtr param ) => OnResult( param ); + [MonoPInvokeCallback] internal void OnResultWithInfoThis( IntPtr self, IntPtr param, bool failure, SteamNative.SteamAPICall_t call ) => OnResultWithInfo( param, failure, call ); + [MonoPInvokeCallback] internal int OnGetSizeThis( IntPtr self ) => OnGetSize(); + [MonoPInvokeCallback] internal int OnGetSize() => template.GetStructSize(); + [MonoPInvokeCallback] internal void OnResult( IntPtr param ) => OnResultWithInfo( param, false, 0 ); + + [MonoPInvokeCallback] + internal void OnResultWithInfo( IntPtr param, bool failure, SteamNative.SteamAPICall_t call ) + { + if ( failure ) return; + + var value = (T)template.Fill( param ); + + Action( value ); + } + } +} \ No newline at end of file diff --git a/Facepunch.Steamworks/Generated/SteamApi.cs b/Facepunch.Steamworks/Generated/SteamApi.cs index a752257..a0206ec 100644 --- a/Facepunch.Steamworks/Generated/SteamApi.cs +++ b/Facepunch.Steamworks/Generated/SteamApi.cs @@ -12,5 +12,14 @@ public static class SteamApi [DllImport( "Steam_api64", EntryPoint = "SteamAPI_GetHSteamUser", CallingConvention = CallingConvention.Cdecl )] public static extern int GetHSteamUser(); - + + [DllImport( "Steam_api64", EntryPoint = "SteamAPI_RunCallbacks", CallingConvention = CallingConvention.Cdecl )] + public static extern int RunCallbacks(); + + [DllImport( "Steam_api64", EntryPoint = "SteamAPI_RegisterCallback", CallingConvention = CallingConvention.Cdecl )] + public static extern int RegisterCallback( IntPtr pCallback, int callback ); + + [DllImport( "Steam_api64", EntryPoint = "SteamAPI_UnregisterCallback", CallingConvention = CallingConvention.Cdecl )] + public static extern int UnregisterCallback( IntPtr pCallback ); + } diff --git a/Facepunch.Steamworks/Redux/Apps.cs b/Facepunch.Steamworks/Redux/Apps.cs index 0db7746..052a3a8 100644 --- a/Facepunch.Steamworks/Redux/Apps.cs +++ b/Facepunch.Steamworks/Redux/Apps.cs @@ -1,4 +1,5 @@ -using System; +using SteamNative; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -24,6 +25,24 @@ internal static Internal.ISteamApps steamapps } } + internal static void InstallEvents() + { + new Event( x => OnDlcInstalled( x.AppID ) ); + new Event( x => OnNewLaunchParameters() ); + } + + /// + /// posted after the user gains ownership of DLC & that DLC is installed + /// + public static event Action< AppId > OnDlcInstalled; + + /// + /// posted after the user gains executes a Steam URL with command line or query parameters + /// such as steam://run/appid//-commandline/?param1=value1¶m2=value2¶m3=value3 etc + /// while the game is already running. The new params can be queried + /// with GetLaunchQueryParam and GetLaunchCommandLine + /// + public static event Action OnNewLaunchParameters; /// /// Checks if the active user is subscribed to the current App ID diff --git a/Facepunch.Steamworks/Redux/Steam.cs b/Facepunch.Steamworks/Redux/Steam.cs index 7cc40ad..c6c6f33 100644 --- a/Facepunch.Steamworks/Redux/Steam.cs +++ b/Facepunch.Steamworks/Redux/Steam.cs @@ -31,5 +31,15 @@ public static void Init( uint appid ) throw new System.Exception( "GetHSteamUser returned 0" ); } } + + internal static void RegisterCallback( IntPtr intPtr, int callbackId ) + { + SteamApi.RegisterCallback( intPtr, callbackId ); + } + + internal static void UnregisterCallback( IntPtr intPtr ) + { + SteamApi.UnregisterCallback( intPtr ); + } } } \ No newline at end of file diff --git a/Facepunch.Steamworks/SteamNative/SteamNative.Callback.cs b/Facepunch.Steamworks/SteamNative/SteamNative.Callback.cs index a9677fb..c2fcb86 100644 --- a/Facepunch.Steamworks/SteamNative/SteamNative.Callback.cs +++ b/Facepunch.Steamworks/SteamNative/SteamNative.Callback.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Collections.Generic; using Facepunch.Steamworks; namespace SteamNative @@ -27,7 +28,27 @@ public class VTable public ResultD ResultA; public ResultWithInfoD ResultB; public GetSizeD GetSize; - } + + internal static IntPtr GetVTable( ResultD onResultThis, ResultWithInfoD onResultWithInfoThis, GetSizeD onGetSizeThis, List allocations ) + { + var vTablePtr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( Callback.VTable ) ) ); + + var vTable = new Callback.VTable + { + ResultA = onResultThis, + ResultB = onResultWithInfoThis, + GetSize = onGetSizeThis, + }; + + allocations.Add( GCHandle.Alloc( vTable.ResultA ) ); + allocations.Add( GCHandle.Alloc( vTable.ResultB ) ); + allocations.Add( GCHandle.Alloc( vTable.GetSize ) ); + + Marshal.StructureToPtr( vTable, vTablePtr, false ); + + return vTablePtr; + } + } [StructLayout( LayoutKind.Sequential, Pack = 1 )] public class VTableWin @@ -39,14 +60,55 @@ public class VTableWin public ResultWithInfoD ResultB; public ResultD ResultA; public GetSizeD GetSize; - } + + internal static IntPtr GetVTable( ResultD onResultThis, ResultWithInfoD onResultWithInfoThis, GetSizeD onGetSizeThis, List allocations ) + { + var vTablePtr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( Callback.VTableWin ) ) ); + + var vTable = new Callback.VTableWin + { + ResultA = onResultThis, + ResultB = onResultWithInfoThis, + GetSize = onGetSizeThis, + }; + + allocations.Add( GCHandle.Alloc( vTable.ResultA ) ); + allocations.Add( GCHandle.Alloc( vTable.ResultB ) ); + allocations.Add( GCHandle.Alloc( vTable.GetSize ) ); + + Marshal.StructureToPtr( vTable, vTablePtr, false ); + + return vTablePtr; + } + } [StructLayout( LayoutKind.Sequential, Pack = 1 )] public class VTableThis { [UnmanagedFunctionPointer( CallingConvention.ThisCall )] public delegate void ResultD( IntPtr thisptr, IntPtr pvParam ); [UnmanagedFunctionPointer( CallingConvention.ThisCall )] public delegate void ResultWithInfoD( IntPtr thisptr, IntPtr pvParam, bool bIOFailure, SteamNative.SteamAPICall_t hSteamAPICall ); - [UnmanagedFunctionPointer( CallingConvention.ThisCall )] public delegate int GetSizeD( IntPtr thisptr ); + + internal static IntPtr GetVTable( ResultD onResultThis, ResultWithInfoD onResultWithInfoThis, GetSizeD onGetSizeThis, List allocations ) + { + var vTablePtr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( Callback.VTableThis ) ) ); + + var vTable = new Callback.VTableThis + { + ResultA = onResultThis, + ResultB = onResultWithInfoThis, + GetSize = onGetSizeThis, + }; + + allocations.Add( GCHandle.Alloc( vTable.ResultA ) ); + allocations.Add( GCHandle.Alloc( vTable.ResultB ) ); + allocations.Add( GCHandle.Alloc( vTable.GetSize ) ); + + Marshal.StructureToPtr( vTable, vTablePtr, false ); + + return vTablePtr; + } + + [UnmanagedFunctionPointer( CallingConvention.ThisCall )] public delegate int GetSizeD( IntPtr thisptr ); public ResultD ResultA; public ResultWithInfoD ResultB; @@ -63,7 +125,27 @@ public class VTableWinThis public ResultWithInfoD ResultB; public ResultD ResultA; public GetSizeD GetSize; - } + + internal static IntPtr GetVTable( ResultD onResultThis, ResultWithInfoD onResultWithInfoThis, GetSizeD onGetSizeThis, List allocations ) + { + var vTablePtr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( Callback.VTableWinThis ) ) ); + + var vTable = new Callback.VTableWinThis + { + ResultA = onResultThis, + ResultB = onResultWithInfoThis, + GetSize = onGetSizeThis, + }; + + allocations.Add( GCHandle.Alloc( vTable.ResultA ) ); + allocations.Add( GCHandle.Alloc( vTable.ResultB ) ); + allocations.Add( GCHandle.Alloc( vTable.GetSize ) ); + + Marshal.StructureToPtr( vTable, vTablePtr, false ); + + return vTablePtr; + } + } }; //