-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
816dee0
commit bcd6078
Showing
7 changed files
with
428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net.Sockets; | ||
using System.Text; | ||
using System.Text.RegularExpressions; | ||
using System.Threading.Tasks; | ||
using OpenGSQ.Responses.Unreal2; | ||
|
||
namespace OpenGSQ.Protocols | ||
{ | ||
/// <summary> | ||
/// Unreal 2 Protocol | ||
/// </summary> | ||
public class Unreal2 : ProtocolBase | ||
{ | ||
/// <inheritdoc/> | ||
public override string FullName => "Unreal 2 Protocol"; | ||
|
||
protected const byte _DETAILS = 0x00; | ||
Check warning on line 20 in OpenGSQ/Protocols/Unreal2.cs GitHub Actions / build (windows-latest)
|
||
protected const byte _RULES = 0x01; | ||
Check warning on line 21 in OpenGSQ/Protocols/Unreal2.cs GitHub Actions / build (windows-latest)
|
||
protected const byte _PLAYERS = 0x02; | ||
Check warning on line 22 in OpenGSQ/Protocols/Unreal2.cs GitHub Actions / build (windows-latest)
|
||
|
||
/// <summary> | ||
/// Initializes a new instance of the Unreal2 class. | ||
/// </summary> | ||
public Unreal2(string host, int port, int timeout = 5000) : base(host, port, timeout) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Gets the details of the server. | ||
/// </summary> | ||
/// <returns>The details of the server.</returns> | ||
/// <exception cref="InvalidPacketException">Thrown when the packet header does not match the expected value.</exception> | ||
public async Task<Status> GetDetails() | ||
{ | ||
using var udpClient = new UdpClient(); | ||
byte[] response = await udpClient.CommunicateAsync(this, new byte[] { 0x79, 0x00, 0x00, 0x00, _DETAILS }); | ||
|
||
// Remove the first 4 bytes \x80\x00\x00\x00 | ||
BinaryReader br = new BinaryReader(new MemoryStream(response.Skip(4).ToArray())); | ||
byte header = br.ReadByte(); | ||
|
||
if (header != _DETAILS) | ||
{ | ||
throw new InvalidPacketException($"Packet header mismatch. Received: {header}. Expected: {_DETAILS}."); | ||
} | ||
|
||
var details = new Status | ||
{ | ||
ServerId = br.ReadInt32(), | ||
ServerIP = br.ReadString(), | ||
GamePort = br.ReadInt32(), | ||
QueryPort = br.ReadInt32(), | ||
ServerName = ReadString(br), | ||
MapName = ReadString(br), | ||
GameType = ReadString(br), | ||
NumPlayers = br.ReadInt32(), | ||
MaxPlayers = br.ReadInt32(), | ||
Ping = br.ReadInt32(), | ||
Flags = br.ReadInt32(), | ||
Skill = ReadString(br) | ||
}; | ||
|
||
return details; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the rules of the server. | ||
/// </summary> | ||
/// <returns>The rules of the server.</returns> | ||
/// <exception cref="InvalidPacketException">Thrown when the packet header does not match the expected value.</exception> | ||
public async Task<Dictionary<string, object>> GetRules() | ||
{ | ||
using var udpClient = new UdpClient(); | ||
byte[] response = await udpClient.CommunicateAsync(this, new byte[] { 0x79, 0x00, 0x00, 0x00, _RULES }); | ||
|
||
// Remove the first 4 bytes \x80\x00\x00\x00 | ||
BinaryReader br = new BinaryReader(new MemoryStream(response.Skip(4).ToArray())); | ||
byte header = br.ReadByte(); | ||
|
||
if (header != _RULES) | ||
{ | ||
throw new InvalidPacketException($"Packet header mismatch. Received: {header}. Expected: {_RULES}."); | ||
} | ||
|
||
var rules = new Dictionary<string, object>(); | ||
var mutators = new List<string>(); | ||
|
||
while (br.BaseStream.Position != br.BaseStream.Length) | ||
{ | ||
string key = ReadString(br); | ||
string val = ReadString(br); | ||
|
||
if (key.ToLower() == "mutator") | ||
{ | ||
mutators.Add(val); | ||
} | ||
else | ||
{ | ||
rules[key] = val; | ||
} | ||
} | ||
|
||
rules["Mutators"] = mutators; | ||
|
||
return rules; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the players of the server. | ||
/// </summary> | ||
/// <returns>A list of players of the server.</returns> | ||
/// <exception cref="InvalidPacketException">Thrown when the packet header does not match the expected value.</exception> | ||
public async Task<List<Player>> GetPlayers() | ||
{ | ||
using var udpClient = new UdpClient(); | ||
byte[] response = await udpClient.CommunicateAsync(this, new byte[] { 0x79, 0x00, 0x00, 0x00, _PLAYERS }); | ||
|
||
// Remove the first 4 bytes \x80\x00\x00\x00 | ||
BinaryReader br = new BinaryReader(new MemoryStream(response.Skip(4).ToArray())); | ||
byte header = br.ReadByte(); | ||
|
||
if (header != _PLAYERS) | ||
{ | ||
throw new InvalidPacketException($"Packet header mismatch. Received: {header}. Expected: {_PLAYERS}."); | ||
} | ||
|
||
var players = new List<Player>(); | ||
|
||
while (br.BaseStream.Position != br.BaseStream.Length) | ||
{ | ||
var player = new Player | ||
{ | ||
Id = br.ReadInt32(), | ||
Name = ReadString(br), | ||
Ping = br.ReadInt32(), | ||
Score = br.ReadInt32(), | ||
StatsId = br.ReadInt32() | ||
}; | ||
|
||
players.Add(player); | ||
} | ||
|
||
return players; | ||
} | ||
|
||
/// <summary> | ||
/// Strips color codes from the given text. | ||
/// </summary> | ||
/// <param name="text">The text to strip color codes from, represented as a byte array.</param> | ||
/// <returns>The text with color codes stripped, represented as a string.</returns> | ||
protected string StripColors(byte[] text) | ||
{ | ||
string str = Encoding.UTF8.GetString(text); | ||
return Regex.Replace(str, @"\x1b...|[\x00-\x1a]", ""); | ||
} | ||
|
||
/// <summary> | ||
/// Reads a string from a BinaryReader, decodes it, and strips color codes. | ||
/// </summary> | ||
/// <param name="br">The BinaryReader to read the string from.</param> | ||
/// <returns>The decoded string with color codes stripped.</returns> | ||
protected string ReadString(BinaryReader br) | ||
{ | ||
int length = br.ReadByte(); | ||
string str = br.ReadStringEx(); | ||
|
||
byte[] b; | ||
if (length == str.Length + 1) | ||
{ | ||
b = Encoding.UTF8.GetBytes(str); | ||
} | ||
else | ||
{ | ||
b = Encoding.Unicode.GetBytes(str); | ||
} | ||
|
||
return StripColors(b); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
namespace OpenGSQ.Responses.Unreal2 | ||
{ | ||
/// <summary> | ||
/// Represents a player in the game. | ||
/// </summary> | ||
public class Player | ||
{ | ||
/// <summary> | ||
/// Gets or sets the ID of the player. | ||
/// </summary> | ||
public int Id { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the name of the player. | ||
/// </summary> | ||
public string Name { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the ping of the player. | ||
/// </summary> | ||
public int Ping { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the score of the player. | ||
/// </summary> | ||
public int Score { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the stats ID of the player. | ||
/// </summary> | ||
public int StatsId { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
namespace OpenGSQ.Responses.Unreal2 | ||
{ | ||
/// <summary> | ||
/// Represents the status of a server. | ||
/// </summary> | ||
public class Status | ||
{ | ||
/// <summary> | ||
/// Gets or sets the server ID. | ||
/// </summary> | ||
public int ServerId { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the IP address of the server. | ||
/// </summary> | ||
public string ServerIP { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the game port of the server. | ||
/// </summary> | ||
public int GamePort { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the query port of the server. | ||
/// </summary> | ||
public int QueryPort { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the name of the server. | ||
/// </summary> | ||
public string ServerName { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the name of the map. | ||
/// </summary> | ||
public string MapName { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the type of the game. | ||
/// </summary> | ||
public string GameType { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the number of players. | ||
/// </summary> | ||
public int NumPlayers { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the maximum number of players. | ||
/// </summary> | ||
public int MaxPlayers { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the ping. | ||
/// </summary> | ||
public int Ping { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the flags. | ||
/// </summary> | ||
public int Flags { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the skill level. | ||
/// </summary> | ||
public string Skill { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using OpenGSQTests; | ||
|
||
namespace OpenGSQ.Protocols.Tests | ||
{ | ||
[TestClass()] | ||
public class Unreal2Tests : TestBase | ||
{ | ||
public Unreal2 unreal2 = new("109.230.224.189", 6970); | ||
|
||
public Unreal2Tests() : base(nameof(Unreal2Tests)) | ||
{ | ||
_EnableSave = !false; | ||
} | ||
|
||
[TestMethod()] | ||
public async Task GetDetailsTest() | ||
{ | ||
SaveResult(nameof(GetDetailsTest), await unreal2.GetDetails()); | ||
} | ||
|
||
[TestMethod()] | ||
public async Task GetRulesTest() | ||
{ | ||
SaveResult(nameof(GetRulesTest), await unreal2.GetRules()); | ||
} | ||
|
||
[TestMethod()] | ||
public async Task GetPlayersTest() | ||
{ | ||
SaveResult(nameof(GetPlayersTest), await unreal2.GetPlayers()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"ServerId": 0, | ||
"ServerIP": "", | ||
"GamePort": 6969, | ||
"QueryPort": 0, | ||
"ServerName": "The Usual Suspects TAM server DE (#rdturd)", | ||
"MapName": "DM-Under_LE-2009", | ||
"GameType": "xDeathMatch", | ||
"NumPlayers": 6, | ||
"MaxPlayers": 16, | ||
"Ping": 0, | ||
"Flags": 0, | ||
"Skill": "0" | ||
} |
Oops, something went wrong.