using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace Steamworks { /// /// Functions for accessing and manipulating Steam user information. /// This is also where the APIs for Steam Voice are exposed. /// public static class User { static Internal.ISteamUser _internal; internal static Internal.ISteamUser Internal { get { if ( _internal == null ) { _internal = new Internal.ISteamUser(); richPresence = new Dictionary(); SampleRate = OptimalSampleRate; } return _internal; } } static Dictionary richPresence; internal static void InstallEvents() { new Event( x => OnSteamServersConnected?.Invoke() ); new Event( x => OnSteamServerConnectFailure?.Invoke() ); new Event( x => OnSteamServersDisconnected?.Invoke() ); new Event( x => OnClientGameServerDeny?.Invoke() ); new Event( x => OnLicensesUpdated?.Invoke() ); new Event( x => OnValidateAuthTicketResponse?.Invoke( x.SteamID, x.OwnerSteamID, x.AuthSessionResponse ) ); new Event( x => OnMicroTxnAuthorizationResponse?.Invoke( x.AppID, x.OrderID, x.Authorized != 0 ) ); new Event( x => OnGameWebCallback?.Invoke( x.URL ) ); } /// /// Called when a connections to the Steam back-end has been established. /// This means the Steam client now has a working connection to the Steam servers. /// Usually this will have occurred before the game has launched, and should only be seen if the /// user has dropped connection due to a networking issue or a Steam server update. /// public static event Action OnSteamServersConnected; /// /// Called when a connection attempt has failed. /// This will occur periodically if the Steam client is not connected, /// and has failed when retrying to establish a connection. /// public static event Action OnSteamServerConnectFailure; /// /// Called if the client has lost connection to the Steam servers. /// Real-time services will be disabled until a matching OnSteamServersConnected has been posted. /// public static event Action OnSteamServersDisconnected; /// /// Sent by the Steam server to the client telling it to disconnect from the specified game server, /// which it may be in the process of or already connected to. /// The game client should immediately disconnect upon receiving this message. /// This can usually occur if the user doesn't have rights to play on the game server. /// public static event Action OnClientGameServerDeny; /// /// Called whenever the users licenses (owned packages) changes. /// public static event Action OnLicensesUpdated; /// /// Called when an auth ticket has been validated. /// The first parameter is the steamid of this user /// The second is the Steam ID that owns the game, this will be different from the first if the game is being borrowed via Steam Family Sharing /// public static event Action OnValidateAuthTicketResponse; /// /// Called when a user has responded to a microtransaction authorization request. /// ( appid, orderid, user authorized ) /// public static event Action OnMicroTxnAuthorizationResponse; /// /// Sent to your game in response to a steam://gamewebcallback/ command from a user clicking a link in the Steam overlay browser. /// You can use this to add support for external site signups where you want to pop back into the browser after some web page /// signup sequence, and optionally get back some detail about that. /// public static event Action OnGameWebCallback; /// /// Checks if the current user's Steam client is connected to the Steam servers. /// If it's not then no real-time services provided by the Steamworks API will be enabled. The Steam /// client will automatically be trying to recreate the connection as often as possible. When the /// connection is restored a SteamServersConnected_t callback will be posted. /// You usually don't need to check for this yourself. All of the API calls that rely on this will /// check internally. Forcefully disabling stuff when the player loses access is usually not a /// very good experience for the player and you could be preventing them from accessing APIs that do not /// need a live connection to Steam. /// public static bool IsLoggedOn => Internal.BLoggedOn(); /// /// Gets the Steam ID of the account currently logged into the Steam client. This is /// commonly called the 'current user', or 'local user'. /// A Steam ID is a unique identifier for a Steam accounts, Steam groups, Lobbies and Chat /// rooms, and used to differentiate users in all parts of the Steamworks API. /// public static SteamId SteamId => Internal.GetSteamID(); static bool _recordingVoice; /// /// Starts/Stops voice recording. /// Once started, use GetAvailableVoice and GetVoice to get the data, and then call StopVoiceRecording /// when the user has released their push-to-talk hotkey or the game session has completed. /// public static bool VoiceRecord { get => _recordingVoice; set { _recordingVoice = value; if ( value ) Internal.StartVoiceRecording(); else Internal.StopVoiceRecording(); } } /// /// Returns true if we have voice data waiting to be read /// public static bool HasVoiceData { get { uint szCompressed = 0, deprecated = 0; if ( Internal.GetAvailableVoice( ref szCompressed, ref deprecated, 0 ) != VoiceResult.OK ) return false; return szCompressed > 0; } } static byte[] readBuffer = new byte[1024*128]; /// /// Reads the voice data and returns the number of bytes written. /// The compressed data can be transmitted by your application and decoded back into raw audio data using /// DecompressVoice on the other side. The compressed data provided is in an arbitrary format and is not meant to be played directly. /// This should be called once per frame, and at worst no more than four times a second to keep the microphone input delay as low as /// possible. Calling this any less may result in gaps in the returned stream. /// public static unsafe int ReadVoiceData( System.IO.Stream stream ) { if ( !HasVoiceData ) return 0; uint szWritten = 0; uint deprecated = 0; fixed ( byte* b = readBuffer ) { if ( Internal.GetVoice( true, (IntPtr)b, (uint)readBuffer.Length, ref szWritten, false, IntPtr.Zero, 0, ref deprecated, 0 ) != VoiceResult.OK ) return 0; } if ( szWritten == 0 ) return 0; stream.Write( readBuffer, 0, (int) szWritten ); return (int) szWritten; } static uint sampleRate = 48000; public static uint SampleRate { get => sampleRate; set { if ( SampleRate < 11025 ) throw new System.Exception( "Sample Rate must be between 11025 and 48000" ); if ( SampleRate > 48000 ) throw new System.Exception( "Sample Rate must be between 11025 and 48000" ); sampleRate = value; } } public static uint OptimalSampleRate => Internal.GetVoiceOptimalSampleRate(); /// /// Decodes the compressed voice data returned by GetVoice. /// The output data is raw single-channel 16-bit PCM audio.The decoder supports any sample rate from 11025 to 48000. /// public static unsafe int DecompressVoice( System.IO.Stream input, int length, System.IO.Stream output ) { var from = Helpers.TakeBuffer( length ); var to = Helpers.TakeBuffer( 1024 * 64 ); // // Copy from input stream to a pinnable buffer // using ( var s = new System.IO.MemoryStream( from ) ) { input.CopyTo( s ); } uint szWritten = 0; fixed ( byte* frm = from ) fixed ( byte* dst = to ) { if ( Internal.DecompressVoice( (IntPtr) frm, (uint) length, (IntPtr)dst, (uint)to.Length, ref szWritten, SampleRate ) != VoiceResult.OK ) return 0; } if ( szWritten == 0 ) return 0; // // Copy to output buffer // output.Write( to, 0, (int)szWritten ); return (int)szWritten; } /// /// Retrieve a authentication ticket to be sent to the entity who wishes to authenticate you. /// public static unsafe AuthTicket GetAuthSessionTicket() { var data = Helpers.TakeBuffer( 1024 ); fixed ( byte* b = data ) { uint ticketLength = 0; uint ticket = Internal.GetAuthSessionTicket( (IntPtr)b, data.Length, ref ticketLength ); if ( ticket == 0 ) return null; return new AuthTicket() { Data = data.Take( (int)ticketLength ).ToArray(), Handle = ticket }; } } public static unsafe BeginAuthResult BeginAuthSession( byte[] ticketData, SteamId steamid ) { fixed ( byte* ptr = ticketData ) { return Internal.BeginAuthSession( (IntPtr) ptr, ticketData.Length, steamid ); } } public static void EndAuthSession( SteamId steamid ) => Internal.EndAuthSession( steamid ); // UserHasLicenseForApp - SERVER VERSION ( DLC CHECKING ) /// /// Checks if the current users looks like they are behind a NAT device. /// This is only valid if the user is connected to the Steam servers and may not catch all forms of NAT. /// public static bool IsBehindNAT => Internal.BIsBehindNAT(); /// /// Gets the Steam level of the user, as shown on their Steam community profile. /// public static int SteamLevel => Internal.GetPlayerSteamLevel(); /// /// Requests a URL which authenticates an in-game browser for store check-out, and then redirects to the specified URL. /// As long as the in-game browser accepts and handles session cookies, Steam microtransaction checkout pages will automatically recognize the user instead of presenting a login page. /// NOTE: The URL has a very short lifetime to prevent history-snooping attacks, so you should only call this API when you are about to launch the browser, or else immediately navigate to the result URL using a hidden browser window. /// NOTE: The resulting authorization cookie has an expiration time of one day, so it would be a good idea to request and visit a new auth URL every 12 hours. /// public static async Task GetStoreAuthUrlAsync( string url ) { var response = await Internal.RequestStoreAuthURL( url ); if ( !response.HasValue ) return null; return response.Value.URL; } /// /// Checks whether the current user has verified their phone number. /// public static bool IsPhoneVerified => Internal.BIsPhoneVerified(); /// /// Checks whether the current user has Steam Guard two factor authentication enabled on their account. /// public static bool IsTwoFactorEnabled => Internal.BIsTwoFactorEnabled(); /// /// Checks whether the user's phone number is used to uniquely identify them. /// public static bool IsPhoneIdentifying => Internal.BIsPhoneIdentifying(); /// /// Checks whether the current user's phone number is awaiting (re)verification. /// public static bool IsPhoneRequiringVerification => Internal.BIsPhoneRequiringVerification(); } }