diff --git a/Facepunch.Steamworks.Test/Client/Friends.cs b/Facepunch.Steamworks.Test/Client/Friends.cs index 9604516..b580f9f 100644 --- a/Facepunch.Steamworks.Test/Client/Friends.cs +++ b/Facepunch.Steamworks.Test/Client/Friends.cs @@ -4,6 +4,8 @@ namespace Facepunch.Steamworks.Test { + [DeploymentItem( "FacepunchSteamworksApi.dll" )] + [DeploymentItem( "steam_appid.txt" )] [TestClass] public class Friends { diff --git a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj index 25e9e2b..c87f1b0 100644 --- a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj +++ b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj @@ -92,6 +92,7 @@ + diff --git a/Facepunch.Steamworks.Test/Server/Server.cs b/Facepunch.Steamworks.Test/Server/Server.cs new file mode 100644 index 0000000..0d85f9c --- /dev/null +++ b/Facepunch.Steamworks.Test/Server/Server.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Facepunch.Steamworks.Test +{ + [DeploymentItem( "FacepunchSteamworksApi.dll" )] + [DeploymentItem( "steam_appid.txt" )] + [DeploymentItem( "tier0_s.dll" )] + [DeploymentItem( "vstdlib_s.dll" )] + [DeploymentItem( "steamclient.dll" )] + [TestClass] + public class Server + { + [TestMethod] + public void Init() + { + using ( var server = new Facepunch.Steamworks.Server( 252490, 0, 20216, 20816, 20817, false, "VersionString" ) ) + { + Assert.IsTrue( server.Valid ); + } + } + } +} diff --git a/Facepunch.Steamworks.Test/bin/Debug/steam_api.dll b/Facepunch.Steamworks.Test/bin/Debug/steam_api.dll new file mode 100644 index 0000000..535ba15 Binary files /dev/null and b/Facepunch.Steamworks.Test/bin/Debug/steam_api.dll differ diff --git a/Facepunch.Steamworks.Test/bin/Debug/steamclient.dll b/Facepunch.Steamworks.Test/bin/Debug/steamclient.dll new file mode 100644 index 0000000..7f00869 Binary files /dev/null and b/Facepunch.Steamworks.Test/bin/Debug/steamclient.dll differ diff --git a/Facepunch.Steamworks.Test/bin/Debug/tier0_s.dll b/Facepunch.Steamworks.Test/bin/Debug/tier0_s.dll new file mode 100644 index 0000000..8ef8a75 Binary files /dev/null and b/Facepunch.Steamworks.Test/bin/Debug/tier0_s.dll differ diff --git a/Facepunch.Steamworks.Test/bin/Debug/vstdlib_s.dll b/Facepunch.Steamworks.Test/bin/Debug/vstdlib_s.dll new file mode 100644 index 0000000..00901fa Binary files /dev/null and b/Facepunch.Steamworks.Test/bin/Debug/vstdlib_s.dll differ diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index 9bc66e3..0b60b3e 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -7,8 +7,6 @@ namespace Facepunch.Steamworks { public partial class Client : IDisposable { - - internal class Internal : IDisposable { private uint _hpipe; diff --git a/Facepunch.Steamworks/Facepunch.Steamworks.csproj b/Facepunch.Steamworks/Facepunch.Steamworks.csproj index 3a9c050..d70f83a 100644 --- a/Facepunch.Steamworks/Facepunch.Steamworks.csproj +++ b/Facepunch.Steamworks/Facepunch.Steamworks.csproj @@ -131,8 +131,11 @@ + + + + - diff --git a/Facepunch.Steamworks/Interop/steam_api_interop.cs b/Facepunch.Steamworks/Interop/steam_api_interop.cs index 01c332a..cf49dbe 100644 --- a/Facepunch.Steamworks/Interop/steam_api_interop.cs +++ b/Facepunch.Steamworks/Interop/steam_api_interop.cs @@ -15,6 +15,28 @@ namespace Valve.Interop internal class NativeEntrypoints { + internal class Extended + { + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl )] + public static extern bool SteamInternal_GameServer_Init( uint unIP, ushort usSteamPort, ushort usGamePort, ushort usQueryPort, int eServerMode, string pchVersionString ); + + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr SteamInternal_CreateInterface( string ver ); + + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl )] + internal static extern uint SteamGameServer_GetHSteamUser(); + + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl )] + internal static extern uint SteamGameServer_GetHSteamPipe(); + + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl )] + internal static extern void SteamGameServer_Shutdown(); + + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl )] + internal static extern void SteamGameServer_RunCallbacks(); + + } + [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SteamAPI_ISteamClient_CreateSteamPipe" )] internal static extern uint SteamAPI_ISteamClient_CreateSteamPipe( IntPtr instancePtr ); [DllImportAttribute( "FacepunchSteamworksApi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SteamAPI_ISteamClient_BReleaseSteamPipe" )] @@ -2416,7 +2438,7 @@ internal override ISteamGameServer GetISteamGameServer( uint hSteamUser, uint hS { CheckIfUsable(); IntPtr result = NativeEntrypoints.SteamAPI_ISteamClient_GetISteamGameServer(m_pSteamClient,hSteamUser,hSteamPipe,pchVersion); - return (ISteamGameServer)Marshal.PtrToStructure( result, typeof( ISteamGameServer ) ); + return new CSteamGameServer( result ); } internal override void SetLocalIPBinding( uint unIP, char usPort ) { @@ -2463,7 +2485,7 @@ internal override ISteamGameServerStats GetISteamGameServerStats( uint hSteamuse { CheckIfUsable(); IntPtr result = NativeEntrypoints.SteamAPI_ISteamClient_GetISteamGameServerStats(m_pSteamClient,hSteamuser,hSteamPipe,pchVersion); - return (ISteamGameServerStats)Marshal.PtrToStructure( result, typeof( ISteamGameServerStats ) ); + return new CSteamGameServerStats( result ); } internal override ISteamApps GetISteamApps( uint hSteamUser, uint hSteamPipe, string pchVersion ) { @@ -2510,7 +2532,7 @@ internal override ISteamHTTP GetISteamHTTP( uint hSteamuser, uint hSteamPipe, st { CheckIfUsable(); IntPtr result = NativeEntrypoints.SteamAPI_ISteamClient_GetISteamHTTP(m_pSteamClient,hSteamuser,hSteamPipe,pchVersion); - return (ISteamHTTP)Marshal.PtrToStructure( result, typeof( ISteamHTTP ) ); + return new CSteamHTTP( result ); } internal override ISteamUnifiedMessages GetISteamUnifiedMessages( uint hSteamuser, uint hSteamPipe, string pchVersion ) { @@ -2528,7 +2550,7 @@ internal override ISteamUGC GetISteamUGC( uint hSteamUser, uint hSteamPipe, stri { CheckIfUsable(); IntPtr result = NativeEntrypoints.SteamAPI_ISteamClient_GetISteamUGC(m_pSteamClient,hSteamUser,hSteamPipe,pchVersion); - return (ISteamUGC)Marshal.PtrToStructure( result, typeof( ISteamUGC ) ); + return new CSteamUGC( result ); } internal override ISteamAppList GetISteamAppList( uint hSteamUser, uint hSteamPipe, string pchVersion ) { diff --git a/Facepunch.Steamworks/Server.cs b/Facepunch.Steamworks/Server.cs new file mode 100644 index 0000000..b99976b --- /dev/null +++ b/Facepunch.Steamworks/Server.cs @@ -0,0 +1,239 @@ + + +using System; +using System.Runtime.InteropServices; + +namespace Facepunch.Steamworks +{ + public partial class Server : IDisposable + { + internal class Internal : IDisposable + { + internal Valve.Steamworks.ISteamClient client; + internal Valve.Steamworks.ISteamGameServer gameServer; + internal Valve.Steamworks.ISteamUtils utils; + internal Valve.Steamworks.ISteamNetworking networking; + internal Valve.Steamworks.ISteamGameServerStats stats; + internal Valve.Steamworks.ISteamHTTP http; + internal Valve.Steamworks.ISteamInventory inventory; + internal Valve.Steamworks.ISteamUGC ugc; + internal Valve.Steamworks.ISteamApps apps; + + internal bool Init() + { + var user = Valve.Interop.NativeEntrypoints.Extended.SteamGameServer_GetHSteamUser(); + var pipe = Valve.Interop.NativeEntrypoints.Extended.SteamGameServer_GetHSteamPipe(); + if ( pipe == 0 ) + return false; + + var clientPtr = Valve.Interop.NativeEntrypoints.Extended.SteamInternal_CreateInterface( "SteamClient017" ); + if ( clientPtr == IntPtr.Zero ) + { + throw new System.Exception( "Steam Server: Couldn't load SteamClient017" ); + } + + client = new Valve.Steamworks.CSteamClient( clientPtr ); + + + gameServer = client.GetISteamGameServer( user, pipe, "SteamGameServer012" ); + + if ( gameServer.GetIntPtr() == IntPtr.Zero ) + { + gameServer = null; + throw new System.Exception( "Steam Server: Couldn't load SteamGameServer012" ); + } + + utils = client.GetISteamUtils( pipe, "SteamUtils008" ); + networking = client.GetISteamNetworking( user, pipe, "SteamNetworking005" ); + stats = client.GetISteamGameServerStats( user, pipe, "SteamGameServerStats001" ); + http = client.GetISteamHTTP( user, pipe, "STEAMHTTP_INTERFACE_VERSION002" ); + inventory = client.GetISteamInventory( user, pipe, "STEAMINVENTORY_INTERFACE_V001" ); + ugc = client.GetISteamUGC( user, pipe, "STEAMUGC_INTERFACE_VERSION008" ); + + if ( ugc.GetIntPtr() == IntPtr.Zero ) + throw new System.Exception( "Steam Server: Couldn't load STEAMUGC_INTERFACE_VERSION008" ); + + apps = client.GetISteamApps( user, pipe, "STEAMAPPS_INTERFACE_VERSION008" ); + + if ( apps.GetIntPtr() == IntPtr.Zero ) + throw new System.Exception( "Steam Server: Couldn't load STEAMAPPS_INTERFACE_VERSION008" ); + + return true; + } + + public void Dispose() + { + if ( client != null ) + { + client.BShutdownIfAllPipesClosed(); + client = null; + } + } + } + + internal Internal native; + + /// + /// Current running program's AppId + /// + public uint AppId { get; private set; } + + /// + /// Current user's Username + /// + public string Username { get; private set; } + + /// + /// Current user's SteamId + /// + public ulong SteamId { get; private set; } + + public enum MessageType : int + { + Message = 0, + Warning = 1 + } + + /// + /// Called with a message from Steam + /// + public Action OnMessage; + + public Server( uint appId, uint IpAddress, ushort SteamPort, ushort GamePort, ushort QueryPort, bool Secure, string VersionString ) + { + if ( !Valve.Interop.NativeEntrypoints.Extended.SteamInternal_GameServer_Init( IpAddress, SteamPort, GamePort, QueryPort, Secure ? 3 : 2 , VersionString ) ) + { + return; + } + + //Valve.Steamworks.SteamAPI.Init( appId ); + + native = new Internal(); + + // + // Get other interfaces + // + if ( !native.Init() ) + { + native.Dispose(); + native = null; + return; + } + + // + // Set up warning hook callback + // + SteamAPIWarningMessageHook ptr = InternalOnWarning; + // native.client.SetWarningMessageHook( Marshal.GetFunctionPointerForDelegate( ptr ) ); + + // + // Cache common, unchanging info + // + AppId = appId; + + // + // Initial settings + // + native.gameServer.EnableHeartbeats( true ); + MaxPlayers = 32; + BotCount = 0; + MapName = "unset"; + + // + // Run update, first call does some initialization + // + Update(); + } + + public void Dispose() + { + if ( native != null) + { + native.Dispose(); + native = null; + } + } + + [UnmanagedFunctionPointer( CallingConvention.Cdecl )] + public delegate void SteamAPIWarningMessageHook( int nSeverity, System.Text.StringBuilder pchDebugText ); + + private void InternalOnWarning( int nSeverity, System.Text.StringBuilder text ) + { + if ( OnMessage != null ) + { + OnMessage( ( MessageType)nSeverity, text.ToString() ); + } + } + + internal event Action OnUpdate; + + /// + /// Should be called at least once every frame + /// + public void Update() + { + if ( native == null ) + return; + + Valve.Interop.NativeEntrypoints.Extended.SteamGameServer_RunCallbacks(); + // Voice.Update(); + // Inventory.Update(); + // Networking.Update(); + + if ( OnUpdate != null ) + OnUpdate(); + } + + public bool Valid + { + get { return native != null; } + } + + internal Action InstallCallback( int type, Delegate action ) + { + var del = Marshal.GetFunctionPointerForDelegate( action ); + + // var ptr = Marshal.GetFunctionPointerForDelegate( action ); + // Valve.Steamworks.SteamAPI.RegisterCallback( del, type ); + + // Valve.Steamworks.SteamAPI.UnregisterCallback( del ); + + //return () => Valve.Steamworks.SteamAPI.UnregisterCallback( ptr ); + return null; + } + + + + /// + /// Gets or sets the current MaxPlayers. + /// This doesn't enforce any kind of limit, it just updates the master server. + /// + public int MaxPlayers + { + get { return _maxplayers; } + set { if ( _maxplayers == value ) return; native.gameServer.SetMaxPlayerCount( value ); _maxplayers = value; } + } + private int _maxplayers = 0; + + /// + /// Gets or sets the current BotCount. + /// This doesn't enforce any kind of limit, it just updates the master server. + /// + public int BotCount + { + get { return _botcount; } + set { if ( _botcount == value ) return; native.gameServer.SetBotPlayerCount( value ); _botcount = value; } + } + private int _botcount = 0; + + /// + /// Gets or sets the current Map Name. + /// + public string MapName + { + get { return _mapname; } + set { if ( _mapname == value ) return; native.gameServer.SetMapName( value ); _mapname = value; } + } + private string _mapname; + } +}