Skip to content

Instantly share code, notes, and snippets.

@HurricanKai
Created December 9, 2019 18:32
Show Gist options
  • Save HurricanKai/1a07eeb198909865dfcbe0ddb33d5645 to your computer and use it in GitHub Desktop.
Save HurricanKai/1a07eeb198909865dfcbe0ddb33d5645 to your computer and use it in GitHub Desktop.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.Logging;
namespace Frontend
{
public sealed class MCConnectionHandler : ConnectionHandler
{
private readonly ILogger<MCConnectionHandler> _logger;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public MCConnectionHandler(ILogger<MCConnectionHandler> logger)
{
_logger = logger;
_jsonSerializerOptions = new JsonSerializerOptions()
{
IgnoreNullValues = true
};
}
public override async Task OnConnectedAsync(ConnectionContext connection)
{
connection.Items["state"] = ConnectionState.Handshaking;
connection.Items["flush"] = false;
connection.Items["close"] = false;
connection.Items["statusPayload"] = new StatusResponsePayload
{
Version = new StatusResponsePayload.VersionPayload()
{
Name = "1.14.4",
Protocol = 498
},
Players = new StatusResponsePayload.PlayersPayload
{
Max = 1000000,
Online = 999999,
Samples = new StatusResponsePayload.PlayersPayload.PlayerSample[0]
},
Description = new Chat("Powered by SM3")
{
Color = "red",
Obfuscated = true
},
Favicon = null
};
var memPool = MemoryPool<byte>.Shared;
await using var inputStream = connection.Transport.Input.AsStream();
await using var outputStream = connection.Transport.Output.AsStream();
while (!connection.ConnectionClosed.IsCancellationRequested)
{
var length = StreamNetworkUtils.ReadVarInt(inputStream);
if (length <= 0)
{
_logger.LogCritical($"Could not read Packet Length. Closing Connection without notice. E: {length}");
connection.Abort(new ConnectionAbortedException("Could not read Packet Length"));
return;
}
using var inputPacketMemoryOwner = memPool.Rent(length);
var inputPacketMemory = inputPacketMemoryOwner.Memory;
var i = await inputStream.ReadAsync(inputPacketMemory, connection.ConnectionClosed);
if (i != length)
{
_logger.LogCritical($"Read Length was not equal to Packet Length. Closing Connection without notice");
connection.Abort(new ConnectionAbortedException("Read Length was not equal to Packet Length"));
return;
}
ParsePacket(inputPacketMemory, connection);
if ((bool) connection.Items["flush"])
{
await connection.Transport.Output.FlushAsync();
connection.Items["flush"] = false;
}
if ((bool) connection.Items["close"])
{
connection.Items["close"] = false;
return;
}
}
}
private void ParsePacket(in Memory<byte> inputPacketMemory, ConnectionContext ctx)
{
var span = inputPacketMemory.Span;
var id = SpanNetworkUtils.GetVarInt(ref span);
switch ((ConnectionState)ctx.Items["state"])
{
case ConnectionState.Handshaking:
switch (id)
{
case 0x00:
var protocolVersion = SpanNetworkUtils.GetVarInt(ref span);
var serverAddress = SpanNetworkUtils.GetString(ref span);
var port = BinaryPrimitives.ReadInt16BigEndian(span);
span = span.Slice(sizeof(Int16));
var nextState = (ConnectionState) SpanNetworkUtils.GetVarInt(ref span);
_logger.LogInformation($"Handshake successful");
_logger.LogInformation($"Protocol: {protocolVersion}");
_logger.LogInformation($"Connection from: {serverAddress}:{port}");
_logger.LogInformation($"Remote Endpoint: {ctx.RemoteEndPoint}");
_logger.LogInformation($"Switching to {Enum.GetName(typeof(ConnectionState), nextState)}");
ctx.Items["state"] = nextState;
break;
default:
_logger.LogWarning($"Could not Handle Handshaking Packet {id:x2}");
break;
}
break;
case ConnectionState.Login:
switch (id)
{
case 0x00:
var username = SpanNetworkUtils.GetString(ref span);
_logger.LogInformation($"Username: {username}");
_logger.LogInformation($"Attempting to Gracefully refuse Login");
var disconnectMessage = JsonSerializer.SerializeToUtf8Bytes(new Chat("Sorry. We do not support Log in currently")
{
Bold = true,
Color = "red"
}, _jsonSerializerOptions);
var dataSize = SpanNetworkUtils.GetVarIntSize(0x00)
+ SpanNetworkUtils.GetVarIntSize(disconnectMessage.Length)
+ disconnectMessage.Length;
var packetSpan = ctx.Transport.Output.GetSpan(dataSize + SpanNetworkUtils.GetVarIntSize(dataSize));
var i = SpanNetworkUtils.WriteVarInt(packetSpan, dataSize);
i += SpanNetworkUtils.WriteVarInt(packetSpan.Slice(i), 0x00);
i += SpanNetworkUtils.WriteVarInt(packetSpan.Slice(i), disconnectMessage.Length);
disconnectMessage.CopyTo(packetSpan.Slice(i));
i += disconnectMessage.Length;
ctx.Transport.Output.Advance(i);
ctx.Items["flush"] = true;
ctx.Items["close"] = true;
break;
default:
_logger.LogWarning($"Could not Handle Login Packet {id:x2}");
break;
}
break;
case ConnectionState.Status:
switch (id)
{
case 0x00:
_logger.LogInformation("Answering Status Request");
var responsePayload = (StatusResponsePayload)ctx.Items["statusPayload"];
var payloadData = JsonSerializer.SerializeToUtf8Bytes(responsePayload, _jsonSerializerOptions);
var msg = Encoding.UTF8.GetString(payloadData);
var payloadSize = SpanNetworkUtils.GetVarIntSize(0x00)
+ SpanNetworkUtils.GetVarIntSize(payloadData.Length)
+ payloadData.Length;
var packetSpan1 = ctx.Transport.Output.GetSpan(payloadSize + SpanNetworkUtils.GetVarIntSize(payloadSize));
var i = SpanNetworkUtils.WriteVarInt(packetSpan1, payloadSize);
i += SpanNetworkUtils.WriteVarInt(packetSpan1.Slice(i), 0x00);
i += SpanNetworkUtils.WriteVarInt(packetSpan1.Slice(i), payloadData.Length);
payloadData.CopyTo(packetSpan1.Slice(i));
i += payloadData.Length;
ctx.Transport.Output.Advance(i);
ctx.Items["flush"] = true;
break;
case 0x01:
_logger.LogInformation("Answering Ping");
var seed = BinaryPrimitives.ReadInt64BigEndian(span);
var dataSize = sizeof(long) + SpanNetworkUtils.GetVarIntSize(0x01);
var packetSize = SpanNetworkUtils.GetVarIntSize(dataSize) + dataSize;
var packetSpan2 = ctx.Transport.Output.GetSpan(packetSize);
var j = SpanNetworkUtils.WriteVarInt(packetSpan2, dataSize);
j += SpanNetworkUtils.WriteVarInt(packetSpan2.Slice(j), 0x01);
BinaryPrimitives.WriteInt64BigEndian(packetSpan2.Slice(j), seed);
ctx.Transport.Output.Advance(j + sizeof(long));
ctx.Items["flush"] = true;
break;
default:
_logger.LogWarning($"Could not Handle Status Packet {id:x2}");
break;
}
break;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment