Skip to content

Commit 907e1ad

Browse files
committed
Add async API's
1 parent 429e538 commit 907e1ad

File tree

2 files changed

+99
-21
lines changed

2 files changed

+99
-21
lines changed

SteamQueryNet/SteamQueryNet/ServerQuery.cs

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
using SteamQueryNet.Utils;
33
using System;
44
using System.Collections.Generic;
5+
using System.Globalization;
56
using System.Linq;
67
using System.Net;
78
using System.Net.NetworkInformation;
89
using System.Net.Sockets;
910
using System.Reflection;
1011
using System.Runtime.InteropServices;
1112
using System.Text;
13+
using System.Threading;
14+
using System.Threading.Tasks;
1215

1316
namespace SteamQueryNet
1417
{
@@ -18,7 +21,14 @@ public interface IServerQuery
1821
/// <summary>
1922
/// Renews the server challenge code of the ServerQuery instance in order to be able to execute further operations.
2023
/// </summary>
21-
void RenewChallenge();
24+
/// <returns>The new created challenge.</returns>
25+
int RenewChallenge();
26+
27+
/// <summary>
28+
/// Renews the server challenge code of the ServerQuery instance in order to be able to execute further operations.
29+
/// </summary>
30+
/// <returns>The new created challenge.</returns>
31+
Task<int> RenewChallengeAsync();
2232

2333
/// <summary>
2434
/// Configures and Connects the created instance of SteamQuery UDP socket for Steam Server Query Operations.
@@ -34,19 +44,39 @@ public interface IServerQuery
3444
/// <returns>Serialized ServerInfo instance.</returns>
3545
ServerInfo GetServerInfo();
3646

47+
/// <summary>
48+
/// Requests and serializes the server information.
49+
/// </summary>
50+
/// <returns>Serialized ServerInfo instance.</returns>
51+
Task<ServerInfo> GetServerInfoAsync();
52+
3753
/// <summary>
3854
/// Requests and serializes the list of player information.
3955
/// </summary>
4056
/// <returns>Serialized list of Player instances.</returns>
4157
List<Player> GetPlayers();
4258

59+
/// <summary>
60+
/// Requests and serializes the list of player information.
61+
/// </summary>
62+
/// <returns>Serialized list of Player instances.</returns>
63+
Task<List<Player>> GetPlayersAsync();
64+
4365
/// <summary>
4466
/// Requests and serializes the list of rules defined by the server.
4567
/// Warning: CS:GO Rules reply is broken since update CSGO 1.32.3.0 (Feb 21, 2014).
4668
/// Before the update rules got truncated when exceeding MTU, after the update rules reply is not sent at all.
4769
/// </summary>
4870
/// <returns>Serialized list of Rule instances.</returns>
4971
List<Rule> GetRules();
72+
73+
/// <summary>
74+
/// Requests and serializes the list of rules defined by the server.
75+
/// Warning: CS:GO Rules reply is broken since update CSGO 1.32.3.0 (Feb 21, 2014).
76+
/// Before the update rules got truncated when exceeding MTU, after the update rules reply is not sent at all.
77+
/// </summary>
78+
/// <returns>Serialized list of Rule instances.</returns>
79+
Task<List<Rule>> GetRulesAsync();
5080
}
5181

5282
public class ServerQuery : IServerQuery, IDisposable
@@ -104,15 +134,15 @@ public IServerQuery Connect(string serverAddress, int port)
104134
}
105135

106136
/// <inheritdoc/>
107-
public ServerInfo GetServerInfo()
137+
public async Task<ServerInfo> GetServerInfoAsync()
108138
{
109139
const string requestPayload = "Source Engine Query\0";
110140
var sInfo = new ServerInfo
111141
{
112142
Ping = new Ping().Send(_ipEndpoint.Address).RoundtripTime
113143
};
114144

115-
byte[] response = SendRequest(RequestHeaders.A2S_INFO, Encoding.UTF8.GetBytes(requestPayload));
145+
byte[] response = await SendRequestAsync(RequestHeaders.A2S_INFO, Encoding.UTF8.GetBytes(requestPayload));
116146
if (response.Length > 0)
117147
{
118148
ExtractData(sInfo, response, nameof(sInfo.EDF), true);
@@ -122,24 +152,38 @@ public ServerInfo GetServerInfo()
122152
}
123153

124154
/// <inheritdoc/>
125-
public void RenewChallenge()
155+
public ServerInfo GetServerInfo()
156+
{
157+
return RunSync(GetServerInfoAsync);
158+
}
159+
160+
/// <inheritdoc/>
161+
public async Task<int> RenewChallengeAsync()
126162
{
127-
byte[] response = SendRequest(RequestHeaders.A2S_PLAYER, BitConverter.GetBytes(-1));
163+
byte[] response = await SendRequestAsync(RequestHeaders.A2S_PLAYER, BitConverter.GetBytes(-1));
128164
if (response.Length > 0)
129165
{
130166
_currentChallenge = BitConverter.ToInt32(response.Skip(RESPONSE_CODE_INDEX).Take(sizeof(int)).ToArray(), 0);
131167
}
168+
169+
return _currentChallenge;
132170
}
133171

134172
/// <inheritdoc/>
135-
public List<Player> GetPlayers()
173+
public int RenewChallenge()
174+
{
175+
return RunSync(RenewChallengeAsync);
176+
}
177+
178+
/// <inheritdoc/>
179+
public async Task<List<Player>> GetPlayersAsync()
136180
{
137181
if (_currentChallenge == 0)
138182
{
139-
RenewChallenge();
183+
await RenewChallengeAsync();
140184
}
141185

142-
byte[] response = SendRequest(RequestHeaders.A2S_PLAYER, BitConverter.GetBytes(_currentChallenge));
186+
byte[] response = await SendRequestAsync(RequestHeaders.A2S_PLAYER, BitConverter.GetBytes(_currentChallenge));
143187
if (response.Length > 0)
144188
{
145189
return ExtractListData<Player>(response);
@@ -151,14 +195,20 @@ public List<Player> GetPlayers()
151195
}
152196

153197
/// <inheritdoc/>
154-
public List<Rule> GetRules()
198+
public List<Player> GetPlayers()
199+
{
200+
return RunSync(GetPlayersAsync);
201+
}
202+
203+
/// <inheritdoc/>
204+
public async Task<List<Rule>> GetRulesAsync()
155205
{
156206
if (_currentChallenge == 0)
157207
{
158-
RenewChallenge();
208+
await RenewChallengeAsync();
159209
}
160210

161-
byte[] response = SendRequest(RequestHeaders.A2S_RULES, BitConverter.GetBytes(_currentChallenge));
211+
byte[] response = await SendRequestAsync(RequestHeaders.A2S_RULES, BitConverter.GetBytes(_currentChallenge));
162212
if (response.Length > 0)
163213
{
164214
var rls = ExtractListData<Rule>(response);
@@ -170,6 +220,12 @@ public List<Rule> GetRules()
170220
}
171221
}
172222

223+
/// <inheritdoc/>
224+
public List<Rule> GetRules()
225+
{
226+
return RunSync(GetRulesAsync);
227+
}
228+
173229
/// <summary>
174230
/// Disposes the object and its disposables.
175231
/// </summary>
@@ -247,11 +303,12 @@ private List<TObject> ExtractListData<TObject>(byte[] rawSource)
247303
return objectList;
248304
}
249305

250-
private byte[] SendRequest(byte requestHeader, byte[] payload = null)
306+
private async Task<byte[]> SendRequestAsync(byte requestHeader, byte[] payload = null)
251307
{
252308
var request = BuildRequest(requestHeader, payload);
253-
_client.Send(request, request.Length);
254-
return _client.Receive(ref _ipEndpoint);
309+
await _client.SendAsync(request, request.Length);
310+
UdpReceiveResult result = await _client.ReceiveAsync();
311+
return result.Buffer;
255312
}
256313

257314
private byte[] BuildRequest(byte headerCode, byte[] extraParams = null)
@@ -261,7 +318,9 @@ private byte[] BuildRequest(byte headerCode, byte[] extraParams = null)
261318
var request = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, headerCode };
262319

