Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Forge FML3 #67

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions Components/MineSharp.Protocol/Channels/Channel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using MineSharp.Core.Common;
using MineSharp.Data;
using NLog;

namespace MineSharp.Protocol.Channels;

/// <summary>
/// Represents a minecraft plugin channel.
/// See https://wiki.vg/Plugin_channels
/// </summary>
public class Channel
{
/// <summary>
/// The identifier of this channel
/// </summary>
public readonly string Identifier;

private readonly PluginChannels parent;
private readonly List<AsyncChannelHandler> handlers;

internal Channel(string identifier, PluginChannels parent)
{
Identifier = identifier;
handlers = new();
this.parent = parent;
}

internal async Task Handle(byte[] data, MinecraftData minecraftData)
{
if (handlers.Count == 0)
{
return;
}

var tasks = new List<Task>(handlers.Count);
foreach (var handler in handlers)
{
var buffer = new PacketBuffer(data, minecraftData.Version.Protocol);
tasks.Add(handler(buffer));
}

try
{
await Task.WhenAll(tasks);
}
catch (Exception)
{
foreach (var exception in tasks.Where(x => x.Exception != null))
{
Logger.Warn($"Error in custom packet handling: {exception.Exception}");
}
}
}

/// <summary>
/// Send data through this channel
/// </summary>
public void Send(byte[] data)
{
this.parent.Send(this, data);
}

/// <summary>
/// Subscribe to this channel
/// </summary>
public void On(AsyncChannelHandler handler)
{
if (handlers.Count > 0)
{
Logger.Warn(
$"The channel {Identifier} has already been subscribed to. This is supported but may lead to unwanted side-effects.");
}

handlers.Add(handler);
}

/// <summary>
/// Unsubscribe the handler from this channel
/// </summary>
/// <param name="handler"></param>
public void Remove(AsyncChannelHandler handler)
{
handlers.Remove(handler);
}

/// <summary>
/// Subscribe to the channel.
/// Equivalent to <see cref="Channel.On"/>
/// </summary>
/// <param name="channel">The channel to subscribe to</param>
/// <param name="handler">The handler</param>
public static Channel operator +(Channel channel, AsyncChannelHandler handler)
{
channel.On(handler);
return channel;
}

/// <summary>
/// Unsubscribe from the channel.
/// </summary>
/// <param name="channel">The channel to unsubscribe</param>
/// <param name="handler">The handler to be removed</param>
/// <returns></returns>
public static Channel operator -(Channel channel, AsyncChannelHandler handler)
{
channel.handlers.Remove(handler);
return channel;
}

/// <summary>
/// A delegate for handling channel data
/// </summary>
public delegate Task AsyncChannelHandler(PacketBuffer buffer);

private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
}
96 changes: 96 additions & 0 deletions Components/MineSharp.Protocol/Channels/PluginChannels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Text;
using MineSharp.Core.Common.Protocol;
using MineSharp.Protocol.Packets;
using NLog;

namespace MineSharp.Protocol.Channels;

/// <summary>
/// Implements minecraft's plugin channel system
/// </summary>
public class PluginChannels
{
private readonly MinecraftClient client;
private readonly Dictionary<string, Channel> channels;

internal PluginChannels(MinecraftClient client)
{
this.client = client;
this.channels = new()
{
{ "minecraft:register", new("minecraft:register", this) },
{ "minecraft:unregister", new("minecraft:unregister", this) }
};
}

/// <summary>
/// Register a channel
/// </summary>
/// <param name="channelIdentifier"></param>
public Channel Register(string channelIdentifier)
{
if (channels.TryGetValue(channelIdentifier, out var channel))
{
return channel;
}

channel = new(channelIdentifier, this);
channels.Add(channelIdentifier, channel);

this["minecraft:register"].Send(Encoding.UTF8.GetBytes(channelIdentifier));

return channel;
}

/// <summary>
/// Unregister a channel
/// </summary>
/// <param name="channelIdentifier"></param>
public void Unregister(string channelIdentifier)
{
channels.Remove(channelIdentifier);

this["minecraft:unregister"].Send(Encoding.UTF8.GetBytes(channelIdentifier));
}

internal void Handle(string identifier, byte[] data)
{
if (!channels.TryGetValue(identifier, out var channel))
{
Logger.Debug($"Received data for unknown channel {identifier}");
return;
}

Task.Run(async () => await channel.Handle(data, client.Data));
}

internal Task Send(Channel channel, byte[] data, CancellationToken token = default)
{
IPacket packet = client.GameState switch
{
GameState.Configuration => new Packets.Serverbound.Configuration.PluginMessagePacket
{
Data = data, ChannelName = channel.Identifier
},
GameState.Play => new Packets.Serverbound.Play.PluginMessagePacket
{
Data = data, ChannelName = channel.Identifier
},
GameState.Login => throw new NotImplementedException(),
_ => throw new InvalidOperationException($"you cannot send plugin messages during {client.GameState} phase")
};

return client.SendPacket(packet, token);
}

/// <summary>
/// Access a channel by name
/// </summary>
/// <param name="identifier"></param>
public Channel this[string identifier]
{
get => channels[identifier];
}

private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
}
42 changes: 23 additions & 19 deletions Components/MineSharp.Protocol/MinecraftClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using MineSharp.Core.Common.Protocol;
using MineSharp.Data;
using MineSharp.Data.Protocol;
using MineSharp.Protocol.Channels;
using MineSharp.Protocol.Connection;
using MineSharp.Protocol.Exceptions;
using MineSharp.Protocol.Packets;
Expand Down Expand Up @@ -80,15 +81,22 @@ public sealed class MinecraftClient : IDisposable
/// </summary>
public readonly ClientSettings Settings;

