Skip to content

Instantly share code, notes, and snippets.

@TheVeryStarlk
Created December 28, 2022 22:24
Show Gist options
  • Save TheVeryStarlk/de565f0908b6f7064977d59e7905858d to your computer and use it in GitHub Desktop.
Save TheVeryStarlk/de565f0908b6f7064977d59e7905858d to your computer and use it in GitHub Desktop.
Reads an MOTD of a Minecraft Java server
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