Skip to content

Commit

Permalink
Support World Opponent Network (WON) Query Protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
BattlefieldDuck committed Jan 17, 2024
1 parent 731ff9c commit c57712f
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 22 deletions.
52 changes: 30 additions & 22 deletions OpenGSQ/Protocols/Source.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,28 @@ public class Source : ProtocolBase
public override string FullName => "Source Engine Protocol";

/// <summary>
/// Source Engine Query Protocol<br />
/// See: <see href="https://developer.valvesoftware.com/wiki/Server_queries">https://developer.valvesoftware.com/wiki/Server_queries</see>
/// The byte array representing the A2S_INFO request.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="timeout"></param>
public Source(string address, int port = 27015, int timeout = 5000) : base(address, port, timeout)
protected byte[] A2S_INFO = new byte[] { 0x54 };

/// <summary>
/// The byte array representing the A2S_PLAYER request.
/// </summary>
protected byte[] A2S_PLAYER = new byte[] { 0x55 };

/// <summary>
/// The byte array representing the A2S_RULES request.
/// </summary>
protected byte[] A2S_RULES = new byte[] { 0x56 };


/// <summary>
/// Initializes a new instance of the Source class.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to. Default is 27015.</param>
/// <param name="timeout">The connection timeout in milliseconds. Default is 5 seconds.</param>
public Source(string host, int port = 27015, int timeout = 5000) : base(host, port, timeout)
{

}
Expand All @@ -40,7 +55,7 @@ public Source(string address, int port = 27015, int timeout = 5000) : base(addre
/// <exception cref="SocketException"></exception>
public async Task<IInfoResponse> GetInfo()
{
var responseData = await ConnectAndSendChallenge(QueryRequest.A2S_INFO);
var responseData = await ConnectAndSendChallenge(A2S_INFO);

using var br = new BinaryReader(new MemoryStream(responseData), Encoding.UTF8);
var header = br.ReadByte();
Expand Down Expand Up @@ -136,8 +151,8 @@ public async Task<IInfoResponse> GetInfo()
goldSource.Link = br.ReadStringEx();
goldSource.DownloadLink = br.ReadStringEx();
br.ReadByte();
goldSource.Version = br.ReadInt64();
goldSource.Size = br.ReadInt64();
goldSource.Version = br.ReadInt32();
goldSource.Size = br.ReadInt32();
goldSource.Type = br.ReadByte();
goldSource.DLL = br.ReadByte();
}
Expand All @@ -157,7 +172,7 @@ public async Task<IInfoResponse> GetInfo()
/// <exception cref="SocketException"></exception>
public async Task<List<Player>> GetPlayers()
{
var responseData = await ConnectAndSendChallenge(QueryRequest.A2S_PLAYER);
var responseData = await ConnectAndSendChallenge(A2S_PLAYER);

using var br = new BinaryReader(new MemoryStream(responseData), Encoding.UTF8);
var header = br.ReadByte();
Expand Down Expand Up @@ -204,7 +219,7 @@ public async Task<List<Player>> GetPlayers()
/// <exception cref="SocketException"></exception>
public async Task<Dictionary<string, string>> GetRules()
{
var responseData = await ConnectAndSendChallenge(QueryRequest.A2S_RULES);
var responseData = await ConnectAndSendChallenge(A2S_RULES);

using var br = new BinaryReader(new MemoryStream(responseData), Encoding.UTF8);
var header = br.ReadByte();
Expand All @@ -227,7 +242,7 @@ public async Task<Dictionary<string, string>> GetRules()
return rules;
}

private async Task<byte[]> ConnectAndSendChallenge(QueryRequest queryRequest)
private async Task<byte[]> ConnectAndSendChallenge(byte[] header)
{
using var udpClient = new UdpClient();

Expand All @@ -237,17 +252,17 @@ private async Task<byte[]> ConnectAndSendChallenge(QueryRequest queryRequest)
udpClient.Client.ReceiveTimeout = Timeout;

// Set up request base
byte[] requestBase = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, (byte)queryRequest };
byte[] requestBase = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }.Concat(header).ToArray();

if (queryRequest == QueryRequest.A2S_INFO)
if (header.SequenceEqual(A2S_INFO))
{
requestBase = requestBase.Concat(Encoding.Default.GetBytes("Source Engine Query\0")).ToArray();
}

// Set up request data
byte[] requestData = requestBase;

if (queryRequest != QueryRequest.A2S_INFO)
if (!header.SequenceEqual(A2S_INFO))
{
requestData = requestData.Concat(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }).ToArray();
}
Expand Down Expand Up @@ -406,13 +421,6 @@ private Environment GetEnvironment(byte environmentByte)
};
}

