2019-04-15 20:54:50 +01:00
using System ;
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading.Tasks ;
2019-04-16 14:38:10 +01:00
using Steamworks.Data ;
2019-04-15 20:54:50 +01:00
namespace Steamworks
{
/// <summary>
/// Provides the core of the Steam Game Servers API
/// </summary>
2020-02-22 20:23:19 +00:00
public partial class SteamServer : SteamClass
2019-04-15 20:54:50 +01:00
{
2020-02-22 20:23:19 +00:00
internal static ISteamGameServer Internal ;
internal override SteamInterface Interface = > Internal ;
2019-04-16 09:30:17 +01:00
2020-02-22 20:23:19 +00:00
internal override void InitializeInterface ( bool server )
2019-04-15 20:54:50 +01:00
{
2020-02-22 20:23:19 +00:00
Internal = new ISteamGameServer ( server ) ;
InstallEvents ( ) ;
2019-04-15 20:54:50 +01:00
}
2020-02-22 20:23:19 +00:00
public static bool IsValid = > Internal ! = null & & Internal . IsValid ;
2019-04-16 09:30:17 +01:00
2019-05-08 20:49:33 +01:00
public static Action < Exception > OnCallbackException ;
2019-04-15 20:54:50 +01:00
internal static void InstallEvents ( )
{
2019-05-18 12:04:43 +01:00
SteamInventory . InstallEvents ( ) ;
2020-02-22 20:23:19 +00:00
//SteamNetworkingSockets.InstallEvents(true);
2019-05-18 12:04:43 +01:00
2020-02-22 20:23:19 +00:00
Dispatch . Install < ValidateAuthTicketResponse_t > ( x = > OnValidateAuthTicketResponse ? . Invoke ( x . SteamID , x . OwnerSteamID , x . AuthSessionResponse ) , true ) ;
Dispatch . Install < SteamServersConnected_t > ( x = > OnSteamServersConnected ? . Invoke ( ) , true ) ;
Dispatch . Install < SteamServerConnectFailure_t > ( x = > OnSteamServerConnectFailure ? . Invoke ( x . Result , x . StillRetrying ) , true ) ;
Dispatch . Install < SteamServersDisconnected_t > ( x = > OnSteamServersDisconnected ? . Invoke ( x . Result ) , true ) ;
2019-04-15 20:54:50 +01:00
}
/// <summary>
/// User has been authed or rejected
/// </summary>
2019-04-16 12:17:24 +01:00
public static event Action < SteamId , SteamId , AuthResponse > OnValidateAuthTicketResponse ;
2019-04-15 20:54:50 +01:00
2019-05-17 01:08:35 +02:00
/// <summary>
/// Called when a connections to the Steam back-end has been established.
/// This means the server now is logged on and has a working connection to the Steam master server.
/// </summary>
public static event Action OnSteamServersConnected ;
/// <summary>
2019-05-27 15:24:43 +01:00
/// This will occur periodically if the Steam client is not connected, and has failed when retrying to establish a connection (result, stilltrying)
2019-05-17 01:08:35 +02:00
/// </summary>
2019-05-27 15:24:43 +01:00
public static event Action < Result , bool > OnSteamServerConnectFailure ;
/// <summary>
/// Disconnected from Steam
/// </summary>
public static event Action < Result > OnSteamServersDisconnected ;
2019-05-17 01:08:35 +02:00
2019-08-17 11:40:52 +01:00
/// <summary>
/// Initialize the steam server.
/// If asyncCallbacks is false you need to call RunCallbacks manually every frame.
/// </summary>
public static void Init ( AppId appid , SteamServerInit init , bool asyncCallbacks = true )
2019-04-15 20:54:50 +01:00
{
uint ipaddress = 0 ; // Any Port
if ( init . SteamPort = = 0 )
init = init . WithRandomSteamPort ( ) ;
if ( init . IpAddress ! = null )
ipaddress = Utility . IpToInt32 ( init . IpAddress ) ;
2019-05-13 09:13:19 +01:00
System . Environment . SetEnvironmentVariable ( "SteamAppId" , appid . ToString ( ) ) ;
System . Environment . SetEnvironmentVariable ( "SteamGameId" , appid . ToString ( ) ) ;
2019-05-13 10:37:36 +01:00
var secure = ( int ) ( init . Secure ? 3 : 2 ) ;
2019-05-13 09:13:19 +01:00
2019-04-15 20:54:50 +01:00
//
// Get other interfaces
//
2019-05-13 10:37:36 +01:00
if ( ! SteamInternal . GameServer_Init ( ipaddress , init . SteamPort , init . GamePort , init . QueryPort , secure , init . VersionString ) )
2019-04-15 20:54:50 +01:00
{
2019-05-13 10:37:36 +01:00
throw new System . Exception ( $"InitGameServer returned false ({ipaddress},{init.SteamPort},{init.GamePort},{init.QueryPort},{secure},\" { init . VersionString } \ ")" ) ;
2019-04-15 20:54:50 +01:00
}
2020-02-22 20:23:19 +00:00
//
// Dispatch is responsible for pumping the
// event loop.
//
Dispatch . Init ( ) ;
Dispatch . ServerPipe = SteamGameServer . GetHSteamPipe ( ) ;
AddInterface < SteamServer > ( ) ;
AddInterface < SteamNetworkingUtils > ( ) ;
AddInterface < SteamNetworkingSockets > ( ) ;
2019-04-16 09:30:17 +01:00
2019-04-15 20:54:50 +01:00
//
// Initial settings
//
2019-05-27 15:24:43 +01:00
AutomaticHeartbeats = true ;
2019-04-15 20:54:50 +01:00
MaxPlayers = 32 ;
BotCount = 0 ;
Product = $"{appid.Value}" ;
ModDir = init . ModDir ;
GameDescription = init . GameDescription ;
Passworded = false ;
2019-05-27 15:24:43 +01:00
DedicatedServer = init . DedicatedServer ;
2019-04-15 20:54:50 +01:00
InstallEvents ( ) ;
2019-05-06 21:19:00 +01:00
2019-08-17 11:40:26 +01:00
if ( asyncCallbacks )
{
2020-02-22 20:23:19 +00:00
//
// This will keep looping in the background every 16 ms
// until we shut down.
//
Dispatch . LoopServerAsync ( ) ;
2019-08-17 11:40:26 +01:00
}
2019-04-15 21:18:03 +01:00
}
2020-02-22 20:23:19 +00:00
internal static void AddInterface < T > ( ) where T : SteamClass , new ( )
2019-05-10 14:08:17 +01:00
{
2020-02-22 20:23:19 +00:00
var t = new T ( ) ;
t . InitializeInterface ( true ) ;
openInterfaces . Add ( t ) ;
2019-05-10 14:08:17 +01:00
}
2020-02-22 20:23:19 +00:00
static List < SteamClass > openInterfaces = new List < SteamClass > ( ) ;
2019-05-10 14:08:17 +01:00
internal static void ShutdownInterfaces ( )
{
2020-02-22 20:23:19 +00:00
foreach ( var e in openInterfaces )
2019-05-10 14:08:17 +01:00
{
2020-02-22 20:23:19 +00:00
e . DestroyInterface ( ) ;
2019-05-10 14:08:17 +01:00
}
2020-02-22 20:23:19 +00:00
openInterfaces . Clear ( ) ;
2019-05-10 14:08:17 +01:00
}
2019-04-16 09:30:17 +01:00
public static void Shutdown ( )
{
2020-02-22 20:23:19 +00:00
Internal = null ;
2019-04-29 12:55:38 +01:00
2019-05-10 14:08:17 +01:00
ShutdownInterfaces ( ) ;
2019-05-06 15:45:01 +01:00
SteamGameServer . Shutdown ( ) ;
2019-04-16 09:30:17 +01:00
}
2019-05-08 20:49:33 +01:00
/// <summary>
/// Run the callbacks. This is also called in Async callbacks.
/// </summary>
public static void RunCallbacks ( )
2019-04-16 09:30:17 +01:00
{
2020-02-22 20:23:19 +00:00
if ( Dispatch . ServerPipe ! = 0 )
{
Dispatch . Frame ( Dispatch . ServerPipe ) ;
}
2019-04-15 20:54:50 +01:00
}
/// <summary>
/// Sets whether this should be marked as a dedicated server.
/// If not, it is assumed to be a listen server.
/// </summary>
public static bool DedicatedServer
{
get = > _dedicatedServer ;
set { if ( _dedicatedServer = = value ) return ; Internal . SetDedicatedServer ( value ) ; _dedicatedServer = value ; }
}
private static bool _dedicatedServer ;
/// <summary>
/// Gets or sets the current MaxPlayers.
/// This doesn't enforce any kind of limit, it just updates the master server.
/// </summary>
public static int MaxPlayers
{
get = > _maxplayers ;
set { if ( _maxplayers = = value ) return ; Internal . SetMaxPlayerCount ( value ) ; _maxplayers = value ; }
}
private static int _maxplayers = 0 ;
/// <summary>
/// Gets or sets the current BotCount.
/// This doesn't enforce any kind of limit, it just updates the master server.
/// </summary>
public static int BotCount
{
get = > _botcount ;
set { if ( _botcount = = value ) return ; Internal . SetBotPlayerCount ( value ) ; _botcount = value ; }
}
private static int _botcount = 0 ;
/// <summary>
/// Gets or sets the current Map Name.
/// </summary>
public static string MapName
{
get = > _mapname ;
set { if ( _mapname = = value ) return ; Internal . SetMapName ( value ) ; _mapname = value ; }
}
private static string _mapname ;
/// <summary>
/// Gets or sets the current ModDir
/// </summary>
public static string ModDir
{
get = > _modDir ;
internal set { if ( _modDir = = value ) return ; Internal . SetModDir ( value ) ; _modDir = value ; }
}
private static string _modDir = "" ;
/// <summary>
/// Gets the current product
/// </summary>
public static string Product
{
get = > _product ;
internal set { if ( _product = = value ) return ; Internal . SetProduct ( value ) ; _product = value ; }
}
private static string _product = "" ;
/// <summary>
/// Gets or sets the current Product
/// </summary>
public static string GameDescription
{
get = > _gameDescription ;
internal set { if ( _gameDescription = = value ) return ; Internal . SetGameDescription ( value ) ; _gameDescription = value ; }
}
private static string _gameDescription = "" ;
/// <summary>
/// Gets or sets the current ServerName
/// </summary>
public static string ServerName
{
get = > _serverName ;
set { if ( _serverName = = value ) return ; Internal . SetServerName ( value ) ; _serverName = value ; }
}
private static string _serverName = "" ;
/// <summary>
/// Set whether the server should report itself as passworded
/// </summary>
public static bool Passworded
{
get = > _passworded ;
set { if ( _passworded = = value ) return ; Internal . SetPasswordProtected ( value ) ; _passworded = value ; }
}
private static bool _passworded ;
/// <summary>
/// Gets or sets the current GameTags. This is a comma seperated list of tags for this server.
/// When querying the server list you can filter by these tags.
/// </summary>
public static string GameTags
{
get = > _gametags ;
2019-05-27 15:24:43 +01:00
set
{
if ( _gametags = = value ) return ;
Internal . SetGameTags ( value ) ;
_gametags = value ;
}
2019-04-15 20:54:50 +01:00
}
private static string _gametags = "" ;
/// <summary>
/// Log onto Steam anonymously.
/// </summary>
public static void LogOnAnonymous ( )
{
Internal . LogOnAnonymous ( ) ;
ForceHeartbeat ( ) ;
}
2019-05-27 15:24:19 +01:00
/// <summary>
/// Log onto Steam anonymously.
/// </summary>
public static void LogOff ( )
{
Internal . LogOff ( ) ;
}
2019-04-15 20:54:50 +01:00
/// <summary>
/// Returns true if the server is connected and registered with the Steam master server
/// You should have called LogOnAnonymous etc on startup.
/// </summary>
public static bool LoggedOn = > Internal . BLoggedOn ( ) ;
/// <summary>
/// To the best of its ability this tries to get the server's
/// current public ip address. Be aware that this is likely to return
/// null for the first few seconds after initialization.
/// </summary>
2020-02-22 20:23:19 +00:00
public static System . Net . IPAddress PublicIp = > Internal . GetPublicIP ( ) ;
2019-04-15 20:54:50 +01:00
/// <summary>
/// Enable or disable heartbeats, which are sent regularly to the master server.
/// Enabled by default.
/// </summary>
public static bool AutomaticHeartbeats
{
set { Internal . EnableHeartbeats ( value ) ; }
}
/// <summary>
/// Set heartbeat interval, if automatic heartbeats are enabled.
/// You can leave this at the default.
/// </summary>
public static int AutomaticHeartbeatRate
{
set { Internal . SetHeartbeatInterval ( value ) ; }
}
/// <summary>
/// Force send a heartbeat to the master server instead of waiting
/// for the next automatic update (if you've left them enabled)
/// </summary>
public static void ForceHeartbeat ( )
{
Internal . ForceHeartbeat ( ) ;
}
/// <summary>
/// Update this connected player's information. You should really call this
/// any time a player's name or score changes. This keeps the information shown
/// to server queries up to date.
/// </summary>
2019-05-06 15:45:01 +01:00
public static void UpdatePlayer ( SteamId steamid , string name , int score )
2019-04-15 20:54:50 +01:00
{
Internal . BUpdateUserData ( steamid , name , ( uint ) score ) ;
}
static Dictionary < string , string > KeyValue = new Dictionary < string , string > ( ) ;
/// <summary>
/// Sets a Key Value. These can be anything you like, and are accessible
/// when querying servers from the server list.
///
/// Information describing gamemodes are common here.
/// </summary>
public static void SetKey ( string Key , string Value )
{
if ( KeyValue . ContainsKey ( Key ) )
{
if ( KeyValue [ Key ] = = Value )
return ;
KeyValue [ Key ] = Value ;
}
else
{
KeyValue . Add ( Key , Value ) ;
}
Internal . SetKeyValue ( Key , Value ) ;
}
2019-05-27 15:24:28 +01:00
/// <summary>
/// Remove all key values
/// </summary>
public static void ClearKeys ( )
{
KeyValue . Clear ( ) ;
Internal . ClearAllKeyValues ( ) ;
}
2019-04-15 20:54:50 +01:00
/// <summary>
/// Start authorizing a ticket. This user isn't authorized yet. Wait for a call to OnAuthChange.
/// </summary>
2019-05-06 15:45:01 +01:00
public static unsafe bool BeginAuthSession ( byte [ ] data , SteamId steamid )
2019-04-15 20:54:50 +01:00
{
fixed ( byte * p = data )
{
var result = Internal . BeginAuthSession ( ( IntPtr ) p , data . Length , steamid ) ;
2019-04-16 12:17:24 +01:00
if ( result = = BeginAuthResult . OK )
2019-04-15 20:54:50 +01:00
return true ;
return false ;
}
}
/// <summary>
/// Forget this guy. They're no longer in the game.
/// </summary>
2019-05-06 15:45:01 +01:00
public static void EndSession ( SteamId steamid )
2019-04-15 20:54:50 +01:00
{
Internal . EndAuthSession ( steamid ) ;
}
/// <summary>
/// If true, Steam wants to send a packet. You should respond by sending
/// this packet in an unconnected way to the returned Address and Port.
/// </summary>
/// <param name="packet">Packet to send. The Data passed is pooled - so use it immediately.</param>
/// <returns>True if we want to send a packet</returns>
public static unsafe bool GetOutgoingPacket ( out OutgoingPacket packet )
{
var buffer = Helpers . TakeBuffer ( 1024 * 32 ) ;
packet = new OutgoingPacket ( ) ;
fixed ( byte * ptr = buffer )
{
uint addr = 0 ;
ushort port = 0 ;
var size = Internal . GetNextOutgoingPacket ( ( IntPtr ) ptr , buffer . Length , ref addr , ref port ) ;
if ( size = = 0 )
return false ;
packet . Size = size ;
packet . Data = buffer ;
packet . Address = addr ;
packet . Port = port ;
return true ;
}
}
/// <summary>
/// We have received a server query on our game port. Pass it to Steam to handle.
/// </summary>
2019-04-29 15:47:56 +01:00
public static unsafe void HandleIncomingPacket ( byte [ ] data , int size , uint address , ushort port )
2019-04-15 20:54:50 +01:00
{
fixed ( byte * ptr = data )
{
2019-05-30 08:15:01 +01:00
HandleIncomingPacket ( ( IntPtr ) ptr , size , address , port ) ;
2019-04-15 20:54:50 +01:00
}
}
2019-05-30 08:15:01 +01:00
/// <summary>
/// We have received a server query on our game port. Pass it to Steam to handle.
/// </summary>
public static unsafe void HandleIncomingPacket ( IntPtr ptr , int size , uint address , ushort port )
{
Internal . HandleIncomingPacket ( ptr , size , address , port ) ;
2019-12-04 11:15:58 +00:00
}
/// <summary>
/// Does the user own this app (which could be DLC)
/// </summary>
public static UserHasLicenseForAppResult UserHasLicenseForApp ( SteamId steamid , AppId appid )
{
return Internal . UserHasLicenseForApp ( steamid , appid ) ;
2019-05-30 08:15:01 +01:00
}
2019-04-15 20:54:50 +01:00
}
}