diff --git a/.gitignore b/.gitignore index 7c5c9fc..04e083e 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,9 @@ Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.Api.dll Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.dll Facepunch.Steamworks/bin/Release/Facepunch.Steamworks.dll *.opendb -*.db \ No newline at end of file +*.db +Facepunch.Steamworks.dll +Facepunch.Steamworks.Test.dll +*UnitTestFramework.dll +mscorlib.dll +*.nlp \ No newline at end of file diff --git a/Facepunch.Steamworks.Test/Client.cs b/Facepunch.Steamworks.Test/Client.cs index b9063f8..a6ca188 100644 --- a/Facepunch.Steamworks.Test/Client.cs +++ b/Facepunch.Steamworks.Test/Client.cs @@ -9,7 +9,7 @@ namespace Facepunch.Steamworks.Test public class Client { [TestMethod] - public void ClientInit() + public void Init() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -18,7 +18,7 @@ public void ClientInit() } [TestMethod] - public void ClientName() + public void Name() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -30,7 +30,7 @@ public void ClientName() } [TestMethod] - public void ClientSteamId() + public void SteamId() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -42,7 +42,7 @@ public void ClientSteamId() } [TestMethod] - public void ClientAuthSessionTicket() + public void AuthSessionTicket() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -59,7 +59,7 @@ public void ClientAuthSessionTicket() } [TestMethod] - public void ClientVoiceOptimalSampleRate() + public void VoiceOptimalSampleRate() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -69,7 +69,7 @@ public void ClientVoiceOptimalSampleRate() } [TestMethod] - public void ClientUpdate() + public void Update() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -82,7 +82,7 @@ public void ClientUpdate() } [TestMethod] - public void ClientGetVoice() + public void GetVoice() { using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) { @@ -110,5 +110,26 @@ public void ClientGetVoice() Console.Write( dataRead ); } } + + [TestMethod] + public void GetServers() + { + using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) + { + var query = client.ServerList.Test(); + + for ( int i = 0; i < 100 ; i++ ) + { + client.Update(); + System.Threading.Thread.Sleep( 5 ); + + if ( query.Finished ) + break; + } + + Console.WriteLine( "Responded: " + query.Responded.Count.ToString() ); + Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() ); + } + } } } diff --git a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj index d83c936..53bbb4a 100644 --- a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj +++ b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj @@ -34,6 +34,42 @@ prompt 4 + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + diff --git a/Facepunch.Steamworks/Client.ServerList.Request.cs b/Facepunch.Steamworks/Client.ServerList.Request.cs new file mode 100644 index 0000000..9537eb4 --- /dev/null +++ b/Facepunch.Steamworks/Client.ServerList.Request.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Valve.Steamworks; + +namespace Facepunch.Steamworks +{ + public partial class ServerList + { + public class Request : IDisposable + { + internal Client client; + internal uint Id; + + private IntPtr m_pVTable; + private GCHandle m_pGCHandle; + + public struct Server + { + public string Name { get; set; } + public int Ping { get; set; } + public string GameDir { get; set; } + public string Map { get; set; } + public string Description { get; set; } + public uint AppId { get; set; } + public int Players { get; set; } + public int MaxPlayers { get; set; } + public int BotPlayers { get; set; } + public bool Passworded { get; set; } + public bool Secure { get; set; } + public uint LastTimePlayed { get; set; } + public int Version { get; set; } + public string[] Tags { get; set; } + public ulong SteamId { get; set; } + + internal static Server FromSteam( gameserveritem_t item ) + { + return new Server() + { + Name = item.m_szServerName, + Ping = item.m_nPing, + GameDir = item.m_szGameDir, + Map = item.m_szMap, + Description = item.m_szGameDescription, + AppId = item.m_nAppID, + Players = item.m_nPlayers, + MaxPlayers = item.m_nMaxPlayers, + BotPlayers = item.m_nBotPlayers, + Passworded = item.m_bPassword, + Secure = item.m_bSecure, + LastTimePlayed = item.m_ulTimeLastPlayed, + Version = item.m_nServerVersion, + Tags = item.m_szGameTags.Split( ',' ), + SteamId = item.m_steamID + }; + } + } + + + public servernetadr_t m_NetAdr; + public int m_nPing; + [MarshalAs(UnmanagedType.I1)] + public bool m_bHadSuccessfulResponse; + [MarshalAs(UnmanagedType.I1)] + public bool m_bDoNotRefresh; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string m_szGameDir; //char[32] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string m_szMap; //char[32] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string m_szGameDescription; //char[64] + public uint m_nAppID; + public int m_nPlayers; + public int m_nMaxPlayers; + public int m_nBotPlayers; + [MarshalAs(UnmanagedType.I1)] + public bool m_bPassword; + [MarshalAs(UnmanagedType.I1)] + public bool m_bSecure; + public uint m_ulTimeLastPlayed; + public int m_nServerVersion; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string m_szServerName; //char[64] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string m_szGameTags; //char[128] + public ulong m_steamID; + + /// + /// A list of servers that responded. If you're only interested in servers that responded since you + /// last updated, then simply clear this list. + /// + public List Responded = new List(); + + /// + /// A list of servers that were in the master list but didn't respond. + /// + public List Unresponsive = new List(); + + /// + /// True when we have finished + /// + public bool Finished = false; + + internal Request() + { + // + // Create a fake vtable for Steam to respond to + // + var vt = new VTable() + { + responded = OnServerResponded, + nonresponsive = NonResponsive, + complete = Complete + }; + + m_pVTable = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( VTable ) ) ); + Marshal.StructureToPtr( vt, m_pVTable, false ); + m_pGCHandle = GCHandle.Alloc( m_pVTable, GCHandleType.Pinned ); + } + + ~Request() + { + Dispose(); + } + + /// + /// Disposing will end the query + /// + public void Dispose() + { + // + // Cancel the query if it's still running + // + if ( !Finished && Id > 0 ) + { + if ( client.Valid ) + client._servers.CancelQuery( Id ); + + Id = 0; + } + + // + // Release the pinned GC resources + // + if ( m_pVTable != IntPtr.Zero ) + { + Marshal.FreeHGlobal( m_pVTable ); + m_pVTable = IntPtr.Zero; + } + + if ( m_pGCHandle.IsAllocated ) + { + m_pGCHandle.Free(); + } + } + + private void Complete( IntPtr thisptr, uint RequestId, int response ) + { + if ( RequestId != Id ) + throw new Exception( "Request ID is invalid!" ); + + Finished = true; + Id = 0; + } + + private void NonResponsive( IntPtr thisptr, uint RequestId, int iServer ) + { + if ( RequestId != Id ) + throw new Exception( "Request ID is invalid!" ); + + var info = client._servers.GetServerDetails( Id, iServer ); + Unresponsive.Add( Server.FromSteam( info ) ); + } + + private void OnServerResponded( IntPtr thisptr, uint RequestId, int iServer ) + { + if ( RequestId != Id ) + throw new Exception( "Request ID is invalid!" ); + + var info = client._servers.GetServerDetails( Id, iServer ); + Responded.Add( Server.FromSteam( info ) ); + } + + internal IntPtr GetVTablePointer() + { + return m_pGCHandle.AddrOfPinnedObject(); + } + + [StructLayout( LayoutKind.Sequential )] + internal class VTable + { + [UnmanagedFunctionPointer( CallingConvention.ThisCall )] + internal delegate void InternalServerResponded( IntPtr thisptr, uint hRequest, int iServer ); + [UnmanagedFunctionPointer( CallingConvention.ThisCall )] + internal delegate void InternalServerFailedToRespond( IntPtr thisptr, uint hRequest, int iServer ); + [UnmanagedFunctionPointer( CallingConvention.ThisCall )] + internal delegate void InternalRefreshComplete( IntPtr thisptr, uint hRequest, int response ); + + [NonSerialized, MarshalAs(UnmanagedType.FunctionPtr)] + internal InternalServerResponded responded; + [NonSerialized, MarshalAs(UnmanagedType.FunctionPtr)] + internal InternalServerFailedToRespond nonresponsive; + [NonSerialized, MarshalAs(UnmanagedType.FunctionPtr)] + internal InternalRefreshComplete complete; + } + } + } +} diff --git a/Facepunch.Steamworks/Client.ServerList.cs b/Facepunch.Steamworks/Client.ServerList.cs index f014820..ad2ff2f 100644 --- a/Facepunch.Steamworks/Client.ServerList.cs +++ b/Facepunch.Steamworks/Client.ServerList.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using Valve.Steamworks; namespace Facepunch.Steamworks { @@ -21,9 +23,40 @@ public ServerList ServerList } } - public class ServerList + + + public partial class ServerList { internal Client client; + [StructLayout( LayoutKind.Sequential )] + private struct MatchPair + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string key; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string value; + } + + public unsafe Request Test() + { + var filters = new List(); + filters.Add( new MatchPair() { key = "gamedir", value = "rust" } ); + + var array = filters.ToArray(); + + //fixed ( void* a = array ) + { + + var request = new Request() + { + client = client + }; + + request.Id = client._servers.RequestInternetServerList( client.AppId, new IntPtr[] { }, request.GetVTablePointer() ); + + return request; + } + } } } diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index c85940f..6404ee3 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -18,7 +18,7 @@ public partial class Client : IDisposable /// /// Current running program's AppId /// - public int AppId; + public uint AppId; /// /// Current user's Username @@ -30,9 +30,9 @@ public partial class Client : IDisposable /// public ulong SteamId; - public Client( int appId ) + public Client( uint appId ) { - Valve.Steamworks.SteamAPI.Init( (uint) appId ); + Valve.Steamworks.SteamAPI.Init( appId ); _client = Valve.Steamworks.SteamAPI.SteamClient(); if ( _client.GetIntPtr() == IntPtr.Zero ) diff --git a/Facepunch.Steamworks/Facepunch.Steamworks.csproj b/Facepunch.Steamworks/Facepunch.Steamworks.csproj index ac414a1..bcf343e 100644 --- a/Facepunch.Steamworks/Facepunch.Steamworks.csproj +++ b/Facepunch.Steamworks/Facepunch.Steamworks.csproj @@ -34,6 +34,76 @@ true + + true + bin\Debug64\ + DEBUG;TRACE + true + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x64\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x64\Debug64\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + true + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Debug64\ + DEBUG;TRACE + true + full + x86 + prompt + MinimumRecommendedRules.ruleset + @@ -45,6 +115,7 @@ + diff --git a/Facepunch.Steamworks/steam_api_interop.cs b/Facepunch.Steamworks/steam_api_interop.cs index 0344964..a3fb9dd 100644 --- a/Facepunch.Steamworks/steam_api_interop.cs +++ b/Facepunch.Steamworks/steam_api_interop.cs @@ -1816,7 +1816,7 @@ internal abstract class ISteamMatchmakingRulesResponse internal abstract class ISteamMatchmakingServers { internal abstract IntPtr GetIntPtr(); - internal abstract uint RequestInternetServerList( uint iApp, IntPtr[] ppchFilters, ISteamMatchmakingServerListResponse pRequestServersResponse ); + internal abstract uint RequestInternetServerList( uint iApp, IntPtr[] ppchFilters, IntPtr pRequestServersResponse ); internal abstract uint RequestLANServerList( uint iApp, ISteamMatchmakingServerListResponse pRequestServersResponse ); internal abstract uint RequestFriendsServerList( uint iApp, IntPtr[] ppchFilters, ISteamMatchmakingServerListResponse pRequestServersResponse ); internal abstract uint RequestFavoritesServerList( uint iApp, IntPtr[] ppchFilters, ISteamMatchmakingServerListResponse pRequestServersResponse ); @@ -3775,10 +3775,10 @@ private void CheckIfUsable() throw new Exception( "Steam Pointer not configured" ); } } - internal override uint RequestInternetServerList( uint iApp, IntPtr[] ppchFilters, ISteamMatchmakingServerListResponse pRequestServersResponse ) + internal override uint RequestInternetServerList( uint iApp, IntPtr[] ppchFilters, IntPtr pRequestServersResponse ) { CheckIfUsable(); - uint result = NativeEntrypoints.SteamAPI_ISteamMatchmakingServers_RequestInternetServerList(m_pSteamMatchmakingServers,iApp,ppchFilters,(uint) ppchFilters.Length,pRequestServersResponse.GetIntPtr()); + uint result = NativeEntrypoints.SteamAPI_ISteamMatchmakingServers_RequestInternetServerList(m_pSteamMatchmakingServers,iApp,ppchFilters,(uint) ppchFilters.Length,pRequestServersResponse); return result; } internal override uint RequestLANServerList( uint iApp, ISteamMatchmakingServerListResponse pRequestServersResponse )