// This file is provided under The MIT License as part of Steamworks.NET. // Copyright (c) 2013-2019 Riley Labrecque // Please see the included LICENSE.txt for additional information. // This file is automatically generated. // Changes to this file will be reverted when you update Steamworks.NET #if UNITY_ANDROID || UNITY_IOS || UNITY_TIZEN || UNITY_TVOS || UNITY_WEBGL || UNITY_WSA || UNITY_PS4 || UNITY_WII || UNITY_XBOXONE || UNITY_SWITCH #define DISABLESTEAMWORKS #endif #if !DISABLESTEAMWORKS #if UNITY_3_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 #error Unsupported Unity platform. Steamworks.NET requires Unity 4.7 or higher. #elif UNITY_4_7 || UNITY_5 || UNITY_2017 || UNITY_2017_1_OR_NEWER #if UNITY_EDITOR_WIN || (UNITY_STANDALONE_WIN && !UNITY_EDITOR) #define WINDOWS_BUILD #endif #elif STEAMWORKS_WIN #define WINDOWS_BUILD #elif STEAMWORKS_LIN_OSX // So that we don't enter the else block below. #else #error You need to define STEAMWORKS_WIN, or STEAMWORKS_LIN_OSX. Refer to the readme for more details. #endif // Unity 32bit Mono on Windows crashes with ThisCall/Cdecl for some reason, StdCall without the 'this' ptr is the only thing that works..? #if (UNITY_EDITOR_WIN && !UNITY_EDITOR_64) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN && !UNITY_64) #define STDCALL #elif STEAMWORKS_WIN #define THISCALL #endif // 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. using System; using System.Runtime.InteropServices; namespace Steamworks { public static class CallbackDispatcher { // We catch exceptions inside callbacks and reroute them here. // For some reason throwing an exception causes RunCallbacks() to break otherwise. // If you have a custom ExceptionHandler in your engine you can register it here manually until we get something more elegant hooked up. public static void ExceptionHandler(Exception e) { #if UNITY_STANDALONE UnityEngine.Debug.LogException(e); #elif STEAMWORKS_WIN || STEAMWORKS_LIN_OSX Console.WriteLine(e.Message); #endif } } public sealed class Callback : IDisposable { private CCallbackBaseVTable m_CallbackBaseVTable; private IntPtr m_pVTable = IntPtr.Zero; private CCallbackBase m_CCallbackBase; private GCHandle m_pCCallbackBase; public delegate void DispatchDelegate(T param); private event DispatchDelegate m_Func; private bool m_bGameServer; private readonly int m_size = Marshal.SizeOf(typeof(T)); private bool m_bDisposed = false; /// /// Creates a new Callback. You must be calling SteamAPI.RunCallbacks() to retrieve the callbacks. /// Returns a handle to the Callback. /// This MUST be assigned to a member variable to prevent the GC from cleaning it up. /// public static Callback Create(DispatchDelegate func) { return new Callback(func, bGameServer: false); } /// /// Creates a new GameServer Callback. You must be calling GameServer.RunCallbacks() to retrieve the callbacks. /// Returns a handle to the Callback. /// This MUST be assigned to a member variable to prevent the GC from cleaning it up. /// public static Callback CreateGameServer(DispatchDelegate func) { return new Callback(func, bGameServer: true); } public Callback(DispatchDelegate func, bool bGameServer = false) { m_bGameServer = bGameServer; BuildCCallbackBase(); Register(func); } ~Callback() { Dispose(); } public void Dispose() { if (m_bDisposed) { return; } GC.SuppressFinalize(this); Unregister(); if (m_pVTable != IntPtr.Zero) { Marshal.FreeHGlobal(m_pVTable); } if (m_pCCallbackBase.IsAllocated) { m_pCCallbackBase.Free(); } m_bDisposed = true; } // Manual registration of the callback public void Register(DispatchDelegate func) { if (func == null) { throw new Exception("Callback function must not be null."); } if ((m_CCallbackBase.m_nCallbackFlags & CCallbackBase.k_ECallbackFlagsRegistered) == CCallbackBase.k_ECallbackFlagsRegistered) { Unregister(); } if (m_bGameServer) { SetGameserverFlag(); } m_Func = func; // k_ECallbackFlagsRegistered is set by SteamAPI_RegisterCallback. NativeMethods.SteamAPI_RegisterCallback(m_pCCallbackBase.AddrOfPinnedObject(), CallbackIdentities.GetCallbackIdentity(typeof(T))); } public void Unregister() { // k_ECallbackFlagsRegistered is removed by SteamAPI_UnregisterCallback. NativeMethods.SteamAPI_UnregisterCallback(m_pCCallbackBase.AddrOfPinnedObject()); } public void SetGameserverFlag() { m_CCallbackBase.m_nCallbackFlags |= CCallbackBase.k_ECallbackFlagsGameServer; } private void OnRunCallback( #if !STDCALL IntPtr thisptr, #endif IntPtr pvParam) { try { m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T))); } catch (Exception e) { CallbackDispatcher.ExceptionHandler(e); } } // Shouldn't ever get called here, but this is what C++ Steamworks does! private void OnRunCallResult( #if !STDCALL IntPtr thisptr, #endif IntPtr pvParam, bool bFailed, ulong hSteamAPICall) { try { m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T))); } catch (Exception e) { CallbackDispatcher.ExceptionHandler(e); } } private int OnGetCallbackSizeBytes( #if !STDCALL IntPtr thisptr #endif ) { return m_size; } // Steamworks.NET Specific private void BuildCCallbackBase() { m_CallbackBaseVTable = new CCallbackBaseVTable() { m_RunCallResult = OnRunCallResult, m_RunCallback = OnRunCallback, m_GetCallbackSizeBytes = OnGetCallbackSizeBytes }; m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CCallbackBaseVTable))); Marshal.StructureToPtr(m_CallbackBaseVTable, m_pVTable, false); m_CCallbackBase = new CCallbackBase() { m_vfptr = m_pVTable, m_nCallbackFlags = 0, m_iCallback = CallbackIdentities.GetCallbackIdentity(typeof(T)) }; m_pCCallbackBase = GCHandle.Alloc(m_CCallbackBase, GCHandleType.Pinned); } } public sealed class CallResult : IDisposable { private CCallbackBaseVTable m_CallbackBaseVTable; private IntPtr m_pVTable = IntPtr.Zero; private CCallbackBase m_CCallbackBase; private GCHandle m_pCCallbackBase; public delegate void APIDispatchDelegate(T param, bool bIOFailure); private event APIDispatchDelegate m_Func; private SteamAPICall_t m_hAPICall = SteamAPICall_t.Invalid; public SteamAPICall_t Handle { get { return m_hAPICall; } } private readonly int m_size = Marshal.SizeOf(typeof(T)); private bool m_bDisposed = false; /// /// Creates a new async CallResult. You must be calling SteamAPI.RunCallbacks() to retrieve the callback. /// Returns a handle to the CallResult. /// This MUST be assigned to a member variable to prevent the GC from cleaning it up. /// public static CallResult Create(APIDispatchDelegate func = null) { return new CallResult(func); } public CallResult(APIDispatchDelegate func = null) { m_Func = func; BuildCCallbackBase(); } ~CallResult() { Dispose(); } public void Dispose() { if (m_bDisposed) { return; } GC.SuppressFinalize(this); Cancel(); if (m_pVTable != IntPtr.Zero) { Marshal.FreeHGlobal(m_pVTable); } if (m_pCCallbackBase.IsAllocated) { m_pCCallbackBase.Free(); } m_bDisposed = true; } public void Set(SteamAPICall_t hAPICall, APIDispatchDelegate func = null) { // Unlike the official SDK we let the user assign a single function during creation, // and allow them to skip having to do so every time that they call .Set() if (func != null) { m_Func = func; } if (m_Func == null) { throw new Exception("CallResult function was null, you must either set it in the CallResult Constructor or via Set()"); } if (m_hAPICall != SteamAPICall_t.Invalid) { NativeMethods.SteamAPI_UnregisterCallResult(m_pCCallbackBase.AddrOfPinnedObject(), (ulong)m_hAPICall); } m_hAPICall = hAPICall; if (hAPICall != SteamAPICall_t.Invalid) { NativeMethods.SteamAPI_RegisterCallResult(m_pCCallbackBase.AddrOfPinnedObject(), (ulong)hAPICall); } } public bool IsActive() { return (m_hAPICall != SteamAPICall_t.Invalid); } public void Cancel() { if (m_hAPICall != SteamAPICall_t.Invalid) { NativeMethods.SteamAPI_UnregisterCallResult(m_pCCallbackBase.AddrOfPinnedObject(), (ulong)m_hAPICall); m_hAPICall = SteamAPICall_t.Invalid; } } public void SetGameserverFlag() { m_CCallbackBase.m_nCallbackFlags |= CCallbackBase.k_ECallbackFlagsGameServer; } // Shouldn't ever get called here, but this is what C++ Steamworks does! private void OnRunCallback( #if !STDCALL IntPtr thisptr, #endif IntPtr pvParam) { m_hAPICall = SteamAPICall_t.Invalid; // Caller unregisters for us try { m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)), false); } catch (Exception e) { CallbackDispatcher.ExceptionHandler(e); } } private void OnRunCallResult( #if !STDCALL IntPtr thisptr, #endif IntPtr pvParam, bool bFailed, ulong hSteamAPICall_) { SteamAPICall_t hSteamAPICall = (SteamAPICall_t)hSteamAPICall_; if (hSteamAPICall == m_hAPICall) { m_hAPICall = SteamAPICall_t.Invalid; // Caller unregisters for us try { m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)), bFailed); } catch (Exception e) { CallbackDispatcher.ExceptionHandler(e); } } } private int OnGetCallbackSizeBytes( #if !STDCALL IntPtr thisptr #endif ) { return m_size; } // Steamworks.NET Specific private void BuildCCallbackBase() { m_CallbackBaseVTable = new CCallbackBaseVTable() { m_RunCallback = OnRunCallback, m_RunCallResult = OnRunCallResult, m_GetCallbackSizeBytes = OnGetCallbackSizeBytes }; m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CCallbackBaseVTable))); Marshal.StructureToPtr(m_CallbackBaseVTable, m_pVTable, false); m_CCallbackBase = new CCallbackBase() { m_vfptr = m_pVTable, m_nCallbackFlags = 0, m_iCallback = CallbackIdentities.GetCallbackIdentity(typeof(T)) }; m_pCCallbackBase = GCHandle.Alloc(m_CCallbackBase, GCHandleType.Pinned); } } [StructLayout(LayoutKind.Sequential)] internal class CCallbackBase { public const byte k_ECallbackFlagsRegistered = 0x01; public const byte k_ECallbackFlagsGameServer = 0x02; public IntPtr m_vfptr; public byte m_nCallbackFlags; public int m_iCallback; } [StructLayout(LayoutKind.Sequential)] internal class CCallbackBaseVTable { #if STDCALL private const CallingConvention cc = CallingConvention.StdCall; [UnmanagedFunctionPointer(cc)] public delegate void RunCBDel(IntPtr pvParam); [UnmanagedFunctionPointer(cc)] public delegate void RunCRDel(IntPtr pvParam, [MarshalAs(UnmanagedType.I1)] bool bIOFailure, ulong hSteamAPICall); [UnmanagedFunctionPointer(cc)] public delegate int GetCallbackSizeBytesDel(); #else #if THISCALL private const CallingConvention cc = CallingConvention.ThisCall; #else private const CallingConvention cc = CallingConvention.Cdecl; #endif [UnmanagedFunctionPointer(cc)] public delegate void RunCBDel(IntPtr thisptr, IntPtr pvParam); [UnmanagedFunctionPointer(cc)] public delegate void RunCRDel(IntPtr thisptr, IntPtr pvParam, [MarshalAs(UnmanagedType.I1)] bool bIOFailure, ulong hSteamAPICall); [UnmanagedFunctionPointer(cc)] public delegate int GetCallbackSizeBytesDel(IntPtr thisptr); #endif // RunCallback and RunCallResult are swapped in MSVC ABI #if WINDOWS_BUILD [NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)] public RunCRDel m_RunCallResult; #endif [NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)] public RunCBDel m_RunCallback; #if !WINDOWS_BUILD [NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)] public RunCRDel m_RunCallResult; #endif [NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)] public GetCallbackSizeBytesDel m_GetCallbackSizeBytes; } } #endif // !DISABLESTEAMWORKS