/// <summary>
/// The plugin channels
/// See https://wiki.vg/Plugin_channels#Definitions
/// </summary>
public readonly PluginChannels Channels;

private readonly IConnectionFactory tcpTcpFactory;

private bool bundlePackets;
private TcpClient? client;

private GameState gameState;
private IPacketHandler internalPacketHandler;
private MinecraftStream? stream;
private Task? streamLoop;

internal GameState GameState;

/// <summary>
/// Create a new MinecraftClient
Expand All @@ -113,17 +121,13 @@ public MinecraftClient(
tcpTcpFactory = tcpFactory;
ip = IpHelper.ResolveHostname(hostnameOrIp, ref port);

if (session.OnlineSession)
{
api ??= new();
}

Api = api;
Session = session;
Port = port;
Hostname = hostnameOrIp;
gameState = GameState.Handshaking;
Settings = settings;
Api = api;
Session = session;
Port = port;
Hostname = hostnameOrIp;
GameState = GameState.Handshaking;
Settings = settings;
Channels = new (this);
}

/// <inheritdoc />
Expand Down Expand Up @@ -263,7 +267,7 @@ public Task<T> WaitForPacket<T>() where T : IPacket
}

/// <summary>
/// Waits until the client jumps into the Play <see cref="gameState" />
/// Waits until the client jumps into the Play <see cref="GameState" />
/// </summary>
/// <returns></returns>
public Task WaitForGame()
Expand All @@ -273,7 +277,7 @@ public Task WaitForGame()

internal void UpdateGameState(GameState next)
{
gameState = next;
GameState = next;

internalPacketHandler = next switch
{
Expand Down Expand Up @@ -379,18 +383,18 @@ private async Task ReceivePackets()
{
var buffer = stream!.ReadPacket();
var packetId = buffer.ReadVarInt();
var packetType = Data.Protocol.GetPacketType(PacketFlow.Clientbound, gameState, packetId);
var packetType = Data.Protocol.GetPacketType(PacketFlow.Clientbound, GameState, packetId);

if (bundlePackets)
{
bundledPackets.Enqueue((packetType, buffer));
}
else
{
await HandleIncomingPacket(packetType, buffer, gameState == GameState.Login);
await HandleIncomingPacket(packetType, buffer, GameState == GameState.Login);
}

if (gameState != GameState.Play)
if (GameState != GameState.Play)
{
await Task.Delay(1);
}
Expand Down Expand Up @@ -531,7 +535,7 @@ private Task InvokeReceivePacketAsync(IPacket packet)

/// <summary>
/// Requests the server status and closes the connection.
/// Works only when <see cref="gameState" /> is <see cref="Core.Common.Protocol.GameState.Status" />.
/// Works only when <see cref="GameState" /> is <see cref="Core.Common.Protocol.GameState.Status" />.
/// </summary>
/// <returns></returns>
public static async Task<ServerStatus> RequestServerStatus(
Expand Down Expand Up @@ -561,7 +565,7 @@ public static async Task<ServerStatus> RequestServerStatus(
client.On<StatusResponsePacket>(async packet =>
{
var json = packet.Response;
var response = ServerStatus.FromJToken(JToken.Parse(json), client.Data);
var response = ServerStatus.Parse(JToken.Parse(json), client.Data);
taskCompletionSource.TrySetResult(response);

// the server closes the connection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,44 @@
using MineSharp.Data.Protocol;

namespace MineSharp.Protocol.Packets.Clientbound.Configuration;
#pragma warning disable CS1591

/// <summary>
/// Plugin message packet
/// Plugin message during configuration phase
/// See https://wiki.vg/Protocol#Clientbound_Plugin_Message_.28configuration.29
/// </summary>
public class PluginMessagePacket : IPacket
{
/// <inheritdoc />
public PacketType Type => PacketType.CB_Configuration_CustomPayload;

/// <summary>
/// Create a new instance
/// </summary>
/// <param name="channelName"></param>
/// <param name="data"></param>
public PluginMessagePacket(string channelName, PacketBuffer data)
{
ChannelName = channelName;
Data = data;
}

/// <summary>
/// The name of the channel the data was sent
/// The channel of this message
/// </summary>
public string ChannelName { get; set; }

public required string ChannelName { get; init; }
/// <summary>
/// The message data
/// The data of this message
/// </summary>
public PacketBuffer Data { get; set; }

/// <inheritdoc />
public PacketType Type => PacketType.CB_Configuration_CustomPayload;
public required byte[] Data { get; init; }


/// <inheritdoc />
public void Write(PacketBuffer buffer, MinecraftData version)
{
buffer.WriteString(ChannelName);
buffer.WriteBytes(Data.GetBuffer());
buffer.WriteBytes(Data);
}

/// <inheritdoc />
public static IPacket Read(PacketBuffer buffer, MinecraftData version)
{
var channelName = buffer.ReadString();
var clone = new PacketBuffer(buffer.ReadBytes((int)buffer.ReadableBytes), version.Version.Protocol);
return new PluginMessagePacket(channelName, clone);
var data = buffer.RestBuffer();

return new PluginMessagePacket
{
ChannelName = channelName,
Data = data
};
}
}
#pragma warning restore CS1591
Loading
Loading