private enum QueryRequest : byte
{
A2S_INFO = 0x54,
A2S_PLAYER = 0x55,
A2S_RULES = 0x56,
}

private enum QueryResponse : byte
{
S2C_CHALLENGE = 0x41,
Expand Down
26 changes: 26 additions & 0 deletions OpenGSQ/Protocols/WON.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text;

namespace OpenGSQ.Protocols
{
/// <summary>
/// World Opponent Network (WON) Query Protocol
/// </summary>
public class WON : Source
{
/// <inheritdoc/>
public override string FullName => "World Opponent Network (WON) Query Protocol";

/// <summary>
/// Initializes a new instance of the WON class.
/// </summary>
/// <param name="host">The host address of the server.</param>
/// <param name="port">The port to connect to. Default is 27015.</param>
/// <param name="timeout">The connection timeout in milliseconds. Default is 5 seconds.</param>
public WON(string host, int port = 27015, int timeout = 5000) : base(host, port, timeout)
{
A2S_INFO = Encoding.ASCII.GetBytes("details\0");
A2S_PLAYER = Encoding.ASCII.GetBytes("players");
A2S_RULES = Encoding.ASCII.GetBytes("rules");
}
}
}
35 changes: 35 additions & 0 deletions OpenGSQTests/Protocols/WONTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenGSQTests;
using System.Threading.Tasks;

namespace OpenGSQ.Protocols.Tests
{
[TestClass()]
public class WONTests : TestBase
{
public WON won = new("212.227.190.150", 27020);

public WONTests() : base(nameof(WONTests))
{
_EnableSave = false;
}

[TestMethod()]
public async Task GetInfoTest()
{
SaveResult(nameof(GetInfoTest), await won.GetInfo());
}

[TestMethod()]
public async Task GetPlayersTest()
{
SaveResult(nameof(GetPlayersTest), await won.GetPlayers());
}

[TestMethod()]
public async Task GetRulesTest()
{
SaveResult(nameof(GetRulesTest), await won.GetRules());
}
}
}
22 changes: 22 additions & 0 deletions OpenGSQTests/Results/WONTests/GetInfoTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"Address": "212.227.190.150:27020",
"Name": "[Murka] NEW CS1.5 DeathMatch s4ke murka-terroristka.de",
"Map": "de_inferno",
"Folder": "cstrike",
"Game": "CounterStrike",
"Players": 6,
"MaxPlayers": 32,
"Protocol": 46,
"ServerType": 100,
"Environment": 108,
"Visibility": 0,
"Mod": 1,
"Link": "murka-terroristka.de",
"DownloadLink": "",
"Version": 1,
"Size": 184000000,
"Type": 0,
"DLL": 1,
"VAC": 0,
"Bots": 6
}
32 changes: 32 additions & 0 deletions OpenGSQTests/Results/WONTests/GetPlayersTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"Name": "[P*D]Sean_Connery (21)",
"Score": 8,
"Duration": 86674.44
},
{
"Name": "[P0D]Jim_Carrey (41)",
"Score": 12,
"Duration": 86674.44
},
{
"Name": "[POD]George_Clooney (61)",
"Score": 10,
"Duration": 86674.44
},
{
"Name": "[P*D]Pseudolukian (21)",
"Score": 14,
"Duration": 86674.44
},
{
"Name": "[P0D]Jack_Nicholson (41)",
"Score": 9,
"Duration": 86674.44
},
{
"Name": "[POD]Jet_Li (61)",
"Score": 15,
"Duration": 86674.44
}
]
105 changes: 105 additions & 0 deletions OpenGSQTests/Results/WONTests/GetRulesTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"allow_spectators": "0",
"amx_client_languages": "0",
"amx_nextmap": "de_nuke",
"amx_timeleft": "02:16",
"amxmodx_version": "1.8.1.3746",
"coop": "0",
"csdmsake_version": "1.1e",
"deathmatch": "1",
"decalfrequency": "30",
"max_queries_sec": "1",
"max_queries_sec_global": "1",
"max_queries_window": "1",
"metamod_version": "1.19",
"mp_allowmonsters": "0",
"mp_autokick": "0",
"mp_autoteambalance": "1",
"mp_buytime": "0.50",
"mp_c4timer": "45",
"mp_chattime": "10",
"mp_consistency": "1",
"mp_fadetoblack": "0",
"mp_footsteps": "1",
"mp_forcecamera": "0",
"mp_forcechasecam": "0",
"mp_freezetime": "6",
"mp_friendlyfire": "1",
"mp_ghostfrequency": "0.1",
"mp_hostagepenalty": "13",
"mp_kickpercent": "0.66",
"mp_limitteams": "1",
"mp_logdetail": "0",
"mp_logfile": "1",
"mp_logmessages": "0",
"mp_mapvoteratio": "0.67",
"mp_maxrounds": "0",
"mp_mirrordamage": "0",
"mp_playerid": "0",
"mp_roundtime": "3",
"mp_startmoney": "800",
"mp_timeleft": "0",
"mp_timelimit": "15",
"mp_tkpunish": "0",
"mp_winlimit": "0",
"nsv_list": "",
"pb_aim_damper_coefficient_x": "0.22",
"pb_aim_damper_coefficient_y": "0.22",
"pb_aim_deviation_x": "2.0",
"pb_aim_deviation_y": "1.0",
"pb_aim_influence_x_on_y": "0.25",
"pb_aim_influence_y_on_x": "0.17",
"pb_aim_notarget_slowdown_ratio": "0.5",
"pb_aim_offset_delay": "1.2",
"pb_aim_spring_stiffness_x": "13.0",
"pb_aim_spring_stiffness_y": "13.0",
"pb_aim_target_anticipation_ratio": "2.2",
"pb_aim_type": "4",
"pb_autokill": "0",
"pb_autokilldelay": "5",
"pb_bot_join_team": "ANY",
"pb_bot_quota_match": "0",
"pb_chat": "0",
"pb_dangerfactor": "800",
"pb_detailnames": "1",
"pb_ffa": "0",
"pb_jasonmode": "0",
"pb_latencybot": "1",
"pb_mapstartbotdelay": "2",
"pb_maxbots": "0",
"pb_maxbotskill": "100",
"pb_maxcamptime": "30",
"pb_maxweaponpickup": "10",
"pb_minbots": "0",
"pb_minbotskill": "1",
"pb_numfollowuser": "5",
"pb_radio": "0",
"pb_restrequipammo": "000000000",
"pb_restrweapons": "00000000000000000000000000",
"pb_shootthruwalls": "1",
"pb_skin": "1",
"pb_spray": "1",
"pb_timer_grenade": "0.5",
"pb_timer_pickup": "0.3",
"pb_timer_sound": "0.5",
"pb_usespeech": "0",
"pb_version": "V3B20q",
"pb_welcomemsgs": "0",
"pb_wptfolder": "wptdefault",
"sv_aim": "0",
"sv_cheats": "0",
"sv_contact": "www.murka-terroristka.de",
"sv_friction": "4",
"sv_godmodetime": "1.5",
"sv_gravity": "800",
"sv_logblocks": "1",
"sv_maxrate": "0",
"sv_maxspeed": "320",
"sv_minrate": "0",
"sv_password": "0",
"sv_proxies": "0",
"sv_restart": "0",
"sv_restartround": "0",
"sv_voiceenable": "1",
"sv_weapons": "4194303"
}

0 comments on commit c57712f

Please sign in to comment.