263320
// If we have any extra payload, concatenate those into our requestHeaders and return;
264-
return extraParams != null ? request.Concat(extraParams).ToArray() : request;
321+
return extraParams != null
322+
? request.Concat(extraParams).ToArray()
323+
: request;
265324
}
266325

267326
private IEnumerable<byte> ExtractData<TObject>(TObject objectRef, byte[] dataSource, string edfPropName = "", bool stripHeaders = false)
@@ -270,7 +329,9 @@ private IEnumerable<byte> ExtractData<TObject>(TObject objectRef, byte[] dataSou
270329
IEnumerable<byte> takenBytes = new List<byte>();
271330

272331
// We can be a good guy and ask for any extra jobs :)
273-
IEnumerable<byte> enumerableSource = stripHeaders ? dataSource.Skip(RESPONSE_HEADER_COUNT) : dataSource;
332+
IEnumerable<byte> enumerableSource = stripHeaders
333+
? dataSource.Skip(RESPONSE_HEADER_COUNT)
334+
: dataSource;
274335

275336
// We get every property that does not contain ParseCustom and NotParsable attributes on them to iterate through all and parse/assign their values.
276337
IEnumerable<PropertyInfo> propsOfObject = typeof(TObject).GetProperties()
@@ -320,14 +381,18 @@ private IEnumerable<byte> ExtractData<TObject>(TObject objectRef, byte[] dataSou
320381
else
321382
{
322383
// Is the property an Enum ? if yes we should be getting the underlying type since it might differ.
323-
Type typeOfProperty = property.PropertyType.IsEnum ? property.PropertyType.GetEnumUnderlyingType() : property.PropertyType;
384+
Type typeOfProperty = property.PropertyType.IsEnum
385+
? property.PropertyType.GetEnumUnderlyingType()
386+
: property.PropertyType;
324387

325388
// Extract the value and the size from the source.
326389
(object result, int size) = ExtractMarshalType(enumerableSource, typeOfProperty);
327390

328391
/* If the property is an enum we should parse it first then assign its value,
329392
* if not we can just give it to SetValue since it was converted by ExtractMarshalType already.*/
330-
property.SetValue(objectRef, property.PropertyType.IsEnum ? Enum.Parse(property.PropertyType, result.ToString()) : result);
393+
property.SetValue(objectRef, property.PropertyType.IsEnum
394+
? Enum.Parse(property.PropertyType, result.ToString())
395+
: result);
331396

332397
// Update the source by skipping the amount of bytes taken from the source.
333398
enumerableSource = enumerableSource.Skip(size);
@@ -338,6 +403,18 @@ private IEnumerable<byte> ExtractData<TObject>(TObject objectRef, byte[] dataSou
338403
return enumerableSource;
339404
}
340405

406+
private TResult RunSync<TResult>(Func<Task<TResult>> func)
407+
{
408+
var cultureUi = CultureInfo.CurrentUICulture;
409+
var culture = CultureInfo.CurrentCulture;
410+
return new TaskFactory().StartNew(() =>
411+
{
412+
Thread.CurrentThread.CurrentCulture = culture;
413+
Thread.CurrentThread.CurrentUICulture = cultureUi;
414+
return func();
415+
}).Unwrap().GetAwaiter().GetResult();
416+
}
417+
341418
private (object, int) ExtractMarshalType(IEnumerable<byte> source, Type type)
342419
{
343420
// Get the size of the given type.

SteamQueryNet/SteamQueryNet/SteamQueryNet.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<Authors>Cem YILMAZ</Authors>
66
<Company>Cem YILMAZ</Company>
7-
<Version>1.0.2</Version>
8-
<AssemblyVersion>1.0.0.2</AssemblyVersion>
7+
<Version>1.0.3</Version>
8+
<AssemblyVersion>1.0.0.3</AssemblyVersion>
99
<NeutralLanguage>en</NeutralLanguage>
1010
<PackageTags>steam,query,net,steamquery,steamquerynet</PackageTags>
11-
<FileVersion>1.0.0.2</FileVersion>
11+
<FileVersion>1.0.0.3</FileVersion>
12+
<PackageReleaseNotes>Add async API support</PackageReleaseNotes>
1213
</PropertyGroup>
1314

1415
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

0 commit comments

Comments
 (0)