|
@@ -0,0 +1,250 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using System.Net.Sockets;
|
|
|
+using System.Net;
|
|
|
+
|
|
|
+//https://github.com/opengsq/opengsq-dotnet/blob/main/OpenGSQ/Protocols/GameSpy2.cs
|
|
|
+//https://github.com/opengsq/opengsq-dotnet/blob/main/OpenGSQ/ProtocolBase.cs
|
|
|
+
|
|
|
+/*
|
|
|
+MIT License
|
|
|
+
|
|
|
+Copyright (c) 2021 OpenGSQ
|
|
|
+
|
|
|
+Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
+of this software and associated documentation files (the "Software"), to deal
|
|
|
+in the Software without restriction, including without limitation the rights
|
|
|
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
+copies of the Software, and to permit persons to whom the Software is
|
|
|
+furnished to do so, subject to the following conditions:
|
|
|
+
|
|
|
+The above copyright notice and this permission notice shall be included in all
|
|
|
+copies or substantial portions of the Software.
|
|
|
+
|
|
|
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
+SOFTWARE.
|
|
|
+
|
|
|
+ */
|
|
|
+
|
|
|
+namespace VeloeMonitorDataCollector.Dependencies
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// Gamespy Query Protocol version 2
|
|
|
+ /// </summary>
|
|
|
+ public class Gs2Status
|
|
|
+ {
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Represents a network endpoint as an IP address and a port number.
|
|
|
+ /// </summary>
|
|
|
+ protected IPEndPoint _EndPoint;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Timeout in millisecond
|
|
|
+ /// </summary>
|
|
|
+ protected int _Timeout;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Cached challenge bytes
|
|
|
+ /// </summary>
|
|
|
+ protected byte[] _Challenge = new byte[0];
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// ProtocolBase
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address"></param>
|
|
|
+ /// <param name="port"></param>
|
|
|
+ /// <param name="timeout"></param>
|
|
|
+ public Gs2Status(string address, int port, int timeout = 5000)
|
|
|
+ {
|
|
|
+ if (IPAddress.TryParse(address, out var ipAddress))
|
|
|
+ {
|
|
|
+ _EndPoint = new IPEndPoint(ipAddress, port);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _EndPoint = new IPEndPoint(Dns.GetHostAddresses(address)[0], port);
|
|
|
+ }
|
|
|
+
|
|
|
+ _Timeout = timeout;
|
|
|
+ }
|
|
|
+ /// <summary>
|
|
|
+ /// Gamespy Query Protocol version 2
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="address"></param>
|
|
|
+ /// <param name="port"></param>
|
|
|
+ /// <param name="timeout"></param>
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Retrieves information about the server including, Info, Players, and Teams.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="request"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ /// <exception cref="SocketException"></exception>
|
|
|
+ public Status GetStatus(Request request = Request.Info | Request.Players | Request.Teams)
|
|
|
+ {
|
|
|
+ using (var udpClient = new UdpClient())
|
|
|
+ {
|
|
|
+ var responseData = ConnectAndSend(udpClient, request);
|
|
|
+
|
|
|
+ using (var br = new BinaryReader(new MemoryStream(responseData), Encoding.UTF8))
|
|
|
+ {
|
|
|
+ var status = new Status();
|
|
|
+
|
|
|
+ // Save Response Info
|
|
|
+ if (request.HasFlag(Request.Info))
|
|
|
+ {
|
|
|
+ status.Info = GetInfo(br);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save Response Players
|
|
|
+ if (request.HasFlag(Request.Players))
|
|
|
+ {
|
|
|
+ status.Players = GetPlayers(br);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save Response Teams
|
|
|
+ if (request.HasFlag(Request.Teams))
|
|
|
+ {
|
|
|
+ status.Teams = GetTeams(br);
|
|
|
+ }
|
|
|
+
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private byte[] ConnectAndSend(UdpClient udpClient, Request request)
|
|
|
+ {
|
|
|
+ // Connect to remote host
|
|
|
+ udpClient.Connect(_EndPoint);
|
|
|
+ udpClient.Client.SendTimeout = _Timeout;
|
|
|
+ udpClient.Client.ReceiveTimeout = _Timeout;
|
|
|
+
|
|
|
+ // Send Request
|
|
|
+ var requestData = new byte[] { 0xFE, 0xFD, 0x00, 0x04, 0x05, 0x06, 0x07 }.Concat(GetRequestBytes(request)).ToArray();
|
|
|
+ udpClient.Send(requestData, requestData.Length);
|
|
|
+
|
|
|
+ // Server response
|
|
|
+ var responseData = udpClient.Receive(ref _EndPoint);
|
|
|
+
|
|
|
+ // Remove the first 5 bytes { 0x00, 0x04, 0x05, 0x06, 0x07 }
|
|
|
+ return responseData.Skip(5).ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private byte[] GetRequestBytes(Request request)
|
|
|
+ {
|
|
|
+ return new byte[] {
|
|
|
+ (byte)(request.HasFlag(Request.Info) ? 0xFF : 0x00),
|
|
|
+ (byte)(request.HasFlag(Request.Players) ? 0xFF : 0x00),
|
|
|
+ (byte)(request.HasFlag(Request.Teams) ? 0xFF : 0x00),
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private Dictionary<string, string> GetInfo(BinaryReader br)
|
|
|
+ {
|
|
|
+ var info = new Dictionary<string, string>();
|
|
|
+
|
|
|
+ // Read all key values
|
|
|
+ while (br.TryReadStringEx(out var key))
|
|
|
+ {
|
|
|
+ info[key] = br.ReadStringEx().Trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ return info;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Dictionary<string, string>> GetPlayers(BinaryReader br)
|
|
|
+ {
|
|
|
+ var players = new List<Dictionary<string, string>>();
|
|
|
+
|
|
|
+ // Skip a byte
|
|
|
+ br.ReadByte();
|
|
|
+
|
|
|
+ // Get player count
|
|
|
+ var playerCount = br.ReadByte();
|
|
|
+
|
|
|
+ // Get all keys
|
|
|
+ var keys = new List<string>();
|
|
|
+
|
|
|
+ while (br.TryReadStringEx(out var key))
|
|
|
+ {
|
|
|
+ keys.Add(key.TrimEnd('_'));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set all keys and values
|
|
|
+ for (int i = 0; i < playerCount; i++)
|
|
|
+ {
|
|
|
+ players.Add(new Dictionary<string, string>());
|
|
|
+
|
|
|
+ foreach (var key in keys)
|
|
|
+ {
|
|
|
+ players[i][key] = br.ReadStringEx().Trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return players;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Dictionary<string, string>> GetTeams(BinaryReader br)
|
|
|
+ {
|
|
|
+ var teams = new List<Dictionary<string, string>>();
|
|
|
+
|
|
|
+ // Skip a byte
|
|
|
+ br.ReadByte();
|
|
|
+
|
|
|
+ // Get team count
|
|
|
+ var teamCount = br.ReadByte();
|
|
|
+
|
|
|
+ // Get all keys
|
|
|
+ var keys = new List<string>();
|
|
|
+
|
|
|
+ while (br.TryReadStringEx(out var key))
|
|
|
+ {
|
|
|
+ keys.Add(key.TrimEnd('t').TrimEnd('_'));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set all keys and values
|
|
|
+ for (int i = 0; i < teamCount; i++)
|
|
|
+ {
|
|
|
+ teams.Add(new Dictionary<string, string>());
|
|
|
+
|
|
|
+ foreach (var key in keys)
|
|
|
+ {
|
|
|
+ teams[i][key] = br.ReadStringEx().Trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return teams;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Request Flag
|
|
|
+ /// </summary>
|
|
|
+ [Flags]
|
|
|
+ public enum Request : short
|
|
|
+ {
|
|
|
+#pragma warning disable 1591
|
|
|
+ Info = 1,
|
|
|
+ Players = 2,
|
|
|
+ Teams = 4,
|
|
|
+ }
|
|
|
+
|
|
|
+ public class Status
|
|
|
+ {
|
|
|
+ public Dictionary<string, string> Info { get; set; }
|
|
|
+
|
|
|
+ public List<Dictionary<string, string>> Players { get; set; }
|
|
|
+
|
|
|
+ public List<Dictionary<string, string>> Teams { get; set; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|