Build 0.21.6.0

This commit is contained in:
Markus Isberg
2023-01-31 18:01:29 +02:00
parent 697ec52120
commit 25fa5a9552
145 changed files with 2317 additions and 1145 deletions
@@ -7,22 +7,25 @@ namespace Steamworks
public byte[] Data;
public uint Handle;
/// <summary>
/// Cancels a ticket.
/// You should cancel your ticket when you close the game or leave a server.
/// </summary>
public void Cancel()
{
if ( Handle != 0 )
{
SteamUser.Internal.CancelAuthTicket( Handle );
}
public bool Canceled { get; private set; }
Handle = 0;
Data = null;
}
/// <summary>
/// Cancels a ticket.
/// You should cancel your ticket when you close the game or leave a server.
/// </summary>
public void Cancel()
{
if (Handle != 0)
{
SteamUser.Internal.CancelAuthTicket(Handle);
}
public void Dispose()
Handle = 0;
Data = null;
Canceled = true;
}
public void Dispose()
{
Cancel();
}
@@ -2,7 +2,7 @@
The MIT License (MIT)
Copyright (c) 2013-2019 Riley Labrecque
Copyright (c) 2013-2022 Riley Labrecque
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -25,48 +25,60 @@ THE SOFTWARE.
**/
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace Steamworks
{
//-----------------------------------------------------------------------------
// Purpose: Callback interface for receiving responses after pinging an individual server
// Purpose: Callback interface for receiving responses after requesting rules
// details on a particular server.
//
// These callbacks all occur in response to querying an individual server
// via the ISteamMatchmakingServers()->PingServer() call below. If you are
// destructing an object that implements this interface then you should call
// via the ISteamMatchmakingServers()->ServerRules() call below. If you are
// destructing an object that implements this interface then you should call
// ISteamMatchmakingServers()->CancelServerQuery() passing in the handle to the query
// which is in progress. Failure to cancel in progress queries when destructing
// a callback handler may result in a crash when a callback later occurs.
//-----------------------------------------------------------------------------
public class SteamMatchmakingPingResponse
public class SteamMatchmakingRulesResponse
{
// Server has responded successfully and has updated data
public delegate void ServerResponded(Steamworks.Data.ServerInfo server);
// Got data on a rule on the server -- you'll get one of these per rule defined on
// the server you are querying
public delegate void RulesResponded(string pchRule, string pchValue);
// Server failed to respond to the ping request
public delegate void ServerFailedToRespond();
// The server failed to respond to the request for rule details
public delegate void RulesFailedToRespond();
private VTable m_VTable;
private IntPtr m_pVTable;
// The server has finished responding to the rule details request
// (ie, you won't get anymore RulesResponded callbacks)
public delegate void RulesRefreshComplete();
private readonly VTable m_VTable;
private readonly IntPtr m_pVTable;
private GCHandle m_pGCHandle;
private ServerResponded m_ServerResponded;
private ServerFailedToRespond m_ServerFailedToRespond;
private readonly RulesResponded m_RulesResponded;
private readonly RulesFailedToRespond m_RulesFailedToRespond;
private readonly RulesRefreshComplete m_RulesRefreshComplete;
public SteamMatchmakingPingResponse(ServerResponded onServerResponded, ServerFailedToRespond onServerFailedToRespond)
public SteamMatchmakingRulesResponse(
RulesResponded onRulesResponded,
RulesFailedToRespond onRulesFailedToRespond,
RulesRefreshComplete onRulesRefreshComplete)
{
if (onServerResponded == null || onServerFailedToRespond == null)
if (onRulesResponded == null || onRulesFailedToRespond == null || onRulesRefreshComplete == null)
{
throw new ArgumentNullException();
}
m_ServerResponded = onServerResponded;
m_ServerFailedToRespond = onServerFailedToRespond;
m_RulesResponded = onRulesResponded;
m_RulesFailedToRespond = onRulesFailedToRespond;
m_RulesRefreshComplete = onRulesRefreshComplete;
m_VTable = new VTable()
{
m_VTServerResponded = InternalOnServerResponded,
m_VTServerFailedToRespond = InternalOnServerFailedToRespond,
m_VTRulesResponded = InternalOnRulesResponded,
m_VTRulesFailedToRespond = InternalOnRulesFailedToRespond,
m_VTRulesRefreshComplete = InternalOnRulesRefreshComplete
};
m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VTable)));
Marshal.StructureToPtr(m_VTable, m_pVTable, false);
@@ -74,21 +86,7 @@ namespace Steamworks
m_pGCHandle = GCHandle.Alloc(m_pVTable, GCHandleType.Pinned);
}
private Data.HServerQuery hserverPing = 0;
public bool QueryActive { get { return hserverPing != 0; } }
public void Cancel()
{
if (hserverPing != 0) { ServerList.Base.Internal.CancelServerQuery(hserverPing); }
hserverPing = 0;
}
public void HQueryPing(IPAddress ip, int port)
{
hserverPing = ServerList.Base.Internal.PingServer(ip.IpToInt32(), (ushort)port, (IntPtr)this);
}
~SteamMatchmakingPingResponse()
~SteamMatchmakingRulesResponse()
{
if (m_pVTable != IntPtr.Zero)
{
@@ -102,48 +100,69 @@ namespace Steamworks
}
#if NOTHISPTR
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void InternalServerResponded(gameserveritem_t server);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void InternalServerFailedToRespond();
private void InternalOnServerResponded(gameserveritem_t server) {
m_ServerResponded(server);
}
private void InternalOnServerFailedToRespond() {
m_ServerFailedToRespond();
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void InternalRulesResponded(IntPtr pchRule, IntPtr pchValue);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void InternalRulesFailedToRespond();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void InternalRulesRefreshComplete();
private void InternalOnRulesResponded(IntPtr pchRule, IntPtr pchValue)
{
m_RulesResponded(Helpers.MemoryToString(pchRule), Helpers.MemoryToString(pchValue));
}
private void InternalOnRulesFailedToRespond()
{
m_RulesFailedToRespond();
}
private void InternalOnRulesRefreshComplete()
{
m_RulesRefreshComplete();
}
#else
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void InternalServerResponded(IntPtr thisptr, Steamworks.Data.gameserveritem_t server);
public delegate void InternalRulesResponded(IntPtr thisptr, IntPtr pchRule, IntPtr pchValue);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void InternalServerFailedToRespond(IntPtr thisptr);
private void InternalOnServerResponded(IntPtr thisptr, Steamworks.Data.gameserveritem_t server)
{
hserverPing = 0;
public delegate void InternalRulesFailedToRespond(IntPtr thisptr);
m_ServerResponded(Steamworks.Data.ServerInfo.From(server));
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate void InternalRulesRefreshComplete(IntPtr thisptr);
private void InternalOnRulesResponded(IntPtr thisptr, IntPtr pchRule, IntPtr pchValue)
{
m_RulesResponded(Helpers.MemoryToString(pchRule), Helpers.MemoryToString(pchValue));
}
private void InternalOnServerFailedToRespond(IntPtr thisptr)
{
hserverPing = 0;
m_ServerFailedToRespond();
private void InternalOnRulesFailedToRespond(IntPtr thisptr)
{
m_RulesFailedToRespond();
}
private void InternalOnRulesRefreshComplete(IntPtr thisptr)
{
m_RulesRefreshComplete();
}
#endif
[StructLayout(LayoutKind.Sequential)]
private class VTable
{
[NonSerialized]
[MarshalAs(UnmanagedType.FunctionPtr)]
public InternalServerResponded m_VTServerResponded;
[NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)]
public InternalRulesResponded m_VTRulesResponded;
[NonSerialized]
[MarshalAs(UnmanagedType.FunctionPtr)]
public InternalServerFailedToRespond m_VTServerFailedToRespond;
[NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)]
public InternalRulesFailedToRespond m_VTRulesFailedToRespond;
[NonSerialized] [MarshalAs(UnmanagedType.FunctionPtr)]
public InternalRulesRefreshComplete m_VTRulesRefreshComplete;
}
public static explicit operator System.IntPtr(SteamMatchmakingPingResponse that)
public static explicit operator System.IntPtr(SteamMatchmakingRulesResponse that)
{
return that.m_pGCHandle.AddrOfPinnedObject();
}
@@ -1,209 +1,76 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Steamworks.Data;
namespace Steamworks
{
internal static class SourceServerQuery
{
private static readonly byte[] A2S_SERVERQUERY_GETCHALLENGE = { 0x55, 0xFF, 0xFF, 0xFF, 0xFF };
// private static readonly byte A2S_PLAYER = 0x55;
private const byte A2S_RULES = 0x56;
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)
{
lastRequestId++;
currId = lastRequestId;
activeRequests.Add(currId);
}
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
{
lock (activeRequests)
{
activeRequests.Remove(currId);
}
}
private enum Status
{
Pending,
Failure,
Success
}
static async Task<Dictionary<string, string>> GetRules( UdpClient client )
private static readonly HashSet<SteamMatchmakingRulesResponse> ruleResponseHandlers
= new HashSet<SteamMatchmakingRulesResponse>();
internal static async Task<Dictionary<string, string>> GetRules(Steamworks.Data.ServerInfo server)
{
var challengeBytes = await GetChallengeData( client );
challengeBytes[0] = A2S_RULES;
await Send( client, challengeBytes );
var ruleData = await Receive( client );
Status status = Status.Pending;
var rules = new Dictionary<string, string>();
using ( var br = new BinaryReader( new MemoryStream( ruleData ) ) )
{
if ( br.ReadByte() != 0x45 )
throw new Exception( "Invalid data received in response to A2S_RULES request" );
SteamMatchmakingRulesResponse responseHandler = null;
var numRules = br.ReadUInt16();
for ( int index = 0; index < numRules; index++ )
{
rules.Add( br.ReadNullTerminatedUTF8String(), br.ReadNullTerminatedUTF8String() );
}
void onRulesResponded(string key, string value)
=> rules.Add(key, value);
void onRulesFailToRespond()
{
finish(Status.Failure);
}
return rules;
}
static async Task<byte[]> Receive( UdpClient client )
{
byte[][] packets = null;
byte packetNumber = 0, packetCount = 1;
do
void onRulesRefreshComplete()
{
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 ) ) )
{
var header = br.ReadInt32();
if ( header == -1 )
{
var unsplitdata = new byte[buffer.Length - br.BaseStream.Position];
Buffer.BlockCopy( buffer, (int)br.BaseStream.Position, unsplitdata, 0, unsplitdata.Length );
return unsplitdata;
}
else if ( header == -2 )
{
int requestId = br.ReadInt32();
packetNumber = br.ReadByte();
packetCount = br.ReadByte();
int splitSize = br.ReadInt32();
}
else
{
throw new System.Exception( "Invalid Header" );
}
if ( packets == null ) packets = new byte[packetCount][];
var data = new byte[buffer.Length - br.BaseStream.Position];
Buffer.BlockCopy( buffer, (int)br.BaseStream.Position, data, 0, data.Length );
packets[packetNumber] = data;
}
finish(Status.Success);
}
while ( packets.Any( p => p == null ) );
var combinedData = Combine( packets );
return combinedData;
}
private static async Task<byte[]> GetChallengeData( UdpClient client )
{
await Send( client, A2S_SERVERQUERY_GETCHALLENGE );
var challengeData = await Receive( client );
if ( challengeData[0] != 0x41 )
throw new Exception( "Invalid Challenge" );
return challengeData;
}
static async Task Send( UdpClient client, byte[] message )
{
var sendBuffer = new byte[message.Length + 4];
sendBuffer[0] = 0xFF;
sendBuffer[1] = 0xFF;
sendBuffer[2] = 0xFF;
sendBuffer[3] = 0xFF;
Buffer.BlockCopy( message, 0, sendBuffer, 4, message.Length );
await client.SendAsync( sendBuffer, message.Length + 4 );
}
static byte[] Combine( byte[][] arrays )
{
var rv = new byte[arrays.Sum( a => a.Length )];
int offset = 0;
foreach ( byte[] array in arrays )
void finish(Status stat)
{
Buffer.BlockCopy( array, 0, rv, offset, array.Length );
offset += array.Length;
if (status == Status.Pending) { status = stat; }
var handler = responseHandler;
if (handler is null) { return; }
lock (ruleResponseHandlers)
{
ruleResponseHandlers.Remove(handler);
}
responseHandler = null;
}
return rv;
responseHandler = new SteamMatchmakingRulesResponse(
onRulesResponded,
onRulesFailToRespond,
onRulesRefreshComplete);
lock (ruleResponseHandlers)
{
ruleResponseHandlers.Add(responseHandler);
}
var query = SteamMatchmakingServers.Internal.ServerRules(
server.AddressRaw, (ushort)server.QueryPort, (IntPtr)responseHandler);
while (status == Status.Pending)
{
await Task.Delay(25);
}
SteamMatchmakingServers.Internal.CancelServerQuery(query);
return status == Status.Success ? rules : null;
}
};