-
-
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
3db8923
commit 4442bc4
Showing
4 changed files
with
224 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,167 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Text.Json; | ||
using System.Linq; | ||
using System.IO; | ||
using System.Text.RegularExpressions; | ||
using System.Net.Sockets; | ||
|
||
namespace OpenGSQ.Protocols | ||
{ | ||
/// <summary> | ||
/// Minecraft Protocol (https://wiki.vg/Server_List_Ping) | ||
/// </summary> | ||
public class Minecraft : ProtocolBase | ||
{ | ||
/// <inheritdoc/> | ||
public override string FullName => "Minecraft Protocol"; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the Minecraft class. | ||
/// </summary> | ||
/// <param name="host">The host.</param> | ||
/// <param name="port">The port.</param> | ||
/// <param name="timeout">The timeout. Default is 5000.</param> | ||
public Minecraft(string host, int port, int timeout = 5000) : base(host, port, timeout) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Gets the server status asynchronously. | ||
/// </summary> | ||
/// <param name="version">The protocol version. Default is 47.</param> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains the server status.</returns> | ||
public async Task<Dictionary<string, object>> GetStatus(int version = 47) | ||
{ | ||
// Prepare the request | ||
var address = Encoding.UTF8.GetBytes(Host); | ||
var protocol = PackVarint(version); | ||
var request = new byte[] { 0x00 }.Concat(protocol).Concat(PackVarint(address.Length)).Concat(address).Concat(BitConverter.GetBytes((short)Port)).Concat(new byte[] { 0x01 }).ToArray(); | ||
request = PackVarint(request.Length).Concat(request).Concat(new byte[] { 0x01, 0x00 }).ToArray(); | ||
|
||
using var tcpClient = new TcpClient(); | ||
tcpClient.ReceiveTimeout = Timeout; | ||
await tcpClient.ConnectAsync(Host, Port); | ||
await tcpClient.SendAsync(request); | ||
|
||
var response = await tcpClient.ReceiveAsync(); | ||
using var br1 = new BinaryReader(new MemoryStream(response)); | ||
var length = UnpackVarint(br1); | ||
|
||
// Keep receiving until reach packet length | ||
while (response.Length < length) | ||
{ | ||
response = response.Concat(await tcpClient.ReceiveAsync()).ToArray(); | ||
} | ||
|
||
// Read full response | ||
using var br = new BinaryReader(new MemoryStream(response)); | ||
UnpackVarint(br); // packet length | ||
UnpackVarint(br); // packet id | ||
var count = UnpackVarint(br); // json length | ||
|
||
// The packet may respond with two json objects, so we need to get the json length exactly | ||
var data = JsonSerializer.Deserialize<Dictionary<string, object>>(Encoding.UTF8.GetString(br.ReadBytes(count))); | ||
|
||
return data; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the server status for servers using a version older than Minecraft 1.7. | ||
/// </summary> | ||
/// <returns>A task that represents the asynchronous operation. The task result contains the server status.</returns> | ||
public async Task<Dictionary<string, object>> GetStatusPre17() | ||
{ | ||
using var tcpClient = new TcpClient(); | ||
var response = await tcpClient.CommunicateAsync(this, new byte[] { 0xFE, 0x01 }); | ||
|
||
using var br = new BinaryReader(new MemoryStream(response)); | ||
var header = br.ReadByte(); | ||
|
||
if (header != 0xFF) | ||
{ | ||
throw new InvalidPacketException($"Packet header mismatch. Received: {header}. Expected: {0xFF}."); | ||
} | ||
|
||
br.ReadBytes(2); // length of the following string | ||
var items = Encoding.BigEndianUnicode.GetString(br.ReadBytes(response.Length - 2)).Split('\0'); | ||
|
||
var result = new Dictionary<string, object> | ||
{ | ||
["protocol"] = items[1], | ||
["version"] = items[2], | ||
["motd"] = items[3], | ||
["numplayers"] = int.Parse(items[4]), | ||
["maxplayers"] = int.Parse(items[5]) | ||
}; | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Strips color codes from the input text. | ||
/// </summary> | ||
/// <param name="text">The text to strip color codes from.</param> | ||
/// <returns>The text with color codes stripped.</returns> | ||
public static string StripColors(string text) | ||
{ | ||
// Strip color codes | ||
return Regex.Replace(text, @"\u00A7[0-9A-FK-OR]", "", RegexOptions.IgnoreCase); | ||
} | ||
|
||
/// <summary> | ||
/// Packs an integer into a Varint. | ||
/// </summary> | ||
/// <param name="val">The integer to pack.</param> | ||
/// <returns>The packed Varint as a byte array.</returns> | ||
protected byte[] PackVarint(int val) | ||
{ | ||
var total = new List<byte>(); | ||
|
||
if (val < 0) | ||
{ | ||
val = (1 << 32) + val; | ||
} | ||
|
||
while (val >= 0x80) | ||
{ | ||
var bits = val & 0x7F; | ||
val >>= 7; | ||
total.Add((byte)(0x80 | bits)); | ||
} | ||
|
||
var lastBits = val & 0x7F; | ||
total.Add((byte)lastBits); | ||
|
||
return total.ToArray(); | ||
} | ||
|
||
/// <summary> | ||
/// Unpacks a Varint into an integer. | ||
/// </summary> | ||
/// <param name="br">The BinaryReader to read the Varint from.</param> | ||
/// <returns>The unpacked integer.</returns> | ||
protected int UnpackVarint(BinaryReader br) | ||
{ | ||
var total = 0; | ||
var shift = 0; | ||
var val = 0x80; | ||
|
||
while ((val & 0x80) != 0) | ||
{ | ||
val = br.ReadByte(); | ||
total |= (val & 0x7F) << shift; | ||
shift += 7; | ||
} | ||
|
||
if ((total & (1 << 31)) != 0) | ||
{ | ||
total -= 1 << 32; | ||
} | ||
|
||
return total; | ||
} | ||
} | ||
} |
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,29 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using OpenGSQTests; | ||
|
||
namespace OpenGSQ.Protocols.Tests | ||
{ | ||
[TestClass()] | ||
public class MinecraftTests : TestBase | ||
{ | ||
public Minecraft minecraft = new("valistar.site", 25565); | ||
|
||
public MinecraftTests() : base(nameof(MinecraftTests)) | ||
{ | ||
_EnableSave = false; | ||
} | ||
|
||
[TestMethod()] | ||
public async Task GetStatusTest() | ||
{ | ||
SaveResult(nameof(GetStatusTest), await minecraft.GetStatus()); | ||
} | ||
|
||
[TestMethod()] | ||
public async Task GetStatusPre17Test() | ||
{ | ||
SaveResult(nameof(GetStatusPre17Test), await minecraft.GetStatusPre17()); | ||
} | ||
} | ||
} |
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,7 @@ | ||
{ | ||
"protocol": "127", | ||
"version": "1.20.1", | ||
"motd": "BetterMC4", | ||
"numplayers": 0, | ||
"maxplayers": 10 | ||
} |
Oops, something went wrong.