Skip to content

Commit

Permalink
Support RakNet Protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
BattlefieldDuck committed Jan 17, 2024
1 parent 4442bc4 commit 66c8aa4
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 0 deletions.
81 changes: 81 additions & 0 deletions OpenGSQ/Protocols/RakNet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
using System.Linq;
using System.IO;
using System.Net.Sockets;
using OpenGSQ.Responses.RakNet;

namespace OpenGSQ.Protocols
{
/// <summary>
/// RakNet Protocol (https://wiki.vg/Raknet_Protocol)
/// </summary>
public class RakNet : ProtocolBase
{
/// <inheritdoc/>
public override string FullName => "RakNet Protocol";

private static readonly byte[] ID_UNCONNECTED_PING = { 0x01 };
private static readonly byte[] ID_UNCONNECTED_PONG = { 0x1C };
private static readonly byte[] TIMESTAMP = { 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89 };
private static readonly byte[] OFFLINE_MESSAGE_DATA_ID = { 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 };
private static readonly byte[] CLIENT_GUID = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

/// <summary>
/// Initializes a new instance of the RakNet class.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
/// <param name="timeout">The timeout. Default is 5000.</param>
public RakNet(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
}

/// <summary>
/// Gets the server status asynchronously.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the server status.</returns>
public async Task<StatusResponse> GetStatus()
{
var request = ID_UNCONNECTED_PING.Concat(TIMESTAMP).Concat(OFFLINE_MESSAGE_DATA_ID).Concat(CLIENT_GUID).ToArray();
using var udpClient = new UdpClient();
var response = await udpClient.CommunicateAsync(this, request);

using var br = new BinaryReader(new MemoryStream(response));
var header = br.ReadByte();

if (header != ID_UNCONNECTED_PONG[0])
{
throw new InvalidPacketException($"Packet header mismatch. Received: {header}. Expected: {ID_UNCONNECTED_PONG[0]}.");
}

br.ReadBytes(TIMESTAMP.Length + CLIENT_GUID.Length); // skip timestamp and guid
var magic = br.ReadBytes(OFFLINE_MESSAGE_DATA_ID.Length);

if (!magic.SequenceEqual(OFFLINE_MESSAGE_DATA_ID))
{
throw new InvalidPacketException($"Magic value mismatch. Received: {magic}. Expected: {OFFLINE_MESSAGE_DATA_ID}.");
}

br.ReadInt16(); // skip remaining packet length

byte[] delimiter = { (byte)';' };

return new StatusResponse
{
Edition = br.ReadStringEx(delimiter),
MotdLine1 = br.ReadStringEx(delimiter),
ProtocolVersion = int.Parse(br.ReadStringEx(delimiter)),
VersionName = br.ReadStringEx(delimiter),
NumPlayers = int.Parse(br.ReadStringEx(delimiter)),
MaxPlayers = int.Parse(br.ReadStringEx(delimiter)),
ServerUniqueId = br.ReadStringEx(delimiter),
MotdLine2 = br.ReadStringEx(delimiter),
GameMode = br.ReadStringEx(delimiter),
GameModeNumeric = int.Parse(br.ReadStringEx(delimiter)),
PortIPv4 = int.Parse(br.ReadStringEx(delimiter)),
PortIPv6 = int.Parse(br.ReadStringEx(delimiter))
};
}
}
}
68 changes: 68 additions & 0 deletions OpenGSQ/Responses/RakNet/StatusResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace OpenGSQ.Responses.RakNet
{
/// <summary>
/// Represents the status response from a Minecraft server.
/// </summary>
public class StatusResponse
{
/// <summary>
/// Gets or sets the edition of the server (MCPE or MCEE for Education Edition).
/// </summary>
public string Edition { get; set; }

/// <summary>
/// Gets or sets the first line of the Message of the Day (MOTD).
/// </summary>
public string MotdLine1 { get; set; }

/// <summary>
/// Gets or sets the protocol version of the server.
/// </summary>
public int ProtocolVersion { get; set; }

/// <summary>
/// Gets or sets the version name of the server.
/// </summary>
public string VersionName { get; set; }

/// <summary>
/// Gets or sets the number of players currently on the server.
/// </summary>
public int NumPlayers { get; set; }

/// <summary>
/// Gets or sets the maximum number of players that can join the server.
/// </summary>
public int MaxPlayers { get; set; }

/// <summary>
/// Gets or sets the unique ID of the server.
/// </summary>
public string ServerUniqueId { get; set; }

/// <summary>
/// Gets or sets the second line of the Message of the Day (MOTD).
/// </summary>
public string MotdLine2 { get; set; }

/// <summary>
/// Gets or sets the game mode of the server.
/// </summary>
public string GameMode { get; set; }

/// <summary>
/// Gets or sets the numeric representation of the game mode.
/// </summary>
public int GameModeNumeric { get; set; }

/// <summary>
/// Gets or sets the IPv4 port of the server.
/// </summary>
public int PortIPv4 { get; set; }

/// <summary>
/// Gets or sets the IPv6 port of the server.
/// </summary>
public int PortIPv6 { get; set; }
}
}
24 changes: 24 additions & 0 deletions OpenGSQTests/Protocols/RakNetTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenGSQTests;

namespace OpenGSQ.Protocols.Tests
{
[TestClass()]
public class RakNetTests : TestBase
{
// minecraftpe
public RakNet raknet = new("mc.advancius.net", 19132);

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

[TestMethod()]
public async Task GetStatusTest()
{
SaveResult(nameof(GetStatusTest), await raknet.GetStatus());
}
}
}
14 changes: 14 additions & 0 deletions OpenGSQTests/Results/RakNetTests/GetStatusTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Edition": "MCPE",
"MotdLine1": "Advancius Network",
"ProtocolVersion": 630,
"VersionName": "1.20.50",
"NumPlayers": 147,
"MaxPlayers": 400,
"ServerUniqueId": "5341688303186863614",
"MotdLine2": "discord.advancius.net",
"GameMode": "Survival",
"GameModeNumeric": 1,
"PortIPv4": 19132,
"PortIPv6": 0
}

0 comments on commit 66c8aa4

Please sign in to comment.