Created
December 28, 2022 22:24
-
-
Save TheVeryStarlk/de565f0908b6f7064977d59e7905858d to your computer and use it in GitHub Desktop.
Reads an MOTD of a Minecraft Java server
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
using System.Net.Sockets; | |
using System.Text; | |
using System.Text.Json; | |
const string address = "mc.hypixel.net"; | |
const ushort port = 25565; | |
// https://wiki.vg/Server_List_Ping#Handshake | |
var handshake = PacketBuilder.Create(Packet.Handshake) | |
.WriteVarInt(761) // Protocol version | |
.WriteString(address) // Server address | |
.WriteUnsignedShort(port) // Server port | |
.WriteVarInt(1) // Next state | |
.Build(); | |
// https://wiki.vg/Server_List_Ping#Status_Request | |
var nextState = PacketBuilder.Create(Packet.NextState).Build(); | |
var client = new TcpClient(); | |
client.Connect(address, port); | |
await using var stream = client.GetStream(); | |
// Send handshake then ask for next state | |
await stream.WriteAsync(handshake); | |
await stream.WriteAsync(nextState); | |
var buffer = new byte[short.MaxValue]; | |
var _ = await stream.ReadAsync(buffer); | |
PacketReader.Create(buffer) | |
.ReadVarInt(out var packetLength) | |
.ReadVarInt(out var packetType) | |
.ReadVarInt(out var jsonLength) | |
.ReadString(out var json, jsonLength); | |
// Console.WriteLine( | |
// $"Received packet type '{packetType}' with length '{packetLength}'.{Environment.NewLine}" | |
// + "Wrote the contents of the JSON payload to 'payload.json'."); | |
// | |
// File.WriteAllText("payload.json", json); | |
Console.WriteLine(JsonSerializer.Deserialize<Server>(json, new JsonSerializerOptions() | |
{ | |
PropertyNameCaseInsensitive = true | |
})?.Description); | |
internal sealed class PacketBuilder | |
{ | |
private readonly Packet type; | |
private readonly List<byte> buffer; | |
private PacketBuilder(Packet packet) | |
{ | |
type = packet; | |
buffer = new List<byte>(); | |
} | |
public static PacketBuilder Create(Packet packet) | |
{ | |
return new PacketBuilder(packet); | |
} | |
public PacketBuilder WriteVarInt(int value) | |
{ | |
buffer.AddRange(WriteVarIntInternal(value)); | |
return this; | |
} | |
public PacketBuilder WriteString(string value) | |
{ | |
var stringBytes = Encoding.UTF8.GetBytes(value); | |
var stringBuffer = new List<byte>(); | |
stringBuffer.AddRange(WriteVarIntInternal(stringBytes.Length)); | |
stringBuffer.AddRange(stringBytes); | |
buffer.AddRange(stringBuffer); | |
return this; | |
} | |
public PacketBuilder WriteUnsignedShort(ushort value) | |
{ | |
buffer.AddRange(BitConverter.GetBytes(value)); | |
return this; | |
} | |
public byte[] Build() | |
{ | |
var oldBuffer = buffer.ToArray(); | |
buffer.Clear(); | |
var id = WriteVarIntInternal((byte) type); | |
var length = WriteVarIntInternal(id.Length + oldBuffer.Length); | |
var packet = new List<byte>(); | |
packet.AddRange(length); | |
packet.AddRange(id); | |
packet.AddRange(oldBuffer); | |
return packet.ToArray(); | |
} | |
private byte[] WriteVarIntInternal(int value) | |
{ | |
var varIntBuffer = new List<byte>(); | |
while ((value & 128) is not 0) | |
{ | |
varIntBuffer.Add((byte) (value & 127 | 128)); | |
value = (int) ((uint) value) >> 7; | |
} | |
varIntBuffer.Add((byte) value); | |
return varIntBuffer.ToArray(); | |
} | |
} | |
internal enum Packet : byte | |
{ | |
Handshake = 0x00, | |
NextState = 0x00 | |
} | |
internal sealed class PacketReader | |
{ | |
private int current; | |
private readonly byte[] buffer; | |
private PacketReader(byte[] buffer) | |
{ | |
this.buffer = buffer; | |
} | |
public static PacketReader Create(byte[] buffer) | |
{ | |
return new PacketReader(buffer); | |
} | |
public PacketReader ReadString(out string value, int? length = null) | |
{ | |
ReadVarInt(out var varInt); | |
length ??= varInt; | |
value = Encoding.UTF8.GetString(buffer.AsSpan().Slice(current, length.Value)); | |
return this; | |
} | |
public PacketReader ReadVarInt(out int value) | |
{ | |
var varInt = 0; | |
var size = 0; | |
int @byte; | |
while (((@byte = buffer[++current]) & 0x80) is 0x80) | |
{ | |
varInt |= (@byte & 0x7F) << (size++ * 7); | |
if (size > 5) | |
{ | |
throw new InvalidOperationException(); | |
} | |
} | |
value = varInt | ((@byte & 0x7F) << (size * 7)); | |
return this; | |
} | |
} | |
internal sealed record Server(Version Version, Metadata Players, string Description, string Favicon); | |
internal sealed record Version(string Name, int Protocol); | |
internal sealed record Metadata(int Max, int Online, IEnumerable<Player> Sample); | |
internal sealed record Player(string Name, string Id); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment