(965c31410) v0.10.4.0
This commit is contained in:
@@ -7,86 +7,70 @@ namespace Steamworks
|
||||
{
|
||||
internal static class Helpers
|
||||
{
|
||||
public const int MaxStringSize = 1024 * 32;
|
||||
public const int MemoryBufferSize = 1024 * 32;
|
||||
|
||||
private static IntPtr[] MemoryPool = new IntPtr[]
|
||||
{
|
||||
Marshal.AllocHGlobal( MemoryBufferSize ),
|
||||
Marshal.AllocHGlobal( MemoryBufferSize ),
|
||||
Marshal.AllocHGlobal( MemoryBufferSize ),
|
||||
Marshal.AllocHGlobal( MemoryBufferSize )
|
||||
};
|
||||
|
||||
private static object mutex = new object();
|
||||
private static IntPtr[] MemoryPool;
|
||||
private static int MemoryPoolIndex;
|
||||
|
||||
public static unsafe IntPtr TakeMemory()
|
||||
{
|
||||
IntPtr take = IntPtr.Zero;
|
||||
lock (mutex)
|
||||
lock ( MemoryPool )
|
||||
{
|
||||
if (MemoryPool == null)
|
||||
{
|
||||
//
|
||||
// The pool has 5 items. This should be safe because we shouldn't really
|
||||
// ever be using more than 2 memory pools
|
||||
//
|
||||
MemoryPool = new IntPtr[5];
|
||||
|
||||
for (int i = 0; i < MemoryPool.Length; i++)
|
||||
MemoryPool[i] = Marshal.AllocHGlobal(MaxStringSize);
|
||||
}
|
||||
|
||||
MemoryPoolIndex++;
|
||||
if (MemoryPoolIndex >= MemoryPool.Length)
|
||||
|
||||
if ( MemoryPoolIndex >= MemoryPool.Length )
|
||||
MemoryPoolIndex = 0;
|
||||
|
||||
take = MemoryPool[MemoryPoolIndex];
|
||||
var take = MemoryPool[MemoryPoolIndex];
|
||||
|
||||
((byte*)take)[0] = 0;
|
||||
}
|
||||
|
||||
return take;
|
||||
return take;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static byte[][] BufferPool;
|
||||
private static byte[][] BufferPool = new byte[4][];
|
||||
private static int BufferPoolIndex;
|
||||
private static object BufferMutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a buffer. This will get returned and reused later on.
|
||||
/// We shouldn't really be using this anymore.
|
||||
/// </summary>
|
||||
public static byte[] TakeBuffer( int minSize )
|
||||
{
|
||||
int bufferPoolIndex;
|
||||
lock (BufferMutex)
|
||||
lock ( BufferPool )
|
||||
{
|
||||
if (BufferPool == null)
|
||||
{
|
||||
//
|
||||
// The pool has 8 items.
|
||||
//
|
||||
BufferPool = new byte[8][];
|
||||
|
||||
for (int i = 0; i < BufferPool.Length; i++)
|
||||
BufferPool[i] = new byte[1024 * 128];
|
||||
}
|
||||
|
||||
BufferPoolIndex++;
|
||||
|
||||
if (BufferPoolIndex < 0 || BufferPoolIndex >= BufferPool.Length)
|
||||
if ( BufferPoolIndex >= BufferPool.Length )
|
||||
BufferPoolIndex = 0;
|
||||
|
||||
bufferPoolIndex = BufferPoolIndex;
|
||||
}
|
||||
if ( BufferPool[BufferPoolIndex] == null )
|
||||
BufferPool[BufferPoolIndex] = new byte[1024 * 256];
|
||||
|
||||
if ( BufferPool[bufferPoolIndex].Length < minSize )
|
||||
{
|
||||
BufferPool[bufferPoolIndex] = new byte[minSize + 1024];
|
||||
}
|
||||
if ( BufferPool[BufferPoolIndex].Length < minSize )
|
||||
{
|
||||
BufferPool[BufferPoolIndex] = new byte[minSize + 1024];
|
||||
}
|
||||
|
||||
return BufferPool[bufferPoolIndex];
|
||||
return BufferPool[BufferPoolIndex];
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe static string MemoryToString( IntPtr ptr )
|
||||
{
|
||||
var len = 0;
|
||||
|
||||
for( len = 0; len < MaxStringSize; len++ )
|
||||
for( len = 0; len < MemoryBufferSize; len++ )
|
||||
{
|
||||
if ( ((byte*)ptr)[len] == 0 )
|
||||
break;
|
||||
|
||||
@@ -13,32 +13,18 @@ namespace Steamworks
|
||||
#if PLATFORM_WIN64
|
||||
public const int StructPlatformPackSize = 8;
|
||||
public const string LibraryName = "steam_api64";
|
||||
public const CallingConvention MemberConvention = CallingConvention.Cdecl;
|
||||
#elif PLATFORM_WIN32
|
||||
public const int StructPlatformPackSize = 8;
|
||||
public const string LibraryName = "steam_api";
|
||||
public const CallingConvention MemberConvention = CallingConvention.ThisCall;
|
||||
#elif PLATFORM_POSIX32
|
||||
public const int StructPlatformPackSize = 4;
|
||||
public const string LibraryName = "libsteam_api";
|
||||
public const CallingConvention MemberConvention = CallingConvention.Cdecl;
|
||||
#elif PLATFORM_POSIX64
|
||||
public const int StructPlatformPackSize = 4;
|
||||
public const string LibraryName = "libsteam_api64";
|
||||
public const CallingConvention MemberConvention = CallingConvention.Cdecl;
|
||||
#elif PLATFORM_POSIX
|
||||
public const int StructPlatformPackSize = 4;
|
||||
public const string LibraryName = "libsteam_api";
|
||||
#endif
|
||||
|
||||
public const CallingConvention CC = CallingConvention.Cdecl;
|
||||
public const int StructPackSize = 4;
|
||||
|
||||
|
||||
|
||||
public static int MemoryOffset( int memLocation )
|
||||
{
|
||||
#if PLATFORM_64
|
||||
return memLocation;
|
||||
#else
|
||||
return memLocation / 2;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,27 +13,82 @@ namespace Steamworks
|
||||
{
|
||||
private static readonly byte[] A2S_SERVERQUERY_GETCHALLENGE = { 0x55, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
// private static readonly byte A2S_PLAYER = 0x55;
|
||||
private static readonly byte A2S_RULES = 0x56;
|
||||
private const byte A2S_RULES = 0x56;
|
||||
|
||||
internal static async Task<Dictionary<string, string>> GetRules( ServerInfo server )
|
||||
{
|
||||
try
|
||||
private static readonly Dictionary<IPEndPoint, Task<Dictionary<string, string>>> PendingQueries =
|
||||
new Dictionary<IPEndPoint, Task<Dictionary<string, string>>>();
|
||||
|
||||
private static HashSet<int> activeRequests = new HashSet<int>();
|
||||
private static int lastRequestId = 0;
|
||||
|
||||
internal static Task<Dictionary<string, string>> GetRules( ServerInfo server )
|
||||
{
|
||||
var endpoint = new IPEndPoint(server.Address, server.QueryPort);
|
||||
|
||||
lock (PendingQueries)
|
||||
{
|
||||
if (PendingQueries.TryGetValue(endpoint, out var pending))
|
||||
return pending;
|
||||
|
||||
var task = GetRulesImpl( endpoint )
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
lock (PendingQueries)
|
||||
{
|
||||
PendingQueries.Remove(endpoint);
|
||||
}
|
||||
|
||||
return t;
|
||||
})
|
||||
.Unwrap();
|
||||
|
||||
PendingQueries.Add(endpoint, task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<string, string>> GetRulesImpl( IPEndPoint endpoint )
|
||||
{
|
||||
int currId;
|
||||
lock (activeRequests)
|
||||
{
|
||||
var endpoint = new IPEndPoint( server.Address, server.QueryPort );
|
||||
|
||||
using ( var client = new UdpClient() )
|
||||
{
|
||||
client.Client.SendTimeout = 3000;
|
||||
client.Client.ReceiveTimeout = 3000;
|
||||
client.Connect( endpoint );
|
||||
|
||||
return await GetRules( client );
|
||||
}
|
||||
lastRequestId++;
|
||||
currId = lastRequestId;
|
||||
activeRequests.Add(currId);
|
||||
}
|
||||
catch ( System.Exception e )
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Yield();
|
||||
while (true)
|
||||
{
|
||||
lock (activeRequests)
|
||||
{
|
||||
if (!activeRequests.Any() || (currId - activeRequests.Min()) < 25) { break; }
|
||||
}
|
||||
await Task.Delay(25);
|
||||
}
|
||||
|
||||
using (var client = new UdpClient())
|
||||
{
|
||||
client.Client.SendTimeout = 3000;
|
||||
client.Client.ReceiveTimeout = 3000;
|
||||
client.Connect(endpoint);
|
||||
|
||||
return await GetRules(client);
|
||||
}
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
//Console.Error.WriteLine( e.Message );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Error.WriteLine( e.Message );
|
||||
return null;
|
||||
lock (activeRequests)
|
||||
{
|
||||
activeRequests.Remove(currId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,14 +109,14 @@ namespace Steamworks
|
||||
var numRules = br.ReadUInt16();
|
||||
for ( int index = 0; index < numRules; index++ )
|
||||
{
|
||||
rules.Add( br.ReadNullTerminatedUTF8String( readBuffer ), br.ReadNullTerminatedUTF8String( readBuffer ) );
|
||||
rules.Add( br.ReadNullTerminatedUTF8String(), br.ReadNullTerminatedUTF8String() );
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
static byte[] readBuffer = new byte[1024 * 8];
|
||||
|
||||
|
||||
static async Task<byte[]> Receive( UdpClient client )
|
||||
{
|
||||
@@ -70,8 +125,13 @@ namespace Steamworks
|
||||
|
||||
do
|
||||
{
|
||||
var result = await client.ReceiveAsync();
|
||||
var buffer = result.Buffer;
|
||||
Task<UdpReceiveResult> result = client.ReceiveAsync();
|
||||
await Task.WhenAny(result, Task.Delay(3000));
|
||||
if (!result.IsCompleted)
|
||||
{
|
||||
throw new Exception("Receive timed out");
|
||||
}
|
||||
var buffer = result.Result.Buffer;
|
||||
|
||||
using ( var br = new BinaryReader( new MemoryStream( buffer ) ) )
|
||||
{
|
||||
@@ -120,10 +180,10 @@ namespace Steamworks
|
||||
return challengeData;
|
||||
}
|
||||
|
||||
static byte[] sendBuffer = new byte[1024];
|
||||
|
||||
static async Task Send( UdpClient client, byte[] message )
|
||||
{
|
||||
var sendBuffer = new byte[message.Length + 4];
|
||||
|
||||
sendBuffer[0] = 0xFF;
|
||||
sendBuffer[1] = 0xFF;
|
||||
sendBuffer[2] = 0xFF;
|
||||
|
||||
@@ -11,103 +11,136 @@ namespace Steamworks
|
||||
{
|
||||
internal abstract class SteamInterface
|
||||
{
|
||||
public virtual IntPtr GetUserInterfacePointer() => IntPtr.Zero;
|
||||
public virtual IntPtr GetServerInterfacePointer() => IntPtr.Zero;
|
||||
public virtual IntPtr GetGlobalInterfacePointer() => IntPtr.Zero;
|
||||
|
||||
public IntPtr Self;
|
||||
public IntPtr VTable;
|
||||
public IntPtr SelfGlobal;
|
||||
public IntPtr SelfServer;
|
||||
public IntPtr SelfClient;
|
||||
|
||||
public virtual string InterfaceName => null;
|
||||
public bool IsValid => Self != IntPtr.Zero && VTable != IntPtr.Zero;
|
||||
public bool IsValid => Self != IntPtr.Zero;
|
||||
public bool IsServer { get; private set; }
|
||||
|
||||
public void Init()
|
||||
internal void SetupInterface( bool gameServer )
|
||||
{
|
||||
if ( SteamClient.IsValid )
|
||||
{
|
||||
InitClient();
|
||||
if ( Self != IntPtr.Zero )
|
||||
return;
|
||||
}
|
||||
|
||||
if ( SteamServer.IsValid )
|
||||
{
|
||||
InitServer();
|
||||
IsServer = gameServer;
|
||||
SelfGlobal = GetGlobalInterfacePointer();
|
||||
Self = SelfGlobal;
|
||||
|
||||
if ( Self != IntPtr.Zero )
|
||||
return;
|
||||
}
|
||||
|
||||
throw new System.Exception( "Trying to initialize Steam Interface but Steam not initialized" );
|
||||
}
|
||||
|
||||
public void InitClient()
|
||||
{
|
||||
|
||||
//
|
||||
// There's an issue for us using FindOrCreateUserInterface on Rust.
|
||||
// We have a different appid for our staging branch, but we use Rust's
|
||||
// appid so we can still test with the live data/setup. The issue is
|
||||
// if we run the staging branch and get interfaces using FindOrCreate
|
||||
// then some callbacks don't work. I assume this is because these interfaces
|
||||
// have already been initialized using the old appid pipe, but since I
|
||||
// can't see inside Steam this is just a gut feeling. Either way using
|
||||
// CreateInterface doesn't seem to have caused any fires, so we'll just use that.
|
||||
//
|
||||
|
||||
//
|
||||
// var pipe = SteamAPI.GetHSteamPipe();
|
||||
//
|
||||
|
||||
Self = SteamInternal.CreateInterface( InterfaceName );
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
if ( gameServer )
|
||||
{
|
||||
var user = SteamAPI.GetHSteamUser();
|
||||
Self = SteamInternal.FindOrCreateUserInterface( user, InterfaceName );
|
||||
SelfServer = GetServerInterfacePointer();
|
||||
Self = SelfServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelfClient = GetUserInterfacePointer();
|
||||
Self = SelfClient;
|
||||
}
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Couldn't find interface {InterfaceName}" );
|
||||
|
||||
VTable = Marshal.ReadIntPtr( Self, 0 );
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Invalid VTable for {InterfaceName}" );
|
||||
|
||||
InitInternals();
|
||||
SteamClient.WatchInterface( this );
|
||||
}
|
||||
|
||||
public void InitServer()
|
||||
{
|
||||
var user = SteamGameServer.GetHSteamUser();
|
||||
Self = SteamInternal.FindOrCreateGameServerInterface( user, InterfaceName );
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Couldn't find server interface {InterfaceName}" );
|
||||
|
||||
VTable = Marshal.ReadIntPtr( Self, 0 );
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Invalid VTable for server {InterfaceName}" );
|
||||
|
||||
InitInternals();
|
||||
SteamServer.WatchInterface( this );
|
||||
}
|
||||
|
||||
public virtual void InitUserless()
|
||||
{
|
||||
Self = SteamInternal.FindOrCreateUserInterface( 0, InterfaceName );
|
||||
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Couldn't find interface {InterfaceName}" );
|
||||
|
||||
VTable = Marshal.ReadIntPtr( Self, 0 );
|
||||
if ( Self == IntPtr.Zero )
|
||||
throw new System.Exception( $"Invalid VTable for {InterfaceName}" );
|
||||
|
||||
InitInternals();
|
||||
}
|
||||
|
||||
internal virtual void Shutdown()
|
||||
internal void ShutdownInterface()
|
||||
{
|
||||
Self = IntPtr.Zero;
|
||||
VTable = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SteamClass
|
||||
{
|
||||
internal abstract void InitializeInterface( bool server );
|
||||
internal abstract void DestroyInterface( bool server );
|
||||
}
|
||||
|
||||
public class SteamSharedClass<T> : SteamClass
|
||||
{
|
||||
internal static SteamInterface Interface => InterfaceClient ?? InterfaceServer;
|
||||
internal static SteamInterface InterfaceClient;
|
||||
internal static SteamInterface InterfaceServer;
|
||||
|
||||
internal override void InitializeInterface( bool server )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public abstract void InitInternals();
|
||||
internal virtual void SetInterface( bool server, SteamInterface iface )
|
||||
{
|
||||
if ( server )
|
||||
{
|
||||
InterfaceServer = iface;
|
||||
}
|
||||
|
||||
if ( !server )
|
||||
{
|
||||
InterfaceClient = iface;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void DestroyInterface( bool server )
|
||||
{
|
||||
if ( !server )
|
||||
{
|
||||
InterfaceClient = null;
|
||||
}
|
||||
|
||||
if ( server )
|
||||
{
|
||||
InterfaceServer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SteamClientClass<T> : SteamClass
|
||||
{
|
||||
internal static SteamInterface Interface;
|
||||
|
||||
internal override void InitializeInterface( bool server )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal virtual void SetInterface( bool server, SteamInterface iface )
|
||||
{
|
||||
if ( server )
|
||||
throw new System.NotSupportedException();
|
||||
|
||||
Interface = iface;
|
||||
}
|
||||
|
||||
internal override void DestroyInterface( bool server )
|
||||
{
|
||||
Interface = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class SteamServerClass<T> : SteamClass
|
||||
{
|
||||
internal static SteamInterface Interface;
|
||||
|
||||
internal override void InitializeInterface( bool server )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal virtual void SetInterface( bool server, SteamInterface iface )
|
||||
{
|
||||
if ( !server )
|
||||
throw new System.NotSupportedException();
|
||||
|
||||
Interface = iface;
|
||||
}
|
||||
|
||||
internal override void DestroyInterface( bool server )
|
||||
{
|
||||
Interface = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,7 +44,9 @@ namespace Steamworks
|
||||
|
||||
internal struct Utf8StringPointer
|
||||
{
|
||||
#pragma warning disable 649
|
||||
internal IntPtr ptr;
|
||||
#pragma warning restore 649
|
||||
|
||||
public unsafe static implicit operator string( Utf8StringPointer p )
|
||||
{
|
||||
|
||||
@@ -3,12 +3,29 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Steamworks
|
||||
{
|
||||
public static partial class Utility
|
||||
{
|
||||
static internal T ToType<T>( this IntPtr ptr )
|
||||
{
|
||||
if ( ptr == IntPtr.Zero )
|
||||
return default;
|
||||
|
||||
return (T)Marshal.PtrToStructure( ptr, typeof( T ) );
|
||||
}
|
||||
|
||||
static internal object ToType( this IntPtr ptr, System.Type t )
|
||||
{
|
||||
if ( ptr == IntPtr.Zero )
|
||||
return default;
|
||||
|
||||
return Marshal.PtrToStructure( ptr, t );
|
||||
}
|
||||
|
||||
static internal uint Swap( uint x )
|
||||
{
|
||||
return ((x & 0x000000ff) << 24) +
|
||||
@@ -80,20 +97,22 @@ namespace Steamworks
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedUTF8String( this BinaryReader br, byte[] buffer = null )
|
||||
static readonly byte[] readBuffer = new byte[1024 * 8];
|
||||
|
||||
public static string ReadNullTerminatedUTF8String( this BinaryReader br )
|
||||
{
|
||||
if ( buffer == null )
|
||||
buffer = new byte[1024];
|
||||
|
||||
byte chr;
|
||||
int i = 0;
|
||||
while ( (chr = br.ReadByte()) != 0 && i < buffer.Length )
|
||||
lock ( readBuffer )
|
||||
{
|
||||
buffer[i] = chr;
|
||||
i++;
|
||||
}
|
||||
byte chr;
|
||||
int i = 0;
|
||||
while ( (chr = br.ReadByte()) != 0 && i < readBuffer.Length )
|
||||
{
|
||||
readBuffer[i] = chr;
|
||||
i++;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString( buffer, 0, i );
|
||||
return Encoding.UTF8.GetString( readBuffer, 0, i );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user